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