Home | History | Annotate | Line # | Download | only in config
      1 /*	$NetBSD: files.c,v 1.38 2024/04/05 00:43:42 riastradh Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1992, 1993
      5  *	The Regents of the University of California.  All rights reserved.
      6  *
      7  * This software was developed by the Computer Systems Engineering group
      8  * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
      9  * contributed to Berkeley.
     10  *
     11  * All advertising materials mentioning features or use of this software
     12  * must display the following acknowledgement:
     13  *	This product includes software developed by the University of
     14  *	California, Lawrence Berkeley Laboratories.
     15  *
     16  * Redistribution and use in source and binary forms, with or without
     17  * modification, are permitted provided that the following conditions
     18  * are met:
     19  * 1. Redistributions of source code must retain the above copyright
     20  *    notice, this list of conditions and the following disclaimer.
     21  * 2. Redistributions in binary form must reproduce the above copyright
     22  *    notice, this list of conditions and the following disclaimer in the
     23  *    documentation and/or other materials provided with the distribution.
     24  * 3. Neither the name of the University nor the names of its contributors
     25  *    may be used to endorse or promote products derived from this software
     26  *    without specific prior written permission.
     27  *
     28  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     29  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     30  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     31  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     32  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     33  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     34  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     35  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     36  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     37  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     38  * SUCH DAMAGE.
     39  *
     40  *	from: @(#)files.c	8.1 (Berkeley) 6/6/93
     41  */
     42 
     43 #if HAVE_NBTOOL_CONFIG_H
     44 #include "nbtool_config.h"
     45 #endif
     46 
     47 #include <sys/cdefs.h>
     48 __RCSID("$NetBSD: files.c,v 1.38 2024/04/05 00:43:42 riastradh Exp $");
     49 
     50 #include <sys/param.h>
     51 #include <assert.h>
     52 #include <errno.h>
     53 #include <stdio.h>
     54 #include <stdlib.h>
     55 #include <string.h>
     56 #include <util.h>
     57 #include "defs.h"
     58 
     59 extern const char *yyfile;
     60 
     61 int nallfiles;
     62 size_t nselfiles;
     63 struct files **selfiles;
     64 
     65 /*
     66  * We check that each full path name is unique.  File base names
     67  * should generally also be unique, e.g., having both a net/xx.c and
     68  * a kern/xx.c (or, worse, a net/xx.c and a new/xx.c++) is probably
     69  * wrong, but is permitted under some conditions.
     70  */
     71 static struct hashtab *basetab;		/* file base names */
     72 static struct hashtab *pathtab;		/* full path names */
     73 
     74 static struct files **unchecked;
     75 
     76 static void	addfiletoattr(const char *, struct files *);
     77 static int	checkaux(const char *, void *);
     78 static int	fixcount(const char *, void *);
     79 static int	fixfsel(const char *, void *);
     80 static int	fixsel(const char *, void *);
     81 
     82 void
     83 initfiles(void)
     84 {
     85 
     86 	basetab = ht_new();
     87 	pathtab = ht_new();
     88 	TAILQ_INIT(&allfiles);
     89 	TAILQ_INIT(&allcfiles);
     90 	TAILQ_INIT(&allsfiles);
     91 	TAILQ_INIT(&allofiles);
     92 	unchecked = &TAILQ_FIRST(&allfiles);
     93 }
     94 
     95 void
     96 addfile(const char *path, struct condexpr *optx, u_char flags, const char *rule)
     97 {
     98 	struct files *fi;
     99 	const char *dotp, *tail;
    100 	size_t baselen;
    101 	size_t dirlen;
    102 	int needc, needf;
    103 	char base[200];
    104 	char dir[MAXPATHLEN];
    105 
    106 	/* check various errors */
    107 	needc = flags & FI_NEEDSCOUNT;
    108 	needf = flags & FI_NEEDSFLAG;
    109 	if (needc && needf) {
    110 		cfgerror("cannot mix needs-count and needs-flag");
    111 		goto bad;
    112 	}
    113 	if (optx == NULL && (needc || needf)) {
    114 		cfgerror("nothing to %s for %s", needc ? "count" : "flag",
    115 		    path);
    116 		goto bad;
    117 	}
    118 	if (*path == '/') {
    119 		cfgerror("path must be relative");
    120 		goto bad;
    121 	}
    122 
    123 	/* find last part of pathname, and same without trailing suffix */
    124 	tail = strrchr(path, '/');
    125 	if (tail == NULL) {
    126 		dirlen = 0;
    127 		tail = path;
    128 	} else {
    129 		dirlen = (size_t)(tail - path);
    130 		tail++;
    131 	}
    132 	memcpy(dir, path, dirlen);
    133 	dir[dirlen] = '\0';
    134 
    135 	dotp = strrchr(tail, '.');
    136 	if (dotp == NULL || dotp[1] == 0 ||
    137 	    (baselen = (size_t)(dotp - tail)) >= sizeof(base)) {
    138 		cfgerror("invalid pathname `%s'", path);
    139 		goto bad;
    140 	}
    141 
    142 	/*
    143 	 * Commit this file to memory.  We will decide later whether it
    144 	 * will be used after all.
    145 	 */
    146 	fi = ecalloc(1, sizeof *fi);
    147 	if (ht_insert(pathtab, path, fi)) {
    148 		free(fi);
    149 		if ((fi = ht_lookup(pathtab, path)) == NULL)
    150 			panic("addfile: ht_lookup(%s)", path);
    151 
    152 		/*
    153 		 * If it's a duplicate entry, it is must specify a make
    154 		 * rule, and only a make rule, and must come from
    155 		 * a different source file than the original entry.
    156 		 * If it does otherwise, it is disallowed.  This allows
    157 		 * machine-dependent files to override the compilation
    158 		 * options for specific files.
    159 		 */
    160 		if (rule != NULL && optx == NULL && flags == 0 &&
    161 		    yyfile != fi->fi_where.w_srcfile) {
    162 			fi->fi_mkrule = rule;
    163 			return;
    164 		}
    165 		cfgerror("duplicate file %s", path);
    166 		cfgxerror(fi->fi_where.w_srcfile, fi->fi_where.w_srcline,
    167 		    "here is the original definition");
    168 		goto bad;
    169 	}
    170 	memcpy(base, tail, baselen);
    171 	base[baselen] = '\0';
    172 	fi->fi_where.w_srcfile = yyfile;
    173 	fi->fi_where.w_srcline = currentline();
    174 	fi->fi_flags = flags;
    175 	fi->fi_path = path;
    176 	fi->fi_tail = tail;
    177 	fi->fi_base = intern(base);
    178 	fi->fi_dir = intern(dir);
    179 	fi->fi_prefix = SLIST_EMPTY(&prefixes) ? NULL :
    180 			SLIST_FIRST(&prefixes)->pf_prefix;
    181 	fi->fi_buildprefix = SLIST_EMPTY(&buildprefixes) ? NULL :
    182 			SLIST_FIRST(&buildprefixes)->pf_prefix;
    183 	fi->fi_len = strlen(path);
    184 	fi->fi_suffix = path[fi->fi_len - 1];
    185 	fi->fi_optx = optx;
    186 	fi->fi_optf = NULL;
    187 	fi->fi_mkrule = rule;
    188 	fi->fi_attr = NULL;
    189 	fi->fi_order = (int)nallfiles + (includedepth << 16);
    190 	switch (fi->fi_suffix) {
    191 	case 'c':
    192 		TAILQ_INSERT_TAIL(&allcfiles, fi, fi_snext);
    193 		TAILQ_INSERT_TAIL(&allfiles, fi, fi_next);
    194 		break;
    195 	case 'S':
    196 		fi->fi_suffix = 's';
    197 		/* FALLTHRU */
    198 	case 's':
    199 		TAILQ_INSERT_TAIL(&allsfiles, fi, fi_snext);
    200 		TAILQ_INSERT_TAIL(&allfiles, fi, fi_next);
    201 		break;
    202 	case 'o':
    203 		TAILQ_INSERT_TAIL(&allofiles, fi, fi_snext);
    204 		TAILQ_INSERT_TAIL(&allfiles, fi, fi_next);
    205 		break;
    206 	default:
    207 		cfgxerror(fi->fi_where.w_srcfile, fi->fi_where.w_srcline,
    208 		    "unknown suffix");
    209 		break;
    210 	}
    211 	CFGDBG(3, "file added `%s' at order score %d", fi->fi_path, fi->fi_order);
    212 	nallfiles++;
    213 	return;
    214  bad:
    215 	if (optx != NULL) {
    216 		condexpr_destroy(optx);
    217 	}
    218 }
    219 
    220 static void
    221 addfiletoattr(const char *name, struct files *fi)
    222 {
    223 	struct attr *a;
    224 
    225 	a = ht_lookup(attrtab, name);
    226 	if (a == NULL) {
    227 		CFGDBG(1, "attr `%s' not found", name);
    228 	} else {
    229 		fi->fi_attr = a;
    230 		TAILQ_INSERT_TAIL(&a->a_files, fi, fi_anext);
    231 	}
    232 }
    233 
    234 /*
    235  * We have finished reading some "files" file, either ../../conf/files
    236  * or ./files.$machine.  Make sure that everything that is flagged as
    237  * needing a count is reasonable.  (This prevents ../../conf/files from
    238  * depending on some machine-specific device.)
    239  */
    240 void
    241 checkfiles(void)
    242 {
    243 	struct files *fi, *last;
    244 
    245 	last = NULL;
    246 	for (fi = *unchecked; fi != NULL;
    247 	    last = fi, fi = TAILQ_NEXT(fi, fi_next)) {
    248 		if ((fi->fi_flags & FI_NEEDSCOUNT) != 0)
    249 			(void)expr_eval(fi->fi_optx, checkaux, fi);
    250 	}
    251 	if (last != NULL)
    252 		unchecked = &TAILQ_NEXT(last, fi_next);
    253 }
    254 
    255 /*
    256  * Auxiliary function for checkfiles, called from expr_eval.
    257  * We are not actually interested in the expression's value.
    258  */
    259 static int
    260 checkaux(const char *name, void *context)
    261 {
    262 	struct files *fi = context;
    263 
    264 	if (ht_lookup(devbasetab, name) == NULL) {
    265 		cfgxerror(fi->fi_where.w_srcfile, fi->fi_where.w_srcline,
    266 		    "`%s' is not a countable device",
    267 		    name);
    268 		/* keep fixfiles() from complaining again */
    269 		fi->fi_flags |= FI_HIDDEN;
    270 	}
    271 	return (0);
    272 }
    273 
    274 static int
    275 cmpfiles(const void *a, const void *b)
    276 {
    277 	const struct files * const *fia = a, * const *fib = b;
    278 	int sa = (*fia)->fi_order;
    279 	int sb = (*fib)->fi_order;
    280 
    281 	if (sa < sb)
    282 		return -1;
    283 	else if (sa > sb)
    284 		return +1;
    285 	else
    286 		abort();	/* no ties possible */
    287 }
    288 
    289 /*
    290  * We have finished reading everything.  Tack the files down: calculate
    291  * selection and counts as needed.  Check that the object files built
    292  * from the selected sources do not collide.
    293  */
    294 int
    295 fixfiles(void)
    296 {
    297 	struct files *fi, *ofi;
    298 	struct nvlist *flathead, **flatp;
    299 	int err, sel;
    300 	struct config *cf;
    301  	char swapname[100];
    302 
    303 	/* Place these files at last. */
    304 	int onallfiles = nallfiles;
    305 	nallfiles = 1 << 30;
    306 	addfile("devsw.c", NULL, 0, NULL);
    307 	addfile("ioconf.c", NULL, 0, NULL);
    308 
    309 	TAILQ_FOREACH(cf, &allcf, cf_next) {
    310  		(void)snprintf(swapname, sizeof(swapname), "swap%s.c",
    311  		    cf->cf_name);
    312  		addfile(intern(swapname), NULL, 0, NULL);
    313  	}
    314 	nallfiles = onallfiles;
    315 
    316 	err = 0;
    317 	TAILQ_FOREACH(fi, &allfiles, fi_next) {
    318 
    319 		/* Skip files that generated counted-device complaints. */
    320 		if (fi->fi_flags & FI_HIDDEN)
    321 			continue;
    322 
    323 		if (fi->fi_optx != NULL) {
    324 			if (fi->fi_optx->cx_type == CX_ATOM) {
    325 				addfiletoattr(fi->fi_optx->cx_u.atom, fi);
    326 			}
    327 			flathead = NULL;
    328 			flatp = &flathead;
    329 			sel = expr_eval(fi->fi_optx,
    330 			    fi->fi_flags & FI_NEEDSCOUNT ? fixcount :
    331 			    fi->fi_flags & FI_NEEDSFLAG ? fixfsel :
    332 			    fixsel,
    333 			    &flatp);
    334 			fi->fi_optf = flathead;
    335 			if (!sel)
    336 				continue;
    337 		}
    338 		if (fi->fi_attr && fi->fi_attr->a_deselected) {
    339 			CFGDBG(5, "file `%s' deselected because attr `%s' was",
    340 			    fi->fi_path, fi->fi_attr->a_name);
    341 			continue;
    342 		}
    343 
    344 		/* We like this file.  Make sure it generates a unique .o. */
    345 		if (ht_insert(basetab, fi->fi_base, fi)) {
    346 			if ((ofi = ht_lookup(basetab, fi->fi_base)) == NULL)
    347 				panic("fixfiles ht_lookup(%s)", fi->fi_base);
    348 			/*
    349 			 * If the new file comes from a different source,
    350 			 * allow the new one to override the old one.
    351 			 */
    352 			if (fi->fi_path != ofi->fi_path) {
    353 				if (ht_replace(basetab, fi->fi_base, fi) != 1)
    354 					panic("fixfiles ht_replace(%s)",
    355 					    fi->fi_base);
    356 				ofi->fi_flags &= (u_char)~FI_SEL;
    357 				ofi->fi_flags |= FI_HIDDEN;
    358 			} else {
    359 				cfgxerror(fi->fi_where.w_srcfile, fi->fi_where.w_srcline,
    360 				    "object file collision on %s.o, from %s",
    361 				    fi->fi_base, fi->fi_path);
    362 				cfgxerror(ofi->fi_where.w_srcfile, ofi->fi_where.w_srcline,
    363 				    "here is the previous file: %s",
    364 				    ofi->fi_path);
    365 				err = 1;
    366 			}
    367 		}
    368 		fi->fi_flags |= FI_SEL;
    369 		nselfiles++;
    370 		CFGDBG(3, "file selected `%s'", fi->fi_path);
    371 
    372 		/* Add other files to the default "netbsd" attribute. */
    373 		if (fi->fi_attr == NULL) {
    374 			addfiletoattr(allattr.a_name, fi);
    375 		}
    376 		CFGDBG(3, "file `%s' belongs to attr `%s'", fi->fi_path,
    377 		    fi->fi_attr->a_name);
    378 	}
    379 
    380 	/* Order files. */
    381 	selfiles = malloc(nselfiles * sizeof(fi));
    382 	unsigned i = 0;
    383 	TAILQ_FOREACH(fi, &allfiles, fi_next) {
    384 		if ((fi->fi_flags & FI_SEL) == 0)
    385 			continue;
    386 		selfiles[i++] = fi;
    387 	}
    388 	assert(i <= nselfiles);
    389 	nselfiles = i;
    390 	qsort(selfiles, nselfiles, (unsigned)sizeof(fi), cmpfiles);
    391 	return (err);
    392 }
    393 
    394 
    395 /*
    396  * We have finished reading everything.  Tack the devsws down: calculate
    397  * selection.
    398  */
    399 int
    400 fixdevsw(void)
    401 {
    402 	int error;
    403 	struct devm *dm, *res;
    404 	struct hashtab *fixdevmtab;
    405 	char mstr[16];
    406 
    407 	error = 0;
    408 	fixdevmtab = ht_new();
    409 
    410 	TAILQ_FOREACH(dm, &alldevms, dm_next) {
    411 		res = ht_lookup(fixdevmtab, intern(dm->dm_name));
    412 		if (res != NULL) {
    413 			if (res->dm_cmajor != dm->dm_cmajor ||
    414 			    res->dm_bmajor != dm->dm_bmajor) {
    415 				cfgxerror(res->dm_where.w_srcfile,
    416 				    res->dm_where.w_srcline,
    417 				    "device-major '%s' "
    418 				    "block %d, char %d redefined"
    419 				    " at %s:%d as block %d, char %d",
    420 				    res->dm_name,
    421 				    res->dm_bmajor, res->dm_cmajor,
    422 				    dm->dm_where.w_srcfile, dm->dm_where.w_srcline,
    423 				    dm->dm_bmajor, dm->dm_cmajor);
    424 			} else {
    425 				cfgxerror(res->dm_where.w_srcfile,
    426 				    res->dm_where.w_srcline,
    427 				    "device-major '%s' "
    428 				    "(block %d, char %d) duplicated"
    429 				    " at %s:%d",
    430 				    dm->dm_name, dm->dm_bmajor,
    431 				    dm->dm_cmajor,
    432 				    dm->dm_where.w_srcfile,
    433 				    dm->dm_where.w_srcline);
    434 			}
    435 			error = 1;
    436 			goto out;
    437 		}
    438 		if (ht_insert(fixdevmtab, intern(dm->dm_name), dm)) {
    439 			panic("fixdevsw: %s char %d block %d",
    440 			      dm->dm_name, dm->dm_cmajor, dm->dm_bmajor);
    441 		}
    442 
    443 		if (dm->dm_opts != NULL &&
    444 		    !expr_eval(dm->dm_opts, fixsel, NULL))
    445 			continue;
    446 
    447 		if (dm->dm_cmajor != NODEVMAJOR) {
    448 			if (ht_lookup(cdevmtab, intern(dm->dm_name)) != NULL) {
    449 				cfgxerror(dm->dm_where.w_srcfile,
    450 				    dm->dm_where.w_srcline,
    451 				   "device-major of character device '%s' "
    452 				   "is already defined", dm->dm_name);
    453 				error = 1;
    454 				goto out;
    455 			}
    456 			(void)snprintf(mstr, sizeof(mstr), "%d", dm->dm_cmajor);
    457 			if (ht_lookup(cdevmtab, intern(mstr)) != NULL) {
    458 				cfgxerror(dm->dm_where.w_srcfile, dm->dm_where.w_srcline,
    459 				       "device-major of character major '%d' "
    460 				       "is already defined", dm->dm_cmajor);
    461 				error = 1;
    462 				goto out;
    463 			}
    464 			if (ht_insert(cdevmtab, intern(dm->dm_name), dm) ||
    465 			    ht_insert(cdevmtab, intern(mstr), dm)) {
    466 				panic("fixdevsw: %s character major %d",
    467 				      dm->dm_name, dm->dm_cmajor);
    468 			}
    469 		}
    470 		if (dm->dm_bmajor != NODEVMAJOR) {
    471 			if (ht_lookup(bdevmtab, intern(dm->dm_name)) != NULL) {
    472 				cfgxerror(dm->dm_where.w_srcfile, dm->dm_where.w_srcline,
    473 				       "device-major of block device '%s' "
    474 				       "is already defined", dm->dm_name);
    475 				error = 1;
    476 				goto out;
    477 			}
    478 			(void)snprintf(mstr, sizeof(mstr), "%d", dm->dm_bmajor);
    479 			if (ht_lookup(bdevmtab, intern(mstr)) != NULL) {
    480 				cfgxerror(dm->dm_where.w_srcfile, dm->dm_where.w_srcline,
    481 				       "device-major of block major '%d' "
    482 				       "is already defined", dm->dm_bmajor);
    483 				error = 1;
    484 				goto out;
    485 			}
    486 			if (ht_insert(bdevmtab, intern(dm->dm_name), dm) ||
    487 			    ht_insert(bdevmtab, intern(mstr), dm)) {
    488 				panic("fixdevsw: %s block major %d",
    489 				      dm->dm_name, dm->dm_bmajor);
    490 			}
    491 		}
    492 	}
    493 
    494 out:
    495 	ht_free(fixdevmtab);
    496 	return (error);
    497 }
    498 
    499 /*
    500  * Called when evaluating a needs-count expression.  Make sure the
    501  * atom is a countable device.  The expression succeeds iff there
    502  * is at least one of them (note that while `xx*' will not always
    503  * set xx's d_umax > 0, you cannot mix '*' and needs-count).  The
    504  * mkheaders() routine wants a flattened, in-order list of the
    505  * atoms for `#define name value' lines, so we build that as we
    506  * are called to eval each atom.
    507  */
    508 static int
    509 fixcount(const char *name, void *context)
    510 {
    511 	struct nvlist ***p = context;
    512 	struct devbase *dev;
    513 	struct nvlist *nv;
    514 
    515 	dev = ht_lookup(devbasetab, name);
    516 	if (dev == NULL)	/* cannot occur here; we checked earlier */
    517 		panic("fixcount(%s)", name);
    518 	nv = newnv(name, NULL, NULL, dev->d_umax, NULL);
    519 	**p = nv;
    520 	*p = &nv->nv_next;
    521 	(void)ht_insert(needcnttab, name, nv);
    522 	return (dev->d_umax != 0);
    523 }
    524 
    525 /*
    526  * Called from fixfiles when eval'ing a selection expression for a
    527  * file that will generate a .h with flags.  We will need the flat list.
    528  */
    529 static int
    530 fixfsel(const char *name, void *context)
    531 {
    532 	struct nvlist ***p = context;
    533 	struct nvlist *nv;
    534 	int sel;
    535 
    536 	sel = ht_lookup(selecttab, name) != NULL;
    537 	nv = newnv(name, NULL, NULL, sel, NULL);
    538 	**p = nv;
    539 	*p = &nv->nv_next;
    540 	return (sel);
    541 }
    542 
    543 /*
    544  * As for fixfsel above, but we do not need the flat list.
    545  */
    546 static int
    547 /*ARGSUSED*/
    548 fixsel(const char *name, void *context)
    549 {
    550 
    551 	return (ht_lookup(selecttab, name) != NULL);
    552 }
    553 
    554 /*
    555  * Eval an expression tree.  Calls the given function on each node,
    556  * passing it the given context & the name; return value is &/|/! of
    557  * results of evaluating atoms.
    558  *
    559  * No short circuiting ever occurs.  fn must return 0 or 1 (otherwise
    560  * our mixing of C's bitwise & boolean here may give surprises).
    561  */
    562 int
    563 expr_eval(struct condexpr *expr, int (*fn)(const char *, void *), void *ctx)
    564 {
    565 	int lhs, rhs;
    566 
    567 	switch (expr->cx_type) {
    568 
    569 	case CX_ATOM:
    570 		return ((*fn)(expr->cx_atom, ctx));
    571 
    572 	case CX_NOT:
    573 		return (!expr_eval(expr->cx_not, fn, ctx));
    574 
    575 	case CX_AND:
    576 		lhs = expr_eval(expr->cx_and.left, fn, ctx);
    577 		rhs = expr_eval(expr->cx_and.right, fn, ctx);
    578 		return (lhs & rhs);
    579 
    580 	case CX_OR:
    581 		lhs = expr_eval(expr->cx_or.left, fn, ctx);
    582 		rhs = expr_eval(expr->cx_or.right, fn, ctx);
    583 		return (lhs | rhs);
    584 	}
    585 	panic("invalid condexpr type %d", (int)expr->cx_type);
    586 	/* NOTREACHED */
    587 	return (0);
    588 }
    589 
    590 #ifdef DEBUG
    591 /*
    592  * Print expression tree.
    593  */
    594 void
    595 prexpr(struct nvlist *expr)
    596 {
    597 	static void pr0();
    598 
    599 	printf("expr =");
    600 	pr0(expr);
    601 	printf("\n");
    602 	(void)fflush(stdout);
    603 }
    604 
    605 static void
    606 pr0(struct nvlist *e)
    607 {
    608 
    609 	switch (e->nv_num) {
    610 	case FX_ATOM:
    611 		printf(" %s", e->nv_name);
    612 		return;
    613 	case FX_NOT:
    614 		printf(" (!");
    615 		break;
    616 	case FX_AND:
    617 		printf(" (&");
    618 		break;
    619 	case FX_OR:
    620 		printf(" (|");
    621 		break;
    622 	default:
    623 		printf(" (?%lld?", e->nv_num);
    624 		break;
    625 	}
    626 	if (e->nv_ptr)
    627 		pr0(e->nv_ptr);
    628 	pr0(e->nv_next);
    629 	printf(")");
    630 }
    631 #endif
    632