1*d9a51c35Sjmc /* $OpenBSD: mansearch.c,v 1.67 2022/12/26 19:16:02 jmc Exp $ */
2eea1c63dSschwarze /*
3eea1c63dSschwarze * Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv>
41ab4f06fSschwarze * Copyright (c) 2013-2018 Ingo Schwarze <schwarze@openbsd.org>
5eea1c63dSschwarze *
6eea1c63dSschwarze * Permission to use, copy, modify, and distribute this software for any
7eea1c63dSschwarze * purpose with or without fee is hereby granted, provided that the above
8eea1c63dSschwarze * copyright notice and this permission notice appear in all copies.
9eea1c63dSschwarze *
104de77decSschwarze * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11eea1c63dSschwarze * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
124de77decSschwarze * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13eea1c63dSschwarze * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14eea1c63dSschwarze * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15eea1c63dSschwarze * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16eea1c63dSschwarze * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17eea1c63dSschwarze */
180f10154cSschwarze
191d77badbSschwarze #include <sys/mman.h>
200f10154cSschwarze #include <sys/types.h>
210f10154cSschwarze
22eea1c63dSschwarze #include <assert.h>
23eba1598bSschwarze #include <err.h>
24cd380c92Sschwarze #include <errno.h>
25eea1c63dSschwarze #include <fcntl.h>
26e8e3ce36Sschwarze #include <glob.h>
27eea1c63dSschwarze #include <limits.h>
28eea1c63dSschwarze #include <regex.h>
29eea1c63dSschwarze #include <stdio.h>
30eea1c63dSschwarze #include <stdint.h>
31eea1c63dSschwarze #include <stddef.h>
32eea1c63dSschwarze #include <stdlib.h>
33eea1c63dSschwarze #include <string.h>
34eea1c63dSschwarze #include <unistd.h>
35eea1c63dSschwarze
364f4f7972Sschwarze #include "mandoc_aux.h"
37c4b66caeSschwarze #include "mandoc_ohash.h"
384de77decSschwarze #include "manconf.h"
39eea1c63dSschwarze #include "mansearch.h"
40ff2dbb0fSschwarze #include "dbm.h"
41eea1c63dSschwarze
42eea1c63dSschwarze struct expr {
43ff2dbb0fSschwarze /* Used for terms: */
44ff2dbb0fSschwarze struct dbm_match match; /* Match type and expression. */
45ff2dbb0fSschwarze uint64_t bits; /* Type mask. */
46ff2dbb0fSschwarze /* Used for OR and AND groups: */
47ff2dbb0fSschwarze struct expr *next; /* Next child in the parent group. */
48ff2dbb0fSschwarze struct expr *child; /* First child in this group. */
49ff2dbb0fSschwarze enum { EXPR_TERM, EXPR_OR, EXPR_AND } type;
50eea1c63dSschwarze };
51eea1c63dSschwarze
52ff2dbb0fSschwarze const char *const mansearch_keynames[KEY_MAX] = {
53ff2dbb0fSschwarze "arch", "sec", "Xr", "Ar", "Fa", "Fl", "Dv", "Fn",
54ff2dbb0fSschwarze "Ic", "Pa", "Cm", "Li", "Em", "Cd", "Va", "Ft",
55ff2dbb0fSschwarze "Tn", "Er", "Ev", "Sy", "Sh", "In", "Ss", "Ox",
56ff2dbb0fSschwarze "An", "Mt", "St", "Bx", "At", "Nx", "Fx", "Lk",
57ff2dbb0fSschwarze "Ms", "Bsx", "Dx", "Rs", "Vt", "Lb", "Nm", "Nd"
58eea1c63dSschwarze };
59eea1c63dSschwarze
60ff2dbb0fSschwarze
61ff2dbb0fSschwarze static struct ohash *manmerge(struct expr *, struct ohash *);
62ff2dbb0fSschwarze static struct ohash *manmerge_term(struct expr *, struct ohash *);
63ff2dbb0fSschwarze static struct ohash *manmerge_or(struct expr *, struct ohash *);
64ff2dbb0fSschwarze static struct ohash *manmerge_and(struct expr *, struct ohash *);
65ff2dbb0fSschwarze static char *buildnames(const struct dbm_page *);
66b0dedf92Sschwarze static char *buildoutput(size_t, struct dbm_page *);
67b0dedf92Sschwarze static size_t lstlen(const char *, size_t);
68b0dedf92Sschwarze static void lstcat(char *, size_t *, const char *, const char *);
69ff2dbb0fSschwarze static int lstmatch(const char *, const char *);
70eea1c63dSschwarze static struct expr *exprcomp(const struct mansearch *,
71ff2dbb0fSschwarze int, char *[], int *);
72ff2dbb0fSschwarze static struct expr *expr_and(const struct mansearch *,
73ff2dbb0fSschwarze int, char *[], int *);
74ff2dbb0fSschwarze static struct expr *exprterm(const struct mansearch *,
75ff2dbb0fSschwarze int, char *[], int *);
76eea1c63dSschwarze static void exprfree(struct expr *);
77923fed8aSschwarze static int manpage_compare(const void *, const void *);
78eea1c63dSschwarze
7949aff9f8Sschwarze
80eea1c63dSschwarze int
mansearch(const struct mansearch * search,const struct manpaths * paths,int argc,char * argv[],struct manpage ** res,size_t * sz)81eea1c63dSschwarze mansearch(const struct mansearch *search,
82eea1c63dSschwarze const struct manpaths *paths,
83eea1c63dSschwarze int argc, char *argv[],
84eea1c63dSschwarze struct manpage **res, size_t *sz)
85eea1c63dSschwarze {
86eea1c63dSschwarze char buf[PATH_MAX];
87ff2dbb0fSschwarze struct dbm_res *rp;
88ff2dbb0fSschwarze struct expr *e;
89ff2dbb0fSschwarze struct dbm_page *page;
90eea1c63dSschwarze struct manpage *mpage;
91ff2dbb0fSschwarze struct ohash *htab;
92ff2dbb0fSschwarze size_t cur, i, maxres, outkey;
93ff2dbb0fSschwarze unsigned int slot;
94ff2dbb0fSschwarze int argi, chdir_status, getcwd_status, im;
95f2b25a73Sschwarze
96ff2dbb0fSschwarze argi = 0;
97ff2dbb0fSschwarze if ((e = exprcomp(search, argc, argv, &argi)) == NULL) {
98f2b25a73Sschwarze *sz = 0;
99526e306bSschwarze return 0;
100f2b25a73Sschwarze }
101eea1c63dSschwarze
102f2b25a73Sschwarze cur = maxres = 0;
10319b6bef7Sschwarze if (res != NULL)
104eea1c63dSschwarze *res = NULL;
105eea1c63dSschwarze
106ff2dbb0fSschwarze outkey = KEY_Nd;
107ff2dbb0fSschwarze if (search->outkey != NULL)
108ff2dbb0fSschwarze for (im = 0; im < KEY_MAX; im++)
1090f10154cSschwarze if (0 == strcasecmp(search->outkey,
110ff2dbb0fSschwarze mansearch_keynames[im])) {
111ff2dbb0fSschwarze outkey = im;
11247a70b7eSschwarze break;
11347a70b7eSschwarze }
11447a70b7eSschwarze
115eea1c63dSschwarze /*
116f2b25a73Sschwarze * Remember the original working directory, if possible.
117f2b25a73Sschwarze * This will be needed if the second or a later directory
118f2b25a73Sschwarze * is given as a relative path.
119f2b25a73Sschwarze * Do not error out if the current directory is not
120f2b25a73Sschwarze * searchable: Maybe it won't be needed after all.
121eea1c63dSschwarze */
122eea1c63dSschwarze
123f2b25a73Sschwarze if (getcwd(buf, PATH_MAX) == NULL) {
124f2b25a73Sschwarze getcwd_status = 0;
125f2b25a73Sschwarze (void)strlcpy(buf, strerror(errno), sizeof(buf));
126f2b25a73Sschwarze } else
127f2b25a73Sschwarze getcwd_status = 1;
128eea1c63dSschwarze
129eea1c63dSschwarze /*
130eea1c63dSschwarze * Loop over the directories (containing databases) for us to
131eea1c63dSschwarze * search.
132eea1c63dSschwarze * Don't let missing/bad databases/directories phase us.
133eea1c63dSschwarze * In each, try to open the resident database and, if it opens,
134eea1c63dSschwarze * scan it for our match expression.
135eea1c63dSschwarze */
136eea1c63dSschwarze
137f2b25a73Sschwarze chdir_status = 0;
138eea1c63dSschwarze for (i = 0; i < paths->sz; i++) {
139f2b25a73Sschwarze if (chdir_status && paths->paths[i][0] != '/') {
140f2b25a73Sschwarze if ( ! getcwd_status) {
141eba1598bSschwarze warnx("%s: getcwd: %s", paths->paths[i], buf);
142f2b25a73Sschwarze continue;
143f2b25a73Sschwarze } else if (chdir(buf) == -1) {
144dd978576Sschwarze warn("%s", buf);
145f2b25a73Sschwarze continue;
146f2b25a73Sschwarze }
147f2b25a73Sschwarze }
148f2b25a73Sschwarze if (chdir(paths->paths[i]) == -1) {
149dd978576Sschwarze warn("%s", paths->paths[i]);
150eea1c63dSschwarze continue;
151eea1c63dSschwarze }
152f2b25a73Sschwarze chdir_status = 1;
153eea1c63dSschwarze
154ff2dbb0fSschwarze if (dbm_open(MANDOC_DB) == -1) {
155c9651edbSschwarze if (errno != ENOENT)
156eba1598bSschwarze warn("%s/%s", paths->paths[i], MANDOC_DB);
157eea1c63dSschwarze continue;
158eea1c63dSschwarze }
159eea1c63dSschwarze
160ff2dbb0fSschwarze if ((htab = manmerge(e, NULL)) == NULL) {
161ff2dbb0fSschwarze dbm_close();
162ff2dbb0fSschwarze continue;
163eea1c63dSschwarze }
164eea1c63dSschwarze
165ff2dbb0fSschwarze for (rp = ohash_first(htab, &slot); rp != NULL;
166ff2dbb0fSschwarze rp = ohash_next(htab, &slot)) {
167ff2dbb0fSschwarze page = dbm_page_get(rp->page);
168eea1c63dSschwarze
169ff2dbb0fSschwarze if (lstmatch(search->sec, page->sect) == 0 ||
1702ab19127Sschwarze lstmatch(search->arch, page->arch) == 0 ||
1712ab19127Sschwarze (search->argmode == ARG_NAME &&
1722ab19127Sschwarze rp->bits <= (int32_t)(NAME_SYN & NAME_MASK)))
173eea1c63dSschwarze continue;
174eea1c63dSschwarze
17519b6bef7Sschwarze if (res == NULL) {
17619b6bef7Sschwarze cur = 1;
17719b6bef7Sschwarze break;
17819b6bef7Sschwarze }
179eea1c63dSschwarze if (cur + 1 > maxres) {
180eea1c63dSschwarze maxres += 1024;
1818286bf36Sschwarze *res = mandoc_reallocarray(*res,
182ff2dbb0fSschwarze maxres, sizeof(**res));
183eea1c63dSschwarze }
184eea1c63dSschwarze mpage = *res + cur;
185ff2dbb0fSschwarze mandoc_asprintf(&mpage->file, "%s/%s",
186ff2dbb0fSschwarze paths->paths[i], page->file + 1);
187205c63d5Sschwarze if (access(chdir_status ? page->file + 1 :
188205c63d5Sschwarze mpage->file, R_OK) == -1) {
189205c63d5Sschwarze warn("%s", mpage->file);
190205c63d5Sschwarze warnx("outdated mandoc.db contains "
191205c63d5Sschwarze "bogus %s entry, run makewhatis %s",
192205c63d5Sschwarze page->file + 1, paths->paths[i]);
193205c63d5Sschwarze free(mpage->file);
194205c63d5Sschwarze free(rp);
195205c63d5Sschwarze continue;
196205c63d5Sschwarze }
197ff2dbb0fSschwarze mpage->names = buildnames(page);
198b0dedf92Sschwarze mpage->output = buildoutput(outkey, page);
199eac741fcSschwarze mpage->bits = search->firstmatch ? rp->bits : 0;
2006f9f8da1Sschwarze mpage->ipath = i;
201ff2dbb0fSschwarze mpage->sec = *page->sect - '0';
202ff2dbb0fSschwarze if (mpage->sec < 0 || mpage->sec > 9)
203923fed8aSschwarze mpage->sec = 10;
204ff2dbb0fSschwarze mpage->form = *page->file;
205ff2dbb0fSschwarze free(rp);
206eea1c63dSschwarze cur++;
207eea1c63dSschwarze }
208ff2dbb0fSschwarze ohash_delete(htab);
209ff2dbb0fSschwarze free(htab);
210ff2dbb0fSschwarze dbm_close();
211fea71919Sschwarze
212fea71919Sschwarze /*
213fea71919Sschwarze * In man(1) mode, prefer matches in earlier trees
214fea71919Sschwarze * over matches in later trees.
215fea71919Sschwarze */
216fea71919Sschwarze
217fea71919Sschwarze if (cur && search->firstmatch)
218fea71919Sschwarze break;
219eea1c63dSschwarze }
22014baa4a2Sschwarze if (res != NULL && cur > 1)
221923fed8aSschwarze qsort(*res, cur, sizeof(struct manpage), manpage_compare);
222f2b25a73Sschwarze if (chdir_status && getcwd_status && chdir(buf) == -1)
223dd978576Sschwarze warn("%s", buf);
2248a326ddaSschwarze exprfree(e);
225eea1c63dSschwarze *sz = cur;
22619b6bef7Sschwarze return res != NULL || cur;
227eea1c63dSschwarze }
228eea1c63dSschwarze
229ff2dbb0fSschwarze /*
230ff2dbb0fSschwarze * Merge the results for the expression tree rooted at e
231ff2dbb0fSschwarze * into the the result list htab.
232ff2dbb0fSschwarze */
233ff2dbb0fSschwarze static struct ohash *
manmerge(struct expr * e,struct ohash * htab)234ff2dbb0fSschwarze manmerge(struct expr *e, struct ohash *htab)
235ff2dbb0fSschwarze {
236ff2dbb0fSschwarze switch (e->type) {
237ff2dbb0fSschwarze case EXPR_TERM:
238ff2dbb0fSschwarze return manmerge_term(e, htab);
239ff2dbb0fSschwarze case EXPR_OR:
240ff2dbb0fSschwarze return manmerge_or(e->child, htab);
241ff2dbb0fSschwarze case EXPR_AND:
242ff2dbb0fSschwarze return manmerge_and(e->child, htab);
243ff2dbb0fSschwarze default:
244ff2dbb0fSschwarze abort();
245ff2dbb0fSschwarze }
246ff2dbb0fSschwarze }
247ff2dbb0fSschwarze
248ff2dbb0fSschwarze static struct ohash *
manmerge_term(struct expr * e,struct ohash * htab)249ff2dbb0fSschwarze manmerge_term(struct expr *e, struct ohash *htab)
250ff2dbb0fSschwarze {
251ff2dbb0fSschwarze struct dbm_res res, *rp;
252ff2dbb0fSschwarze uint64_t ib;
253ff2dbb0fSschwarze unsigned int slot;
254ff2dbb0fSschwarze int im;
255ff2dbb0fSschwarze
256ff2dbb0fSschwarze if (htab == NULL) {
257ff2dbb0fSschwarze htab = mandoc_malloc(sizeof(*htab));
258ff2dbb0fSschwarze mandoc_ohash_init(htab, 4, offsetof(struct dbm_res, page));
259ff2dbb0fSschwarze }
260ff2dbb0fSschwarze
261ff2dbb0fSschwarze for (im = 0, ib = 1; im < KEY_MAX; im++, ib <<= 1) {
262ff2dbb0fSschwarze if ((e->bits & ib) == 0)
263ff2dbb0fSschwarze continue;
264ff2dbb0fSschwarze
265ff2dbb0fSschwarze switch (ib) {
266ff2dbb0fSschwarze case TYPE_arch:
267ff2dbb0fSschwarze dbm_page_byarch(&e->match);
268ff2dbb0fSschwarze break;
269ff2dbb0fSschwarze case TYPE_sec:
270ff2dbb0fSschwarze dbm_page_bysect(&e->match);
271ff2dbb0fSschwarze break;
272ff2dbb0fSschwarze case TYPE_Nm:
273ff2dbb0fSschwarze dbm_page_byname(&e->match);
274ff2dbb0fSschwarze break;
275ff2dbb0fSschwarze case TYPE_Nd:
276ff2dbb0fSschwarze dbm_page_bydesc(&e->match);
277ff2dbb0fSschwarze break;
278ff2dbb0fSschwarze default:
279ff2dbb0fSschwarze dbm_page_bymacro(im - 2, &e->match);
280ff2dbb0fSschwarze break;
281ff2dbb0fSschwarze }
282ff2dbb0fSschwarze
283ff2dbb0fSschwarze /*
284ff2dbb0fSschwarze * When hashing for deduplication, use the unique
285ff2dbb0fSschwarze * page ID itself instead of a hash function;
286ff2dbb0fSschwarze * that is quite efficient.
287ff2dbb0fSschwarze */
288ff2dbb0fSschwarze
289ff2dbb0fSschwarze for (;;) {
290ff2dbb0fSschwarze res = dbm_page_next();
291ff2dbb0fSschwarze if (res.page == -1)
292ff2dbb0fSschwarze break;
293ff2dbb0fSschwarze slot = ohash_lookup_memory(htab,
294ff2dbb0fSschwarze (char *)&res, sizeof(res.page), res.page);
295eac741fcSschwarze if ((rp = ohash_find(htab, slot)) != NULL) {
296eac741fcSschwarze rp->bits |= res.bits;
297ff2dbb0fSschwarze continue;
298eac741fcSschwarze }
299ff2dbb0fSschwarze rp = mandoc_malloc(sizeof(*rp));
300ff2dbb0fSschwarze *rp = res;
301ff2dbb0fSschwarze ohash_insert(htab, slot, rp);
302ff2dbb0fSschwarze }
303ff2dbb0fSschwarze }
304ff2dbb0fSschwarze return htab;
305ff2dbb0fSschwarze }
306ff2dbb0fSschwarze
307ff2dbb0fSschwarze static struct ohash *
manmerge_or(struct expr * e,struct ohash * htab)308ff2dbb0fSschwarze manmerge_or(struct expr *e, struct ohash *htab)
309ff2dbb0fSschwarze {
310ff2dbb0fSschwarze while (e != NULL) {
311ff2dbb0fSschwarze htab = manmerge(e, htab);
312ff2dbb0fSschwarze e = e->next;
313ff2dbb0fSschwarze }
314ff2dbb0fSschwarze return htab;
315ff2dbb0fSschwarze }
316ff2dbb0fSschwarze
317ff2dbb0fSschwarze static struct ohash *
manmerge_and(struct expr * e,struct ohash * htab)318ff2dbb0fSschwarze manmerge_and(struct expr *e, struct ohash *htab)
319ff2dbb0fSschwarze {
320ff2dbb0fSschwarze struct ohash *hand, *h1, *h2;
321ff2dbb0fSschwarze struct dbm_res *res;
322ff2dbb0fSschwarze unsigned int slot1, slot2;
323ff2dbb0fSschwarze
324ff2dbb0fSschwarze /* Evaluate the first term of the AND clause. */
325ff2dbb0fSschwarze
326ff2dbb0fSschwarze hand = manmerge(e, NULL);
327ff2dbb0fSschwarze
328ff2dbb0fSschwarze while ((e = e->next) != NULL) {
329ff2dbb0fSschwarze
330ff2dbb0fSschwarze /* Evaluate the next term and prepare for ANDing. */
331ff2dbb0fSschwarze
332ff2dbb0fSschwarze h2 = manmerge(e, NULL);
333ff2dbb0fSschwarze if (ohash_entries(h2) < ohash_entries(hand)) {
334ff2dbb0fSschwarze h1 = h2;
335ff2dbb0fSschwarze h2 = hand;
336ff2dbb0fSschwarze } else
337ff2dbb0fSschwarze h1 = hand;
338ff2dbb0fSschwarze hand = mandoc_malloc(sizeof(*hand));
339ff2dbb0fSschwarze mandoc_ohash_init(hand, 4, offsetof(struct dbm_res, page));
340ff2dbb0fSschwarze
341ff2dbb0fSschwarze /* Keep all pages that are in both result sets. */
342ff2dbb0fSschwarze
343ff2dbb0fSschwarze for (res = ohash_first(h1, &slot1); res != NULL;
344ff2dbb0fSschwarze res = ohash_next(h1, &slot1)) {
345ff2dbb0fSschwarze if (ohash_find(h2, ohash_lookup_memory(h2,
346ff2dbb0fSschwarze (char *)res, sizeof(res->page),
347ff2dbb0fSschwarze res->page)) == NULL)
348ff2dbb0fSschwarze free(res);
349ff2dbb0fSschwarze else
350ff2dbb0fSschwarze ohash_insert(hand, ohash_lookup_memory(hand,
351ff2dbb0fSschwarze (char *)res, sizeof(res->page),
352ff2dbb0fSschwarze res->page), res);
353ff2dbb0fSschwarze }
354ff2dbb0fSschwarze
355ff2dbb0fSschwarze /* Discard the merged results. */
356ff2dbb0fSschwarze
357ff2dbb0fSschwarze for (res = ohash_first(h2, &slot2); res != NULL;
358ff2dbb0fSschwarze res = ohash_next(h2, &slot2))
359ff2dbb0fSschwarze free(res);
360ff2dbb0fSschwarze ohash_delete(h2);
361ff2dbb0fSschwarze free(h2);
362ff2dbb0fSschwarze ohash_delete(h1);
363ff2dbb0fSschwarze free(h1);
364ff2dbb0fSschwarze }
365ff2dbb0fSschwarze
366ff2dbb0fSschwarze /* Merge the result of the AND into htab. */
367ff2dbb0fSschwarze
368ff2dbb0fSschwarze if (htab == NULL)
369ff2dbb0fSschwarze return hand;
370ff2dbb0fSschwarze
371ff2dbb0fSschwarze for (res = ohash_first(hand, &slot1); res != NULL;
372ff2dbb0fSschwarze res = ohash_next(hand, &slot1)) {
373ff2dbb0fSschwarze slot2 = ohash_lookup_memory(htab,
374ff2dbb0fSschwarze (char *)res, sizeof(res->page), res->page);
375ff2dbb0fSschwarze if (ohash_find(htab, slot2) == NULL)
376ff2dbb0fSschwarze ohash_insert(htab, slot2, res);
377ff2dbb0fSschwarze else
378ff2dbb0fSschwarze free(res);
379ff2dbb0fSschwarze }
380ff2dbb0fSschwarze
381ff2dbb0fSschwarze /* Discard the merged result. */
382ff2dbb0fSschwarze
383ff2dbb0fSschwarze ohash_delete(hand);
384ff2dbb0fSschwarze free(hand);
385ff2dbb0fSschwarze return htab;
386ff2dbb0fSschwarze }
387ff2dbb0fSschwarze
3880f10154cSschwarze void
mansearch_free(struct manpage * res,size_t sz)3890f10154cSschwarze mansearch_free(struct manpage *res, size_t sz)
3900f10154cSschwarze {
3910f10154cSschwarze size_t i;
3920f10154cSschwarze
3930f10154cSschwarze for (i = 0; i < sz; i++) {
3940f10154cSschwarze free(res[i].file);
3950f10154cSschwarze free(res[i].names);
3960f10154cSschwarze free(res[i].output);
3970f10154cSschwarze }
3980f10154cSschwarze free(res);
3990f10154cSschwarze }
4000f10154cSschwarze
401923fed8aSschwarze static int
manpage_compare(const void * vp1,const void * vp2)402923fed8aSschwarze manpage_compare(const void *vp1, const void *vp2)
403923fed8aSschwarze {
404923fed8aSschwarze const struct manpage *mp1, *mp2;
4052270ef14Sschwarze const char *cp1, *cp2;
4062270ef14Sschwarze size_t sz1, sz2;
407923fed8aSschwarze int diff;
408923fed8aSschwarze
409923fed8aSschwarze mp1 = vp1;
410923fed8aSschwarze mp2 = vp2;
411eac741fcSschwarze if ((diff = mp2->bits - mp1->bits) ||
412eac741fcSschwarze (diff = mp1->sec - mp2->sec))
4132270ef14Sschwarze return diff;
4142270ef14Sschwarze
4152270ef14Sschwarze /* Fall back to alphabetic ordering of names. */
4162270ef14Sschwarze sz1 = strcspn(mp1->names, "(");
4172270ef14Sschwarze sz2 = strcspn(mp2->names, "(");
4182270ef14Sschwarze if (sz1 < sz2)
4192270ef14Sschwarze sz1 = sz2;
4202270ef14Sschwarze if ((diff = strncasecmp(mp1->names, mp2->names, sz1)))
4212270ef14Sschwarze return diff;
4222270ef14Sschwarze
4232270ef14Sschwarze /* For identical names and sections, prefer arch-dependent. */
4242270ef14Sschwarze cp1 = strchr(mp1->names + sz1, '/');
4252270ef14Sschwarze cp2 = strchr(mp2->names + sz2, '/');
4262270ef14Sschwarze return cp1 != NULL && cp2 != NULL ? strcasecmp(cp1, cp2) :
4272270ef14Sschwarze cp1 != NULL ? -1 : cp2 != NULL ? 1 : 0;
428923fed8aSschwarze }
429923fed8aSschwarze
43047a70b7eSschwarze static char *
buildnames(const struct dbm_page * page)431ff2dbb0fSschwarze buildnames(const struct dbm_page *page)
43247a70b7eSschwarze {
433ff2dbb0fSschwarze char *buf;
434ff2dbb0fSschwarze size_t i, sz;
435ff2dbb0fSschwarze
436b0dedf92Sschwarze sz = lstlen(page->name, 2) + 1 + lstlen(page->sect, 2) +
437b0dedf92Sschwarze (page->arch == NULL ? 0 : 1 + lstlen(page->arch, 2)) + 2;
438ff2dbb0fSschwarze buf = mandoc_malloc(sz);
439ff2dbb0fSschwarze i = 0;
440b0dedf92Sschwarze lstcat(buf, &i, page->name, ", ");
441ff2dbb0fSschwarze buf[i++] = '(';
442b0dedf92Sschwarze lstcat(buf, &i, page->sect, ", ");
443ff2dbb0fSschwarze if (page->arch != NULL) {
444ff2dbb0fSschwarze buf[i++] = '/';
445b0dedf92Sschwarze lstcat(buf, &i, page->arch, ", ");
446ff2dbb0fSschwarze }
447ff2dbb0fSschwarze buf[i++] = ')';
448ff2dbb0fSschwarze buf[i++] = '\0';
449ff2dbb0fSschwarze assert(i == sz);
450ff2dbb0fSschwarze return buf;
451ff2dbb0fSschwarze }
452ff2dbb0fSschwarze
453ff2dbb0fSschwarze /*
454ff2dbb0fSschwarze * Count the buffer space needed to print the NUL-terminated
455b0dedf92Sschwarze * list of NUL-terminated strings, when printing sep separator
456ff2dbb0fSschwarze * characters between strings.
457ff2dbb0fSschwarze */
458ff2dbb0fSschwarze static size_t
lstlen(const char * cp,size_t sep)459b0dedf92Sschwarze lstlen(const char *cp, size_t sep)
460ff2dbb0fSschwarze {
461ff2dbb0fSschwarze size_t sz;
462ff2dbb0fSschwarze
4632ab19127Sschwarze for (sz = 0; *cp != '\0'; cp++) {
4642ab19127Sschwarze
4652ab19127Sschwarze /* Skip names appearing only in the SYNOPSIS. */
4662ab19127Sschwarze if (*cp <= (char)(NAME_SYN & NAME_MASK)) {
4672ab19127Sschwarze while (*cp != '\0')
468ff2dbb0fSschwarze cp++;
4692ab19127Sschwarze continue;
4702ab19127Sschwarze }
4712ab19127Sschwarze
4722ab19127Sschwarze /* Skip name class markers. */
4732ab19127Sschwarze if (*cp < ' ')
4742ab19127Sschwarze cp++;
4752ab19127Sschwarze
4762ab19127Sschwarze /* Print a separator before each but the first string. */
4772ab19127Sschwarze if (sz)
4782ab19127Sschwarze sz += sep;
4792ab19127Sschwarze
4802ab19127Sschwarze /* Copy one string. */
4812ab19127Sschwarze while (*cp != '\0') {
4822ab19127Sschwarze sz++;
4832ab19127Sschwarze cp++;
4842ab19127Sschwarze }
485ff2dbb0fSschwarze }
486ff2dbb0fSschwarze return sz;
487ff2dbb0fSschwarze }
488ff2dbb0fSschwarze
489ff2dbb0fSschwarze /*
490ff2dbb0fSschwarze * Print the NUL-terminated list of NUL-terminated strings
491*d9a51c35Sjmc * into the buffer, separating strings with sep.
492ff2dbb0fSschwarze */
493ff2dbb0fSschwarze static void
lstcat(char * buf,size_t * i,const char * cp,const char * sep)494b0dedf92Sschwarze lstcat(char *buf, size_t *i, const char *cp, const char *sep)
495ff2dbb0fSschwarze {
496b0dedf92Sschwarze const char *s;
4972ab19127Sschwarze size_t i_start;
498b0dedf92Sschwarze
4992ab19127Sschwarze for (i_start = *i; *cp != '\0'; cp++) {
5002ab19127Sschwarze
5012ab19127Sschwarze /* Skip names appearing only in the SYNOPSIS. */
5022ab19127Sschwarze if (*cp <= (char)(NAME_SYN & NAME_MASK)) {
5032ab19127Sschwarze while (*cp != '\0')
5042ab19127Sschwarze cp++;
5052ab19127Sschwarze continue;
5062ab19127Sschwarze }
5072ab19127Sschwarze
5082ab19127Sschwarze /* Skip name class markers. */
5092ab19127Sschwarze if (*cp < ' ')
5102ab19127Sschwarze cp++;
5112ab19127Sschwarze
5122ab19127Sschwarze /* Print a separator before each but the first string. */
5132ab19127Sschwarze if (*i > i_start) {
514b0dedf92Sschwarze s = sep;
515b0dedf92Sschwarze while (*s != '\0')
516b0dedf92Sschwarze buf[(*i)++] = *s++;
517ff2dbb0fSschwarze }
5182ab19127Sschwarze
5192ab19127Sschwarze /* Copy one string. */
5202ab19127Sschwarze while (*cp != '\0')
5212ab19127Sschwarze buf[(*i)++] = *cp++;
5222ab19127Sschwarze }
5232ab19127Sschwarze
524ff2dbb0fSschwarze }
525ff2dbb0fSschwarze
526ff2dbb0fSschwarze /*
527ff2dbb0fSschwarze * Return 1 if the string *want occurs in any of the strings
528ff2dbb0fSschwarze * in the NUL-terminated string list *have, or 0 otherwise.
529ff2dbb0fSschwarze * If either argument is NULL or empty, assume no filtering
530ff2dbb0fSschwarze * is desired and return 1.
531ff2dbb0fSschwarze */
532ff2dbb0fSschwarze static int
lstmatch(const char * want,const char * have)533ff2dbb0fSschwarze lstmatch(const char *want, const char *have)
534ff2dbb0fSschwarze {
535ff2dbb0fSschwarze if (want == NULL || have == NULL || *have == '\0')
536ff2dbb0fSschwarze return 1;
537ff2dbb0fSschwarze while (*have != '\0') {
538ff2dbb0fSschwarze if (strcasestr(have, want) != NULL)
539ff2dbb0fSschwarze return 1;
540ff2dbb0fSschwarze have = strchr(have, '\0') + 1;
541ff2dbb0fSschwarze }
542ff2dbb0fSschwarze return 0;
543ff2dbb0fSschwarze }
544ff2dbb0fSschwarze
545ff2dbb0fSschwarze /*
546b0dedf92Sschwarze * Build a list of values taken by the macro im in the manual page.
547ff2dbb0fSschwarze */
548ff2dbb0fSschwarze static char *
buildoutput(size_t im,struct dbm_page * page)549b0dedf92Sschwarze buildoutput(size_t im, struct dbm_page *page)
550ff2dbb0fSschwarze {
551b0dedf92Sschwarze const char *oldoutput, *sep, *input;
552ff2dbb0fSschwarze char *output, *newoutput, *value;
553b0dedf92Sschwarze size_t sz, i;
554b0dedf92Sschwarze
555b0dedf92Sschwarze switch (im) {
556b0dedf92Sschwarze case KEY_Nd:
557b0dedf92Sschwarze return mandoc_strdup(page->desc);
558b0dedf92Sschwarze case KEY_Nm:
559b0dedf92Sschwarze input = page->name;
560b0dedf92Sschwarze break;
561b0dedf92Sschwarze case KEY_sec:
562b0dedf92Sschwarze input = page->sect;
563b0dedf92Sschwarze break;
564b0dedf92Sschwarze case KEY_arch:
565b0dedf92Sschwarze input = page->arch;
566b0dedf92Sschwarze if (input == NULL)
567b0dedf92Sschwarze input = "all\0";
568b0dedf92Sschwarze break;
569b0dedf92Sschwarze default:
570b0dedf92Sschwarze input = NULL;
571b0dedf92Sschwarze break;
572b0dedf92Sschwarze }
573b0dedf92Sschwarze
574b0dedf92Sschwarze if (input != NULL) {
575b0dedf92Sschwarze sz = lstlen(input, 3) + 1;
576b0dedf92Sschwarze output = mandoc_malloc(sz);
577b0dedf92Sschwarze i = 0;
578b0dedf92Sschwarze lstcat(output, &i, input, " # ");
57965807c88Sschwarze output[i++] = '\0';
58065807c88Sschwarze assert(i == sz);
581b0dedf92Sschwarze return output;
582b0dedf92Sschwarze }
58347a70b7eSschwarze
58447a70b7eSschwarze output = NULL;
585b0dedf92Sschwarze dbm_macro_bypage(im - 2, page->addr);
586ff2dbb0fSschwarze while ((value = dbm_macro_next()) != NULL) {
587ff2dbb0fSschwarze if (output == NULL) {
58847a70b7eSschwarze oldoutput = "";
589ff2dbb0fSschwarze sep = "";
59047a70b7eSschwarze } else {
59147a70b7eSschwarze oldoutput = output;
592ff2dbb0fSschwarze sep = " # ";
59347a70b7eSschwarze }
594ff2dbb0fSschwarze mandoc_asprintf(&newoutput, "%s%s%s", oldoutput, sep, value);
59547a70b7eSschwarze free(output);
59647a70b7eSschwarze output = newoutput;
59747a70b7eSschwarze }
598526e306bSschwarze return output;
59947a70b7eSschwarze }
60047a70b7eSschwarze
601eea1c63dSschwarze /*
602eea1c63dSschwarze * Compile a set of string tokens into an expression.
603eea1c63dSschwarze * Tokens in "argv" are assumed to be individual expression atoms (e.g.,
604eea1c63dSschwarze * "(", "foo=bar", etc.).
605eea1c63dSschwarze */
606eea1c63dSschwarze static struct expr *
exprcomp(const struct mansearch * search,int argc,char * argv[],int * argi)607ff2dbb0fSschwarze exprcomp(const struct mansearch *search, int argc, char *argv[], int *argi)
608eea1c63dSschwarze {
609ff2dbb0fSschwarze struct expr *parent, *child;
610ff2dbb0fSschwarze int needterm, nested;
611eea1c63dSschwarze
612ff2dbb0fSschwarze if ((nested = *argi) == argc)
613526e306bSschwarze return NULL;
614ff2dbb0fSschwarze needterm = 1;
615ff2dbb0fSschwarze parent = child = NULL;
616ff2dbb0fSschwarze while (*argi < argc) {
617ff2dbb0fSschwarze if (strcmp(")", argv[*argi]) == 0) {
618ff2dbb0fSschwarze if (needterm)
619ff2dbb0fSschwarze warnx("missing term "
620ff2dbb0fSschwarze "before closing parenthesis");
621ff2dbb0fSschwarze needterm = 0;
622ff2dbb0fSschwarze if (nested)
623ff2dbb0fSschwarze break;
624ff2dbb0fSschwarze warnx("ignoring unmatched right parenthesis");
625ff2dbb0fSschwarze ++*argi;
626ff2dbb0fSschwarze continue;
627ff2dbb0fSschwarze }
628ff2dbb0fSschwarze if (strcmp("-o", argv[*argi]) == 0) {
629ff2dbb0fSschwarze if (needterm) {
630ff2dbb0fSschwarze if (*argi > 0)
631ff2dbb0fSschwarze warnx("ignoring -o after %s",
632ff2dbb0fSschwarze argv[*argi - 1]);
633ff2dbb0fSschwarze else
634ff2dbb0fSschwarze warnx("ignoring initial -o");
635ff2dbb0fSschwarze }
636ff2dbb0fSschwarze needterm = 1;
637ff2dbb0fSschwarze ++*argi;
638ff2dbb0fSschwarze continue;
639ff2dbb0fSschwarze }
640ff2dbb0fSschwarze needterm = 0;
641ff2dbb0fSschwarze if (child == NULL) {
642ff2dbb0fSschwarze child = expr_and(search, argc, argv, argi);
643ff2dbb0fSschwarze continue;
644ff2dbb0fSschwarze }
645ff2dbb0fSschwarze if (parent == NULL) {
646ff2dbb0fSschwarze parent = mandoc_calloc(1, sizeof(*parent));
647ff2dbb0fSschwarze parent->type = EXPR_OR;
648ff2dbb0fSschwarze parent->next = NULL;
649ff2dbb0fSschwarze parent->child = child;
650ff2dbb0fSschwarze }
651ff2dbb0fSschwarze child->next = expr_and(search, argc, argv, argi);
652ff2dbb0fSschwarze child = child->next;
653ff2dbb0fSschwarze }
654ff2dbb0fSschwarze if (needterm && *argi)
655ff2dbb0fSschwarze warnx("ignoring trailing %s", argv[*argi - 1]);
656ff2dbb0fSschwarze return parent == NULL ? child : parent;
657eea1c63dSschwarze }
658eea1c63dSschwarze
659eea1c63dSschwarze static struct expr *
expr_and(const struct mansearch * search,int argc,char * argv[],int * argi)660ff2dbb0fSschwarze expr_and(const struct mansearch *search, int argc, char *argv[], int *argi)
661ff2dbb0fSschwarze {
662ff2dbb0fSschwarze struct expr *parent, *child;
663ff2dbb0fSschwarze int needterm;
664ff2dbb0fSschwarze
665ff2dbb0fSschwarze needterm = 1;
666ff2dbb0fSschwarze parent = child = NULL;
667ff2dbb0fSschwarze while (*argi < argc) {
668ff2dbb0fSschwarze if (strcmp(")", argv[*argi]) == 0) {
669ff2dbb0fSschwarze if (needterm)
670ff2dbb0fSschwarze warnx("missing term "
671ff2dbb0fSschwarze "before closing parenthesis");
672ff2dbb0fSschwarze needterm = 0;
673ff2dbb0fSschwarze break;
674ff2dbb0fSschwarze }
675ff2dbb0fSschwarze if (strcmp("-o", argv[*argi]) == 0)
676ff2dbb0fSschwarze break;
677ff2dbb0fSschwarze if (strcmp("-a", argv[*argi]) == 0) {
678ff2dbb0fSschwarze if (needterm) {
679ff2dbb0fSschwarze if (*argi > 0)
680ff2dbb0fSschwarze warnx("ignoring -a after %s",
681ff2dbb0fSschwarze argv[*argi - 1]);
682ff2dbb0fSschwarze else
683ff2dbb0fSschwarze warnx("ignoring initial -a");
684ff2dbb0fSschwarze }
685ff2dbb0fSschwarze needterm = 1;
686ff2dbb0fSschwarze ++*argi;
687ff2dbb0fSschwarze continue;
688ff2dbb0fSschwarze }
689ff2dbb0fSschwarze if (needterm == 0)
690ff2dbb0fSschwarze break;
691ff2dbb0fSschwarze if (child == NULL) {
692ff2dbb0fSschwarze child = exprterm(search, argc, argv, argi);
693ff2dbb0fSschwarze if (child != NULL)
694ff2dbb0fSschwarze needterm = 0;
695ff2dbb0fSschwarze continue;
696ff2dbb0fSschwarze }
697ff2dbb0fSschwarze needterm = 0;
698ff2dbb0fSschwarze if (parent == NULL) {
699ff2dbb0fSschwarze parent = mandoc_calloc(1, sizeof(*parent));
700ff2dbb0fSschwarze parent->type = EXPR_AND;
701ff2dbb0fSschwarze parent->next = NULL;
702ff2dbb0fSschwarze parent->child = child;
703ff2dbb0fSschwarze }
704ff2dbb0fSschwarze child->next = exprterm(search, argc, argv, argi);
705ff2dbb0fSschwarze if (child->next != NULL) {
706ff2dbb0fSschwarze child = child->next;
707ff2dbb0fSschwarze needterm = 0;
708ff2dbb0fSschwarze }
709ff2dbb0fSschwarze }
710ff2dbb0fSschwarze if (needterm && *argi)
711ff2dbb0fSschwarze warnx("ignoring trailing %s", argv[*argi - 1]);
712ff2dbb0fSschwarze return parent == NULL ? child : parent;
713ff2dbb0fSschwarze }
714ff2dbb0fSschwarze
715ff2dbb0fSschwarze static struct expr *
exprterm(const struct mansearch * search,int argc,char * argv[],int * argi)716ff2dbb0fSschwarze exprterm(const struct mansearch *search, int argc, char *argv[], int *argi)
717eea1c63dSschwarze {
718f81d09daSschwarze char errbuf[BUFSIZ];
719eea1c63dSschwarze struct expr *e;
720eb258672Sschwarze char *key, *val;
72162a7ee29Sschwarze uint64_t iterbit;
722ff2dbb0fSschwarze int cs, i, irc;
723eea1c63dSschwarze
724ff2dbb0fSschwarze if (strcmp("(", argv[*argi]) == 0) {
725ff2dbb0fSschwarze ++*argi;
726ff2dbb0fSschwarze e = exprcomp(search, argc, argv, argi);
727ff2dbb0fSschwarze if (*argi < argc) {
728ff2dbb0fSschwarze assert(strcmp(")", argv[*argi]) == 0);
729ff2dbb0fSschwarze ++*argi;
730ff2dbb0fSschwarze } else
731ff2dbb0fSschwarze warnx("unclosed parenthesis");
732ff2dbb0fSschwarze return e;
733ff2dbb0fSschwarze }
734eea1c63dSschwarze
73576cc2bb0Sschwarze if (strcmp("-i", argv[*argi]) == 0 && *argi + 1 < argc) {
73676cc2bb0Sschwarze cs = 0;
73776cc2bb0Sschwarze ++*argi;
73876cc2bb0Sschwarze } else
73976cc2bb0Sschwarze cs = 1;
74076cc2bb0Sschwarze
741ff2dbb0fSschwarze e = mandoc_calloc(1, sizeof(*e));
742ff2dbb0fSschwarze e->type = EXPR_TERM;
743ff2dbb0fSschwarze e->bits = 0;
744ff2dbb0fSschwarze e->next = NULL;
745ff2dbb0fSschwarze e->child = NULL;
746eea1c63dSschwarze
7470f10154cSschwarze if (search->argmode == ARG_NAME) {
7480f10154cSschwarze e->bits = TYPE_Nm;
749ff2dbb0fSschwarze e->match.type = DBM_EXACT;
750ff2dbb0fSschwarze e->match.str = argv[(*argi)++];
751526e306bSschwarze return e;
752eea1c63dSschwarze }
753eea1c63dSschwarze
754eea1c63dSschwarze /*
7550f10154cSschwarze * Separate macro keys from search string.
756ff2dbb0fSschwarze * If needed, request regular expression handling.
757eea1c63dSschwarze */
758eea1c63dSschwarze
7590f10154cSschwarze if (search->argmode == ARG_WORD) {
7600f10154cSschwarze e->bits = TYPE_Nm;
761ff2dbb0fSschwarze e->match.type = DBM_REGEX;
762ff2dbb0fSschwarze mandoc_asprintf(&val, "[[:<:]]%s[[:>:]]", argv[*argi]);
7630f10154cSschwarze cs = 0;
764ff2dbb0fSschwarze } else if ((val = strpbrk(argv[*argi], "=~")) == NULL) {
7650f10154cSschwarze e->bits = TYPE_Nm | TYPE_Nd;
7661ab4f06fSschwarze e->match.type = DBM_REGEX;
7671ab4f06fSschwarze val = argv[*argi];
7681ab4f06fSschwarze cs = 0;
769eb258672Sschwarze } else {
770ff2dbb0fSschwarze if (val == argv[*argi])
7710f10154cSschwarze e->bits = TYPE_Nm | TYPE_Nd;
772ff2dbb0fSschwarze if (*val == '=') {
773ff2dbb0fSschwarze e->match.type = DBM_SUB;
774ff2dbb0fSschwarze e->match.str = val + 1;
775ff2dbb0fSschwarze } else
776ff2dbb0fSschwarze e->match.type = DBM_REGEX;
777eb258672Sschwarze *val++ = '\0';
778ff2dbb0fSschwarze if (strstr(argv[*argi], "arch") != NULL)
779b530bd48Sschwarze cs = 0;
780eb258672Sschwarze }
781eb258672Sschwarze
782eb258672Sschwarze /* Compile regular expressions. */
783eb258672Sschwarze
784ff2dbb0fSschwarze if (e->match.type == DBM_REGEX) {
785ff2dbb0fSschwarze e->match.re = mandoc_malloc(sizeof(*e->match.re));
786ff2dbb0fSschwarze irc = regcomp(e->match.re, val,
787eb258672Sschwarze REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE));
788ff2dbb0fSschwarze if (irc) {
789ff2dbb0fSschwarze regerror(irc, e->match.re, errbuf, sizeof(errbuf));
790ff2dbb0fSschwarze warnx("regcomp /%s/: %s", val, errbuf);
791ff2dbb0fSschwarze }
7920f10154cSschwarze if (search->argmode == ARG_WORD)
793eb258672Sschwarze free(val);
794eb258672Sschwarze if (irc) {
795ff2dbb0fSschwarze free(e->match.re);
796eea1c63dSschwarze free(e);
797ff2dbb0fSschwarze ++*argi;
798526e306bSschwarze return NULL;
799eea1c63dSschwarze }
800eb258672Sschwarze }
801eb258672Sschwarze
802ff2dbb0fSschwarze if (e->bits) {
803ff2dbb0fSschwarze ++*argi;
804526e306bSschwarze return e;
805ff2dbb0fSschwarze }
806eea1c63dSschwarze
807eea1c63dSschwarze /*
808eea1c63dSschwarze * Parse out all possible fields.
809eea1c63dSschwarze * If the field doesn't resolve, bail.
810eea1c63dSschwarze */
811eea1c63dSschwarze
812ff2dbb0fSschwarze while (NULL != (key = strsep(&argv[*argi], ","))) {
813eea1c63dSschwarze if ('\0' == *key)
814eea1c63dSschwarze continue;
815ff2dbb0fSschwarze for (i = 0, iterbit = 1; i < KEY_MAX; i++, iterbit <<= 1) {
816ff2dbb0fSschwarze if (0 == strcasecmp(key, mansearch_keynames[i])) {
81762a7ee29Sschwarze e->bits |= iterbit;
81862a7ee29Sschwarze break;
81962a7ee29Sschwarze }
82062a7ee29Sschwarze }
821ff2dbb0fSschwarze if (i == KEY_MAX) {
822ff2dbb0fSschwarze if (strcasecmp(key, "any"))
823ff2dbb0fSschwarze warnx("treating unknown key "
824ff2dbb0fSschwarze "\"%s\" as \"any\"", key);
82562a7ee29Sschwarze e->bits |= ~0ULL;
82662a7ee29Sschwarze }
827eea1c63dSschwarze }
828eea1c63dSschwarze
829ff2dbb0fSschwarze ++*argi;
830526e306bSschwarze return e;
831eea1c63dSschwarze }
832eea1c63dSschwarze
833eea1c63dSschwarze static void
exprfree(struct expr * e)834ff2dbb0fSschwarze exprfree(struct expr *e)
835eea1c63dSschwarze {
836ff2dbb0fSschwarze if (e->next != NULL)
837ff2dbb0fSschwarze exprfree(e->next);
838ff2dbb0fSschwarze if (e->child != NULL)
839ff2dbb0fSschwarze exprfree(e->child);
840ff2dbb0fSschwarze free(e);
841eea1c63dSschwarze }
842