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