xref: /openbsd-src/usr.bin/mandoc/mansearch.c (revision d4741794dd2f512d997014f8bd85fbb24d935059)
1 /*	$OpenBSD: mansearch.c,v 1.51 2016/08/01 10:32:39 schwarze Exp $ */
2 /*
3  * Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2013, 2014, 2015, 2016 Ingo Schwarze <schwarze@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/mman.h>
20 #include <sys/types.h>
21 
22 #include <assert.h>
23 #include <err.h>
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <glob.h>
27 #include <limits.h>
28 #include <regex.h>
29 #include <stdio.h>
30 #include <stdint.h>
31 #include <stddef.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35 
36 #include "mandoc.h"
37 #include "mandoc_aux.h"
38 #include "mandoc_ohash.h"
39 #include "manconf.h"
40 #include "mansearch.h"
41 #include "dbm.h"
42 
43 struct	expr {
44 	/* Used for terms: */
45 	struct dbm_match match;   /* Match type and expression. */
46 	uint64_t	 bits;    /* Type mask. */
47 	/* Used for OR and AND groups: */
48 	struct expr	*next;    /* Next child in the parent group. */
49 	struct expr	*child;   /* First child in this group. */
50 	enum { EXPR_TERM, EXPR_OR, EXPR_AND } type;
51 };
52 
53 const char *const mansearch_keynames[KEY_MAX] = {
54 	"arch",	"sec",	"Xr",	"Ar",	"Fa",	"Fl",	"Dv",	"Fn",
55 	"Ic",	"Pa",	"Cm",	"Li",	"Em",	"Cd",	"Va",	"Ft",
56 	"Tn",	"Er",	"Ev",	"Sy",	"Sh",	"In",	"Ss",	"Ox",
57 	"An",	"Mt",	"St",	"Bx",	"At",	"Nx",	"Fx",	"Lk",
58 	"Ms",	"Bsx",	"Dx",	"Rs",	"Vt",	"Lb",	"Nm",	"Nd"
59 };
60 
61 
62 static	struct ohash	*manmerge(struct expr *, struct ohash *);
63 static	struct ohash	*manmerge_term(struct expr *, struct ohash *);
64 static	struct ohash	*manmerge_or(struct expr *, struct ohash *);
65 static	struct ohash	*manmerge_and(struct expr *, struct ohash *);
66 static	char		*buildnames(const struct dbm_page *);
67 static	char		*buildoutput(size_t, int32_t);
68 static	size_t		 lstlen(const char *);
69 static	void		 lstcat(char *, size_t *, const char *);
70 static	int		 lstmatch(const char *, const char *);
71 static	struct expr	*exprcomp(const struct mansearch *,
72 				int, char *[], int *);
73 static	struct expr	*expr_and(const struct mansearch *,
74 				int, char *[], int *);
75 static	struct expr	*exprterm(const struct mansearch *,
76 				int, char *[], int *);
77 static	void		 exprfree(struct expr *);
78 static	int		 manpage_compare(const void *, const void *);
79 
80 
81 int
82 mansearch(const struct mansearch *search,
83 		const struct manpaths *paths,
84 		int argc, char *argv[],
85 		struct manpage **res, size_t *sz)
86 {
87 	char		 buf[PATH_MAX];
88 	struct dbm_res	*rp;
89 	struct expr	*e;
90 	struct dbm_page	*page;
91 	struct manpage	*mpage;
92 	struct ohash	*htab;
93 	size_t		 cur, i, maxres, outkey;
94 	unsigned int	 slot;
95 	int		 argi, chdir_status, getcwd_status, im;
96 
97 	argi = 0;
98 	if ((e = exprcomp(search, argc, argv, &argi)) == NULL) {
99 		*sz = 0;
100 		return 0;
101 	}
102 
103 	cur = maxres = 0;
104 	*res = NULL;
105 
106 	outkey = KEY_Nd;
107 	if (search->outkey != NULL)
108 		for (im = 0; im < KEY_MAX; im++)
109 			if (0 == strcasecmp(search->outkey,
110 			    mansearch_keynames[im])) {
111 				outkey = im;
112 				break;
113 			}
114 
115 	/*
116 	 * Remember the original working directory, if possible.
117 	 * This will be needed if the second or a later directory
118 	 * is given as a relative path.
119 	 * Do not error out if the current directory is not
120 	 * searchable: Maybe it won't be needed after all.
121 	 */
122 
123 	if (getcwd(buf, PATH_MAX) == NULL) {
124 		getcwd_status = 0;
125 		(void)strlcpy(buf, strerror(errno), sizeof(buf));
126 	} else
127 		getcwd_status = 1;
128 
129 	/*
130 	 * Loop over the directories (containing databases) for us to
131 	 * search.
132 	 * Don't let missing/bad databases/directories phase us.
133 	 * In each, try to open the resident database and, if it opens,
134 	 * scan it for our match expression.
135 	 */
136 
137 	chdir_status = 0;
138 	for (i = 0; i < paths->sz; i++) {
139 		if (chdir_status && paths->paths[i][0] != '/') {
140 			if ( ! getcwd_status) {
141 				warnx("%s: getcwd: %s", paths->paths[i], buf);
142 				continue;
143 			} else if (chdir(buf) == -1) {
144 				warn("%s", buf);
145 				continue;
146 			}
147 		}
148 		if (chdir(paths->paths[i]) == -1) {
149 			warn("%s", paths->paths[i]);
150 			continue;
151 		}
152 		chdir_status = 1;
153 
154 		if (dbm_open(MANDOC_DB) == -1) {
155 			warn("%s/%s", paths->paths[i], MANDOC_DB);
156 			continue;
157 		}
158 
159 		if ((htab = manmerge(e, NULL)) == NULL) {
160 			dbm_close();
161 			continue;
162 		}
163 
164 		for (rp = ohash_first(htab, &slot); rp != NULL;
165 		    rp = ohash_next(htab, &slot)) {
166 			page = dbm_page_get(rp->page);
167 
168 			if (lstmatch(search->sec, page->sect) == 0 ||
169 			    lstmatch(search->arch, page->arch) == 0)
170 				continue;
171 
172 			if (cur + 1 > maxres) {
173 				maxres += 1024;
174 				*res = mandoc_reallocarray(*res,
175 				    maxres, sizeof(**res));
176 			}
177 			mpage = *res + cur;
178 			mandoc_asprintf(&mpage->file, "%s/%s",
179 			    paths->paths[i], page->file + 1);
180 			mpage->names = buildnames(page);
181 			mpage->output = (int)outkey == KEY_Nd ?
182 			    mandoc_strdup(page->desc) :
183 			    buildoutput(outkey, page->addr);
184 			mpage->ipath = i;
185 			mpage->bits = rp->bits;
186 			mpage->sec = *page->sect - '0';
187 			if (mpage->sec < 0 || mpage->sec > 9)
188 				mpage->sec = 10;
189 			mpage->form = *page->file;
190 			free(rp);
191 			cur++;
192 		}
193 		ohash_delete(htab);
194 		free(htab);
195 		dbm_close();
196 
197 		/*
198 		 * In man(1) mode, prefer matches in earlier trees
199 		 * over matches in later trees.
200 		 */
201 
202 		if (cur && search->firstmatch)
203 			break;
204 	}
205 	qsort(*res, cur, sizeof(struct manpage), manpage_compare);
206 	if (chdir_status && getcwd_status && chdir(buf) == -1)
207 		warn("%s", buf);
208 	exprfree(e);
209 	*sz = cur;
210 	return 1;
211 }
212 
213 /*
214  * Merge the results for the expression tree rooted at e
215  * into the the result list htab.
216  */
217 static struct ohash *
218 manmerge(struct expr *e, struct ohash *htab)
219 {
220 	switch (e->type) {
221 	case EXPR_TERM:
222 		return manmerge_term(e, htab);
223 	case EXPR_OR:
224 		return manmerge_or(e->child, htab);
225 	case EXPR_AND:
226 		return manmerge_and(e->child, htab);
227 	default:
228 		abort();
229 	}
230 }
231 
232 static struct ohash *
233 manmerge_term(struct expr *e, struct ohash *htab)
234 {
235 	struct dbm_res	 res, *rp;
236 	uint64_t	 ib;
237 	unsigned int	 slot;
238 	int		 im;
239 
240 	if (htab == NULL) {
241 		htab = mandoc_malloc(sizeof(*htab));
242 		mandoc_ohash_init(htab, 4, offsetof(struct dbm_res, page));
243 	}
244 
245 	for (im = 0, ib = 1; im < KEY_MAX; im++, ib <<= 1) {
246 		if ((e->bits & ib) == 0)
247 			continue;
248 
249 		switch (ib) {
250 		case TYPE_arch:
251 			dbm_page_byarch(&e->match);
252 			break;
253 		case TYPE_sec:
254 			dbm_page_bysect(&e->match);
255 			break;
256 		case TYPE_Nm:
257 			dbm_page_byname(&e->match);
258 			break;
259 		case TYPE_Nd:
260 			dbm_page_bydesc(&e->match);
261 			break;
262 		default:
263 			dbm_page_bymacro(im - 2, &e->match);
264 			break;
265 		}
266 
267 		/*
268 		 * When hashing for deduplication, use the unique
269 		 * page ID itself instead of a hash function;
270 		 * that is quite efficient.
271 		 */
272 
273 		for (;;) {
274 			res = dbm_page_next();
275 			if (res.page == -1)
276 				break;
277 			slot = ohash_lookup_memory(htab,
278 			    (char *)&res, sizeof(res.page), res.page);
279 			if ((rp = ohash_find(htab, slot)) != NULL) {
280 				rp->bits |= res.bits;
281 				continue;
282 			}
283 			rp = mandoc_malloc(sizeof(*rp));
284 			*rp = res;
285 			ohash_insert(htab, slot, rp);
286 		}
287 	}
288 	return htab;
289 }
290 
291 static struct ohash *
292 manmerge_or(struct expr *e, struct ohash *htab)
293 {
294 	while (e != NULL) {
295 		htab = manmerge(e, htab);
296 		e = e->next;
297 	}
298 	return htab;
299 }
300 
301 static struct ohash *
302 manmerge_and(struct expr *e, struct ohash *htab)
303 {
304 	struct ohash	*hand, *h1, *h2;
305 	struct dbm_res	*res;
306 	unsigned int	 slot1, slot2;
307 
308 	/* Evaluate the first term of the AND clause. */
309 
310 	hand = manmerge(e, NULL);
311 
312 	while ((e = e->next) != NULL) {
313 
314 		/* Evaluate the next term and prepare for ANDing. */
315 
316 		h2 = manmerge(e, NULL);
317 		if (ohash_entries(h2) < ohash_entries(hand)) {
318 			h1 = h2;
319 			h2 = hand;
320 		} else
321 			h1 = hand;
322 		hand = mandoc_malloc(sizeof(*hand));
323 		mandoc_ohash_init(hand, 4, offsetof(struct dbm_res, page));
324 
325 		/* Keep all pages that are in both result sets. */
326 
327 		for (res = ohash_first(h1, &slot1); res != NULL;
328 		    res = ohash_next(h1, &slot1)) {
329 			if (ohash_find(h2, ohash_lookup_memory(h2,
330 			    (char *)res, sizeof(res->page),
331 			    res->page)) == NULL)
332 				free(res);
333 			else
334 				ohash_insert(hand, ohash_lookup_memory(hand,
335 				    (char *)res, sizeof(res->page),
336 				    res->page), res);
337 		}
338 
339 		/* Discard the merged results. */
340 
341 		for (res = ohash_first(h2, &slot2); res != NULL;
342 		    res = ohash_next(h2, &slot2))
343 			free(res);
344 		ohash_delete(h2);
345 		free(h2);
346 		ohash_delete(h1);
347 		free(h1);
348 	}
349 
350 	/* Merge the result of the AND into htab. */
351 
352 	if (htab == NULL)
353 		return hand;
354 
355 	for (res = ohash_first(hand, &slot1); res != NULL;
356 	    res = ohash_next(hand, &slot1)) {
357 		slot2 = ohash_lookup_memory(htab,
358 		    (char *)res, sizeof(res->page), res->page);
359 		if (ohash_find(htab, slot2) == NULL)
360 			ohash_insert(htab, slot2, res);
361 		else
362 			free(res);
363 	}
364 
365 	/* Discard the merged result. */
366 
367 	ohash_delete(hand);
368 	free(hand);
369 	return htab;
370 }
371 
372 void
373 mansearch_free(struct manpage *res, size_t sz)
374 {
375 	size_t	 i;
376 
377 	for (i = 0; i < sz; i++) {
378 		free(res[i].file);
379 		free(res[i].names);
380 		free(res[i].output);
381 	}
382 	free(res);
383 }
384 
385 static int
386 manpage_compare(const void *vp1, const void *vp2)
387 {
388 	const struct manpage	*mp1, *mp2;
389 	int			 diff;
390 
391 	mp1 = vp1;
392 	mp2 = vp2;
393 	return (diff = mp2->bits - mp1->bits) ? diff :
394 	    (diff = mp1->sec - mp2->sec) ? diff :
395 	    strcasecmp(mp1->names, mp2->names);
396 }
397 
398 static char *
399 buildnames(const struct dbm_page *page)
400 {
401 	char	*buf;
402 	size_t	 i, sz;
403 
404 	sz = lstlen(page->name) + 1 + lstlen(page->sect) +
405 	    (page->arch == NULL ? 0 : 1 + lstlen(page->arch)) + 2;
406 	buf = mandoc_malloc(sz);
407 	i = 0;
408 	lstcat(buf, &i, page->name);
409 	buf[i++] = '(';
410 	lstcat(buf, &i, page->sect);
411 	if (page->arch != NULL) {
412 		buf[i++] = '/';
413 		lstcat(buf, &i, page->arch);
414 	}
415 	buf[i++] = ')';
416 	buf[i++] = '\0';
417 	assert(i == sz);
418 	return buf;
419 }
420 
421 /*
422  * Count the buffer space needed to print the NUL-terminated
423  * list of NUL-terminated strings, when printing two separator
424  * characters between strings.
425  */
426 static size_t
427 lstlen(const char *cp)
428 {
429 	size_t	 sz;
430 
431 	for (sz = 0;; sz++) {
432 		if (cp[0] == '\0') {
433 			if (cp[1] == '\0')
434 				break;
435 			sz++;
436 		} else if (cp[0] < ' ')
437 			sz--;
438 		cp++;
439 	}
440 	return sz;
441 }
442 
443 /*
444  * Print the NUL-terminated list of NUL-terminated strings
445  * into the buffer, seperating strings with a comma and a blank.
446  */
447 static void
448 lstcat(char *buf, size_t *i, const char *cp)
449 {
450 	for (;;) {
451 		if (cp[0] == '\0') {
452 			if (cp[1] == '\0')
453 				break;
454 			buf[(*i)++] = ',';
455 			buf[(*i)++] = ' ';
456 		} else if (cp[0] >= ' ')
457 			buf[(*i)++] = cp[0];
458 		cp++;
459 	}
460 }
461 
462 /*
463  * Return 1 if the string *want occurs in any of the strings
464  * in the NUL-terminated string list *have, or 0 otherwise.
465  * If either argument is NULL or empty, assume no filtering
466  * is desired and return 1.
467  */
468 static int
469 lstmatch(const char *want, const char *have)
470 {
471         if (want == NULL || have == NULL || *have == '\0')
472                 return 1;
473         while (*have != '\0') {
474                 if (strcasestr(have, want) != NULL)
475                         return 1;
476                 have = strchr(have, '\0') + 1;
477         }
478         return 0;
479 }
480 
481 /*
482  * Build a list of values taken by the macro im
483  * in the manual page with big-endian address addr.
484  */
485 static char *
486 buildoutput(size_t im, int32_t addr)
487 {
488 	const char	*oldoutput, *sep;
489 	char		*output, *newoutput, *value;
490 
491 	output = NULL;
492 	dbm_macro_bypage(im - 2, addr);
493 	while ((value = dbm_macro_next()) != NULL) {
494 		if (output == NULL) {
495 			oldoutput = "";
496 			sep = "";
497 		} else {
498 			oldoutput = output;
499 			sep = " # ";
500 		}
501 		mandoc_asprintf(&newoutput, "%s%s%s", oldoutput, sep, value);
502 		free(output);
503 		output = newoutput;
504 	}
505 	return output;
506 }
507 
508 /*
509  * Compile a set of string tokens into an expression.
510  * Tokens in "argv" are assumed to be individual expression atoms (e.g.,
511  * "(", "foo=bar", etc.).
512  */
513 static struct expr *
514 exprcomp(const struct mansearch *search, int argc, char *argv[], int *argi)
515 {
516 	struct expr	*parent, *child;
517 	int		 needterm, nested;
518 
519 	if ((nested = *argi) == argc)
520 		return NULL;
521 	needterm = 1;
522 	parent = child = NULL;
523 	while (*argi < argc) {
524 		if (strcmp(")", argv[*argi]) == 0) {
525 			if (needterm)
526 				warnx("missing term "
527 				    "before closing parenthesis");
528 			needterm = 0;
529 			if (nested)
530 				break;
531 			warnx("ignoring unmatched right parenthesis");
532 			++*argi;
533 			continue;
534 		}
535 		if (strcmp("-o", argv[*argi]) == 0) {
536 			if (needterm) {
537 				if (*argi > 0)
538 					warnx("ignoring -o after %s",
539 					    argv[*argi - 1]);
540 				else
541 					warnx("ignoring initial -o");
542 			}
543 			needterm = 1;
544 			++*argi;
545 			continue;
546 		}
547 		needterm = 0;
548 		if (child == NULL) {
549 			child = expr_and(search, argc, argv, argi);
550 			continue;
551 		}
552 		if (parent == NULL) {
553 			parent = mandoc_calloc(1, sizeof(*parent));
554 			parent->type = EXPR_OR;
555 			parent->next = NULL;
556 			parent->child = child;
557 		}
558 		child->next = expr_and(search, argc, argv, argi);
559 		child = child->next;
560 	}
561 	if (needterm && *argi)
562 		warnx("ignoring trailing %s", argv[*argi - 1]);
563 	return parent == NULL ? child : parent;
564 }
565 
566 static struct expr *
567 expr_and(const struct mansearch *search, int argc, char *argv[], int *argi)
568 {
569 	struct expr	*parent, *child;
570 	int		 needterm;
571 
572 	needterm = 1;
573 	parent = child = NULL;
574 	while (*argi < argc) {
575 		if (strcmp(")", argv[*argi]) == 0) {
576 			if (needterm)
577 				warnx("missing term "
578 				    "before closing parenthesis");
579 			needterm = 0;
580 			break;
581 		}
582 		if (strcmp("-o", argv[*argi]) == 0)
583 			break;
584 		if (strcmp("-a", argv[*argi]) == 0) {
585 			if (needterm) {
586 				if (*argi > 0)
587 					warnx("ignoring -a after %s",
588 					    argv[*argi - 1]);
589 				else
590 					warnx("ignoring initial -a");
591 			}
592 			needterm = 1;
593 			++*argi;
594 			continue;
595 		}
596 		if (needterm == 0)
597 			break;
598 		if (child == NULL) {
599 			child = exprterm(search, argc, argv, argi);
600 			if (child != NULL)
601 				needterm = 0;
602 			continue;
603 		}
604 		needterm = 0;
605 		if (parent == NULL) {
606 			parent = mandoc_calloc(1, sizeof(*parent));
607 			parent->type = EXPR_AND;
608 			parent->next = NULL;
609 			parent->child = child;
610 		}
611 		child->next = exprterm(search, argc, argv, argi);
612 		if (child->next != NULL) {
613 			child = child->next;
614 			needterm = 0;
615 		}
616 	}
617 	if (needterm && *argi)
618 		warnx("ignoring trailing %s", argv[*argi - 1]);
619 	return parent == NULL ? child : parent;
620 }
621 
622 static struct expr *
623 exprterm(const struct mansearch *search, int argc, char *argv[], int *argi)
624 {
625 	char		 errbuf[BUFSIZ];
626 	struct expr	*e;
627 	char		*key, *val;
628 	uint64_t	 iterbit;
629 	int		 cs, i, irc;
630 
631 	if (strcmp("(", argv[*argi]) == 0) {
632 		++*argi;
633 		e = exprcomp(search, argc, argv, argi);
634 		if (*argi < argc) {
635 			assert(strcmp(")", argv[*argi]) == 0);
636 			++*argi;
637 		} else
638 			warnx("unclosed parenthesis");
639 		return e;
640 	}
641 
642 	e = mandoc_calloc(1, sizeof(*e));
643 	e->type = EXPR_TERM;
644 	e->bits = 0;
645 	e->next = NULL;
646 	e->child = NULL;
647 
648 	if (search->argmode == ARG_NAME) {
649 		e->bits = TYPE_Nm;
650 		e->match.type = DBM_EXACT;
651 		e->match.str = argv[(*argi)++];
652 		return e;
653 	}
654 
655 	/*
656 	 * Separate macro keys from search string.
657 	 * If needed, request regular expression handling.
658 	 */
659 
660 	if (search->argmode == ARG_WORD) {
661 		e->bits = TYPE_Nm;
662 		e->match.type = DBM_REGEX;
663 		mandoc_asprintf(&val, "[[:<:]]%s[[:>:]]", argv[*argi]);
664 		cs = 0;
665 	} else if ((val = strpbrk(argv[*argi], "=~")) == NULL) {
666 		e->bits = TYPE_Nm | TYPE_Nd;
667 		e->match.type = DBM_SUB;
668 		e->match.str = argv[*argi];
669 	} else {
670 		if (val == argv[*argi])
671 			e->bits = TYPE_Nm | TYPE_Nd;
672 		if (*val == '=') {
673 			e->match.type = DBM_SUB;
674 			e->match.str = val + 1;
675 		} else
676 			e->match.type = DBM_REGEX;
677 		*val++ = '\0';
678 		if (strstr(argv[*argi], "arch") != NULL)
679 			cs = 0;
680 	}
681 
682 	/* Compile regular expressions. */
683 
684 	if (e->match.type == DBM_REGEX) {
685 		e->match.re = mandoc_malloc(sizeof(*e->match.re));
686 		irc = regcomp(e->match.re, val,
687 		    REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE));
688 		if (irc) {
689 			regerror(irc, e->match.re, errbuf, sizeof(errbuf));
690 			warnx("regcomp /%s/: %s", val, errbuf);
691 		}
692 		if (search->argmode == ARG_WORD)
693 			free(val);
694 		if (irc) {
695 			free(e->match.re);
696 			free(e);
697 			++*argi;
698 			return NULL;
699 		}
700 	}
701 
702 	if (e->bits) {
703 		++*argi;
704 		return e;
705 	}
706 
707 	/*
708 	 * Parse out all possible fields.
709 	 * If the field doesn't resolve, bail.
710 	 */
711 
712 	while (NULL != (key = strsep(&argv[*argi], ","))) {
713 		if ('\0' == *key)
714 			continue;
715 		for (i = 0, iterbit = 1; i < KEY_MAX; i++, iterbit <<= 1) {
716 			if (0 == strcasecmp(key, mansearch_keynames[i])) {
717 				e->bits |= iterbit;
718 				break;
719 			}
720 		}
721 		if (i == KEY_MAX) {
722 			if (strcasecmp(key, "any"))
723 				warnx("treating unknown key "
724 				    "\"%s\" as \"any\"", key);
725 			e->bits |= ~0ULL;
726 		}
727 	}
728 
729 	++*argi;
730 	return e;
731 }
732 
733 static void
734 exprfree(struct expr *e)
735 {
736 	if (e->next != NULL)
737 		exprfree(e->next);
738 	if (e->child != NULL)
739 		exprfree(e->child);
740 	free(e);
741 }
742