Annotation of mandoc/mandocdb.c, Revision 1.44
1.44 ! kristaps 1: /* $Id: mandocdb.c,v 1.43 2011/12/31 18:47:52 kristaps Exp $ */
1.1 kristaps 2: /*
3: * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
1.12 schwarze 4: * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
1.1 kristaps 5: *
6: * Permission to use, copy, modify, and distribute this software for any
7: * purpose with or without fee is hereby granted, provided that the above
8: * copyright notice and this permission notice appear in all copies.
9: *
10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17: */
18: #ifdef HAVE_CONFIG_H
19: #include "config.h"
20: #endif
21:
22: #include <sys/param.h>
1.14 schwarze 23: #include <sys/types.h>
1.1 kristaps 24:
25: #include <assert.h>
1.43 kristaps 26: #include <ctype.h>
1.4 kristaps 27: #include <dirent.h>
1.1 kristaps 28: #include <fcntl.h>
29: #include <getopt.h>
30: #include <stdio.h>
31: #include <stdint.h>
32: #include <stdlib.h>
33: #include <string.h>
1.17 schwarze 34: #include <unistd.h>
1.1 kristaps 35:
1.21 kristaps 36: #if defined(__linux__)
37: # include <endian.h>
1.1 kristaps 38: # include <db_185.h>
1.21 kristaps 39: #elif defined(__APPLE__)
40: # include <libkern/OSByteOrder.h>
41: # include <db.h>
1.1 kristaps 42: #else
43: # include <db.h>
44: #endif
45:
46: #include "man.h"
47: #include "mdoc.h"
48: #include "mandoc.h"
1.8 schwarze 49: #include "mandocdb.h"
1.10 kristaps 50: #include "manpath.h"
1.1 kristaps 51:
52: #define MANDOC_BUFSZ BUFSIZ
53: #define MANDOC_SLOP 1024
54:
1.14 schwarze 55: #define MANDOC_SRC 0x1
56: #define MANDOC_FORM 0x2
57:
1.38 schwarze 58: /* Access to the mandoc database on disk. */
59:
60: struct mdb {
61: char idxn[MAXPATHLEN]; /* index db filename */
62: char dbn[MAXPATHLEN]; /* keyword db filename */
63: DB *idx; /* index recno database */
64: DB *db; /* keyword btree database */
65: };
66:
67: /* Stack of temporarily unused index records. */
68:
69: struct recs {
70: recno_t *stack; /* pointer to a malloc'ed array */
71: size_t size; /* number of allocated slots */
72: size_t cur; /* current number of empty records */
73: recno_t last; /* last record number in the index */
74: };
75:
1.5 kristaps 76: /* Tiny list for files. No need to bring in QUEUE. */
77:
1.3 kristaps 78: struct of {
1.5 kristaps 79: char *fname; /* heap-allocated */
1.12 schwarze 80: char *sec;
81: char *arch;
82: char *title;
1.14 schwarze 83: int src_form;
1.5 kristaps 84: struct of *next; /* NULL for last one */
85: struct of *first; /* first in list */
1.3 kristaps 86: };
87:
1.1 kristaps 88: /* Buffer for storing growable data. */
89:
90: struct buf {
91: char *cp;
1.5 kristaps 92: size_t len; /* current length */
93: size_t size; /* total buffer size */
1.1 kristaps 94: };
95:
96: /* Operation we're going to perform. */
97:
98: enum op {
1.38 schwarze 99: OP_DEFAULT = 0, /* new dbs from dir list or default config */
100: OP_CONFFILE, /* new databases from custom config file */
1.5 kristaps 101: OP_UPDATE, /* delete/add entries in existing database */
1.38 schwarze 102: OP_DELETE, /* delete entries from existing database */
103: OP_TEST /* change no databases, report potential problems */
1.1 kristaps 104: };
105:
106: #define MAN_ARGS DB *hash, \
107: struct buf *buf, \
108: struct buf *dbuf, \
109: const struct man_node *n
110: #define MDOC_ARGS DB *hash, \
111: struct buf *buf, \
112: struct buf *dbuf, \
113: const struct mdoc_node *n, \
114: const struct mdoc_meta *m
115:
116: static void buf_appendmdoc(struct buf *,
117: const struct mdoc_node *, int);
118: static void buf_append(struct buf *, const char *);
119: static void buf_appendb(struct buf *,
120: const void *, size_t);
121: static void dbt_put(DB *, const char *, DBT *, DBT *);
1.9 kristaps 122: static void hash_put(DB *, const struct buf *, uint64_t);
1.1 kristaps 123: static void hash_reset(DB **);
1.3 kristaps 124: static void index_merge(const struct of *, struct mparse *,
1.16 schwarze 125: struct buf *, struct buf *, DB *,
1.44 ! kristaps 126: struct mdb *, struct recs *,
! 127: const char *);
1.38 schwarze 128: static void index_prune(const struct of *, struct mdb *,
1.44 ! kristaps 129: struct recs *, const char *);
! 130: static void ofile_argbuild(int, char *[],
! 131: struct of **, const char *);
1.35 kristaps 132: static void ofile_dirbuild(const char *, const char *,
1.16 schwarze 133: const char *, int, struct of **);
1.4 kristaps 134: static void ofile_free(struct of *);
1.44 ! kristaps 135: static void pformatted(DB *, struct buf *, struct buf *,
! 136: const struct of *, const char *);
1.1 kristaps 137: static int pman_node(MAN_ARGS);
138: static void pmdoc_node(MDOC_ARGS);
1.25 schwarze 139: static int pmdoc_head(MDOC_ARGS);
140: static int pmdoc_body(MDOC_ARGS);
141: static int pmdoc_Fd(MDOC_ARGS);
142: static int pmdoc_In(MDOC_ARGS);
143: static int pmdoc_Fn(MDOC_ARGS);
144: static int pmdoc_Nd(MDOC_ARGS);
145: static int pmdoc_Nm(MDOC_ARGS);
146: static int pmdoc_Sh(MDOC_ARGS);
147: static int pmdoc_St(MDOC_ARGS);
148: static int pmdoc_Xr(MDOC_ARGS);
1.1 kristaps 149:
1.25 schwarze 150: #define MDOCF_CHILD 0x01 /* Automatically index child nodes. */
1.1 kristaps 151:
1.25 schwarze 152: struct mdoc_handler {
153: int (*fp)(MDOC_ARGS); /* Optional handler. */
154: uint64_t mask; /* Set unless handler returns 0. */
155: int flags; /* For use by pmdoc_node. */
156: };
157:
158: static const struct mdoc_handler mdocs[MDOC_MAX] = {
159: { NULL, 0, 0 }, /* Ap */
160: { NULL, 0, 0 }, /* Dd */
161: { NULL, 0, 0 }, /* Dt */
162: { NULL, 0, 0 }, /* Os */
163: { pmdoc_Sh, TYPE_Sh, MDOCF_CHILD }, /* Sh */
164: { pmdoc_head, TYPE_Ss, MDOCF_CHILD }, /* Ss */
165: { NULL, 0, 0 }, /* Pp */
166: { NULL, 0, 0 }, /* D1 */
167: { NULL, 0, 0 }, /* Dl */
168: { NULL, 0, 0 }, /* Bd */
169: { NULL, 0, 0 }, /* Ed */
170: { NULL, 0, 0 }, /* Bl */
171: { NULL, 0, 0 }, /* El */
172: { NULL, 0, 0 }, /* It */
173: { NULL, 0, 0 }, /* Ad */
174: { NULL, TYPE_An, MDOCF_CHILD }, /* An */
175: { NULL, TYPE_Ar, MDOCF_CHILD }, /* Ar */
176: { NULL, TYPE_Cd, MDOCF_CHILD }, /* Cd */
177: { NULL, TYPE_Cm, MDOCF_CHILD }, /* Cm */
178: { NULL, TYPE_Dv, MDOCF_CHILD }, /* Dv */
179: { NULL, TYPE_Er, MDOCF_CHILD }, /* Er */
180: { NULL, TYPE_Ev, MDOCF_CHILD }, /* Ev */
181: { NULL, 0, 0 }, /* Ex */
182: { NULL, TYPE_Fa, MDOCF_CHILD }, /* Fa */
183: { pmdoc_Fd, TYPE_In, 0 }, /* Fd */
184: { NULL, TYPE_Fl, MDOCF_CHILD }, /* Fl */
185: { pmdoc_Fn, 0, 0 }, /* Fn */
186: { NULL, TYPE_Ft, MDOCF_CHILD }, /* Ft */
187: { NULL, TYPE_Ic, MDOCF_CHILD }, /* Ic */
188: { pmdoc_In, TYPE_In, 0 }, /* In */
189: { NULL, TYPE_Li, MDOCF_CHILD }, /* Li */
190: { pmdoc_Nd, TYPE_Nd, MDOCF_CHILD }, /* Nd */
191: { pmdoc_Nm, TYPE_Nm, MDOCF_CHILD }, /* Nm */
192: { NULL, 0, 0 }, /* Op */
193: { NULL, 0, 0 }, /* Ot */
194: { NULL, TYPE_Pa, MDOCF_CHILD }, /* Pa */
195: { NULL, 0, 0 }, /* Rv */
196: { pmdoc_St, TYPE_St, 0 }, /* St */
197: { NULL, TYPE_Va, MDOCF_CHILD }, /* Va */
198: { pmdoc_body, TYPE_Va, MDOCF_CHILD }, /* Vt */
199: { pmdoc_Xr, TYPE_Xr, 0 }, /* Xr */
200: { NULL, 0, 0 }, /* %A */
201: { NULL, 0, 0 }, /* %B */
202: { NULL, 0, 0 }, /* %D */
203: { NULL, 0, 0 }, /* %I */
204: { NULL, 0, 0 }, /* %J */
205: { NULL, 0, 0 }, /* %N */
206: { NULL, 0, 0 }, /* %O */
207: { NULL, 0, 0 }, /* %P */
208: { NULL, 0, 0 }, /* %R */
209: { NULL, 0, 0 }, /* %T */
210: { NULL, 0, 0 }, /* %V */
211: { NULL, 0, 0 }, /* Ac */
212: { NULL, 0, 0 }, /* Ao */
213: { NULL, 0, 0 }, /* Aq */
214: { NULL, TYPE_At, MDOCF_CHILD }, /* At */
215: { NULL, 0, 0 }, /* Bc */
216: { NULL, 0, 0 }, /* Bf */
217: { NULL, 0, 0 }, /* Bo */
218: { NULL, 0, 0 }, /* Bq */
219: { NULL, TYPE_Bsx, MDOCF_CHILD }, /* Bsx */
220: { NULL, TYPE_Bx, MDOCF_CHILD }, /* Bx */
221: { NULL, 0, 0 }, /* Db */
222: { NULL, 0, 0 }, /* Dc */
223: { NULL, 0, 0 }, /* Do */
224: { NULL, 0, 0 }, /* Dq */
225: { NULL, 0, 0 }, /* Ec */
226: { NULL, 0, 0 }, /* Ef */
227: { NULL, TYPE_Em, MDOCF_CHILD }, /* Em */
228: { NULL, 0, 0 }, /* Eo */
229: { NULL, TYPE_Fx, MDOCF_CHILD }, /* Fx */
230: { NULL, TYPE_Ms, MDOCF_CHILD }, /* Ms */
231: { NULL, 0, 0 }, /* No */
232: { NULL, 0, 0 }, /* Ns */
233: { NULL, TYPE_Nx, MDOCF_CHILD }, /* Nx */
234: { NULL, TYPE_Ox, MDOCF_CHILD }, /* Ox */
235: { NULL, 0, 0 }, /* Pc */
236: { NULL, 0, 0 }, /* Pf */
237: { NULL, 0, 0 }, /* Po */
238: { NULL, 0, 0 }, /* Pq */
239: { NULL, 0, 0 }, /* Qc */
240: { NULL, 0, 0 }, /* Ql */
241: { NULL, 0, 0 }, /* Qo */
242: { NULL, 0, 0 }, /* Qq */
243: { NULL, 0, 0 }, /* Re */
244: { NULL, 0, 0 }, /* Rs */
245: { NULL, 0, 0 }, /* Sc */
246: { NULL, 0, 0 }, /* So */
247: { NULL, 0, 0 }, /* Sq */
248: { NULL, 0, 0 }, /* Sm */
249: { NULL, 0, 0 }, /* Sx */
250: { NULL, TYPE_Sy, MDOCF_CHILD }, /* Sy */
251: { NULL, TYPE_Tn, MDOCF_CHILD }, /* Tn */
252: { NULL, 0, 0 }, /* Ux */
253: { NULL, 0, 0 }, /* Xc */
254: { NULL, 0, 0 }, /* Xo */
255: { pmdoc_head, TYPE_Fn, 0 }, /* Fo */
256: { NULL, 0, 0 }, /* Fc */
257: { NULL, 0, 0 }, /* Oo */
258: { NULL, 0, 0 }, /* Oc */
259: { NULL, 0, 0 }, /* Bk */
260: { NULL, 0, 0 }, /* Ek */
261: { NULL, 0, 0 }, /* Bt */
262: { NULL, 0, 0 }, /* Hf */
263: { NULL, 0, 0 }, /* Fr */
264: { NULL, 0, 0 }, /* Ud */
265: { NULL, TYPE_Lb, MDOCF_CHILD }, /* Lb */
266: { NULL, 0, 0 }, /* Lp */
267: { NULL, TYPE_Lk, MDOCF_CHILD }, /* Lk */
268: { NULL, TYPE_Mt, MDOCF_CHILD }, /* Mt */
269: { NULL, 0, 0 }, /* Brq */
270: { NULL, 0, 0 }, /* Bro */
271: { NULL, 0, 0 }, /* Brc */
272: { NULL, 0, 0 }, /* %C */
273: { NULL, 0, 0 }, /* Es */
274: { NULL, 0, 0 }, /* En */
275: { NULL, TYPE_Dx, MDOCF_CHILD }, /* Dx */
276: { NULL, 0, 0 }, /* %Q */
277: { NULL, 0, 0 }, /* br */
278: { NULL, 0, 0 }, /* sp */
279: { NULL, 0, 0 }, /* %U */
280: { NULL, 0, 0 }, /* Ta */
1.1 kristaps 281: };
282:
283: static const char *progname;
1.16 schwarze 284: static int use_all; /* Use all directories and files. */
285: static int verb; /* Output verbosity level. */
1.38 schwarze 286: static int warnings; /* Potential problems in manuals. */
1.1 kristaps 287:
288: int
289: main(int argc, char *argv[])
290: {
291: struct mparse *mp; /* parse sequence */
1.10 kristaps 292: struct manpaths dirs;
1.38 schwarze 293: struct mdb mdb;
294: struct recs recs;
1.1 kristaps 295: enum op op; /* current operation */
1.5 kristaps 296: const char *dir;
1.16 schwarze 297: int ch, i, flags;
1.38 schwarze 298: DB *hash; /* temporary keyword hashtable */
1.1 kristaps 299: BTREEINFO info; /* btree configuration */
1.38 schwarze 300: size_t sz1, sz2;
1.1 kristaps 301: struct buf buf, /* keyword buffer */
302: dbuf; /* description buffer */
1.5 kristaps 303: struct of *of; /* list of files for processing */
1.1 kristaps 304: extern int optind;
305: extern char *optarg;
306:
307: progname = strrchr(argv[0], '/');
308: if (progname == NULL)
309: progname = argv[0];
310: else
311: ++progname;
312:
1.10 kristaps 313: memset(&dirs, 0, sizeof(struct manpaths));
1.38 schwarze 314: memset(&mdb, 0, sizeof(struct mdb));
315: memset(&recs, 0, sizeof(struct recs));
1.10 kristaps 316:
1.4 kristaps 317: of = NULL;
1.1 kristaps 318: mp = NULL;
319: hash = NULL;
1.38 schwarze 320: op = OP_DEFAULT;
1.5 kristaps 321: dir = NULL;
1.1 kristaps 322:
1.38 schwarze 323: while (-1 != (ch = getopt(argc, argv, "aC:d:tu:vW")))
1.1 kristaps 324: switch (ch) {
1.12 schwarze 325: case ('a'):
326: use_all = 1;
327: break;
1.34 schwarze 328: case ('C'):
1.38 schwarze 329: if (op) {
330: fprintf(stderr,
331: "-C: conflicting options\n");
332: goto usage;
333: }
334: dir = optarg;
335: op = OP_CONFFILE;
1.34 schwarze 336: break;
1.5 kristaps 337: case ('d'):
1.38 schwarze 338: if (op) {
339: fprintf(stderr,
340: "-d: conflicting options\n");
341: goto usage;
342: }
1.5 kristaps 343: dir = optarg;
344: op = OP_UPDATE;
345: break;
1.38 schwarze 346: case ('t'):
347: dup2(STDOUT_FILENO, STDERR_FILENO);
348: if (op) {
349: fprintf(stderr,
350: "-t: conflicting options\n");
351: goto usage;
352: }
353: op = OP_TEST;
354: use_all = 1;
355: warnings = 1;
356: break;
1.5 kristaps 357: case ('u'):
1.38 schwarze 358: if (op) {
359: fprintf(stderr,
360: "-u: conflicting options\n");
361: goto usage;
362: }
1.5 kristaps 363: dir = optarg;
364: op = OP_DELETE;
365: break;
366: case ('v'):
367: verb++;
368: break;
1.38 schwarze 369: case ('W'):
370: warnings = 1;
371: break;
1.1 kristaps 372: default:
1.38 schwarze 373: goto usage;
1.1 kristaps 374: }
375:
376: argc -= optind;
377: argv += optind;
378:
1.38 schwarze 379: if (OP_CONFFILE == op && argc > 0) {
380: fprintf(stderr, "-C: too many arguments\n");
381: goto usage;
382: }
383:
1.4 kristaps 384: memset(&info, 0, sizeof(BTREEINFO));
1.44 ! kristaps 385: info.lorder = 4321;
1.4 kristaps 386: info.flags = R_DUP;
1.1 kristaps 387:
1.4 kristaps 388: mp = mparse_alloc(MPARSE_AUTO, MANDOCLEVEL_FATAL, NULL, NULL);
1.1 kristaps 389:
1.5 kristaps 390: memset(&buf, 0, sizeof(struct buf));
391: memset(&dbuf, 0, sizeof(struct buf));
1.1 kristaps 392:
1.4 kristaps 393: buf.size = dbuf.size = MANDOC_BUFSZ;
1.1 kristaps 394:
1.4 kristaps 395: buf.cp = mandoc_malloc(buf.size);
396: dbuf.cp = mandoc_malloc(dbuf.size);
1.1 kristaps 397:
1.38 schwarze 398: if (OP_TEST == op) {
1.44 ! kristaps 399: ofile_argbuild(argc, argv, &of, ".");
1.38 schwarze 400: if (NULL == of)
401: goto out;
1.44 ! kristaps 402: index_merge(of, mp, &dbuf, &buf,
! 403: hash, &mdb, &recs, ".");
1.38 schwarze 404: goto out;
405: }
1.5 kristaps 406:
407: if (OP_UPDATE == op || OP_DELETE == op) {
1.38 schwarze 408: strlcat(mdb.dbn, dir, MAXPATHLEN);
409: strlcat(mdb.dbn, "/", MAXPATHLEN);
410: sz1 = strlcat(mdb.dbn, MANDOC_DB, MAXPATHLEN);
411:
412: strlcat(mdb.idxn, dir, MAXPATHLEN);
413: strlcat(mdb.idxn, "/", MAXPATHLEN);
414: sz2 = strlcat(mdb.idxn, MANDOC_IDX, MAXPATHLEN);
1.5 kristaps 415:
416: if (sz1 >= MAXPATHLEN || sz2 >= MAXPATHLEN) {
1.38 schwarze 417: fprintf(stderr, "%s: path too long\n", dir);
1.5 kristaps 418: exit((int)MANDOCLEVEL_BADARG);
419: }
420:
1.44 ! kristaps 421: flags = O_CREAT | O_RDWR;
1.38 schwarze 422: mdb.db = dbopen(mdb.dbn, flags, 0644, DB_BTREE, &info);
423: mdb.idx = dbopen(mdb.idxn, flags, 0644, DB_RECNO, NULL);
1.5 kristaps 424:
1.38 schwarze 425: if (NULL == mdb.db) {
426: perror(mdb.dbn);
1.5 kristaps 427: exit((int)MANDOCLEVEL_SYSERR);
1.38 schwarze 428: } else if (NULL == mdb.idx) {
429: perror(mdb.idxn);
1.5 kristaps 430: exit((int)MANDOCLEVEL_SYSERR);
431: }
432:
1.44 ! kristaps 433: ofile_argbuild(argc, argv, &of, dir);
1.5 kristaps 434:
435: if (NULL == of)
436: goto out;
437:
1.44 ! kristaps 438: index_prune(of, &mdb, &recs, dir);
1.5 kristaps 439:
1.17 schwarze 440: /*
1.35 kristaps 441: * Go to the root of the respective manual tree.
442: * This must work or no manuals may be found (they're
443: * indexed relative to the root).
1.17 schwarze 444: */
445:
446: if (OP_UPDATE == op) {
1.35 kristaps 447: if (-1 == chdir(dir)) {
448: perror(dir);
449: exit((int)MANDOCLEVEL_SYSERR);
450: }
1.13 schwarze 451: index_merge(of, mp, &dbuf, &buf, hash,
1.44 ! kristaps 452: &mdb, &recs, dir);
1.17 schwarze 453: }
1.5 kristaps 454:
455: goto out;
456: }
457:
1.10 kristaps 458: /*
459: * Configure the directories we're going to scan.
460: * If we have command-line arguments, use them.
461: * If not, we use man(1)'s method (see mandocdb.8).
462: */
463:
464: if (argc > 0) {
1.26 kristaps 465: dirs.paths = mandoc_calloc(argc, sizeof(char *));
1.10 kristaps 466: dirs.sz = argc;
1.44 ! kristaps 467: for (i = 0; i < argc; i++)
! 468: dirs.paths[i] = mandoc_strdup(argv[i]);
1.10 kristaps 469: } else
1.38 schwarze 470: manpath_parse(&dirs, dir, NULL, NULL);
1.10 kristaps 471:
472: for (i = 0; i < dirs.sz; i++) {
1.44 ! kristaps 473: /*
! 474: * Go to the root of the respective manual tree.
! 475: * This must work or no manuals may be found:
! 476: * They are indexed relative to the root.
! 477: */
1.1 kristaps 478:
1.44 ! kristaps 479: if (-1 == chdir(dirs.paths[i])) {
! 480: perror(dirs.paths[i]);
! 481: exit((int)MANDOCLEVEL_SYSERR);
1.4 kristaps 482: }
1.3 kristaps 483:
1.44 ! kristaps 484: strlcpy(mdb.dbn, MANDOC_DB, MAXPATHLEN);
! 485: strlcpy(mdb.idxn, MANDOC_IDX, MAXPATHLEN);
1.13 schwarze 486:
1.44 ! kristaps 487: flags = O_CREAT | O_TRUNC | O_RDWR;
1.38 schwarze 488: mdb.db = dbopen(mdb.dbn, flags, 0644, DB_BTREE, &info);
489: mdb.idx = dbopen(mdb.idxn, flags, 0644, DB_RECNO, NULL);
1.3 kristaps 490:
1.38 schwarze 491: if (NULL == mdb.db) {
492: perror(mdb.dbn);
1.5 kristaps 493: exit((int)MANDOCLEVEL_SYSERR);
1.38 schwarze 494: } else if (NULL == mdb.idx) {
495: perror(mdb.idxn);
1.5 kristaps 496: exit((int)MANDOCLEVEL_SYSERR);
497: }
498:
1.44 ! kristaps 499: /*
! 500: * Search for manuals and fill the new database.
! 501: */
1.1 kristaps 502:
1.38 schwarze 503: ofile_dirbuild(".", "", "", 0, &of);
1.1 kristaps 504:
1.44 ! kristaps 505: if (NULL != of) {
! 506: index_merge(of, mp, &dbuf, &buf, hash,
! 507: &mdb, &recs, dirs.paths[i]);
! 508: ofile_free(of);
! 509: of = NULL;
1.35 kristaps 510: }
511:
1.44 ! kristaps 512: (*mdb.db->close)(mdb.db);
! 513: (*mdb.idx->close)(mdb.idx);
! 514: mdb.db = NULL;
! 515: mdb.idx = NULL;
1.4 kristaps 516: }
1.3 kristaps 517:
1.5 kristaps 518: out:
1.38 schwarze 519: if (mdb.db)
520: (*mdb.db->close)(mdb.db);
521: if (mdb.idx)
522: (*mdb.idx->close)(mdb.idx);
1.3 kristaps 523: if (hash)
524: (*hash->close)(hash);
525: if (mp)
526: mparse_free(mp);
527:
1.10 kristaps 528: manpath_free(&dirs);
1.4 kristaps 529: ofile_free(of);
1.3 kristaps 530: free(buf.cp);
531: free(dbuf.cp);
1.38 schwarze 532: free(recs.stack);
1.3 kristaps 533:
1.5 kristaps 534: return(MANDOCLEVEL_OK);
1.38 schwarze 535:
536: usage:
537: fprintf(stderr,
538: "usage: %s [-avvv] [-C file] | dir ... | -t file ...\n"
539: " -d dir [file ...] | "
540: "-u dir [file ...]\n",
541: progname);
542:
543: return((int)MANDOCLEVEL_BADARG);
1.3 kristaps 544: }
545:
546: void
547: index_merge(const struct of *of, struct mparse *mp,
1.16 schwarze 548: struct buf *dbuf, struct buf *buf, DB *hash,
1.44 ! kristaps 549: struct mdb *mdb, struct recs *recs,
! 550: const char *basedir)
1.3 kristaps 551: {
552: recno_t rec;
1.38 schwarze 553: int ch, skip;
1.3 kristaps 554: DBT key, val;
1.44 ! kristaps 555: DB *files; /* temporary file name table */
! 556: char emptystring[1] = {'\0'};
1.3 kristaps 557: struct mdoc *mdoc;
558: struct man *man;
1.44 ! kristaps 559: char *p;
1.38 schwarze 560: const char *fn, *msec, *march, *mtitle;
1.37 schwarze 561: uint64_t mask;
1.3 kristaps 562: size_t sv;
563: unsigned seq;
1.39 schwarze 564: uint64_t vbuf[2];
1.36 kristaps 565: char type;
1.3 kristaps 566:
1.44 ! kristaps 567: if (warnings) {
! 568: files = NULL;
! 569: hash_reset(&files);
! 570: }
! 571:
1.38 schwarze 572: rec = 0;
573: for (of = of->first; of; of = of->next) {
1.3 kristaps 574: fn = of->fname;
1.14 schwarze 575:
576: /*
1.33 schwarze 577: * Try interpreting the file as mdoc(7) or man(7)
578: * source code, unless it is already known to be
579: * formatted. Fall back to formatted mode.
1.14 schwarze 580: */
581:
1.1 kristaps 582: mparse_reset(mp);
1.14 schwarze 583: mdoc = NULL;
584: man = NULL;
1.1 kristaps 585:
1.14 schwarze 586: if ((MANDOC_SRC & of->src_form ||
587: ! (MANDOC_FORM & of->src_form)) &&
588: MANDOCLEVEL_FATAL > mparse_readfd(mp, -1, fn))
589: mparse_result(mp, &mdoc, &man);
590:
591: if (NULL != mdoc) {
592: msec = mdoc_meta(mdoc)->msec;
1.38 schwarze 593: march = mdoc_meta(mdoc)->arch;
594: if (NULL == march)
595: march = "";
1.14 schwarze 596: mtitle = mdoc_meta(mdoc)->title;
597: } else if (NULL != man) {
598: msec = man_meta(man)->msec;
1.38 schwarze 599: march = "";
1.14 schwarze 600: mtitle = man_meta(man)->title;
601: } else {
602: msec = of->sec;
1.38 schwarze 603: march = of->arch;
1.14 schwarze 604: mtitle = of->title;
1.1 kristaps 605: }
606:
1.12 schwarze 607: /*
1.44 ! kristaps 608: * Check whether the manual section given in a file
! 609: * agrees with the directory where the file is located.
! 610: * Some manuals have suffixes like (3p) on their
! 611: * section number either inside the file or in the
! 612: * directory name, some are linked into more than one
! 613: * section, like encrypt(1) = makekey(8). Do not skip
! 614: * manuals for such reasons.
1.12 schwarze 615: */
616:
1.38 schwarze 617: skip = 0;
618: assert(of->sec);
619: assert(msec);
1.44 ! kristaps 620: if (warnings && strcasecmp(msec, of->sec))
! 621: fprintf(stderr, "%s: section \"%s\" manual "
1.38 schwarze 622: "in \"%s\" directory\n",
623: fn, msec, of->sec);
1.44 ! kristaps 624:
1.38 schwarze 625:
1.42 schwarze 626: /*
627: * Manual page directories exist for each kernel
628: * architecture as returned by machine(1).
629: * However, many manuals only depend on the
630: * application architecture as returned by arch(1).
631: * For example, some (2/ARM) manuals are shared
632: * across the "armish" and "zaurus" kernel
633: * architectures.
634: * A few manuals are even shared across completely
635: * different architectures, for example fdformat(1)
636: * on amd64, i386, sparc, and sparc64.
637: * Thus, warn about architecture mismatches,
638: * but don't skip manuals for this reason.
639: */
640:
1.38 schwarze 641: assert(of->arch);
642: assert(march);
1.44 ! kristaps 643: if (warnings && strcasecmp(march, of->arch))
! 644: fprintf(stderr, "%s: architecture \"%s\" manual "
1.38 schwarze 645: "in \"%s\" directory\n",
646: fn, march, of->arch);
1.12 schwarze 647:
1.38 schwarze 648: /*
1.12 schwarze 649: * By default, skip a file if the title given
650: * in the file disagrees with the file name.
1.44 ! kristaps 651: * Do not warn, this happens for all MLINKs.
1.12 schwarze 652: */
653:
654: assert(of->title);
655: assert(mtitle);
1.44 ! kristaps 656: if (strcasecmp(mtitle, of->title))
1.38 schwarze 657: skip = 1;
1.44 ! kristaps 658:
! 659: /*
! 660: * Build a title string for the file. If it matches
! 661: * the location of the file, remember the title as
! 662: * found; else, remember it as missing.
! 663: */
! 664:
! 665: if (warnings) {
! 666: buf->len = 0;
! 667: buf_appendb(buf, mtitle, strlen(mtitle));
! 668: buf_appendb(buf, "(", 1);
! 669: buf_appendb(buf, msec, strlen(msec));
! 670: if ('\0' != *march) {
! 671: buf_appendb(buf, "/", 1);
! 672: buf_appendb(buf, march, strlen(march));
! 673: }
! 674: buf_appendb(buf, ")", 2);
! 675: for (p = buf->cp; '\0' != *p; p++)
! 676: *p = tolower(*p);
! 677: key.data = buf->cp;
! 678: key.size = buf->len;
! 679: val.data = NULL;
! 680: val.size = 0;
! 681: if (0 == skip)
! 682: val.data = emptystring;
! 683: else {
! 684: ch = (*files->get)(files, &key, &val, 0);
! 685: if (ch < 0) {
! 686: perror("hash");
! 687: exit((int)MANDOCLEVEL_SYSERR);
! 688: } else if (ch > 0) {
! 689: val.data = (void *)fn;
! 690: val.size = strlen(fn) + 1;
! 691: } else
! 692: val.data = NULL;
! 693: }
! 694: if (NULL != val.data &&
! 695: (*files->put)(files, &key, &val, 0) < 0) {
! 696: perror("hash");
! 697: exit((int)MANDOCLEVEL_SYSERR);
! 698: }
! 699: }
1.12 schwarze 700:
1.38 schwarze 701: if (skip && !use_all)
1.12 schwarze 702: continue;
703:
1.38 schwarze 704: /*
1.1 kristaps 705: * The index record value consists of a nil-terminated
706: * filename, a nil-terminated manual section, and a
1.44 ! kristaps 707: * nil-terminated description. Use the actual
! 708: * location of the file, such that the user can find
! 709: * it with man(1). Since the description may not be
! 710: * set, we set a sentinel to see if we're going to
! 711: * write a nil byte in its place.
1.1 kristaps 712: */
713:
1.3 kristaps 714: dbuf->len = 0;
1.36 kristaps 715: type = mdoc ? 'd' : (man ? 'a' : 'c');
716: buf_appendb(dbuf, &type, 1);
1.3 kristaps 717: buf_appendb(dbuf, fn, strlen(fn) + 1);
1.44 ! kristaps 718: buf_appendb(dbuf, of->sec, strlen(of->sec) + 1);
! 719: buf_appendb(dbuf, of->title, strlen(of->title) + 1);
! 720: buf_appendb(dbuf, of->arch, strlen(of->arch) + 1);
1.1 kristaps 721:
1.3 kristaps 722: sv = dbuf->len;
1.1 kristaps 723:
1.33 schwarze 724: /*
725: * Collect keyword/mask pairs.
726: * Each pair will become a new btree node.
727: */
1.1 kristaps 728:
1.33 schwarze 729: hash_reset(&hash);
1.1 kristaps 730: if (mdoc)
1.3 kristaps 731: pmdoc_node(hash, buf, dbuf,
1.1 kristaps 732: mdoc_node(mdoc), mdoc_meta(mdoc));
1.14 schwarze 733: else if (man)
1.3 kristaps 734: pman_node(hash, buf, dbuf, man_node(man));
1.14 schwarze 735: else
1.44 ! kristaps 736: pformatted(hash, buf, dbuf, of, basedir);
1.1 kristaps 737:
1.38 schwarze 738: /* Test mode, do not access any database. */
739:
740: if (NULL == mdb->db || NULL == mdb->idx)
741: continue;
742:
1.1 kristaps 743: /*
1.44 ! kristaps 744: * Make sure the file name is always registered
! 745: * as an .Nm search key.
! 746: */
! 747: buf->len = 0;
! 748: buf_append(buf, of->title);
! 749: hash_put(hash, buf, TYPE_Nm);
! 750:
! 751: /*
1.33 schwarze 752: * Reclaim an empty index record, if available.
753: * Use its record number for all new btree nodes.
1.1 kristaps 754: */
755:
1.38 schwarze 756: if (recs->cur > 0) {
757: recs->cur--;
758: rec = recs->stack[(int)recs->cur];
759: } else if (recs->last > 0) {
760: rec = recs->last;
761: recs->last = 0;
1.33 schwarze 762: } else
763: rec++;
1.39 schwarze 764: vbuf[1] = htobe64(rec);
1.33 schwarze 765:
766: /*
767: * Copy from the in-memory hashtable of pending
768: * keyword/mask pairs into the database.
769: */
770:
1.1 kristaps 771: seq = R_FIRST;
772: while (0 == (ch = (*hash->seq)(hash, &key, &val, seq))) {
773: seq = R_NEXT;
1.37 schwarze 774: assert(sizeof(uint64_t) == val.size);
775: memcpy(&mask, val.data, val.size);
1.39 schwarze 776: vbuf[0] = htobe64(mask);
777: val.size = sizeof(vbuf);
1.9 kristaps 778: val.data = &vbuf;
1.38 schwarze 779: dbt_put(mdb->db, mdb->dbn, &key, &val);
1.1 kristaps 780: }
781: if (ch < 0) {
782: perror("hash");
783: exit((int)MANDOCLEVEL_SYSERR);
784: }
1.38 schwarze 785:
1.1 kristaps 786: /*
787: * Apply to the index. If we haven't had a description
788: * set, put an empty one in now.
789: */
790:
1.3 kristaps 791: if (dbuf->len == sv)
792: buf_appendb(dbuf, "", 1);
1.1 kristaps 793:
794: key.data = &rec;
795: key.size = sizeof(recno_t);
796:
1.3 kristaps 797: val.data = dbuf->cp;
798: val.size = dbuf->len;
1.1 kristaps 799:
1.5 kristaps 800: if (verb)
1.44 ! kristaps 801: printf("%s: Adding to index: %s\n", basedir, fn);
1.18 kristaps 802:
1.38 schwarze 803: dbt_put(mdb->idx, mdb->idxn, &key, &val);
1.3 kristaps 804: }
1.44 ! kristaps 805:
! 806: /*
! 807: * Iterate the remembered file titles and check that
! 808: * all files can be found by their main title.
! 809: */
! 810:
! 811: if (warnings) {
! 812: seq = R_FIRST;
! 813: while (0 == (*files->seq)(files, &key, &val, seq)) {
! 814: seq = R_NEXT;
! 815: if (val.size)
! 816: fprintf(stderr, "%s: probably "
! 817: "unreachable, title is %s\n",
! 818: (char *)val.data, (char *)key.data);
! 819: }
! 820: (*files->close)(files);
! 821: }
1.3 kristaps 822: }
823:
824: /*
825: * Scan through all entries in the index file `idx' and prune those
826: * entries in `ofile'.
827: * Pruning consists of removing from `db', then invalidating the entry
828: * in `idx' (zeroing its value size).
829: */
830: static void
1.44 ! kristaps 831: index_prune(const struct of *ofile, struct mdb *mdb,
! 832: struct recs *recs, const char *basedir)
1.3 kristaps 833: {
834: const struct of *of;
1.36 kristaps 835: const char *fn;
1.39 schwarze 836: uint64_t vbuf[2];
1.3 kristaps 837: unsigned seq, sseq;
838: DBT key, val;
839: int ch;
840:
1.38 schwarze 841: recs->cur = 0;
1.3 kristaps 842: seq = R_FIRST;
1.38 schwarze 843: while (0 == (ch = (*mdb->idx->seq)(mdb->idx, &key, &val, seq))) {
1.3 kristaps 844: seq = R_NEXT;
1.37 schwarze 845: assert(sizeof(recno_t) == key.size);
1.38 schwarze 846: memcpy(&recs->last, key.data, key.size);
1.18 kristaps 847:
848: /* Deleted records are zero-sized. Skip them. */
849:
850: if (0 == val.size)
851: goto cont;
852:
853: /*
854: * Make sure we're sane.
855: * Read past our mdoc/man/cat type to the next string,
856: * then make sure it's bounded by a NUL.
857: * Failing any of these, we go into our error handler.
858: */
859:
1.36 kristaps 860: fn = (char *)val.data + 1;
861: if (NULL == memchr(fn, '\0', val.size - 1))
1.18 kristaps 862: break;
863:
1.38 schwarze 864: /*
1.18 kristaps 865: * Search for the file in those we care about.
866: * XXX: build this into a tree. Too slow.
867: */
1.3 kristaps 868:
1.38 schwarze 869: for (of = ofile->first; of; of = of->next)
1.3 kristaps 870: if (0 == strcmp(fn, of->fname))
871: break;
872:
873: if (NULL == of)
874: continue;
875:
1.18 kristaps 876: /*
877: * Search through the keyword database, throwing out all
878: * references to our file.
879: */
880:
1.3 kristaps 881: sseq = R_FIRST;
1.38 schwarze 882: while (0 == (ch = (*mdb->db->seq)(mdb->db,
883: &key, &val, sseq))) {
1.3 kristaps 884: sseq = R_NEXT;
1.39 schwarze 885: if (sizeof(vbuf) != val.size)
1.18 kristaps 886: break;
887:
1.39 schwarze 888: memcpy(vbuf, val.data, val.size);
889: if (recs->last != betoh64(vbuf[1]))
1.3 kristaps 890: continue;
1.18 kristaps 891:
1.38 schwarze 892: if ((ch = (*mdb->db->del)(mdb->db,
893: &key, R_CURSOR)) < 0)
1.3 kristaps 894: break;
895: }
1.18 kristaps 896:
1.3 kristaps 897: if (ch < 0) {
1.38 schwarze 898: perror(mdb->dbn);
1.3 kristaps 899: exit((int)MANDOCLEVEL_SYSERR);
1.18 kristaps 900: } else if (1 != ch) {
1.38 schwarze 901: fprintf(stderr, "%s: corrupt database\n",
902: mdb->dbn);
1.18 kristaps 903: exit((int)MANDOCLEVEL_SYSERR);
1.3 kristaps 904: }
1.1 kristaps 905:
1.5 kristaps 906: if (verb)
1.44 ! kristaps 907: printf("%s: Deleting from index: %s\n",
! 908: basedir, fn);
1.1 kristaps 909:
1.3 kristaps 910: val.size = 0;
1.38 schwarze 911: ch = (*mdb->idx->put)(mdb->idx, &key, &val, R_CURSOR);
1.1 kristaps 912:
1.18 kristaps 913: if (ch < 0)
914: break;
915: cont:
1.38 schwarze 916: if (recs->cur >= recs->size) {
917: recs->size += MANDOC_SLOP;
918: recs->stack = mandoc_realloc(recs->stack,
919: recs->size * sizeof(recno_t));
1.3 kristaps 920: }
1.1 kristaps 921:
1.38 schwarze 922: recs->stack[(int)recs->cur] = recs->last;
923: recs->cur++;
1.3 kristaps 924: }
1.18 kristaps 925:
926: if (ch < 0) {
1.38 schwarze 927: perror(mdb->idxn);
1.18 kristaps 928: exit((int)MANDOCLEVEL_SYSERR);
929: } else if (1 != ch) {
1.38 schwarze 930: fprintf(stderr, "%s: corrupt index\n", mdb->idxn);
1.18 kristaps 931: exit((int)MANDOCLEVEL_SYSERR);
932: }
933:
1.38 schwarze 934: recs->last++;
1.1 kristaps 935: }
936:
937: /*
938: * Grow the buffer (if necessary) and copy in a binary string.
939: */
940: static void
941: buf_appendb(struct buf *buf, const void *cp, size_t sz)
942: {
943:
944: /* Overshoot by MANDOC_BUFSZ. */
945:
946: while (buf->len + sz >= buf->size) {
947: buf->size = buf->len + sz + MANDOC_BUFSZ;
948: buf->cp = mandoc_realloc(buf->cp, buf->size);
949: }
950:
951: memcpy(buf->cp + (int)buf->len, cp, sz);
952: buf->len += sz;
953: }
954:
955: /*
956: * Append a nil-terminated string to the buffer.
957: * This can be invoked multiple times.
958: * The buffer string will be nil-terminated.
959: * If invoked multiple times, a space is put between strings.
960: */
961: static void
962: buf_append(struct buf *buf, const char *cp)
963: {
964: size_t sz;
965:
966: if (0 == (sz = strlen(cp)))
967: return;
968:
969: if (buf->len)
970: buf->cp[(int)buf->len - 1] = ' ';
971:
972: buf_appendb(buf, cp, sz + 1);
973: }
974:
975: /*
976: * Recursively add all text from a given node.
977: * This is optimised for general mdoc nodes in this context, which do
978: * not consist of subexpressions and having a recursive call for n->next
979: * would be wasteful.
980: * The "f" variable should be 0 unless called from pmdoc_Nd for the
981: * description buffer, which does not start at the beginning of the
982: * buffer.
983: */
984: static void
985: buf_appendmdoc(struct buf *buf, const struct mdoc_node *n, int f)
986: {
987:
988: for ( ; n; n = n->next) {
989: if (n->child)
990: buf_appendmdoc(buf, n->child, f);
991:
992: if (MDOC_TEXT == n->type && f) {
993: f = 0;
994: buf_appendb(buf, n->string,
995: strlen(n->string) + 1);
996: } else if (MDOC_TEXT == n->type)
997: buf_append(buf, n->string);
998:
999: }
1000: }
1001:
1002: static void
1003: hash_reset(DB **db)
1004: {
1005: DB *hash;
1006:
1007: if (NULL != (hash = *db))
1008: (*hash->close)(hash);
1009:
1.5 kristaps 1010: *db = dbopen(NULL, O_CREAT|O_RDWR, 0644, DB_HASH, NULL);
1.1 kristaps 1011: if (NULL == *db) {
1012: perror("hash");
1013: exit((int)MANDOCLEVEL_SYSERR);
1014: }
1015: }
1016:
1017: /* ARGSUSED */
1.25 schwarze 1018: static int
1019: pmdoc_head(MDOC_ARGS)
1020: {
1021:
1022: return(MDOC_HEAD == n->type);
1023: }
1024:
1025: /* ARGSUSED */
1026: static int
1027: pmdoc_body(MDOC_ARGS)
1028: {
1029:
1030: return(MDOC_BODY == n->type);
1031: }
1032:
1033: /* ARGSUSED */
1034: static int
1.1 kristaps 1035: pmdoc_Fd(MDOC_ARGS)
1036: {
1037: const char *start, *end;
1038: size_t sz;
1.25 schwarze 1039:
1.1 kristaps 1040: if (SEC_SYNOPSIS != n->sec)
1.25 schwarze 1041: return(0);
1.1 kristaps 1042: if (NULL == (n = n->child) || MDOC_TEXT != n->type)
1.25 schwarze 1043: return(0);
1.1 kristaps 1044:
1045: /*
1046: * Only consider those `Fd' macro fields that begin with an
1047: * "inclusion" token (versus, e.g., #define).
1048: */
1049: if (strcmp("#include", n->string))
1.25 schwarze 1050: return(0);
1.1 kristaps 1051:
1052: if (NULL == (n = n->next) || MDOC_TEXT != n->type)
1.25 schwarze 1053: return(0);
1.1 kristaps 1054:
1055: /*
1056: * Strip away the enclosing angle brackets and make sure we're
1057: * not zero-length.
1058: */
1059:
1060: start = n->string;
1061: if ('<' == *start || '"' == *start)
1062: start++;
1063:
1064: if (0 == (sz = strlen(start)))
1.25 schwarze 1065: return(0);
1.1 kristaps 1066:
1067: end = &start[(int)sz - 1];
1068: if ('>' == *end || '"' == *end)
1069: end--;
1070:
1071: assert(end >= start);
1072:
1073: buf_appendb(buf, start, (size_t)(end - start + 1));
1074: buf_appendb(buf, "", 1);
1.25 schwarze 1075: return(1);
1.1 kristaps 1076: }
1077:
1078: /* ARGSUSED */
1.25 schwarze 1079: static int
1080: pmdoc_In(MDOC_ARGS)
1.1 kristaps 1081: {
1082:
1083: if (NULL == n->child || MDOC_TEXT != n->child->type)
1.25 schwarze 1084: return(0);
1.1 kristaps 1085:
1086: buf_append(buf, n->child->string);
1.25 schwarze 1087: return(1);
1.1 kristaps 1088: }
1089:
1090: /* ARGSUSED */
1.25 schwarze 1091: static int
1.1 kristaps 1092: pmdoc_Fn(MDOC_ARGS)
1093: {
1.25 schwarze 1094: struct mdoc_node *nn;
1.1 kristaps 1095: const char *cp;
1096:
1.25 schwarze 1097: nn = n->child;
1098:
1099: if (NULL == nn || MDOC_TEXT != nn->type)
1100: return(0);
1101:
1102: /* .Fn "struct type *name" "char *arg" */
1.1 kristaps 1103:
1.25 schwarze 1104: cp = strrchr(nn->string, ' ');
1.1 kristaps 1105: if (NULL == cp)
1.25 schwarze 1106: cp = nn->string;
1.1 kristaps 1107:
1108: /* Strip away pointer symbol. */
1109:
1110: while ('*' == *cp)
1111: cp++;
1112:
1.25 schwarze 1113: /* Store the function name. */
1114:
1.1 kristaps 1115: buf_append(buf, cp);
1.8 schwarze 1116: hash_put(hash, buf, TYPE_Fn);
1.25 schwarze 1117:
1118: /* Store the function type. */
1119:
1120: if (nn->string < cp) {
1121: buf->len = 0;
1122: buf_appendb(buf, nn->string, cp - nn->string);
1123: buf_appendb(buf, "", 1);
1124: hash_put(hash, buf, TYPE_Ft);
1125: }
1126:
1127: /* Store the arguments. */
1128:
1129: for (nn = nn->next; nn; nn = nn->next) {
1130: if (MDOC_TEXT != nn->type)
1131: continue;
1132: buf->len = 0;
1133: buf_append(buf, nn->string);
1134: hash_put(hash, buf, TYPE_Fa);
1135: }
1136:
1137: return(0);
1.1 kristaps 1138: }
1139:
1140: /* ARGSUSED */
1.25 schwarze 1141: static int
1.1 kristaps 1142: pmdoc_St(MDOC_ARGS)
1143: {
1.25 schwarze 1144:
1.1 kristaps 1145: if (NULL == n->child || MDOC_TEXT != n->child->type)
1.25 schwarze 1146: return(0);
1.1 kristaps 1147:
1148: buf_append(buf, n->child->string);
1.25 schwarze 1149: return(1);
1.1 kristaps 1150: }
1151:
1152: /* ARGSUSED */
1.25 schwarze 1153: static int
1.1 kristaps 1154: pmdoc_Xr(MDOC_ARGS)
1155: {
1156:
1157: if (NULL == (n = n->child))
1.25 schwarze 1158: return(0);
1.1 kristaps 1159:
1160: buf_appendb(buf, n->string, strlen(n->string));
1161:
1162: if (NULL != (n = n->next)) {
1163: buf_appendb(buf, ".", 1);
1164: buf_appendb(buf, n->string, strlen(n->string) + 1);
1165: } else
1166: buf_appendb(buf, ".", 2);
1167:
1.25 schwarze 1168: return(1);
1.1 kristaps 1169: }
1170:
1171: /* ARGSUSED */
1.25 schwarze 1172: static int
1.1 kristaps 1173: pmdoc_Nd(MDOC_ARGS)
1174: {
1175:
1176: if (MDOC_BODY != n->type)
1.25 schwarze 1177: return(0);
1.1 kristaps 1178:
1179: buf_appendmdoc(dbuf, n->child, 1);
1.25 schwarze 1180: return(1);
1.1 kristaps 1181: }
1182:
1183: /* ARGSUSED */
1.25 schwarze 1184: static int
1185: pmdoc_Nm(MDOC_ARGS)
1.1 kristaps 1186: {
1187:
1.25 schwarze 1188: if (SEC_NAME == n->sec)
1189: return(1);
1190: else if (SEC_SYNOPSIS != n->sec || MDOC_HEAD != n->type)
1191: return(0);
1.1 kristaps 1192:
1.25 schwarze 1193: if (NULL == n->child)
1194: buf_append(buf, m->name);
1.1 kristaps 1195:
1.25 schwarze 1196: return(1);
1.1 kristaps 1197: }
1198:
1199: /* ARGSUSED */
1.25 schwarze 1200: static int
1201: pmdoc_Sh(MDOC_ARGS)
1.1 kristaps 1202: {
1203:
1.25 schwarze 1204: return(SEC_CUSTOM == n->sec && MDOC_HEAD == n->type);
1.1 kristaps 1205: }
1206:
1207: static void
1.9 kristaps 1208: hash_put(DB *db, const struct buf *buf, uint64_t mask)
1.1 kristaps 1209: {
1.37 schwarze 1210: uint64_t oldmask;
1.1 kristaps 1211: DBT key, val;
1212: int rc;
1213:
1214: if (buf->len < 2)
1215: return;
1216:
1217: key.data = buf->cp;
1218: key.size = buf->len;
1219:
1220: if ((rc = (*db->get)(db, &key, &val, 0)) < 0) {
1221: perror("hash");
1222: exit((int)MANDOCLEVEL_SYSERR);
1.37 schwarze 1223: } else if (0 == rc) {
1224: assert(sizeof(uint64_t) == val.size);
1225: memcpy(&oldmask, val.data, val.size);
1226: mask |= oldmask;
1227: }
1.1 kristaps 1228:
1229: val.data = &mask;
1.9 kristaps 1230: val.size = sizeof(uint64_t);
1.1 kristaps 1231:
1232: if ((rc = (*db->put)(db, &key, &val, 0)) < 0) {
1233: perror("hash");
1234: exit((int)MANDOCLEVEL_SYSERR);
1235: }
1236: }
1237:
1238: static void
1239: dbt_put(DB *db, const char *dbn, DBT *key, DBT *val)
1240: {
1241:
1242: assert(key->size);
1243: assert(val->size);
1244:
1245: if (0 == (*db->put)(db, key, val, 0))
1246: return;
1247:
1248: perror(dbn);
1249: exit((int)MANDOCLEVEL_SYSERR);
1250: /* NOTREACHED */
1251: }
1252:
1253: /*
1254: * Call out to per-macro handlers after clearing the persistent database
1255: * key. If the macro sets the database key, flush it to the database.
1256: */
1257: static void
1258: pmdoc_node(MDOC_ARGS)
1259: {
1260:
1261: if (NULL == n)
1262: return;
1263:
1264: switch (n->type) {
1265: case (MDOC_HEAD):
1266: /* FALLTHROUGH */
1267: case (MDOC_BODY):
1268: /* FALLTHROUGH */
1269: case (MDOC_TAIL):
1270: /* FALLTHROUGH */
1271: case (MDOC_BLOCK):
1272: /* FALLTHROUGH */
1273: case (MDOC_ELEM):
1.25 schwarze 1274: buf->len = 0;
1275:
1276: /*
1277: * Both NULL handlers and handlers returning true
1278: * request using the data. Only skip the element
1279: * when the handler returns false.
1280: */
1281:
1282: if (NULL != mdocs[n->tok].fp &&
1283: 0 == (*mdocs[n->tok].fp)(hash, buf, dbuf, n, m))
1.1 kristaps 1284: break;
1285:
1.25 schwarze 1286: /*
1287: * For many macros, use the text from all children.
1288: * Set zero flags for macros not needing this.
1289: * In that case, the handler must fill the buffer.
1290: */
1291:
1292: if (MDOCF_CHILD & mdocs[n->tok].flags)
1293: buf_appendmdoc(buf, n->child, 0);
1294:
1295: /*
1296: * Cover the most common case:
1297: * Automatically stage one string per element.
1298: * Set a zero mask for macros not needing this.
1299: * Additional staging can be done in the handler.
1300: */
1301:
1302: if (mdocs[n->tok].mask)
1303: hash_put(hash, buf, mdocs[n->tok].mask);
1.1 kristaps 1304: break;
1305: default:
1306: break;
1307: }
1308:
1309: pmdoc_node(hash, buf, dbuf, n->child, m);
1310: pmdoc_node(hash, buf, dbuf, n->next, m);
1311: }
1312:
1313: static int
1314: pman_node(MAN_ARGS)
1315: {
1316: const struct man_node *head, *body;
1317: const char *start, *sv;
1318: size_t sz;
1319:
1320: if (NULL == n)
1321: return(0);
1322:
1323: /*
1324: * We're only searching for one thing: the first text child in
1325: * the BODY of a NAME section. Since we don't keep track of
1326: * sections in -man, run some hoops to find out whether we're in
1327: * the correct section or not.
1328: */
1329:
1330: if (MAN_BODY == n->type && MAN_SH == n->tok) {
1331: body = n;
1332: assert(body->parent);
1333: if (NULL != (head = body->parent->head) &&
1334: 1 == head->nchild &&
1335: NULL != (head = (head->child)) &&
1336: MAN_TEXT == head->type &&
1337: 0 == strcmp(head->string, "NAME") &&
1338: NULL != (body = body->child) &&
1339: MAN_TEXT == body->type) {
1340:
1341: assert(body->string);
1342: start = sv = body->string;
1343:
1344: /*
1345: * Go through a special heuristic dance here.
1346: * This is why -man manuals are great!
1347: * (I'm being sarcastic: my eyes are bleeding.)
1348: * Conventionally, one or more manual names are
1349: * comma-specified prior to a whitespace, then a
1350: * dash, then a description. Try to puzzle out
1351: * the name parts here.
1352: */
1353:
1354: for ( ;; ) {
1355: sz = strcspn(start, " ,");
1356: if ('\0' == start[(int)sz])
1357: break;
1358:
1359: buf->len = 0;
1360: buf_appendb(buf, start, sz);
1361: buf_appendb(buf, "", 1);
1362:
1.8 schwarze 1363: hash_put(hash, buf, TYPE_Nm);
1.1 kristaps 1364:
1365: if (' ' == start[(int)sz]) {
1366: start += (int)sz + 1;
1367: break;
1368: }
1369:
1370: assert(',' == start[(int)sz]);
1371: start += (int)sz + 1;
1372: while (' ' == *start)
1373: start++;
1374: }
1375:
1376: buf->len = 0;
1377:
1378: if (sv == start) {
1379: buf_append(buf, start);
1380: return(1);
1381: }
1382:
1383: while (' ' == *start)
1384: start++;
1385:
1386: if (0 == strncmp(start, "-", 1))
1387: start += 1;
1.43 kristaps 1388: else if (0 == strncmp(start, "\\-\\-", 4))
1389: start += 4;
1.1 kristaps 1390: else if (0 == strncmp(start, "\\-", 2))
1391: start += 2;
1392: else if (0 == strncmp(start, "\\(en", 4))
1393: start += 4;
1394: else if (0 == strncmp(start, "\\(em", 4))
1395: start += 4;
1396:
1397: while (' ' == *start)
1398: start++;
1399:
1400: sz = strlen(start) + 1;
1401: buf_appendb(dbuf, start, sz);
1402: buf_appendb(buf, start, sz);
1403:
1.8 schwarze 1404: hash_put(hash, buf, TYPE_Nd);
1.1 kristaps 1405: }
1406: }
1407:
1.7 schwarze 1408: for (n = n->child; n; n = n->next)
1409: if (pman_node(hash, buf, dbuf, n))
1410: return(1);
1.1 kristaps 1411:
1412: return(0);
1413: }
1414:
1.14 schwarze 1415: /*
1416: * Parse a formatted manual page.
1417: * By necessity, this involves rather crude guesswork.
1418: */
1419: static void
1.44 ! kristaps 1420: pformatted(DB *hash, struct buf *buf, struct buf *dbuf,
! 1421: const struct of *of, const char *basedir)
1.14 schwarze 1422: {
1423: FILE *stream;
1.43 kristaps 1424: char *line, *p, *title;
1425: size_t len, plen, titlesz;
1.14 schwarze 1426:
1427: if (NULL == (stream = fopen(of->fname, "r"))) {
1.38 schwarze 1428: if (warnings)
1429: perror(of->fname);
1.14 schwarze 1430: return;
1431: }
1432:
1433: /*
1434: * Always use the title derived from the filename up front,
1435: * do not even try to find it in the file. This also makes
1436: * sure we don't end up with an orphan index record, even if
1437: * the file content turns out to be completely unintelligible.
1438: */
1439:
1440: buf->len = 0;
1441: buf_append(buf, of->title);
1442: hash_put(hash, buf, TYPE_Nm);
1443:
1.31 schwarze 1444: /* Skip to first blank line. */
1.14 schwarze 1445:
1.28 kristaps 1446: while (NULL != (line = fgetln(stream, &len)))
1.31 schwarze 1447: if ('\n' == *line)
1.28 kristaps 1448: break;
1449:
1.31 schwarze 1450: /*
1451: * Assume the first line that is not indented
1452: * is the first section header. Skip to it.
1.28 kristaps 1453: */
1454:
1455: while (NULL != (line = fgetln(stream, &len)))
1.31 schwarze 1456: if ('\n' != *line && ' ' != *line)
1.28 kristaps 1457: break;
1.43 kristaps 1458:
1459: /*
1460: * Read up until the next section into a buffer.
1461: * Strip the leading and trailing newline from each read line,
1462: * appending a trailing space.
1463: * Ignore empty (whitespace-only) lines.
1464: */
1465:
1466: titlesz = 0;
1467: title = NULL;
1468:
1469: while (NULL != (line = fgetln(stream, &len))) {
1470: if (' ' != *line || '\n' != line[(int)len - 1])
1471: break;
1472: while (len > 0 && isspace((unsigned char)*line)) {
1473: line++;
1474: len--;
1475: }
1476: if (1 == len)
1477: continue;
1478: title = mandoc_realloc(title, titlesz + len);
1479: memcpy(title + titlesz, line, len);
1480: titlesz += len;
1481: title[(int)titlesz - 1] = ' ';
1482: }
1483:
1.14 schwarze 1484:
1485: /*
1.31 schwarze 1486: * If no page content can be found, or the input line
1487: * is already the next section header, or there is no
1488: * trailing newline, reuse the page title as the page
1489: * description.
1.14 schwarze 1490: */
1491:
1.43 kristaps 1492: if (NULL == title || '\0' == *title) {
1.38 schwarze 1493: if (warnings)
1.44 ! kristaps 1494: fprintf(stderr, "%s: Cannot find NAME "
! 1495: "section: %s\n", basedir, of->fname);
1.14 schwarze 1496: buf_appendb(dbuf, buf->cp, buf->size);
1497: hash_put(hash, buf, TYPE_Nd);
1498: fclose(stream);
1.43 kristaps 1499: free(title);
1.14 schwarze 1500: return;
1501: }
1502:
1.43 kristaps 1503: title = mandoc_realloc(title, titlesz + 1);
1504: title[(int)titlesz] = '\0';
1.28 kristaps 1505:
1.31 schwarze 1506: /*
1507: * Skip to the first dash.
1.28 kristaps 1508: * Use the remaining line as the description (no more than 70
1509: * bytes).
1.14 schwarze 1510: */
1511:
1.43 kristaps 1512: if (NULL != (p = strstr(title, "- "))) {
1.30 kristaps 1513: for (p += 2; ' ' == *p || '\b' == *p; p++)
1.28 kristaps 1514: /* Skip to next word. */ ;
1.38 schwarze 1515: } else {
1516: if (warnings)
1.44 ! kristaps 1517: fprintf(stderr, "%s/%s: No dash in "
! 1518: "title line\n", basedir, of->fname);
1.43 kristaps 1519: p = title;
1.38 schwarze 1520: }
1.28 kristaps 1521:
1.43 kristaps 1522: plen = strlen(p);
1.29 kristaps 1523:
1524: /* Strip backspace-encoding from line. */
1525:
1526: while (NULL != (line = memchr(p, '\b', plen))) {
1527: len = line - p;
1528: if (0 == len) {
1529: memmove(line, line + 1, plen--);
1530: continue;
1531: }
1532: memmove(line - 1, line + 1, plen - len);
1533: plen -= 2;
1.14 schwarze 1534: }
1535:
1.28 kristaps 1536: buf_appendb(dbuf, p, plen + 1);
1.14 schwarze 1537: buf->len = 0;
1.28 kristaps 1538: buf_appendb(buf, p, plen + 1);
1.14 schwarze 1539: hash_put(hash, buf, TYPE_Nd);
1.28 kristaps 1540: fclose(stream);
1.43 kristaps 1541: free(title);
1.14 schwarze 1542: }
1543:
1.5 kristaps 1544: static void
1.44 ! kristaps 1545: ofile_argbuild(int argc, char *argv[],
! 1546: struct of **of, const char *basedir)
1.5 kristaps 1547: {
1.12 schwarze 1548: char buf[MAXPATHLEN];
1.41 kristaps 1549: const char *sec, *arch, *title;
1550: char *p;
1.14 schwarze 1551: int i, src_form;
1.5 kristaps 1552: struct of *nof;
1553:
1554: for (i = 0; i < argc; i++) {
1.12 schwarze 1555:
1556: /*
1557: * Try to infer the manual section, architecture and
1558: * page title from the path, assuming it looks like
1.14 schwarze 1559: * man*[/<arch>]/<title>.<section> or
1560: * cat<section>[/<arch>]/<title>.0
1.12 schwarze 1561: */
1562:
1563: if (strlcpy(buf, argv[i], sizeof(buf)) >= sizeof(buf)) {
1.44 ! kristaps 1564: fprintf(stderr, "%s: Path too long\n", argv[i]);
1.12 schwarze 1565: continue;
1566: }
1.38 schwarze 1567: sec = arch = title = "";
1.14 schwarze 1568: src_form = 0;
1.12 schwarze 1569: p = strrchr(buf, '\0');
1570: while (p-- > buf) {
1.38 schwarze 1571: if ('\0' == *sec && '.' == *p) {
1.12 schwarze 1572: sec = p + 1;
1573: *p = '\0';
1.14 schwarze 1574: if ('0' == *sec)
1575: src_form |= MANDOC_FORM;
1576: else if ('1' <= *sec && '9' >= *sec)
1577: src_form |= MANDOC_SRC;
1.12 schwarze 1578: continue;
1579: }
1580: if ('/' != *p)
1581: continue;
1.38 schwarze 1582: if ('\0' == *title) {
1.12 schwarze 1583: title = p + 1;
1584: *p = '\0';
1585: continue;
1586: }
1.24 schwarze 1587: if (0 == strncmp("man", p + 1, 3))
1.14 schwarze 1588: src_form |= MANDOC_SRC;
1.24 schwarze 1589: else if (0 == strncmp("cat", p + 1, 3))
1.14 schwarze 1590: src_form |= MANDOC_FORM;
1.24 schwarze 1591: else
1.12 schwarze 1592: arch = p + 1;
1593: break;
1594: }
1.38 schwarze 1595: if ('\0' == *title) {
1596: if (warnings)
1.44 ! kristaps 1597: fprintf(stderr, "%s/%s: Cannot deduce "
! 1598: "title from filename\n",
! 1599: basedir, argv[i]);
1.12 schwarze 1600: title = buf;
1.38 schwarze 1601: }
1.12 schwarze 1602:
1603: /*
1604: * Build the file structure.
1605: */
1606:
1.5 kristaps 1607: nof = mandoc_calloc(1, sizeof(struct of));
1.12 schwarze 1608: nof->fname = mandoc_strdup(argv[i]);
1.38 schwarze 1609: nof->sec = mandoc_strdup(sec);
1610: nof->arch = mandoc_strdup(arch);
1.12 schwarze 1611: nof->title = mandoc_strdup(title);
1.14 schwarze 1612: nof->src_form = src_form;
1.12 schwarze 1613:
1614: /*
1615: * Add the structure to the list.
1616: */
1617:
1.5 kristaps 1618: if (NULL == *of) {
1619: *of = nof;
1620: (*of)->first = nof;
1621: } else {
1622: nof->first = (*of)->first;
1623: (*of)->next = nof;
1624: *of = nof;
1625: }
1626: }
1627: }
1628:
1.4 kristaps 1629: /*
1630: * Recursively build up a list of files to parse.
1631: * We use this instead of ftw() and so on because I don't want global
1632: * variables hanging around.
1.44 ! kristaps 1633: * This ignores the mandocdb.db and mandocdb.index files, but assumes that
1.4 kristaps 1634: * everything else is a manual.
1635: * Pass in a pointer to a NULL structure for the first invocation.
1636: */
1.35 kristaps 1637: static void
1.12 schwarze 1638: ofile_dirbuild(const char *dir, const char* psec, const char *parch,
1.16 schwarze 1639: int p_src_form, struct of **of)
1.4 kristaps 1640: {
1.5 kristaps 1641: char buf[MAXPATHLEN];
1642: size_t sz;
1.4 kristaps 1643: DIR *d;
1.12 schwarze 1644: const char *fn, *sec, *arch;
1.14 schwarze 1645: char *p, *q, *suffix;
1.4 kristaps 1646: struct of *nof;
1647: struct dirent *dp;
1.14 schwarze 1648: int src_form;
1.4 kristaps 1649:
1650: if (NULL == (d = opendir(dir))) {
1.38 schwarze 1651: if (warnings)
1652: perror(dir);
1653: return;
1.4 kristaps 1654: }
1655:
1656: while (NULL != (dp = readdir(d))) {
1657: fn = dp->d_name;
1.12 schwarze 1658:
1659: if ('.' == *fn)
1660: continue;
1661:
1.14 schwarze 1662: src_form = p_src_form;
1663:
1.4 kristaps 1664: if (DT_DIR == dp->d_type) {
1.12 schwarze 1665: sec = psec;
1666: arch = parch;
1667:
1668: /*
1669: * By default, only use directories called:
1.14 schwarze 1670: * man<section>/[<arch>/] or
1671: * cat<section>/[<arch>/]
1.12 schwarze 1672: */
1673:
1.38 schwarze 1674: if ('\0' == *sec) {
1.14 schwarze 1675: if(0 == strncmp("man", fn, 3)) {
1676: src_form |= MANDOC_SRC;
1.12 schwarze 1677: sec = fn + 3;
1.14 schwarze 1678: } else if (0 == strncmp("cat", fn, 3)) {
1679: src_form |= MANDOC_FORM;
1680: sec = fn + 3;
1.38 schwarze 1681: } else {
1682: if (warnings) fprintf(stderr,
1683: "%s/%s: bad section\n",
1684: dir, fn);
1685: if (use_all)
1686: sec = fn;
1687: else
1688: continue;
1689: }
1690: } else if ('\0' == *arch) {
1691: if (NULL != strchr(fn, '.')) {
1692: if (warnings) fprintf(stderr,
1693: "%s/%s: bad architecture\n",
1694: dir, fn);
1695: if (0 == use_all)
1696: continue;
1697: }
1698: arch = fn;
1699: } else {
1700: if (warnings) fprintf(stderr, "%s/%s: "
1701: "excessive subdirectory\n", dir, fn);
1702: if (0 == use_all)
1.12 schwarze 1703: continue;
1.38 schwarze 1704: }
1.5 kristaps 1705:
1706: buf[0] = '\0';
1707: strlcat(buf, dir, MAXPATHLEN);
1708: strlcat(buf, "/", MAXPATHLEN);
1709: sz = strlcat(buf, fn, MAXPATHLEN);
1710:
1.12 schwarze 1711: if (MAXPATHLEN <= sz) {
1.38 schwarze 1712: if (warnings) fprintf(stderr, "%s/%s: "
1713: "path too long\n", dir, fn);
1714: continue;
1.12 schwarze 1715: }
1.38 schwarze 1716:
1.35 kristaps 1717: ofile_dirbuild(buf, sec, arch, src_form, of);
1.38 schwarze 1718: continue;
1.35 kristaps 1719: }
1.12 schwarze 1720:
1.38 schwarze 1721: if (DT_REG != dp->d_type) {
1722: if (warnings)
1723: fprintf(stderr,
1724: "%s/%s: not a regular file\n",
1725: dir, fn);
1726: continue;
1727: }
1728: if (!strcmp(MANDOC_DB, fn) || !strcmp(MANDOC_IDX, fn))
1.12 schwarze 1729: continue;
1.38 schwarze 1730: if ('\0' == *psec) {
1731: if (warnings)
1732: fprintf(stderr,
1733: "%s/%s: file outside section\n",
1734: dir, fn);
1735: if (0 == use_all)
1736: continue;
1737: }
1.12 schwarze 1738:
1739: /*
1740: * By default, skip files where the file name suffix
1741: * does not agree with the section directory
1742: * they are located in.
1743: */
1744:
1745: suffix = strrchr(fn, '.');
1.38 schwarze 1746: if (NULL == suffix) {
1747: if (warnings)
1748: fprintf(stderr,
1749: "%s/%s: no filename suffix\n",
1750: dir, fn);
1751: if (0 == use_all)
1.5 kristaps 1752: continue;
1.38 schwarze 1753: } else if ((MANDOC_SRC & src_form &&
1754: strcmp(suffix + 1, psec)) ||
1.14 schwarze 1755: (MANDOC_FORM & src_form &&
1.38 schwarze 1756: strcmp(suffix + 1, "0"))) {
1757: if (warnings)
1758: fprintf(stderr,
1759: "%s/%s: wrong filename suffix\n",
1760: dir, fn);
1761: if (0 == use_all)
1762: continue;
1.14 schwarze 1763: if ('0' == suffix[1])
1764: src_form |= MANDOC_FORM;
1765: else if ('1' <= suffix[1] && '9' >= suffix[1])
1766: src_form |= MANDOC_SRC;
1767: }
1768:
1769: /*
1770: * Skip formatted manuals if a source version is
1771: * available. Ignore the age: it is very unlikely
1772: * that people install newer formatted base manuals
1773: * when they used to have source manuals before,
1774: * and in ports, old manuals get removed on update.
1775: */
1776: if (0 == use_all && MANDOC_FORM & src_form &&
1.38 schwarze 1777: '\0' != *psec) {
1.14 schwarze 1778: buf[0] = '\0';
1779: strlcat(buf, dir, MAXPATHLEN);
1780: p = strrchr(buf, '/');
1.38 schwarze 1781: if ('\0' != *parch && NULL != p)
1.32 schwarze 1782: for (p--; p > buf; p--)
1783: if ('/' == *p)
1784: break;
1.14 schwarze 1785: if (NULL == p)
1786: p = buf;
1787: else
1788: p++;
1789: if (0 == strncmp("cat", p, 3))
1790: memcpy(p, "man", 3);
1791: strlcat(buf, "/", MAXPATHLEN);
1792: sz = strlcat(buf, fn, MAXPATHLEN);
1793: if (sz >= MAXPATHLEN) {
1.38 schwarze 1794: if (warnings) fprintf(stderr,
1795: "%s/%s: path too long\n",
1796: dir, fn);
1.5 kristaps 1797: continue;
1.14 schwarze 1798: }
1799: q = strrchr(buf, '.');
1800: if (NULL != q && p < q++) {
1801: *q = '\0';
1802: sz = strlcat(buf, psec, MAXPATHLEN);
1803: if (sz >= MAXPATHLEN) {
1.38 schwarze 1804: if (warnings) fprintf(stderr,
1805: "%s/%s: path too long\n",
1806: dir, fn);
1.14 schwarze 1807: continue;
1808: }
1.35 kristaps 1809: if (0 == access(buf, R_OK))
1.14 schwarze 1810: continue;
1811: }
1.5 kristaps 1812: }
1.4 kristaps 1813:
1.38 schwarze 1814: buf[0] = '\0';
1.35 kristaps 1815: assert('.' == dir[0]);
1.38 schwarze 1816: if ('/' == dir[1]) {
1817: strlcat(buf, dir + 2, MAXPATHLEN);
1818: strlcat(buf, "/", MAXPATHLEN);
1819: }
1.6 schwarze 1820: sz = strlcat(buf, fn, MAXPATHLEN);
1.5 kristaps 1821: if (sz >= MAXPATHLEN) {
1.38 schwarze 1822: if (warnings) fprintf(stderr,
1823: "%s/%s: path too long\n", dir, fn);
1.14 schwarze 1824: continue;
1.5 kristaps 1825: }
1826:
1.4 kristaps 1827: nof = mandoc_calloc(1, sizeof(struct of));
1.5 kristaps 1828: nof->fname = mandoc_strdup(buf);
1.38 schwarze 1829: nof->sec = mandoc_strdup(psec);
1830: nof->arch = mandoc_strdup(parch);
1.14 schwarze 1831: nof->src_form = src_form;
1.12 schwarze 1832:
1833: /*
1834: * Remember the file name without the extension,
1835: * to be used as the page title in the database.
1836: */
1837:
1838: if (NULL != suffix)
1839: *suffix = '\0';
1840: nof->title = mandoc_strdup(fn);
1.5 kristaps 1841:
1.14 schwarze 1842: /*
1843: * Add the structure to the list.
1844: */
1.41 kristaps 1845:
1.4 kristaps 1846: if (NULL == *of) {
1847: *of = nof;
1848: (*of)->first = nof;
1849: } else {
1.5 kristaps 1850: nof->first = (*of)->first;
1.4 kristaps 1851: (*of)->next = nof;
1852: *of = nof;
1853: }
1854: }
1855:
1.7 schwarze 1856: closedir(d);
1.4 kristaps 1857: }
1858:
1859: static void
1860: ofile_free(struct of *of)
1861: {
1862: struct of *nof;
1863:
1.41 kristaps 1864: if (NULL != of)
1865: of = of->first;
1866:
1867: while (NULL != of) {
1.4 kristaps 1868: nof = of->next;
1869: free(of->fname);
1.12 schwarze 1870: free(of->sec);
1871: free(of->arch);
1872: free(of->title);
1.4 kristaps 1873: free(of);
1874: of = nof;
1875: }
1.1 kristaps 1876: }
CVSweb