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