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 #include <sys/fm/protocol.h> 84 85 #include <topo_alloc.h> 86 #include <topo_builtin.h> 87 #include <topo_string.h> 88 #include <topo_error.h> 89 #include <topo_subr.h> 90 91 static void topo_snap_destroy(topo_hdl_t *); 92 93 static topo_hdl_t * 94 set_open_errno(topo_hdl_t *thp, int *errp, int err) 95 { 96 if (thp != NULL) { 97 topo_close(thp); 98 } 99 if (errp != NULL) 100 *errp = err; 101 return (NULL); 102 } 103 104 topo_hdl_t * 105 topo_open(int version, const char *rootdir, int *errp) 106 { 107 topo_hdl_t *thp = NULL; 108 topo_alloc_t *tap; 109 110 char platform[MAXNAMELEN]; 111 char isa[MAXNAMELEN]; 112 struct utsname uts; 113 struct stat st; 114 115 smbios_hdl_t *shp; 116 smbios_system_t s1; 117 smbios_info_t s2; 118 id_t id; 119 120 char *dbflags, *dbout; 121 122 if (version != TOPO_VERSION) 123 return (set_open_errno(thp, errp, ETOPO_HDL_ABIVER)); 124 125 if (rootdir != NULL && stat(rootdir, &st) < 0) 126 return (set_open_errno(thp, errp, ETOPO_HDL_INVAL)); 127 128 if ((thp = topo_zalloc(sizeof (topo_hdl_t), 0)) == NULL) 129 return (set_open_errno(thp, errp, ETOPO_NOMEM)); 130 131 (void) pthread_mutex_init(&thp->th_lock, NULL); 132 133 if ((tap = topo_zalloc(sizeof (topo_alloc_t), 0)) == NULL) 134 return (set_open_errno(thp, errp, ETOPO_NOMEM)); 135 136 /* 137 * Install default allocators 138 */ 139 tap->ta_flags = 0; 140 tap->ta_alloc = topo_alloc; 141 tap->ta_zalloc = topo_zalloc; 142 tap->ta_free = topo_free; 143 tap->ta_nvops.nv_ao_alloc = topo_nv_alloc; 144 tap->ta_nvops.nv_ao_free = topo_nv_free; 145 (void) nv_alloc_init(&tap->ta_nva, &tap->ta_nvops); 146 thp->th_alloc = tap; 147 148 if ((thp->th_modhash = topo_modhash_create(thp)) == NULL) 149 return (set_open_errno(thp, errp, ETOPO_NOMEM)); 150 151 /* 152 * Set-up system information and search paths for modules 153 * and topology map files 154 */ 155 if (rootdir == NULL) { 156 rootdir = topo_hdl_strdup(thp, "/"); 157 thp->th_rootdir = (char *)rootdir; 158 } else { 159 int len; 160 char *rpath; 161 162 len = strlen(rootdir); 163 if (len >= PATH_MAX) 164 return (set_open_errno(thp, errp, EINVAL)); 165 166 if (rootdir[len - 1] != '/') { 167 rpath = alloca(len + 2); 168 (void) snprintf(rpath, len + 2, "%s/", rootdir); 169 } else { 170 rpath = (char *)rootdir; 171 } 172 thp->th_rootdir = topo_hdl_strdup(thp, rpath); 173 } 174 175 platform[0] = '\0'; 176 isa[0] = '\0'; 177 (void) sysinfo(SI_PLATFORM, platform, sizeof (platform)); 178 (void) sysinfo(SI_ARCHITECTURE, isa, sizeof (isa)); 179 (void) uname(&uts); 180 thp->th_platform = topo_hdl_strdup(thp, platform); 181 thp->th_isa = topo_hdl_strdup(thp, isa); 182 thp->th_machine = topo_hdl_strdup(thp, uts.machine); 183 if ((shp = smbios_open(NULL, SMB_VERSION, 0, NULL)) != NULL) { 184 if ((id = smbios_info_system(shp, &s1)) != SMB_ERR && 185 smbios_info_common(shp, id, &s2) != SMB_ERR) { 186 187 if (strcmp(s2.smbi_product, SMB_DEFAULT1) != 0 && 188 strcmp(s2.smbi_product, SMB_DEFAULT2) != 0) { 189 thp->th_product = topo_cleanup_auth_str(thp, 190 s2.smbi_product); 191 } 192 } 193 smbios_close(shp); 194 } else { 195 thp->th_product = topo_hdl_strdup(thp, thp->th_platform); 196 } 197 198 if (thp->th_rootdir == NULL || thp->th_platform == NULL || 199 thp->th_machine == NULL) 200 return (set_open_errno(thp, errp, ETOPO_NOMEM)); 201 202 dbflags = getenv("TOPO_DEBUG"); 203 dbout = getenv("TOPO_DEBUG_OUT"); 204 if (dbflags != NULL) 205 topo_debug_set(thp, dbflags, dbout); 206 207 if (topo_builtin_create(thp, thp->th_rootdir) != 0) { 208 topo_dprintf(thp, TOPO_DBG_ERR, 209 "failed to load builtin modules: %s\n", 210 topo_hdl_errmsg(thp)); 211 topo_close(thp); 212 return (NULL); 213 } 214 215 return (thp); 216 } 217 218 void 219 topo_close(topo_hdl_t *thp) 220 { 221 ttree_t *tp; 222 223 topo_hdl_lock(thp); 224 if (thp->th_platform != NULL) 225 topo_hdl_strfree(thp, thp->th_platform); 226 if (thp->th_isa != NULL) 227 topo_hdl_strfree(thp, thp->th_isa); 228 if (thp->th_machine != NULL) 229 topo_hdl_strfree(thp, thp->th_machine); 230 if (thp->th_product != NULL) 231 topo_hdl_strfree(thp, thp->th_product); 232 if (thp->th_rootdir != NULL) 233 topo_hdl_strfree(thp, thp->th_rootdir); 234 if (thp->th_ipmi != NULL) 235 ipmi_close(thp->th_ipmi); 236 237 /* 238 * Clean-up snapshot 239 */ 240 topo_snap_destroy(thp); 241 242 /* 243 * Clean-up trees 244 */ 245 while ((tp = topo_list_next(&thp->th_trees)) != NULL) { 246 topo_list_delete(&thp->th_trees, tp); 247 topo_tree_destroy(tp); 248 } 249 250 /* 251 * Unload all plugins 252 */ 253 topo_modhash_unload_all(thp); 254 255 if (thp->th_modhash != NULL) 256 topo_modhash_destroy(thp); 257 if (thp->th_alloc != NULL) 258 topo_free(thp->th_alloc, sizeof (topo_alloc_t)); 259 260 topo_hdl_unlock(thp); 261 262 topo_free(thp, sizeof (topo_hdl_t)); 263 } 264 265 static char * 266 topo_snap_create(topo_hdl_t *thp, int *errp) 267 { 268 uuid_t uuid; 269 char *ustr = NULL; 270 271 topo_hdl_lock(thp); 272 if (thp->th_uuid != NULL) { 273 *errp = ETOPO_HDL_UUID; 274 topo_hdl_unlock(thp); 275 return (NULL); 276 } 277 278 if ((thp->th_uuid = topo_hdl_zalloc(thp, TOPO_UUID_SIZE)) == NULL) { 279 *errp = ETOPO_NOMEM; 280 topo_dprintf(thp, TOPO_DBG_ERR, "unable to allocate uuid: %s\n", 281 topo_strerror(*errp)); 282 topo_hdl_unlock(thp); 283 return (NULL); 284 } 285 286 uuid_generate(uuid); 287 uuid_unparse(uuid, thp->th_uuid); 288 289 if (topo_tree_enum_all(thp) < 0) { 290 topo_dprintf(thp, TOPO_DBG_ERR, "enumeration failure: %s\n", 291 topo_hdl_errmsg(thp)); 292 if (topo_hdl_errno(thp) == ETOPO_ENUM_FATAL) { 293 *errp = thp->th_errno; 294 topo_hdl_unlock(thp); 295 return (NULL); 296 } 297 } 298 299 if (thp->th_ipmi != NULL && 300 ipmi_sdr_changed(thp->th_ipmi) && 301 ipmi_sdr_refresh(thp->th_ipmi) != 0) { 302 topo_dprintf(thp, TOPO_DBG_ERR, 303 "failed to refresh IPMI sdr repository: %s\n", 304 ipmi_errmsg(thp->th_ipmi)); 305 } 306 307 if ((ustr = topo_hdl_strdup(thp, thp->th_uuid)) == NULL) 308 *errp = ETOPO_NOMEM; 309 310 thp->th_di = DI_NODE_NIL; 311 thp->th_pi = DI_PROM_HANDLE_NIL; 312 313 topo_hdl_unlock(thp); 314 315 return (ustr); 316 } 317 318 /*ARGSUSED*/ 319 static char * 320 topo_snap_log_create(topo_hdl_t *thp, const char *uuid, int *errp) 321 { 322 return ((char *)uuid); 323 } 324 325 /*ARGSUSED*/ 326 static int 327 fac_walker(topo_hdl_t *thp, tnode_t *node, void *arg) 328 { 329 int err; 330 nvlist_t *out; 331 332 if (topo_method_supported(node, TOPO_METH_FAC_ENUM, 0)) { 333 /* 334 * If the facility enumeration method fails, note the failure, 335 * but continue on with the walk. 336 */ 337 if (topo_method_invoke(node, TOPO_METH_FAC_ENUM, 0, NULL, &out, 338 &err) != 0) { 339 topo_dprintf(thp, TOPO_DBG_ERR, 340 "facility enumeration method failed on node %s=%d " 341 "(%s)\n", topo_node_name(node), 342 topo_node_instance(node), topo_strerror(err)); 343 } 344 } 345 return (TOPO_WALK_NEXT); 346 } 347 348 /* 349 * Return snapshot id 350 */ 351 char * 352 topo_snap_hold(topo_hdl_t *thp, const char *uuid, int *errp) 353 { 354 topo_walk_t *twp; 355 356 if (thp == NULL) 357 return (NULL); 358 359 if (uuid == NULL) { 360 char *ret; 361 362 ret = topo_snap_create(thp, errp); 363 364 /* 365 * Now walk the tree and invoke any facility enumeration methods 366 */ 367 if (ret != NULL) { 368 if ((twp = topo_walk_init(thp, FM_FMRI_SCHEME_HC, 369 fac_walker, (void *)0, errp)) == NULL) { 370 return (ret); 371 } 372 if (topo_walk_step(twp, TOPO_WALK_CHILD) 373 != TOPO_WALK_ERR) { 374 topo_walk_fini(twp); 375 return (ret); 376 } 377 topo_walk_fini(twp); 378 } 379 return (ret); 380 } 381 return (topo_snap_log_create(thp, uuid, errp)); 382 } 383 384 /*ARGSUSED*/ 385 static int 386 topo_walk_destroy(topo_hdl_t *thp, tnode_t *node, void *notused) 387 { 388 tnode_t *cnode; 389 390 cnode = topo_child_first(node); 391 392 if (cnode != NULL) 393 return (TOPO_WALK_NEXT); 394 395 topo_node_unbind(node); 396 397 return (TOPO_WALK_NEXT); 398 } 399 400 static void 401 topo_snap_destroy(topo_hdl_t *thp) 402 { 403 int i; 404 ttree_t *tp; 405 topo_walk_t *twp; 406 tnode_t *root; 407 topo_nodehash_t *nhp; 408 topo_mod_t *mod; 409 410 for (tp = topo_list_next(&thp->th_trees); tp != NULL; 411 tp = topo_list_next(tp)) { 412 413 root = tp->tt_root; 414 twp = tp->tt_walk; 415 /* 416 * Clean-up tree nodes from the bottom-up 417 */ 418 if ((twp->tw_node = topo_child_first(root)) != NULL) { 419 twp->tw_cb = topo_walk_destroy; 420 topo_node_hold(root); 421 topo_node_hold(twp->tw_node); /* released at walk end */ 422 (void) topo_walk_bottomup(twp, TOPO_WALK_CHILD); 423 topo_node_rele(root); 424 } 425 426 /* 427 * Tidy-up the root node 428 */ 429 while ((nhp = topo_list_next(&root->tn_children)) != NULL) { 430 for (i = 0; i < nhp->th_arrlen; i++) { 431 assert(nhp->th_nodearr[i] == NULL); 432 } 433 mod = nhp->th_enum; 434 topo_mod_strfree(mod, nhp->th_name); 435 topo_mod_free(mod, nhp->th_nodearr, 436 nhp->th_arrlen * sizeof (tnode_t *)); 437 topo_list_delete(&root->tn_children, nhp); 438 topo_mod_free(mod, nhp, sizeof (topo_nodehash_t)); 439 topo_mod_rele(mod); 440 } 441 442 } 443 444 if (thp->th_uuid != NULL) { 445 topo_hdl_free(thp, thp->th_uuid, TOPO_UUID_SIZE); 446 thp->th_uuid = NULL; 447 } 448 } 449 450 void 451 topo_snap_release(topo_hdl_t *thp) 452 { 453 if (thp == NULL) 454 return; 455 456 topo_hdl_lock(thp); 457 topo_snap_destroy(thp); 458 topo_hdl_unlock(thp); 459 } 460 461 topo_walk_t * 462 topo_walk_init(topo_hdl_t *thp, const char *scheme, topo_walk_cb_t cb_f, 463 void *pdata, int *errp) 464 { 465 ttree_t *tp; 466 topo_walk_t *wp; 467 468 for (tp = topo_list_next(&thp->th_trees); tp != NULL; 469 tp = topo_list_next(tp)) { 470 if (strcmp(scheme, tp->tt_scheme) == 0) { 471 472 /* 473 * Hold the root node and start walk at the first 474 * child node 475 */ 476 assert(tp->tt_root != NULL); 477 478 if ((wp = topo_node_walk_init(thp, NULL, tp->tt_root, 479 cb_f, pdata, errp)) == NULL) /* errp set */ 480 return (NULL); 481 482 return (wp); 483 } 484 } 485 486 *errp = ETOPO_WALK_NOTFOUND; 487 return (NULL); 488 } 489 490 static int 491 step_child(tnode_t *cnp, topo_walk_t *wp, int flag, int bottomup) 492 { 493 int status; 494 tnode_t *nnp; 495 496 nnp = topo_child_first(cnp); 497 498 if (nnp == NULL) { 499 topo_dprintf(wp->tw_thp, TOPO_DBG_WALK, 500 "step_child: TOPO_WALK_TERMINATE for %s=%d\n", 501 cnp->tn_name, cnp->tn_instance); 502 return (TOPO_WALK_TERMINATE); 503 } 504 505 topo_dprintf(wp->tw_thp, TOPO_DBG_WALK, 506 "step_child: walk through node %s=%d to %s=%d\n", 507 cnp->tn_name, cnp->tn_instance, nnp->tn_name, nnp->tn_instance); 508 509 topo_node_hold(nnp); /* released on return from walk_step */ 510 wp->tw_node = nnp; 511 if (bottomup == 1) 512 status = topo_walk_bottomup(wp, flag); 513 else 514 status = topo_walk_step(wp, flag); 515 516 return (status); 517 } 518 519 static int 520 step_sibling(tnode_t *cnp, topo_walk_t *wp, int flag, int bottomup) 521 { 522 int status; 523 tnode_t *nnp; 524 525 nnp = topo_child_next(cnp->tn_parent, cnp); 526 527 if (nnp == NULL) { 528 topo_dprintf(wp->tw_thp, TOPO_DBG_WALK, 529 "step_sibling: TOPO_WALK_TERMINATE for %s=%d\n", 530 cnp->tn_name, cnp->tn_instance); 531 return (TOPO_WALK_TERMINATE); 532 } 533 534 topo_dprintf(wp->tw_thp, TOPO_DBG_WALK, 535 "step_sibling: through sibling node %s=%d to %s=%d\n", 536 cnp->tn_name, cnp->tn_instance, nnp->tn_name, nnp->tn_instance); 537 538 topo_node_hold(nnp); /* released on return from walk_step */ 539 wp->tw_node = nnp; 540 if (bottomup == 1) 541 status = topo_walk_bottomup(wp, flag); 542 else 543 status = topo_walk_step(wp, flag); 544 545 return (status); 546 } 547 548 int 549 topo_walk_byid(topo_walk_t *wp, const char *name, topo_instance_t inst) 550 { 551 int status; 552 tnode_t *nnp, *cnp; 553 554 cnp = wp->tw_node; 555 nnp = topo_node_lookup(cnp, name, inst); 556 if (nnp == NULL) 557 return (TOPO_WALK_TERMINATE); 558 559 topo_node_hold(nnp); 560 wp->tw_node = nnp; 561 if (wp->tw_mod != NULL) 562 status = wp->tw_cb(wp->tw_mod, nnp, wp->tw_pdata); 563 else 564 status = wp->tw_cb(wp->tw_thp, nnp, wp->tw_pdata); 565 topo_node_rele(nnp); 566 wp->tw_node = cnp; 567 568 return (status); 569 } 570 571 int 572 topo_walk_bysibling(topo_walk_t *wp, const char *name, topo_instance_t inst) 573 { 574 int status; 575 tnode_t *cnp, *pnp; 576 577 cnp = wp->tw_node; 578 pnp = topo_node_parent(cnp); 579 assert(pnp != NULL); 580 581 topo_node_hold(pnp); 582 wp->tw_node = pnp; 583 status = topo_walk_byid(wp, name, inst); 584 topo_node_rele(pnp); 585 wp->tw_node = cnp; 586 587 return (status); 588 } 589 590 int 591 topo_walk_step(topo_walk_t *wp, int flag) 592 { 593 int status; 594 tnode_t *cnp = wp->tw_node; 595 596 if (flag != TOPO_WALK_CHILD && flag != TOPO_WALK_SIBLING) { 597 topo_node_rele(cnp); 598 return (TOPO_WALK_ERR); 599 } 600 601 /* 602 * No more nodes to walk 603 */ 604 if (cnp == NULL) { 605 topo_dprintf(wp->tw_thp, TOPO_DBG_WALK, 606 "walk_step terminated\n"); 607 topo_node_rele(cnp); 608 return (TOPO_WALK_TERMINATE); 609 } 610 611 612 if (wp->tw_mod != NULL) 613 status = wp->tw_cb(wp->tw_mod, cnp, wp->tw_pdata); 614 else 615 status = wp->tw_cb(wp->tw_thp, cnp, wp->tw_pdata); 616 617 /* 618 * Walker callback says we're done 619 */ 620 if (status != TOPO_WALK_NEXT) { 621 topo_node_rele(cnp); 622 return (status); 623 } 624 625 if (flag == TOPO_WALK_CHILD) 626 status = step_child(cnp, wp, flag, 0); 627 else 628 status = step_sibling(cnp, wp, flag, 0); 629 630 /* 631 * No more nodes in this hash, skip to next node hash by stepping 632 * to next sibling (child-first walk) or next child (sibling-first 633 * walk). 634 */ 635 if (status == TOPO_WALK_TERMINATE) { 636 if (flag == TOPO_WALK_CHILD) 637 status = step_sibling(cnp, wp, flag, 0); 638 else 639 status = step_child(cnp, wp, flag, 0); 640 } 641 642 topo_node_rele(cnp); /* done with current node */ 643 644 return (status); 645 } 646 647 void 648 topo_walk_fini(topo_walk_t *wp) 649 { 650 if (wp == NULL) 651 return; 652 653 topo_node_rele(wp->tw_root); 654 655 topo_hdl_free(wp->tw_thp, wp, sizeof (topo_walk_t)); 656 } 657 658 int 659 topo_walk_bottomup(topo_walk_t *wp, int flag) 660 { 661 int status; 662 tnode_t *cnp; 663 664 if (wp == NULL) 665 return (TOPO_WALK_ERR); 666 667 cnp = wp->tw_node; 668 if (flag != TOPO_WALK_CHILD && flag != TOPO_WALK_SIBLING) { 669 topo_node_rele(cnp); 670 return (TOPO_WALK_ERR); 671 } 672 673 /* 674 * End of the line 675 */ 676 if (cnp == NULL) { 677 topo_dprintf(wp->tw_thp, TOPO_DBG_WALK, 678 "walk_bottomup terminated\n"); 679 topo_node_rele(cnp); 680 return (TOPO_WALK_TERMINATE); 681 } 682 683 topo_dprintf(wp->tw_thp, TOPO_DBG_WALK, 684 "%s walk_bottomup through node %s=%d\n", 685 (flag == TOPO_WALK_CHILD ? "TOPO_WALK_CHILD" : "TOPO_WALK_SIBLING"), 686 cnp->tn_name, cnp->tn_instance); 687 688 if (flag == TOPO_WALK_CHILD) 689 status = step_child(cnp, wp, flag, 1); 690 else 691 status = step_sibling(cnp, wp, flag, 1); 692 693 /* 694 * At a leaf, run the callback 695 */ 696 if (status == TOPO_WALK_TERMINATE) { 697 if ((status = wp->tw_cb(wp->tw_thp, cnp, wp->tw_pdata)) 698 != TOPO_WALK_NEXT) { 699 topo_node_rele(cnp); 700 return (status); 701 } 702 } 703 704 /* 705 * Try next child or sibling 706 */ 707 if (status == TOPO_WALK_NEXT) { 708 if (flag == TOPO_WALK_CHILD) 709 status = step_sibling(cnp, wp, flag, 1); 710 else 711 status = step_child(cnp, wp, flag, 1); 712 } 713 714 topo_node_rele(cnp); /* done with current node */ 715 716 return (status); 717 } 718