makewhatis.c revision 1.45 1 /* $NetBSD: makewhatis.c,v 1.45 2008/11/16 06:17:05 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.45 2008/11/16 06:17:05 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 /* Include trailing '/', so we get
282 * 'arch/'. */
283 sdoff = s + 1 - fe->fts_path;
284 sdlen = lsl - s + 1;
285 } else {
286 sdoff = 0;
287 sdlen = 0;
288 }
289
290 addmanpage(&source, fe->fts_statp->st_ino,
291 fe->fts_path, sdoff, sdlen);
292 }
293 /*FALLTHROUGH*/
294 case FTS_D:
295 case FTS_DC:
296 case FTS_DEFAULT:
297 case FTS_DP:
298 case FTS_SL:
299 case FTS_DOT:
300 case FTS_W:
301 case FTS_NSOK:
302 case FTS_INIT:
303 break;
304 case FTS_SLNONE:
305 warnx("Symbolic link with no target: `%s'",
306 fe->fts_path);
307 break;
308 case FTS_DNR:
309 warnx("Unreadable directory: `%s'", fe->fts_path);
310 break;
311 case FTS_NS:
312 errno = fe->fts_errno;
313 warn("Cannot stat `%s'", fe->fts_path);
314 break;
315 case FTS_ERR:
316 errno = fe->fts_errno;
317 warn("Error reading `%s'", fe->fts_path);
318 break;
319 default:
320 errx(EXIT_FAILURE, "Unknown info %d returned from fts "
321 " for path: `%s'", fe->fts_info, fe->fts_path);
322 }
323 }
324
325 (void)fts_close(fts);
326
327 dest = NULL;
328 processmanpages(&source, &dest);
329
330 if (chdir(manpath[0]) == -1)
331 err(EXIT_FAILURE, "Cannot change dir to `%s'", manpath[0]);
332
333 /*
334 * makewhatis runs unattended, so it needs to be able to
335 * recover if the last run crashed out. Therefore, if
336 * whatisdb_new exists and is more than (arbitrarily) sixteen
337 * hours old, nuke it. If it exists but is not so old, refuse
338 * to run until it's cleaned up, in case another makewhatis is
339 * already running. Also, open the output with O_EXCL to make
340 * sure we get our own, in case two copies start exactly at
341 * once. (Unlikely? Maybe, maybe not, if two copies of cron
342 * end up running.)
343 *
344 * Similarly, before renaming the file after we finish writing
345 * to it, make sure it's still the same file we opened. This
346 * can't be completely race-free, but getting caught by it
347 * would require an unexplained sixteen-hour-or-more lag
348 * between the last mtime update when we wrote to it and when
349 * we get to the stat call *and* another makewhatis starting
350 * out to write at exactly the wrong moment. Not impossible,
351 * but not likely enough to worry about.
352 *
353 * This is maybe unnecessarily elaborate, but generating
354 * corrupted output isn't so good either.
355 */
356
357 if (stat(whatisdb_new, &st_before) == 0) {
358 if (st_before.st_mtime - time(NULL) > 16*60*60) {
359 /* Don't complain if someone else just removed it. */
360 if (unlink(whatisdb_new) == -1 && errno != ENOENT) {
361 err(EXIT_FAILURE, "Could not remove `%s'",
362 whatisdb_new);
363 } else {
364 warnx("Removed stale `%s'", whatisdb_new);
365 }
366 } else {
367 errx(EXIT_FAILURE, "The file `%s' already exists "
368 "-- am I already running?", whatisdb_new);
369 }
370 } else if (errno != ENOENT) {
371 /* Something unexpected happened. */
372 err(EXIT_FAILURE, "Cannot stat `%s'", whatisdb_new);
373 }
374
375 outfd = open(whatisdb_new, O_WRONLY|O_CREAT|O_EXCL,
376 S_IRUSR|S_IRGRP|S_IROTH);
377 if (outfd < 0)
378 err(EXIT_FAILURE, "Cannot open `%s'", whatisdb_new);
379
380 if (fstat(outfd, &st_before) == -1)
381 err(EXIT_FAILURE, "Cannot fstat `%s'", whatisdb_new);
382
383 if ((out = fdopen(outfd, "w")) == NULL)
384 err(EXIT_FAILURE, "Cannot fdopen `%s'", whatisdb_new);
385
386 dumpwhatis(out, dest);
387 if (fchmod(fileno(out), S_IRUSR|S_IRGRP|S_IROTH) == -1)
388 err(EXIT_FAILURE, "Cannot chmod `%s'", whatisdb_new);
389 if (fclose(out) != 0)
390 err(EXIT_FAILURE, "Cannot close `%s'", whatisdb_new);
391
392 if (stat(whatisdb_new, &st_after) == -1)
393 err(EXIT_FAILURE, "Cannot stat `%s' (after writing)",
394 whatisdb_new);
395
396 if (st_before.st_dev != st_after.st_dev ||
397 st_before.st_ino != st_after.st_ino) {
398 errx(EXIT_FAILURE, "The file `%s' changed under me; giving up",
399 whatisdb_new);
400 }
401
402 if (rename(whatisdb_new, whatisdb) == -1)
403 err(EXIT_FAILURE, "Could not rename `%s' to `%s'",
404 whatisdb_new, whatisdb);
405
406 return EXIT_SUCCESS;
407 }
408
409 static char *
410 findwhitespace(char *str)
411 {
412 while (!ISSPACE(*str))
413 if (*str++ == '\0') {
414 str = NULL;
415 break;
416 }
417
418 return str;
419 }
420
421 static char
422 *strmove(char *dest,char *src)
423 {
424 return memmove(dest, src, strlen(src) + 1);
425 }
426
427 static char *
428 GetS(gzFile in, char *buffer, size_t length)
429 {
430 char *ptr;
431
432 if (((ptr = gzgets(in, buffer, (int)length)) != NULL) && (*ptr == '\0'))
433 ptr = NULL;
434
435 return ptr;
436 }
437
438 static char *
439 makesection(int s)
440 {
441 char sectionbuffer[24];
442 if (s == -1)
443 return NULL;
444 (void)snprintf(sectionbuffer, sizeof(sectionbuffer),
445 " (%c) - ", sectionext[s]);
446 return estrdup(sectionbuffer);
447 }
448
449 static int
450 pathnamesection(const char *pat, const char *name)
451 {
452 char *ptr, *ext;
453 size_t len = strlen(pat);
454
455
456 while ((ptr = strstr(name, pat)) != NULL) {
457 if ((ext = strchr(sectionext, ptr[len])) != NULL) {
458 return ext - sectionext;
459 }
460 name = ptr + 1;
461 }
462 return -1;
463 }
464
465
466 static int
467 manpagesection(char *name)
468 {
469 char *ptr;
470
471 if ((ptr = strrchr(name, '/')) != NULL)
472 ptr++;
473 else
474 ptr = name;
475
476 while ((ptr = strchr(ptr, '.')) != NULL) {
477 int section;
478
479 ptr++;
480 section = 0;
481 while (sectionext[section] != '\0')
482 if (sectionext[section] == *ptr)
483 return section;
484 else
485 section++;
486 }
487 return -1;
488 }
489
490 static char *
491 createsectionstring(char *section_id)
492 {
493 char *section;
494
495 if (asprintf(§ion, " (%s) - ", section_id) < 0)
496 err(EXIT_FAILURE, "malloc failed");
497 return section;
498 }
499
500 static void
501 addmanpage(manpage **tree,ino_t inode,char *name, size_t sdoff, size_t sdlen)
502 {
503 manpage *mp;
504
505 while ((mp = *tree) != NULL) {
506 if (mp->mp_inode == inode)
507 return;
508 tree = inode < mp->mp_inode ? &mp->mp_left : &mp->mp_right;
509 }
510
511 mp = emalloc(sizeof(manpage) + strlen(name));
512 mp->mp_left = NULL;
513 mp->mp_right = NULL;
514 mp->mp_inode = inode;
515 mp->mp_sdoff = sdoff;
516 mp->mp_sdlen = sdlen;
517 (void)strcpy(mp->mp_name, name);
518 *tree = mp;
519 }
520
521 static void
522 addwhatis(whatis **tree, char *data, char *prefix)
523 {
524 whatis *wi;
525 int result;
526
527 while (ISSPACE(*data))
528 data++;
529
530 if (*data == '/') {
531 char *ptr;
532
533 ptr = ++data;
534 while ((*ptr != '\0') && !ISSPACE(*ptr))
535 if (*ptr++ == '/')
536 data = ptr;
537 }
538
539 while ((wi = *tree) != NULL) {
540 result = strcmp(data, wi->wi_data);
541 if (result == 0) return;
542 tree = result < 0 ? &wi->wi_left : &wi->wi_right;
543 }
544
545 wi = emalloc(sizeof(whatis) + strlen(prefix));
546
547 wi->wi_left = NULL;
548 wi->wi_right = NULL;
549 wi->wi_data = data;
550 if (prefix[0] != '\0')
551 (void) strcpy(wi->wi_prefix, prefix);
552 else
553 wi->wi_prefix[0] = '\0';
554 *tree = wi;
555 }
556
557 static void
558 catpreprocess(char *from)
559 {
560 char *to;
561
562 to = from;
563 while (ISSPACE(*from)) from++;
564
565 while (*from != '\0')
566 if (ISSPACE(*from)) {
567 while (ISSPACE(*++from));
568 if (*from != '\0')
569 *to++ = ' ';
570 }
571 else if (*(from + 1) == '\b')
572 from += 2;
573 else
574 *to++ = *from++;
575
576 *to = '\0';
577 }
578
579 static char *
580 makewhatisline(const char *file, const char *line, const char *section)
581 {
582 static const char *del[] = {
583 " - ",
584 " -- ",
585 "- ",
586 " -",
587 NULL
588 };
589 size_t i, pos;
590 size_t llen, slen, dlen;
591 char *result, *ptr;
592
593 ptr = NULL;
594 if (section == NULL) {
595 if (dowarn)
596 warnx("%s: No section provided for `%s'", file, line);
597 return estrdup(line);
598 }
599
600 for (i = 0; del[i]; i++)
601 if ((ptr = strstr(line, del[i])) != NULL)
602 break;
603
604 if (del[i] == NULL) {
605 if (dowarn)
606 warnx("%s: Bad format line `%s'", file, line);
607 return estrdup(line);
608 }
609
610 slen = strlen(section);
611 llen = strlen(line);
612 dlen = strlen(del[i]);
613
614 result = emalloc(llen - dlen + slen + 1);
615 pos = ptr - line;
616
617 (void)memcpy(result, line, pos);
618 (void)memcpy(&result[pos], section, slen);
619 (void)strcpy(&result[pos + slen], &line[pos + dlen]);
620 return result;
621 }
622
623 static char *
624 parsecatpage(const char *name, gzFile *in)
625 {
626 char buffer[8192];
627 char *section, *ptr, *last;
628 size_t size;
629
630 do {
631 if (GetS(in, buffer, sizeof(buffer)) == NULL)
632 return NULL;
633 }
634 while (buffer[0] == '\n');
635
636 section = NULL;
637 if ((ptr = strchr(buffer, '(')) != NULL) {
638 if ((last = strchr(ptr + 1, ')')) !=NULL) {
639 size_t length;
640
641 length = last - ptr + 1;
642 section = emalloc(length + 5);
643 *section = ' ';
644 (void) memcpy(section + 1, ptr, length);
645 (void) strcpy(section + 1 + length, " - ");
646 }
647 }
648
649 for (;;) {
650 if (GetS(in, buffer, sizeof(buffer)) == NULL) {
651 free(section);
652 return NULL;
653 }
654 catpreprocess(buffer);
655 if (strncmp(buffer, "NAME", 4) == 0)
656 break;
657 }
658 if (section == NULL)
659 section = makesection(pathnamesection("/cat", name));
660
661 ptr = last = buffer;
662 size = sizeof(buffer) - 1;
663 while ((size > 0) && (GetS(in, ptr, size) != NULL)) {
664 int length;
665
666 catpreprocess(ptr);
667
668 length = strlen(ptr);
669 if (length == 0) {
670 *last = '\0';
671
672 ptr = makewhatisline(name, buffer, section);
673 free(section);
674 return ptr;
675 }
676 if ((length > 1) && (ptr[length - 1] == '-') &&
677 ISALPHA(ptr[length - 2]))
678 last = &ptr[--length];
679 else {
680 last = &ptr[length++];
681 *last = ' ';
682 }
683
684 ptr += length;
685 size -= length;
686 }
687
688 free(section);
689
690 return NULL;
691 }
692
693 static int
694 manpreprocess(char *line)
695 {
696 char *from, *to;
697
698 to = from = line;
699 while (ISSPACE(*from))
700 from++;
701 if (strncmp(from, ".\\\"", 3) == 0)
702 return 1;
703
704 while (*from != '\0')
705 if (ISSPACE(*from)) {
706 while (ISSPACE(*++from));
707 if ((*from != '\0') && (*from != ','))
708 *to++ = ' ';
709 } else if (*from == '\\') {
710 switch (*++from) {
711 case '\0':
712 case '-':
713 break;
714 case 'f':
715 case 's':
716 from++;
717 if ((*from=='+') || (*from=='-'))
718 from++;
719 while (ISDIGIT(*from))
720 from++;
721 break;
722 default:
723 from++;
724 }
725 } else {
726 if (*from == '"')
727 from++;
728 else
729 *to++ = *from++;
730 }
731
732 *to = '\0';
733
734 if (strncasecmp(line, ".Xr", 3) == 0) {
735 char *sect;
736
737 from = line + 3;
738 if (ISSPACE(*from))
739 from++;
740
741 if ((sect = findwhitespace(from)) != NULL) {
742 size_t length;
743 char *trail;
744
745 *sect++ = '\0';
746 if ((trail = findwhitespace(sect)) != NULL)
747 *trail++ = '\0';
748 length = strlen(from);
749 (void) memmove(line, from, length);
750 line[length++] = '(';
751 to = &line[length];
752 length = strlen(sect);
753 (void) memmove(to, sect, length);
754 if (trail == NULL) {
755 (void) strcpy(&to[length], ")");
756 } else {
757 to += length;
758 *to++ = ')';
759 length = strlen(trail);
760 (void) memmove(to, trail, length + 1);
761 }
762 }
763 }
764
765 return 0;
766 }
767
768 static char *
769 nroff(const char *inname, gzFile *in)
770 {
771 char tempname[MAXPATHLEN], buffer[65536], *data;
772 int tempfd, bytes, pipefd[2], status;
773 static int devnull = -1;
774 pid_t child;
775
776 if (gzrewind(in) < 0)
777 err(EXIT_FAILURE, "Cannot rewind pipe");
778
779 if ((devnull < 0) &&
780 ((devnull = open(_PATH_DEVNULL, O_WRONLY, 0)) < 0))
781 err(EXIT_FAILURE, "Cannot open `/dev/null'");
782
783 (void)strlcpy(tempname, _PATH_TMP "makewhatis.XXXXXX",
784 sizeof(tempname));
785 if ((tempfd = mkstemp(tempname)) == -1)
786 err(EXIT_FAILURE, "Cannot create temp file");
787
788 while ((bytes = gzread(in, buffer, sizeof(buffer))) > 0)
789 if (write(tempfd, buffer, (size_t)bytes) != bytes) {
790 bytes = -1;
791 break;
792 }
793
794 if (bytes < 0) {
795 (void)close(tempfd);
796 (void)unlink(tempname);
797 err(EXIT_FAILURE, "Read from pipe failed");
798 }
799 if (lseek(tempfd, (off_t)0, SEEK_SET) == (off_t)-1) {
800 (void)close(tempfd);
801 (void)unlink(tempname);
802 err(EXIT_FAILURE, "Cannot rewind temp file");
803 }
804 if (pipe(pipefd) == -1) {
805 (void)close(tempfd);
806 (void)unlink(tempname);
807 err(EXIT_FAILURE, "Cannot create pipe");
808 }
809
810 switch (child = vfork()) {
811 case -1:
812 (void)close(pipefd[1]);
813 (void)close(pipefd[0]);
814 (void)close(tempfd);
815 (void)unlink(tempname);
816 err(EXIT_FAILURE, "Fork failed");
817 /* NOTREACHED */
818 case 0:
819 (void)close(pipefd[0]);
820 if (tempfd != STDIN_FILENO) {
821 (void)dup2(tempfd, STDIN_FILENO);
822 (void)close(tempfd);
823 }
824 if (pipefd[1] != STDOUT_FILENO) {
825 (void)dup2(pipefd[1], STDOUT_FILENO);
826 (void)close(pipefd[1]);
827 }
828 if (devnull != STDERR_FILENO) {
829 (void)dup2(devnull, STDERR_FILENO);
830 (void)close(devnull);
831 }
832 (void)execlp(NROFF, NROFF, "-S", "-man", NULL);
833 _exit(EXIT_FAILURE);
834 /*NOTREACHED*/
835 default:
836 (void)close(pipefd[1]);
837 (void)close(tempfd);
838 break;
839 }
840
841 if ((in = gzdopen(pipefd[0], "r")) == NULL) {
842 if (errno == 0)
843 errno = ENOMEM;
844 (void)close(pipefd[0]);
845 (void)kill(child, SIGTERM);
846 while (waitpid(child, NULL, 0) != child);
847 (void)unlink(tempname);
848 err(EXIT_FAILURE, "Cannot read from pipe");
849 }
850
851 data = parsecatpage(inname, in);
852 while (gzread(in, buffer, sizeof(buffer)) > 0);
853 (void)gzclose(in);
854
855 while (waitpid(child, &status, 0) != child);
856 if ((data != NULL) &&
857 !(WIFEXITED(status) && (WEXITSTATUS(status) == 0))) {
858 free(data);
859 errx(EXIT_FAILURE, NROFF " on `%s' exited with %d status",
860 inname, WEXITSTATUS(status));
861 }
862
863 (void)unlink(tempname);
864 return data;
865 }
866
867 static char *
868 parsemanpage(const char *name, gzFile *in, int defaultsection)
869 {
870 char *section, buffer[8192], *ptr;
871
872 section = NULL;
873 do {
874 if (GetS(in, buffer, sizeof(buffer) - 1) == NULL) {
875 free(section);
876 return NULL;
877 }
878 if (manpreprocess(buffer))
879 continue;
880 if (strncasecmp(buffer, ".Dt", 3) == 0) {
881 char *end;
882
883 ptr = &buffer[3];
884 if (ISSPACE(*ptr))
885 ptr++;
886 if ((ptr = findwhitespace(ptr)) == NULL)
887 continue;
888
889 if ((end = findwhitespace(++ptr)) != NULL)
890 *end = '\0';
891
892 free(section);
893 section = createsectionstring(ptr);
894 }
895 else if (strncasecmp(buffer, ".TH", 3) == 0) {
896 ptr = &buffer[3];
897 while (ISSPACE(*ptr))
898 ptr++;
899 if ((ptr = findwhitespace(ptr)) != NULL) {
900 char *next;
901
902 while (ISSPACE(*ptr))
903 ptr++;
904 if ((next = findwhitespace(ptr)) != NULL)
905 *next = '\0';
906 free(section);
907 section = createsectionstring(ptr);
908 }
909 }
910 else if (strncasecmp(buffer, ".Ds", 3) == 0) {
911 free(section);
912 return NULL;
913 }
914 } while (strncasecmp(buffer, ".Sh NAME", 8) != 0);
915
916 do {
917 if (GetS(in, buffer, sizeof(buffer) - 1) == NULL) {
918 free(section);
919 return NULL;
920 }
921 } while (manpreprocess(buffer));
922
923 if (strncasecmp(buffer, ".Nm", 3) == 0) {
924 size_t length, offset;
925
926 ptr = &buffer[3];
927 while (ISSPACE(*ptr))
928 ptr++;
929
930 length = strlen(ptr);
931 if ((length > 1) && (ptr[length - 1] == ',') &&
932 ISSPACE(ptr[length - 2])) {
933 ptr[--length] = '\0';
934 ptr[length - 1] = ',';
935 }
936 (void) memmove(buffer, ptr, length + 1);
937
938 offset = length + 3;
939 ptr = &buffer[offset];
940 for (;;) {
941 size_t more;
942
943 if ((sizeof(buffer) == offset) ||
944 (GetS(in, ptr, sizeof(buffer) - offset)
945 == NULL)) {
946 free(section);
947 return NULL;
948 }
949 if (manpreprocess(ptr))
950 continue;
951
952 if (strncasecmp(ptr, ".Nm", 3) != 0) break;
953
954 ptr += 3;
955 if (ISSPACE(*ptr))
956 ptr++;
957
958 buffer[length++] = ' ';
959 more = strlen(ptr);
960 if ((more > 1) && (ptr[more - 1] == ',') &&
961 ISSPACE(ptr[more - 2])) {
962 ptr[--more] = '\0';
963 ptr[more - 1] = ',';
964 }
965
966 (void) memmove(&buffer[length], ptr, more + 1);
967 length += more;
968 offset = length + 3;
969
970 ptr = &buffer[offset];
971 }
972
973 if (strncasecmp(ptr, ".Nd", 3) == 0) {
974 (void) strlcpy(&buffer[length], " -",
975 sizeof(buffer) - length);
976
977 while (strncasecmp(ptr, ".Sh", 3) != 0) {
978 int more;
979
980 if (*ptr == '.') {
981 char *space;
982
983 if (strncasecmp(ptr, ".Nd", 3) != 0 ||
984 strchr(ptr, '[') != NULL) {
985 free(section);
986 return NULL;
987 }
988 space = findwhitespace(ptr);
989 if (space == NULL) {
990 ptr = "";
991 } else {
992 space++;
993 (void) strmove(ptr, space);
994 }
995 }
996
997 if (*ptr != '\0') {
998 buffer[offset - 1] = ' ';
999 more = strlen(ptr) + 1;
1000 offset += more;
1001 }
1002 ptr = &buffer[offset];
1003 if ((sizeof(buffer) == offset) ||
1004 (GetS(in, ptr, sizeof(buffer) - offset)
1005 == NULL)) {
1006 free(section);
1007 return NULL;
1008 }
1009 if (manpreprocess(ptr))
1010 *ptr = '\0';
1011 }
1012 }
1013 }
1014 else {
1015 int offset;
1016
1017 if (*buffer == '.') {
1018 char *space;
1019
1020 if ((space = findwhitespace(&buffer[1])) == NULL) {
1021 free(section);
1022 return NULL;
1023 }
1024 space++;
1025 (void) strmove(buffer, space);
1026 }
1027
1028 offset = strlen(buffer) + 1;
1029 for (;;) {
1030 int more;
1031
1032 ptr = &buffer[offset];
1033 if ((sizeof(buffer) == offset) ||
1034 (GetS(in, ptr, sizeof(buffer) - offset)
1035 == NULL)) {
1036 free(section);
1037 return NULL;
1038 }
1039 if (manpreprocess(ptr) || (*ptr == '\0'))
1040 continue;
1041
1042 if ((strncasecmp(ptr, ".Sh", 3) == 0) ||
1043 (strncasecmp(ptr, ".Ss", 3) == 0))
1044 break;
1045
1046 if (*ptr == '.') {
1047 char *space;
1048
1049 if ((space = findwhitespace(ptr)) == NULL) {
1050 continue;
1051 }
1052
1053 space++;
1054 (void) memmove(ptr, space, strlen(space) + 1);
1055 }
1056
1057 buffer[offset - 1] = ' ';
1058 more = strlen(ptr);
1059 if ((more > 1) && (ptr[more - 1] == ',') &&
1060 ISSPACE(ptr[more - 2])) {
1061 ptr[more - 1] = '\0';
1062 ptr[more - 2] = ',';
1063 }
1064 else more++;
1065 offset += more;
1066 }
1067 }
1068
1069 if (section == NULL)
1070 section = makesection(defaultsection);
1071
1072 ptr = makewhatisline(name, buffer, section);
1073 free(section);
1074 return ptr;
1075 }
1076
1077 static char *
1078 getwhatisdata(char *name)
1079 {
1080 gzFile *in;
1081 char *data;
1082 int section;
1083
1084 if ((in = gzopen(name, "r")) == NULL) {
1085 if (errno == 0)
1086 errno = ENOMEM;
1087 err(EXIT_FAILURE, "Cannot open `%s'", name);
1088 /* NOTREACHED */
1089 }
1090
1091 section = manpagesection(name);
1092 if (section == 0) {
1093 data = parsecatpage(name, in);
1094 } else {
1095 data = parsemanpage(name, in, section);
1096 if (data == NULL)
1097 data = nroff(name, in);
1098 }
1099
1100 (void) gzclose(in);
1101 return data;
1102 }
1103
1104 static void
1105 processmanpages(manpage **source, whatis **dest)
1106 {
1107 manpage *mp;
1108 char sd[128];
1109
1110 mp = *source;
1111 *source = NULL;
1112
1113 while (mp != NULL) {
1114 manpage *obsolete;
1115 char *data;
1116
1117 if (mp->mp_left != NULL)
1118 processmanpages(&mp->mp_left,dest);
1119
1120 if ((data = getwhatisdata(mp->mp_name)) != NULL) {
1121 /* Pass eventual directory prefix to addwhatis() */
1122 if (mp->mp_sdlen > 0 && mp->mp_sdlen < sizeof(sd)-1)
1123 strlcpy(sd, &mp->mp_name[mp->mp_sdoff],
1124 mp->mp_sdlen);
1125 else
1126 sd[0] = '\0';
1127
1128 addwhatis(dest, data, sd);
1129 }
1130
1131 obsolete = mp;
1132 mp = mp->mp_right;
1133 free(obsolete);
1134 }
1135 }
1136
1137 static void
1138 dumpwhatis(FILE *out, whatis *tree)
1139 {
1140 while (tree != NULL) {
1141 if (tree->wi_left)
1142 dumpwhatis(out, tree->wi_left);
1143
1144 if ((tree->wi_data[0] && fputs(tree->wi_prefix, out) == EOF) ||
1145 (fputs(tree->wi_data, out) == EOF) ||
1146 (fputc('\n', out) == EOF))
1147 err(EXIT_FAILURE, "Write failed");
1148
1149 tree = tree->wi_right;
1150 }
1151 }
1152