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