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