1 /* $OpenBSD: mansearch.c,v 1.58 2017/07/19 14:05:09 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2013-2017 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, struct dbm_page *); 68 static size_t lstlen(const char *, size_t); 69 static void lstcat(char *, size_t *, const char *, 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 if (res != NULL) 105 *res = NULL; 106 107 outkey = KEY_Nd; 108 if (search->outkey != NULL) 109 for (im = 0; im < KEY_MAX; im++) 110 if (0 == strcasecmp(search->outkey, 111 mansearch_keynames[im])) { 112 outkey = im; 113 break; 114 } 115 116 /* 117 * Remember the original working directory, if possible. 118 * This will be needed if the second or a later directory 119 * is given as a relative path. 120 * Do not error out if the current directory is not 121 * searchable: Maybe it won't be needed after all. 122 */ 123 124 if (getcwd(buf, PATH_MAX) == NULL) { 125 getcwd_status = 0; 126 (void)strlcpy(buf, strerror(errno), sizeof(buf)); 127 } else 128 getcwd_status = 1; 129 130 /* 131 * Loop over the directories (containing databases) for us to 132 * search. 133 * Don't let missing/bad databases/directories phase us. 134 * In each, try to open the resident database and, if it opens, 135 * scan it for our match expression. 136 */ 137 138 chdir_status = 0; 139 for (i = 0; i < paths->sz; i++) { 140 if (chdir_status && paths->paths[i][0] != '/') { 141 if ( ! getcwd_status) { 142 warnx("%s: getcwd: %s", paths->paths[i], buf); 143 continue; 144 } else if (chdir(buf) == -1) { 145 warn("%s", buf); 146 continue; 147 } 148 } 149 if (chdir(paths->paths[i]) == -1) { 150 warn("%s", paths->paths[i]); 151 continue; 152 } 153 chdir_status = 1; 154 155 if (dbm_open(MANDOC_DB) == -1) { 156 if (errno != ENOENT) 157 warn("%s/%s", paths->paths[i], MANDOC_DB); 158 continue; 159 } 160 161 if ((htab = manmerge(e, NULL)) == NULL) { 162 dbm_close(); 163 continue; 164 } 165 166 for (rp = ohash_first(htab, &slot); rp != NULL; 167 rp = ohash_next(htab, &slot)) { 168 page = dbm_page_get(rp->page); 169 170 if (lstmatch(search->sec, page->sect) == 0 || 171 lstmatch(search->arch, page->arch) == 0) 172 continue; 173 174 if (res == NULL) { 175 cur = 1; 176 break; 177 } 178 if (cur + 1 > maxres) { 179 maxres += 1024; 180 *res = mandoc_reallocarray(*res, 181 maxres, sizeof(**res)); 182 } 183 mpage = *res + cur; 184 mandoc_asprintf(&mpage->file, "%s/%s", 185 paths->paths[i], page->file + 1); 186 mpage->names = buildnames(page); 187 mpage->output = buildoutput(outkey, page); 188 mpage->ipath = i; 189 mpage->bits = rp->bits; 190 mpage->sec = *page->sect - '0'; 191 if (mpage->sec < 0 || mpage->sec > 9) 192 mpage->sec = 10; 193 mpage->form = *page->file; 194 free(rp); 195 cur++; 196 } 197 ohash_delete(htab); 198 free(htab); 199 dbm_close(); 200 201 /* 202 * In man(1) mode, prefer matches in earlier trees 203 * over matches in later trees. 204 */ 205 206 if (cur && search->firstmatch) 207 break; 208 } 209 if (res != NULL) 210 qsort(*res, cur, sizeof(struct manpage), manpage_compare); 211 if (chdir_status && getcwd_status && chdir(buf) == -1) 212 warn("%s", buf); 213 exprfree(e); 214 *sz = cur; 215 return res != NULL || cur; 216 } 217 218 /* 219 * Merge the results for the expression tree rooted at e 220 * into the the result list htab. 221 */ 222 static struct ohash * 223 manmerge(struct expr *e, struct ohash *htab) 224 { 225 switch (e->type) { 226 case EXPR_TERM: 227 return manmerge_term(e, htab); 228 case EXPR_OR: 229 return manmerge_or(e->child, htab); 230 case EXPR_AND: 231 return manmerge_and(e->child, htab); 232 default: 233 abort(); 234 } 235 } 236 237 static struct ohash * 238 manmerge_term(struct expr *e, struct ohash *htab) 239 { 240 struct dbm_res res, *rp; 241 uint64_t ib; 242 unsigned int slot; 243 int im; 244 245 if (htab == NULL) { 246 htab = mandoc_malloc(sizeof(*htab)); 247 mandoc_ohash_init(htab, 4, offsetof(struct dbm_res, page)); 248 } 249 250 for (im = 0, ib = 1; im < KEY_MAX; im++, ib <<= 1) { 251 if ((e->bits & ib) == 0) 252 continue; 253 254 switch (ib) { 255 case TYPE_arch: 256 dbm_page_byarch(&e->match); 257 break; 258 case TYPE_sec: 259 dbm_page_bysect(&e->match); 260 break; 261 case TYPE_Nm: 262 dbm_page_byname(&e->match); 263 break; 264 case TYPE_Nd: 265 dbm_page_bydesc(&e->match); 266 break; 267 default: 268 dbm_page_bymacro(im - 2, &e->match); 269 break; 270 } 271 272 /* 273 * When hashing for deduplication, use the unique 274 * page ID itself instead of a hash function; 275 * that is quite efficient. 276 */ 277 278 for (;;) { 279 res = dbm_page_next(); 280 if (res.page == -1) 281 break; 282 slot = ohash_lookup_memory(htab, 283 (char *)&res, sizeof(res.page), res.page); 284 if ((rp = ohash_find(htab, slot)) != NULL) { 285 rp->bits |= res.bits; 286 continue; 287 } 288 rp = mandoc_malloc(sizeof(*rp)); 289 *rp = res; 290 ohash_insert(htab, slot, rp); 291 } 292 } 293 return htab; 294 } 295 296 static struct ohash * 297 manmerge_or(struct expr *e, struct ohash *htab) 298 { 299 while (e != NULL) { 300 htab = manmerge(e, htab); 301 e = e->next; 302 } 303 return htab; 304 } 305 306 static struct ohash * 307 manmerge_and(struct expr *e, struct ohash *htab) 308 { 309 struct ohash *hand, *h1, *h2; 310 struct dbm_res *res; 311 unsigned int slot1, slot2; 312 313 /* Evaluate the first term of the AND clause. */ 314 315 hand = manmerge(e, NULL); 316 317 while ((e = e->next) != NULL) { 318 319 /* Evaluate the next term and prepare for ANDing. */ 320 321 h2 = manmerge(e, NULL); 322 if (ohash_entries(h2) < ohash_entries(hand)) { 323 h1 = h2; 324 h2 = hand; 325 } else 326 h1 = hand; 327 hand = mandoc_malloc(sizeof(*hand)); 328 mandoc_ohash_init(hand, 4, offsetof(struct dbm_res, page)); 329 330 /* Keep all pages that are in both result sets. */ 331 332 for (res = ohash_first(h1, &slot1); res != NULL; 333 res = ohash_next(h1, &slot1)) { 334 if (ohash_find(h2, ohash_lookup_memory(h2, 335 (char *)res, sizeof(res->page), 336 res->page)) == NULL) 337 free(res); 338 else 339 ohash_insert(hand, ohash_lookup_memory(hand, 340 (char *)res, sizeof(res->page), 341 res->page), res); 342 } 343 344 /* Discard the merged results. */ 345 346 for (res = ohash_first(h2, &slot2); res != NULL; 347 res = ohash_next(h2, &slot2)) 348 free(res); 349 ohash_delete(h2); 350 free(h2); 351 ohash_delete(h1); 352 free(h1); 353 } 354 355 /* Merge the result of the AND into htab. */ 356 357 if (htab == NULL) 358 return hand; 359 360 for (res = ohash_first(hand, &slot1); res != NULL; 361 res = ohash_next(hand, &slot1)) { 362 slot2 = ohash_lookup_memory(htab, 363 (char *)res, sizeof(res->page), res->page); 364 if (ohash_find(htab, slot2) == NULL) 365 ohash_insert(htab, slot2, res); 366 else 367 free(res); 368 } 369 370 /* Discard the merged result. */ 371 372 ohash_delete(hand); 373 free(hand); 374 return htab; 375 } 376 377 void 378 mansearch_free(struct manpage *res, size_t sz) 379 { 380 size_t i; 381 382 for (i = 0; i < sz; i++) { 383 free(res[i].file); 384 free(res[i].names); 385 free(res[i].output); 386 } 387 free(res); 388 } 389 390 static int 391 manpage_compare(const void *vp1, const void *vp2) 392 { 393 const struct manpage *mp1, *mp2; 394 const char *cp1, *cp2; 395 size_t sz1, sz2; 396 int diff; 397 398 mp1 = vp1; 399 mp2 = vp2; 400 if ((diff = mp2->bits - mp1->bits) || 401 (diff = mp1->sec - mp2->sec)) 402 return diff; 403 404 /* Fall back to alphabetic ordering of names. */ 405 sz1 = strcspn(mp1->names, "("); 406 sz2 = strcspn(mp2->names, "("); 407 if (sz1 < sz2) 408 sz1 = sz2; 409 if ((diff = strncasecmp(mp1->names, mp2->names, sz1))) 410 return diff; 411 412 /* For identical names and sections, prefer arch-dependent. */ 413 cp1 = strchr(mp1->names + sz1, '/'); 414 cp2 = strchr(mp2->names + sz2, '/'); 415 return cp1 != NULL && cp2 != NULL ? strcasecmp(cp1, cp2) : 416 cp1 != NULL ? -1 : cp2 != NULL ? 1 : 0; 417 } 418 419 static char * 420 buildnames(const struct dbm_page *page) 421 { 422 char *buf; 423 size_t i, sz; 424 425 sz = lstlen(page->name, 2) + 1 + lstlen(page->sect, 2) + 426 (page->arch == NULL ? 0 : 1 + lstlen(page->arch, 2)) + 2; 427 buf = mandoc_malloc(sz); 428 i = 0; 429 lstcat(buf, &i, page->name, ", "); 430 buf[i++] = '('; 431 lstcat(buf, &i, page->sect, ", "); 432 if (page->arch != NULL) { 433 buf[i++] = '/'; 434 lstcat(buf, &i, page->arch, ", "); 435 } 436 buf[i++] = ')'; 437 buf[i++] = '\0'; 438 assert(i == sz); 439 return buf; 440 } 441 442 /* 443 * Count the buffer space needed to print the NUL-terminated 444 * list of NUL-terminated strings, when printing sep separator 445 * characters between strings. 446 */ 447 static size_t 448 lstlen(const char *cp, size_t sep) 449 { 450 size_t sz; 451 452 for (sz = 0;; sz++) { 453 if (cp[0] == '\0') { 454 if (cp[1] == '\0') 455 break; 456 sz += sep - 1; 457 } else if (cp[0] < ' ') 458 sz--; 459 cp++; 460 } 461 return sz; 462 } 463 464 /* 465 * Print the NUL-terminated list of NUL-terminated strings 466 * into the buffer, seperating strings with sep. 467 */ 468 static void 469 lstcat(char *buf, size_t *i, const char *cp, const char *sep) 470 { 471 const char *s; 472 473 for (;;) { 474 if (cp[0] == '\0') { 475 if (cp[1] == '\0') 476 break; 477 s = sep; 478 while (*s != '\0') 479 buf[(*i)++] = *s++; 480 } else if (cp[0] >= ' ') 481 buf[(*i)++] = cp[0]; 482 cp++; 483 } 484 } 485 486 /* 487 * Return 1 if the string *want occurs in any of the strings 488 * in the NUL-terminated string list *have, or 0 otherwise. 489 * If either argument is NULL or empty, assume no filtering 490 * is desired and return 1. 491 */ 492 static int 493 lstmatch(const char *want, const char *have) 494 { 495 if (want == NULL || have == NULL || *have == '\0') 496 return 1; 497 while (*have != '\0') { 498 if (strcasestr(have, want) != NULL) 499 return 1; 500 have = strchr(have, '\0') + 1; 501 } 502 return 0; 503 } 504 505 /* 506 * Build a list of values taken by the macro im in the manual page. 507 */ 508 static char * 509 buildoutput(size_t im, struct dbm_page *page) 510 { 511 const char *oldoutput, *sep, *input; 512 char *output, *newoutput, *value; 513 size_t sz, i; 514 515 switch (im) { 516 case KEY_Nd: 517 return mandoc_strdup(page->desc); 518 case KEY_Nm: 519 input = page->name; 520 break; 521 case KEY_sec: 522 input = page->sect; 523 break; 524 case KEY_arch: 525 input = page->arch; 526 if (input == NULL) 527 input = "all\0"; 528 break; 529 default: 530 input = NULL; 531 break; 532 } 533 534 if (input != NULL) { 535 sz = lstlen(input, 3) + 1; 536 output = mandoc_malloc(sz); 537 i = 0; 538 lstcat(output, &i, input, " # "); 539 output[i++] = '\0'; 540 assert(i == sz); 541 return output; 542 } 543 544 output = NULL; 545 dbm_macro_bypage(im - 2, page->addr); 546 while ((value = dbm_macro_next()) != NULL) { 547 if (output == NULL) { 548 oldoutput = ""; 549 sep = ""; 550 } else { 551 oldoutput = output; 552 sep = " # "; 553 } 554 mandoc_asprintf(&newoutput, "%s%s%s", oldoutput, sep, value); 555 free(output); 556 output = newoutput; 557 } 558 return output; 559 } 560 561 /* 562 * Compile a set of string tokens into an expression. 563 * Tokens in "argv" are assumed to be individual expression atoms (e.g., 564 * "(", "foo=bar", etc.). 565 */ 566 static struct expr * 567 exprcomp(const struct mansearch *search, int argc, char *argv[], int *argi) 568 { 569 struct expr *parent, *child; 570 int needterm, nested; 571 572 if ((nested = *argi) == argc) 573 return NULL; 574 needterm = 1; 575 parent = child = NULL; 576 while (*argi < argc) { 577 if (strcmp(")", argv[*argi]) == 0) { 578 if (needterm) 579 warnx("missing term " 580 "before closing parenthesis"); 581 needterm = 0; 582 if (nested) 583 break; 584 warnx("ignoring unmatched right parenthesis"); 585 ++*argi; 586 continue; 587 } 588 if (strcmp("-o", argv[*argi]) == 0) { 589 if (needterm) { 590 if (*argi > 0) 591 warnx("ignoring -o after %s", 592 argv[*argi - 1]); 593 else 594 warnx("ignoring initial -o"); 595 } 596 needterm = 1; 597 ++*argi; 598 continue; 599 } 600 needterm = 0; 601 if (child == NULL) { 602 child = expr_and(search, argc, argv, argi); 603 continue; 604 } 605 if (parent == NULL) { 606 parent = mandoc_calloc(1, sizeof(*parent)); 607 parent->type = EXPR_OR; 608 parent->next = NULL; 609 parent->child = child; 610 } 611 child->next = expr_and(search, argc, argv, argi); 612 child = child->next; 613 } 614 if (needterm && *argi) 615 warnx("ignoring trailing %s", argv[*argi - 1]); 616 return parent == NULL ? child : parent; 617 } 618 619 static struct expr * 620 expr_and(const struct mansearch *search, int argc, char *argv[], int *argi) 621 { 622 struct expr *parent, *child; 623 int needterm; 624 625 needterm = 1; 626 parent = child = NULL; 627 while (*argi < argc) { 628 if (strcmp(")", argv[*argi]) == 0) { 629 if (needterm) 630 warnx("missing term " 631 "before closing parenthesis"); 632 needterm = 0; 633 break; 634 } 635 if (strcmp("-o", argv[*argi]) == 0) 636 break; 637 if (strcmp("-a", argv[*argi]) == 0) { 638 if (needterm) { 639 if (*argi > 0) 640 warnx("ignoring -a after %s", 641 argv[*argi - 1]); 642 else 643 warnx("ignoring initial -a"); 644 } 645 needterm = 1; 646 ++*argi; 647 continue; 648 } 649 if (needterm == 0) 650 break; 651 if (child == NULL) { 652 child = exprterm(search, argc, argv, argi); 653 if (child != NULL) 654 needterm = 0; 655 continue; 656 } 657 needterm = 0; 658 if (parent == NULL) { 659 parent = mandoc_calloc(1, sizeof(*parent)); 660 parent->type = EXPR_AND; 661 parent->next = NULL; 662 parent->child = child; 663 } 664 child->next = exprterm(search, argc, argv, argi); 665 if (child->next != NULL) { 666 child = child->next; 667 needterm = 0; 668 } 669 } 670 if (needterm && *argi) 671 warnx("ignoring trailing %s", argv[*argi - 1]); 672 return parent == NULL ? child : parent; 673 } 674 675 static struct expr * 676 exprterm(const struct mansearch *search, int argc, char *argv[], int *argi) 677 { 678 char errbuf[BUFSIZ]; 679 struct expr *e; 680 char *key, *val; 681 uint64_t iterbit; 682 int cs, i, irc; 683 684 if (strcmp("(", argv[*argi]) == 0) { 685 ++*argi; 686 e = exprcomp(search, argc, argv, argi); 687 if (*argi < argc) { 688 assert(strcmp(")", argv[*argi]) == 0); 689 ++*argi; 690 } else 691 warnx("unclosed parenthesis"); 692 return e; 693 } 694 695 if (strcmp("-i", argv[*argi]) == 0 && *argi + 1 < argc) { 696 cs = 0; 697 ++*argi; 698 } else 699 cs = 1; 700 701 e = mandoc_calloc(1, sizeof(*e)); 702 e->type = EXPR_TERM; 703 e->bits = 0; 704 e->next = NULL; 705 e->child = NULL; 706 707 if (search->argmode == ARG_NAME) { 708 e->bits = TYPE_Nm; 709 e->match.type = DBM_EXACT; 710 e->match.str = argv[(*argi)++]; 711 return e; 712 } 713 714 /* 715 * Separate macro keys from search string. 716 * If needed, request regular expression handling. 717 */ 718 719 if (search->argmode == ARG_WORD) { 720 e->bits = TYPE_Nm; 721 e->match.type = DBM_REGEX; 722 mandoc_asprintf(&val, "[[:<:]]%s[[:>:]]", argv[*argi]); 723 cs = 0; 724 } else if ((val = strpbrk(argv[*argi], "=~")) == NULL) { 725 e->bits = TYPE_Nm | TYPE_Nd; 726 e->match.type = DBM_SUB; 727 e->match.str = argv[*argi]; 728 } else { 729 if (val == argv[*argi]) 730 e->bits = TYPE_Nm | TYPE_Nd; 731 if (*val == '=') { 732 e->match.type = DBM_SUB; 733 e->match.str = val + 1; 734 } else 735 e->match.type = DBM_REGEX; 736 *val++ = '\0'; 737 if (strstr(argv[*argi], "arch") != NULL) 738 cs = 0; 739 } 740 741 /* Compile regular expressions. */ 742 743 if (e->match.type == DBM_REGEX) { 744 e->match.re = mandoc_malloc(sizeof(*e->match.re)); 745 irc = regcomp(e->match.re, val, 746 REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE)); 747 if (irc) { 748 regerror(irc, e->match.re, errbuf, sizeof(errbuf)); 749 warnx("regcomp /%s/: %s", val, errbuf); 750 } 751 if (search->argmode == ARG_WORD) 752 free(val); 753 if (irc) { 754 free(e->match.re); 755 free(e); 756 ++*argi; 757 return NULL; 758 } 759 } 760 761 if (e->bits) { 762 ++*argi; 763 return e; 764 } 765 766 /* 767 * Parse out all possible fields. 768 * If the field doesn't resolve, bail. 769 */ 770 771 while (NULL != (key = strsep(&argv[*argi], ","))) { 772 if ('\0' == *key) 773 continue; 774 for (i = 0, iterbit = 1; i < KEY_MAX; i++, iterbit <<= 1) { 775 if (0 == strcasecmp(key, mansearch_keynames[i])) { 776 e->bits |= iterbit; 777 break; 778 } 779 } 780 if (i == KEY_MAX) { 781 if (strcasecmp(key, "any")) 782 warnx("treating unknown key " 783 "\"%s\" as \"any\"", key); 784 e->bits |= ~0ULL; 785 } 786 } 787 788 ++*argi; 789 return e; 790 } 791 792 static void 793 exprfree(struct expr *e) 794 { 795 if (e->next != NULL) 796 exprfree(e->next); 797 if (e->child != NULL) 798 exprfree(e->child); 799 free(e); 800 } 801