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