xref: /original-bsd/usr.bin/man/man.c (revision 0997b878)
1 /*
2  * Copyright (c) 1987, 1993, 1994, 1995
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * %sccs.include.redist.c%
6  */
7 
8 #ifndef lint
9 static char copyright[] =
10 "@(#) Copyright (c) 1987, 1993, 1994, 1995\n\
11 	The Regents of the University of California.  All rights reserved.\n";
12 #endif /* not lint */
13 
14 #ifndef lint
15 static char sccsid[] = "@(#)man.c	8.17 (Berkeley) 01/31/95";
16 #endif /* not lint */
17 
18 #include <sys/param.h>
19 #include <sys/queue.h>
20 
21 #include <ctype.h>
22 #include <err.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <fnmatch.h>
26 #include <glob.h>
27 #include <signal.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32 
33 #include "config.h"
34 #include "pathnames.h"
35 
36 int f_all, f_where;
37 
38 static void	 build_page __P((char *, char **));
39 static void	 cat __P((char *));
40 static char	*check_pager __P((char *));
41 static int	 cleanup __P((void));
42 static void	 how __P((char *));
43 static void	 jump __P((char **, char *, char *));
44 static int	 manual __P((char *, TAG *, glob_t *));
45 static void	 onsig __P((int));
46 static void	 usage __P((void));
47 
48 int
49 main(argc, argv)
50 	int argc;
51 	char *argv[];
52 {
53 	extern char *optarg;
54 	extern int optind;
55 	TAG *defp, *defnewp, *section, *sectnewp, *subp;
56 	ENTRY *e_defp, *e_sectp, *e_subp, *ep;
57 	glob_t pg;
58 	size_t len;
59 	int ch, f_cat, f_how, found;
60 	char **ap, *cmd, *machine, *p, *p_add, *p_path, *pager, *slashp;
61 	char *conffile, buf[MAXPATHLEN * 2];
62 
63 	f_cat = f_how = 0;
64 	conffile = p_add = p_path = NULL;
65 	while ((ch = getopt(argc, argv, "-aC:cfhkM:m:P:w")) != EOF)
66 		switch (ch) {
67 		case 'a':
68 			f_all = 1;
69 			break;
70 		case 'C':
71 			conffile = optarg;
72 			break;
73 		case 'c':
74 		case '-':		/* Deprecated. */
75 			f_cat = 1;
76 			break;
77 		case 'h':
78 			f_how = 1;
79 			break;
80 		case 'm':
81 			p_add = optarg;
82 			break;
83 		case 'M':
84 		case 'P':		/* Backward compatibility. */
85 			p_path = optarg;
86 			break;
87 		/*
88 		 * The -f and -k options are backward compatible,
89 		 * undocumented ways of calling whatis(1) and apropos(1).
90 		 */
91 		case 'f':
92 			jump(argv, "-f", "whatis");
93 			/* NOTREACHED */
94 		case 'k':
95 			jump(argv, "-k", "apropos");
96 			/* NOTREACHED */
97 		case 'w':
98 			f_all = f_where = 1;
99 			break;
100 		case '?':
101 		default:
102 			usage();
103 		}
104 	argc -= optind;
105 	argv += optind;
106 
107 	if (!*argv)
108 		usage();
109 
110 	if (!f_cat && !f_how && !f_where)
111 		if (!isatty(1))
112 			f_cat = 1;
113 		else if ((pager = getenv("PAGER")) != NULL)
114 			pager = check_pager(pager);
115 		else
116 			pager = _PATH_PAGER;
117 
118 	/* Read the configuration file. */
119 	config(conffile);
120 
121 	/* Get the machine type. */
122 	if ((machine = getenv("MACHINE")) == NULL)
123 		machine = MACHINE;
124 
125 	/* If there's no _default list, create an empty one. */
126 	if ((defp = getlist("_default")) == NULL)
127 		defp = addlist("_default");
128 
129 	/*
130 	 * 1: If the user specified a MANPATH variable, or set the -M
131 	 *    option, we replace the _default list with the user's list,
132 	 *    appending the entries in the _subdir list and the machine.
133 	 */
134 	if (p_path == NULL)
135 		p_path = getenv("MANPATH");
136 	if (p_path != NULL) {
137 		while ((e_defp = defp->list.tqh_first) != NULL) {
138 			free(e_defp->s);
139 			TAILQ_REMOVE(&defp->list, e_defp, q);
140 		}
141 		for (p = strtok(p_path, ":");
142 		    p != NULL; p = strtok(NULL, ":")) {
143 			slashp = p[strlen(p) - 1] == '/' ? "" : "/";
144 			e_subp = (subp = getlist("_subdir")) == NULL ?
145 			    NULL : subp->list.tqh_first;
146 			for (; e_subp != NULL; e_subp = e_subp->q.tqe_next) {
147 				(void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,}",
148 				    p, slashp, e_subp->s, machine);
149 				if ((ep = malloc(sizeof(ENTRY))) == NULL ||
150 				    (ep->s = strdup(buf)) == NULL)
151 					err(1, NULL);
152 				TAILQ_INSERT_TAIL(&defp->list, ep, q);
153 			}
154 		}
155 	}
156 
157 	/*
158 	 * 2: If the user did not specify MANPATH, -M or a section, rewrite
159 	 *    the _default list to include the _subdir list and the machine.
160 	 */
161 	if (argv[1] == NULL)
162 		section = NULL;
163 	else if ((section = getlist(*argv)) != NULL)
164 		++argv;
165 	if (p_path == NULL && section == NULL) {
166 		defnewp = addlist("_default_new");
167 		e_defp =
168 		    defp->list.tqh_first == NULL ? NULL : defp->list.tqh_first;
169 		for (; e_defp != NULL; e_defp = e_defp->q.tqe_next) {
170 			slashp =
171 			    e_defp->s[strlen(e_defp->s) - 1] == '/' ? "" : "/";
172 			e_subp = (subp = getlist("_subdir")) == NULL ?
173 			    NULL : subp->list.tqh_first;
174 			for (; e_subp != NULL; e_subp = e_subp->q.tqe_next) {
175 				(void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,}",
176 				e_defp->s, slashp, e_subp->s, machine);
177 				if ((ep = malloc(sizeof(ENTRY))) == NULL ||
178 				    (ep->s = strdup(buf)) == NULL)
179 					err(1, NULL);
180 				TAILQ_INSERT_TAIL(&defnewp->list, ep, q);
181 			}
182 		}
183 		defp = getlist("_default");
184 		while ((e_defp = defp->list.tqh_first) != NULL) {
185 			free(e_defp->s);
186 			TAILQ_REMOVE(&defp->list, e_defp, q);
187 		}
188 		free(defp->s);
189 		TAILQ_REMOVE(&head, defp, q);
190 		defnewp = getlist("_default_new");
191 		free(defnewp->s);
192 		defnewp->s = "_default";
193 		defp = defnewp;
194 	}
195 
196 	/*
197 	 * 3: If the user set the -m option, insert the user's list before
198 	 *    whatever list we have, again appending the _subdir list and
199 	 *    the machine.
200 	 */
201 	if (p_add != NULL)
202 		for (p = strtok(p_add, ":"); p != NULL; p = strtok(NULL, ":")) {
203 			slashp = p[strlen(p) - 1] == '/' ? "" : "/";
204 			e_subp = (subp = getlist("_subdir")) == NULL ?
205 			    NULL : subp->list.tqh_first;
206 			for (; e_subp != NULL; e_subp = e_subp->q.tqe_next) {
207 				(void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,}",
208 				    p, slashp, e_subp->s, machine);
209 				if ((ep = malloc(sizeof(ENTRY))) == NULL ||
210 				    (ep->s = strdup(buf)) == NULL)
211 					err(1, NULL);
212 				TAILQ_INSERT_HEAD(&defp->list, ep, q);
213 			}
214 		}
215 
216 	/*
217 	 * 4: If none of MANPATH, -M, or -m were specified, and a section was,
218 	 *    rewrite the section's paths (if they have a trailing slash) to
219 	 *    append the _subdir list and the machine.  This then becomes the
220 	 *    _default list.
221 	 */
222 	if (p_path == NULL && p_add == NULL && section != NULL) {
223 		sectnewp = addlist("_section_new");
224 		for (e_sectp = section->list.tqh_first;
225 		    e_sectp != NULL; e_sectp = e_sectp->q.tqe_next) {
226 			if (e_sectp->s[strlen(e_sectp->s) - 1] != '/') {
227 				(void)snprintf(buf, sizeof(buf),
228 				    "%s{/%s,}", e_sectp->s, machine);
229 				if ((ep = malloc(sizeof(ENTRY))) == NULL ||
230 				    (ep->s = strdup(buf)) == NULL)
231 					err(1, NULL);
232 				TAILQ_INSERT_TAIL(&sectnewp->list, ep, q);
233 				continue;
234 			}
235 			e_subp = (subp = getlist("_subdir")) == NULL ?
236 			    NULL : subp->list.tqh_first;
237 			for (; e_subp != NULL; e_subp = e_subp->q.tqe_next) {
238 				(void)snprintf(buf, sizeof(buf), "%s%s{/%s,}",
239 				    e_sectp->s, e_subp->s, machine);
240 				if ((ep = malloc(sizeof(ENTRY))) == NULL ||
241 				    (ep->s = strdup(buf)) == NULL)
242 					err(1, NULL);
243 				TAILQ_INSERT_TAIL(&sectnewp->list, ep, q);
244 			}
245 		}
246 		sectnewp->s = section->s;
247 		defp = sectnewp;
248 		TAILQ_REMOVE(&head, section, q);
249 	}
250 
251 	/*
252 	 * 5: Search for the files.  Set up an interrupt handler, so the
253 	 *    temporary files go away.
254 	 */
255 	(void)signal(SIGINT, onsig);
256 	(void)signal(SIGHUP, onsig);
257 
258 	memset(&pg, 0, sizeof(pg));
259 	for (found = 0; *argv; ++argv)
260 		if (manual(*argv, defp, &pg))
261 			found = 1;
262 
263 	/* 6: If nothing found, we're done. */
264 	if (!found) {
265 		(void)cleanup();
266 		exit (1);
267 	}
268 
269 	/* 7: If it's simple, display it fast. */
270 	if (f_cat) {
271 		for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
272 			if (**ap == '\0')
273 				continue;
274 			cat(*ap);
275 		}
276 		exit (cleanup());
277 	}
278 	if (f_how) {
279 		for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
280 			if (**ap == '\0')
281 				continue;
282 			how(*ap);
283 		}
284 		exit(cleanup());
285 	}
286 	if (f_where) {
287 		for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
288 			if (**ap == '\0')
289 				continue;
290 			(void)printf("%s\n", *ap);
291 		}
292 		exit(cleanup());
293 	}
294 
295 	/*
296 	 * 8: We display things in a single command; build a list of things
297 	 *    to display.
298 	 */
299 	for (ap = pg.gl_pathv, len = strlen(pager) + 1; *ap != NULL; ++ap) {
300 		if (**ap == '\0')
301 			continue;
302 		len += strlen(*ap) + 1;
303 	}
304 	if ((cmd = malloc(len)) == NULL) {
305 		warn(NULL);
306 		(void)cleanup();
307 		exit(1);
308 	}
309 	p = cmd;
310 	len = strlen(pager);
311 	memmove(p, pager, len);
312 	p += len;
313 	*p++ = ' ';
314 	for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
315 		if (**ap == '\0')
316 			continue;
317 		len = strlen(*ap);
318 		memmove(p, *ap, len);
319 		p += len;
320 		*p++ = ' ';
321 	}
322 	*p = '\0';
323 
324 	/* Use system(3) in case someone's pager is "pager arg1 arg2". */
325 	(void)system(cmd);
326 
327 	exit(cleanup());
328 }
329 
330 /*
331  * manual --
332  *	Search the manuals for the pages.
333  */
334 static int
335 manual(page, tag, pg)
336 	char *page;
337 	TAG *tag;
338 	glob_t *pg;
339 {
340 	ENTRY *ep, *e_sufp, *e_tag;
341 	TAG *missp, *sufp;
342 	int anyfound, cnt, found;
343 	char *p, buf[128];
344 
345 	anyfound = 0;
346 	buf[0] = '*';
347 
348 	/* For each element in the list... */
349 	e_tag = tag == NULL ? NULL : tag->list.tqh_first;
350 	for (; e_tag != NULL; e_tag = e_tag->q.tqe_next) {
351 		(void)snprintf(buf, sizeof(buf), "%s/%s.*", e_tag->s, page);
352 		if (glob(buf,
353 		    GLOB_APPEND | GLOB_BRACE | GLOB_NOSORT | GLOB_QUOTE,
354 		    NULL, pg)) {
355 			warn("globbing");
356 			(void)cleanup();
357 			exit(1);
358 		}
359 		if (pg->gl_matchc == 0)
360 			continue;
361 
362 		/* Find out if it's really a man page. */
363 		for (cnt = pg->gl_pathc - pg->gl_matchc;
364 		    cnt < pg->gl_pathc; ++cnt) {
365 
366 			/*
367 			 * Try the _suffix key words first.
368 			 *
369 			 * XXX
370 			 * Older versions of man.conf didn't have the suffix
371 			 * key words, it was assumed that everything was a .0.
372 			 * We just test for .0 first, it's fast and probably
373 			 * going to hit.
374 			 */
375 			(void)snprintf(buf, sizeof(buf), "*/%s.0", page);
376 			if (!fnmatch(buf, pg->gl_pathv[cnt], 0))
377 				goto next;
378 
379 			e_sufp = (sufp = getlist("_suffix")) == NULL ?
380 			    NULL : sufp->list.tqh_first;
381 			for (found = 0;
382 			    e_sufp != NULL; e_sufp = e_sufp->q.tqe_next) {
383 				(void)snprintf(buf,
384 				     sizeof(buf), "*/%s%s", page, e_sufp->s);
385 				if (!fnmatch(buf, pg->gl_pathv[cnt], 0)) {
386 					found = 1;
387 					break;
388 				}
389 			}
390 			if (found)
391 				goto next;
392 
393 			/* Try the _build key words next. */
394 			e_sufp = (sufp = getlist("_build")) == NULL ?
395 			    NULL : sufp->list.tqh_first;
396 			for (found = 0;
397 			    e_sufp != NULL; e_sufp = e_sufp->q.tqe_next) {
398 				for (p = e_sufp->s;
399 				    *p != '\0' && !isspace(*p); ++p);
400 				if (*p == '\0')
401 					continue;
402 				*p = '\0';
403 				(void)snprintf(buf,
404 				     sizeof(buf), "*/%s%s", page, e_sufp->s);
405 				if (!fnmatch(buf, pg->gl_pathv[cnt], 0)) {
406 					if (!f_where)
407 						build_page(p + 1,
408 						    &pg->gl_pathv[cnt]);
409 					*p = ' ';
410 					found = 1;
411 					break;
412 				}
413 				*p = ' ';
414 			}
415 			if (found) {
416 next:				anyfound = 1;
417 				if (!f_all) {
418 					/* Delete any other matches. */
419 					while (++cnt< pg->gl_pathc)
420 						pg->gl_pathv[cnt] = "";
421 					break;
422 				}
423 				continue;
424 			}
425 
426 			/* It's not a man page, forget about it. */
427 			pg->gl_pathv[cnt] = "";
428 		}
429 
430 		if (anyfound && !f_all)
431 			break;
432 	}
433 
434 	/* If not found, enter onto the missing list. */
435 	if (!anyfound) {
436 		if ((missp = getlist("_missing")) == NULL)
437 			missp = addlist("_missing");
438 		if ((ep = malloc(sizeof(ENTRY))) == NULL ||
439 		    (ep->s = strdup(page)) == NULL) {
440 			warn(NULL);
441 			(void)cleanup();
442 			exit(1);
443 		}
444 		TAILQ_INSERT_TAIL(&missp->list, ep, q);
445 	}
446 	return (anyfound);
447 }
448 
449 /*
450  * build_page --
451  *	Build a man page for display.
452  */
453 static void
454 build_page(fmt, pathp)
455 	char *fmt, **pathp;
456 {
457 	static int warned;
458 	ENTRY *ep;
459 	TAG *intmpp;
460 	int fd;
461 	char buf[MAXPATHLEN], cmd[MAXPATHLEN], tpath[sizeof(_PATH_TMP)];
462 
463 	/* Let the user know this may take awhile. */
464 	if (!warned) {
465 		warned = 1;
466 		warnx("Formatting manual page...");
467 	}
468 
469 	/* Add a remove-when-done list. */
470 	if ((intmpp = getlist("_intmp")) == NULL)
471 		intmpp = addlist("_intmp");
472 
473 	/* Move to the printf(3) format string. */
474 	for (; *fmt && isspace(*fmt); ++fmt);
475 
476 	/*
477 	 * Get a temporary file and build a version of the file
478 	 * to display.  Replace the old file name with the new one.
479 	 */
480 	(void)strcpy(tpath, _PATH_TMP);
481 	if ((fd = mkstemp(tpath)) == -1) {
482 		warn("%s", tpath);
483 		(void)cleanup();
484 		exit(1);
485 	}
486 	(void)snprintf(buf, sizeof(buf), "%s > %s", fmt, tpath);
487 	(void)snprintf(cmd, sizeof(cmd), buf, *pathp);
488 	(void)system(cmd);
489 	(void)close(fd);
490 	if ((*pathp = strdup(tpath)) == NULL) {
491 		warn(NULL);
492 		(void)cleanup();
493 		exit(1);
494 	}
495 
496 	/* Link the built file into the remove-when-done list. */
497 	if ((ep = malloc(sizeof(ENTRY))) == NULL) {
498 		warn(NULL);
499 		(void)cleanup();
500 		exit(1);
501 	}
502 	ep->s = *pathp;
503 	TAILQ_INSERT_TAIL(&intmpp->list, ep, q);
504 }
505 
506 /*
507  * how --
508  *	display how information
509  */
510 static void
511 how(fname)
512 	char *fname;
513 {
514 	FILE *fp;
515 
516 	int lcnt, print;
517 	char *p, buf[256];
518 
519 	if (!(fp = fopen(fname, "r"))) {
520 		warn("%s", fname);
521 		(void)cleanup();
522 		exit (1);
523 	}
524 #define	S1	"SYNOPSIS"
525 #define	S2	"S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS"
526 #define	D1	"DESCRIPTION"
527 #define	D2	"D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN"
528 	for (lcnt = print = 0; fgets(buf, sizeof(buf), fp);) {
529 		if (!strncmp(buf, S1, sizeof(S1) - 1) ||
530 		    !strncmp(buf, S2, sizeof(S2) - 1)) {
531 			print = 1;
532 			continue;
533 		} else if (!strncmp(buf, D1, sizeof(D1) - 1) ||
534 		    !strncmp(buf, D2, sizeof(D2) - 1))
535 			return;
536 		if (!print)
537 			continue;
538 		if (*buf == '\n')
539 			++lcnt;
540 		else {
541 			for(; lcnt; --lcnt)
542 				(void)putchar('\n');
543 			for (p = buf; isspace(*p); ++p);
544 			(void)fputs(p, stdout);
545 		}
546 	}
547 	(void)fclose(fp);
548 }
549 
550 /*
551  * cat --
552  *	cat out the file
553  */
554 static void
555 cat(fname)
556 	char *fname;
557 {
558 	int fd, n;
559 	char buf[2048];
560 
561 	if ((fd = open(fname, O_RDONLY, 0)) < 0) {
562 		warn("%s", fname);
563 		(void)cleanup();
564 		exit(1);
565 	}
566 	while ((n = read(fd, buf, sizeof(buf))) > 0)
567 		if (write(STDOUT_FILENO, buf, n) != n) {
568 			warn("write");
569 			(void)cleanup();
570 			exit (1);
571 		}
572 	if (n == -1) {
573 		warn("read");
574 		(void)cleanup();
575 		exit(1);
576 	}
577 	(void)close(fd);
578 }
579 
580 /*
581  * check_pager --
582  *	check the user supplied page information
583  */
584 static char *
585 check_pager(name)
586 	char *name;
587 {
588 	char *p, *save;
589 
590 	/*
591 	 * if the user uses "more", we make it "more -s"; watch out for
592 	 * PAGER = "mypager /usr/ucb/more"
593 	 */
594 	for (p = name; *p && !isspace(*p); ++p);
595 	for (; p > name && *p != '/'; --p);
596 	if (p != name)
597 		++p;
598 
599 	/* make sure it's "more", not "morex" */
600 	if (!strncmp(p, "more", 4) && (!p[4] || isspace(p[4]))){
601 		save = name;
602 		/* allocate space to add the "-s" */
603 		if (!(name =
604 		    malloc((u_int)(strlen(save) + sizeof("-s") + 1))))
605 			err(1, NULL);
606 		(void)sprintf(name, "%s %s", save, "-s");
607 	}
608 	return(name);
609 }
610 
611 /*
612  * jump --
613  *	strip out flag argument and jump
614  */
615 static void
616 jump(argv, flag, name)
617 	char **argv, *flag, *name;
618 {
619 	char **arg;
620 
621 	argv[0] = name;
622 	for (arg = argv + 1; *arg; ++arg)
623 		if (!strcmp(*arg, flag))
624 			break;
625 	for (; *arg; ++arg)
626 		arg[0] = arg[1];
627 	execvp(name, argv);
628 	(void)fprintf(stderr, "%s: Command not found.\n", name);
629 	exit(1);
630 }
631 
632 /*
633  * onsig --
634  *	If signaled, delete the temporary files.
635  */
636 static void
637 onsig(signo)
638 	int signo;
639 {
640 	(void)cleanup();
641 
642 	(void)signal(signo, SIG_DFL);
643 	(void)kill(getpid(), signo);
644 
645 	/* NOTREACHED */
646 	exit (1);
647 }
648 
649 /*
650  * cleanup --
651  *	Clean up temporary files, show any error messages.
652  */
653 static int
654 cleanup()
655 {
656 	TAG *intmpp, *missp;
657 	ENTRY *ep;
658 	int rval;
659 
660 	rval = 0;
661 	ep = (missp = getlist("_missing")) == NULL ?
662 	    NULL : missp->list.tqh_first;
663 	if (ep != NULL)
664 		for (; ep != NULL; ep = ep->q.tqe_next) {
665 			warnx("no entry for %s in the manual.", ep->s);
666 			rval = 1;
667 		}
668 
669 	ep = (intmpp = getlist("_intmp")) == NULL ?
670 	    NULL : intmpp->list.tqh_first;
671 	for (; ep != NULL; ep = ep->q.tqe_next)
672 		(void)unlink(ep->s);
673 	return (rval);
674 }
675 
676 /*
677  * usage --
678  *	print usage message and die
679  */
680 static void
681 usage()
682 {
683 	(void)fprintf(stderr,
684     "usage: man [-achw] [-C file] [-M path] [-m path] [section] title ...\n");
685 	exit(1);
686 }
687