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