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