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