File: | src/usr.bin/mandoc/mandocdb.c |
Warning: | line 1873, column 2 Value stored to 'rc' is never read |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* $OpenBSD: mandocdb.c,v 1.219 2022/12/26 19:16:02 jmc Exp $ */ |
2 | /* |
3 | * Copyright (c) 2011-2020 Ingo Schwarze <schwarze@openbsd.org> |
4 | * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> |
5 | * Copyright (c) 2016 Ed Maste <emaste@freebsd.org> |
6 | * |
7 | * Permission to use, copy, modify, and distribute this software for any |
8 | * purpose with or without fee is hereby granted, provided that the above |
9 | * copyright notice and this permission notice appear in all copies. |
10 | * |
11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES |
12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR |
14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
16 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
17 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
18 | * |
19 | * Implementation of the makewhatis(8) program. |
20 | */ |
21 | #include <sys/types.h> |
22 | #include <sys/mman.h> |
23 | #include <sys/stat.h> |
24 | |
25 | #include <assert.h> |
26 | #include <ctype.h> |
27 | #include <err.h> |
28 | #include <errno(*__errno()).h> |
29 | #include <fcntl.h> |
30 | #include <fts.h> |
31 | #include <limits.h> |
32 | #include <stdarg.h> |
33 | #include <stddef.h> |
34 | #include <stdio.h> |
35 | #include <stdint.h> |
36 | #include <stdlib.h> |
37 | #include <string.h> |
38 | #include <unistd.h> |
39 | |
40 | #include "mandoc_aux.h" |
41 | #include "mandoc_ohash.h" |
42 | #include "mandoc.h" |
43 | #include "roff.h" |
44 | #include "mdoc.h" |
45 | #include "man.h" |
46 | #include "mandoc_parse.h" |
47 | #include "manconf.h" |
48 | #include "mansearch.h" |
49 | #include "dba_array.h" |
50 | #include "dba.h" |
51 | |
52 | extern const char *const mansearch_keynames[]; |
53 | |
54 | enum op { |
55 | OP_DEFAULT = 0, /* new dbs from dir list or default config */ |
56 | OP_CONFFILE, /* new databases from custom config file */ |
57 | OP_UPDATE, /* delete/add entries in existing database */ |
58 | OP_DELETE, /* delete entries from existing database */ |
59 | OP_TEST /* change no databases, report potential problems */ |
60 | }; |
61 | |
62 | struct str { |
63 | const struct mpage *mpage; /* if set, the owning parse */ |
64 | uint64_t mask; /* bitmask in sequence */ |
65 | char key[]; /* rendered text */ |
66 | }; |
67 | |
68 | struct inodev { |
69 | ino_t st_ino; |
70 | dev_t st_dev; |
71 | }; |
72 | |
73 | struct mpage { |
74 | struct inodev inodev; /* used for hashing routine */ |
75 | struct dba_array *dba; |
76 | char *sec; /* section from file content */ |
77 | char *arch; /* architecture from file content */ |
78 | char *title; /* title from file content */ |
79 | char *desc; /* description from file content */ |
80 | struct mpage *next; /* singly linked list */ |
81 | struct mlink *mlinks; /* singly linked list */ |
82 | int name_head_done; |
83 | enum form form; /* format from file content */ |
84 | }; |
85 | |
86 | struct mlink { |
87 | char file[PATH_MAX1024]; /* filename rel. to manpath */ |
88 | char *dsec; /* section from directory */ |
89 | char *arch; /* architecture from directory */ |
90 | char *name; /* name from file name (not empty) */ |
91 | char *fsec; /* section from file name suffix */ |
92 | struct mlink *next; /* singly linked list */ |
93 | struct mpage *mpage; /* parent */ |
94 | int gzip; /* filename has a .gz suffix */ |
95 | enum form dform; /* format from directory */ |
96 | enum form fform; /* format from file name suffix */ |
97 | }; |
98 | |
99 | typedef int (*mdoc_fp)(struct mpage *, const struct roff_meta *, |
100 | const struct roff_node *); |
101 | |
102 | struct mdoc_handler { |
103 | mdoc_fp fp; /* optional handler */ |
104 | uint64_t mask; /* set unless handler returns 0 */ |
105 | int taboo; /* node flags that must not be set */ |
106 | }; |
107 | |
108 | |
109 | int mandocdb(int, char *[]); |
110 | |
111 | static void dbadd(struct dba *, struct mpage *); |
112 | static void dbadd_mlink(const struct mlink *); |
113 | static void dbprune(struct dba *); |
114 | static void dbwrite(struct dba *); |
115 | static void filescan(const char *); |
116 | static int fts_compare(const FTSENT **, const FTSENT **); |
117 | static void mlink_add(struct mlink *, const struct stat *); |
118 | static void mlink_check(struct mpage *, struct mlink *); |
119 | static void mlink_free(struct mlink *); |
120 | static void mlinks_undupe(struct mpage *); |
121 | static void mpages_free(void); |
122 | static void mpages_merge(struct dba *, struct mparse *); |
123 | static void parse_cat(struct mpage *, int); |
124 | static void parse_man(struct mpage *, const struct roff_meta *, |
125 | const struct roff_node *); |
126 | static void parse_mdoc(struct mpage *, const struct roff_meta *, |
127 | const struct roff_node *); |
128 | static int parse_mdoc_head(struct mpage *, const struct roff_meta *, |
129 | const struct roff_node *); |
130 | static int parse_mdoc_Fa(struct mpage *, const struct roff_meta *, |
131 | const struct roff_node *); |
132 | static int parse_mdoc_Fd(struct mpage *, const struct roff_meta *, |
133 | const struct roff_node *); |
134 | static void parse_mdoc_fname(struct mpage *, const struct roff_node *); |
135 | static int parse_mdoc_Fn(struct mpage *, const struct roff_meta *, |
136 | const struct roff_node *); |
137 | static int parse_mdoc_Fo(struct mpage *, const struct roff_meta *, |
138 | const struct roff_node *); |
139 | static int parse_mdoc_Nd(struct mpage *, const struct roff_meta *, |
140 | const struct roff_node *); |
141 | static int parse_mdoc_Nm(struct mpage *, const struct roff_meta *, |
142 | const struct roff_node *); |
143 | static int parse_mdoc_Sh(struct mpage *, const struct roff_meta *, |
144 | const struct roff_node *); |
145 | static int parse_mdoc_Va(struct mpage *, const struct roff_meta *, |
146 | const struct roff_node *); |
147 | static int parse_mdoc_Xr(struct mpage *, const struct roff_meta *, |
148 | const struct roff_node *); |
149 | static void putkey(const struct mpage *, char *, uint64_t); |
150 | static void putkeys(const struct mpage *, char *, size_t, uint64_t); |
151 | static void putmdockey(const struct mpage *, |
152 | const struct roff_node *, uint64_t, int); |
153 | static int render_string(char **, size_t *); |
154 | static void say(const char *, const char *, ...) |
155 | __attribute__((__format__ (__printf__, 2, 3))); |
156 | static int set_basedir(const char *, int); |
157 | static int treescan(void); |
158 | static size_t utf8(unsigned int, char [7]); |
159 | |
160 | static int nodb; /* no database changes */ |
161 | static int mparse_options; /* abort the parse early */ |
162 | static int use_all; /* use all found files */ |
163 | static int debug; /* print what we're doing */ |
164 | static int warnings; /* warn about crap */ |
165 | static int write_utf8; /* write UTF-8 output; else ASCII */ |
166 | static int exitcode; /* to be returned by main */ |
167 | static enum op op; /* operational mode */ |
168 | static char basedir[PATH_MAX1024]; /* current base directory */ |
169 | static size_t basedir_len; /* strlen(basedir) */ |
170 | static struct mpage *mpage_head; /* list of distinct manual pages */ |
171 | static struct ohash mpages; /* table of distinct manual pages */ |
172 | static struct ohash mlinks; /* table of directory entries */ |
173 | static struct ohash names; /* table of all names */ |
174 | static struct ohash strings; /* table of all strings */ |
175 | static uint64_t name_mask; |
176 | |
177 | static const struct mdoc_handler mdoc_handlers[MDOC_MAX - MDOC_Dd] = { |
178 | { NULL((void *)0), 0, NODE_NOPRT(1 << 10) }, /* Dd */ |
179 | { NULL((void *)0), 0, NODE_NOPRT(1 << 10) }, /* Dt */ |
180 | { NULL((void *)0), 0, NODE_NOPRT(1 << 10) }, /* Os */ |
181 | { parse_mdoc_Sh, TYPE_Sh0x0000000000100000ULL, 0 }, /* Sh */ |
182 | { parse_mdoc_head, TYPE_Ss0x0000000000400000ULL, 0 }, /* Ss */ |
183 | { NULL((void *)0), 0, 0 }, /* Pp */ |
184 | { NULL((void *)0), 0, 0 }, /* D1 */ |
185 | { NULL((void *)0), 0, 0 }, /* Dl */ |
186 | { NULL((void *)0), 0, 0 }, /* Bd */ |
187 | { NULL((void *)0), 0, 0 }, /* Ed */ |
188 | { NULL((void *)0), 0, 0 }, /* Bl */ |
189 | { NULL((void *)0), 0, 0 }, /* El */ |
190 | { NULL((void *)0), 0, 0 }, /* It */ |
191 | { NULL((void *)0), 0, 0 }, /* Ad */ |
192 | { NULL((void *)0), TYPE_An0x0000000001000000ULL, 0 }, /* An */ |
193 | { NULL((void *)0), 0, 0 }, /* Ap */ |
194 | { NULL((void *)0), TYPE_Ar0x0000000000000008ULL, 0 }, /* Ar */ |
195 | { NULL((void *)0), TYPE_Cd0x0000000000002000ULL, 0 }, /* Cd */ |
196 | { NULL((void *)0), TYPE_Cm0x0000000000000400ULL, 0 }, /* Cm */ |
197 | { NULL((void *)0), TYPE_Dv0x0000000000000040ULL, 0 }, /* Dv */ |
198 | { NULL((void *)0), TYPE_Er0x0000000000020000ULL, 0 }, /* Er */ |
199 | { NULL((void *)0), TYPE_Ev0x0000000000040000ULL, 0 }, /* Ev */ |
200 | { NULL((void *)0), 0, 0 }, /* Ex */ |
201 | { parse_mdoc_Fa, 0, 0 }, /* Fa */ |
202 | { parse_mdoc_Fd, 0, 0 }, /* Fd */ |
203 | { NULL((void *)0), TYPE_Fl0x0000000000000020ULL, 0 }, /* Fl */ |
204 | { parse_mdoc_Fn, 0, 0 }, /* Fn */ |
205 | { NULL((void *)0), TYPE_Ft0x0000000000008000ULL | TYPE_Vt0x0000001000000000ULL, 0 }, /* Ft */ |
206 | { NULL((void *)0), TYPE_Ic0x0000000000000100ULL, 0 }, /* Ic */ |
207 | { NULL((void *)0), TYPE_In0x0000000000200000ULL, 0 }, /* In */ |
208 | { NULL((void *)0), TYPE_Li0x0000000000000800ULL, 0 }, /* Li */ |
209 | { parse_mdoc_Nd, 0, 0 }, /* Nd */ |
210 | { parse_mdoc_Nm, 0, 0 }, /* Nm */ |
211 | { NULL((void *)0), 0, 0 }, /* Op */ |
212 | { NULL((void *)0), 0, 0 }, /* Ot */ |
213 | { NULL((void *)0), TYPE_Pa0x0000000000000200ULL, NODE_NOSRC(1 << 9) }, /* Pa */ |
214 | { NULL((void *)0), 0, 0 }, /* Rv */ |
215 | { NULL((void *)0), TYPE_St0x0000000004000000ULL, 0 }, /* St */ |
216 | { parse_mdoc_Va, TYPE_Va0x0000000000004000ULL, 0 }, /* Va */ |
217 | { parse_mdoc_Va, TYPE_Vt0x0000001000000000ULL, 0 }, /* Vt */ |
218 | { parse_mdoc_Xr, 0, 0 }, /* Xr */ |
219 | { NULL((void *)0), 0, 0 }, /* %A */ |
220 | { NULL((void *)0), 0, 0 }, /* %B */ |
221 | { NULL((void *)0), 0, 0 }, /* %D */ |
222 | { NULL((void *)0), 0, 0 }, /* %I */ |
223 | { NULL((void *)0), 0, 0 }, /* %J */ |
224 | { NULL((void *)0), 0, 0 }, /* %N */ |
225 | { NULL((void *)0), 0, 0 }, /* %O */ |
226 | { NULL((void *)0), 0, 0 }, /* %P */ |
227 | { NULL((void *)0), 0, 0 }, /* %R */ |
228 | { NULL((void *)0), 0, 0 }, /* %T */ |
229 | { NULL((void *)0), 0, 0 }, /* %V */ |
230 | { NULL((void *)0), 0, 0 }, /* Ac */ |
231 | { NULL((void *)0), 0, 0 }, /* Ao */ |
232 | { NULL((void *)0), 0, 0 }, /* Aq */ |
233 | { NULL((void *)0), TYPE_At0x0000000010000000ULL, 0 }, /* At */ |
234 | { NULL((void *)0), 0, 0 }, /* Bc */ |
235 | { NULL((void *)0), 0, 0 }, /* Bf */ |
236 | { NULL((void *)0), 0, 0 }, /* Bo */ |
237 | { NULL((void *)0), 0, 0 }, /* Bq */ |
238 | { NULL((void *)0), TYPE_Bsx0x0000000200000000ULL, NODE_NOSRC(1 << 9) }, /* Bsx */ |
239 | { NULL((void *)0), TYPE_Bx0x0000000008000000ULL, NODE_NOSRC(1 << 9) }, /* Bx */ |
240 | { NULL((void *)0), 0, 0 }, /* Db */ |
241 | { NULL((void *)0), 0, 0 }, /* Dc */ |
242 | { NULL((void *)0), 0, 0 }, /* Do */ |
243 | { NULL((void *)0), 0, 0 }, /* Dq */ |
244 | { NULL((void *)0), 0, 0 }, /* Ec */ |
245 | { NULL((void *)0), 0, 0 }, /* Ef */ |
246 | { NULL((void *)0), TYPE_Em0x0000000000001000ULL, 0 }, /* Em */ |
247 | { NULL((void *)0), 0, 0 }, /* Eo */ |
248 | { NULL((void *)0), TYPE_Fx0x0000000040000000ULL, NODE_NOSRC(1 << 9) }, /* Fx */ |
249 | { NULL((void *)0), TYPE_Ms0x0000000100000000ULL, 0 }, /* Ms */ |
250 | { NULL((void *)0), 0, 0 }, /* No */ |
251 | { NULL((void *)0), 0, 0 }, /* Ns */ |
252 | { NULL((void *)0), TYPE_Nx0x0000000020000000ULL, NODE_NOSRC(1 << 9) }, /* Nx */ |
253 | { NULL((void *)0), TYPE_Ox0x0000000000800000ULL, NODE_NOSRC(1 << 9) }, /* Ox */ |
254 | { NULL((void *)0), 0, 0 }, /* Pc */ |
255 | { NULL((void *)0), 0, 0 }, /* Pf */ |
256 | { NULL((void *)0), 0, 0 }, /* Po */ |
257 | { NULL((void *)0), 0, 0 }, /* Pq */ |
258 | { NULL((void *)0), 0, 0 }, /* Qc */ |
259 | { NULL((void *)0), 0, 0 }, /* Ql */ |
260 | { NULL((void *)0), 0, 0 }, /* Qo */ |
261 | { NULL((void *)0), 0, 0 }, /* Qq */ |
262 | { NULL((void *)0), 0, 0 }, /* Re */ |
263 | { NULL((void *)0), 0, 0 }, /* Rs */ |
264 | { NULL((void *)0), 0, 0 }, /* Sc */ |
265 | { NULL((void *)0), 0, 0 }, /* So */ |
266 | { NULL((void *)0), 0, 0 }, /* Sq */ |
267 | { NULL((void *)0), 0, 0 }, /* Sm */ |
268 | { NULL((void *)0), 0, 0 }, /* Sx */ |
269 | { NULL((void *)0), TYPE_Sy0x0000000000080000ULL, 0 }, /* Sy */ |
270 | { NULL((void *)0), TYPE_Tn0x0000000000010000ULL, 0 }, /* Tn */ |
271 | { NULL((void *)0), 0, NODE_NOSRC(1 << 9) }, /* Ux */ |
272 | { NULL((void *)0), 0, 0 }, /* Xc */ |
273 | { NULL((void *)0), 0, 0 }, /* Xo */ |
274 | { parse_mdoc_Fo, 0, 0 }, /* Fo */ |
275 | { NULL((void *)0), 0, 0 }, /* Fc */ |
276 | { NULL((void *)0), 0, 0 }, /* Oo */ |
277 | { NULL((void *)0), 0, 0 }, /* Oc */ |
278 | { NULL((void *)0), 0, 0 }, /* Bk */ |
279 | { NULL((void *)0), 0, 0 }, /* Ek */ |
280 | { NULL((void *)0), 0, 0 }, /* Bt */ |
281 | { NULL((void *)0), 0, 0 }, /* Hf */ |
282 | { NULL((void *)0), 0, 0 }, /* Fr */ |
283 | { NULL((void *)0), 0, 0 }, /* Ud */ |
284 | { NULL((void *)0), TYPE_Lb0x0000002000000000ULL, NODE_NOSRC(1 << 9) }, /* Lb */ |
285 | { NULL((void *)0), 0, 0 }, /* Lp */ |
286 | { NULL((void *)0), TYPE_Lk0x0000000080000000ULL, 0 }, /* Lk */ |
287 | { NULL((void *)0), TYPE_Mt0x0000000002000000ULL, NODE_NOSRC(1 << 9) }, /* Mt */ |
288 | { NULL((void *)0), 0, 0 }, /* Brq */ |
289 | { NULL((void *)0), 0, 0 }, /* Bro */ |
290 | { NULL((void *)0), 0, 0 }, /* Brc */ |
291 | { NULL((void *)0), 0, 0 }, /* %C */ |
292 | { NULL((void *)0), 0, 0 }, /* Es */ |
293 | { NULL((void *)0), 0, 0 }, /* En */ |
294 | { NULL((void *)0), TYPE_Dx0x0000000400000000ULL, NODE_NOSRC(1 << 9) }, /* Dx */ |
295 | { NULL((void *)0), 0, 0 }, /* %Q */ |
296 | { NULL((void *)0), 0, 0 }, /* %U */ |
297 | { NULL((void *)0), 0, 0 }, /* Ta */ |
298 | }; |
299 | |
300 | |
301 | int |
302 | mandocdb(int argc, char *argv[]) |
303 | { |
304 | struct manconf conf; |
305 | struct mparse *mp; |
306 | struct dba *dba; |
307 | const char *path_arg, *progname; |
308 | size_t j, sz; |
309 | int ch, i; |
310 | |
311 | if (pledge("stdio rpath wpath cpath", NULL((void *)0)) == -1) { |
312 | warn("pledge"); |
313 | return (int)MANDOCLEVEL_SYSERR; |
314 | } |
315 | |
316 | memset(&conf, 0, sizeof(conf)); |
317 | |
318 | /* |
319 | * We accept a few different invocations. |
320 | * The CHECKOP macro makes sure that invocation styles don't |
321 | * clobber each other. |
322 | */ |
323 | #define CHECKOP(_op, _ch)do if ((_op) != OP_DEFAULT) { warnx("-%c: Conflicting option" , (_ch)); goto usage; } while ( 0) do \ |
324 | if ((_op) != OP_DEFAULT) { \ |
325 | warnx("-%c: Conflicting option", (_ch)); \ |
326 | goto usage; \ |
327 | } while (/*CONSTCOND*/0) |
328 | |
329 | mparse_options = MPARSE_VALIDATE(1 << 6); |
330 | path_arg = NULL((void *)0); |
331 | op = OP_DEFAULT; |
332 | |
333 | while ((ch = getopt(argc, argv, "aC:Dd:npQT:tu:v")) != -1) |
334 | switch (ch) { |
335 | case 'a': |
336 | use_all = 1; |
337 | break; |
338 | case 'C': |
339 | CHECKOP(op, ch)do if ((op) != OP_DEFAULT) { warnx("-%c: Conflicting option", (ch)); goto usage; } while ( 0); |
340 | path_arg = optarg; |
341 | op = OP_CONFFILE; |
342 | break; |
343 | case 'D': |
344 | debug++; |
345 | break; |
346 | case 'd': |
347 | CHECKOP(op, ch)do if ((op) != OP_DEFAULT) { warnx("-%c: Conflicting option", (ch)); goto usage; } while ( 0); |
348 | path_arg = optarg; |
349 | op = OP_UPDATE; |
350 | break; |
351 | case 'n': |
352 | nodb = 1; |
353 | break; |
354 | case 'p': |
355 | warnings = 1; |
356 | break; |
357 | case 'Q': |
358 | mparse_options |= MPARSE_QUICK(1 << 3); |
359 | break; |
360 | case 'T': |
361 | if (strcmp(optarg, "utf8") != 0) { |
362 | warnx("-T%s: Unsupported output format", |
363 | optarg); |
364 | goto usage; |
365 | } |
366 | write_utf8 = 1; |
367 | break; |
368 | case 't': |
369 | CHECKOP(op, ch)do if ((op) != OP_DEFAULT) { warnx("-%c: Conflicting option", (ch)); goto usage; } while ( 0); |
370 | dup2(STDOUT_FILENO1, STDERR_FILENO2); |
371 | op = OP_TEST; |
372 | nodb = warnings = 1; |
373 | break; |
374 | case 'u': |
375 | CHECKOP(op, ch)do if ((op) != OP_DEFAULT) { warnx("-%c: Conflicting option", (ch)); goto usage; } while ( 0); |
376 | path_arg = optarg; |
377 | op = OP_DELETE; |
378 | break; |
379 | case 'v': |
380 | /* Compatibility with espie@'s makewhatis. */ |
381 | break; |
382 | default: |
383 | goto usage; |
384 | } |
385 | |
386 | argc -= optind; |
387 | argv += optind; |
388 | |
389 | if (nodb) { |
390 | if (pledge("stdio rpath", NULL((void *)0)) == -1) { |
391 | warn("pledge"); |
392 | return (int)MANDOCLEVEL_SYSERR; |
393 | } |
394 | } |
395 | |
396 | if (op == OP_CONFFILE && argc > 0) { |
397 | warnx("-C: Too many arguments"); |
398 | goto usage; |
399 | } |
400 | |
401 | exitcode = (int)MANDOCLEVEL_OK; |
402 | mchars_alloc(); |
403 | mp = mparse_alloc(mparse_options, MANDOC_OS_OTHER, NULL((void *)0)); |
404 | mandoc_ohash_init(&mpages, 6, offsetof(struct mpage, inodev)__builtin_offsetof(struct mpage, inodev)); |
405 | mandoc_ohash_init(&mlinks, 6, offsetof(struct mlink, file)__builtin_offsetof(struct mlink, file)); |
406 | |
407 | if (op == OP_UPDATE || op == OP_DELETE || op == OP_TEST) { |
408 | |
409 | /* |
410 | * Most of these deal with a specific directory. |
411 | * Jump into that directory first. |
412 | */ |
413 | if (op != OP_TEST && set_basedir(path_arg, 1) == 0) |
414 | goto out; |
415 | |
416 | dba = nodb ? dba_new(128) : dba_read(MANDOC_DB"mandoc.db"); |
417 | if (dba != NULL((void *)0)) { |
418 | /* |
419 | * The existing database is usable. Process |
420 | * all files specified on the command-line. |
421 | */ |
422 | use_all = 1; |
423 | for (i = 0; i < argc; i++) |
424 | filescan(argv[i]); |
425 | if (nodb == 0) |
426 | dbprune(dba); |
427 | } else { |
428 | /* Database missing or corrupt. */ |
429 | if (op != OP_UPDATE || errno(*__errno()) != ENOENT2) |
430 | say(MANDOC_DB"mandoc.db", "%s: Automatically recreating" |
431 | " from scratch", strerror(errno(*__errno()))); |
432 | exitcode = (int)MANDOCLEVEL_OK; |
433 | op = OP_DEFAULT; |
434 | if (treescan() == 0) |
435 | goto out; |
436 | dba = dba_new(128); |
437 | } |
438 | if (op != OP_DELETE) |
439 | mpages_merge(dba, mp); |
440 | if (nodb == 0) |
441 | dbwrite(dba); |
442 | dba_free(dba); |
443 | } else { |
444 | /* |
445 | * If we have arguments, use them as our manpaths. |
446 | * If we don't, use man.conf(5). |
447 | */ |
448 | if (argc > 0) { |
449 | conf.manpath.paths = mandoc_reallocarray(NULL((void *)0), |
450 | argc, sizeof(char *)); |
451 | conf.manpath.sz = (size_t)argc; |
452 | for (i = 0; i < argc; i++) |
453 | conf.manpath.paths[i] = mandoc_strdup(argv[i]); |
454 | } else |
455 | manconf_parse(&conf, path_arg, NULL((void *)0), NULL((void *)0)); |
456 | |
457 | if (conf.manpath.sz == 0) { |
458 | exitcode = (int)MANDOCLEVEL_BADARG; |
459 | say("", "Empty manpath"); |
460 | } |
461 | |
462 | /* |
463 | * First scan the tree rooted at a base directory, then |
464 | * build a new database and finally move it into place. |
465 | * Ignore zero-length directories and strip trailing |
466 | * slashes. |
467 | */ |
468 | for (j = 0; j < conf.manpath.sz; j++) { |
469 | sz = strlen(conf.manpath.paths[j]); |
470 | if (sz && conf.manpath.paths[j][sz - 1] == '/') |
471 | conf.manpath.paths[j][--sz] = '\0'; |
472 | if (sz == 0) |
473 | continue; |
474 | |
475 | if (j) { |
476 | mandoc_ohash_init(&mpages, 6, |
477 | offsetof(struct mpage, inodev)__builtin_offsetof(struct mpage, inodev)); |
478 | mandoc_ohash_init(&mlinks, 6, |
479 | offsetof(struct mlink, file)__builtin_offsetof(struct mlink, file)); |
480 | } |
481 | |
482 | if (set_basedir(conf.manpath.paths[j], argc > 0) == 0) |
483 | continue; |
484 | if (treescan() == 0) |
485 | continue; |
486 | dba = dba_new(128); |
487 | mpages_merge(dba, mp); |
488 | if (nodb == 0) |
489 | dbwrite(dba); |
490 | dba_free(dba); |
491 | |
492 | if (j + 1 < conf.manpath.sz) { |
493 | mpages_free(); |
494 | ohash_delete(&mpages); |
495 | ohash_delete(&mlinks); |
496 | } |
497 | } |
498 | } |
499 | out: |
500 | manconf_free(&conf); |
501 | mparse_free(mp); |
502 | mchars_free(); |
503 | mpages_free(); |
504 | ohash_delete(&mpages); |
505 | ohash_delete(&mlinks); |
506 | return exitcode; |
507 | usage: |
508 | progname = getprogname(); |
509 | fprintf(stderr(&__sF[2]), "usage: %s [-aDnpQ] [-C file] [-Tutf8]\n" |
510 | " %s [-aDnpQ] [-Tutf8] dir ...\n" |
511 | " %s [-DnpQ] [-Tutf8] -d dir [file ...]\n" |
512 | " %s [-Dnp] -u dir [file ...]\n" |
513 | " %s [-Q] -t file ...\n", |
514 | progname, progname, progname, progname, progname); |
515 | |
516 | return (int)MANDOCLEVEL_BADARG; |
517 | } |
518 | |
519 | /* |
520 | * To get a singly linked list in alpha order while inserting entries |
521 | * at the beginning, process directory entries in reverse alpha order. |
522 | */ |
523 | static int |
524 | fts_compare(const FTSENT **a, const FTSENT **b) |
525 | { |
526 | return -strcmp((*a)->fts_name, (*b)->fts_name); |
527 | } |
528 | |
529 | /* |
530 | * Scan a directory tree rooted at "basedir" for manpages. |
531 | * We use fts(), scanning directory parts along the way for clues to our |
532 | * section and architecture. |
533 | * |
534 | * If use_all has been specified, grok all files. |
535 | * If not, sanitise paths to the following: |
536 | * |
537 | * [./]man*[/<arch>]/<name>.<section> |
538 | * or |
539 | * [./]cat<section>[/<arch>]/<name>.0 |
540 | * |
541 | * TODO: accommodate for multi-language directories. |
542 | */ |
543 | static int |
544 | treescan(void) |
545 | { |
546 | char buf[PATH_MAX1024]; |
547 | FTS *f; |
548 | FTSENT *ff; |
549 | struct mlink *mlink; |
550 | int gzip; |
551 | enum form dform; |
552 | char *dsec, *arch, *fsec, *cp; |
553 | const char *path; |
554 | const char *argv[2]; |
555 | |
556 | argv[0] = "."; |
557 | argv[1] = NULL((void *)0); |
558 | |
559 | f = fts_open((char * const *)argv, FTS_PHYSICAL0x0010 | FTS_NOCHDIR0x0004, |
560 | fts_compare); |
561 | if (f == NULL((void *)0)) { |
562 | exitcode = (int)MANDOCLEVEL_SYSERR; |
563 | say("", "&fts_open"); |
564 | return 0; |
565 | } |
566 | |
567 | dsec = arch = NULL((void *)0); |
568 | dform = FORM_NONE; |
569 | |
570 | while ((ff = fts_read(f)) != NULL((void *)0)) { |
571 | path = ff->fts_path + 2; |
572 | switch (ff->fts_info) { |
573 | |
574 | /* |
575 | * Symbolic links require various sanity checks, |
576 | * then get handled just like regular files. |
577 | */ |
578 | case FTS_SL12: |
579 | if (realpath(path, buf) == NULL((void *)0)) { |
580 | if (warnings) |
581 | say(path, "&realpath"); |
582 | continue; |
583 | } |
584 | if (strncmp(buf, basedir, basedir_len) != 0) { |
585 | if (warnings) say("", |
586 | "%s: outside base directory", buf); |
587 | continue; |
588 | } |
589 | /* Use logical inode to avoid mpages dupe. */ |
590 | if (stat(path, ff->fts_statp) == -1) { |
591 | if (warnings) |
592 | say(path, "&stat"); |
593 | continue; |
594 | } |
595 | if ((ff->fts_statp->st_mode & S_IFMT0170000) != S_IFREG0100000) |
596 | continue; |
597 | /* FALLTHROUGH */ |
598 | |
599 | /* |
600 | * If we're a regular file, add an mlink by using the |
601 | * stored directory data and handling the filename. |
602 | */ |
603 | case FTS_F8: |
604 | if ( ! strcmp(path, MANDOC_DB"mandoc.db")) |
605 | continue; |
606 | if ( ! use_all && ff->fts_level < 2) { |
607 | if (warnings) |
608 | say(path, "Extraneous file"); |
609 | continue; |
610 | } |
611 | gzip = 0; |
612 | fsec = NULL((void *)0); |
613 | while (fsec == NULL((void *)0)) { |
614 | fsec = strrchr(ff->fts_name, '.'); |
615 | if (fsec == NULL((void *)0) || strcmp(fsec+1, "gz")) |
616 | break; |
617 | gzip = 1; |
618 | *fsec = '\0'; |
619 | fsec = NULL((void *)0); |
620 | } |
621 | if (fsec == NULL((void *)0)) { |
622 | if ( ! use_all) { |
623 | if (warnings) |
624 | say(path, |
625 | "No filename suffix"); |
626 | continue; |
627 | } |
628 | } else if ( ! strcmp(++fsec, "html")) { |
629 | if (warnings) |
630 | say(path, "Skip html"); |
631 | continue; |
632 | } else if ( ! strcmp(fsec, "ps")) { |
633 | if (warnings) |
634 | say(path, "Skip ps"); |
635 | continue; |
636 | } else if ( ! strcmp(fsec, "pdf")) { |
637 | if (warnings) |
638 | say(path, "Skip pdf"); |
639 | continue; |
640 | } else if ( ! use_all && |
641 | ((dform == FORM_SRC && |
642 | strncmp(fsec, dsec, strlen(dsec))) || |
643 | (dform == FORM_CAT && strcmp(fsec, "0")))) { |
644 | if (warnings) |
645 | say(path, "Wrong filename suffix"); |
646 | continue; |
647 | } else |
648 | fsec[-1] = '\0'; |
649 | |
650 | mlink = mandoc_calloc(1, sizeof(struct mlink)); |
651 | if (strlcpy(mlink->file, path, |
652 | sizeof(mlink->file)) >= |
653 | sizeof(mlink->file)) { |
654 | say(path, "Filename too long"); |
655 | free(mlink); |
656 | continue; |
657 | } |
658 | mlink->dform = dform; |
659 | mlink->dsec = dsec; |
660 | mlink->arch = arch; |
661 | mlink->name = ff->fts_name; |
662 | mlink->fsec = fsec; |
663 | mlink->gzip = gzip; |
664 | mlink_add(mlink, ff->fts_statp); |
665 | continue; |
666 | |
667 | case FTS_D1: |
668 | case FTS_DP6: |
669 | break; |
670 | |
671 | default: |
672 | if (warnings) |
673 | say(path, "Not a regular file"); |
674 | continue; |
675 | } |
676 | |
677 | switch (ff->fts_level) { |
678 | case 0: |
679 | /* Ignore the root directory. */ |
680 | break; |
681 | case 1: |
682 | /* |
683 | * This might contain manX/ or catX/. |
684 | * Try to infer this from the name. |
685 | * If we're not in use_all, enforce it. |
686 | */ |
687 | cp = ff->fts_name; |
688 | if (ff->fts_info == FTS_DP6) { |
689 | dform = FORM_NONE; |
690 | dsec = NULL((void *)0); |
691 | break; |
692 | } |
693 | |
694 | if ( ! strncmp(cp, "man", 3)) { |
695 | dform = FORM_SRC; |
696 | dsec = cp + 3; |
697 | } else if ( ! strncmp(cp, "cat", 3)) { |
698 | dform = FORM_CAT; |
699 | dsec = cp + 3; |
700 | } else { |
701 | dform = FORM_NONE; |
702 | dsec = NULL((void *)0); |
703 | } |
704 | |
705 | if (dsec != NULL((void *)0) || use_all) |
706 | break; |
707 | |
708 | if (warnings) |
709 | say(path, "Unknown directory part"); |
710 | fts_set(f, ff, FTS_SKIP4); |
711 | break; |
712 | case 2: |
713 | /* |
714 | * Possibly our architecture. |
715 | * If we're descending, keep tabs on it. |
716 | */ |
717 | if (ff->fts_info != FTS_DP6 && dsec != NULL((void *)0)) |
718 | arch = ff->fts_name; |
719 | else |
720 | arch = NULL((void *)0); |
721 | break; |
722 | default: |
723 | if (ff->fts_info == FTS_DP6 || use_all) |
724 | break; |
725 | if (warnings) |
726 | say(path, "Extraneous directory part"); |
727 | fts_set(f, ff, FTS_SKIP4); |
728 | break; |
729 | } |
730 | } |
731 | |
732 | fts_close(f); |
733 | return 1; |
734 | } |
735 | |
736 | /* |
737 | * Add a file to the mlinks table. |
738 | * Do not verify that it's a "valid" looking manpage (we'll do that |
739 | * later). |
740 | * |
741 | * Try to infer the manual section, architecture, and page name from the |
742 | * path, assuming it looks like |
743 | * |
744 | * [./]man*[/<arch>]/<name>.<section> |
745 | * or |
746 | * [./]cat<section>[/<arch>]/<name>.0 |
747 | * |
748 | * See treescan() for the fts(3) version of this. |
749 | */ |
750 | static void |
751 | filescan(const char *infile) |
752 | { |
753 | struct stat st; |
754 | struct mlink *mlink; |
755 | char *linkfile, *p, *realdir, *start, *usefile; |
756 | size_t realdir_len; |
757 | |
758 | assert(use_all)((use_all) ? (void)0 : __assert2("/usr/src/usr.bin/mandoc/mandocdb.c" , 758, __func__, "use_all")); |
759 | |
760 | if (strncmp(infile, "./", 2) == 0) |
761 | infile += 2; |
762 | |
763 | /* |
764 | * We have to do lstat(2) before realpath(3) loses |
765 | * the information whether this is a symbolic link. |
766 | * We need to know that because for symbolic links, |
767 | * we want to use the original file name, while for |
768 | * regular files, we want to use the real path. |
769 | */ |
770 | if (lstat(infile, &st) == -1) { |
771 | exitcode = (int)MANDOCLEVEL_BADARG; |
772 | say(infile, "&lstat"); |
773 | return; |
774 | } else if (S_ISREG(st.st_mode)((st.st_mode & 0170000) == 0100000) == 0 && S_ISLNK(st.st_mode)((st.st_mode & 0170000) == 0120000) == 0) { |
775 | exitcode = (int)MANDOCLEVEL_BADARG; |
776 | say(infile, "Not a regular file"); |
777 | return; |
778 | } |
779 | |
780 | /* |
781 | * We have to resolve the file name to the real path |
782 | * in any case for the base directory check. |
783 | */ |
784 | if ((usefile = realpath(infile, NULL((void *)0))) == NULL((void *)0)) { |
785 | exitcode = (int)MANDOCLEVEL_BADARG; |
786 | say(infile, "&realpath"); |
787 | return; |
788 | } |
789 | |
790 | if (op == OP_TEST) |
791 | start = usefile; |
792 | else if (strncmp(usefile, basedir, basedir_len) == 0) |
793 | start = usefile + basedir_len; |
794 | else { |
795 | exitcode = (int)MANDOCLEVEL_BADARG; |
796 | say("", "%s: outside base directory", infile); |
797 | free(usefile); |
798 | return; |
799 | } |
800 | |
801 | /* |
802 | * Now we are sure the file is inside our tree. |
803 | * If it is a symbolic link, ignore the real path |
804 | * and use the original name. |
805 | */ |
806 | do { |
807 | if (S_ISLNK(st.st_mode)((st.st_mode & 0170000) == 0120000) == 0) |
808 | break; |
809 | |
810 | /* |
811 | * Some implementations of realpath(3) may succeed |
812 | * even if the target of the link does not exist, |
813 | * so check again for extra safety. |
814 | */ |
815 | if (stat(usefile, &st) == -1) { |
816 | exitcode = (int)MANDOCLEVEL_BADARG; |
817 | say(infile, "&stat"); |
818 | free(usefile); |
819 | return; |
820 | } |
821 | linkfile = mandoc_strdup(infile); |
822 | if (op == OP_TEST) { |
823 | free(usefile); |
824 | start = usefile = linkfile; |
825 | break; |
826 | } |
827 | if (strncmp(infile, basedir, basedir_len) == 0) { |
828 | free(usefile); |
829 | usefile = linkfile; |
830 | start = usefile + basedir_len; |
831 | break; |
832 | } |
833 | |
834 | /* |
835 | * This symbolic link points into the basedir |
836 | * from the outside. Let's see whether any of |
837 | * the parent directories resolve to the basedir. |
838 | */ |
839 | p = strchr(linkfile, '\0'); |
840 | do { |
841 | while (*--p != '/') |
842 | continue; |
843 | *p = '\0'; |
844 | if ((realdir = realpath(linkfile, NULL((void *)0))) == NULL((void *)0)) { |
845 | exitcode = (int)MANDOCLEVEL_BADARG; |
846 | say(infile, "&realpath"); |
847 | free(linkfile); |
848 | free(usefile); |
849 | return; |
850 | } |
851 | realdir_len = strlen(realdir) + 1; |
852 | free(realdir); |
853 | *p = '/'; |
854 | } while (realdir_len > basedir_len); |
855 | |
856 | /* |
857 | * If one of the directories resolves to the basedir, |
858 | * use the rest of the original name. |
859 | * Otherwise, the best we can do |
860 | * is to use the filename pointed to. |
861 | */ |
862 | if (realdir_len == basedir_len) { |
863 | free(usefile); |
864 | usefile = linkfile; |
865 | start = p + 1; |
866 | } else { |
867 | free(linkfile); |
868 | start = usefile + basedir_len; |
869 | } |
870 | } while (/* CONSTCOND */ 0); |
871 | |
872 | mlink = mandoc_calloc(1, sizeof(struct mlink)); |
873 | mlink->dform = FORM_NONE; |
874 | if (strlcpy(mlink->file, start, sizeof(mlink->file)) >= |
875 | sizeof(mlink->file)) { |
876 | say(start, "Filename too long"); |
877 | free(mlink); |
878 | free(usefile); |
879 | return; |
880 | } |
881 | |
882 | /* |
883 | * In test mode or when the original name is absolute |
884 | * but outside our tree, guess the base directory. |
885 | */ |
886 | |
887 | if (op == OP_TEST || (start == usefile && *start == '/')) { |
888 | if (strncmp(usefile, "man/", 4) == 0) |
889 | start = usefile + 4; |
890 | else if ((start = strstr(usefile, "/man/")) != NULL((void *)0)) |
891 | start += 5; |
892 | else |
893 | start = usefile; |
894 | } |
895 | |
896 | /* |
897 | * First try to guess our directory structure. |
898 | * If we find a separator, try to look for man* or cat*. |
899 | * If we find one of these and what's underneath is a directory, |
900 | * assume it's an architecture. |
901 | */ |
902 | if ((p = strchr(start, '/')) != NULL((void *)0)) { |
903 | *p++ = '\0'; |
904 | if (strncmp(start, "man", 3) == 0) { |
905 | mlink->dform = FORM_SRC; |
906 | mlink->dsec = start + 3; |
907 | } else if (strncmp(start, "cat", 3) == 0) { |
908 | mlink->dform = FORM_CAT; |
909 | mlink->dsec = start + 3; |
910 | } |
911 | |
912 | start = p; |
913 | if (mlink->dsec != NULL((void *)0) && (p = strchr(start, '/')) != NULL((void *)0)) { |
914 | *p++ = '\0'; |
915 | mlink->arch = start; |
916 | start = p; |
917 | } |
918 | } |
919 | |
920 | /* |
921 | * Now check the file suffix. |
922 | * Suffix of `.0' indicates a catpage, `.1-9' is a manpage. |
923 | */ |
924 | p = strrchr(start, '\0'); |
925 | while (p-- > start && *p != '/' && *p != '.') |
926 | continue; |
927 | |
928 | if (*p == '.') { |
929 | *p++ = '\0'; |
930 | mlink->fsec = p; |
931 | } |
932 | |
933 | /* |
934 | * Now try to parse the name. |
935 | * Use the filename portion of the path. |
936 | */ |
937 | mlink->name = start; |
938 | if ((p = strrchr(start, '/')) != NULL((void *)0)) { |
939 | mlink->name = p + 1; |
940 | *p = '\0'; |
941 | } |
942 | mlink_add(mlink, &st); |
943 | free(usefile); |
944 | } |
945 | |
946 | static void |
947 | mlink_add(struct mlink *mlink, const struct stat *st) |
948 | { |
949 | struct inodev inodev; |
950 | struct mpage *mpage; |
951 | unsigned int slot; |
952 | |
953 | assert(NULL != mlink->file)((((void *)0) != mlink->file) ? (void)0 : __assert2("/usr/src/usr.bin/mandoc/mandocdb.c" , 953, __func__, "NULL != mlink->file")); |
954 | |
955 | mlink->dsec = mandoc_strdup(mlink->dsec ? mlink->dsec : ""); |
956 | mlink->arch = mandoc_strdup(mlink->arch ? mlink->arch : ""); |
957 | mlink->name = mandoc_strdup(mlink->name ? mlink->name : ""); |
958 | mlink->fsec = mandoc_strdup(mlink->fsec ? mlink->fsec : ""); |
959 | |
960 | if ('0' == *mlink->fsec) { |
961 | free(mlink->fsec); |
962 | mlink->fsec = mandoc_strdup(mlink->dsec); |
963 | mlink->fform = FORM_CAT; |
964 | } else if ('1' <= *mlink->fsec && '9' >= *mlink->fsec) |
965 | mlink->fform = FORM_SRC; |
966 | else |
967 | mlink->fform = FORM_NONE; |
968 | |
969 | slot = ohash_qlookup(&mlinks, mlink->file); |
970 | assert(NULL == ohash_find(&mlinks, slot))((((void *)0) == ohash_find(&mlinks, slot)) ? (void)0 : __assert2 ("/usr/src/usr.bin/mandoc/mandocdb.c", 970, __func__, "NULL == ohash_find(&mlinks, slot)" )); |
971 | ohash_insert(&mlinks, slot, mlink); |
972 | |
973 | memset(&inodev, 0, sizeof(inodev)); /* Clear padding. */ |
974 | inodev.st_ino = st->st_ino; |
975 | inodev.st_dev = st->st_dev; |
976 | slot = ohash_lookup_memory(&mpages, (char *)&inodev, |
977 | sizeof(struct inodev), inodev.st_ino); |
978 | mpage = ohash_find(&mpages, slot); |
979 | if (NULL((void *)0) == mpage) { |
980 | mpage = mandoc_calloc(1, sizeof(struct mpage)); |
981 | mpage->inodev.st_ino = inodev.st_ino; |
982 | mpage->inodev.st_dev = inodev.st_dev; |
983 | mpage->form = FORM_NONE; |
984 | mpage->next = mpage_head; |
985 | mpage_head = mpage; |
986 | ohash_insert(&mpages, slot, mpage); |
987 | } else |
988 | mlink->next = mpage->mlinks; |
989 | mpage->mlinks = mlink; |
990 | mlink->mpage = mpage; |
991 | } |
992 | |
993 | static void |
994 | mlink_free(struct mlink *mlink) |
995 | { |
996 | |
997 | free(mlink->dsec); |
998 | free(mlink->arch); |
999 | free(mlink->name); |
1000 | free(mlink->fsec); |
1001 | free(mlink); |
1002 | } |
1003 | |
1004 | static void |
1005 | mpages_free(void) |
1006 | { |
1007 | struct mpage *mpage; |
1008 | struct mlink *mlink; |
1009 | |
1010 | while ((mpage = mpage_head) != NULL((void *)0)) { |
1011 | while ((mlink = mpage->mlinks) != NULL((void *)0)) { |
1012 | mpage->mlinks = mlink->next; |
1013 | mlink_free(mlink); |
1014 | } |
1015 | mpage_head = mpage->next; |
1016 | free(mpage->sec); |
1017 | free(mpage->arch); |
1018 | free(mpage->title); |
1019 | free(mpage->desc); |
1020 | free(mpage); |
1021 | } |
1022 | } |
1023 | |
1024 | /* |
1025 | * For each mlink to the mpage, check whether the path looks like |
1026 | * it is formatted, and if it does, check whether a source manual |
1027 | * exists by the same name, ignoring the suffix. |
1028 | * If both conditions hold, drop the mlink. |
1029 | */ |
1030 | static void |
1031 | mlinks_undupe(struct mpage *mpage) |
1032 | { |
1033 | char buf[PATH_MAX1024]; |
1034 | struct mlink **prev; |
1035 | struct mlink *mlink; |
1036 | char *bufp; |
1037 | |
1038 | mpage->form = FORM_CAT; |
1039 | prev = &mpage->mlinks; |
1040 | while (NULL((void *)0) != (mlink = *prev)) { |
1041 | if (FORM_CAT != mlink->dform) { |
1042 | mpage->form = FORM_NONE; |
1043 | goto nextlink; |
1044 | } |
1045 | (void)strlcpy(buf, mlink->file, sizeof(buf)); |
1046 | bufp = strstr(buf, "cat"); |
1047 | assert(NULL != bufp)((((void *)0) != bufp) ? (void)0 : __assert2("/usr/src/usr.bin/mandoc/mandocdb.c" , 1047, __func__, "NULL != bufp")); |
1048 | memcpy(bufp, "man", 3); |
1049 | if (NULL((void *)0) != (bufp = strrchr(buf, '.'))) |
1050 | *++bufp = '\0'; |
1051 | (void)strlcat(buf, mlink->dsec, sizeof(buf)); |
1052 | if (NULL((void *)0) == ohash_find(&mlinks, |
1053 | ohash_qlookup(&mlinks, buf))) |
1054 | goto nextlink; |
1055 | if (warnings) |
1056 | say(mlink->file, "Man source exists: %s", buf); |
1057 | if (use_all) |
1058 | goto nextlink; |
1059 | *prev = mlink->next; |
1060 | mlink_free(mlink); |
1061 | continue; |
1062 | nextlink: |
1063 | prev = &(*prev)->next; |
1064 | } |
1065 | } |
1066 | |
1067 | static void |
1068 | mlink_check(struct mpage *mpage, struct mlink *mlink) |
1069 | { |
1070 | struct str *str; |
1071 | unsigned int slot; |
1072 | |
1073 | /* |
1074 | * Check whether the manual section given in a file |
1075 | * agrees with the directory where the file is located. |
1076 | * Some manuals have suffixes like (3p) on their |
1077 | * section number either inside the file or in the |
1078 | * directory name, some are linked into more than one |
1079 | * section, like encrypt(1) = makekey(8). |
1080 | */ |
1081 | |
1082 | if (FORM_SRC == mpage->form && |
1083 | strcasecmp(mpage->sec, mlink->dsec)) |
1084 | say(mlink->file, "Section \"%s\" manual in %s directory", |
1085 | mpage->sec, mlink->dsec); |
1086 | |
1087 | /* |
1088 | * Manual page directories exist for each kernel |
1089 | * architecture as returned by machine(1). |
1090 | * However, many manuals only depend on the |
1091 | * application architecture as returned by arch(1). |
1092 | * For example, some (2/ARM) manuals are shared |
1093 | * across the "armish" and "zaurus" kernel |
1094 | * architectures. |
1095 | * A few manuals are even shared across completely |
1096 | * different architectures, for example fdformat(1) |
1097 | * on amd64, i386, and sparc64. |
1098 | */ |
1099 | |
1100 | if (strcasecmp(mpage->arch, mlink->arch)) |
1101 | say(mlink->file, "Architecture \"%s\" manual in " |
1102 | "\"%s\" directory", mpage->arch, mlink->arch); |
1103 | |
1104 | /* |
1105 | * XXX |
1106 | * parse_cat() doesn't set NAME_TITLE yet. |
1107 | */ |
1108 | |
1109 | if (FORM_CAT == mpage->form) |
1110 | return; |
1111 | |
1112 | /* |
1113 | * Check whether this mlink |
1114 | * appears as a name in the NAME section. |
1115 | */ |
1116 | |
1117 | slot = ohash_qlookup(&names, mlink->name); |
1118 | str = ohash_find(&names, slot); |
1119 | assert(NULL != str)((((void *)0) != str) ? (void)0 : __assert2("/usr/src/usr.bin/mandoc/mandocdb.c" , 1119, __func__, "NULL != str")); |
1120 | if ( ! (NAME_TITLE0x0000004000000006ULL & str->mask)) |
1121 | say(mlink->file, "Name missing in NAME section"); |
1122 | } |
1123 | |
1124 | /* |
1125 | * Run through the files in the global vector "mpages" |
1126 | * and add them to the database specified in "basedir". |
1127 | * |
1128 | * This handles the parsing scheme itself, using the cues of directory |
1129 | * and filename to determine whether the file is parsable or not. |
1130 | */ |
1131 | static void |
1132 | mpages_merge(struct dba *dba, struct mparse *mp) |
1133 | { |
1134 | struct mpage *mpage, *mpage_dest; |
1135 | struct mlink *mlink, *mlink_dest; |
1136 | struct roff_meta *meta; |
1137 | char *cp; |
1138 | int fd; |
1139 | |
1140 | for (mpage = mpage_head; mpage != NULL((void *)0); mpage = mpage->next) { |
1141 | mlinks_undupe(mpage); |
1142 | if ((mlink = mpage->mlinks) == NULL((void *)0)) |
1143 | continue; |
1144 | |
1145 | name_mask = NAME_MASK0x000000000000001fULL; |
1146 | mandoc_ohash_init(&names, 4, offsetof(struct str, key)__builtin_offsetof(struct str, key)); |
1147 | mandoc_ohash_init(&strings, 6, offsetof(struct str, key)__builtin_offsetof(struct str, key)); |
1148 | mparse_reset(mp); |
1149 | meta = NULL((void *)0); |
1150 | |
1151 | if ((fd = mparse_open(mp, mlink->file)) == -1) { |
1152 | say(mlink->file, "&open"); |
1153 | goto nextpage; |
1154 | } |
1155 | |
1156 | /* |
1157 | * Interpret the file as mdoc(7) or man(7) source |
1158 | * code, unless it is known to be formatted. |
1159 | */ |
1160 | if (mlink->dform != FORM_CAT || mlink->fform != FORM_CAT) { |
1161 | mparse_readfd(mp, fd, mlink->file); |
1162 | close(fd); |
1163 | fd = -1; |
1164 | meta = mparse_result(mp); |
1165 | } |
1166 | |
1167 | if (meta != NULL((void *)0) && meta->sodest != NULL((void *)0)) { |
1168 | mlink_dest = ohash_find(&mlinks, |
1169 | ohash_qlookup(&mlinks, meta->sodest)); |
1170 | if (mlink_dest == NULL((void *)0)) { |
1171 | mandoc_asprintf(&cp, "%s.gz", meta->sodest); |
1172 | mlink_dest = ohash_find(&mlinks, |
1173 | ohash_qlookup(&mlinks, cp)); |
1174 | free(cp); |
1175 | } |
1176 | if (mlink_dest != NULL((void *)0)) { |
1177 | |
1178 | /* The .so target exists. */ |
1179 | |
1180 | mpage_dest = mlink_dest->mpage; |
1181 | while (1) { |
1182 | mlink->mpage = mpage_dest; |
1183 | |
1184 | /* |
1185 | * If the target was already |
1186 | * processed, add the links |
1187 | * to the database now. |
1188 | * Otherwise, this will |
1189 | * happen when we come |
1190 | * to the target. |
1191 | */ |
1192 | |
1193 | if (mpage_dest->dba != NULL((void *)0)) |
1194 | dbadd_mlink(mlink); |
1195 | |
1196 | if (mlink->next == NULL((void *)0)) |
1197 | break; |
1198 | mlink = mlink->next; |
1199 | } |
1200 | |
1201 | /* Move all links to the target. */ |
1202 | |
1203 | mlink->next = mlink_dest->next; |
1204 | mlink_dest->next = mpage->mlinks; |
1205 | mpage->mlinks = NULL((void *)0); |
1206 | goto nextpage; |
1207 | } |
1208 | meta->macroset = MACROSET_NONE; |
1209 | } |
1210 | if (meta != NULL((void *)0) && meta->macroset == MACROSET_MDOC) { |
1211 | mpage->form = FORM_SRC; |
1212 | mpage->sec = meta->msec; |
1213 | mpage->sec = mandoc_strdup( |
1214 | mpage->sec == NULL((void *)0) ? "" : mpage->sec); |
1215 | mpage->arch = meta->arch; |
1216 | mpage->arch = mandoc_strdup( |
1217 | mpage->arch == NULL((void *)0) ? "" : mpage->arch); |
1218 | mpage->title = mandoc_strdup(meta->title); |
1219 | } else if (meta != NULL((void *)0) && meta->macroset == MACROSET_MAN) { |
1220 | if (*meta->msec != '\0' || *meta->title != '\0') { |
1221 | mpage->form = FORM_SRC; |
1222 | mpage->sec = mandoc_strdup(meta->msec); |
1223 | mpage->arch = mandoc_strdup(mlink->arch); |
1224 | mpage->title = mandoc_strdup(meta->title); |
1225 | } else |
1226 | meta = NULL((void *)0); |
1227 | } |
1228 | |
1229 | assert(mpage->desc == NULL)((mpage->desc == ((void *)0)) ? (void)0 : __assert2("/usr/src/usr.bin/mandoc/mandocdb.c" , 1229, __func__, "mpage->desc == NULL")); |
1230 | if (meta == NULL((void *)0) || meta->sodest != NULL((void *)0)) { |
1231 | mpage->sec = mandoc_strdup(mlink->dsec); |
1232 | mpage->arch = mandoc_strdup(mlink->arch); |
1233 | mpage->title = mandoc_strdup(mlink->name); |
1234 | if (meta == NULL((void *)0)) { |
1235 | mpage->form = FORM_CAT; |
1236 | parse_cat(mpage, fd); |
1237 | } else |
1238 | mpage->form = FORM_SRC; |
1239 | } else if (meta->macroset == MACROSET_MDOC) |
1240 | parse_mdoc(mpage, meta, meta->first); |
1241 | else |
1242 | parse_man(mpage, meta, meta->first); |
1243 | if (mpage->desc == NULL((void *)0)) { |
1244 | mpage->desc = mandoc_strdup(mlink->name); |
1245 | if (warnings) |
1246 | say(mlink->file, "No one-line description, " |
1247 | "using filename \"%s\"", mlink->name); |
1248 | } |
1249 | |
1250 | for (mlink = mpage->mlinks; |
1251 | mlink != NULL((void *)0); |
1252 | mlink = mlink->next) { |
1253 | putkey(mpage, mlink->name, NAME_FILE0x0000004000000010ULL); |
1254 | if (warnings && !use_all) |
1255 | mlink_check(mpage, mlink); |
1256 | } |
1257 | |
1258 | dbadd(dba, mpage); |
1259 | |
1260 | nextpage: |
1261 | ohash_delete(&strings); |
1262 | ohash_delete(&names); |
1263 | } |
1264 | } |
1265 | |
1266 | static void |
1267 | parse_cat(struct mpage *mpage, int fd) |
1268 | { |
1269 | FILE *stream; |
1270 | struct mlink *mlink; |
1271 | char *line, *p, *title, *sec; |
1272 | size_t linesz, plen, titlesz; |
1273 | ssize_t len; |
1274 | int offs; |
1275 | |
1276 | mlink = mpage->mlinks; |
1277 | stream = fd == -1 ? fopen(mlink->file, "r") : fdopen(fd, "r"); |
1278 | if (stream == NULL((void *)0)) { |
1279 | if (fd != -1) |
1280 | close(fd); |
1281 | if (warnings) |
1282 | say(mlink->file, "&fopen"); |
1283 | return; |
1284 | } |
1285 | |
1286 | line = NULL((void *)0); |
1287 | linesz = 0; |
1288 | |
1289 | /* Parse the section number from the header line. */ |
1290 | |
1291 | while (getline(&line, &linesz, stream) != -1) { |
1292 | if (*line == '\n') |
1293 | continue; |
1294 | if ((sec = strchr(line, '(')) == NULL((void *)0)) |
1295 | break; |
1296 | if ((p = strchr(++sec, ')')) == NULL((void *)0)) |
1297 | break; |
1298 | free(mpage->sec); |
1299 | mpage->sec = mandoc_strndup(sec, p - sec); |
1300 | if (warnings && *mlink->dsec != '\0' && |
1301 | strcasecmp(mpage->sec, mlink->dsec)) |
1302 | say(mlink->file, |
1303 | "Section \"%s\" manual in %s directory", |
1304 | mpage->sec, mlink->dsec); |
1305 | break; |
1306 | } |
1307 | |
1308 | /* Skip to first blank line. */ |
1309 | |
1310 | while (line == NULL((void *)0) || *line != '\n') |
1311 | if (getline(&line, &linesz, stream) == -1) |
1312 | break; |
1313 | |
1314 | /* |
1315 | * Assume the first line that is not indented |
1316 | * is the first section header. Skip to it. |
1317 | */ |
1318 | |
1319 | while (getline(&line, &linesz, stream) != -1) |
1320 | if (*line != '\n' && *line != ' ') |
1321 | break; |
1322 | |
1323 | /* |
1324 | * Read up until the next section into a buffer. |
1325 | * Strip the leading and trailing newline from each read line, |
1326 | * appending a trailing space. |
1327 | * Ignore empty (whitespace-only) lines. |
1328 | */ |
1329 | |
1330 | titlesz = 0; |
1331 | title = NULL((void *)0); |
1332 | |
1333 | while ((len = getline(&line, &linesz, stream)) != -1) { |
1334 | if (*line != ' ') |
1335 | break; |
1336 | offs = 0; |
1337 | while (isspace((unsigned char)line[offs])) |
1338 | offs++; |
1339 | if (line[offs] == '\0') |
1340 | continue; |
1341 | title = mandoc_realloc(title, titlesz + len - offs); |
1342 | memcpy(title + titlesz, line + offs, len - offs); |
1343 | titlesz += len - offs; |
1344 | title[titlesz - 1] = ' '; |
1345 | } |
1346 | free(line); |
1347 | |
1348 | /* |
1349 | * If no page content can be found, or the input line |
1350 | * is already the next section header, or there is no |
1351 | * trailing newline, reuse the page title as the page |
1352 | * description. |
1353 | */ |
1354 | |
1355 | if (NULL((void *)0) == title || '\0' == *title) { |
1356 | if (warnings) |
1357 | say(mlink->file, "Cannot find NAME section"); |
1358 | fclose(stream); |
1359 | free(title); |
1360 | return; |
1361 | } |
1362 | |
1363 | title[titlesz - 1] = '\0'; |
1364 | |
1365 | /* |
1366 | * Skip to the first dash. |
1367 | * Use the remaining line as the description (no more than 70 |
1368 | * bytes). |
1369 | */ |
1370 | |
1371 | if (NULL((void *)0) != (p = strstr(title, "- "))) { |
1372 | for (p += 2; ' ' == *p || '\b' == *p; p++) |
1373 | /* Skip to next word. */ ; |
1374 | } else { |
1375 | if (warnings) |
1376 | say(mlink->file, "No dash in title line, " |
1377 | "reusing \"%s\" as one-line description", title); |
1378 | p = title; |
1379 | } |
1380 | |
1381 | plen = strlen(p); |
1382 | |
1383 | /* Strip backspace-encoding from line. */ |
1384 | |
1385 | while (NULL((void *)0) != (line = memchr(p, '\b', plen))) { |
1386 | len = line - p; |
1387 | if (0 == len) { |
1388 | memmove(line, line + 1, plen--); |
1389 | continue; |
1390 | } |
1391 | memmove(line - 1, line + 1, plen - len); |
1392 | plen -= 2; |
1393 | } |
1394 | |
1395 | /* |
1396 | * Cut off excessive one-line descriptions. |
1397 | * Bad pages are not worth better heuristics. |
1398 | */ |
1399 | |
1400 | mpage->desc = mandoc_strndup(p, 150); |
1401 | fclose(stream); |
1402 | free(title); |
1403 | } |
1404 | |
1405 | /* |
1406 | * Put a type/word pair into the word database for this particular file. |
1407 | */ |
1408 | static void |
1409 | putkey(const struct mpage *mpage, char *value, uint64_t type) |
1410 | { |
1411 | putkeys(mpage, value, strlen(value), type); |
1412 | } |
1413 | |
1414 | /* |
1415 | * Grok all nodes at or below a certain mdoc node into putkey(). |
1416 | */ |
1417 | static void |
1418 | putmdockey(const struct mpage *mpage, |
1419 | const struct roff_node *n, uint64_t m, int taboo) |
1420 | { |
1421 | |
1422 | for ( ; NULL((void *)0) != n; n = n->next) { |
1423 | if (n->flags & taboo) |
1424 | continue; |
1425 | if (NULL((void *)0) != n->child) |
1426 | putmdockey(mpage, n->child, m, taboo); |
1427 | if (n->type == ROFFT_TEXT) |
1428 | putkey(mpage, n->string, m); |
1429 | } |
1430 | } |
1431 | |
1432 | static void |
1433 | parse_man(struct mpage *mpage, const struct roff_meta *meta, |
1434 | const struct roff_node *n) |
1435 | { |
1436 | const struct roff_node *head, *body; |
1437 | char *start, *title; |
1438 | char byte; |
1439 | size_t sz; |
1440 | |
1441 | if (n == NULL((void *)0)) |
1442 | return; |
1443 | |
1444 | /* |
1445 | * We're only searching for one thing: the first text child in |
1446 | * the BODY of a NAME section. Since we don't keep track of |
1447 | * sections in -man, run some hoops to find out whether we're in |
1448 | * the correct section or not. |
1449 | */ |
1450 | |
1451 | if (n->type == ROFFT_BODY && n->tok == MAN_SH) { |
1452 | body = n; |
1453 | if ((head = body->parent->head) != NULL((void *)0) && |
1454 | (head = head->child) != NULL((void *)0) && |
1455 | head->next == NULL((void *)0) && |
1456 | head->type == ROFFT_TEXT && |
1457 | strcmp(head->string, "NAME") == 0 && |
1458 | body->child != NULL((void *)0)) { |
1459 | |
1460 | /* |
1461 | * Suck the entire NAME section into memory. |
1462 | * Yes, we might run away. |
1463 | * But too many manuals have big, spread-out |
1464 | * NAME sections over many lines. |
1465 | */ |
1466 | |
1467 | title = NULL((void *)0); |
1468 | deroff(&title, body); |
1469 | if (NULL((void *)0) == title) |
1470 | return; |
1471 | |
1472 | /* |
1473 | * Go through a special heuristic dance here. |
1474 | * Conventionally, one or more manual names are |
1475 | * comma-specified prior to a whitespace, then a |
1476 | * dash, then a description. Try to puzzle out |
1477 | * the name parts here. |
1478 | */ |
1479 | |
1480 | start = title; |
1481 | for ( ;; ) { |
1482 | sz = strcspn(start, " ,"); |
1483 | if ('\0' == start[sz]) |
1484 | break; |
1485 | |
1486 | byte = start[sz]; |
1487 | start[sz] = '\0'; |
1488 | |
1489 | /* |
1490 | * Assume a stray trailing comma in the |
1491 | * name list if a name begins with a dash. |
1492 | */ |
1493 | |
1494 | if ('-' == start[0] || |
1495 | ('\\' == start[0] && '-' == start[1])) |
1496 | break; |
1497 | |
1498 | putkey(mpage, start, NAME_TITLE0x0000004000000006ULL); |
1499 | if ( ! (mpage->name_head_done || |
1500 | strcasecmp(start, meta->title))) { |
1501 | putkey(mpage, start, NAME_HEAD0x0000004000000008ULL); |
1502 | mpage->name_head_done = 1; |
1503 | } |
1504 | |
1505 | if (' ' == byte) { |
1506 | start += sz + 1; |
1507 | break; |
1508 | } |
1509 | |
1510 | assert(',' == byte)((',' == byte) ? (void)0 : __assert2("/usr/src/usr.bin/mandoc/mandocdb.c" , 1510, __func__, "',' == byte")); |
1511 | start += sz + 1; |
1512 | while (' ' == *start) |
1513 | start++; |
1514 | } |
1515 | |
1516 | if (start == title) { |
1517 | putkey(mpage, start, NAME_TITLE0x0000004000000006ULL); |
1518 | if ( ! (mpage->name_head_done || |
1519 | strcasecmp(start, meta->title))) { |
1520 | putkey(mpage, start, NAME_HEAD0x0000004000000008ULL); |
1521 | mpage->name_head_done = 1; |
1522 | } |
1523 | free(title); |
1524 | return; |
1525 | } |
1526 | |
1527 | while (isspace((unsigned char)*start)) |
1528 | start++; |
1529 | |
1530 | if (0 == strncmp(start, "-", 1)) |
1531 | start += 1; |
1532 | else if (0 == strncmp(start, "\\-\\-", 4)) |
1533 | start += 4; |
1534 | else if (0 == strncmp(start, "\\-", 2)) |
1535 | start += 2; |
1536 | else if (0 == strncmp(start, "\\(en", 4)) |
1537 | start += 4; |
1538 | else if (0 == strncmp(start, "\\(em", 4)) |
1539 | start += 4; |
1540 | |
1541 | while (' ' == *start) |
1542 | start++; |
1543 | |
1544 | /* |
1545 | * Cut off excessive one-line descriptions. |
1546 | * Bad pages are not worth better heuristics. |
1547 | */ |
1548 | |
1549 | mpage->desc = mandoc_strndup(start, 150); |
1550 | free(title); |
1551 | return; |
1552 | } |
1553 | } |
1554 | |
1555 | for (n = n->child; n; n = n->next) { |
1556 | if (NULL((void *)0) != mpage->desc) |
1557 | break; |
1558 | parse_man(mpage, meta, n); |
1559 | } |
1560 | } |
1561 | |
1562 | static void |
1563 | parse_mdoc(struct mpage *mpage, const struct roff_meta *meta, |
1564 | const struct roff_node *n) |
1565 | { |
1566 | const struct mdoc_handler *handler; |
1567 | |
1568 | for (n = n->child; n != NULL((void *)0); n = n->next) { |
1569 | if (n->tok == TOKEN_NONE || n->tok < ROFF_MAX) |
1570 | continue; |
1571 | assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX)((n->tok >= MDOC_Dd && n->tok < MDOC_MAX) ? (void)0 : __assert2("/usr/src/usr.bin/mandoc/mandocdb.c", 1571 , __func__, "n->tok >= MDOC_Dd && n->tok < MDOC_MAX" )); |
1572 | handler = mdoc_handlers + (n->tok - MDOC_Dd); |
1573 | if (n->flags & handler->taboo) |
1574 | continue; |
1575 | |
1576 | switch (n->type) { |
1577 | case ROFFT_ELEM: |
1578 | case ROFFT_BLOCK: |
1579 | case ROFFT_HEAD: |
1580 | case ROFFT_BODY: |
1581 | case ROFFT_TAIL: |
1582 | if (handler->fp != NULL((void *)0) && |
1583 | (*handler->fp)(mpage, meta, n) == 0) |
1584 | break; |
1585 | if (handler->mask) |
1586 | putmdockey(mpage, n->child, |
1587 | handler->mask, handler->taboo); |
1588 | break; |
1589 | default: |
1590 | continue; |
1591 | } |
1592 | if (NULL((void *)0) != n->child) |
1593 | parse_mdoc(mpage, meta, n); |
1594 | } |
1595 | } |
1596 | |
1597 | static int |
1598 | parse_mdoc_Fa(struct mpage *mpage, const struct roff_meta *meta, |
1599 | const struct roff_node *n) |
1600 | { |
1601 | uint64_t mask; |
1602 | |
1603 | mask = TYPE_Fa0x0000000000000010ULL; |
1604 | if (n->sec == SEC_SYNOPSIS) |
1605 | mask |= TYPE_Vt0x0000001000000000ULL; |
1606 | |
1607 | putmdockey(mpage, n->child, mask, 0); |
1608 | return 0; |
1609 | } |
1610 | |
1611 | static int |
1612 | parse_mdoc_Fd(struct mpage *mpage, const struct roff_meta *meta, |
1613 | const struct roff_node *n) |
1614 | { |
1615 | char *start, *end; |
1616 | size_t sz; |
1617 | |
1618 | if (SEC_SYNOPSIS != n->sec || |
1619 | NULL((void *)0) == (n = n->child) || |
1620 | n->type != ROFFT_TEXT) |
1621 | return 0; |
1622 | |
1623 | /* |
1624 | * Only consider those `Fd' macro fields that begin with an |
1625 | * "inclusion" token (versus, e.g., #define). |
1626 | */ |
1627 | |
1628 | if (strcmp("#include", n->string)) |
1629 | return 0; |
1630 | |
1631 | if ((n = n->next) == NULL((void *)0) || n->type != ROFFT_TEXT) |
1632 | return 0; |
1633 | |
1634 | /* |
1635 | * Strip away the enclosing angle brackets and make sure we're |
1636 | * not zero-length. |
1637 | */ |
1638 | |
1639 | start = n->string; |
1640 | if ('<' == *start || '"' == *start) |
1641 | start++; |
1642 | |
1643 | if (0 == (sz = strlen(start))) |
1644 | return 0; |
1645 | |
1646 | end = &start[(int)sz - 1]; |
1647 | if ('>' == *end || '"' == *end) |
1648 | end--; |
1649 | |
1650 | if (end > start) |
1651 | putkeys(mpage, start, end - start + 1, TYPE_In0x0000000000200000ULL); |
1652 | return 0; |
1653 | } |
1654 | |
1655 | static void |
1656 | parse_mdoc_fname(struct mpage *mpage, const struct roff_node *n) |
1657 | { |
1658 | char *cp; |
1659 | size_t sz; |
1660 | |
1661 | if (n->type != ROFFT_TEXT) |
1662 | return; |
1663 | |
1664 | /* Skip function pointer punctuation. */ |
1665 | |
1666 | cp = n->string; |
1667 | while (*cp == '(' || *cp == '*') |
1668 | cp++; |
1669 | sz = strcspn(cp, "()"); |
1670 | |
1671 | putkeys(mpage, cp, sz, TYPE_Fn0x0000000000000080ULL); |
1672 | if (n->sec == SEC_SYNOPSIS) |
1673 | putkeys(mpage, cp, sz, NAME_SYN0x0000004000000001ULL); |
1674 | } |
1675 | |
1676 | static int |
1677 | parse_mdoc_Fn(struct mpage *mpage, const struct roff_meta *meta, |
1678 | const struct roff_node *n) |
1679 | { |
1680 | uint64_t mask; |
1681 | |
1682 | if (n->child == NULL((void *)0)) |
1683 | return 0; |
1684 | |
1685 | parse_mdoc_fname(mpage, n->child); |
1686 | |
1687 | n = n->child->next; |
1688 | if (n != NULL((void *)0) && n->type == ROFFT_TEXT) { |
1689 | mask = TYPE_Fa0x0000000000000010ULL; |
1690 | if (n->sec == SEC_SYNOPSIS) |
1691 | mask |= TYPE_Vt0x0000001000000000ULL; |
1692 | putmdockey(mpage, n, mask, 0); |
1693 | } |
1694 | |
1695 | return 0; |
1696 | } |
1697 | |
1698 | static int |
1699 | parse_mdoc_Fo(struct mpage *mpage, const struct roff_meta *meta, |
1700 | const struct roff_node *n) |
1701 | { |
1702 | |
1703 | if (n->type != ROFFT_HEAD) |
1704 | return 1; |
1705 | |
1706 | if (n->child != NULL((void *)0)) |
1707 | parse_mdoc_fname(mpage, n->child); |
1708 | |
1709 | return 0; |
1710 | } |
1711 | |
1712 | static int |
1713 | parse_mdoc_Va(struct mpage *mpage, const struct roff_meta *meta, |
1714 | const struct roff_node *n) |
1715 | { |
1716 | char *cp; |
1717 | |
1718 | if (n->type != ROFFT_ELEM && n->type != ROFFT_BODY) |
1719 | return 0; |
1720 | |
1721 | if (n->child != NULL((void *)0) && |
1722 | n->child->next == NULL((void *)0) && |
1723 | n->child->type == ROFFT_TEXT) |
1724 | return 1; |
1725 | |
1726 | cp = NULL((void *)0); |
1727 | deroff(&cp, n); |
1728 | if (cp != NULL((void *)0)) { |
1729 | putkey(mpage, cp, TYPE_Vt0x0000001000000000ULL | (n->tok == MDOC_Va || |
1730 | n->type == ROFFT_BODY ? TYPE_Va0x0000000000004000ULL : 0)); |
1731 | free(cp); |
1732 | } |
1733 | |
1734 | return 0; |
1735 | } |
1736 | |
1737 | static int |
1738 | parse_mdoc_Xr(struct mpage *mpage, const struct roff_meta *meta, |
1739 | const struct roff_node *n) |
1740 | { |
1741 | char *cp; |
1742 | |
1743 | if (NULL((void *)0) == (n = n->child)) |
1744 | return 0; |
1745 | |
1746 | if (NULL((void *)0) == n->next) { |
1747 | putkey(mpage, n->string, TYPE_Xr0x0000000000000004ULL); |
1748 | return 0; |
1749 | } |
1750 | |
1751 | mandoc_asprintf(&cp, "%s(%s)", n->string, n->next->string); |
1752 | putkey(mpage, cp, TYPE_Xr0x0000000000000004ULL); |
1753 | free(cp); |
1754 | return 0; |
1755 | } |
1756 | |
1757 | static int |
1758 | parse_mdoc_Nd(struct mpage *mpage, const struct roff_meta *meta, |
1759 | const struct roff_node *n) |
1760 | { |
1761 | |
1762 | if (n->type == ROFFT_BODY) |
1763 | deroff(&mpage->desc, n); |
1764 | return 0; |
1765 | } |
1766 | |
1767 | static int |
1768 | parse_mdoc_Nm(struct mpage *mpage, const struct roff_meta *meta, |
1769 | const struct roff_node *n) |
1770 | { |
1771 | |
1772 | if (SEC_NAME == n->sec) |
1773 | putmdockey(mpage, n->child, NAME_TITLE0x0000004000000006ULL, 0); |
1774 | else if (n->sec == SEC_SYNOPSIS && n->type == ROFFT_HEAD) { |
1775 | if (n->child == NULL((void *)0)) |
1776 | putkey(mpage, meta->name, NAME_SYN0x0000004000000001ULL); |
1777 | else |
1778 | putmdockey(mpage, n->child, NAME_SYN0x0000004000000001ULL, 0); |
1779 | } |
1780 | if ( ! (mpage->name_head_done || |
1781 | n->child == NULL((void *)0) || n->child->string == NULL((void *)0) || |
1782 | strcasecmp(n->child->string, meta->title))) { |
1783 | putkey(mpage, n->child->string, NAME_HEAD0x0000004000000008ULL); |
1784 | mpage->name_head_done = 1; |
1785 | } |
1786 | return 0; |
1787 | } |
1788 | |
1789 | static int |
1790 | parse_mdoc_Sh(struct mpage *mpage, const struct roff_meta *meta, |
1791 | const struct roff_node *n) |
1792 | { |
1793 | |
1794 | return n->sec == SEC_CUSTOM && n->type == ROFFT_HEAD; |
1795 | } |
1796 | |
1797 | static int |
1798 | parse_mdoc_head(struct mpage *mpage, const struct roff_meta *meta, |
1799 | const struct roff_node *n) |
1800 | { |
1801 | |
1802 | return n->type == ROFFT_HEAD; |
1803 | } |
1804 | |
1805 | /* |
1806 | * Add a string to the hash table for the current manual. |
1807 | * Each string has a bitmask telling which macros it belongs to. |
1808 | * When we finish the manual, we'll dump the table. |
1809 | */ |
1810 | static void |
1811 | putkeys(const struct mpage *mpage, char *cp, size_t sz, uint64_t v) |
1812 | { |
1813 | struct ohash *htab; |
1814 | struct str *s; |
1815 | const char *end; |
1816 | unsigned int slot; |
1817 | int i, mustfree; |
1818 | |
1819 | if (0 == sz) |
1820 | return; |
1821 | |
1822 | mustfree = render_string(&cp, &sz); |
1823 | |
1824 | if (TYPE_Nm0x0000004000000000ULL & v) { |
1825 | htab = &names; |
1826 | v &= name_mask; |
1827 | if (v & NAME_FIRST0x0000004000000004ULL) |
1828 | name_mask &= ~NAME_FIRST0x0000004000000004ULL; |
1829 | if (debug > 1) |
1830 | say(mpage->mlinks->file, |
1831 | "Adding name %*s, bits=0x%llx", (int)sz, cp, |
1832 | (unsigned long long)v); |
1833 | } else { |
1834 | htab = &strings; |
1835 | if (debug > 1) |
1836 | for (i = 0; i < KEY_MAX40; i++) |
1837 | if ((uint64_t)1 << i & v) |
1838 | say(mpage->mlinks->file, |
1839 | "Adding key %s=%*s", |
1840 | mansearch_keynames[i], (int)sz, cp); |
1841 | } |
1842 | |
1843 | end = cp + sz; |
1844 | slot = ohash_qlookupi(htab, cp, &end); |
1845 | s = ohash_find(htab, slot); |
1846 | |
1847 | if (NULL((void *)0) != s && mpage == s->mpage) { |
1848 | s->mask |= v; |
1849 | return; |
1850 | } else if (NULL((void *)0) == s) { |
1851 | s = mandoc_calloc(1, sizeof(struct str) + sz + 1); |
1852 | memcpy(s->key, cp, sz); |
1853 | ohash_insert(htab, slot, s); |
1854 | } |
1855 | s->mpage = mpage; |
1856 | s->mask = v; |
1857 | |
1858 | if (mustfree) |
1859 | free(cp); |
1860 | } |
1861 | |
1862 | /* |
1863 | * Take a Unicode codepoint and produce its UTF-8 encoding. |
1864 | * This isn't the best way to do this, but it works. |
1865 | * The magic numbers are from the UTF-8 packaging. |
1866 | * They're not as scary as they seem: read the UTF-8 spec for details. |
1867 | */ |
1868 | static size_t |
1869 | utf8(unsigned int cp, char out[7]) |
1870 | { |
1871 | size_t rc; |
1872 | |
1873 | rc = 0; |
Value stored to 'rc' is never read | |
1874 | if (cp <= 0x0000007F) { |
1875 | rc = 1; |
1876 | out[0] = (char)cp; |
1877 | } else if (cp <= 0x000007FF) { |
1878 | rc = 2; |
1879 | out[0] = (cp >> 6 & 31) | 192; |
1880 | out[1] = (cp & 63) | 128; |
1881 | } else if (cp <= 0x0000FFFF) { |
1882 | rc = 3; |
1883 | out[0] = (cp >> 12 & 15) | 224; |
1884 | out[1] = (cp >> 6 & 63) | 128; |
1885 | out[2] = (cp & 63) | 128; |
1886 | } else if (cp <= 0x001FFFFF) { |
1887 | rc = 4; |
1888 | out[0] = (cp >> 18 & 7) | 240; |
1889 | out[1] = (cp >> 12 & 63) | 128; |
1890 | out[2] = (cp >> 6 & 63) | 128; |
1891 | out[3] = (cp & 63) | 128; |
1892 | } else if (cp <= 0x03FFFFFF) { |
1893 | rc = 5; |
1894 | out[0] = (cp >> 24 & 3) | 248; |
1895 | out[1] = (cp >> 18 & 63) | 128; |
1896 | out[2] = (cp >> 12 & 63) | 128; |
1897 | out[3] = (cp >> 6 & 63) | 128; |
1898 | out[4] = (cp & 63) | 128; |
1899 | } else if (cp <= 0x7FFFFFFF) { |
1900 | rc = 6; |
1901 | out[0] = (cp >> 30 & 1) | 252; |
1902 | out[1] = (cp >> 24 & 63) | 128; |
1903 | out[2] = (cp >> 18 & 63) | 128; |
1904 | out[3] = (cp >> 12 & 63) | 128; |
1905 | out[4] = (cp >> 6 & 63) | 128; |
1906 | out[5] = (cp & 63) | 128; |
1907 | } else |
1908 | return 0; |
1909 | |
1910 | out[rc] = '\0'; |
1911 | return rc; |
1912 | } |
1913 | |
1914 | /* |
1915 | * If the string contains escape sequences, |
1916 | * replace it with an allocated rendering and return 1, |
1917 | * such that the caller can free it after use. |
1918 | * Otherwise, do nothing and return 0. |
1919 | */ |
1920 | static int |
1921 | render_string(char **public, size_t *psz) |
1922 | { |
1923 | const char *src, *scp, *addcp, *seq; |
1924 | char *dst; |
1925 | size_t ssz, dsz, addsz; |
1926 | char utfbuf[7], res[6]; |
1927 | int seqlen, unicode; |
1928 | |
1929 | res[0] = '\\'; |
1930 | res[1] = '\t'; |
1931 | res[2] = ASCII_NBRSP31; |
1932 | res[3] = ASCII_HYPH28; |
1933 | res[4] = ASCII_BREAK29; |
1934 | res[5] = '\0'; |
1935 | |
1936 | src = scp = *public; |
1937 | ssz = *psz; |
1938 | dst = NULL((void *)0); |
1939 | dsz = 0; |
1940 | |
1941 | while (scp < src + *psz) { |
1942 | |
1943 | /* Leave normal characters unchanged. */ |
1944 | |
1945 | if (strchr(res, *scp) == NULL((void *)0)) { |
1946 | if (dst != NULL((void *)0)) |
1947 | dst[dsz++] = *scp; |
1948 | scp++; |
1949 | continue; |
1950 | } |
1951 | |
1952 | /* |
1953 | * Found something that requires replacing, |
1954 | * make sure we have a destination buffer. |
1955 | */ |
1956 | |
1957 | if (dst == NULL((void *)0)) { |
1958 | dst = mandoc_malloc(ssz + 1); |
1959 | dsz = scp - src; |
1960 | memcpy(dst, src, dsz); |
1961 | } |
1962 | |
1963 | /* Handle single-char special characters. */ |
1964 | |
1965 | switch (*scp) { |
1966 | case '\\': |
1967 | break; |
1968 | case '\t': |
1969 | case ASCII_NBRSP31: |
1970 | dst[dsz++] = ' '; |
1971 | scp++; |
1972 | continue; |
1973 | case ASCII_HYPH28: |
1974 | dst[dsz++] = '-'; |
1975 | /* FALLTHROUGH */ |
1976 | case ASCII_BREAK29: |
1977 | scp++; |
1978 | continue; |
1979 | default: |
1980 | abort(); |
1981 | } |
1982 | |
1983 | /* |
1984 | * Found an escape sequence. |
1985 | * Read past the slash, then parse it. |
1986 | * Ignore everything except characters. |
1987 | */ |
1988 | |
1989 | scp++; |
1990 | if (mandoc_escape(&scp, &seq, &seqlen) != ESCAPE_SPECIAL) |
1991 | continue; |
1992 | |
1993 | /* |
1994 | * Render the special character |
1995 | * as either UTF-8 or ASCII. |
1996 | */ |
1997 | |
1998 | if (write_utf8) { |
1999 | unicode = mchars_spec2cp(seq, seqlen); |
2000 | if (unicode <= 0) |
2001 | continue; |
2002 | addsz = utf8(unicode, utfbuf); |
2003 | if (addsz == 0) |
2004 | continue; |
2005 | addcp = utfbuf; |
2006 | } else { |
2007 | addcp = mchars_spec2str(seq, seqlen, &addsz); |
2008 | if (addcp == NULL((void *)0)) |
2009 | continue; |
2010 | if (*addcp == ASCII_NBRSP31) { |
2011 | addcp = " "; |
2012 | addsz = 1; |
2013 | } |
2014 | } |
2015 | |
2016 | /* Copy the rendered glyph into the stream. */ |
2017 | |
2018 | ssz += addsz; |
2019 | dst = mandoc_realloc(dst, ssz + 1); |
2020 | memcpy(dst + dsz, addcp, addsz); |
2021 | dsz += addsz; |
2022 | } |
2023 | if (dst != NULL((void *)0)) { |
2024 | *public = dst; |
2025 | *psz = dsz; |
2026 | } |
2027 | |
2028 | /* Trim trailing whitespace and NUL-terminate. */ |
2029 | |
2030 | while (*psz > 0 && (*public)[*psz - 1] == ' ') |
2031 | --*psz; |
2032 | if (dst != NULL((void *)0)) { |
2033 | (*public)[*psz] = '\0'; |
2034 | return 1; |
2035 | } else |
2036 | return 0; |
2037 | } |
2038 | |
2039 | static void |
2040 | dbadd_mlink(const struct mlink *mlink) |
2041 | { |
2042 | dba_page_alias(mlink->mpage->dba, mlink->name, NAME_FILE0x0000004000000010ULL); |
2043 | dba_page_add(mlink->mpage->dba, DBP_SECT1, mlink->dsec); |
2044 | dba_page_add(mlink->mpage->dba, DBP_SECT1, mlink->fsec); |
2045 | dba_page_add(mlink->mpage->dba, DBP_ARCH2, mlink->arch); |
2046 | dba_page_add(mlink->mpage->dba, DBP_FILE4, mlink->file); |
2047 | } |
2048 | |
2049 | /* |
2050 | * Flush the current page's terms (and their bits) into the database. |
2051 | * Also, handle escape sequences at the last possible moment. |
2052 | */ |
2053 | static void |
2054 | dbadd(struct dba *dba, struct mpage *mpage) |
2055 | { |
2056 | struct mlink *mlink; |
2057 | struct str *key; |
2058 | char *cp; |
2059 | uint64_t mask; |
2060 | size_t i; |
2061 | unsigned int slot; |
2062 | int mustfree; |
2063 | |
2064 | mlink = mpage->mlinks; |
2065 | |
2066 | if (nodb) { |
2067 | for (key = ohash_first(&names, &slot); NULL((void *)0) != key; |
2068 | key = ohash_next(&names, &slot)) |
2069 | free(key); |
2070 | for (key = ohash_first(&strings, &slot); NULL((void *)0) != key; |
2071 | key = ohash_next(&strings, &slot)) |
2072 | free(key); |
2073 | if (0 == debug) |
2074 | return; |
2075 | while (NULL((void *)0) != mlink) { |
2076 | fputs(mlink->name, stdout(&__sF[1])); |
2077 | if (NULL((void *)0) == mlink->next || |
2078 | strcmp(mlink->dsec, mlink->next->dsec) || |
2079 | strcmp(mlink->fsec, mlink->next->fsec) || |
2080 | strcmp(mlink->arch, mlink->next->arch)) { |
2081 | putchar('(')(!__isthreaded ? __sputc('(', (&__sF[1])) : (putc)('(', ( &__sF[1]))); |
2082 | if ('\0' == *mlink->dsec) |
2083 | fputs(mlink->fsec, stdout(&__sF[1])); |
2084 | else |
2085 | fputs(mlink->dsec, stdout(&__sF[1])); |
2086 | if ('\0' != *mlink->arch) |
2087 | printf("/%s", mlink->arch); |
2088 | putchar(')')(!__isthreaded ? __sputc(')', (&__sF[1])) : (putc)(')', ( &__sF[1]))); |
2089 | } |
2090 | mlink = mlink->next; |
2091 | if (NULL((void *)0) != mlink) |
2092 | fputs(", ", stdout(&__sF[1])); |
2093 | } |
2094 | printf(" - %s\n", mpage->desc); |
2095 | return; |
2096 | } |
2097 | |
2098 | if (debug) |
2099 | say(mlink->file, "Adding to database"); |
2100 | |
2101 | cp = mpage->desc; |
2102 | i = strlen(cp); |
2103 | mustfree = render_string(&cp, &i); |
2104 | mpage->dba = dba_page_new(dba->pages, |
2105 | *mpage->arch == '\0' ? mlink->arch : mpage->arch, |
2106 | cp, mlink->file, mpage->form); |
2107 | if (mustfree) |
2108 | free(cp); |
2109 | dba_page_add(mpage->dba, DBP_SECT1, mpage->sec); |
2110 | |
2111 | while (mlink != NULL((void *)0)) { |
2112 | dbadd_mlink(mlink); |
2113 | mlink = mlink->next; |
2114 | } |
2115 | |
2116 | for (key = ohash_first(&names, &slot); NULL((void *)0) != key; |
2117 | key = ohash_next(&names, &slot)) { |
2118 | assert(key->mpage == mpage)((key->mpage == mpage) ? (void)0 : __assert2("/usr/src/usr.bin/mandoc/mandocdb.c" , 2118, __func__, "key->mpage == mpage")); |
2119 | dba_page_alias(mpage->dba, key->key, key->mask); |
2120 | free(key); |
2121 | } |
2122 | for (key = ohash_first(&strings, &slot); NULL((void *)0) != key; |
2123 | key = ohash_next(&strings, &slot)) { |
2124 | assert(key->mpage == mpage)((key->mpage == mpage) ? (void)0 : __assert2("/usr/src/usr.bin/mandoc/mandocdb.c" , 2124, __func__, "key->mpage == mpage")); |
2125 | i = 0; |
2126 | for (mask = TYPE_Xr0x0000000000000004ULL; mask <= TYPE_Lb0x0000002000000000ULL; mask *= 2) { |
2127 | if (key->mask & mask) |
2128 | dba_macro_add(dba->macros, i, |
2129 | key->key, mpage->dba); |
2130 | i++; |
2131 | } |
2132 | free(key); |
2133 | } |
2134 | } |
2135 | |
2136 | static void |
2137 | dbprune(struct dba *dba) |
2138 | { |
2139 | struct dba_array *page, *files; |
2140 | char *file; |
2141 | |
2142 | dba_array_FOREACH(dba->pages, page)dba_array_start(dba->pages); while (((page) = dba_array_next (dba->pages)) != ((void *)0)) { |
2143 | files = dba_array_get(page, DBP_FILE4); |
2144 | dba_array_FOREACH(files, file)dba_array_start(files); while (((file) = dba_array_next(files )) != ((void *)0)) { |
2145 | if (*file < ' ') |
2146 | file++; |
2147 | if (ohash_find(&mlinks, ohash_qlookup(&mlinks, |
2148 | file)) != NULL((void *)0)) { |
2149 | if (debug) |
2150 | say(file, "Deleting from database"); |
2151 | dba_array_del(dba->pages); |
2152 | break; |
2153 | } |
2154 | } |
2155 | } |
2156 | } |
2157 | |
2158 | /* |
2159 | * Write the database from memory to disk. |
2160 | */ |
2161 | static void |
2162 | dbwrite(struct dba *dba) |
2163 | { |
2164 | struct stat sb1, sb2; |
2165 | char tfn[33], *cp1, *cp2; |
2166 | off_t i; |
2167 | int fd1, fd2; |
2168 | |
2169 | /* |
2170 | * Do not write empty databases, and delete existing ones |
2171 | * when makewhatis -u causes them to become empty. |
2172 | */ |
2173 | |
2174 | dba_array_start(dba->pages); |
2175 | if (dba_array_next(dba->pages) == NULL((void *)0)) { |
2176 | if (unlink(MANDOC_DB"mandoc.db") == -1 && errno(*__errno()) != ENOENT2) |
2177 | say(MANDOC_DB"mandoc.db", "&unlink"); |
2178 | return; |
2179 | } |
2180 | |
2181 | /* |
2182 | * Build the database in a temporary file, |
2183 | * then atomically move it into place. |
2184 | */ |
2185 | |
2186 | if (dba_write(MANDOC_DB"mandoc.db" "~", dba) != -1) { |
2187 | if (rename(MANDOC_DB"mandoc.db" "~", MANDOC_DB"mandoc.db") == -1) { |
2188 | exitcode = (int)MANDOCLEVEL_SYSERR; |
2189 | say(MANDOC_DB"mandoc.db", "&rename"); |
2190 | unlink(MANDOC_DB"mandoc.db" "~"); |
2191 | } |
2192 | return; |
2193 | } |
2194 | |
2195 | /* |
2196 | * We lack write permission and cannot replace the database |
2197 | * file, but let's at least check whether the data changed. |
2198 | */ |
2199 | |
2200 | (void)strlcpy(tfn, "/tmp/mandocdb.XXXXXXXX", sizeof(tfn)); |
2201 | if (mkdtemp(tfn) == NULL((void *)0)) { |
2202 | exitcode = (int)MANDOCLEVEL_SYSERR; |
2203 | say("", "&%s", tfn); |
2204 | return; |
2205 | } |
2206 | cp1 = cp2 = MAP_FAILED((void *)-1); |
2207 | fd1 = fd2 = -1; |
2208 | (void)strlcat(tfn, "/" MANDOC_DB"mandoc.db", sizeof(tfn)); |
2209 | if (dba_write(tfn, dba) == -1) { |
2210 | say(tfn, "&dba_write"); |
2211 | goto err; |
2212 | } |
2213 | if ((fd1 = open(MANDOC_DB"mandoc.db", O_RDONLY0x0000)) == -1) { |
2214 | say(MANDOC_DB"mandoc.db", "&open"); |
2215 | goto err; |
2216 | } |
2217 | if ((fd2 = open(tfn, O_RDONLY0x0000)) == -1) { |
2218 | say(tfn, "&open"); |
2219 | goto err; |
2220 | } |
2221 | if (fstat(fd1, &sb1) == -1) { |
2222 | say(MANDOC_DB"mandoc.db", "&fstat"); |
2223 | goto err; |
2224 | } |
2225 | if (fstat(fd2, &sb2) == -1) { |
2226 | say(tfn, "&fstat"); |
2227 | goto err; |
2228 | } |
2229 | if (sb1.st_size != sb2.st_size) |
2230 | goto err; |
2231 | if ((cp1 = mmap(NULL((void *)0), sb1.st_size, PROT_READ0x01, MAP_PRIVATE0x0002, |
2232 | fd1, 0)) == MAP_FAILED((void *)-1)) { |
2233 | say(MANDOC_DB"mandoc.db", "&mmap"); |
2234 | goto err; |
2235 | } |
2236 | if ((cp2 = mmap(NULL((void *)0), sb2.st_size, PROT_READ0x01, MAP_PRIVATE0x0002, |
2237 | fd2, 0)) == MAP_FAILED((void *)-1)) { |
2238 | say(tfn, "&mmap"); |
2239 | goto err; |
2240 | } |
2241 | for (i = 0; i < sb1.st_size; i++) |
2242 | if (cp1[i] != cp2[i]) |
2243 | goto err; |
2244 | goto out; |
2245 | |
2246 | err: |
2247 | exitcode = (int)MANDOCLEVEL_SYSERR; |
2248 | say(MANDOC_DB"mandoc.db", "Data changed, but cannot replace database"); |
2249 | |
2250 | out: |
2251 | if (cp1 != MAP_FAILED((void *)-1)) |
2252 | munmap(cp1, sb1.st_size); |
2253 | if (cp2 != MAP_FAILED((void *)-1)) |
2254 | munmap(cp2, sb2.st_size); |
2255 | if (fd1 != -1) |
2256 | close(fd1); |
2257 | if (fd2 != -1) |
2258 | close(fd2); |
2259 | unlink(tfn); |
2260 | *strrchr(tfn, '/') = '\0'; |
2261 | rmdir(tfn); |
2262 | } |
2263 | |
2264 | static int |
2265 | set_basedir(const char *targetdir, int report_baddir) |
2266 | { |
2267 | static char startdir[PATH_MAX1024]; |
2268 | static int getcwd_status; /* 1 = ok, 2 = failure */ |
2269 | static int chdir_status; /* 1 = changed directory */ |
2270 | |
2271 | /* |
2272 | * Remember the original working directory, if possible. |
2273 | * This will be needed if the second or a later directory |
2274 | * on the command line is given as a relative path. |
2275 | * Do not error out if the current directory is not |
2276 | * searchable: Maybe it won't be needed after all. |
2277 | */ |
2278 | if (getcwd_status == 0) { |
2279 | if (getcwd(startdir, sizeof(startdir)) == NULL((void *)0)) { |
2280 | getcwd_status = 2; |
2281 | (void)strlcpy(startdir, strerror(errno(*__errno())), |
2282 | sizeof(startdir)); |
2283 | } else |
2284 | getcwd_status = 1; |
2285 | } |
2286 | |
2287 | /* |
2288 | * We are leaving the old base directory. |
2289 | * Do not use it any longer, not even for messages. |
2290 | */ |
2291 | *basedir = '\0'; |
2292 | basedir_len = 0; |
2293 | |
2294 | /* |
2295 | * If and only if the directory was changed earlier and |
2296 | * the next directory to process is given as a relative path, |
2297 | * first go back, or bail out if that is impossible. |
2298 | */ |
2299 | if (chdir_status && *targetdir != '/') { |
2300 | if (getcwd_status == 2) { |
2301 | exitcode = (int)MANDOCLEVEL_SYSERR; |
2302 | say("", "getcwd: %s", startdir); |
2303 | return 0; |
2304 | } |
2305 | if (chdir(startdir) == -1) { |
2306 | exitcode = (int)MANDOCLEVEL_SYSERR; |
2307 | say("", "&chdir %s", startdir); |
2308 | return 0; |
2309 | } |
2310 | } |
2311 | |
2312 | /* |
2313 | * Always resolve basedir to the canonicalized absolute |
2314 | * pathname and append a trailing slash, such that |
2315 | * we can reliably check whether files are inside. |
2316 | */ |
2317 | if (realpath(targetdir, basedir) == NULL((void *)0)) { |
2318 | if (report_baddir || errno(*__errno()) != ENOENT2) { |
2319 | exitcode = (int)MANDOCLEVEL_BADARG; |
2320 | say("", "&%s: realpath", targetdir); |
2321 | } |
2322 | *basedir = '\0'; |
2323 | return 0; |
2324 | } else if (chdir(basedir) == -1) { |
2325 | if (report_baddir || errno(*__errno()) != ENOENT2) { |
2326 | exitcode = (int)MANDOCLEVEL_BADARG; |
2327 | say("", "&chdir"); |
2328 | } |
2329 | *basedir = '\0'; |
2330 | return 0; |
2331 | } |
2332 | chdir_status = 1; |
2333 | basedir_len = strlen(basedir); |
2334 | if (basedir[basedir_len - 1] != '/') { |
2335 | if (basedir_len >= PATH_MAX1024 - 1) { |
2336 | exitcode = (int)MANDOCLEVEL_SYSERR; |
2337 | say("", "Filename too long"); |
2338 | *basedir = '\0'; |
2339 | basedir_len = 0; |
2340 | return 0; |
2341 | } |
2342 | basedir[basedir_len++] = '/'; |
2343 | basedir[basedir_len] = '\0'; |
2344 | } |
2345 | return 1; |
2346 | } |
2347 | |
2348 | static void |
2349 | say(const char *file, const char *format, ...) |
2350 | { |
2351 | va_list ap; |
2352 | int use_errno; |
2353 | |
2354 | if (*basedir != '\0') |
2355 | fprintf(stderr(&__sF[2]), "%s", basedir); |
2356 | if (*basedir != '\0' && *file != '\0') |
2357 | fputc('/', stderr(&__sF[2])); |
2358 | if (*file != '\0') |
2359 | fprintf(stderr(&__sF[2]), "%s", file); |
2360 | |
2361 | use_errno = 1; |
2362 | if (format != NULL((void *)0)) { |
2363 | switch (*format) { |
2364 | case '&': |
2365 | format++; |
2366 | break; |
2367 | case '\0': |
2368 | format = NULL((void *)0); |
2369 | break; |
2370 | default: |
2371 | use_errno = 0; |
2372 | break; |
2373 | } |
2374 | } |
2375 | if (format != NULL((void *)0)) { |
2376 | if (*basedir != '\0' || *file != '\0') |
2377 | fputs(": ", stderr(&__sF[2])); |
2378 | va_start(ap, format)__builtin_va_start((ap), format); |
2379 | vfprintf(stderr(&__sF[2]), format, ap); |
2380 | va_end(ap)__builtin_va_end((ap)); |
2381 | } |
2382 | if (use_errno) { |
2383 | if (*basedir != '\0' || *file != '\0' || format != NULL((void *)0)) |
2384 | fputs(": ", stderr(&__sF[2])); |
2385 | perror(NULL((void *)0)); |
2386 | } else |
2387 | fputc('\n', stderr(&__sF[2])); |
2388 | } |