xref: /original-bsd/usr.bin/man/man.c (revision 2301fdfb)
1 /*
2  * Copyright (c) 1987 Regents of the University of California.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms are permitted
6  * provided that the above copyright notice and this paragraph are
7  * duplicated in all such forms and that any documentation,
8  * advertising materials, and other materials related to such
9  * distribution and use acknowledge that the software was developed
10  * by the University of California, Berkeley.  The name of the
11  * University may not be used to endorse or promote products derived
12  * from this software without specific prior written permission.
13  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
14  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
15  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
16  */
17 
18 #ifndef lint
19 char copyright[] =
20 "@(#) Copyright (c) 1987 Regents of the University of California.\n\
21  All rights reserved.\n";
22 #endif /* not lint */
23 
24 #ifndef lint
25 static char sccsid[] = "@(#)man.c	5.17 (Berkeley) 06/29/88";
26 #endif /* not lint */
27 
28 #include <sys/param.h>
29 #include <sys/file.h>
30 #include <ctype.h>
31 
32 #define	DEF_PAGER	"/usr/ucb/more -s"
33 #define	DEF_PATH	"/usr/man:/usr/new/man:/usr/local/man"
34 #define	LOCAL_PATH	"/usr/local/man"
35 #define	NEW_PATH	"/usr/new/man"
36 
37 #define	NO	0
38 #define	YES	1
39 
40 static char	*command,		/* command buffer */
41 		*defpath,		/* default search path */
42 		*locpath,		/* local search path */
43 		*machine,		/* machine type */
44 		*manpath,		/* current search path */
45 		*newpath,		/* new search path */
46 		*pager,			/* requested pager */
47 		how;			/* how to display */
48 
49 #define	ALL	0x1			/* show all man pages */
50 #define	CAT	0x2			/* copy file to stdout */
51 #define	WHERE	0x4			/* just tell me where */
52 
53 main(argc, argv)
54 	int argc;
55 	register char **argv;
56 {
57 	extern char *optarg;
58 	extern int optind;
59 	int ch;
60 	char *getenv(), *malloc();
61 
62 	while ((ch = getopt(argc, argv, "-M:P:afkw")) != EOF)
63 		switch((char)ch) {
64 		case '-':
65 			how |= CAT;
66 			break;
67 		case 'M':
68 		case 'P':		/* backward compatibility */
69 			defpath = optarg;
70 			break;
71 		case 'a':
72 			how |= ALL;
73 			break;
74 		/*
75 		 * "man -f" and "man -k" are backward contemptible,
76 		 * undocumented ways of calling whatis(1) and apropos(1).
77 		 */
78 		case 'f':
79 			jump(argv, "-f", "whatis");
80 			/*NOTREACHED*/
81 		case 'k':
82 			jump(argv, "-k", "apropos");
83 			/*NOTREACHED*/
84 		/*
85 		 * Deliberately undocumented; really only useful when
86 		 * you're moving man pages around.  Not worth adding.
87 		 */
88 		case 'w':
89 			how |= WHERE | ALL;
90 			break;
91 		case '?':
92 		default:
93 			usage();
94 		}
95 	argv += optind;
96 
97 	if (!*argv)
98 		usage();
99 
100 	if (!(how & CAT))
101 		if (!isatty(1))
102 			how |= CAT;
103 		else if (pager = getenv("PAGER")) {
104 			register char *p;
105 
106 			/*
107 			 * if the user uses "more", we make it "more -s"
108 			 * watch out for PAGER = "mypager /usr/ucb/more"
109 			 */
110 			for (p = pager; *p && !isspace(*p); ++p);
111 			for (; p > pager && *p != '/'; --p);
112 			if (p != pager)
113 				++p;
114 			/* make sure it's "more", not "morex" */
115 			if (!strncmp(p, "more", 4) && (!p[4] || isspace(p[4]))){
116 				char *opager = pager;
117 				/*
118 				 * allocate space to add the "-s"
119 				 */
120 				if (!(pager = malloc((u_int)(strlen(opager)
121 				    + sizeof("-s") + 1)))) {
122 					fputs("man: out of space.\n", stderr);
123 					exit(1);
124 				}
125 				(void)sprintf(pager, "%s %s", opager, "-s");
126 			}
127 		}
128 		else
129 			pager = DEF_PAGER;
130 	if (!(machine = getenv("MACHINE")))
131 		machine = MACHINE;
132 	if (!defpath && !(defpath = getenv("MANPATH")))
133 		defpath = DEF_PATH;
134 	locpath = LOCAL_PATH;
135 	newpath = NEW_PATH;
136 	man(argv);
137 	/* use system(3) in case someone's pager is "pager arg1 arg2" */
138 	if (command)
139 		(void)system(command);
140 	exit(0);
141 }
142 
143 typedef struct {
144 	char	*name, *msg;
145 } DIR;
146 static DIR	list1[] = {		/* section one list */
147 	"cat1", "1st",		"cat8", "8th",		"cat6", "6th",
148 	"cat.old", "old",	NULL, NULL,
149 },		list2[] = {		/* rest of the list */
150 	"cat2", "2nd",		"cat3", "3rd",		"cat4", "4th",
151 	"cat5", "5th", 		"cat7", "7th",		"cat3f", "3rd (F)",
152 	NULL, NULL,
153 },		list3[2];		/* single section */
154 
155 static
156 man(argv)
157 	char **argv;
158 {
159 	register char *p;
160 	DIR *section, *getsect();
161 	int res;
162 
163 	for (; *argv; ++argv) {
164 		manpath = defpath;
165 		section = NULL;
166 		switch(**argv) {
167 		case 'l':				/* local */
168 			/* support the "{l,local,n,new}###"  syntax */
169 			for (p = *argv; isalpha(*p); ++p);
170 			if (!strncmp(*argv, "l", p - *argv) ||
171 			    !strncmp(*argv, "local", p - *argv)) {
172 				++argv;
173 				manpath = locpath;
174 				section = getsect(p);
175 			}
176 			break;
177 		case 'n':				/* new */
178 			for (p = *argv; isalpha(*p); ++p);
179 			if (!strncmp(*argv, "n", p - *argv) ||
180 			    !strncmp(*argv, "new", p - *argv)) {
181 				++argv;
182 				manpath = newpath;
183 				section = getsect(p);
184 			}
185 			break;
186 		/*
187 		 * old isn't really a separate section of the manual,
188 		 * and its entries are all in a single directory.
189 		 */
190 		case 'o':				/* old */
191 			for (p = *argv; isalpha(*p); ++p);
192 			if (!strncmp(*argv, "o", p - *argv) ||
193 			    !strncmp(*argv, "old", p - *argv)) {
194 				++argv;
195 				list3[0] = list1[3];
196 				section = list3;
197 			}
198 			break;
199 		case '1': case '2': case '3': case '4':
200 		case '5': case '6': case '7': case '8':
201 			if (section = getsect(*argv))
202 				++argv;
203 		}
204 
205 		if (*argv) {
206 			if (section)
207 				res = manual(section, *argv);
208 			else {
209 				res = manual(list1, *argv);
210 				if (!res || (how & ALL))
211 					res += manual(list2, *argv);
212 			}
213 			if (res || how&WHERE)
214 				continue;
215 		}
216 
217 		fputs("man: ", stderr);
218 		if (*argv)
219 			fprintf(stderr, "no entry for %s in the ", *argv);
220 		else
221 			fputs("what do you want from the ", stderr);
222 		if (section)
223 			fprintf(stderr, "%s section of the ", section->msg);
224 		if (manpath == locpath)
225 			fputs("local ", stderr);
226 		else if (manpath == newpath)
227 			fputs("new ", stderr);
228 		if (*argv)
229 			fputs("manual.\n", stderr);
230 		else
231 			fputs("manual?\n", stderr);
232 		exit(1);
233 	}
234 }
235 
236 /*
237  * manual --
238  *	given a directory list and a file name find a file that
239  *	matches; check ${directory}/${dir}/{file name} and
240  *	${directory}/${dir}/${machine}/${file name}.
241  */
242 static
243 manual(section, name)
244 	DIR *section;
245 	char *name;
246 {
247 	register char *beg, *end;
248 	register DIR *dp;
249 	register int res;
250 	char fname[MAXPATHLEN + 1], *index();
251 
252 	for (beg = manpath, res = 0;; beg = end + 1) {
253 		if (end = index(beg, ':'))
254 			*end = '\0';
255 		for (dp = section; dp->name; ++dp) {
256 			(void)sprintf(fname, "%s/%s/%s.0", beg, dp->name, name);
257 			if (access(fname, R_OK)) {
258 				(void)sprintf(fname, "%s/%s/%s/%s.0", beg,
259 				    dp->name, machine, name);
260 				if (access(fname, R_OK))
261 					continue;
262 			}
263 			if (how & WHERE)
264 				printf("man: found in %s.\n", fname);
265 			else if (how & CAT)
266 				cat(fname);
267 			else
268 				add(fname);
269 			if (!(how & ALL))
270 				return(1);
271 			res = 1;
272 		}
273 		if (!end)
274 			return(res);
275 		*end = ':';
276 	}
277 	/*NOTREACHED*/
278 }
279 
280 /*
281  * cat --
282  *	cat out the file
283  */
284 static
285 cat(fname)
286 	char *fname;
287 {
288 	register int fd, n;
289 	char buf[BUFSIZ];
290 
291 	if (!(fd = open(fname, O_RDONLY, 0))) {
292 		perror("man: open");
293 		exit(1);
294 	}
295 	while ((n = read(fd, buf, sizeof(buf))) > 0)
296 		if (write(1, buf, n) != n) {
297 			perror("man: write");
298 			exit(1);
299 		}
300 	if (n == -1) {
301 		perror("man: read");
302 		exit(1);
303 	}
304 	(void)close(fd);
305 }
306 
307 /*
308  * add --
309  *	add a file name to the list for future paging
310  */
311 static
312 add(fname)
313 	char *fname;
314 {
315 	static u_int buflen;
316 	static int len;
317 	static char *cp;
318 	int flen;
319 	char *malloc(), *realloc(), *strcpy();
320 
321 	if (!command) {
322 		if (!(command = malloc(buflen = 1024))) {
323 			fputs("man: out of space.\n", stderr);
324 			exit(1);
325 		}
326 		len = strlen(strcpy(command, pager));
327 		cp = command + len;
328 	}
329 	flen = strlen(fname);
330 	if (len + flen + 2 > buflen) {		/* +2 == space, EOS */
331 		if (!(command = realloc(command, buflen += 1024))) {
332 			fputs("man: out of space.\n", stderr);
333 			exit(1);
334 		}
335 		cp = command + len;
336 	}
337 	*cp++ = ' ';
338 	len += flen + 1;			/* +1 = space */
339 	(void)strcpy(cp, fname);
340 	cp += flen;
341 }
342 
343 /*
344  * getsect --
345  *	return a point to the section structure for a particular suffix
346  */
347 static DIR *
348 getsect(s)
349 	char *s;
350 {
351 	switch(*s++) {
352 	case '1':
353 		if (!*s)
354 			return(list1);
355 		break;
356 	case '2':
357 		if (!*s) {
358 			list3[0] = list2[0];
359 			return(list3);
360 		}
361 		break;
362 	/* sect. 3 requests are for either section 3, or section 3[fF]. */
363 	case '3':
364 		if (!*s) {
365 			list3[0] = list2[1];
366 			return(list3);
367 		}
368 		else if ((*s == 'f'  || *s == 'F') && !*++s) {
369 			list3[0] = list2[5];
370 			return(list3);
371 		}
372 		break;
373 	case '4':
374 		if (!*s) {
375 			list3[0] = list2[2];
376 			return(list3);
377 		}
378 		break;
379 	case '5':
380 		if (!*s) {
381 			list3[0] = list2[3];
382 			return(list3);
383 		}
384 		break;
385 	case '6':
386 		if (!*s) {
387 			list3[0] = list1[2];
388 			return(list3);
389 		}
390 		break;
391 	case '7':
392 		if (!*s) {
393 			list3[0] = list2[4];
394 			return(list3);
395 		}
396 		break;
397 	case '8':
398 		if (!*s) {
399 			list3[0] = list1[1];
400 			return(list3);
401 		}
402 	}
403 	return((DIR *)NULL);
404 }
405 
406 /*
407  * jump --
408  *	strip out flag argument and jump
409  */
410 static
411 jump(argv, flag, name)
412 	char **argv, *name;
413 	register char *flag;
414 {
415 	register char **arg;
416 
417 	argv[0] = name;
418 	for (arg = argv + 1; *arg; ++arg)
419 		if (!strcmp(*arg, flag))
420 			break;
421 	for (; *arg; ++arg)
422 		arg[0] = arg[1];
423 	execvp(name, argv);
424 	fprintf(stderr, "%s: Command not found.\n", name);
425 	exit(1);
426 }
427 
428 /*
429  * usage --
430  *	print usage and die
431  */
432 static
433 usage()
434 {
435 	fputs("usage: man [-] [-a] [-M path] [section] title ...\n", stderr);
436 	exit(1);
437 }
438