1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  * Snapshot Library Interfaces
30  *
31  * Consumers of topology data may use the interfaces in this file to open,
32  * snapshot and close a topology exported by FMRI scheme (hc, mem and cpu)
33  * builtin plugins and their helper modules.  A topology handle is obtained
34  * by calling topo_open().  Upon a successful return, the caller may use this
35  * handle to open a new snapshot.  Each snapshot is assigned a Universally
36  * Unique Identifier that in a future enchancement to the libtopo API will be
37  * used as the file locator in /var/fm/topo to persist new snapshots or lookup
38  * a previously captured snapshot.  topo_snap_hold() will capture the current
39  * system topology.  All consumers of the topo_hdl_t argument will be
40  * blocked from accessing the topology trees until the snapshot completes.
41  *
42  * A snapshot may be cleared by calling topo_snap_rele().  As with
43  * topo_snap_hold(), all topology accesses are blocked until the topology
44  * trees have been released and deallocated.
45  *
46  * Walker Library Interfaces
47  *
48  * Once a snapshot has been taken with topo_snap_hold(), topo_hdl_t holders
49  * may initiate topology tree walks on a scheme-tree basis.  topo_walk_init()
50  * will initiate the data structures required to walk any one one of the
51  * FMRI scheme trees.  The walker data structure, topo_walk_t, is an opaque
52  * handle passed to topo_walk_step to begin the walk.  At each node in the
53  * topology tree, a callback function is called with access to the node at
54  * which our current walk falls.  The callback function is passed in during
55  * calls to topo_walk_init() and used throughout the walk_step of the
56  * scheme tree.  At any time, the callback may terminate the walk by returning
57  * TOPO_WALK_TERMINATE or TOPO_WALK_ERR.  TOPO_WALK_NEXT will continue the walk.
58  *
59  * The type of walk through the tree may be sibling first or child first by
60  * respectively passing in TOPO_WALK_SIBLING or TOPO_WALK_CHILD to
61  * the topo_walk_step() function.  Topology nodes
62  * associated with an outstanding walk are held in place and will not be
63  * deallocated until the walk through that node completes.
64  *
65  * Once the walk has terminated, the walking process should call
66  * topo_walk_fini() to clean-up resources created in topo_walk_init()
67  * and release nodes that may be still held.
68  */
69 
70 #include <alloca.h>
71 #include <ctype.h>
72 #include <pthread.h>
73 #include <limits.h>
74 #include <assert.h>
75 #include <fcntl.h>
76 #include <smbios.h>
77 #include <sys/param.h>
78 #include <sys/types.h>
79 #include <sys/stat.h>
80 #include <sys/systeminfo.h>
81 #include <sys/utsname.h>
82 #include <uuid/uuid.h>
83 
84 #include <fm/libtopo.h>
85 
86 #include <topo_alloc.h>
87 #include <topo_builtin.h>
88 #include <topo_string.h>
89 #include <topo_error.h>
90 #include <topo_subr.h>
91 
92 static void topo_snap_destroy(topo_hdl_t *);
93 
94 static topo_hdl_t *
95 set_open_errno(topo_hdl_t *thp, int *errp, int err)
96 {
97 	if (thp != NULL) {
98 		topo_close(thp);
99 	}
100 	if (errp != NULL)
101 		*errp = err;
102 	return (NULL);
103 }
104 
105 topo_hdl_t *
106 topo_open(int version, const char *rootdir, int *errp)
107 {
108 	topo_hdl_t *thp = NULL;
109 	topo_alloc_t *tap;
110 
111 	char platform[MAXNAMELEN];
112 	char isa[MAXNAMELEN];
113 	struct utsname uts;
114 	struct stat st;
115 
116 	smbios_hdl_t *shp;
117 	smbios_system_t s1;
118 	smbios_info_t s2;
119 	id_t id;
120 
121 	char *dbflags, *dbout;
122 
123 	if (version != TOPO_VERSION)
124 		return (set_open_errno(thp, errp, ETOPO_HDL_ABIVER));
125 
126 	if (rootdir != NULL && stat(rootdir, &st) < 0)
127 		return (set_open_errno(thp, errp, ETOPO_HDL_INVAL));
128 
129 	if ((thp = topo_zalloc(sizeof (topo_hdl_t), 0)) == NULL)
130 		return (set_open_errno(thp, errp, ETOPO_NOMEM));
131 
132 	(void) pthread_mutex_init(&thp->th_lock, NULL);
133 
134 	if ((tap = topo_zalloc(sizeof (topo_alloc_t), 0)) == NULL)
135 		return (set_open_errno(thp, errp, ETOPO_NOMEM));
136 
137 	/*
138 	 * Install default allocators
139 	 */
140 	tap->ta_flags = 0;
141 	tap->ta_alloc = topo_alloc;
142 	tap->ta_zalloc = topo_zalloc;
143 	tap->ta_free = topo_free;
144 	tap->ta_nvops.nv_ao_alloc = topo_nv_alloc;
145 	tap->ta_nvops.nv_ao_free = topo_nv_free;
146 	(void) nv_alloc_init(&tap->ta_nva, &tap->ta_nvops);
147 	thp->th_alloc = tap;
148 
149 	if ((thp->th_modhash = topo_modhash_create(thp)) == NULL)
150 		return (set_open_errno(thp, errp, ETOPO_NOMEM));
151 
152 	/*
153 	 * Set-up system information and search paths for modules
154 	 * and topology map files
155 	 */
156 	if (rootdir == NULL) {
157 		rootdir = topo_hdl_strdup(thp, "/");
158 		thp->th_rootdir = (char *)rootdir;
159 	} else {
160 		int len;
161 		char *rpath;
162 
163 		len = strlen(rootdir);
164 		if (len >= PATH_MAX)
165 			return (set_open_errno(thp, errp, EINVAL));
166 
167 		if (rootdir[len - 1] != '/') {
168 			rpath = alloca(len + 2);
169 			(void) snprintf(rpath, len + 2, "%s/", rootdir);
170 		} else {
171 			rpath = (char *)rootdir;
172 		}
173 		thp->th_rootdir = topo_hdl_strdup(thp, rpath);
174 	}
175 
176 	platform[0] = '\0';
177 	isa[0] = '\0';
178 	(void) sysinfo(SI_PLATFORM, platform, sizeof (platform));
179 	(void) sysinfo(SI_ARCHITECTURE, isa, sizeof (isa));
180 	(void) uname(&uts);
181 	thp->th_platform = topo_hdl_strdup(thp, platform);
182 	thp->th_isa = topo_hdl_strdup(thp, isa);
183 	thp->th_machine = topo_hdl_strdup(thp, uts.machine);
184 	if ((shp = smbios_open(NULL, SMB_VERSION, 0, NULL)) != NULL) {
185 		if ((id = smbios_info_system(shp, &s1)) != SMB_ERR &&
186 		    smbios_info_common(shp, id, &s2) != SMB_ERR) {
187 
188 			if (strcmp(s2.smbi_product, SMB_DEFAULT1) != 0 &&
189 			    strcmp(s2.smbi_product, SMB_DEFAULT2) != 0) {
190 				thp->th_product = topo_cleanup_auth_str(thp,
191 				    (char *)s2.smbi_product);
192 			}
193 		}
194 		smbios_close(shp);
195 	} else {
196 		thp->th_product = topo_hdl_strdup(thp, thp->th_platform);
197 	}
198 
199 	if (thp->th_rootdir == NULL || thp->th_platform == NULL ||
200 	    thp->th_machine == NULL)
201 		return (set_open_errno(thp, errp, ETOPO_NOMEM));
202 
203 	dbflags	 = getenv("TOPO_DEBUG");
204 	dbout = getenv("TOPO_DEBUG_OUT");
205 	if (dbflags != NULL)
206 		topo_debug_set(thp, dbflags, dbout);
207 
208 	if (topo_builtin_create(thp, thp->th_rootdir) != 0) {
209 		topo_dprintf(thp, TOPO_DBG_ERR,
210 		    "failed to load builtin modules: %s\n",
211 		    topo_hdl_errmsg(thp));
212 		topo_close(thp);
213 		return (NULL);
214 	}
215 
216 	return (thp);
217 }
218 
219 void
220 topo_close(topo_hdl_t *thp)
221 {
222 	ttree_t *tp;
223 
224 	topo_hdl_lock(thp);
225 	if (thp->th_platform != NULL)
226 		topo_hdl_strfree(thp, thp->th_platform);
227 	if (thp->th_isa != NULL)
228 		topo_hdl_strfree(thp, thp->th_isa);
229 	if (thp->th_machine != NULL)
230 		topo_hdl_strfree(thp, thp->th_machine);
231 	if (thp->th_product != NULL)
232 		topo_hdl_strfree(thp, thp->th_product);
233 	if (thp->th_rootdir != NULL)
234 		topo_hdl_strfree(thp, thp->th_rootdir);
235 	if (thp->th_ipmi != NULL)
236 		ipmi_close(thp->th_ipmi);
237 
238 	/*
239 	 * Clean-up snapshot
240 	 */
241 	topo_snap_destroy(thp);
242 
243 	/*
244 	 * Clean-up trees
245 	 */
246 	while ((tp = topo_list_next(&thp->th_trees)) != NULL) {
247 		topo_list_delete(&thp->th_trees, tp);
248 		topo_tree_destroy(tp);
249 	}
250 
251 	/*
252 	 * Unload all plugins
253 	 */
254 	topo_modhash_unload_all(thp);
255 
256 	if (thp->th_modhash != NULL)
257 		topo_modhash_destroy(thp);
258 	if (thp->th_alloc != NULL)
259 		topo_free(thp->th_alloc, sizeof (topo_alloc_t));
260 
261 	topo_hdl_unlock(thp);
262 
263 	topo_free(thp, sizeof (topo_hdl_t));
264 }
265 
266 static char *
267 topo_snap_create(topo_hdl_t *thp, int *errp)
268 {
269 	uuid_t uuid;
270 	char *ustr = NULL;
271 
272 	topo_hdl_lock(thp);
273 	if (thp->th_uuid != NULL) {
274 		*errp = ETOPO_HDL_UUID;
275 		topo_hdl_unlock(thp);
276 		return (NULL);
277 	}
278 
279 	if ((thp->th_uuid = topo_hdl_zalloc(thp, TOPO_UUID_SIZE)) == NULL) {
280 		*errp = ETOPO_NOMEM;
281 		topo_dprintf(thp, TOPO_DBG_ERR, "unable to allocate uuid: %s\n",
282 		    topo_strerror(*errp));
283 		topo_hdl_unlock(thp);
284 		return (NULL);
285 	}
286 
287 	uuid_generate(uuid);
288 	uuid_unparse(uuid, thp->th_uuid);
289 
290 	if (topo_tree_enum_all(thp) < 0) {
291 		topo_dprintf(thp, TOPO_DBG_ERR, "enumeration failure: %s\n",
292 		    topo_hdl_errmsg(thp));
293 		if (topo_hdl_errno(thp) == ETOPO_ENUM_FATAL) {
294 			*errp = thp->th_errno;
295 			topo_hdl_unlock(thp);
296 			return (NULL);
297 		}
298 	}
299 
300 	if (thp->th_ipmi != NULL &&
301 	    ipmi_sdr_changed(thp->th_ipmi) &&
302 	    ipmi_sdr_refresh(thp->th_ipmi) != 0) {
303 		topo_dprintf(thp, TOPO_DBG_ERR,
304 		    "failed to refresh IPMI sdr repository: %s\n",
305 		    ipmi_errmsg(thp->th_ipmi));
306 	}
307 
308 	if ((ustr = topo_hdl_strdup(thp, thp->th_uuid)) == NULL)
309 		*errp = ETOPO_NOMEM;
310 
311 	thp->th_di = DI_NODE_NIL;
312 	thp->th_pi = DI_PROM_HANDLE_NIL;
313 
314 	topo_hdl_unlock(thp);
315 
316 	return (ustr);
317 }
318 
319 /*ARGSUSED*/
320 static char *
321 topo_snap_log_create(topo_hdl_t *thp, const char *uuid, int *errp)
322 {
323 	return ((char *)uuid);
324 }
325 
326 /*
327  * Return snapshot id
328  */
329 char *
330 topo_snap_hold(topo_hdl_t *thp, const char *uuid, int *errp)
331 {
332 	if (thp == NULL)
333 		return (NULL);
334 
335 	if (uuid == NULL)
336 		return (topo_snap_create(thp, errp));
337 	else
338 		return (topo_snap_log_create(thp, uuid, errp));
339 }
340 
341 /*ARGSUSED*/
342 static int
343 topo_walk_destroy(topo_hdl_t *thp, tnode_t *node, void *notused)
344 {
345 	tnode_t *cnode;
346 
347 	cnode = topo_child_first(node);
348 
349 	if (cnode != NULL)
350 		return (TOPO_WALK_NEXT);
351 
352 	topo_node_unbind(node);
353 
354 	return (TOPO_WALK_NEXT);
355 }
356 
357 static void
358 topo_snap_destroy(topo_hdl_t *thp)
359 {
360 	int i;
361 	ttree_t *tp;
362 	topo_walk_t *twp;
363 	tnode_t *root;
364 	topo_nodehash_t *nhp;
365 	topo_mod_t *mod;
366 
367 	for (tp = topo_list_next(&thp->th_trees); tp != NULL;
368 	    tp = topo_list_next(tp)) {
369 
370 		root = tp->tt_root;
371 		twp = tp->tt_walk;
372 		/*
373 		 * Clean-up tree nodes from the bottom-up
374 		 */
375 		if ((twp->tw_node = topo_child_first(root)) != NULL) {
376 			twp->tw_cb = topo_walk_destroy;
377 			topo_node_hold(root);
378 			topo_node_hold(twp->tw_node); /* released at walk end */
379 			(void) topo_walk_bottomup(twp, TOPO_WALK_CHILD);
380 			topo_node_rele(root);
381 		}
382 
383 		/*
384 		 * Tidy-up the root node
385 		 */
386 		while ((nhp = topo_list_next(&root->tn_children)) != NULL) {
387 			for (i = 0; i < nhp->th_arrlen; i++) {
388 				assert(nhp->th_nodearr[i] == NULL);
389 			}
390 			mod = nhp->th_enum;
391 			topo_mod_strfree(mod, nhp->th_name);
392 			topo_mod_free(mod, nhp->th_nodearr,
393 			    nhp->th_arrlen * sizeof (tnode_t *));
394 			topo_list_delete(&root->tn_children, nhp);
395 			topo_mod_free(mod, nhp, sizeof (topo_nodehash_t));
396 			topo_mod_rele(mod);
397 		}
398 
399 	}
400 
401 	if (thp->th_uuid != NULL) {
402 		topo_hdl_free(thp, thp->th_uuid, TOPO_UUID_SIZE);
403 		thp->th_uuid = NULL;
404 	}
405 }
406 
407 void
408 topo_snap_release(topo_hdl_t *thp)
409 {
410 	if (thp == NULL)
411 		return;
412 
413 	topo_hdl_lock(thp);
414 	topo_snap_destroy(thp);
415 	topo_hdl_unlock(thp);
416 }
417 
418 topo_walk_t *
419 topo_walk_init(topo_hdl_t *thp, const char *scheme, topo_walk_cb_t cb_f,
420     void *pdata, int *errp)
421 {
422 	ttree_t *tp;
423 	topo_walk_t *wp;
424 
425 	for (tp = topo_list_next(&thp->th_trees); tp != NULL;
426 	    tp = topo_list_next(tp)) {
427 		if (strcmp(scheme, tp->tt_scheme) == 0) {
428 
429 			/*
430 			 * Hold the root node and start walk at the first
431 			 * child node
432 			 */
433 			assert(tp->tt_root != NULL);
434 
435 			if ((wp = topo_node_walk_init(thp, NULL, tp->tt_root,
436 			    cb_f, pdata, errp)) == NULL) /* errp set */
437 				return (NULL);
438 
439 			return (wp);
440 		}
441 	}
442 
443 	*errp = ETOPO_WALK_NOTFOUND;
444 	return (NULL);
445 }
446 
447 static int
448 step_child(tnode_t *cnp, topo_walk_t *wp, int flag, int bottomup)
449 {
450 	int status;
451 	tnode_t *nnp;
452 
453 	nnp = topo_child_first(cnp);
454 
455 	if (nnp == NULL) {
456 		topo_dprintf(wp->tw_thp, TOPO_DBG_WALK,
457 		    "step_child: TOPO_WALK_TERMINATE for %s=%d\n",
458 		    cnp->tn_name, cnp->tn_instance);
459 		return (TOPO_WALK_TERMINATE);
460 	}
461 
462 	topo_dprintf(wp->tw_thp, TOPO_DBG_WALK,
463 	    "step_child: walk through node %s=%d to %s=%d\n",
464 	    cnp->tn_name, cnp->tn_instance, nnp->tn_name, nnp->tn_instance);
465 
466 	topo_node_hold(nnp); /* released on return from walk_step */
467 	wp->tw_node = nnp;
468 	if (bottomup == 1)
469 		status = topo_walk_bottomup(wp, flag);
470 	else
471 		status = topo_walk_step(wp, flag);
472 
473 	return (status);
474 }
475 
476 static int
477 step_sibling(tnode_t *cnp, topo_walk_t *wp, int flag, int bottomup)
478 {
479 	int status;
480 	tnode_t *nnp;
481 
482 	nnp = topo_child_next(cnp->tn_parent, cnp);
483 
484 	if (nnp == NULL) {
485 		topo_dprintf(wp->tw_thp, TOPO_DBG_WALK,
486 		    "step_sibling: TOPO_WALK_TERMINATE for %s=%d\n",
487 		    cnp->tn_name, cnp->tn_instance);
488 		return (TOPO_WALK_TERMINATE);
489 	}
490 
491 	topo_dprintf(wp->tw_thp, TOPO_DBG_WALK,
492 	    "step_sibling: through sibling node %s=%d to %s=%d\n",
493 	    cnp->tn_name, cnp->tn_instance, nnp->tn_name, nnp->tn_instance);
494 
495 	topo_node_hold(nnp); /* released on return from walk_step */
496 	wp->tw_node = nnp;
497 	if (bottomup == 1)
498 		status = topo_walk_bottomup(wp, flag);
499 	else
500 		status = topo_walk_step(wp, flag);
501 
502 	return (status);
503 }
504 
505 int
506 topo_walk_byid(topo_walk_t *wp, const char *name, topo_instance_t inst)
507 {
508 	int status;
509 	tnode_t *nnp, *cnp;
510 
511 	cnp = wp->tw_node;
512 	nnp = topo_node_lookup(cnp, name, inst);
513 	if (nnp == NULL)
514 		return (TOPO_WALK_TERMINATE);
515 
516 	topo_node_hold(nnp);
517 	wp->tw_node = nnp;
518 	if (wp->tw_mod != NULL)
519 		status = wp->tw_cb(wp->tw_mod, nnp, wp->tw_pdata);
520 	else
521 		status = wp->tw_cb(wp->tw_thp, nnp, wp->tw_pdata);
522 	topo_node_rele(nnp);
523 	wp->tw_node = cnp;
524 
525 	return (status);
526 }
527 
528 int
529 topo_walk_bysibling(topo_walk_t *wp, const char *name, topo_instance_t inst)
530 {
531 	int status;
532 	tnode_t *cnp, *pnp;
533 
534 	cnp = wp->tw_node;
535 	pnp = topo_node_parent(cnp);
536 	assert(pnp != NULL);
537 
538 	topo_node_hold(pnp);
539 	wp->tw_node = pnp;
540 	status = topo_walk_byid(wp, name, inst);
541 	topo_node_rele(pnp);
542 	wp->tw_node = cnp;
543 
544 	return (status);
545 }
546 
547 int
548 topo_walk_step(topo_walk_t *wp, int flag)
549 {
550 	int status;
551 	tnode_t *cnp = wp->tw_node;
552 
553 	if (flag != TOPO_WALK_CHILD && flag != TOPO_WALK_SIBLING) {
554 		topo_node_rele(cnp);
555 		return (TOPO_WALK_ERR);
556 	}
557 
558 	/*
559 	 * No more nodes to walk
560 	 */
561 	if (cnp == NULL) {
562 		topo_dprintf(wp->tw_thp, TOPO_DBG_WALK,
563 		    "walk_step terminated\n");
564 		topo_node_rele(cnp);
565 		return (TOPO_WALK_TERMINATE);
566 	}
567 
568 
569 	if (wp->tw_mod != NULL)
570 		status = wp->tw_cb(wp->tw_mod, cnp, wp->tw_pdata);
571 	else
572 		status = wp->tw_cb(wp->tw_thp, cnp, wp->tw_pdata);
573 
574 	/*
575 	 * Walker callback says we're done
576 	 */
577 	if (status != TOPO_WALK_NEXT) {
578 		topo_node_rele(cnp);
579 		return (status);
580 	}
581 
582 	if (flag == TOPO_WALK_CHILD)
583 		status = step_child(cnp, wp, flag, 0);
584 	else
585 		status = step_sibling(cnp, wp, flag, 0);
586 
587 	/*
588 	 * No more nodes in this hash, skip to next node hash by stepping
589 	 * to next sibling (child-first walk) or next child (sibling-first
590 	 * walk).
591 	 */
592 	if (status == TOPO_WALK_TERMINATE) {
593 		if (flag == TOPO_WALK_CHILD)
594 			status = step_sibling(cnp, wp, flag, 0);
595 		else
596 			status = step_child(cnp, wp, flag, 0);
597 	}
598 
599 	topo_node_rele(cnp); /* done with current node */
600 
601 	return (status);
602 }
603 
604 void
605 topo_walk_fini(topo_walk_t *wp)
606 {
607 	if (wp == NULL)
608 		return;
609 
610 	topo_node_rele(wp->tw_root);
611 
612 	topo_hdl_free(wp->tw_thp, wp, sizeof (topo_walk_t));
613 }
614 
615 int
616 topo_walk_bottomup(topo_walk_t *wp, int flag)
617 {
618 	int status;
619 	tnode_t *cnp;
620 
621 	if (wp == NULL)
622 		return (TOPO_WALK_ERR);
623 
624 	cnp = wp->tw_node;
625 	if (flag != TOPO_WALK_CHILD && flag != TOPO_WALK_SIBLING) {
626 		topo_node_rele(cnp);
627 		return (TOPO_WALK_ERR);
628 	}
629 
630 	/*
631 	 * End of the line
632 	 */
633 	if (cnp == NULL) {
634 		topo_dprintf(wp->tw_thp, TOPO_DBG_WALK,
635 		    "walk_bottomup terminated\n");
636 		topo_node_rele(cnp);
637 		return (TOPO_WALK_TERMINATE);
638 	}
639 
640 	topo_dprintf(wp->tw_thp, TOPO_DBG_WALK,
641 	    "%s walk_bottomup through node %s=%d\n",
642 	    (flag == TOPO_WALK_CHILD ? "TOPO_WALK_CHILD" : "TOPO_WALK_SIBLING"),
643 	    cnp->tn_name, cnp->tn_instance);
644 
645 	if (flag == TOPO_WALK_CHILD)
646 		status = step_child(cnp, wp, flag, 1);
647 	else
648 		status = step_sibling(cnp, wp, flag, 1);
649 
650 	/*
651 	 * At a leaf, run the callback
652 	 */
653 	if (status == TOPO_WALK_TERMINATE) {
654 		if ((status = wp->tw_cb(wp->tw_thp, cnp, wp->tw_pdata))
655 		    != TOPO_WALK_NEXT) {
656 			topo_node_rele(cnp);
657 			return (status);
658 		}
659 	}
660 
661 	/*
662 	 * Try next child or sibling
663 	 */
664 	if (status == TOPO_WALK_NEXT) {
665 		if (flag == TOPO_WALK_CHILD)
666 			status = step_sibling(cnp, wp, flag, 1);
667 		else
668 			status = step_child(cnp, wp, flag, 1);
669 	}
670 
671 	topo_node_rele(cnp); /* done with current node */
672 
673 	return (status);
674 }
675