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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 * 26 * config.c -- system configuration cache module 27 * 28 * this module caches the system configuration in a format useful 29 * to eft. the information is loaded into this module by 30 * config_snapshot() at the beginning of each FME. config_snapshot() 31 * calls the platform-specific platform_config_snapshot() to get 32 * the configuration information loaded up. 33 */ 34 35 #pragma ident "%Z%%M% %I% %E% SMI" 36 37 #include <stdio.h> 38 #include <stdlib.h> 39 #include <ctype.h> 40 #include <string.h> 41 #include <strings.h> 42 #include <fm/libtopo_enum.h> 43 #include "alloc.h" 44 #include "out.h" 45 #include "literals.h" 46 #include "stable.h" 47 #include "lut.h" 48 #include "tree.h" 49 #include "itree.h" 50 #include "ipath.h" 51 #include "ptree.h" 52 #include "eval.h" 53 #include "config.h" 54 #include "fme.h" 55 #include "platform.h" 56 57 /* 58 * private data structure for storing config. all access to 59 * to this information happens using the config.h interfaces. 60 */ 61 struct config { 62 struct config *next; 63 struct config *child; 64 struct config *parent; 65 const char *s; 66 int num; 67 struct lut *props; 68 }; 69 70 /* 71 * newcnode -- local function to allocate new config node 72 */ 73 static struct config * 74 newcnode(const char *s, int num) 75 { 76 struct config *retval; 77 78 retval = MALLOC(sizeof (struct config)); 79 80 retval->s = s; 81 retval->num = num; 82 retval->next = NULL; 83 retval->props = NULL; 84 retval->child = retval->parent = NULL; 85 86 return (retval); 87 } 88 89 /* 90 * If we need to cache certain types of nodes for reverse look-up or 91 * somesuch, do it here. Currently we need to cache nodes representing 92 * cpus. 93 */ 94 static void 95 config_node_cache(struct cfgdata *cdata, struct config *n) 96 { 97 if (n->s != stable("cpu")) 98 return; 99 cdata->cpucache = lut_add(cdata->cpucache, 100 (void *)n->num, (void *)n, NULL); 101 } 102 103 /* 104 * config_lookup -- lookup/add components in configuration cache 105 */ 106 struct config * 107 config_lookup(struct config *croot, char *path, int add) 108 { 109 char *pathbegin = path; 110 struct config *parent = croot; 111 struct config *cp; 112 struct config *lastcp; 113 struct config *newnode; 114 char *thiscom; /* this component */ 115 char *nextcom; /* next component */ 116 char svdigit; 117 int len; 118 int num; 119 const char *s; 120 int exists; 121 122 if (parent == NULL) 123 out(O_DIE, "uninitialized configuration"); 124 125 while (*path) { 126 if ((nextcom = strchr(path, '/')) != NULL) 127 *nextcom = '\0'; 128 if ((len = strlen(path)) == 0) 129 out(O_DIE, "config_lookup: zero length component"); 130 /* start at end of string and work backwards */ 131 thiscom = &path[len - 1]; 132 if (!isdigit(*thiscom)) 133 out(O_DIE, "config_lookup: " 134 "component \"%s\" has no number following it", 135 path); 136 while (thiscom > path && isdigit(*thiscom)) 137 thiscom--; 138 if (thiscom == path && isdigit(*thiscom)) 139 out(O_DIE, "config_lookup: " 140 "component \"%s\" has no name part", path); 141 thiscom++; /* move to first numeric character */ 142 num = atoi(thiscom); 143 svdigit = *thiscom; 144 *thiscom = '\0'; 145 s = stable(path); 146 *thiscom = svdigit; 147 148 if (nextcom != NULL) 149 *nextcom++ = '/'; 150 151 /* now we have s & num, figure out if it exists already */ 152 exists = 0; 153 lastcp = NULL; 154 for (cp = parent->child; cp; lastcp = cp, cp = cp->next) 155 if (cp->s == s && cp->num == num) { 156 exists = 1; 157 parent = cp; 158 } 159 160 if (!exists) { 161 /* creating new node */ 162 if (!add) { 163 /* 164 * indicate component not found by copying 165 * it to path (allows better error messages 166 * in the caller). 167 */ 168 (void) strcpy(pathbegin, s); 169 return (NULL); 170 } 171 172 newnode = newcnode(s, num); 173 174 if (lastcp) 175 lastcp->next = newnode; 176 else 177 parent->child = newnode; 178 179 newnode->parent = parent; 180 parent = newnode; 181 } 182 183 if (nextcom == NULL) 184 return (parent); /* all done */ 185 186 /* move on to next component */ 187 path = nextcom; 188 } 189 return (parent); 190 } 191 192 /* 193 * addconfigprop -- add a config prop to a config cache entry 194 */ 195 static void 196 addconfigprop(const char *lhs, struct node *rhs, void *arg) 197 { 198 struct config *cp = (struct config *)arg; 199 200 ASSERT(cp != NULL); 201 ASSERT(lhs != NULL); 202 ASSERT(rhs != NULL); 203 ASSERT(rhs->t == T_QUOTE); 204 205 config_setprop(cp, lhs, STRDUP(rhs->u.quote.s)); 206 } 207 208 /* 209 * addconfig -- add a config from parse tree to given configuration cache 210 */ 211 /*ARGSUSED*/ 212 static void 213 addconfig(struct node *lhs, struct node *rhs, void *arg) 214 { 215 struct config *parent = (struct config *)arg; 216 struct config *cp; 217 const char *s; 218 int num; 219 struct config *lastcp; 220 struct config *newnode; 221 int exists; 222 struct lut *lutp; 223 224 ASSERT(rhs->t == T_CONFIG); 225 226 lutp = rhs->u.stmt.lutp; 227 rhs = rhs->u.stmt.np; 228 while (rhs != NULL) { 229 ASSERT(rhs->t == T_NAME); 230 ASSERT(rhs->u.name.child->t == T_NUM); 231 s = rhs->u.name.s; 232 num = rhs->u.name.child->u.ull; 233 234 /* now we have s & num, figure out if it exists already */ 235 exists = 0; 236 lastcp = NULL; 237 for (cp = parent->child; cp; lastcp = cp, cp = cp->next) 238 if (cp->s == s && cp->num == num) { 239 exists = 1; 240 parent = cp; 241 } 242 243 if (!exists) { 244 /* creating new node */ 245 246 newnode = newcnode(s, num); 247 248 if (lastcp) 249 lastcp->next = newnode; 250 else 251 parent->child = newnode; 252 253 parent = newnode; 254 } 255 256 /* move on to next component */ 257 rhs = rhs->u.name.next; 258 } 259 260 /* add configuration properties */ 261 lut_walk(lutp, (lut_cb)addconfigprop, (void *)parent); 262 } 263 264 /* 265 * config_cook -- convert raw config strings to eft internal representation 266 */ 267 void 268 config_cook(struct cfgdata *cdata) 269 { 270 struct config *newnode; 271 char *cfgstr, *equals; 272 const char *pn, *sv; 273 char *pv; 274 275 if (cdata->cooked != NULL) 276 return; 277 278 cdata->cooked = newcnode(NULL, 0); 279 280 if ((cfgstr = cdata->begin) == cdata->nextfree) { 281 out(O_ALTFP|O_VERB, "Platform provided no config data."); 282 goto eftcfgs; 283 } 284 285 out(O_ALTFP|O_VERB3, "Raw config data follows:"); 286 out(O_ALTFP|O_VERB3|O_NONL, 287 "nextfree is %p\n%p ", (void *)cdata->nextfree, (void *)cfgstr); 288 while (cfgstr < cdata->nextfree) { 289 if (!*cfgstr) 290 out(O_ALTFP|O_VERB3|O_NONL, "\n%p ", 291 (void *)(cfgstr + 1)); 292 else 293 out(O_ALTFP|O_VERB3|O_NONL, "%c", *cfgstr); 294 cfgstr++; 295 } 296 out(O_ALTFP|O_VERB3, NULL); 297 298 cfgstr = cdata->begin; 299 while (cfgstr < cdata->nextfree) { 300 while (*cfgstr == '/' && cfgstr < cdata->nextfree) { 301 out(O_ALTFP|O_VERB3, 302 "next string (%p) is %s", (void *)cfgstr, cfgstr); 303 /* skip the initial slash from libtopo */ 304 newnode = config_lookup(cdata->cooked, cfgstr + 1, 1); 305 /* 306 * Note we'll only cache nodes that have 307 * properties on them. Intermediate nodes 308 * will have been added to the config tree, 309 * but we don't have easy means of accessing 310 * them except if we climb the tree from this 311 * newnode to the root. 312 * 313 * Luckily, the nodes we care to cache 314 * (currently just cpus) always have some 315 * properties attached to them 316 * so we don't bother climbing the tree. 317 */ 318 config_node_cache(cdata, newnode); 319 cfgstr += strlen(cfgstr) + 1; 320 } 321 322 if (cfgstr >= cdata->nextfree) 323 break; 324 325 out(O_ALTFP|O_VERB3, "next string (%p) is %s", (void *)cfgstr, 326 cfgstr); 327 if ((equals = strchr(cfgstr, '=')) == NULL) { 328 out(O_ALTFP|O_VERB3, "raw config data bad (%p); " 329 "property missing equals.\n", (void *)cfgstr); 330 break; 331 } 332 333 *equals = '\0'; 334 pn = stable(cfgstr); 335 pv = STRDUP(equals + 1); 336 337 out(O_ALTFP|O_VERB3, "add prop (%s) val %p", pn, (void *)pv); 338 config_setprop(newnode, pn, pv); 339 340 /* 341 * If this property is a device path, cache it for quick lookup 342 */ 343 if (pn == stable(DEV)) { 344 sv = stable(pv); 345 out(O_ALTFP|O_VERB3, "caching %s\n", sv); 346 cdata->devcache = lut_add(cdata->devcache, 347 (void *)sv, (void *)newnode, NULL); 348 } 349 350 *equals = '='; 351 cfgstr += strlen(cfgstr) + 1; 352 } 353 354 eftcfgs: 355 /* now run through Configs table, adding to config cache */ 356 lut_walk(Configs, (lut_cb)addconfig, (void *)cdata->cooked); 357 } 358 359 /* 360 * config_snapshot -- gather a snapshot of the current configuration 361 */ 362 struct cfgdata * 363 config_snapshot(void) 364 { 365 struct cfgdata *rawcfg; 366 367 rawcfg = platform_config_snapshot(); 368 config_cook(rawcfg); 369 return (rawcfg); 370 } 371 372 /* 373 * prop_destructor -- free a prop value 374 */ 375 /*ARGSUSED*/ 376 static void 377 prop_destructor(void *left, void *right, void *arg) 378 { 379 FREE(right); 380 } 381 382 /* 383 * structconfig_free -- free a struct config pointer and all its relatives 384 */ 385 static void 386 structconfig_free(struct config *cp) 387 { 388 if (cp == NULL) 389 return; 390 391 structconfig_free(cp->child); 392 structconfig_free(cp->next); 393 lut_free(cp->props, prop_destructor, NULL); 394 FREE(cp); 395 } 396 397 /* 398 * config_free -- free a configuration snapshot 399 */ 400 void 401 config_free(struct cfgdata *cp) 402 { 403 if (cp == NULL) 404 return; 405 406 if (--cp->refcnt > 0) 407 return; 408 409 if (cp->cooked != NULL) 410 structconfig_free(cp->cooked); 411 if (cp->begin != NULL) 412 FREE(cp->begin); 413 if (cp->devcache != NULL) 414 lut_free(cp->devcache, NULL, NULL); 415 if (cp->cpucache != NULL) 416 lut_free(cp->cpucache, NULL, NULL); 417 FREE(cp); 418 } 419 420 /* 421 * config_next -- get the "next" config node 422 */ 423 struct config * 424 config_next(struct config *cp) 425 { 426 ASSERT(cp != NULL); 427 428 return ((struct config *)((struct config *)cp)->next); 429 } 430 431 432 /* 433 * config_child -- get the "child" of a config node 434 */ 435 struct config * 436 config_child(struct config *cp) 437 { 438 ASSERT(cp != NULL); 439 440 return ((struct config *)((struct config *)cp)->child); 441 } 442 443 /* 444 * config_setprop -- add a property to a config node 445 */ 446 void 447 config_setprop(struct config *cp, const char *propname, const char *propvalue) 448 { 449 const char *pn = stable(propname); 450 451 cp->props = lut_add(cp->props, (void *)pn, (void *)propvalue, NULL); 452 } 453 454 /* 455 * config_getprop -- lookup a config property 456 */ 457 const char * 458 config_getprop(struct config *cp, const char *propname) 459 { 460 return (lut_lookup(cp->props, (void *) stable(propname), NULL)); 461 } 462 463 /* 464 * config_getcompname -- get the component name of a config node 465 */ 466 void 467 config_getcompname(struct config *cp, char **name, int *inst) 468 { 469 ASSERT(cp != NULL); 470 471 if (name != NULL) 472 *name = (char *)cp->s; 473 if (inst != NULL) 474 *inst = cp->num; 475 } 476 477 /* 478 * config_nodeize -- convert the config element represented by cp to struct 479 * node format 480 */ 481 static struct node * 482 config_nodeize(struct config *cp) 483 { 484 struct node *tmpn, *ptmpn; 485 struct node *numn; 486 const char *sname; 487 488 if (cp == NULL || cp->s == NULL) 489 return (NULL); 490 491 sname = stable(cp->s); 492 numn = newnode(T_NUM, NULL, 0); 493 numn->u.ull = cp->num; 494 495 tmpn = tree_name_iterator(tree_name(sname, IT_VERTICAL, NULL, 0), numn); 496 if ((ptmpn = config_nodeize(cp->parent)) == NULL) 497 return (tmpn); 498 return (tree_name_append(ptmpn, tmpn)); 499 } 500 501 /*ARGSUSED*/ 502 static void 503 prtdevcache(void *lhs, void *rhs, void *arg) 504 { 505 out(O_ALTFP|O_VERB3, "%s -> %p", (char *)lhs, rhs); 506 } 507 508 /*ARGSUSED*/ 509 static void 510 prtcpucache(void *lhs, void *rhs, void *arg) 511 { 512 out(O_ALTFP|O_VERB, "%u -> %p", (uint32_t)lhs, rhs); 513 } 514 515 /* 516 * config_bydev_lookup -- look up the path in our DEVcache lut. If we find 517 * it return the config path, but as a struct node. 518 */ 519 struct node * 520 config_bydev_lookup(struct cfgdata *fromcfg, const char *path) 521 { 522 struct config *find; 523 struct node *np; 524 525 out(O_ALTFP|O_VERB3, "Device path cache:"); 526 lut_walk(fromcfg->devcache, (lut_cb)prtdevcache, NULL); 527 528 if ((find = lut_lookup(fromcfg->devcache, 529 (void *) stable(path), NULL)) == NULL) 530 return (NULL); 531 532 np = config_nodeize(find); 533 if (np != NULL) { 534 out(O_ALTFP|O_VERB, "Matching config entry:"); 535 ptree_name_iter(O_ALTFP|O_VERB|O_NONL, np); 536 out(O_ALTFP|O_VERB, NULL); 537 } 538 return (np); 539 } 540 541 /* 542 * config_bycpuid_lookup -- look up the cpu id in our CPUcache lut. 543 * If we find it return the config path, but as a struct node. 544 */ 545 struct node * 546 config_bycpuid_lookup(struct cfgdata *fromcfg, uint32_t id) 547 { 548 struct config *find; 549 struct node *np; 550 551 out(O_ALTFP|O_VERB, "Cpu cache:"); 552 lut_walk(fromcfg->cpucache, (lut_cb)prtcpucache, NULL); 553 554 if ((find = lut_lookup(fromcfg->cpucache, 555 (void *)id, NULL)) == NULL) 556 return (NULL); 557 558 np = config_nodeize(find); 559 if (np != NULL) { 560 out(O_ALTFP|O_VERB3, "Matching config entry:"); 561 ptree_name_iter(O_ALTFP|O_VERB3|O_NONL, np); 562 out(O_ALTFP|O_VERB3, NULL); 563 } 564 return (np); 565 } 566 567 /* 568 * given the following: 569 * - np of type T_NAME which denotes a pathname 570 * - croot, the root node of a configuration 571 * 572 * return the cp for the last component in np's path 573 */ 574 static struct config * 575 name2cp(struct node *np, struct config *croot) 576 { 577 char *path; 578 struct config *cp; 579 580 if (np->u.name.last->u.name.cp != NULL) 581 return (np->u.name.last->u.name.cp); 582 583 path = ipath2str(NULL, ipath(np)); 584 585 cp = config_lookup(croot, path, 0); 586 FREE((void *)path); 587 588 return (cp); 589 } 590 591 int 592 config_confprop(struct node *np, struct config *croot, struct evalue *valuep) 593 { 594 struct node *nodep; 595 struct config *cp; 596 const char *s; 597 598 if (np->u.expr.left->u.func.s == L_fru) 599 nodep = eval_fru(np->u.expr.left->u.func.arglist); 600 else if (np->u.expr.left->u.func.s == L_asru) 601 nodep = eval_asru(np->u.expr.left->u.func.arglist); 602 603 cp = name2cp(nodep, croot); 604 if (cp == NULL) 605 return (1); 606 607 /* for now s will point to a quote [see addconfigprop()] */ 608 ASSERT(np->u.expr.right->t == T_QUOTE); 609 610 s = config_getprop(cp, np->u.expr.right->u.quote.s); 611 if (s == NULL) 612 return (1); 613 614 valuep->t = STRING; 615 valuep->v = (unsigned long long)stable(s); 616 617 return (0); 618 } 619 620 #define CONNECTED_SEPCHARS " ," 621 622 int 623 config_is_connected(struct node *np, struct config *croot, 624 struct evalue *valuep) 625 { 626 const char *connstrings[] = { "connected", "CONNECTED", NULL }; 627 struct config *cp[2], *compcp; 628 struct node *nptr[2]; 629 const char *searchforname, *matchthis[2], *s; 630 char *nameslist, *w; 631 int i, j; 632 633 valuep->t = UINT64; 634 valuep->v = 0; 635 636 if (np->u.expr.left->t == T_NAME) 637 nptr[0] = np->u.expr.left; 638 else if (np->u.expr.left->u.func.s == L_fru) 639 nptr[0] = eval_fru(np->u.expr.left->u.func.arglist); 640 else if (np->u.expr.left->u.func.s == L_asru) 641 nptr[0] = eval_asru(np->u.expr.left->u.func.arglist); 642 643 if (np->u.expr.right->t == T_NAME) 644 nptr[1] = np->u.expr.right; 645 else if (np->u.expr.right->u.func.s == L_fru) 646 nptr[1] = eval_fru(np->u.expr.right->u.func.arglist); 647 else if (np->u.expr.right->u.func.s == L_asru) 648 nptr[1] = eval_asru(np->u.expr.right->u.func.arglist); 649 650 for (i = 0; i < 2; i++) { 651 cp[i] = name2cp(nptr[i], croot); 652 if (cp[i] == NULL) 653 return (1); 654 } 655 656 /* to thine self always be connected */ 657 if (cp[0] == cp[1]) { 658 valuep->v = 1; 659 return (0); 660 } 661 662 /* 663 * set one of the cp[]s to compcp and extract its "connected" 664 * property. search this property for the name associated with the 665 * other cp[]. 666 */ 667 for (i = 0; i < 2 && valuep->v == 0; i++) { 668 compcp = cp[i]; 669 670 searchforname = ipath2str(NULL, ipath(nptr[(i == 0 ? 1 : 0)])); 671 matchthis[i] = stable(searchforname); 672 FREE((void *)searchforname); 673 674 for (j = 0; connstrings[j] != NULL && valuep->v == 0; j++) { 675 s = config_getprop(compcp, stable(connstrings[j])); 676 if (s != NULL) { 677 nameslist = STRDUP(s); 678 w = strtok(nameslist, CONNECTED_SEPCHARS); 679 while (w != NULL) { 680 if (stable(w) == matchthis[i]) { 681 valuep->v = 1; 682 break; 683 } 684 w = strtok(NULL, CONNECTED_SEPCHARS); 685 } 686 FREE(nameslist); 687 } 688 } 689 } 690 691 /* a path shouldn't have more than one cp node */ 692 if (valuep->v == 0) 693 ASSERT(matchthis[0] != matchthis[1]); 694 695 return (0); 696 } 697 698 int 699 config_is_type(struct node *np, struct config *croot, struct evalue *valuep) 700 { 701 const char *typestrings[] = { "type", "TYPE", NULL }; 702 struct config *cp; 703 struct node *nodep; 704 const char *s; 705 int i; 706 707 valuep->t = STRING; 708 valuep->v = 0; 709 710 if (np->u.func.s == L_fru) 711 nodep = eval_fru(np->u.func.arglist); 712 else if (np->u.func.s == L_asru) 713 nodep = eval_asru(np->u.func.arglist); 714 715 cp = name2cp(nodep, croot); 716 if (cp == NULL) 717 return (1); 718 719 for (i = 0; typestrings[i] != NULL; i++) { 720 s = config_getprop(cp, stable(typestrings[i])); 721 if (s != NULL) { 722 valuep->v = (unsigned long long)stable(s); 723 break; 724 } 725 } 726 727 /* no entry for "type" */ 728 if (valuep->v == 0) 729 return (1); 730 731 return (0); 732 } 733 734 int 735 config_is_on(struct node *np, struct config *croot, struct evalue *valuep) 736 { 737 const char *onstrings[] = { "on", "ON", NULL }; 738 const char *truestrings[] = { "yes", "YES", "y", "Y", 739 "true", "TRUE", "t", "T", 740 "1", NULL }; 741 struct config *cp; 742 struct node *nodep; 743 const char *s; 744 int i, j; 745 746 valuep->t = UINT64; 747 valuep->v = 0; 748 749 if (np->u.func.s == L_fru) 750 nodep = eval_fru(np->u.func.arglist); 751 else if (np->u.func.s == L_asru) 752 nodep = eval_asru(np->u.func.arglist); 753 754 cp = name2cp(nodep, croot); 755 if (cp == NULL) 756 return (1); 757 758 for (i = 0; onstrings[i] != NULL; i++) { 759 s = config_getprop(cp, stable(onstrings[i])); 760 if (s != NULL) { 761 s = stable(s); 762 for (j = 0; truestrings[j] != NULL; j++) { 763 if (s == stable(truestrings[j])) { 764 valuep->v = 1; 765 return (0); 766 } 767 } 768 } 769 } 770 771 return (0); 772 } 773 774 int 775 config_is_present(struct node *np, struct config *croot, struct evalue *valuep) 776 { 777 struct config *cp; 778 struct node *nodep; 779 780 valuep->t = UINT64; 781 valuep->v = 0; 782 783 if (np->u.func.s == L_fru) 784 nodep = eval_fru(np->u.func.arglist); 785 else if (np->u.func.s == L_asru) 786 nodep = eval_asru(np->u.func.arglist); 787 788 cp = name2cp(nodep, croot); 789 if (cp != NULL) 790 valuep->v = 1; 791 792 return (0); 793 } 794 795 /* 796 * printprop -- print prop associated with config node 797 */ 798 static void 799 printprop(const char *lhs, const char *rhs, void *arg) 800 { 801 int flags = (int)arg; 802 803 out(flags, "\t%s=%s", lhs, rhs); 804 } 805 806 /* 807 * pconf -- internal printing function to recurse through the tree 808 */ 809 static void 810 pconf(int flags, struct config *cp, char *buf, int offset, int limit) 811 { 812 char *sep = "/"; 813 814 if (offset) 815 sep = "/"; 816 else 817 sep = ""; 818 (void) snprintf(&buf[offset], limit - offset, "%s%s%d", 819 sep, cp->s, cp->num); 820 if (cp->child == NULL) { 821 out(flags, "%s", buf); 822 lut_walk(cp->props, (lut_cb)printprop, (void *)flags); 823 } else 824 pconf(flags, cp->child, buf, strlen(buf), limit); 825 if (cp->next) 826 pconf(flags, cp->next, buf, offset, limit); 827 } 828 829 /* 830 * config_print -- spew the current configuration cache 831 */ 832 833 #define MAXCONFLINE 4096 834 835 void 836 config_print(int flags, struct config *croot) 837 { 838 char buf[MAXCONFLINE]; 839 840 if (croot == NULL) 841 out(flags, "empty configuration"); 842 else 843 pconf(flags, croot->child, buf, 0, MAXCONFLINE); 844 } 845