[net.sources] Directory tree peruser

mwm@ea.UUCP (07/06/84)

#N:ea:12000002:000:35402
ea!mwm    Jul  6 15:54:00 1984

echo README
sed 's/^	//' > README << 'All work and no play makes Jack a dull boy'
	Here, finally, is the file peruser I mentioned lo these many months ago.
	This creature started life as an on-line help system for v6. It rapidly
	added the ability to be a directory browser and a restricted shell. I
	recently tacked on the ability to be a menu system. Creeping Featurism
	Lives!
	
	Below (in shar format) you will find both the much-hacked source code, and
	the user documentation (yes, that dates from v6, too). Though not obvious,
	from the docs, the included code can function as:
	
		1) A file system browser, just invoked with a name that starts
			with "b".
		2) A menu system, if invoked as something that starts with "m"
			or "-m".
		3) A restricted shell, if invoked as "-" anything but "m".
		4) A help system, if invoked as anything else, and you have the
			help directory set up correctly.
	
	When running as a menu system, b checks for the file name defined by
	MENUFILE in the current directory, and displays it to the user. This
	feature may be more mis than otherwise, as I haven't tried using it.  It is
	punished by the Unix I/O system and b trying to run buffered output and
	still be able to stop at the drop of an interrupt key. If it can be made to
	work reasonably, b could be the basis for a VMS-like help system (Yeah!),
	with the additional nicety of not having to use a librarian to build help
	files.
	
	In it's current incarnation, b is 4.2 dependent. It uses the 4.2 directory
	routines, and assumes that it can keep the list of file names in memory.
	I've included the 4.2 compatibility library, but I haven't tested it. Those
	of you with small address spaces (or small memoried VAXen running AT&T
	Unix) will have to live with b occasionally exiting rudely, or fix it so
	that b will work with the directory on disk instead of in core.
	
		Good Luck,
		<mike
All work and no play makes Jack a dull boy
echo b.c
sed 's/^	//' > b.c << 'All work and no play makes Jack a dull boy'
	#
	/*
	 *
	 *	help - A program to list documentation and peruse file systems.
	 *
	 *	 	copyright (c) 1980 michael w. meyer
	 *
	 *			9/79 - mwm
	 *
	 *	put in code to handle moving around in the file 10/79 - mwm
	 *
	 *	added n flag, plus ability to turn flags off interactivly 12/79 - mwm
	 *
	 *	put in the !<number> hack when shelling out, plus made fpipe
	 *	a single routine, as opposed to being in both roff and pcat. 1/80 - mwm
	 *
	 *	make the mods for the restricted visual shell mode. 2/80 - mwm
	 *
	 *	made pflg on the default for background, and added info about
	 *	'$' to the help routine - 4/80 mwm
	 *
	 *	converted to use v7 system calls, added interaction with man,
	 *	and added a default file to look at if invoked w/out args. 8/80 - mwm
	 *
	 *	changed to read in the directory and use it if we can instead of
	 *	reading it in off disk everytime.  this should allow sorting and
	 *	possibly increase efficiency. (hope hope)  the sflg added to
	 *	toggle the sorting of directories.	ps -- 10/1/80
	 *
	 *	added the `?' flag to the list of flags. It gives the user info
	 *	regarding the flags.  - mwm 10/80
	 *
	 *	added code to get erase from termcap and the environment.
	 *	dwb -- 3/2/81
	 *
	 *	added 4.1bsd and 2.8bsd executable magic numbers. -jab 3/82
	 *
	 *	removed V6 code -- ps 7/83
	 *
	 *	change to 4.2 directory code, plus assume large address space.
	 *	The directory either goes in memory, or we exit.
	 *	Clean up various things that the creeping featurism has
	 *	added. - mwm 5/84
	 *
	 *	Added the PAGER environment variable check and the MENUFILE
	 *	magic. - mwm 6/84
	 */
	
	#include <stdio.h>
	#include <sys/types.h>
	#include <sys/dir.h>
	#include <sys/stat.h>
	#include <signal.h>
	#include <ctype.h>
	
	/* Things to change, depending on your site. */
	#define HELPFILE	"help"		/* file in ROOT to show the user */
	#define MENUFILE	".menu"		/* name of menu file in a directory */
	#define ROOT		"/usr/man/hel1"	/* place to start looking */
	#define CHAPST		"/usr/man/hel"	/* prefix for help chapters */
	#define DIRHUNK		128		/* # of dir entries to alloc at a time */
	#define HELPID		17		/* id of help user, if he exists */
	
	/* Things to leave alone */
	#define fdup(file)	dup(fileno(file))
	#define fdup2(f, o)	dup2(fileno(f), fileno(o))
	#define dot		"."
	#define LINEWIDTH	40
	#define BUFLEN		160
	#define NILL		(char *) NULL
	
	/* flags returned by type to doswitch */
	#define DIRECT	0
	#define EXEC	DIRECT + 1
	#define ROFF	EXEC + 1
	#define SPCL	ROFF + 1
	#define PDS	SPCL + 1
	#define ARCH	PDS + 1
	#define APL	ARCH + 1
	#define DEF	APL + 1
	#define OOPS	DEF + 1
	#define PACK	OOPS + 1
	#define EMPTY	PACK + 1
	#define COMPACT	EMPTY + 1
	
	/* the global control flags */
	char	tbuf[BUFSIZ];			/* a place to put env type strings */
	struct stat	Statbuf ;
	char	*erase = "\033\032\033\014\014",/* default erase string */
		*pager = 0 ,			/* program to page files */
		root[BUFLEN] = ROOT ,		/* the root of the mess */
		*ProgName ,			/* name I was invoked as */
		*format = "%3d: %-14.14s\0XXXXX" ;	/* Standard 14-char format */
	int	lines = 23 ,			/* reasonable defualt */
		rows = 80 ,			/* ditto */
		entries = 4,			/* default # of entries on a line */
		pflg = 1 ,			/* we want paging */
		nflg = 1 ,			/* nroff files for him?? */
		aflg = 0 ,			/* show "." files? */
		sflg = 1 ,			/* sort directory listings */
		mflg = 0 ,			/* menu explanation? */
		debug = 0 ,			/* true if being debugged */
		rflg = 0 ,			/* the "restricted shell" mode */
		sh = 0 ;			/* used to tell if we are a shell */
	
	struct direct *gdirp ;
	int	numen = DIRHUNK ;
	
	/*
	 * main - interact with the user, after/while invoking various
	 * routines.  It should be noted that cflg is a non-global flag,
	 * and is used to indicate that the current directory is no
	 * longer visible to the user, so a listing must be given to him.
	 */
	main(argc, argv) char **argv; {
		register char buffer[BUFLEN] ;
		FILE *file ;
		int page(), cat(), special(), reset(), cflg ;
		int pagelist(), list() ;
		char errbuf[BUFSIZ], outbuf[BUFSIZ] ;
	
		if (debug) fprintf(stderr, "entering main\n") ;
		ProgName = argv[0] ;
		/* Not portable, but we want to realloc it later... */
		gdirp = (struct direct *) malloc(DIRHUNK * sizeof(struct direct)) ;
		if (!debug) setbuf(stderr, errbuf) ;
		setbuf(stdout, outbuf) ;
		getenvinfo() ;
		startup(buffer, &file, argc, argv) ;
		if (debug) fprintf(stderr, "out of startup succesfully\n") ;
		if (doswitch(file, buffer, &cflg)) exit(0) ;
		signal(SIGINT, SIG_IGN) ;
		signal(SIGQUIT, SIG_IGN) ;
		for (;;) {
			if (file != NULL) fclose(file) ;
			if (cflg) {
				cflg = 0 ;
				call(pflg ? pagelist : list, 0) ;
				}
			fprintf(stderr, "\n\nSelect topic ('?' for help): ") ;
			fflush(stderr) ;
			if (gets(buffer) == NULL) finis() ;
			if (*buffer == '$') finis() ;
			if (!*buffer) continue ;
			if (*buffer == '?') {
				help() ;
				continue ;
				}
			if (*buffer == '!') {
				if (sh) {
					fprintf(stderr,
					"Sorry Charlie - can't call the shell!\n") ;
					continue ;
					}
				call(special, &buffer[1]) ;
				continue ;
				}
			if (*buffer == '~') {
				setflgs(buffer + 1) ;
				continue ;
				}
			if (isdigit(*buffer) && getfile(atoi(buffer), buffer)) {
				fprintf(stderr, "file number %s not found.\n", buffer) ;
				continue ;
				}
			if (*buffer == '/' && !buffer[1]) strcpy(buffer, root) ;
			if (debug) fprintf(stderr, "buffer = %s\n", buffer) ;
			if ((file = fopen(buffer, "r")) != NULL)
				(void) doswitch(file, buffer, &cflg) ;
			else {
				fprintf(stderr, "can't open file %s.\n", buffer) ;
				strcpy(buffer, dot) ;
				}
			}
		}
	/*
	 * startup, and the following function (findroot) handle
	 * arguments, and initialize files and globals based on them.
	 */
	startup(where, file, argc, argv)
		char *where, **argv; FILE **file; {
	
		register int count = argc ;
		register char **vec = argv, *tmpv ;
		int bomb = 0, sect = 0 ;
		char *name = NILL, *psect = "012345678ul" ;
		
		if (debug) fprintf(stderr, "entering startup\nbomb = %d\n", bomb) ;
		if (argc == 1) name = HELPFILE ;
		if (**vec == '-') {
			sh++ ;
			if ((*vec)[1] == 'm') mflg++ ;
			if (getuid() != HELPID) {
				strcpy(root, dot) ;
				strcpy(name, dot) ;
	/*			chroot(dot) ;	*/
				rflg++ ;
				sect-- ;
				}
			}
		else if (**vec == 'm') mflg++, rflg++ ;
		else if (**vec == 'b') {
			getdir(root) ;
			name = dot ;
			nflg = 0 ;
			aflg = 1 ;
			sflg = 0 ;
			sect-- ;
			}
		if (debug) fprintf(stderr, "parsing arguments...") ;
		while (--count) {
			tmpv = *++vec ;
			if (*tmpv == '-') {
				if (setflgs(++tmpv)) bomb++ ;
				}
			else if (*tmpv == '.' || *tmpv == '/') {
				if ((*file = fopen(tmpv, "r")) != (FILE *) NULL) {
					strcpy(where, tmpv) ;
					return ;
					}
				fprintf(stderr, "can't open file %s.\n", tmpv) ;
				exit(1) ;
				}
			else if (!tmpv[1] && index(psect, *tmpv) && sect != -1)
				sect = *tmpv ;
			else name = tmpv ;
			}
		if (debug) fprintf(stderr, "completed!\nbomb = %d\n", bomb) ;
		if (bomb) exit(1) ;
		if (!isatty(fileno(stdout))) pflg = 0 ;
		if (sect) {
			findroot(where, name, sect) ;
			if (debug) fprintf(stderr, "where = %s\n", where) ;
			if ((*file = fopen(where, "r")) != (FILE *) NULL) return ;
	# ifdef noman
			fprintf(stderr, "help not available for %s.\n", name) ;
	# else
			execlp("man", "man", name, NILL);
			fprintf(stderr, "couldn't exec man!\n") ;
	# endif
			exit(1) ;
			}
		else if (name != NILL) {
			findroot(where, name, -1) ;
			if (debug) fprintf(stderr, "where = %s\n", where) ;
			if ((*file = fopen(where, "r")) != (FILE *) NULL) return ;
			findroot(where, name, (int) '1') ;
			if (debug) fprintf(stderr, "where = %s\n", where) ;
			if ((*file = fopen(where, "r")) != (FILE *) NULL) return ;
	# ifdef noman
			fprintf(stderr, "help not available for %s.\n", name) ;
	# else
			execlp("man", "man", name, NILL);
			fprintf(stderr, "couldn't exec man!\n") ;
	# endif
			exit(1) ;
			}
		else {
			if ((*file = fopen(root, "r")) != (FILE *) NULL) {
				strcpy(where, root) ;
				return ;
				}
			fprintf(stderr, "can't open documentation root!\n") ;
			exit(1) ;
			}
		}
	
	findroot(where, what, sect) char *where, *what; {
		
		if (debug) fprintf(stderr, "entering findroot\n") ;
		if (sect == -1) {
			strcpy(where, root) ;
			where[strlen(root)] = '/' ;
			strcpy(where + strlen(root) + 1, what) ;
			return ;
			}
		if (what == (char *) NULL) sprintf(where, "%s%c", CHAPST, sect) ;
		else sprintf(where, "%s%c/%s.%c", CHAPST, sect, what, sect) ;
		if (debug) fprintf(stderr, "Where is %s\n", where) ;
		}
	/*
	 * getdir... return current directory. If something is wrong, e.g. 
	 * you are in a an orphan directory or /bin/pwd is missing, returns
	 * "/" as the current directory...
	 * The basic idea behind this routine came to me from jab. It was modified
	 * to follow the conventions of help, and my coding style. - mwm
	 */
	getdir(p) char *p; {
		FILE *infile, *outfile ;
		register int pid ;
	
		fpipe(&infile, &outfile) ;
		if (pid = fork()) {
			while (wait((int *) 0) != pid)
				;
			if (fgets(p, BUFLEN, infile) == NULL) {
				p[0] = '/' ;
				p[1] = '\0' ;
				}
			p[strlen(p) - 1] = '\0' ;
			fclose(outfile) ;
			fclose(infile) ;
			}
		else {
			fclose(stdout) ;
			fdup(outfile) ;
			execl("/bin/pwd", NILL) ;
			putchar('/') ;
			exit(1) ;
			}
		}
	/*
	 * doswitch - where most of the work actually gets done.  Doswitch
	 * is given a file, and takes appropriate action in each case.  It
	 * lists files, comments when the wrong kind of file is looked at,
	 * does chdir's, and changes a parameter to let the caller know.
	 * Doswitch also returns a value of true or false.  In the main
	 * loop, this is ignored. However, on the first call, if
	 * it returns true (i.e., the user does not want to look at a
	 * directory,) the program exits. This causes help to behave as
	 * expected on "help so-and-so."
	 */
	doswitch(file, buffer, cflg) FILE *file; char *buffer; int *cflg; {
		int run(), pcat(), roff() ;
	
		if (debug) fprintf(stderr, "entering doswitch...\n") ;
		switch (type(buffer)) {
			case DIRECT:
				if (debug) fprintf(stderr, "It's a directory...\n") ;
				if (chdir(buffer) < 0) {
					fprintf(stderr, "can't examine %s.\n", buffer) ;
					return(0) ;
					}
				(*cflg) = 1 ;
				fildir() ;
				return(0) ;
			case SPCL: fprintf(stderr, "%s is a special file.\n", buffer) ;
				break ;
			case PDS: fprintf(stderr, "%s is a PDS.\n", buffer) ;
				break ;
			case ARCH: fprintf(stderr, "%s is an archive.\n", buffer) ;
				break ;
			case APL: fprintf(stderr, "%s is an APL workspace.\n", buffer) ;
				break ;
			case EXEC:
			 	if (rflg)
					call(run, buffer) ;
				else
					fprintf(stderr, "%s is executable.\n", buffer) ;
				break ;
			case OOPS: fprintf(stderr, "program error OOPS!\n") ;
				break ;
			case PACK: call(pcat, buffer) ;
				*cflg = pflg ;
				break ;
			case COMPACT: fprintf(stderr, "%s is a compacted file.\n", buffer) ;
				break ;
			case ROFF: *cflg = pflg ;
				if (nflg) call(roff, file) ;
				else call(pflg ? page : cat, file) ;
				break ;
			case EMPTY: fprintf(stderr, "%s is empty.\n", buffer) ;
				break ;
			default: *cflg = pflg ;
				call(pflg ? page : cat, file) ;
				break ;
			}
		fflush(stdout) ;
		return(1) ;
		}
	/*
	 * type - determines the type of a file.  This routine borrows heavily
	 * on file (with beauty mods, changes for portability reasons, plus
	 * simplification).
	 */
	type(name) char *name; {
		register int ch ;
		register FILE *file ;
	
		if (debug) fprintf(stderr, "entering type...\n") ;
		if (stat(name, &Statbuf) < 0) {
			return(OOPS) ;
			}
		switch(Statbuf . st_mode & S_IFMT) {
			case S_IFCHR:
			case S_IFBLK:
				return(SPCL) ;
			case S_IFDIR:
				return(DIRECT) ;
			}
		if (Statbuf . st_size == 0l)
			return(EMPTY) ;
		if ((file = fopen(name, "r")) == (FILE *) NULL) return OOPS ;
		ch = getw(file) ;
		rewind(file) ;
		switch(ch) {
			case 0407:	/* pdp11 files.. */
			case 0410:
			case 0411:
			case 0413:	/* VAX "-z" format file */
			case 0430:	/* 2.8bsd overlay */
			case 0431:	/* 2.8bsd overlay, i/d */
				return(EXEC) ;
			case 0177545:
			case 0177555:
				return(ARCH) ;
			case 0100554:
			case 0100555:
				return(APL) ;
			case 052525:
				return(PDS) ;
			case 017437:
				return(PACK) ;
			case 017777:
				return(COMPACT) ;
			default:
				if ((0377 & ch) == '.') return(ROFF)  ;
				else return(DEF) ;
			}
		}
	/*
	 * run - runs a command. This is used in the restricted shell mode to let
	 * users run commands.
	 */
	run(buffer) char *buffer; {
	
		execl(buffer, buffer, NILL) ;
		fprintf(stderr, "couldn't exec %s.\n", buffer) ;
		}
	/*
	 * The following five routines do (almost) all of the IO. They list
	 * files (cat & page) and directories (list & pagelist).
	 * There are two for each, one with paging (page & pagelist) and one
	 * without (cat and list).  Each of these is provoked by call, so
	 * that they can be interupted.
	 */
	list() {
		register int i ;
		register struct direct *dirp ;
		register FILE *menu ;
		FILE	*fopen() ;
		int	cat() ;
	
		if (mflg && (menu = fopen(MENUFILE, "r")) != (FILE *) NULL)
			call(cat, menu) ;
		if (debug) fprintf(stderr, "In list, numen = %d, top = %D\n", numen,
				(long) &gdirp[numen]) ;
		fprintf(stderr, "\n\ncurrent topics are:\n\n") ;
		for (i = 0, dirp = gdirp; dirp < &gdirp[numen] ; dirp++) {
			if (*(dirp -> d_name) == 0) break ;
			if (!aflg && dirp->d_name[0] == '.') continue ;
			fprintf(stderr, format, ++i, dirp->d_name) ;
			if (i && i % entries ==  0)
				putc('\n', stderr) ;
			}
		if (i == 0)
			fprintf(stderr, "The directory is empty.\n") ;
		if (menu != (FILE *) NULL) fclose(menu) ;
		fflush(stderr) ;
		}
	
	pagelist(file) FILE *file; {
		register int i = 0 ;
		register struct direct *dirp ;
		char buffer[BUFLEN] ;
		register FILE *menu ;
		FILE	*fopen() ;
		int	page() ;
	
		if (debug) fprintf(stderr, "In pagelist, numen = %d\n", numen) ;
		if (mflg && (menu = fopen(MENUFILE, "r")) != (FILE *) NULL) {
			fprintf(stderr, "%s", erase) ;
			fflush(stderr) ;
			call(cat, menu) ;
			fprintf(stderr, "\n\nHit enter for menu:") ;
			fflush(stderr) ;
			gets(buffer) ;
			fprintf(stderr, "\n") ;
			fflush(stderr) ;
			}
		else fprintf(stderr, "%s\n\ncurrent topics are:\n\n", erase) ;
		for (i = 0, dirp = gdirp; dirp < &gdirp[numen]; dirp++) {
			if (*(dirp -> d_name) == 0) break ;
			if (!aflg && *(dirp -> d_name) == '.') continue ;
			fprintf(stderr, format, ++i, dirp -> d_name) ;
			if (i && i % entries == 0)
				if (i % (entries * (lines - 3)) != 0)
					putc('\n', stderr) ;
				else {
					putc('\n', stderr) ;
					fflush(stderr) ;
					if (gets(buffer) == NULL)
						finis() ;
					fputs(erase, stderr) ;
					}
			}
		if (!i) fprintf(stderr, "The directory is empty.") ;
		if (menu != (FILE *) NULL) fclose(menu) ;
		fflush(stderr) ;
		}
	
	cat(file) FILE *file; {
		register int c ;
	
		while ((c = getc(file)) != EOF)
			putchar(c) ;
		}
	
	page(file) FILE *file; {
		register char buffer[BUFLEN] ;
		register int i ;
	
		if (pager != (char *) NULL) {
			fclose(stdin) ;		/* point stdin to the file */
			fdup2(file, stdin) ;
			execlp(pager, pager, 0) ;
			}			/* if the execl fails, use default */
		for (;;) {
			puts(erase) ;
			for (i = 0; ++i < lines;)
				if (fgets(buffer, BUFLEN, file) != NULL)
					fputs(buffer, stdout) ;
				else {
					i = 0 ;
					break ;
					}
			fflush(stdout) ;
			fprintf(stderr, "Press enter to continue") ;
			if (gets(buffer) == NULL)
				finis() ;
			if (!i && command(buffer, file)) return ;
			else if (*buffer) command(buffer, file) ;
			}
		}
	/*
	 * command - run the pointer around in the file for the user
	 */
	command(buffer, file) char *buffer; FILE *file; {
		register int i ;
		register char *buf = buffer ;
		long j, ftell() ;
		char tbuf[BUFLEN] ;
	
		while (*buf == ' ' || *buf == '\t')
			buf++ ;
		if (!(*buf)) return(1) ;
		if (*buf == '0') {
			rewind(file) ;
			return(0) ;
			}
		i = atoi(buf) ;
		while (isdigit(*buf))
			*buf++ ;
		if (*buf == '+') i = i ? i : 1 ;
		else if (*buf == '-') i = i ? -i - 1 : -2 ;
		while (*++buf == '-' || *buf == '+')
			i += *buf == '-' ? -1 : 1 ;
		j = i * lines * LINEWIDTH ;
		if (ftell(file) < -j) rewind(file) ;
		else {
			fseek(file, j, 1) ;
			fgets(tbuf, BUFLEN, file) ;
			}
		return(1) ;
		}
	/*
	 * pcat - unpack a file and show it to the user...
	 */
	pcat(file) char *file; {
		FILE *infile, *outfile ;
		register int pid ;
	
		fpipe(&infile, &outfile) ;
		if (pid = fork()) {
			fclose(outfile) ;
			if (pflg) page(infile) ;
			else cat(infile) ;
			fflush(stdout) ;
			while (wait((int *) 0) != pid)
				;
			}
		else {
			fclose(stdout) ;
			fdup(outfile) ;
			execl("/usr/bin/pcat", "pcat", file, NILL) ;
			fprintf(stderr, "couldn't exec pcat.\n") ;
			}
		}
	/*
	 * roff - list a file for the user, through nroff.....
	 */
	roff(file) FILE *file; {
		FILE *infile, *outfile ;
		register int pid ;
	
		fpipe(&infile, &outfile) ;
		if (pid = fork()) {
			fclose(file) ;
			fclose(outfile) ;
			if (pflg) page(infile) ;
			else cat(infile) ;
			while (wait((int *) 0) != pid)
				;
			}
		else {
			fclose(stdin) ;
			fdup(file) ;
			fclose(stdout) ;
			fdup(outfile) ;
			fclose(infile) ;
			execl("/usr/bin/nroff", "nroff", "-h",  "-man", NILL) ;
			fprintf(stderr, "couldn't exec nroff.\n") ;
			}
		}
	/*
	 * setflgs - set the global flags.
	 */
	setflgs(in) char *in; {
		register int tmp ;
		register char *flgs ;
	
		for (flgs = in; *flgs; flgs++)
			switch (*flgs) {
				case 0:   break ;	/* ignore null flags */
				case 'a': aflg ^= 1 ;
					break ;
				case 'p': pflg = 1 ;
					break ;
				case 'c': pflg = 0 ;
					break ;
				case 'e': pflg = 1 ;
					if ((tmp = strlen(++flgs)) < 6)
						strcpy(erase, flgs) ;
					else {
						flgs[5] = '\0' ;
						strcpy(erase, flgs) ;
						}
					return(0) ;
				case 'l': pflg = 1 ;
					tmp = atoi(flgs + 1) ;
					if (tmp > 0) lines = tmp ;
					return(0) ;
				case 'n': nflg ^= 1 ;
					break ;
				case 'd': debug ^= 1 ;
					break ;
				case 's': sflg ^= 1 ;
					break ;
				case '?': showflgs() ;
					break ;
				default:
					fprintf(stderr, "bad flag %s\n", flgs) ;
					return(1) ;
				}
		return(0) ;
		}
	/*
	 * showflgs - print usefull information about the flags...
	 */
	showflgs() {
	
		fprintf(stderr, "\n\tflag\tstate\thandles\n\n") ;
		fprintf(stderr, "\tl\t%d\tnumber of lines per page\n", lines) ;
		fprintf(stderr, "\te\t???\tstring to erase the screen\n") ;
		fprintf(stderr, "\tp\t%s\tpage through the file\n",
				pflg ? "on" : "off") ;
		fprintf(stderr, "\tc\t%s\tturn off paging\n", pflg ? "off" : "on") ;
		fprintf(stderr, "\ts\t%s\tsort the directory\n", sflg ? "on" : "off") ;
		fprintf(stderr, "\ta\t%s\tshow all files\n", aflg ? "on" : "off") ;
		fprintf(stderr, "\tn\t%s\tnroff nroffable files\n",
				nflg ? "on" : "off") ;
		fflush(stderr) ;
		}
	/*
	 * getfile - turns numbers into filenames (the inverse of the mapping
	 * done by list and pagelist)
	 */
	getfile(num, name) char *name; {
		register struct direct *dirp ;
	
		if (!num || num > numen) return (1) ;
		for (dirp = gdirp; dirp < &gdirp[numen]; dirp++) {
			if (*(dirp->d_name) == 0) return(1) ;
			if (!aflg && dirp->d_name[0] == '.') continue ;
			if (--num == 0) break ;
			}
		strcpy(name, dirp -> d_name) ;
		return(0) ;
		}
	/*
	 * call - the routine that gets provoked the most.  call forks a process
	 * which points interrups someplace obscure, then invokes the wanted routine
	 * with the given argument.  This is the routine that makes interrupts work
	 * right. Signals get pointed to _exit, as just turning them on leaves process
	 * hanging around.
	 */
	/* VARARGS1 */
	call(routine, it) int (*routine)(); {
		register int fp ;
		int	(*oldint)(), (*oldquit)() ;
		extern _exit() ;
	
		oldint = signal(SIGINT, SIG_IGN) ;
		oldquit = signal(SIGQUIT, SIG_IGN) ;
	
		if (fp = fork())
			while (wait((int *) 0) != fp)
				;
		else {
			signal(SIGINT, _exit) ;
			signal(SIGQUIT, _exit) ;
			(*routine)(it) ;
			exit(0) ;
			}
	
		signal(SIGINT, oldint) ;
		signal(SIGQUIT, oldquit) ;
		}
	/*
	 * fpipe - makes a "stdio" pipe. This is the only place in help where
	 * file manipulation is not STRICTLY stdio. If help/b is ever moved to
	 * another system, this will have to be rewritten.
	 */ 
	fpipe(filein, fileout) FILE **filein,  **fileout; {
		register int fd[2] ;
	
		pipe(fd) ;
		*filein = fdopen(fd[0], "r") ;
		*fileout = fdopen(fd[1], "w") ;
		}
	/*
	 * help - for the user.
	 */
	help() {
		fprintf(stderr,
			"\n\nenter a name or a number to see more documentation\n") ;
		fprintf(stderr, "type '.' to list current documentation level\n") ;
		fprintf(stderr, "type '/' to return to documentation root\n") ;
		if (!sh) fprintf(stderr, "type '!' followed by shell command\n") ;
		fprintf(stderr, "type $ (or ^D) to exit from help\n") ;
		fprintf(stderr, "type '?' for this message\n\n\n") ;
		}
	/*
	 * Used to process '!' input. Provoked by call
	 */
	special(input) char *input; {
		register char *ptr, *cmdptr, *tmpptr ;
		char command[BUFLEN], file[BUFLEN], changeflg = 0 ;
	
		for (ptr = input, cmdptr = command; *ptr;)
			if (*ptr != '!' || !isdigit(ptr[1]))
				*cmdptr++ = *ptr++ ;
			else {
				if (getfile(atoi(++ptr), file))  {
					fprintf(stderr, "file number %s not found.\n",
						ptr) ;
					fflush(stderr) ;
					return ;
					}
				changeflg++ ;
				for (tmpptr = file; *tmpptr;)
					*cmdptr++ = *tmpptr++ ;
				while (isdigit(*ptr)) ptr++ ;
				}
		*cmdptr = '\0' ;
		if (changeflg) fprintf(stderr, "command is: %s\n", command) ;
		fflush(stderr) ;
		execl("/bin/csh", "sh", "-c", command, NILL) ;
		fprintf(stderr, "couldn't exec the shell!\n") ;
		}
	
	/*
	 * used to tidy up before we go away...
	 */
	finis() {
	
		putc('\n', stderr) ;
		fflush(stderr) ;
		exit(0) ;
		}
	
	/*
	 * fildir - allocate space for and read the directory into memory
	 */
	fildir(file) register FILE *file; {
		char *calloc() ;
		int dircmp(), i ;
		struct direct	*tmp ;
		register struct direct	*direct = gdirp ;
		register DIR	*thisdir ;
	
		if ((thisdir = opendir(dot)) == (DIR *) NULL) {
			perror(ProgName) ;
			exit(1) ;
			}
		for (;;) {
			if (direct - gdirp == numen) {	/* out of directory memory */
				gdirp = (struct direct *) realloc(gdirp,
					(numen += DIRHUNK) * sizeof(struct direct)) ;
				if (gdirp == (struct direct *) NULL) {
					perror(ProgName) ;
					exit(1) ;
					}
				}
			if ((tmp = readdir(thisdir)) == (struct direct *) NULL)
				break ;
			if (tmp -> d_namlen > entries)	entries = tmp -> d_namlen ;
			*direct++ = *tmp ;		/* copy it into place */
			}
	
		if (debug) fprintf(stderr, "in filldir, have %d entries\n",
			direct - gdirp) ;
	
		if (sflg)
			qsort((char *)gdirp, direct - gdirp,
				sizeof(struct direct), dircmp) ;
	
		/* fill out rest so we know it's empty */
		for (i = numen - (direct - gdirp); i >= 0; i--)
			*(direct++ -> d_name) = 0 ;
	
		sprintf(format, "%%3d: %%-%d.%ds", entries, entries) ;
		entries = rows / (entries + 5) ;
			
		}
	
	dircmp(dp1, dp2) register struct direct *dp1, *dp2; {
	
		return(strcmp(dp1->d_name, dp2->d_name)) ;
		}
	
	getenvinfo() {
		char	buf[1024] ;
		char	*p ;
		char	*getenv() ;
		int	i ;
	
		if ((p = getenv("TERM")) == (char *) NULL) pflg = 0 ;
		else
			switch 	(tgetent(buf, p)) {
				case -1: fprintf(stderr, "can't open termcap\n") ;
					break ;
				case 0: fprintf(stderr, "unknown term type: %s.\n", p) ;
					break ;
				case 1: p = tbuf ;
					tgetstr("cl", &p) ;
					erase = tbuf ;
					if (erase = (char *) NULL) pflg = 0 ;
					i = tgetnum("li") ;
					if (i > 0) lines = i ;
					i = tgetnum("co") ;
					if (i > 0) rows = i ;
					if (tgetflag("am")) --rows ;
					break ;
				}
		pager = getenv("PAGER") ;
		}
All work and no play makes Jack a dull boy
echo b.nr
sed 's/^	//' > b.nr << 'All work and no play makes Jack a dull boy'
	.sh NAME
	help, b -- peruse system files and documentation
	
	.sh SYNOPSIS
	help [-apcn] [-eccc] [-lnnn] [name] [section]
	
	.sh DESCRIPTION
	Help and browse are a document perusing system, the command help being
	one entry point, and b being the other.  The behavior of the system
	does not, for the most part, depend on which is invoked, so help will
	be described, with the important differences of b being noted.
	.s3
	To start help, type help, with the a name and/or section number.  If
	both are omitted, help will show a short document on the help system. If
	only a name is given, help will search the documentation root and
	then section 1 for name. If name starts with a '.' or a '/', help
	will assume that it is a valid file name, and try to open it
	without searching the documentation root.  If only a section number is
	given, help starts on that section.  If both a name and section are given,
	help searches the section given for name. B acts approximatly the same
	way, except that the section is ignored, and the current directory
	is searched instead of the documentation root.
	.s3
	The flags for help toggle on and off various features, and change defaults.
	The p flag starts help in paging mode, which is the default.  The c
	flag causes help to run in continuous mode.  The l and e flag are
	used only in paging mode, and are, respectively, the number of
	lines per page, and the erase string to be used.  The a flag causes
	help to show all files, even ones that have a dot as the first character.
	The n flag causes help to not nroff files. This is useful when
	you are examining as programs.  B starts in all mode, without nroffing.
	.s3
	When help first starts, it determines if the file it was asked to
	show is a directory or not.  If the file is a directory, help
	will display a menu, and wait for the user to pick a
	topic from it. Otherwise, help allows the user to examine the file,
	and then returns to the shell.
	.s3
	Help has two modes, paging and continuous.  When in continuous mode,
	help assumes the user is at a hard-copy terminal, and acts accordingly.
	It will list files without pause, and shows menu's only when the
	current menu changes.
	When help displays a menu, it will display a list of names, each
	having a number associated with it. It then waits for the user
	to pick a topic.
	While help is waiting, the following
	responses are valid.
	.s1
		1) a number: examine the file with that number in the
			current menu.
	.s1
		2) a name: examine the file of that name in the current
			menu.
	.s1
		3) .: redisplay the current menu.
	.s1
		4) ..: go up to the directory (menu) above the current
			one.
	.s1
		5) ~[flags]: toggle the flags back and forth. Use `~~' 
			to enter a `~' at the beginning of a line.
			~? will display the state of the flags
	.s1
		6) ?: give a short help listing for help.
	.s1
		7) ![cmd]: execute cmd as a shell cmd. See below
	.s1
		8) /: go back to the documentation root.
			(for b, the working directory when it was
			started.)
	.s1
		9) //: go to they system root.
	.s1
		10) $ or ^D: end the current session.
	.s1
		11) finally, /names and ../.. all work as expected.
	.s3
	When running shell commands from inside help (via the `!' mechanism),
	you can reference the file numbers given in the menu. This is done by
	putting `!' followed by a number in the command. In this case, help
	will echo the modified command.
	.s3
	For example, assume that there are two file in the current listing,
	these being:
	.s1
		1: glop		2: gort.c
	.s1
	Then you could find out what glop was by typing `!file !1'.
	If glop turned out to be executable, it could be run by typeing `!!1'.
	.s3
	When help is in paging mode, it assumes the user is at a crt.  The most
	noticeable thing that this causes is a pause at the bottom of a page
	when listing files and directories.  Help will wait until the user
	types a newline before continuing. If help is listing a file, it will
	also watch what the user types on that line, and take special action
	based on this.
	.s3
	If nothing but a blank line is typed, help will keep on listing the file.
	If it sees an unsigned number, or a number followed by a '+', help will
	skip forward that many pages. A '+' alone acts like a '1+'.
	If help sees a '-' or a number followed by a '-', it will go
	backwards 1 or number pages.  If help sees a '0' (as the first character!)
	it will start the file over.
	The full syntax of this is 
	
	 one or more spaces or tabs,
	 followed by an optional number, or '0'
	 followed by either a '+', a '-', or nothing.
	.s3
	If you would prefer a different pager, say more, setting the environment
	variable PAGER to the name of the program will cause it to be invoked
	instead of the help internal pager described above.
	.s3
	Note that if asked to skip all the way past the end of the file, help
	will act as if it has gone past the end normally, and stop.  If asked
	to go out the front end of the file, help starts again at the
	front.
	.s3
	If help is showing you a packed or roffed file, help will ignore these things.
	.sh DIAGNOSTICS
	mostly self-explanatory.
	.sh FILES
	The documentation root is /usr/man, with /usr/man/helx being the
	directories searched.
All work and no play makes Jack a dull boy
echo dir.c
sed 's/^	//' > dir.c << 'All work and no play makes Jack a dull boy'
	#include <sys/types.h>
	#include <sys/param.h>
	#include "dir.h"
	
	/*
	 * close a directory.
	 */
	void
	closedir(dirp)
		register DIR *dirp;
	{
		close(dirp->dd_fd);
		dirp->dd_fd = -1;
		dirp->dd_loc = 0;
		free(dirp);
	}
	
	
	
	/*
	 * open a directory.
	 */
	DIR *
	opendir(name)
		char *name;
	{
		register DIR *dirp;
		register int fd;
	
		if ((fd = open(name, 0)) == -1)
			return NULL;
		if ((dirp = (DIR *)malloc(sizeof(DIR))) == NULL) {
			close (fd);
			return NULL;
		}
		dirp->dd_fd = fd;
		dirp->dd_loc = 0;
		return dirp;
	}
	
	
	
	/*
	 * read an old style directory entry and present it as a new one
	 */
	#define	ODIRSIZ	14
	
	struct	olddirect {
		ino_t	od_ino;
		char	od_name[ODIRSIZ];
	};
	
	/*
	 * get next entry in a directory.
	 */
	struct direct *
	readdir(dirp)
		register DIR *dirp;
	{
		register struct olddirect *dp;
		static struct direct dir;
	
		for (;;) {
			if (dirp->dd_loc == 0) {
				dirp->dd_size = read(dirp->dd_fd, dirp->dd_buf, 
				    DIRBLKSIZ);
				if (dirp->dd_size <= 0)
					return NULL;
			}
			if (dirp->dd_loc >= dirp->dd_size) {
				dirp->dd_loc = 0;
				continue;
			}
			dp = (struct olddirect *)(dirp->dd_buf + dirp->dd_loc);
			dirp->dd_loc += sizeof(struct olddirect);
			if (dp->od_ino == 0)
				continue;
			dir.d_ino = dp->od_ino;
			strncpy(dir.d_name, dp->od_name, ODIRSIZ);
			dir.d_name[ODIRSIZ] = '\0'; /* insure null termination */
			dir.d_namlen = strlen(dir.d_name);
			dir.d_reclen = DIRBLKSIZ;
			return (&dir);
		}
	}
All work and no play makes Jack a dull boy
echo dir.h
sed 's/^	//' > dir.h << 'All work and no play makes Jack a dull boy'
	/*	dir.h	4.4	82/07/25	*/
	
	/*
	 * A directory consists of some number of blocks of DIRBLKSIZ
	 * bytes, where DIRBLKSIZ is chosen such that it can be transferred
	 * to disk in a single atomic operation (e.g. 512 bytes on most machines).
	 *
	 * Each DIRBLKSIZ byte block contains some number of directory entry
	 * structures, which are of variable length.  Each directory entry has
	 * a struct direct at the front of it, containing its inode number,
	 * the length of the entry, and the length of the name contained in
	 * the entry.  These are followed by the name padded to a 4 byte boundary
	 * with null bytes.  All names are guaranteed null terminated.
	 * The maximum length of a name in a directory is MAXNAMLEN.
	 *
	 * The macro DIRSIZ(dp) gives the amount of space required to represent
	 * a directory entry.  Free space in a directory is represented by
	 * entries which have dp->d_reclen >= DIRSIZ(dp).  All DIRBLKSIZ bytes
	 * in a directory block are claimed by the directory entries.  This
	 * usually results in the last entry in a directory having a large
	 * dp->d_reclen.  When entries are deleted from a directory, the
	 * space is returned to the previous entry in the same directory
	 * block by increasing its dp->d_reclen.  If the first entry of
	 * a directory block is free, then its dp->d_ino is set to 0.
	 * Entries other than the first in a directory do not normally have
	 * dp->d_ino set to 0.
	 */
	#define DIRBLKSIZ	512
	#define	MAXNAMLEN	255
	
	
	struct	direct {
		long	d_ino;			/* inode number of entry */
		short	d_reclen;		/* length of this record */
		short	d_namlen;		/* length of string in d_name */
		char	d_name[MAXNAMLEN + 1];	/* name must be no longer than this */
	};
	
	/*
	 * The DIRSIZ macro gives the minimum record length which will hold
	 * the directory entry.  This requires the amount of space in struct direct
	 * without the d_name field, plus enough space for the name with a terminating
	 * null byte (dp->d_namlen+1), rounded up to a 4 byte boundary.
	 */
	#undef DIRSIZ
	#define DIRSIZ(dp) \
	    ((sizeof (struct direct) - (MAXNAMLEN+1)) + (((dp)->d_namlen+1 + 3) &~ 3))
	
	#ifndef KERNEL
	/*
	 * Definitions for library routines operating on directories.
	 */
	typedef struct _dirdesc {
		int	dd_fd;
		long	dd_loc;
		long	dd_size;
		char	dd_buf[DIRBLKSIZ];
	} DIR;
	#ifndef NULL
	#define NULL 0
	#endif
	extern	DIR *opendir();
	extern	struct direct *readdir();
	extern	long telldir();
	extern	void seekdir();
	#define rewinddir(dirp)	seekdir((dirp), (long)0)
	extern	void closedir();
	#endif KERNEL
All work and no play makes Jack a dull boy