1 /* $Id: mandocdb.c,v 1.253 2017/07/28 14:48:25 schwarze Exp $ */
2 /*
3 * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2011-2017 Ingo Schwarze <schwarze@openbsd.org>
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 #include "config.h"
20
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <sys/wait.h>
24
25 #include <assert.h>
26 #include <ctype.h>
27 #if HAVE_ERR
28 #include <err.h>
29 #endif
30 #include <errno.h>
31 #include <fcntl.h>
32 #if HAVE_FTS
33 #include <fts.h>
34 #else
35 #include "compat_fts.h"
36 #endif
37 #include <limits.h>
38 #if HAVE_SANDBOX_INIT
39 #include <sandbox.h>
40 #endif
41 #include <stdarg.h>
42 #include <stddef.h>
43 #include <stdio.h>
122 static void dbwrite(struct dba *);
123 static void filescan(const char *);
124 #if HAVE_FTS_COMPARE_CONST
125 static int fts_compare(const FTSENT *const *, const FTSENT *const *);
126 #else
127 static int fts_compare(const FTSENT **, const FTSENT **);
128 #endif
129 static void mlink_add(struct mlink *, const struct stat *);
130 static void mlink_check(struct mpage *, struct mlink *);
131 static void mlink_free(struct mlink *);
132 static void mlinks_undupe(struct mpage *);
133 static void mpages_free(void);
134 static void mpages_merge(struct dba *, struct mparse *);
135 static void parse_cat(struct mpage *, int);
136 static void parse_man(struct mpage *, const struct roff_meta *,
137 const struct roff_node *);
138 static void parse_mdoc(struct mpage *, const struct roff_meta *,
139 const struct roff_node *);
140 static int parse_mdoc_head(struct mpage *, const struct roff_meta *,
141 const struct roff_node *);
142 static int parse_mdoc_Fd(struct mpage *, const struct roff_meta *,
143 const struct roff_node *);
144 static void parse_mdoc_fname(struct mpage *, const struct roff_node *);
145 static int parse_mdoc_Fn(struct mpage *, const struct roff_meta *,
146 const struct roff_node *);
147 static int parse_mdoc_Fo(struct mpage *, const struct roff_meta *,
148 const struct roff_node *);
149 static int parse_mdoc_Nd(struct mpage *, const struct roff_meta *,
150 const struct roff_node *);
151 static int parse_mdoc_Nm(struct mpage *, const struct roff_meta *,
152 const struct roff_node *);
153 static int parse_mdoc_Sh(struct mpage *, const struct roff_meta *,
154 const struct roff_node *);
155 static int parse_mdoc_Va(struct mpage *, const struct roff_meta *,
156 const struct roff_node *);
157 static int parse_mdoc_Xr(struct mpage *, const struct roff_meta *,
158 const struct roff_node *);
159 static void putkey(const struct mpage *, char *, uint64_t);
160 static void putkeys(const struct mpage *, char *, size_t, uint64_t);
161 static void putmdockey(const struct mpage *,
190 { parse_mdoc_Sh, TYPE_Sh, 0 }, /* Sh */
191 { parse_mdoc_head, TYPE_Ss, 0 }, /* Ss */
192 { NULL, 0, 0 }, /* Pp */
193 { NULL, 0, 0 }, /* D1 */
194 { NULL, 0, 0 }, /* Dl */
195 { NULL, 0, 0 }, /* Bd */
196 { NULL, 0, 0 }, /* Ed */
197 { NULL, 0, 0 }, /* Bl */
198 { NULL, 0, 0 }, /* El */
199 { NULL, 0, 0 }, /* It */
200 { NULL, 0, 0 }, /* Ad */
201 { NULL, TYPE_An, 0 }, /* An */
202 { NULL, 0, 0 }, /* Ap */
203 { NULL, TYPE_Ar, 0 }, /* Ar */
204 { NULL, TYPE_Cd, 0 }, /* Cd */
205 { NULL, TYPE_Cm, 0 }, /* Cm */
206 { NULL, TYPE_Dv, 0 }, /* Dv */
207 { NULL, TYPE_Er, 0 }, /* Er */
208 { NULL, TYPE_Ev, 0 }, /* Ev */
209 { NULL, 0, 0 }, /* Ex */
210 { NULL, TYPE_Fa, 0 }, /* Fa */
211 { parse_mdoc_Fd, 0, 0 }, /* Fd */
212 { NULL, TYPE_Fl, 0 }, /* Fl */
213 { parse_mdoc_Fn, 0, 0 }, /* Fn */
214 { NULL, TYPE_Ft, 0 }, /* Ft */
215 { NULL, TYPE_Ic, 0 }, /* Ic */
216 { NULL, TYPE_In, 0 }, /* In */
217 { NULL, TYPE_Li, 0 }, /* Li */
218 { parse_mdoc_Nd, 0, 0 }, /* Nd */
219 { parse_mdoc_Nm, 0, 0 }, /* Nm */
220 { NULL, 0, 0 }, /* Op */
221 { NULL, 0, 0 }, /* Ot */
222 { NULL, TYPE_Pa, NODE_NOSRC }, /* Pa */
223 { NULL, 0, 0 }, /* Rv */
224 { NULL, TYPE_St, 0 }, /* St */
225 { parse_mdoc_Va, TYPE_Va, 0 }, /* Va */
226 { parse_mdoc_Va, TYPE_Vt, 0 }, /* Vt */
227 { parse_mdoc_Xr, 0, 0 }, /* Xr */
228 { NULL, 0, 0 }, /* %A */
229 { NULL, 0, 0 }, /* %B */
230 { NULL, 0, 0 }, /* %D */
231 { NULL, 0, 0 }, /* %I */
232 { NULL, 0, 0 }, /* %J */
233 { NULL, 0, 0 }, /* %N */
234 { NULL, 0, 0 }, /* %O */
302 { NULL, 0, 0 }, /* En */
303 { NULL, TYPE_Dx, NODE_NOSRC }, /* Dx */
304 { NULL, 0, 0 }, /* %Q */
305 { NULL, 0, 0 }, /* %U */
306 { NULL, 0, 0 }, /* Ta */
307 };
308 static const struct mdoc_handler *const mdocs = __mdocs - MDOC_Dd;
309
310
311 int
312 mandocdb(int argc, char *argv[])
313 {
314 struct manconf conf;
315 struct mparse *mp;
316 struct dba *dba;
317 const char *path_arg, *progname;
318 size_t j, sz;
319 int ch, i;
320
321 #if HAVE_PLEDGE
322 if (pledge("stdio rpath wpath cpath fattr flock proc exec", NULL) == -1) {
323 warn("pledge");
324 return (int)MANDOCLEVEL_SYSERR;
325 }
326 #endif
327
328 #if HAVE_SANDBOX_INIT
329 if (sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED, NULL) == -1) {
330 warnx("sandbox_init");
331 return (int)MANDOCLEVEL_SYSERR;
332 }
333 #endif
334
335 memset(&conf, 0, sizeof(conf));
336
337 /*
338 * We accept a few different invocations.
339 * The CHECKOP macro makes sure that invocation styles don't
340 * clobber each other.
341 */
342 #define CHECKOP(_op, _ch) do \
423 mp = mparse_alloc(mparse_options, MANDOCERR_MAX, NULL,
424 MANDOC_OS_OTHER, NULL);
425 mandoc_ohash_init(&mpages, 6, offsetof(struct mpage, inodev));
426 mandoc_ohash_init(&mlinks, 6, offsetof(struct mlink, file));
427
428 if (OP_UPDATE == op || OP_DELETE == op || OP_TEST == op) {
429
430 /*
431 * Most of these deal with a specific directory.
432 * Jump into that directory first.
433 */
434 if (OP_TEST != op && 0 == set_basedir(path_arg, 1))
435 goto out;
436
437 dba = nodb ? dba_new(128) : dba_read(MANDOC_DB);
438 if (dba != NULL) {
439 /*
440 * The existing database is usable. Process
441 * all files specified on the command-line.
442 */
443 #if HAVE_PLEDGE
444 if (!nodb) {
445 if (pledge("stdio rpath wpath cpath fattr flock", NULL) == -1) {
446 warn("pledge");
447 exitcode = (int)MANDOCLEVEL_SYSERR;
448 goto out;
449 }
450 }
451 #endif
452 use_all = 1;
453 for (i = 0; i < argc; i++)
454 filescan(argv[i]);
455 if (nodb == 0)
456 dbprune(dba);
457 } else {
458 /* Database missing or corrupt. */
459 if (op != OP_UPDATE || errno != ENOENT)
460 say(MANDOC_DB, "%s: Automatically recreating"
461 " from scratch", strerror(errno));
462 exitcode = (int)MANDOCLEVEL_OK;
463 op = OP_DEFAULT;
464 if (0 == treescan())
465 goto out;
466 dba = dba_new(128);
467 }
468 if (OP_DELETE != op)
469 mpages_merge(dba, mp);
470 if (nodb == 0)
471 dbwrite(dba);
1365 if (warnings)
1366 say(mlink->file, "No dash in title line, "
1367 "reusing \"%s\" as one-line description", title);
1368 p = title;
1369 }
1370
1371 plen = strlen(p);
1372
1373 /* Strip backspace-encoding from line. */
1374
1375 while (NULL != (line = memchr(p, '\b', plen))) {
1376 len = line - p;
1377 if (0 == len) {
1378 memmove(line, line + 1, plen--);
1379 continue;
1380 }
1381 memmove(line - 1, line + 1, plen - len);
1382 plen -= 2;
1383 }
1384
1385 mpage->desc = mandoc_strdup(p);
1386 fclose(stream);
1387 free(title);
1388 }
1389
1390 /*
1391 * Put a type/word pair into the word database for this particular file.
1392 */
1393 static void
1394 putkey(const struct mpage *mpage, char *value, uint64_t type)
1395 {
1396 putkeys(mpage, value, strlen(value), type);
1397 }
1398
1399 /*
1400 * Grok all nodes at or below a certain mdoc node into putkey().
1401 */
1402 static void
1403 putmdockey(const struct mpage *mpage,
1404 const struct roff_node *n, uint64_t m, int taboo)
1405 {
1509 return;
1510 }
1511
1512 while (isspace((unsigned char)*start))
1513 start++;
1514
1515 if (0 == strncmp(start, "-", 1))
1516 start += 1;
1517 else if (0 == strncmp(start, "\\-\\-", 4))
1518 start += 4;
1519 else if (0 == strncmp(start, "\\-", 2))
1520 start += 2;
1521 else if (0 == strncmp(start, "\\(en", 4))
1522 start += 4;
1523 else if (0 == strncmp(start, "\\(em", 4))
1524 start += 4;
1525
1526 while (' ' == *start)
1527 start++;
1528
1529 mpage->desc = mandoc_strdup(start);
1530 free(title);
1531 return;
1532 }
1533 }
1534
1535 for (n = n->child; n; n = n->next) {
1536 if (NULL != mpage->desc)
1537 break;
1538 parse_man(mpage, meta, n);
1539 }
1540 }
1541
1542 static void
1543 parse_mdoc(struct mpage *mpage, const struct roff_meta *meta,
1544 const struct roff_node *n)
1545 {
1546
1547 for (n = n->child; n != NULL; n = n->next) {
1548 if (n->tok == TOKEN_NONE ||
1549 n->tok < ROFF_MAX ||
1555 case ROFFT_BLOCK:
1556 case ROFFT_HEAD:
1557 case ROFFT_BODY:
1558 case ROFFT_TAIL:
1559 if (mdocs[n->tok].fp != NULL &&
1560 (*mdocs[n->tok].fp)(mpage, meta, n) == 0)
1561 break;
1562 if (mdocs[n->tok].mask)
1563 putmdockey(mpage, n->child,
1564 mdocs[n->tok].mask, mdocs[n->tok].taboo);
1565 break;
1566 default:
1567 continue;
1568 }
1569 if (NULL != n->child)
1570 parse_mdoc(mpage, meta, n);
1571 }
1572 }
1573
1574 static int
1575 parse_mdoc_Fd(struct mpage *mpage, const struct roff_meta *meta,
1576 const struct roff_node *n)
1577 {
1578 char *start, *end;
1579 size_t sz;
1580
1581 if (SEC_SYNOPSIS != n->sec ||
1582 NULL == (n = n->child) ||
1583 n->type != ROFFT_TEXT)
1584 return 0;
1585
1586 /*
1587 * Only consider those `Fd' macro fields that begin with an
1588 * "inclusion" token (versus, e.g., #define).
1589 */
1590
1591 if (strcmp("#include", n->string))
1592 return 0;
1593
1594 if ((n = n->next) == NULL || n->type != ROFFT_TEXT)
1623
1624 if (n->type != ROFFT_TEXT)
1625 return;
1626
1627 /* Skip function pointer punctuation. */
1628
1629 cp = n->string;
1630 while (*cp == '(' || *cp == '*')
1631 cp++;
1632 sz = strcspn(cp, "()");
1633
1634 putkeys(mpage, cp, sz, TYPE_Fn);
1635 if (n->sec == SEC_SYNOPSIS)
1636 putkeys(mpage, cp, sz, NAME_SYN);
1637 }
1638
1639 static int
1640 parse_mdoc_Fn(struct mpage *mpage, const struct roff_meta *meta,
1641 const struct roff_node *n)
1642 {
1643
1644 if (n->child == NULL)
1645 return 0;
1646
1647 parse_mdoc_fname(mpage, n->child);
1648
1649 for (n = n->child->next; n != NULL; n = n->next)
1650 if (n->type == ROFFT_TEXT)
1651 putkey(mpage, n->string, TYPE_Fa);
1652
1653 return 0;
1654 }
1655
1656 static int
1657 parse_mdoc_Fo(struct mpage *mpage, const struct roff_meta *meta,
1658 const struct roff_node *n)
1659 {
1660
1661 if (n->type != ROFFT_HEAD)
1662 return 1;
1663
1664 if (n->child != NULL)
1665 parse_mdoc_fname(mpage, n->child);
1666
1667 return 0;
1668 }
1669
1670 static int
1671 parse_mdoc_Va(struct mpage *mpage, const struct roff_meta *meta,
2102 dba_array_FOREACH(files, file) {
2103 if (*file < ' ')
2104 file++;
2105 if (ohash_find(&mlinks, ohash_qlookup(&mlinks,
2106 file)) != NULL) {
2107 if (debug)
2108 say(file, "Deleting from database");
2109 dba_array_del(dba->pages);
2110 break;
2111 }
2112 }
2113 }
2114 }
2115
2116 /*
2117 * Write the database from memory to disk.
2118 */
2119 static void
2120 dbwrite(struct dba *dba)
2121 {
2122 char tfn[32];
2123 int status;
2124 pid_t child;
2125
2126 /*
2127 * Do not write empty databases, and delete existing ones
2128 * when makewhatis -u causes them to become empty.
2129 */
2130
2131 dba_array_start(dba->pages);
2132 if (dba_array_next(dba->pages) == NULL) {
2133 if (unlink(MANDOC_DB) == -1 && errno != ENOENT)
2134 say(MANDOC_DB, "&unlink");
2135 return;
2136 }
2137
2138 /*
2139 * Build the database in a temporary file,
2140 * then atomically move it into place.
2141 */
2142
2143 if (dba_write(MANDOC_DB "~", dba) != -1) {
2144 if (rename(MANDOC_DB "~", MANDOC_DB) == -1) {
2145 exitcode = (int)MANDOCLEVEL_SYSERR;
2146 say(MANDOC_DB, "&rename");
2147 unlink(MANDOC_DB "~");
2148 }
2149 return;
2150 }
2151
2152 /*
2153 * We lack write permission and cannot replace the database
2154 * file, but let's at least check whether the data changed.
2155 */
2156
2157 (void)strlcpy(tfn, "/tmp/mandocdb.XXXXXXXX", sizeof(tfn));
2158 if (mkdtemp(tfn) == NULL) {
2159 exitcode = (int)MANDOCLEVEL_SYSERR;
2160 say("", "&%s", tfn);
2161 return;
2162 }
2163
2164 (void)strlcat(tfn, "/" MANDOC_DB, sizeof(tfn));
2165 if (dba_write(tfn, dba) == -1) {
2166 exitcode = (int)MANDOCLEVEL_SYSERR;
2167 say(tfn, "&dba_write");
2168 goto out;
2169 }
2170
2171 switch (child = fork()) {
2172 case -1:
2173 exitcode = (int)MANDOCLEVEL_SYSERR;
2174 say("", "&fork cmp");
2175 return;
2176 case 0:
2177 execlp("cmp", "cmp", "-s", tfn, MANDOC_DB, (char *)NULL);
2178 say("", "&exec cmp");
2179 exit(0);
2180 default:
2181 break;
2182 }
2183 if (waitpid(child, &status, 0) == -1) {
2184 exitcode = (int)MANDOCLEVEL_SYSERR;
2185 say("", "&wait cmp");
2186 } else if (WIFSIGNALED(status)) {
2187 exitcode = (int)MANDOCLEVEL_SYSERR;
2188 say("", "cmp died from signal %d", WTERMSIG(status));
2189 } else if (WEXITSTATUS(status)) {
2190 exitcode = (int)MANDOCLEVEL_SYSERR;
2191 say(MANDOC_DB,
2192 "Data changed, but cannot replace database");
2193 }
2194
2195 out:
2196 *strrchr(tfn, '/') = '\0';
2197 switch (child = fork()) {
2198 case -1:
2199 exitcode = (int)MANDOCLEVEL_SYSERR;
2200 say("", "&fork rm");
2201 return;
2202 case 0:
2203 execlp("rm", "rm", "-rf", tfn, (char *)NULL);
2204 say("", "&exec rm");
2205 exit((int)MANDOCLEVEL_SYSERR);
2206 default:
2207 break;
2208 }
2209 if (waitpid(child, &status, 0) == -1) {
2210 exitcode = (int)MANDOCLEVEL_SYSERR;
2211 say("", "&wait rm");
2212 } else if (WIFSIGNALED(status) || WEXITSTATUS(status)) {
2213 exitcode = (int)MANDOCLEVEL_SYSERR;
2214 say("", "%s: Cannot remove temporary directory", tfn);
2215 }
2216 }
2217
2218 static int
2219 set_basedir(const char *targetdir, int report_baddir)
2220 {
2221 static char startdir[PATH_MAX];
2222 static int getcwd_status; /* 1 = ok, 2 = failure */
2223 static int chdir_status; /* 1 = changed directory */
2224 char *cp;
2225
2226 /*
2227 * Remember the original working directory, if possible.
2228 * This will be needed if the second or a later directory
2229 * on the command line is given as a relative path.
2230 * Do not error out if the current directory is not
2231 * searchable: Maybe it won't be needed after all.
2232 */
2233 if (0 == getcwd_status) {
2234 if (NULL == getcwd(startdir, sizeof(startdir))) {
2235 getcwd_status = 2;
|
1 /* $Id: mandocdb.c,v 1.258 2018/02/23 18:25:57 schwarze Exp $ */
2 /*
3 * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2011-2017 Ingo Schwarze <schwarze@openbsd.org>
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 #include "config.h"
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 #if HAVE_ERR
28 #include <err.h>
29 #endif
30 #include <errno.h>
31 #include <fcntl.h>
32 #if HAVE_FTS
33 #include <fts.h>
34 #else
35 #include "compat_fts.h"
36 #endif
37 #include <limits.h>
38 #if HAVE_SANDBOX_INIT
39 #include <sandbox.h>
40 #endif
41 #include <stdarg.h>
42 #include <stddef.h>
43 #include <stdio.h>
122 static void dbwrite(struct dba *);
123 static void filescan(const char *);
124 #if HAVE_FTS_COMPARE_CONST
125 static int fts_compare(const FTSENT *const *, const FTSENT *const *);
126 #else
127 static int fts_compare(const FTSENT **, const FTSENT **);
128 #endif
129 static void mlink_add(struct mlink *, const struct stat *);
130 static void mlink_check(struct mpage *, struct mlink *);
131 static void mlink_free(struct mlink *);
132 static void mlinks_undupe(struct mpage *);
133 static void mpages_free(void);
134 static void mpages_merge(struct dba *, struct mparse *);
135 static void parse_cat(struct mpage *, int);
136 static void parse_man(struct mpage *, const struct roff_meta *,
137 const struct roff_node *);
138 static void parse_mdoc(struct mpage *, const struct roff_meta *,
139 const struct roff_node *);
140 static int parse_mdoc_head(struct mpage *, const struct roff_meta *,
141 const struct roff_node *);
142 static int parse_mdoc_Fa(struct mpage *, const struct roff_meta *,
143 const struct roff_node *);
144 static int parse_mdoc_Fd(struct mpage *, const struct roff_meta *,
145 const struct roff_node *);
146 static void parse_mdoc_fname(struct mpage *, const struct roff_node *);
147 static int parse_mdoc_Fn(struct mpage *, const struct roff_meta *,
148 const struct roff_node *);
149 static int parse_mdoc_Fo(struct mpage *, const struct roff_meta *,
150 const struct roff_node *);
151 static int parse_mdoc_Nd(struct mpage *, const struct roff_meta *,
152 const struct roff_node *);
153 static int parse_mdoc_Nm(struct mpage *, const struct roff_meta *,
154 const struct roff_node *);
155 static int parse_mdoc_Sh(struct mpage *, const struct roff_meta *,
156 const struct roff_node *);
157 static int parse_mdoc_Va(struct mpage *, const struct roff_meta *,
158 const struct roff_node *);
159 static int parse_mdoc_Xr(struct mpage *, const struct roff_meta *,
160 const struct roff_node *);
161 static void putkey(const struct mpage *, char *, uint64_t);
162 static void putkeys(const struct mpage *, char *, size_t, uint64_t);
163 static void putmdockey(const struct mpage *,
192 { parse_mdoc_Sh, TYPE_Sh, 0 }, /* Sh */
193 { parse_mdoc_head, TYPE_Ss, 0 }, /* Ss */
194 { NULL, 0, 0 }, /* Pp */
195 { NULL, 0, 0 }, /* D1 */
196 { NULL, 0, 0 }, /* Dl */
197 { NULL, 0, 0 }, /* Bd */
198 { NULL, 0, 0 }, /* Ed */
199 { NULL, 0, 0 }, /* Bl */
200 { NULL, 0, 0 }, /* El */
201 { NULL, 0, 0 }, /* It */
202 { NULL, 0, 0 }, /* Ad */
203 { NULL, TYPE_An, 0 }, /* An */
204 { NULL, 0, 0 }, /* Ap */
205 { NULL, TYPE_Ar, 0 }, /* Ar */
206 { NULL, TYPE_Cd, 0 }, /* Cd */
207 { NULL, TYPE_Cm, 0 }, /* Cm */
208 { NULL, TYPE_Dv, 0 }, /* Dv */
209 { NULL, TYPE_Er, 0 }, /* Er */
210 { NULL, TYPE_Ev, 0 }, /* Ev */
211 { NULL, 0, 0 }, /* Ex */
212 { parse_mdoc_Fa, 0, 0 }, /* Fa */
213 { parse_mdoc_Fd, 0, 0 }, /* Fd */
214 { NULL, TYPE_Fl, 0 }, /* Fl */
215 { parse_mdoc_Fn, 0, 0 }, /* Fn */
216 { NULL, TYPE_Ft | TYPE_Vt, 0 }, /* Ft */
217 { NULL, TYPE_Ic, 0 }, /* Ic */
218 { NULL, TYPE_In, 0 }, /* In */
219 { NULL, TYPE_Li, 0 }, /* Li */
220 { parse_mdoc_Nd, 0, 0 }, /* Nd */
221 { parse_mdoc_Nm, 0, 0 }, /* Nm */
222 { NULL, 0, 0 }, /* Op */
223 { NULL, 0, 0 }, /* Ot */
224 { NULL, TYPE_Pa, NODE_NOSRC }, /* Pa */
225 { NULL, 0, 0 }, /* Rv */
226 { NULL, TYPE_St, 0 }, /* St */
227 { parse_mdoc_Va, TYPE_Va, 0 }, /* Va */
228 { parse_mdoc_Va, TYPE_Vt, 0 }, /* Vt */
229 { parse_mdoc_Xr, 0, 0 }, /* Xr */
230 { NULL, 0, 0 }, /* %A */
231 { NULL, 0, 0 }, /* %B */
232 { NULL, 0, 0 }, /* %D */
233 { NULL, 0, 0 }, /* %I */
234 { NULL, 0, 0 }, /* %J */
235 { NULL, 0, 0 }, /* %N */
236 { NULL, 0, 0 }, /* %O */
304 { NULL, 0, 0 }, /* En */
305 { NULL, TYPE_Dx, NODE_NOSRC }, /* Dx */
306 { NULL, 0, 0 }, /* %Q */
307 { NULL, 0, 0 }, /* %U */
308 { NULL, 0, 0 }, /* Ta */
309 };
310 static const struct mdoc_handler *const mdocs = __mdocs - MDOC_Dd;
311
312
313 int
314 mandocdb(int argc, char *argv[])
315 {
316 struct manconf conf;
317 struct mparse *mp;
318 struct dba *dba;
319 const char *path_arg, *progname;
320 size_t j, sz;
321 int ch, i;
322
323 #if HAVE_PLEDGE
324 if (pledge("stdio rpath wpath cpath", NULL) == -1) {
325 warn("pledge");
326 return (int)MANDOCLEVEL_SYSERR;
327 }
328 #endif
329
330 #if HAVE_SANDBOX_INIT
331 if (sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED, NULL) == -1) {
332 warnx("sandbox_init");
333 return (int)MANDOCLEVEL_SYSERR;
334 }
335 #endif
336
337 memset(&conf, 0, sizeof(conf));
338
339 /*
340 * We accept a few different invocations.
341 * The CHECKOP macro makes sure that invocation styles don't
342 * clobber each other.
343 */
344 #define CHECKOP(_op, _ch) do \
425 mp = mparse_alloc(mparse_options, MANDOCERR_MAX, NULL,
426 MANDOC_OS_OTHER, NULL);
427 mandoc_ohash_init(&mpages, 6, offsetof(struct mpage, inodev));
428 mandoc_ohash_init(&mlinks, 6, offsetof(struct mlink, file));
429
430 if (OP_UPDATE == op || OP_DELETE == op || OP_TEST == op) {
431
432 /*
433 * Most of these deal with a specific directory.
434 * Jump into that directory first.
435 */
436 if (OP_TEST != op && 0 == set_basedir(path_arg, 1))
437 goto out;
438
439 dba = nodb ? dba_new(128) : dba_read(MANDOC_DB);
440 if (dba != NULL) {
441 /*
442 * The existing database is usable. Process
443 * all files specified on the command-line.
444 */
445 use_all = 1;
446 for (i = 0; i < argc; i++)
447 filescan(argv[i]);
448 if (nodb == 0)
449 dbprune(dba);
450 } else {
451 /* Database missing or corrupt. */
452 if (op != OP_UPDATE || errno != ENOENT)
453 say(MANDOC_DB, "%s: Automatically recreating"
454 " from scratch", strerror(errno));
455 exitcode = (int)MANDOCLEVEL_OK;
456 op = OP_DEFAULT;
457 if (0 == treescan())
458 goto out;
459 dba = dba_new(128);
460 }
461 if (OP_DELETE != op)
462 mpages_merge(dba, mp);
463 if (nodb == 0)
464 dbwrite(dba);
1358 if (warnings)
1359 say(mlink->file, "No dash in title line, "
1360 "reusing \"%s\" as one-line description", title);
1361 p = title;
1362 }
1363
1364 plen = strlen(p);
1365
1366 /* Strip backspace-encoding from line. */
1367
1368 while (NULL != (line = memchr(p, '\b', plen))) {
1369 len = line - p;
1370 if (0 == len) {
1371 memmove(line, line + 1, plen--);
1372 continue;
1373 }
1374 memmove(line - 1, line + 1, plen - len);
1375 plen -= 2;
1376 }
1377
1378 /*
1379 * Cut off excessive one-line descriptions.
1380 * Bad pages are not worth better heuristics.
1381 */
1382
1383 mpage->desc = mandoc_strndup(p, 150);
1384 fclose(stream);
1385 free(title);
1386 }
1387
1388 /*
1389 * Put a type/word pair into the word database for this particular file.
1390 */
1391 static void
1392 putkey(const struct mpage *mpage, char *value, uint64_t type)
1393 {
1394 putkeys(mpage, value, strlen(value), type);
1395 }
1396
1397 /*
1398 * Grok all nodes at or below a certain mdoc node into putkey().
1399 */
1400 static void
1401 putmdockey(const struct mpage *mpage,
1402 const struct roff_node *n, uint64_t m, int taboo)
1403 {
1507 return;
1508 }
1509
1510 while (isspace((unsigned char)*start))
1511 start++;
1512
1513 if (0 == strncmp(start, "-", 1))
1514 start += 1;
1515 else if (0 == strncmp(start, "\\-\\-", 4))
1516 start += 4;
1517 else if (0 == strncmp(start, "\\-", 2))
1518 start += 2;
1519 else if (0 == strncmp(start, "\\(en", 4))
1520 start += 4;
1521 else if (0 == strncmp(start, "\\(em", 4))
1522 start += 4;
1523
1524 while (' ' == *start)
1525 start++;
1526
1527 /*
1528 * Cut off excessive one-line descriptions.
1529 * Bad pages are not worth better heuristics.
1530 */
1531
1532 mpage->desc = mandoc_strndup(start, 150);
1533 free(title);
1534 return;
1535 }
1536 }
1537
1538 for (n = n->child; n; n = n->next) {
1539 if (NULL != mpage->desc)
1540 break;
1541 parse_man(mpage, meta, n);
1542 }
1543 }
1544
1545 static void
1546 parse_mdoc(struct mpage *mpage, const struct roff_meta *meta,
1547 const struct roff_node *n)
1548 {
1549
1550 for (n = n->child; n != NULL; n = n->next) {
1551 if (n->tok == TOKEN_NONE ||
1552 n->tok < ROFF_MAX ||
1558 case ROFFT_BLOCK:
1559 case ROFFT_HEAD:
1560 case ROFFT_BODY:
1561 case ROFFT_TAIL:
1562 if (mdocs[n->tok].fp != NULL &&
1563 (*mdocs[n->tok].fp)(mpage, meta, n) == 0)
1564 break;
1565 if (mdocs[n->tok].mask)
1566 putmdockey(mpage, n->child,
1567 mdocs[n->tok].mask, mdocs[n->tok].taboo);
1568 break;
1569 default:
1570 continue;
1571 }
1572 if (NULL != n->child)
1573 parse_mdoc(mpage, meta, n);
1574 }
1575 }
1576
1577 static int
1578 parse_mdoc_Fa(struct mpage *mpage, const struct roff_meta *meta,
1579 const struct roff_node *n)
1580 {
1581 uint64_t mask;
1582
1583 mask = TYPE_Fa;
1584 if (n->sec == SEC_SYNOPSIS)
1585 mask |= TYPE_Vt;
1586
1587 putmdockey(mpage, n->child, mask, 0);
1588 return 0;
1589 }
1590
1591 static int
1592 parse_mdoc_Fd(struct mpage *mpage, const struct roff_meta *meta,
1593 const struct roff_node *n)
1594 {
1595 char *start, *end;
1596 size_t sz;
1597
1598 if (SEC_SYNOPSIS != n->sec ||
1599 NULL == (n = n->child) ||
1600 n->type != ROFFT_TEXT)
1601 return 0;
1602
1603 /*
1604 * Only consider those `Fd' macro fields that begin with an
1605 * "inclusion" token (versus, e.g., #define).
1606 */
1607
1608 if (strcmp("#include", n->string))
1609 return 0;
1610
1611 if ((n = n->next) == NULL || n->type != ROFFT_TEXT)
1640
1641 if (n->type != ROFFT_TEXT)
1642 return;
1643
1644 /* Skip function pointer punctuation. */
1645
1646 cp = n->string;
1647 while (*cp == '(' || *cp == '*')
1648 cp++;
1649 sz = strcspn(cp, "()");
1650
1651 putkeys(mpage, cp, sz, TYPE_Fn);
1652 if (n->sec == SEC_SYNOPSIS)
1653 putkeys(mpage, cp, sz, NAME_SYN);
1654 }
1655
1656 static int
1657 parse_mdoc_Fn(struct mpage *mpage, const struct roff_meta *meta,
1658 const struct roff_node *n)
1659 {
1660 uint64_t mask;
1661
1662 if (n->child == NULL)
1663 return 0;
1664
1665 parse_mdoc_fname(mpage, n->child);
1666
1667 n = n->child->next;
1668 if (n != NULL && n->type == ROFFT_TEXT) {
1669 mask = TYPE_Fa;
1670 if (n->sec == SEC_SYNOPSIS)
1671 mask |= TYPE_Vt;
1672 putmdockey(mpage, n, mask, 0);
1673 }
1674
1675 return 0;
1676 }
1677
1678 static int
1679 parse_mdoc_Fo(struct mpage *mpage, const struct roff_meta *meta,
1680 const struct roff_node *n)
1681 {
1682
1683 if (n->type != ROFFT_HEAD)
1684 return 1;
1685
1686 if (n->child != NULL)
1687 parse_mdoc_fname(mpage, n->child);
1688
1689 return 0;
1690 }
1691
1692 static int
1693 parse_mdoc_Va(struct mpage *mpage, const struct roff_meta *meta,
2124 dba_array_FOREACH(files, file) {
2125 if (*file < ' ')
2126 file++;
2127 if (ohash_find(&mlinks, ohash_qlookup(&mlinks,
2128 file)) != NULL) {
2129 if (debug)
2130 say(file, "Deleting from database");
2131 dba_array_del(dba->pages);
2132 break;
2133 }
2134 }
2135 }
2136 }
2137
2138 /*
2139 * Write the database from memory to disk.
2140 */
2141 static void
2142 dbwrite(struct dba *dba)
2143 {
2144 struct stat sb1, sb2;
2145 char tfn[33], *cp1, *cp2;
2146 off_t i;
2147 int fd1, fd2;
2148
2149 /*
2150 * Do not write empty databases, and delete existing ones
2151 * when makewhatis -u causes them to become empty.
2152 */
2153
2154 dba_array_start(dba->pages);
2155 if (dba_array_next(dba->pages) == NULL) {
2156 if (unlink(MANDOC_DB) == -1 && errno != ENOENT)
2157 say(MANDOC_DB, "&unlink");
2158 return;
2159 }
2160
2161 /*
2162 * Build the database in a temporary file,
2163 * then atomically move it into place.
2164 */
2165
2166 if (dba_write(MANDOC_DB "~", dba) != -1) {
2167 if (rename(MANDOC_DB "~", MANDOC_DB) == -1) {
2168 exitcode = (int)MANDOCLEVEL_SYSERR;
2169 say(MANDOC_DB, "&rename");
2170 unlink(MANDOC_DB "~");
2171 }
2172 return;
2173 }
2174
2175 /*
2176 * We lack write permission and cannot replace the database
2177 * file, but let's at least check whether the data changed.
2178 */
2179
2180 (void)strlcpy(tfn, "/tmp/mandocdb.XXXXXXXX", sizeof(tfn));
2181 if (mkdtemp(tfn) == NULL) {
2182 exitcode = (int)MANDOCLEVEL_SYSERR;
2183 say("", "&%s", tfn);
2184 return;
2185 }
2186 cp1 = cp2 = MAP_FAILED;
2187 fd1 = fd2 = -1;
2188 (void)strlcat(tfn, "/" MANDOC_DB, sizeof(tfn));
2189 if (dba_write(tfn, dba) == -1) {
2190 say(tfn, "&dba_write");
2191 goto err;
2192 }
2193 if ((fd1 = open(MANDOC_DB, O_RDONLY, 0)) == -1) {
2194 say(MANDOC_DB, "&open");
2195 goto err;
2196 }
2197 if ((fd2 = open(tfn, O_RDONLY, 0)) == -1) {
2198 say(tfn, "&open");
2199 goto err;
2200 }
2201 if (fstat(fd1, &sb1) == -1) {
2202 say(MANDOC_DB, "&fstat");
2203 goto err;
2204 }
2205 if (fstat(fd2, &sb2) == -1) {
2206 say(tfn, "&fstat");
2207 goto err;
2208 }
2209 if (sb1.st_size != sb2.st_size)
2210 goto err;
2211 if ((cp1 = mmap(NULL, sb1.st_size, PROT_READ, MAP_PRIVATE,
2212 fd1, 0)) == MAP_FAILED) {
2213 say(MANDOC_DB, "&mmap");
2214 goto err;
2215 }
2216 if ((cp2 = mmap(NULL, sb2.st_size, PROT_READ, MAP_PRIVATE,
2217 fd2, 0)) == MAP_FAILED) {
2218 say(tfn, "&mmap");
2219 goto err;
2220 }
2221 for (i = 0; i < sb1.st_size; i++)
2222 if (cp1[i] != cp2[i])
2223 goto err;
2224 goto out;
2225
2226 err:
2227 exitcode = (int)MANDOCLEVEL_SYSERR;
2228 say(MANDOC_DB, "Data changed, but cannot replace database");
2229
2230 out:
2231 if (cp1 != MAP_FAILED)
2232 munmap(cp1, sb1.st_size);
2233 if (cp2 != MAP_FAILED)
2234 munmap(cp2, sb2.st_size);
2235 if (fd1 != -1)
2236 close(fd1);
2237 if (fd2 != -1)
2238 close(fd2);
2239 unlink(tfn);
2240 *strrchr(tfn, '/') = '\0';
2241 rmdir(tfn);
2242 }
2243
2244 static int
2245 set_basedir(const char *targetdir, int report_baddir)
2246 {
2247 static char startdir[PATH_MAX];
2248 static int getcwd_status; /* 1 = ok, 2 = failure */
2249 static int chdir_status; /* 1 = changed directory */
2250 char *cp;
2251
2252 /*
2253 * Remember the original working directory, if possible.
2254 * This will be needed if the second or a later directory
2255 * on the command line is given as a relative path.
2256 * Do not error out if the current directory is not
2257 * searchable: Maybe it won't be needed after all.
2258 */
2259 if (0 == getcwd_status) {
2260 if (NULL == getcwd(startdir, sizeof(startdir))) {
2261 getcwd_status = 2;
|