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