xref: /titanic_41/usr/src/cmd/fm/modules/common/eversholt/config.c (revision 9dd828891378a0a6a509ab601b4c5c20ca5562ec)
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 #define	CONNECTED_SEPCHARS " ,"
593 
594 int
595 config_is_connected(struct node *np, struct config *croot,
596 		    struct evalue *valuep)
597 {
598 	const char *connstrings[] = { "connected", "CONNECTED", NULL };
599 	struct config *cp[2], *compcp;
600 	struct node *nptr[2];
601 	const char *searchforname, *matchthis[2], *s;
602 	char *nameslist, *w;
603 	int i, j;
604 
605 	valuep->t = UINT64;
606 	valuep->v = 0;
607 
608 	if (np->u.expr.left->t == T_NAME)
609 		nptr[0] = np->u.expr.left;
610 	else if (np->u.expr.left->u.func.s == L_fru)
611 		nptr[0] = eval_fru(np->u.expr.left->u.func.arglist);
612 	else if (np->u.expr.left->u.func.s == L_asru)
613 		nptr[0] = eval_asru(np->u.expr.left->u.func.arglist);
614 
615 	if (np->u.expr.right->t == T_NAME)
616 		nptr[1] = np->u.expr.right;
617 	else if (np->u.expr.right->u.func.s == L_fru)
618 		nptr[1] = eval_fru(np->u.expr.right->u.func.arglist);
619 	else if (np->u.expr.right->u.func.s == L_asru)
620 		nptr[1] = eval_asru(np->u.expr.right->u.func.arglist);
621 
622 	for (i = 0; i < 2; i++) {
623 		cp[i] = name2cp(nptr[i], croot);
624 		if (cp[i] == NULL)
625 			return (1);
626 	}
627 
628 	/* to thine self always be connected */
629 	if (cp[0] == cp[1]) {
630 		valuep->v = 1;
631 		return (0);
632 	}
633 
634 	/*
635 	 * set one of the cp[]s to compcp and extract its "connected"
636 	 * property.  search this property for the name associated with the
637 	 * other cp[].
638 	 */
639 	for (i = 0; i < 2 && valuep->v == 0; i++) {
640 		compcp = cp[i];
641 
642 		searchforname = ipath2str(NULL, ipath(nptr[(i == 0 ? 1 : 0)]));
643 		matchthis[i] = stable(searchforname);
644 		FREE((void *)searchforname);
645 
646 		for (j = 0; connstrings[j] != NULL && valuep->v == 0; j++) {
647 			s = config_getprop(compcp, stable(connstrings[j]));
648 			if (s != NULL) {
649 				nameslist = STRDUP(s);
650 				w = strtok(nameslist, CONNECTED_SEPCHARS);
651 				while (w != NULL) {
652 					if (stable(w) == matchthis[i]) {
653 						valuep->v = 1;
654 						break;
655 					}
656 					w = strtok(NULL, CONNECTED_SEPCHARS);
657 				}
658 				FREE(nameslist);
659 			}
660 		}
661 	}
662 
663 	/* a path shouldn't have more than one cp node */
664 	if (valuep->v == 0)
665 		ASSERT(matchthis[0] != matchthis[1]);
666 
667 	return (0);
668 }
669 
670 int
671 config_is_type(struct node *np, struct config *croot, struct evalue *valuep)
672 {
673 	const char *typestrings[] = { "type", "TYPE", NULL };
674 	struct config *cp;
675 	struct node *nodep;
676 	const char *s;
677 	int i;
678 
679 	valuep->t = STRING;
680 	valuep->v = 0;
681 
682 	if (np->u.func.s == L_fru)
683 		nodep = eval_fru(np->u.func.arglist);
684 	else if (np->u.func.s == L_asru)
685 		nodep = eval_asru(np->u.func.arglist);
686 
687 	cp = name2cp(nodep, croot);
688 	if (cp == NULL)
689 		return (1);
690 
691 	for (i = 0; typestrings[i] != NULL; i++) {
692 		s = config_getprop(cp, stable(typestrings[i]));
693 		if (s != NULL) {
694 			valuep->v = (uintptr_t)stable(s);
695 			break;
696 		}
697 	}
698 
699 	/* no entry for "type" */
700 	if (valuep->v == 0)
701 		return (1);
702 
703 	return (0);
704 }
705 
706 int
707 config_is_on(struct node *np, struct config *croot, struct evalue *valuep)
708 {
709 	const char *onstrings[] = { "on", "ON", NULL };
710 	const char *truestrings[] = { "yes", "YES", "y", "Y",
711 				    "true", "TRUE", "t", "T",
712 				    "1", NULL };
713 	struct config *cp;
714 	struct node *nodep;
715 	const char *s;
716 	int i, j;
717 
718 	valuep->t = UINT64;
719 	valuep->v = 0;
720 
721 	if (np->u.func.s == L_fru)
722 		nodep = eval_fru(np->u.func.arglist);
723 	else if (np->u.func.s == L_asru)
724 		nodep = eval_asru(np->u.func.arglist);
725 
726 	cp = name2cp(nodep, croot);
727 	if (cp == NULL)
728 		return (1);
729 
730 	for (i = 0; onstrings[i] != NULL; i++) {
731 		s = config_getprop(cp, stable(onstrings[i]));
732 		if (s != NULL) {
733 			s = stable(s);
734 			for (j = 0; truestrings[j] != NULL; j++) {
735 				if (s == stable(truestrings[j])) {
736 					valuep->v = 1;
737 					return (0);
738 				}
739 			}
740 		}
741 	}
742 
743 	return (0);
744 }
745 
746 int
747 config_is_present(struct node *np, struct config *croot, struct evalue *valuep)
748 {
749 	struct config *cp;
750 	struct node *nodep;
751 
752 	valuep->t = UINT64;
753 	valuep->v = 0;
754 
755 	if (np->u.func.s == L_fru)
756 		nodep = eval_fru(np->u.func.arglist);
757 	else if (np->u.func.s == L_asru)
758 		nodep = eval_asru(np->u.func.arglist);
759 
760 	cp = name2cp(nodep, croot);
761 	if (cp != NULL)
762 		valuep->v = 1;
763 
764 	return (0);
765 }
766 
767 /*
768  * printprop -- print prop associated with config node
769  */
770 static void
771 printprop(const char *lhs, const char *rhs, void *arg)
772 {
773 	int flags = (int)arg;
774 
775 	out(flags, "\t%s=%s", lhs, rhs);
776 }
777 
778 /*
779  * pconf -- internal printing function to recurse through the tree
780  */
781 static void
782 pconf(int flags, struct config *cp, char *buf, int offset, int limit)
783 {
784 	char *sep = "/";
785 
786 	if (offset)
787 		sep = "/";
788 	else
789 		sep = "";
790 	(void) snprintf(&buf[offset], limit - offset, "%s%s%d",
791 	    sep, cp->s, cp->num);
792 	if (cp->child == NULL) {
793 		out(flags, "%s", buf);
794 		lut_walk(cp->props, (lut_cb)printprop, (void *)flags);
795 	} else
796 		pconf(flags, cp->child, buf, strlen(buf), limit);
797 	if (cp->next)
798 		pconf(flags, cp->next, buf, offset, limit);
799 }
800 
801 /*
802  * config_print -- spew the current configuration cache
803  */
804 
805 #define	MAXCONFLINE 4096
806 
807 void
808 config_print(int flags, struct config *croot)
809 {
810 	char buf[MAXCONFLINE];
811 
812 	if (croot == NULL)
813 		out(flags, "empty configuration");
814 	else
815 		pconf(flags, croot->child, buf, 0, MAXCONFLINE);
816 }
817