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