1 /*	$OpenBSD: man.c,v 1.28 2004/02/23 14:14:14 jmc Exp $	*/
2 /*	$NetBSD: man.c,v 1.7 1995/09/28 06:05:34 tls Exp $	*/
3 
4 /*
5  * Copyright (c) 1987, 1993, 1994, 1995
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 #ifndef lint
34 static char copyright[] =
35 "@(#) Copyright (c) 1987, 1993, 1994, 1995\n\
36 	The Regents of the University of California.  All rights reserved.\n";
37 #endif /* not lint */
38 
39 #ifndef lint
40 #if 0
41 static char sccsid[] = "@(#)man.c	8.17 (Berkeley) 1/31/95";
42 #else
43 static char rcsid[] = "$OpenBSD: man.c,v 1.28 2004/02/23 14:14:14 jmc Exp $";
44 #endif
45 #endif /* not lint */
46 
47 #include <sys/param.h>
48 #include <sys/queue.h>
49 
50 #include <ctype.h>
51 #include <err.h>
52 #include <errno.h>
53 #include <fcntl.h>
54 #include <fnmatch.h>
55 #include <glob.h>
56 #include <signal.h>
57 #include <stdio.h>
58 #include <libgen.h>
59 #include <stdlib.h>
60 #include <string.h>
61 #include <unistd.h>
62 
63 #include "config.h"
64 #include "pathnames.h"
65 
66 int f_all, f_where;
67 static TAG *section;	/* could be passed to cleanup() instead */
68 
69 extern char *__progname;
70 
71 static void	 build_page(char *, char **);
72 static void	 cat(char *);
73 static char	*check_pager(char *);
74 static int	 cleanup(void);
75 static void	 how(char *);
76 static void	 jump(char **, char *, char *);
77 static int	 manual(char *, TAG *, glob_t *);
78 static void	 onsig(int);
79 static void	 usage(void);
80 
81 sigset_t	blocksigs;
82 
83 int
main(int argc,char * argv[])84 main(int argc, char *argv[])
85 {
86 	extern char *optarg;
87 	extern int optind;
88 	TAG *defp, *defnewp, *sectnewp, *subp;
89 	ENTRY *e_defp, *e_sectp, *e_subp, *ep;
90 	glob_t pg;
91 	size_t len;
92 	int ch, f_cat, f_how, found;
93 	char **ap, *cmd, *machine, *p, *p_add, *p_path, *pager, *sflag, *slashp;
94 	char *conffile, buf[MAXPATHLEN * 2];
95 
96 	if (argv[1] == NULL && strcmp(basename(__progname), "help") == 0) {
97 		static char *nargv[3];
98 		nargv[0] = "man";
99 		nargv[1] = "help";
100 		nargv[2] = NULL;
101 		argv = nargv;
102 		argc = 2;
103 	}
104 
105 	machine = sflag = NULL;
106 	f_cat = f_how = 0;
107 	conffile = p_add = p_path = NULL;
108 	while ((ch = getopt(argc, argv, "aC:cfhkM:m:P:s:S:w-")) != -1)
109 		switch (ch) {
110 		case 'a':
111 			f_all = 1;
112 			break;
113 		case 'C':
114 			conffile = optarg;
115 			break;
116 		case 'c':
117 		case '-':		/* Deprecated. */
118 			f_cat = 1;
119 			break;
120 		case 'h':
121 			f_how = 1;
122 			break;
123 		case 'm':
124 			p_add = optarg;
125 			break;
126 		case 'M':
127 		case 'P':		/* Backward compatibility. */
128 			p_path = optarg;
129 			break;
130 		case 's':		/* SVR4 compatibility. */
131 			sflag = optarg;
132 			break;
133 		case 'S':
134 			machine = optarg;
135 			break;
136 		/*
137 		 * The -f and -k options are backward compatible
138 		 * ways of calling whatis(1) and apropos(1).
139 		 */
140 		case 'f':
141 			jump(argv, "-f", "whatis");
142 			/* NOTREACHED */
143 		case 'k':
144 			jump(argv, "-k", "apropos");
145 			/* NOTREACHED */
146 		case 'w':
147 			f_all = f_where = 1;
148 			break;
149 		case '?':
150 		default:
151 			usage();
152 		}
153 	argc -= optind;
154 	argv += optind;
155 
156 	if (!*argv)
157 		usage();
158 
159 	if (!f_cat && !f_how && !f_where) {
160 		if (!isatty(1))
161 			f_cat = 1;
162 		else if ((pager = getenv("MANPAGER")) != NULL &&
163 				(*pager != '\0'))
164 			pager = check_pager(pager);
165 		else if ((pager = getenv("PAGER")) != NULL && (*pager != '\0'))
166 			pager = check_pager(pager);
167 		else
168 			pager = _PATH_PAGER;
169 	}
170 
171 	/* Read the configuration file. */
172 	config(conffile);
173 
174 	/* Get the machine type unless specified by -S. */
175 	if (machine == NULL && (machine = getenv("MACHINE")) == NULL)
176 		machine = MACHINE;
177 
178 	/* If there's no _default list, create an empty one. */
179 	if ((defp = getlist("_default")) == NULL)
180 		defp = addlist("_default");
181 
182 	/*
183 	 * 1: If the user specified a MANPATH variable, or set the -M
184 	 *    option, we replace the _default list with the user's list,
185 	 *    appending the entries in the _subdir list and the machine.
186 	 */
187 	if (p_path == NULL)
188 		p_path = getenv("MANPATH");
189 	if (p_path != NULL) {
190 		while ((e_defp = defp->list.tqh_first) != NULL) {
191 			free(e_defp->s);
192 			TAILQ_REMOVE(&defp->list, e_defp, q);
193 		}
194 		for (p = strtok(p_path, ":");
195 		    p != NULL; p = strtok(NULL, ":")) {
196 			slashp = p[strlen(p) - 1] == '/' ? "" : "/";
197 			e_subp = (subp = getlist("_subdir")) == NULL ?
198 			    NULL : subp->list.tqh_first;
199 			for (; e_subp != NULL; e_subp = e_subp->q.tqe_next) {
200 				(void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,}",
201 				    p, slashp, e_subp->s, machine);
202 				if ((ep = malloc(sizeof(ENTRY))) == NULL ||
203 				    (ep->s = strdup(buf)) == NULL)
204 					err(1, NULL);
205 				TAILQ_INSERT_TAIL(&defp->list, ep, q);
206 			}
207 		}
208 	}
209 
210 	/*
211 	 * 2: If the user did not specify MANPATH, -M or a section, rewrite
212 	 *    the _default list to include the _subdir list and the machine.
213 	 */
214 	if (sflag == NULL && argv[1] == NULL)
215 		section = NULL;
216 	else {
217 		if (sflag != NULL && (section = getlist(sflag)) == NULL)
218 			errx(1, "unknown manual section `%s'", sflag);
219 		else if (sflag == NULL && (section = getlist(*argv)) != NULL)
220 			++argv;
221 	}
222 	if (p_path == NULL && section == NULL) {
223 		defnewp = addlist("_default_new");
224 		e_defp =
225 		    defp->list.tqh_first == NULL ? NULL : defp->list.tqh_first;
226 		for (; e_defp != NULL; e_defp = e_defp->q.tqe_next) {
227 			slashp =
228 			    e_defp->s[strlen(e_defp->s) - 1] == '/' ? "" : "/";
229 			e_subp = (subp = getlist("_subdir")) == NULL ?
230 			    NULL : subp->list.tqh_first;
231 			for (; e_subp != NULL; e_subp = e_subp->q.tqe_next) {
232 				(void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,}",
233 				e_defp->s, slashp, e_subp->s, machine);
234 				if ((ep = malloc(sizeof(ENTRY))) == NULL ||
235 				    (ep->s = strdup(buf)) == NULL)
236 					err(1, NULL);
237 				TAILQ_INSERT_TAIL(&defnewp->list, ep, q);
238 			}
239 		}
240 		defp = getlist("_default");
241 		while ((e_defp = defp->list.tqh_first) != NULL) {
242 			free(e_defp->s);
243 			TAILQ_REMOVE(&defp->list, e_defp, q);
244 		}
245 		free(defp->s);
246 		TAILQ_REMOVE(&head, defp, q);
247 		defnewp = getlist("_default_new");
248 		free(defnewp->s);
249 		defnewp->s = "_default";
250 		defp = defnewp;
251 	}
252 
253 	/*
254 	 * 3: If the user set the -m option, insert the user's list before
255 	 *    whatever list we have, again appending the _subdir list and
256 	 *    the machine.
257 	 */
258 	if (p_add != NULL) {
259 		e_sectp = NULL;
260 		for (p = strtok(p_add, ":"); p != NULL; p = strtok(NULL, ":")) {
261 			slashp = p[strlen(p) - 1] == '/' ? "" : "/";
262 			e_subp = (subp = getlist("_subdir")) == NULL ?
263 			    NULL : subp->list.tqh_first;
264 			for (; e_subp != NULL; e_subp = e_subp->q.tqe_next) {
265 				(void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,}",
266 				    p, slashp, e_subp->s, machine);
267 				if ((ep = malloc(sizeof(ENTRY))) == NULL ||
268 				    (ep->s = strdup(buf)) == NULL)
269 					err(1, NULL);
270 				/*
271 				 * puts it at the end, should be at the top,
272 				 * but then the added entries would be in
273 				 * reverse order, fix later when all are added
274 				 */
275 				TAILQ_INSERT_TAIL(&defp->list, ep, q);
276 				if (e_sectp == NULL)
277 				 	/* save first added, to-be the new top */
278 					e_sectp = ep;
279 			}
280 		}
281 		if (e_sectp != NULL) { /* entries added, fix order */
282 			/* save original head */
283 			ep->q.tqe_next = defp->list.tqh_first;
284 			/* first added entry, new top */
285 			defp->list.tqh_first = e_sectp;
286 			/* terminate list */
287 			*e_sectp->q.tqe_prev = NULL;
288 		}
289 	}
290 	/*
291 	 * 4: If no -m was specified, and a section was, rewrite the section's
292 	 *    paths (if they have a trailing slash) to append the _subdir list
293 	 *    and the machine.  This then becomes the _default list.
294 	 */
295 	if (p_add == NULL && section != NULL) {
296 		sectnewp = addlist("_section_new");
297 		for (e_sectp = section->list.tqh_first;
298 		    e_sectp != NULL; e_sectp = e_sectp->q.tqe_next) {
299 			if (e_sectp->s[strlen(e_sectp->s) - 1] != '/') {
300 				(void)snprintf(buf, sizeof(buf),
301 				    "%s{/%s,}", e_sectp->s, machine);
302 				if ((ep = malloc(sizeof(ENTRY))) == NULL ||
303 				    (ep->s = strdup(buf)) == NULL)
304 					err(1, NULL);
305 				TAILQ_INSERT_TAIL(&sectnewp->list, ep, q);
306 				continue;
307 			}
308 			e_subp = (subp = getlist("_subdir")) == NULL ?
309 			    NULL : subp->list.tqh_first;
310 			for (; e_subp != NULL; e_subp = e_subp->q.tqe_next) {
311 				(void)snprintf(buf, sizeof(buf), "%s%s{/%s,}",
312 				    e_sectp->s, e_subp->s, machine);
313 				if ((ep = malloc(sizeof(ENTRY))) == NULL ||
314 				    (ep->s = strdup(buf)) == NULL)
315 					err(1, NULL);
316 				TAILQ_INSERT_TAIL(&sectnewp->list, ep, q);
317 			}
318 		}
319 		sectnewp->s = section->s;
320 		defp = sectnewp;
321 		TAILQ_REMOVE(&head, section, q);
322 	}
323 
324 	/*
325 	 * 5: Search for the files.  Set up an interrupt handler, so the
326 	 *    temporary files go away.
327 	 */
328 	(void)signal(SIGINT, onsig);
329 	(void)signal(SIGHUP, onsig);
330 
331 	sigemptyset(&blocksigs);
332 	sigaddset(&blocksigs, SIGINT);
333 	sigaddset(&blocksigs, SIGHUP);
334 
335 	memset(&pg, 0, sizeof(pg));
336 	for (found = 0; *argv; ++argv)
337 		if (manual(*argv, defp, &pg))
338 			found = 1;
339 
340 	/* 6: If nothing found, we're done. */
341 	if (!found) {
342 		(void)cleanup();
343 		exit (1);
344 	}
345 
346 	/* 7: If it's simple, display it fast. */
347 	if (f_cat) {
348 		for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
349 			if (**ap == '\0')
350 				continue;
351 			cat(*ap);
352 		}
353 		exit (cleanup());
354 	}
355 	if (f_how) {
356 		for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
357 			if (**ap == '\0')
358 				continue;
359 			how(*ap);
360 		}
361 		exit(cleanup());
362 	}
363 	if (f_where) {
364 		for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
365 			if (**ap == '\0')
366 				continue;
367 			(void)puts(*ap);
368 		}
369 		exit(cleanup());
370 	}
371 
372 	/*
373 	 * 8: We display things in a single command; build a list of things
374 	 *    to display.
375 	 */
376 	for (ap = pg.gl_pathv, len = strlen(pager) + 1; *ap != NULL; ++ap) {
377 		if (**ap == '\0')
378 			continue;
379 		len += strlen(*ap) + 1;
380 	}
381 	if ((cmd = malloc(len)) == NULL) {
382 		warn(NULL);
383 		(void)cleanup();
384 		exit(1);
385 	}
386 	p = cmd;
387 	len = strlen(pager);
388 	memcpy(p, pager, len);
389 	p += len;
390 	*p++ = ' ';
391 	for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
392 		if (**ap == '\0')
393 			continue;
394 		len = strlen(*ap);
395 		memcpy(p, *ap, len);
396 		p += len;
397 		*p++ = ' ';
398 	}
399 	*--p = '\0';
400 
401 	/* Use system(3) in case someone's pager is "pager arg1 arg2". */
402 	(void)system(cmd);
403 
404 	exit(cleanup());
405 }
406 
407 /*
408  * manual --
409  *	Search the manuals for the pages.
410  */
411 static int
manual(char * page,TAG * tag,glob_t * pg)412 manual(char *page, TAG *tag, glob_t *pg)
413 {
414 	ENTRY *ep, *e_sufp, *e_tag;
415 	TAG *missp, *sufp;
416 	int anyfound, cnt, found;
417 	char *p, buf[MAXPATHLEN];
418 
419 	anyfound = 0;
420 	buf[0] = '*';
421 
422 	/* For each element in the list... */
423 	e_tag = tag == NULL ? NULL : tag->list.tqh_first;
424 	for (; e_tag != NULL; e_tag = e_tag->q.tqe_next) {
425 		(void)snprintf(buf, sizeof(buf), "%s/%s.*", e_tag->s, page);
426 		if (glob(buf,
427 		    GLOB_APPEND | GLOB_BRACE | GLOB_NOSORT | GLOB_QUOTE,
428 		    NULL, pg)) {
429 			warn("globbing");
430 			(void)cleanup();
431 			exit(1);
432 		}
433 		if (pg->gl_matchc == 0)
434 			continue;
435 
436 		/* Find out if it's really a man page. */
437 		for (cnt = pg->gl_pathc - pg->gl_matchc;
438 		    cnt < pg->gl_pathc; ++cnt) {
439 
440 			/*
441 			 * Try the _suffix key words first.
442 			 *
443 			 * XXX
444 			 * Older versions of man.conf didn't have the suffix
445 			 * key words, it was assumed that everything was a .0.
446 			 * We just test for .0 first, it's fast and probably
447 			 * going to hit.
448 			 */
449 			(void)snprintf(buf, sizeof(buf), "*/%s.0", page);
450 			if (!fnmatch(buf, pg->gl_pathv[cnt], 0))
451 				goto next;
452 
453 			e_sufp = (sufp = getlist("_suffix")) == NULL ?
454 			    NULL : sufp->list.tqh_first;
455 			for (found = 0;
456 			    e_sufp != NULL; e_sufp = e_sufp->q.tqe_next) {
457 				(void)snprintf(buf,
458 				     sizeof(buf), "*/%s%s", page, e_sufp->s);
459 				if (!fnmatch(buf, pg->gl_pathv[cnt], 0)) {
460 					found = 1;
461 					break;
462 				}
463 			}
464 			if (found)
465 				goto next;
466 
467 			/* Try the _build key words next. */
468 			e_sufp = (sufp = getlist("_build")) == NULL ?
469 			    NULL : sufp->list.tqh_first;
470 			for (found = 0;
471 			    e_sufp != NULL; e_sufp = e_sufp->q.tqe_next) {
472 				for (p = e_sufp->s;
473 				    *p != '\0' && !isspace(*p); ++p);
474 				if (*p == '\0')
475 					continue;
476 				*p = '\0';
477 				(void)snprintf(buf,
478 				     sizeof(buf), "*/%s%s", page, e_sufp->s);
479 				if (!fnmatch(buf, pg->gl_pathv[cnt], 0)) {
480 					if (!f_where)
481 						build_page(p + 1,
482 						    &pg->gl_pathv[cnt]);
483 					*p = ' ';
484 					found = 1;
485 					break;
486 				}
487 				*p = ' ';
488 			}
489 			if (found) {
490 next:				anyfound = 1;
491 				if (!f_all) {
492 					/* Delete any other matches. */
493 					while (++cnt< pg->gl_pathc)
494 						pg->gl_pathv[cnt] = "";
495 					break;
496 				}
497 				continue;
498 			}
499 
500 			/* It's not a man page, forget about it. */
501 			pg->gl_pathv[cnt] = "";
502 		}
503 
504 		if (anyfound && !f_all)
505 			break;
506 	}
507 
508 	/* If not found, enter onto the missing list. */
509 	if (!anyfound) {
510 		sigset_t osigs;
511 
512 		sigprocmask(SIG_BLOCK, &blocksigs, &osigs);
513 
514 		if ((missp = getlist("_missing")) == NULL)
515 			missp = addlist("_missing");
516 		if ((ep = malloc(sizeof(ENTRY))) == NULL ||
517 		    (ep->s = strdup(page)) == NULL) {
518 			warn(NULL);
519 			(void)cleanup();
520 			exit(1);
521 		}
522 		TAILQ_INSERT_TAIL(&missp->list, ep, q);
523 		sigprocmask(SIG_SETMASK, &osigs, NULL);
524 	}
525 	return (anyfound);
526 }
527 
528 /*
529  * build_page --
530  *	Build a man page for display.
531  */
532 static void
build_page(char * fmt,char ** pathp)533 build_page(char *fmt, char **pathp)
534 {
535 	static int warned;
536 	ENTRY *ep;
537 	TAG *intmpp;
538 	int fd, n;
539 	char *p, *b;
540 	char buf[MAXPATHLEN], cmd[MAXPATHLEN], tpath[MAXPATHLEN];
541 	sigset_t osigs;
542 
543 	/* Let the user know this may take awhile. */
544 	if (!warned) {
545 		warned = 1;
546 		warnx("Formatting manual page...");
547 	}
548 
549        /*
550         * Historically man chdir'd to the root of the man tree.
551         * This was used in man pages that contained relative ".so"
552         * directives (including other man pages for command aliases etc.)
553         * It even went one step farther, by examining the first line
554         * of the man page and parsing the .so filename so it would
555         * make hard(?) links to the cat'ted man pages for space savings.
556         * (We don't do that here, but we could).
557         */
558 
559        /* copy and find the end */
560        for (b = buf, p = *pathp; (*b++ = *p++) != '\0';)
561                continue;
562 
563        /* skip the last two path components, page name and man[n] */
564        for (--b, n = 2; b != buf; b--)
565                if (*b == '/')
566                        if (--n == 0) {
567                                *b = '\0';
568                                (void) chdir(buf);
569                        }
570 
571 
572 	/* Add a remove-when-done list. */
573 	sigprocmask(SIG_BLOCK, &blocksigs, &osigs);
574 	if ((intmpp = getlist("_intmp")) == NULL)
575 		intmpp = addlist("_intmp");
576 	sigprocmask(SIG_SETMASK, &osigs, NULL);
577 
578 	/* Move to the printf(3) format string. */
579 	for (; *fmt && isspace(*fmt); ++fmt)
580 		;
581 
582 	/*
583 	 * Get a temporary file and build a version of the file
584 	 * to display.  Replace the old file name with the new one.
585 	 */
586 	(void)strlcpy(tpath, _PATH_TMPFILE, sizeof(tpath));
587 	if ((fd = mkstemp(tpath)) == -1) {
588 		warn("%s", tpath);
589 		(void)cleanup();
590 		exit(1);
591 	}
592 	(void)snprintf(buf, sizeof(buf), "%s > %s", fmt, tpath);
593 	(void)snprintf(cmd, sizeof(cmd), buf, *pathp);
594 	(void)system(cmd);
595 	(void)close(fd);
596 	if ((*pathp = strdup(tpath)) == NULL) {
597 		warn(NULL);
598 		(void)cleanup();
599 		exit(1);
600 	}
601 
602 	/* Link the built file into the remove-when-done list. */
603 	if ((ep = malloc(sizeof(ENTRY))) == NULL) {
604 		warn(NULL);
605 		(void)cleanup();
606 		exit(1);
607 	}
608 	ep->s = *pathp;
609 	TAILQ_INSERT_TAIL(&intmpp->list, ep, q);
610 }
611 
612 /*
613  * how --
614  *	display how information
615  */
616 static void
how(char * fname)617 how(char *fname)
618 {
619 	FILE *fp;
620 
621 	int lcnt, print;
622 	char *p, buf[256];
623 
624 	if (!(fp = fopen(fname, "r"))) {
625 		warn("%s", fname);
626 		(void)cleanup();
627 		exit (1);
628 	}
629 #define	S1	"SYNOPSIS"
630 #define	S2	"S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS"
631 #define	D1	"DESCRIPTION"
632 #define	D2	"D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN"
633 	for (lcnt = print = 0; fgets(buf, sizeof(buf), fp);) {
634 		if (!strncmp(buf, S1, sizeof(S1) - 1) ||
635 		    !strncmp(buf, S2, sizeof(S2) - 1)) {
636 			print = 1;
637 			continue;
638 		} else if (!strncmp(buf, D1, sizeof(D1) - 1) ||
639 		    !strncmp(buf, D2, sizeof(D2) - 1))
640 			return;
641 		if (!print)
642 			continue;
643 		if (*buf == '\n')
644 			++lcnt;
645 		else {
646 			while (lcnt) {
647 				--lcnt;
648 				(void)putchar('\n');
649 			}
650 			for (p = buf; isspace(*p); ++p)
651 				;
652 			(void)fputs(p, stdout);
653 		}
654 	}
655 	(void)fclose(fp);
656 }
657 
658 /*
659  * cat --
660  *	cat out the file
661  */
662 static void
cat(char * fname)663 cat(char *fname)
664 {
665 	int fd, n;
666 	char buf[2048];
667 
668 	if ((fd = open(fname, O_RDONLY, 0)) < 0) {
669 		warn("%s", fname);
670 		(void)cleanup();
671 		exit(1);
672 	}
673 	while ((n = read(fd, buf, sizeof(buf))) > 0)
674 		if (write(STDOUT_FILENO, buf, n) != n) {
675 			warn("write");
676 			(void)cleanup();
677 			exit (1);
678 		}
679 	if (n == -1) {
680 		warn("read");
681 		(void)cleanup();
682 		exit(1);
683 	}
684 	(void)close(fd);
685 }
686 
687 /*
688  * check_pager --
689  *	check the user supplied page information
690  */
691 static char *
check_pager(char * name)692 check_pager(char *name)
693 {
694 	char *p, *save;
695 	int len;
696 
697 	/*
698 	 * if the user uses "more", we make it "more -s"; watch out for
699 	 * PAGER = "mypager /usr/bin/more"
700 	 */
701 	for (p = name; *p && !isspace(*p); ++p)
702 		;
703 	for (; p > name && *p != '/'; --p)
704 		;
705 	if (p != name)
706 		++p;
707 
708 	/* make sure it's "more", not "morex" */
709 	if (!strncmp(p, "more", 4) && (p[4] == '\0' || isspace(p[4]))){
710 		save = name;
711 		/* allocate space to add the "-s" */
712 		len = strlen(save) + 1 + sizeof("-s");
713 		if ((name = malloc(len)) == NULL)
714 			err(1, NULL);
715 		(void)snprintf(name, len, "%s %s", save, "-s");
716 	}
717 	return(name);
718 }
719 
720 /*
721  * jump --
722  *	strip out flag argument and jump
723  */
724 static void
jump(char ** argv,char * flag,char * name)725 jump(char **argv, char *flag, char *name)
726 {
727 	char **arg;
728 
729 	argv[0] = name;
730 	for (arg = argv + 1; *arg; ++arg)
731 		if (!strcmp(*arg, flag))
732 			break;
733 	for (; *arg; ++arg)
734 		arg[0] = arg[1];
735 	execvp(name, argv);
736 	(void)fprintf(stderr, "%s: Command not found.\n", name);
737 	exit(1);
738 }
739 
740 /*
741  * onsig --
742  *	If signaled, delete the temporary files.
743  */
744 static void
onsig(int signo)745 onsig(int signo)
746 {
747 	(void)cleanup();	/* XXX signal race */
748 
749 	(void)signal(signo, SIG_DFL);
750 	(void)kill(getpid(), signo);
751 
752 	/* NOTREACHED */
753 	_exit(1);
754 }
755 
756 /*
757  * cleanup --
758  *	Clean up temporary files, show any error messages.
759  */
760 static int
cleanup(void)761 cleanup(void)
762 {
763 	TAG *intmpp, *missp;
764 	ENTRY *ep;
765 	int rval;
766 
767 	rval = 0;
768 	ep = (missp = getlist("_missing")) == NULL ?
769 	    NULL : missp->list.tqh_first;
770 	if (ep != NULL)
771 		for (; ep != NULL; ep = ep->q.tqe_next) {
772 			if (section)
773 				warnx("no entry for %s in section %s of the manual.",
774 					ep->s, section->s);
775 			else
776 				warnx("no entry for %s in the manual.", ep->s);
777 			rval = 1;
778 		}
779 
780 	ep = (intmpp = getlist("_intmp")) == NULL ?
781 	    NULL : intmpp->list.tqh_first;
782 	for (; ep != NULL; ep = ep->q.tqe_next)
783 		(void)unlink(ep->s);
784 	return (rval);
785 }
786 
787 /*
788  * usage --
789  *	print usage message and die
790  */
791 static void
usage(void)792 usage(void)
793 {
794 	(void)fprintf(stderr, "usage: %s [-achw] [-C file] [-M path] [-m path] "
795 	    "[-S subsection] [-s section]\n\t   [section] name [...]\n",
796 	    __progname);
797 	(void)fprintf(stderr, "usage: %s -f command\n", __progname);
798 	(void)fprintf(stderr, "usage: %s -k keyword\n", __progname);
799 	exit(1);
800 }
801