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