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