makewhatis.c revision 1.46 1 /* $NetBSD: makewhatis.c,v 1.46 2008/11/16 06:26:12 dholland Exp $ */
2
3 /*-
4 * Copyright (c) 1999 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Matthias Scheler.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #if HAVE_NBTOOL_CONFIG_H
33 #include "nbtool_config.h"
34 #endif
35
36 #include <sys/cdefs.h>
37 #if !defined(lint)
38 __COPYRIGHT("@(#) Copyright (c) 1999\
39 The NetBSD Foundation, Inc. All rights reserved.");
40 __RCSID("$NetBSD: makewhatis.c,v 1.46 2008/11/16 06:26:12 dholland Exp $");
41 #endif /* not lint */
42
43 #include <sys/types.h>
44 #include <sys/param.h>
45 #include <sys/queue.h>
46 #include <sys/stat.h>
47 #include <sys/wait.h>
48
49 #include <ctype.h>
50 #include <err.h>
51 #include <errno.h>
52 #include <fcntl.h>
53 #include <fts.h>
54 #include <glob.h>
55 #include <locale.h>
56 #include <paths.h>
57 #include <signal.h>
58 #include <stdio.h>
59 #include <stdlib.h>
60 #include <string.h>
61 #include <unistd.h>
62 #include <zlib.h>
63 #include <util.h>
64
65 #include <man/manconf.h>
66 #include <man/pathnames.h>
67
68 #ifndef NROFF
69 #define NROFF "nroff"
70 #endif
71
72 typedef struct manpagestruct manpage;
73 struct manpagestruct {
74 manpage *mp_left, *mp_right;
75 ino_t mp_inode;
76 size_t mp_sdoff;
77 size_t mp_sdlen;
78 char mp_name[1];
79 };
80
81 typedef struct whatisstruct whatis;
82 struct whatisstruct {
83 whatis *wi_left, *wi_right;
84 char *wi_data;
85 char wi_prefix[1];
86 };
87
88 int main(int, char * const *);
89 static char *findwhitespace(char *);
90 static char *strmove(char *, char *);
91 static char *GetS(gzFile, char *, size_t);
92 static int pathnamesection(const char *, const char *);
93 static int manpagesection(char *);
94 static char *createsectionstring(char *);
95 static void addmanpage(manpage **, ino_t, char *, size_t, size_t);
96 static void addwhatis(whatis **, char *, char *);
97 static char *makesection(int);
98 static char *makewhatisline(const char *, const char *, const char *);
99 static void catpreprocess(char *);
100 static char *parsecatpage(const char *, gzFile *);
101 static int manpreprocess(char *);
102 static char *nroff(const char *, gzFile *);
103 static char *parsemanpage(const char *, gzFile *, int);
104 static char *getwhatisdata(char *);
105 static void processmanpages(manpage **, whatis **);
106 static void dumpwhatis(FILE *, whatis *);
107 static int makewhatis(char * const *manpath);
108
109 static char * const default_manpath[] = {
110 "/usr/share/man",
111 NULL
112 };
113
114 static const char *sectionext = "0123456789ln";
115 static const char *whatisdb = _PATH_WHATIS;
116 static const char *whatisdb_new = _PATH_WHATIS ".new";
117 static int dowarn = 0;
118
119 #define ISALPHA(c) isalpha((unsigned char)(c))
120 #define ISDIGIT(c) isdigit((unsigned char)(c))
121 #define ISSPACE(c) isspace((unsigned char)(c))
122
123 int
124 main(int argc, char *const *argv)
125 {
126 char * const *manpath;
127 int c, dofork;
128 const char *conffile;
129 ENTRY *ep;
130 TAG *tp;
131 int rv, jobs, status;
132 glob_t pg;
133 char *paths[2], **p, *sl;
134 int retval;
135
136 dofork = 1;
137 conffile = NULL;
138 jobs = 0;
139 retval = EXIT_SUCCESS;
140
141 (void)setlocale(LC_ALL, "");
142
143 while ((c = getopt(argc, argv, "C:fw")) != -1) {
144 switch (c) {
145 case 'C':
146 conffile = optarg;
147 break;
148 case 'f':
149 /* run all processing on foreground */
150 dofork = 0;
151 break;
152 case 'w':
153 dowarn++;
154 break;
155 default:
156 fprintf(stderr, "Usage: %s [-fw] [-C file] [manpath ...]\n",
157 getprogname());
158 exit(EXIT_FAILURE);
159 }
160 }
161 argc -= optind;
162 argv += optind;
163
164 if (argc >= 1) {
165 manpath = &argv[0];
166
167 mkwhatis:
168 return makewhatis(manpath);
169 }
170
171 /*
172 * Try read config file, fallback to default_manpath[]
173 * if man.conf not available.
174 */
175 config(conffile);
176 if ((tp = gettag("_whatdb", 0)) == NULL) {
177 manpath = default_manpath;
178 goto mkwhatis;
179 }
180
181 /* Build individual databases */
182 paths[1] = NULL;
183 TAILQ_FOREACH(ep, &tp->entrylist, q) {
184 if ((rv = glob(ep->s,
185 GLOB_BRACE | GLOB_NOSORT | GLOB_ERR | GLOB_NOCHECK,
186 NULL, &pg)) != 0)
187 err(EXIT_FAILURE, "glob('%s')", ep->s);
188
189 /* We always have something to work with here */
190 for (p = pg.gl_pathv; *p; p++) {
191 sl = strrchr(*p, '/');
192 if (sl == NULL) {
193 err(EXIT_FAILURE, "glob: _whatdb entry '%s' "
194 "doesn't contain slash", ep->s);
195 }
196
197 /*
198 * Cut the last component of path, leaving just
199 * the directory. We will use the result as root
200 * for manpage search.
201 * glob malloc()s space for the paths, so it's
202 * okay to change it in-place.
203 */
204 *sl = '\0';
205 paths[0] = *p;
206
207 if (!dofork) {
208 /* Do not fork child */
209 makewhatis(paths);
210 continue;
211 }
212
213 switch (fork()) {
214 case 0:
215 exit(makewhatis(paths));
216 break;
217 case -1:
218 warn("fork");
219 makewhatis(paths);
220 break;
221 default:
222 jobs++;
223 break;
224 }
225
226 }
227
228 globfree(&pg);
229 }
230
231 /* Wait for the childern to finish */
232 while (jobs > 0) {
233 (void)wait(&status);
234 if (!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS)
235 retval = EXIT_FAILURE;
236 jobs--;
237 }
238
239 return retval;
240 }
241
242 static int
243 makewhatis(char * const * manpath)
244 {
245 FTS *fts;
246 FTSENT *fe;
247 manpage *source;
248 whatis *dest;
249 FILE *out;
250 size_t sdoff, sdlen;
251 int outfd;
252 struct stat st_before, st_after;
253
254 if ((fts = fts_open(manpath, FTS_LOGICAL, NULL)) == NULL)
255 err(EXIT_FAILURE, "Cannot open `%s'", *manpath);
256
257 source = NULL;
258 while ((fe = fts_read(fts)) != NULL) {
259 switch (fe->fts_info) {
260 case FTS_F:
261 if (manpagesection(fe->fts_path) >= 0) {
262 /*
263 * Get manpage subdirectory prefix. Most
264 * commonly, this is arch-specific subdirectory.
265 */
266 if (fe->fts_level >= 3) {
267 int sl;
268 const char *s, *lsl;
269
270 lsl = NULL;
271 s = &fe->fts_path[fe->fts_pathlen - 1];
272 for(sl = fe->fts_level - 1; sl > 0;
273 sl--) {
274 s--;
275 while (s[0] != '/')
276 s--;
277 if (lsl == NULL)
278 lsl = s;
279 }
280
281 /*
282 * Include trailing '/', so we get
283 * 'arch/'.
284 */
285 sdoff = s + 1 - fe->fts_path;
286 sdlen = lsl - s + 1;
287 } else {
288 sdoff = 0;
289 sdlen = 0;
290 }
291
292 addmanpage(&source, fe->fts_statp->st_ino,
293 fe->fts_path, sdoff, sdlen);
294 }
295 /*FALLTHROUGH*/
296 case FTS_D:
297 case FTS_DC:
298 case FTS_DEFAULT:
299 case FTS_DP:
300 case FTS_SL:
301 case FTS_DOT:
302 case FTS_W:
303 case FTS_NSOK:
304 case FTS_INIT:
305 break;
306 case FTS_SLNONE:
307 warnx("Symbolic link with no target: `%s'",
308 fe->fts_path);
309 break;
310 case FTS_DNR:
311 warnx("Unreadable directory: `%s'", fe->fts_path);
312 break;
313 case FTS_NS:
314 errno = fe->fts_errno;
315 warn("Cannot stat `%s'", fe->fts_path);
316 break;
317 case FTS_ERR:
318 errno = fe->fts_errno;
319 warn("Error reading `%s'", fe->fts_path);
320 break;
321 default:
322 errx(EXIT_FAILURE, "Unknown info %d returned from fts "
323 " for path: `%s'", fe->fts_info, fe->fts_path);
324 }
325 }
326
327 (void)fts_close(fts);
328
329 dest = NULL;
330 processmanpages(&source, &dest);
331
332 if (chdir(manpath[0]) == -1)
333 err(EXIT_FAILURE, "Cannot change dir to `%s'", manpath[0]);
334
335 /*
336 * makewhatis runs unattended, so it needs to be able to
337 * recover if the last run crashed out. Therefore, if
338 * whatisdb_new exists and is more than (arbitrarily) sixteen
339 * hours old, nuke it. If it exists but is not so old, refuse
340 * to run until it's cleaned up, in case another makewhatis is
341 * already running. Also, open the output with O_EXCL to make
342 * sure we get our own, in case two copies start exactly at
343 * once. (Unlikely? Maybe, maybe not, if two copies of cron
344 * end up running.)
345 *
346 * Similarly, before renaming the file after we finish writing
347 * to it, make sure it's still the same file we opened. This
348 * can't be completely race-free, but getting caught by it
349 * would require an unexplained sixteen-hour-or-more lag
350 * between the last mtime update when we wrote to it and when
351 * we get to the stat call *and* another makewhatis starting
352 * out to write at exactly the wrong moment. Not impossible,
353 * but not likely enough to worry about.
354 *
355 * This is maybe unnecessarily elaborate, but generating
356 * corrupted output isn't so good either.
357 */
358
359 if (stat(whatisdb_new, &st_before) == 0) {
360 if (st_before.st_mtime - time(NULL) > 16*60*60) {
361 /* Don't complain if someone else just removed it. */
362 if (unlink(whatisdb_new) == -1 && errno != ENOENT) {
363 err(EXIT_FAILURE, "Could not remove `%s'",
364 whatisdb_new);
365 } else {
366 warnx("Removed stale `%s'", whatisdb_new);
367 }
368 } else {
369 errx(EXIT_FAILURE, "The file `%s' already exists "
370 "-- am I already running?", whatisdb_new);
371 }
372 } else if (errno != ENOENT) {
373 /* Something unexpected happened. */
374 err(EXIT_FAILURE, "Cannot stat `%s'", whatisdb_new);
375 }
376
377 outfd = open(whatisdb_new, O_WRONLY|O_CREAT|O_EXCL,
378 S_IRUSR|S_IRGRP|S_IROTH);
379 if (outfd < 0)
380 err(EXIT_FAILURE, "Cannot open `%s'", whatisdb_new);
381
382 if (fstat(outfd, &st_before) == -1)
383 err(EXIT_FAILURE, "Cannot fstat `%s'", whatisdb_new);
384
385 if ((out = fdopen(outfd, "w")) == NULL)
386 err(EXIT_FAILURE, "Cannot fdopen `%s'", whatisdb_new);
387
388 dumpwhatis(out, dest);
389 if (fchmod(fileno(out), S_IRUSR|S_IRGRP|S_IROTH) == -1)
390 err(EXIT_FAILURE, "Cannot chmod `%s'", whatisdb_new);
391 if (fclose(out) != 0)
392 err(EXIT_FAILURE, "Cannot close `%s'", whatisdb_new);
393
394 if (stat(whatisdb_new, &st_after) == -1)
395 err(EXIT_FAILURE, "Cannot stat `%s' (after writing)",
396 whatisdb_new);
397
398 if (st_before.st_dev != st_after.st_dev ||
399 st_before.st_ino != st_after.st_ino) {
400 errx(EXIT_FAILURE, "The file `%s' changed under me; giving up",
401 whatisdb_new);
402 }
403
404 if (rename(whatisdb_new, whatisdb) == -1)
405 err(EXIT_FAILURE, "Could not rename `%s' to `%s'",
406 whatisdb_new, whatisdb);
407
408 return EXIT_SUCCESS;
409 }
410
411 static char *
412 findwhitespace(char *str)
413 {
414 while (!ISSPACE(*str))
415 if (*str++ == '\0') {
416 str = NULL;
417 break;
418 }
419
420 return str;
421 }
422
423 static char *
424 strmove(char *dest, char *src)
425 {
426 return memmove(dest, src, strlen(src) + 1);
427 }
428
429 static char *
430 GetS(gzFile in, char *buffer, size_t length)
431 {
432 char *ptr;
433
434 if (((ptr = gzgets(in, buffer, (int)length)) != NULL) && (*ptr == '\0'))
435 ptr = NULL;
436
437 return ptr;
438 }
439
440 static char *
441 makesection(int s)
442 {
443 char sectionbuffer[24];
444 if (s == -1)
445 return NULL;
446 (void)snprintf(sectionbuffer, sizeof(sectionbuffer),
447 " (%c) - ", sectionext[s]);
448 return estrdup(sectionbuffer);
449 }
450
451 static int
452 pathnamesection(const char *pat, const char *name)
453 {
454 char *ptr, *ext;
455 size_t len = strlen(pat);
456
457
458 while ((ptr = strstr(name, pat)) != NULL) {
459 if ((ext = strchr(sectionext, ptr[len])) != NULL) {
460 return ext - sectionext;
461 }
462 name = ptr + 1;
463 }
464 return -1;
465 }
466
467
468 static int
469 manpagesection(char *name)
470 {
471 char *ptr;
472
473 if ((ptr = strrchr(name, '/')) != NULL)
474 ptr++;
475 else
476 ptr = name;
477
478 while ((ptr = strchr(ptr, '.')) != NULL) {
479 int section;
480
481 ptr++;
482 section = 0;
483 while (sectionext[section] != '\0')
484 if (sectionext[section] == *ptr)
485 return section;
486 else
487 section++;
488 }
489 return -1;
490 }
491
492 static char *
493 createsectionstring(char *section_id)
494 {
495 char *section;
496
497 if (asprintf(§ion, " (%s) - ", section_id) < 0)
498 err(EXIT_FAILURE, "malloc failed");
499 return section;
500 }
501
502 static void
503 addmanpage(manpage **tree, ino_t inode, char *name, size_t sdoff, size_t sdlen)
504 {
505 manpage *mp;
506
507 while ((mp = *tree) != NULL) {
508 if (mp->mp_inode == inode)
509 return;
510 tree = inode < mp->mp_inode ? &mp->mp_left : &mp->mp_right;
511 }
512
513 mp = emalloc(sizeof(manpage) + strlen(name));
514 mp->mp_left = NULL;
515 mp->mp_right = NULL;
516 mp->mp_inode = inode;
517 mp->mp_sdoff = sdoff;
518 mp->mp_sdlen = sdlen;
519 (void)strcpy(mp->mp_name, name);
520 *tree = mp;
521 }
522
523 static void
524 addwhatis(whatis **tree, char *data, char *prefix)
525 {
526 whatis *wi;
527 int result;
528
529 while (ISSPACE(*data))
530 data++;
531
532 if (*data == '/') {
533 char *ptr;
534
535 ptr = ++data;
536 while ((*ptr != '\0') && !ISSPACE(*ptr))
537 if (*ptr++ == '/')
538 data = ptr;
539 }
540
541 while ((wi = *tree) != NULL) {
542 result = strcmp(data, wi->wi_data);
543 if (result == 0) return;
544 tree = result < 0 ? &wi->wi_left : &wi->wi_right;
545 }
546
547 wi = emalloc(sizeof(whatis) + strlen(prefix));
548
549 wi->wi_left = NULL;
550 wi->wi_right = NULL;
551 wi->wi_data = data;
552 if (prefix[0] != '\0')
553 (void) strcpy(wi->wi_prefix, prefix);
554 else
555 wi->wi_prefix[0] = '\0';
556 *tree = wi;
557 }
558
559 static void
560 catpreprocess(char *from)
561 {
562 char *to;
563
564 to = from;
565 while (ISSPACE(*from)) from++;
566
567 while (*from != '\0')
568 if (ISSPACE(*from)) {
569 while (ISSPACE(*++from));
570 if (*from != '\0')
571 *to++ = ' ';
572 }
573 else if (*(from + 1) == '\b')
574 from += 2;
575 else
576 *to++ = *from++;
577
578 *to = '\0';
579 }
580
581 static char *
582 makewhatisline(const char *file, const char *line, const char *section)
583 {
584 static const char *del[] = {
585 " - ",
586 " -- ",
587 "- ",
588 " -",
589 NULL
590 };
591 size_t i, pos;
592 size_t llen, slen, dlen;
593 char *result, *ptr;
594
595 ptr = NULL;
596 if (section == NULL) {
597 if (dowarn)
598 warnx("%s: No section provided for `%s'", file, line);
599 return estrdup(line);
600 }
601
602 for (i = 0; del[i]; i++)
603 if ((ptr = strstr(line, del[i])) != NULL)
604 break;
605
606 if (del[i] == NULL) {
607 if (dowarn)
608 warnx("%s: Bad format line `%s'", file, line);
609 return estrdup(line);
610 }
611
612 slen = strlen(section);
613 llen = strlen(line);
614 dlen = strlen(del[i]);
615
616 result = emalloc(llen - dlen + slen + 1);
617 pos = ptr - line;
618
619 (void)memcpy(result, line, pos);
620 (void)memcpy(&result[pos], section, slen);
621 (void)strcpy(&result[pos + slen], &line[pos + dlen]);
622 return result;
623 }
624
625 static char *
626 parsecatpage(const char *name, gzFile *in)
627 {
628 char buffer[8192];
629 char *section, *ptr, *last;
630 size_t size;
631
632 do {
633 if (GetS(in, buffer, sizeof(buffer)) == NULL)
634 return NULL;
635 }
636 while (buffer[0] == '\n');
637
638 section = NULL;
639 if ((ptr = strchr(buffer, '(')) != NULL) {
640 if ((last = strchr(ptr + 1, ')')) !=NULL) {
641 size_t length;
642
643 length = last - ptr + 1;
644 section = emalloc(length + 5);
645 *section = ' ';
646 (void) memcpy(section + 1, ptr, length);
647 (void) strcpy(section + 1 + length, " - ");
648 }
649 }
650
651 for (;;) {
652 if (GetS(in, buffer, sizeof(buffer)) == NULL) {
653 free(section);
654 return NULL;
655 }
656 catpreprocess(buffer);
657 if (strncmp(buffer, "NAME", 4) == 0)
658 break;
659 }
660 if (section == NULL)
661 section = makesection(pathnamesection("/cat", name));
662
663 ptr = last = buffer;
664 size = sizeof(buffer) - 1;
665 while ((size > 0) && (GetS(in, ptr, size) != NULL)) {
666 int length;
667
668 catpreprocess(ptr);
669
670 length = strlen(ptr);
671 if (length == 0) {
672 *last = '\0';
673
674 ptr = makewhatisline(name, buffer, section);
675 free(section);
676 return ptr;
677 }
678 if ((length > 1) && (ptr[length - 1] == '-') &&
679 ISALPHA(ptr[length - 2]))
680 last = &ptr[--length];
681 else {
682 last = &ptr[length++];
683 *last = ' ';
684 }
685
686 ptr += length;
687 size -= length;
688 }
689
690 free(section);
691
692 return NULL;
693 }
694
695 static int
696 manpreprocess(char *line)
697 {
698 char *from, *to;
699
700 to = from = line;
701 while (ISSPACE(*from))
702 from++;
703 if (strncmp(from, ".\\\"", 3) == 0)
704 return 1;
705
706 while (*from != '\0')
707 if (ISSPACE(*from)) {
708 while (ISSPACE(*++from));
709 if ((*from != '\0') && (*from != ','))
710 *to++ = ' ';
711 } else if (*from == '\\') {
712 switch (*++from) {
713 case '\0':
714 case '-':
715 break;
716 case 'f':
717 case 's':
718 from++;
719 if ((*from=='+') || (*from=='-'))
720 from++;
721 while (ISDIGIT(*from))
722 from++;
723 break;
724 default:
725 from++;
726 }
727 } else {
728 if (*from == '"')
729 from++;
730 else
731 *to++ = *from++;
732 }
733
734 *to = '\0';
735
736 if (strncasecmp(line, ".Xr", 3) == 0) {
737 char *sect;
738
739 from = line + 3;
740 if (ISSPACE(*from))
741 from++;
742
743 if ((sect = findwhitespace(from)) != NULL) {
744 size_t length;
745 char *trail;
746
747 *sect++ = '\0';
748 if ((trail = findwhitespace(sect)) != NULL)
749 *trail++ = '\0';
750 length = strlen(from);
751 (void) memmove(line, from, length);
752 line[length++] = '(';
753 to = &line[length];
754 length = strlen(sect);
755 (void) memmove(to, sect, length);
756 if (trail == NULL) {
757 (void) strcpy(&to[length], ")");
758 } else {
759 to += length;
760 *to++ = ')';
761 length = strlen(trail);
762 (void) memmove(to, trail, length + 1);
763 }
764 }
765 }
766
767 return 0;
768 }
769
770 static char *
771 nroff(const char *inname, gzFile *in)
772 {
773 char tempname[MAXPATHLEN], buffer[65536], *data;
774 int tempfd, bytes, pipefd[2], status;
775 static int devnull = -1;
776 pid_t child;
777
778 if (gzrewind(in) < 0)
779 err(EXIT_FAILURE, "Cannot rewind pipe");
780
781 if ((devnull < 0) &&
782 ((devnull = open(_PATH_DEVNULL, O_WRONLY, 0)) < 0))
783 err(EXIT_FAILURE, "Cannot open `/dev/null'");
784
785 (void)strlcpy(tempname, _PATH_TMP "makewhatis.XXXXXX",
786 sizeof(tempname));
787 if ((tempfd = mkstemp(tempname)) == -1)
788 err(EXIT_FAILURE, "Cannot create temp file");
789
790 while ((bytes = gzread(in, buffer, sizeof(buffer))) > 0)
791 if (write(tempfd, buffer, (size_t)bytes) != bytes) {
792 bytes = -1;
793 break;
794 }
795
796 if (bytes < 0) {
797 (void)close(tempfd);
798 (void)unlink(tempname);
799 err(EXIT_FAILURE, "Read from pipe failed");
800 }
801 if (lseek(tempfd, (off_t)0, SEEK_SET) == (off_t)-1) {
802 (void)close(tempfd);
803 (void)unlink(tempname);
804 err(EXIT_FAILURE, "Cannot rewind temp file");
805 }
806 if (pipe(pipefd) == -1) {
807 (void)close(tempfd);
808 (void)unlink(tempname);
809 err(EXIT_FAILURE, "Cannot create pipe");
810 }
811
812 switch (child = vfork()) {
813 case -1:
814 (void)close(pipefd[1]);
815 (void)close(pipefd[0]);
816 (void)close(tempfd);
817 (void)unlink(tempname);
818 err(EXIT_FAILURE, "Fork failed");
819 /* NOTREACHED */
820 case 0:
821 (void)close(pipefd[0]);
822 if (tempfd != STDIN_FILENO) {
823 (void)dup2(tempfd, STDIN_FILENO);
824 (void)close(tempfd);
825 }
826 if (pipefd[1] != STDOUT_FILENO) {
827 (void)dup2(pipefd[1], STDOUT_FILENO);
828 (void)close(pipefd[1]);
829 }
830 if (devnull != STDERR_FILENO) {
831 (void)dup2(devnull, STDERR_FILENO);
832 (void)close(devnull);
833 }
834 (void)execlp(NROFF, NROFF, "-S", "-man", NULL);
835 _exit(EXIT_FAILURE);
836 /*NOTREACHED*/
837 default:
838 (void)close(pipefd[1]);
839 (void)close(tempfd);
840 break;
841 }
842
843 if ((in = gzdopen(pipefd[0], "r")) == NULL) {
844 if (errno == 0)
845 errno = ENOMEM;
846 (void)close(pipefd[0]);
847 (void)kill(child, SIGTERM);
848 while (waitpid(child, NULL, 0) != child);
849 (void)unlink(tempname);
850 err(EXIT_FAILURE, "Cannot read from pipe");
851 }
852
853 data = parsecatpage(inname, in);
854 while (gzread(in, buffer, sizeof(buffer)) > 0);
855 (void)gzclose(in);
856
857 while (waitpid(child, &status, 0) != child);
858 if ((data != NULL) &&
859 !(WIFEXITED(status) && (WEXITSTATUS(status) == 0))) {
860 free(data);
861 errx(EXIT_FAILURE, NROFF " on `%s' exited with %d status",
862 inname, WEXITSTATUS(status));
863 }
864
865 (void)unlink(tempname);
866 return data;
867 }
868
869 static char *
870 parsemanpage(const char *name, gzFile *in, int defaultsection)
871 {
872 char *section, buffer[8192], *ptr;
873
874 section = NULL;
875 do {
876 if (GetS(in, buffer, sizeof(buffer) - 1) == NULL) {
877 free(section);
878 return NULL;
879 }
880 if (manpreprocess(buffer))
881 continue;
882 if (strncasecmp(buffer, ".Dt", 3) == 0) {
883 char *end;
884
885 ptr = &buffer[3];
886 if (ISSPACE(*ptr))
887 ptr++;
888 if ((ptr = findwhitespace(ptr)) == NULL)
889 continue;
890
891 if ((end = findwhitespace(++ptr)) != NULL)
892 *end = '\0';
893
894 free(section);
895 section = createsectionstring(ptr);
896 }
897 else if (strncasecmp(buffer, ".TH", 3) == 0) {
898 ptr = &buffer[3];
899 while (ISSPACE(*ptr))
900 ptr++;
901 if ((ptr = findwhitespace(ptr)) != NULL) {
902 char *next;
903
904 while (ISSPACE(*ptr))
905 ptr++;
906 if ((next = findwhitespace(ptr)) != NULL)
907 *next = '\0';
908 free(section);
909 section = createsectionstring(ptr);
910 }
911 }
912 else if (strncasecmp(buffer, ".Ds", 3) == 0) {
913 free(section);
914 return NULL;
915 }
916 } while (strncasecmp(buffer, ".Sh NAME", 8) != 0);
917
918 do {
919 if (GetS(in, buffer, sizeof(buffer) - 1) == NULL) {
920 free(section);
921 return NULL;
922 }
923 } while (manpreprocess(buffer));
924
925 if (strncasecmp(buffer, ".Nm", 3) == 0) {
926 size_t length, offset;
927
928 ptr = &buffer[3];
929 while (ISSPACE(*ptr))
930 ptr++;
931
932 length = strlen(ptr);
933 if ((length > 1) && (ptr[length - 1] == ',') &&
934 ISSPACE(ptr[length - 2])) {
935 ptr[--length] = '\0';
936 ptr[length - 1] = ',';
937 }
938 (void) memmove(buffer, ptr, length + 1);
939
940 offset = length + 3;
941 ptr = &buffer[offset];
942 for (;;) {
943 size_t more;
944
945 if ((sizeof(buffer) == offset) ||
946 (GetS(in, ptr, sizeof(buffer) - offset)
947 == NULL)) {
948 free(section);
949 return NULL;
950 }
951 if (manpreprocess(ptr))
952 continue;
953
954 if (strncasecmp(ptr, ".Nm", 3) != 0) break;
955
956 ptr += 3;
957 if (ISSPACE(*ptr))
958 ptr++;
959
960 buffer[length++] = ' ';
961 more = strlen(ptr);
962 if ((more > 1) && (ptr[more - 1] == ',') &&
963 ISSPACE(ptr[more - 2])) {
964 ptr[--more] = '\0';
965 ptr[more - 1] = ',';
966 }
967
968 (void) memmove(&buffer[length], ptr, more + 1);
969 length += more;
970 offset = length + 3;
971
972 ptr = &buffer[offset];
973 }
974
975 if (strncasecmp(ptr, ".Nd", 3) == 0) {
976 (void) strlcpy(&buffer[length], " -",
977 sizeof(buffer) - length);
978
979 while (strncasecmp(ptr, ".Sh", 3) != 0) {
980 int more;
981
982 if (*ptr == '.') {
983 char *space;
984
985 if (strncasecmp(ptr, ".Nd", 3) != 0 ||
986 strchr(ptr, '[') != NULL) {
987 free(section);
988 return NULL;
989 }
990 space = findwhitespace(ptr);
991 if (space == NULL) {
992 ptr = "";
993 } else {
994 space++;
995 (void) strmove(ptr, space);
996 }
997 }
998
999 if (*ptr != '\0') {
1000 buffer[offset - 1] = ' ';
1001 more = strlen(ptr) + 1;
1002 offset += more;
1003 }
1004 ptr = &buffer[offset];
1005 if ((sizeof(buffer) == offset) ||
1006 (GetS(in, ptr, sizeof(buffer) - offset)
1007 == NULL)) {
1008 free(section);
1009 return NULL;
1010 }
1011 if (manpreprocess(ptr))
1012 *ptr = '\0';
1013 }
1014 }
1015 }
1016 else {
1017 int offset;
1018
1019 if (*buffer == '.') {
1020 char *space;
1021
1022 if ((space = findwhitespace(&buffer[1])) == NULL) {
1023 free(section);
1024 return NULL;
1025 }
1026 space++;
1027 (void) strmove(buffer, space);
1028 }
1029
1030 offset = strlen(buffer) + 1;
1031 for (;;) {
1032 int more;
1033
1034 ptr = &buffer[offset];
1035 if ((sizeof(buffer) == offset) ||
1036 (GetS(in, ptr, sizeof(buffer) - offset)
1037 == NULL)) {
1038 free(section);
1039 return NULL;
1040 }
1041 if (manpreprocess(ptr) || (*ptr == '\0'))
1042 continue;
1043
1044 if ((strncasecmp(ptr, ".Sh", 3) == 0) ||
1045 (strncasecmp(ptr, ".Ss", 3) == 0))
1046 break;
1047
1048 if (*ptr == '.') {
1049 char *space;
1050
1051 if ((space = findwhitespace(ptr)) == NULL) {
1052 continue;
1053 }
1054
1055 space++;
1056 (void) memmove(ptr, space, strlen(space) + 1);
1057 }
1058
1059 buffer[offset - 1] = ' ';
1060 more = strlen(ptr);
1061 if ((more > 1) && (ptr[more - 1] == ',') &&
1062 ISSPACE(ptr[more - 2])) {
1063 ptr[more - 1] = '\0';
1064 ptr[more - 2] = ',';
1065 }
1066 else more++;
1067 offset += more;
1068 }
1069 }
1070
1071 if (section == NULL)
1072 section = makesection(defaultsection);
1073
1074 ptr = makewhatisline(name, buffer, section);
1075 free(section);
1076 return ptr;
1077 }
1078
1079 static char *
1080 getwhatisdata(char *name)
1081 {
1082 gzFile *in;
1083 char *data;
1084 int section;
1085
1086 if ((in = gzopen(name, "r")) == NULL) {
1087 if (errno == 0)
1088 errno = ENOMEM;
1089 err(EXIT_FAILURE, "Cannot open `%s'", name);
1090 /* NOTREACHED */
1091 }
1092
1093 section = manpagesection(name);
1094 if (section == 0) {
1095 data = parsecatpage(name, in);
1096 } else {
1097 data = parsemanpage(name, in, section);
1098 if (data == NULL)
1099 data = nroff(name, in);
1100 }
1101
1102 (void) gzclose(in);
1103 return data;
1104 }
1105
1106 static void
1107 processmanpages(manpage **source, whatis **dest)
1108 {
1109 manpage *mp;
1110 char sd[128];
1111
1112 mp = *source;
1113 *source = NULL;
1114
1115 while (mp != NULL) {
1116 manpage *obsolete;
1117 char *data;
1118
1119 if (mp->mp_left != NULL)
1120 processmanpages(&mp->mp_left, dest);
1121
1122 if ((data = getwhatisdata(mp->mp_name)) != NULL) {
1123 /* Pass eventual directory prefix to addwhatis() */
1124 if (mp->mp_sdlen > 0 && mp->mp_sdlen < sizeof(sd)-1)
1125 strlcpy(sd, &mp->mp_name[mp->mp_sdoff],
1126 mp->mp_sdlen);
1127 else
1128 sd[0] = '\0';
1129
1130 addwhatis(dest, data, sd);
1131 }
1132
1133 obsolete = mp;
1134 mp = mp->mp_right;
1135 free(obsolete);
1136 }
1137 }
1138
1139 static void
1140 dumpwhatis(FILE *out, whatis *tree)
1141 {
1142 while (tree != NULL) {
1143 if (tree->wi_left)
1144 dumpwhatis(out, tree->wi_left);
1145
1146 if ((tree->wi_data[0] && fputs(tree->wi_prefix, out) == EOF) ||
1147 (fputs(tree->wi_data, out) == EOF) ||
1148 (fputc('\n', out) == EOF))
1149 err(EXIT_FAILURE, "Write failed");
1150
1151 tree = tree->wi_right;
1152 }
1153 }
1154