xref: /csrg-svn/usr.bin/man/man.c (revision 33054)
131703Sbostic /*
231703Sbostic  * Copyright (c) 1987 Regents of the University of California.
3*33054Sbostic  * All rights reserved.
4*33054Sbostic  *
5*33054Sbostic  * Redistribution and use in source and binary forms are permitted
6*33054Sbostic  * provided that this notice is preserved and that due credit is given
7*33054Sbostic  * to the University of California at Berkeley. The name of the University
8*33054Sbostic  * may not be used to endorse or promote products derived from this
9*33054Sbostic  * software without specific prior written permission. This software
10*33054Sbostic  * is provided ``as is'' without express or implied warranty.
1131703Sbostic  */
1231703Sbostic 
1331703Sbostic #ifndef lint
1431703Sbostic char copyright[] =
1531703Sbostic "@(#) Copyright (c) 1987 Regents of the University of California.\n\
1631703Sbostic  All rights reserved.\n";
17*33054Sbostic #endif /* not lint */
1831703Sbostic 
1931703Sbostic #ifndef lint
20*33054Sbostic static char sccsid[] = "@(#)man.c	5.10 (Berkeley) 12/16/87";
21*33054Sbostic #endif /* not lint */
2231703Sbostic 
2331703Sbostic #include <sys/param.h>
2431703Sbostic #include <sys/file.h>
2531703Sbostic #include <ctype.h>
2631703Sbostic 
2731779Sbostic #define	DEF_PAGER	"/usr/ucb/more -s"
2831936Sbostic #define	DEF_PATH	"/usr/man:/usr/new/man:/usr/local/man"
2931936Sbostic 
3031713Sbostic #define	LOCAL_PATH	"/usr/local/man"
3131713Sbostic #define	LOCAL_NAME	"local"
3231936Sbostic 
3331936Sbostic #define	NEW_PATH	"/usr/new/man"
3431936Sbostic #define	NEW_NAME	"new"
3531936Sbostic 
3631713Sbostic #define	NO		0
3731713Sbostic #define	YES		1
3831703Sbostic 
3931703Sbostic #define	NO_SECTION	0
4031713Sbostic #define	S_THREEF	9
4131936Sbostic #define	S_OLD		10
4231713Sbostic 
4331778Sbostic /* this array maps a character (ex: '4') to an offset in stanlist */
4431713Sbostic #define	secno(x)	(seclist[(int)(x - '0')])
4531713Sbostic static int	seclist[] = { -1, 1, 4, 5, 6, 7, 3, 8, 2, -1, -1 };
4631713Sbostic 
4731713Sbostic /* sub directory list, ordered for searching */
4831778Sbostic typedef struct {
4931713Sbostic 	char	*name,
5031713Sbostic 		*msg;
5131713Sbostic } DIR;
5231713Sbostic 
5331778Sbostic DIR	stanlist[] = {		/* standard sub-directory list */
5431713Sbostic 	"notused", "",		"cat1", "1st",		"cat8", "8th",
5531713Sbostic 	"cat6", "6th",		"cat2", "2nd",		"cat3", "3rd",
5631713Sbostic 	"cat4", "4th",		"cat5", "5th", 		"cat7", "7th",
5731936Sbostic 	"cat3f", "3rd (F)",	"cat.old", "old",	NULL, NULL,
5831778Sbostic },	sec1list[] = {		/* section one list */
5931778Sbostic 	"notused", "",		"cat1", "1st",		"cat8", "8th",
6031936Sbostic 	"cat6", "6th",		"cat.old", "old",	NULL, NULL,
6131703Sbostic };
6231703Sbostic 
6331778Sbostic static DIR	*dirlist;		/* list of directories to search */
6432565Sbostic static int	nomore,			/* copy file to stdout */
6532565Sbostic 		where;			/* just tell me where */
6631713Sbostic static char	*defpath,		/* default search path */
6731713Sbostic 		*locpath,		/* local search path */
6831713Sbostic 		*machine,		/* machine type */
6931713Sbostic 		*manpath,		/* current search path */
7031936Sbostic 		*newpath,		/* new search path */
7131713Sbostic 		*pager;			/* requested pager */
7231703Sbostic 
7331703Sbostic main(argc, argv)
7431703Sbostic 	int	argc;
7531713Sbostic 	register char	**argv;
7631703Sbostic {
7731703Sbostic 	int	section;
7831703Sbostic 	char	**arg_start, **arg,
7931779Sbostic 		*getenv(), *malloc();
8031703Sbostic 
8131703Sbostic 	arg_start = argv;
8231703Sbostic 	for (--argc, ++argv; argc && (*argv)[0] == '-'; --argc, ++argv)
8331703Sbostic 		switch((*argv)[1]) {
8431703Sbostic 		case 0:			/* just write to stdout */
8531703Sbostic 			nomore = YES;
8631703Sbostic 			break;
8731703Sbostic 		case 'M':
8831703Sbostic 		case 'P':		/* backward compatibility */
8931703Sbostic 			if ((*argv)[2])
9031906Sbostic 				defpath = *argv + 2;
9131703Sbostic 			else {
9231703Sbostic 				if (argc < 2) {
9331703Sbostic 					fprintf(stderr, "%s: missing path\n", *argv);
9431703Sbostic 					exit(1);
9531703Sbostic 				}
9631703Sbostic 				--argc;
9731906Sbostic 				defpath = *++argv;
9831703Sbostic 			}
9931703Sbostic 			break;
10031703Sbostic 		/*
10131703Sbostic 		 * "man -f" and "man -k" are undocumented ways of calling
10231703Sbostic 		 * whatis(1) and apropos(1).  Just strip out the flag
10331703Sbostic 		 * argument and jump.
10431703Sbostic 		 */
10531703Sbostic 		case 'f':
10631703Sbostic 			for (arg = argv; arg[0] = arg[1]; ++arg);
10731703Sbostic 			*arg_start = "whatis";
10831703Sbostic 			execvp(*arg_start, arg_start);
10931703Sbostic 			fputs("whatis: Command not found.\n", stderr);
11031703Sbostic 			exit(1);
11131703Sbostic 		case 'k':
11231703Sbostic 			for (arg = argv; *arg = arg[1]; ++arg);
11331703Sbostic 			*arg_start = "apropos";
11431703Sbostic 			execvp(*arg_start, arg_start);
11531703Sbostic 			fputs("apropos: Command not found.\n", stderr);
11631703Sbostic 			exit(1);
11732565Sbostic 		case 'w':
11832565Sbostic 			/*
11932565Sbostic 			 * Deliberately undocumented; really only useful when
12032565Sbostic 			 * you're moving man pages around.  Not worth adding.
12132565Sbostic 			 */
12232565Sbostic 			where = YES;
12332565Sbostic 			break;
12431703Sbostic 		case '?':
12531703Sbostic 		default:
12632614Sbostic 			fprintf(stderr, "man: illegal option -- %c\n", (*argv)[1]);
12731703Sbostic 			usage();
12831703Sbostic 		}
12931703Sbostic 	if (!argc)
13031703Sbostic 		usage();
13131703Sbostic 
13231703Sbostic 	if (!nomore)
13331703Sbostic 		if (!isatty(1))
13431703Sbostic 			nomore = YES;
13531779Sbostic 		else if (pager = getenv("PAGER")) {
13631779Sbostic 			register char	*C;
13731779Sbostic 
13831779Sbostic 			/*
13931779Sbostic 			 * if the user uses "more", we make it "more -s"
14031779Sbostic 			 * watch out for PAGER = "mypager /usr/ucb/more"
14131779Sbostic 			 */
14231779Sbostic 			for (C = pager; *C && !isspace(*C); ++C);
14331779Sbostic 			for (; C > pager && *C != '/'; --C);
14431779Sbostic 			if (C != pager)
14531779Sbostic 				++C;
14631779Sbostic 			/* make sure it's "more", not "morex" */
14731779Sbostic 			if (!strncmp(C, "more", 4) && (!C[4] || isspace(C[4]))) {
14831779Sbostic 				C += 4;
14931779Sbostic 				/*
15031779Sbostic 				 * sizeof is 1 more than # of chars, so,
15131779Sbostic 				 * allocate for the rest of the PAGER
15231779Sbostic 				 * environment variable, a space, and the EOS.
15331779Sbostic 				 */
15431779Sbostic 				if (!(pager = malloc((u_int)(strlen(C) + sizeof(DEF_PAGER) + 1)))) {
15531779Sbostic 					fputs("man: out of space.\n", stderr);
15631779Sbostic 					exit(1);
15731779Sbostic 				}
15831779Sbostic 				(void)sprintf(pager, "%s %s", DEF_PAGER, C);
15931779Sbostic 			}
16031779Sbostic 		}
16131779Sbostic 		else
16231703Sbostic 			pager = DEF_PAGER;
16331703Sbostic 	if (!(machine = getenv("MACHINE")))
16431703Sbostic 		machine = MACHINE;
16531713Sbostic 	if (!defpath && !(defpath = getenv("MANPATH")))
16631713Sbostic 		defpath = DEF_PATH;
16731713Sbostic 	locpath = LOCAL_PATH;
16831936Sbostic 	newpath = NEW_PATH;
16931713Sbostic 	for (; *defpath && *defpath == ':'; ++defpath);
17031703Sbostic 
17131778Sbostic 	/* Gentlemen... start your kludges! */
17231703Sbostic 	for (; *argv; ++argv) {
17331703Sbostic 		section = NO_SECTION;
17431906Sbostic 		manpath = defpath;
17531703Sbostic 		switch(**argv) {
17631778Sbostic 		/*
17731936Sbostic 		 * Section 1 requests are really for section 1, 6, 8, in the
17831936Sbostic 		 * standard, local and new directories and section old. Since
17931936Sbostic 		 * old isn't broken up into a directory of cat[1-8], we just
18031936Sbostic 		 * treat it like a subdirectory of the others.
18131778Sbostic 		 */
18231936Sbostic 		case '1': case '2': case '4': case '5': case '6':
18331713Sbostic 		case '7': case '8':
18431703Sbostic 			if (!(*argv)[1]) {
18531713Sbostic 				section = secno((*argv)[0]);
18631713Sbostic 				goto numtest;
18731713Sbostic 			}
18831713Sbostic 			break;
18931778Sbostic 
19031778Sbostic 		/* sect. 3 requests are for either section 3, or section 3F. */
19131713Sbostic 		case '3':
19231713Sbostic 			if (!(*argv)[1]) {			/* "3" */
19331713Sbostic 				section = secno((*argv)[0]);
19431713Sbostic numtest:			if (!*++argv) {
19531778Sbostic 					fprintf(stderr, "man: what do you want from the %s section of the manual?\n", stanlist[section].msg);
19631713Sbostic 					exit(1);
19731713Sbostic 				}
19831713Sbostic 			}					/* "3[fF]" */
19931936Sbostic 			else if (((*argv)[1] == 'f'  || (*argv)[1] == 'F') && !(*argv)[2]) {
20031713Sbostic 				section = S_THREEF;
20131703Sbostic 				if (!*++argv) {
20231778Sbostic 					fprintf(stderr, "man: what do you want from the %s section of the manual?\n", stanlist[S_THREEF].msg);
20331703Sbostic 					exit(1);
20431703Sbostic 				}
20531703Sbostic 			}
20631703Sbostic 			break;
20731778Sbostic 		/*
20831936Sbostic 		 * Requests for the new or local sections can have subsection
20931936Sbostic 		 * numbers appended to them, i.e. "local3" is really
21031936Sbostic 		 * local/cat3.
21131778Sbostic 		 */
21231713Sbostic 		case 'l':					/* local */
21331713Sbostic 			if (!(*argv)[1])			/* "l" */
21431713Sbostic 				section = NO_SECTION;		/* "l2" */
21531713Sbostic 			else if (isdigit((*argv)[1]) && !(*argv)[2])
21631713Sbostic 				section = secno((*argv)[1]);
21731713Sbostic 			else {
21831713Sbostic 				int	lex;
21931713Sbostic 				lex = strcmp(LOCAL_NAME, *argv);
22031713Sbostic 				if (!lex)			/* "local" */
22131713Sbostic 					section = NO_SECTION;	/* "local2" */
22231713Sbostic 				else if (lex < 0 && isdigit((*argv)[sizeof(LOCAL_NAME) - 1]) && !(*argv)[sizeof(LOCAL_NAME)])
22331713Sbostic 					section = secno((*argv)[sizeof(LOCAL_NAME) - 1]);
22431713Sbostic 				else
22531713Sbostic 					break;
22631703Sbostic 			}
22731713Sbostic 			if (!*++argv) {
22831713Sbostic 				fputs("man: what do you want from the local section of the manual?\n", stderr);
22931713Sbostic 				exit(1);
23031713Sbostic 			}
23131713Sbostic 			manpath = locpath;
23231703Sbostic 			break;
23331713Sbostic 		case 'n':					/* new */
23431936Sbostic 			if (!(*argv)[1])			/* "n" */
23531936Sbostic 				section = NO_SECTION;		/* "n2" */
23631936Sbostic 			else if (isdigit((*argv)[1]) && !(*argv)[2])
23731936Sbostic 				section = secno((*argv)[1]);
23831936Sbostic 			else {
23931936Sbostic 				int	lex;
24031936Sbostic 				lex = strcmp(NEW_NAME, *argv);
24131936Sbostic 				if (!lex)			/* "new" */
24231936Sbostic 					section = NO_SECTION;	/* "new2" */
24331936Sbostic 				else if (lex < 0 && isdigit((*argv)[sizeof(NEW_NAME) - 1]) && !(*argv)[sizeof(NEW_NAME)])
24431936Sbostic 					section = secno((*argv)[sizeof(NEW_NAME) - 1]);
24531936Sbostic 				else
24631936Sbostic 					break;
24731703Sbostic 			}
24831936Sbostic 			if (!*++argv) {
24931936Sbostic 				fputs("man: what do you want from the new section of the manual?\n", stderr);
25031936Sbostic 				exit(1);
25131936Sbostic 			}
25231936Sbostic 			manpath = newpath;
25331703Sbostic 			break;
25431713Sbostic 		case 'o':					/* old */
25531778Sbostic 			if (!(*argv)[1] || !strcmp(*argv, stanlist[S_OLD].msg)) {
25631713Sbostic 				section = S_OLD;
25731936Sbostic 				if (!*++argv) {
25831778Sbostic 					fprintf(stderr, "man: what do you want from the %s section of the manual?\n", stanlist[section].msg);
25931703Sbostic 					exit(1);
26031703Sbostic 				}
26131703Sbostic 			}
26231703Sbostic 			break;
26331703Sbostic 		}
26431936Sbostic 		if (section == 1) {
26531936Sbostic 			dirlist = sec1list;
26631936Sbostic 			section = NO_SECTION;
26731936Sbostic 		}
26831936Sbostic 		else
26931936Sbostic 			dirlist = stanlist;
27031778Sbostic 		/*
27131778Sbostic 		 * This is really silly, but I wanted to put out rational
27231778Sbostic 		 * errors, not just "I couldn't find it."  This if statement
27331778Sbostic 		 * knows an awful lot about what gets assigned to what in
27431778Sbostic 		 * the switch statement we just passed through.  Sorry.
27531778Sbostic 		 */
27632565Sbostic 		if (!manual(section, *argv) && !where)
27731713Sbostic 			if (manpath == locpath)
27831936Sbostic 				if (section == NO_SECTION)
27931936Sbostic 					fprintf(stderr, "No entry for %s in the local manual.\n", *argv);
28031936Sbostic 				else
28131936Sbostic 					fprintf(stderr, "No entry for %s in the %s section of the local manual.\n", *argv, stanlist[section].msg);
28231936Sbostic 			else if (manpath == newpath)
28331936Sbostic 				if (section == NO_SECTION)
28431936Sbostic 					fprintf(stderr, "No entry for %s in the new manual.\n", *argv);
28531936Sbostic 				else
28631936Sbostic 					fprintf(stderr, "No entry for %s in the %s section of the new manual.\n", *argv, stanlist[section].msg);
28731778Sbostic 			else if (dirlist == sec1list)
28831778Sbostic 				fprintf(stderr, "No entry for %s in the 1st section of the manual.\n", *argv);
28931713Sbostic 			else if (section == NO_SECTION)
29031713Sbostic 				fprintf(stderr, "No entry for %s in the manual.\n", *argv);
29131703Sbostic 			else
29231778Sbostic 				fprintf(stderr, "No entry for %s in the %s section of the manual.\n", *argv, stanlist[section].msg);
29331703Sbostic 	}
29431703Sbostic 	exit(0);
29531703Sbostic }
29631703Sbostic 
29731778Sbostic /*
29831778Sbostic  * manual --
29931778Sbostic  *	given a section number and a file name go through the directory
30031778Sbostic  *	list and find a file that matches.
30131778Sbostic  */
30231703Sbostic static
30331703Sbostic manual(section, name)
30431703Sbostic 	int	section;
30531703Sbostic 	char	*name;
30631703Sbostic {
30731713Sbostic 	register DIR	*dir;
30831713Sbostic 	register char	*beg, *end;
30931703Sbostic 	char	*index();
31031703Sbostic 
31131703Sbostic 	for (beg = manpath;; beg = end + 1) {
31231703Sbostic 		if (end = index(beg, ':'))
31331703Sbostic 			*end = '\0';
31431778Sbostic 		if (section == NO_SECTION)
31531778Sbostic 			for (dir = dirlist; (++dir)->name;) {
31631713Sbostic 				if (find(beg, dir->name, name))
31731703Sbostic 					return(YES);
31831778Sbostic 			}
31931778Sbostic 		else if (find(beg, stanlist[section].name, name))
32031703Sbostic 			return(YES);
32131703Sbostic 		if (!end)
32231703Sbostic 			return(NO);
32331703Sbostic 	}
32431703Sbostic 	/*NOTREACHED*/
32531703Sbostic }
32631703Sbostic 
32731778Sbostic /*
32831778Sbostic  * find --
32931778Sbostic  *	given a directory path, a sub-directory and a file name,
33031778Sbostic  *	see if a file exists in ${directory}/${dir}/{file name}
33131778Sbostic  *	or in ${directory}/${dir}/${machine}/${file name}.
33231778Sbostic  */
33331703Sbostic static
33431703Sbostic find(beg, dir, name)
33531703Sbostic 	char	*beg, *dir, *name;
33631703Sbostic {
33731703Sbostic 	char	fname[MAXPATHLEN + 1];
33831703Sbostic 
33931713Sbostic 	(void)sprintf(fname, "%s/%s/%s.0", beg, dir, name);
34032565Sbostic 	if (access(fname, R_OK)) {
34132565Sbostic 		(void)sprintf(fname, "%s/%s/%s/%s.0", beg, dir, machine, name);
34232565Sbostic 		if (access(fname, R_OK))
34332565Sbostic 			return(NO);
34431703Sbostic 	}
34532565Sbostic 	if (where)
34632565Sbostic 		printf("man: found in %s.\n", fname);
34732565Sbostic 	else
34831703Sbostic 		show(fname);
34932565Sbostic 	return(!where);
35031703Sbostic }
35131703Sbostic 
35231778Sbostic /*
35331778Sbostic  * show --
35431778Sbostic  *	display the file
35531778Sbostic  */
35631703Sbostic static
35731703Sbostic show(fname)
35831703Sbostic 	char	*fname;
35931703Sbostic {
36031703Sbostic 	register int	fd, n;
36131703Sbostic 	char	buf[BUFSIZ];
36231703Sbostic 
36331703Sbostic 	if (nomore) {
36431703Sbostic 		if (!(fd = open(fname, O_RDONLY, 0))) {
36531703Sbostic 			perror("man: open");
36631703Sbostic 			exit(1);
36731703Sbostic 		}
36831703Sbostic 		while ((n = read(fd, buf, sizeof(buf))) > 0)
36931703Sbostic 			if (write(1, buf, n) != n) {
37031703Sbostic 				perror("man: write");
37131703Sbostic 				exit(1);
37231703Sbostic 			}
37331703Sbostic 		if (n == -1) {
37431703Sbostic 			perror("man: read");
37531703Sbostic 			exit(1);
37631703Sbostic 		}
37731703Sbostic 		(void)close(fd);
37831703Sbostic 	}
37931703Sbostic 	else {
38031703Sbostic 		/*
38131703Sbostic 		 * use system(2) in case someone's pager is
38231703Sbostic 		 * "command arg1 arg2"
38331703Sbostic 		 */
38431703Sbostic 		(void)sprintf(buf, "%s %s", pager, fname);
38531703Sbostic 		(void)system(buf);
38631703Sbostic 	}
38731703Sbostic }
38831703Sbostic 
38931778Sbostic /*
39031778Sbostic  * usage --
39131778Sbostic  *	print out a usage message and die
39231778Sbostic  */
39331703Sbostic static
39431703Sbostic usage()
39531703Sbostic {
39631703Sbostic 	fputs("usage: man [-] [-M path] [section] title ...\n", stderr);
39731703Sbostic 	exit(1);
39831703Sbostic }
399