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