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