/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

#ifndef lint
static char rcsid[] = "$Header: man.c 1.10 90/04/16 $";
#endif

#include <stdio.h>
#include <ctype.h>
#include <sgtty.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <dirent.h>
#include <string.h>
/*
 * man
 * link also to apropos and whatis
 * This version uses pg for paging.
 */
#define	MANDIR	"/usr/cat/man"			/* this is deeply embedded */
#define WHATIS	"/usr/catman/whatis"
#define	MANALIASES "/usr/catman/aliases"	/* for aliasing man pages */
#define	PG "/usr/bin/pg"			/* paging filter */
#define	PACK "/usr/bin/pack"			/* for compressing pages */
#define	UNPACK "/usr/bin/pcat"			/* for uncompressing pages */

#define	ALLSECT	"1nl823457po"	/* order to look through sections */
#define	SECT1	"1nlo"		/* sections to look at if 1 is specified */

int	nopg;
char	*manpath = "/usr/catman";
char	*pager;
char	*alias;
char	*getenv();
char	*trim();
char	*findpage();
int	remove();
int	section;
char	subsec[4] = { '\0', '\0','\0','\0' };

#define	eq(a,b)	(strcmp(a,b) == 0)

main(argc, argv)
	int argc;
	char *argv[];
{

	if (signal(SIGINT, SIG_IGN) == SIG_DFL) {
		signal(SIGINT, remove);
		signal(SIGQUIT, remove);
		signal(SIGTERM, remove);
	}
	umask(0);
	if (eq(argv[0], "apropos")) {
		apropos(argc-1, argv+1);
		exit(0);
	}
	if (eq(argv[0], "whatis")) {
		whatis(argc-1, argv+1);
		exit(0);
	}
	argc--, argv++;
	while (argc > 0 && argv[0][0] == '-') {
		switch(argv[0][1]) {

		case 0:
			nopg++;
			break;

		case 'k':
			apropos(argc-1, argv+1);
			exit(0);

		case 'f':
			whatis(argc-1, argv+1);
			exit(0);

		case 'P':
			argc--, argv++;
			manpath = *argv;
			break;

		default:
			goto usage;
		}
		argc--, argv++;
	}
	if (argc <= 0) {
usage:
		fprintf(stderr,
		    "Usage:  man [-P path] [-] [ section ] name ...\n");
		fprintf(stderr, "\tman -k keyword ...\n\tman -f file ...\n");
		exit(1);
	}
	if (chdir(manpath) < 0) {
		fprintf(stderr, "Can't chdir to %s.\n", manpath);
		exit(1);
	}
	if ((pager = getenv("PAGER")) == NULL)
		pager = PG;
	if (nopg == 0 && !isatty(1))
		nopg++;
	section = 0;
	do {
		if (eq(argv[0], "local") || eq(argv[0], "l")) {
			section = 'l';
			goto sectin;
		} else if (eq(argv[0], "new") || eq(argv[0], "n")) {
			section = 'n';
			goto sectin;
		} else if (eq(argv[0], "old") || eq(argv[0], "o")) {
			section = 'o';
			goto sectin;
		} else if (eq(argv[0], "public") || eq(argv[0], "p")) {
			section = 'p';
			goto sectin;
		} else if (isdigit(argv[0][0]) && argc != 1) {
			char *cp;
			section = argv[0][0];
			strncpy(subsec, &argv[0][1],3);
			cp = subsec;
			while(*cp != '\0') {
				*cp = tolower(*cp);
				cp++;
			}
sectin:
			argc--, argv++;
			if (argc == 0) {
				fprintf(stderr, "But what do you want from section %s?\n", argv[-1]);
				exit(1);
			}
			continue;
		}
		if (search_alias(section, argv[0])) {
			manual(section, alias);
		} else {
			manual(section, argv[0]);
		}
		argc--, argv++;
	} while (argc > 0);
	exit(0);
}

manual(sec, name)
	char sec;
	char *name;
{
	char section = sec;
	char *catman;
	char *sp = ALLSECT;

	catman = NULL;
	if (section == '1') {
		sp = SECT1;
		section = 0;
	}
	if (section == 0) {
		section = *sp++;
		while(section && ((catman = findpage(name, section)) == NULL))
			section = *sp++;
		if (catman == NULL) {
			if (sec == 0)
				printf("No manual entry for %s.\n", name);
			else
				printf("No entry for %s in section %c of the manual.\n", name, sec);
			return;
		}
	} else {
		catman = findpage(name, section);
		if (catman == NULL) {
			printf("No entry for %s in section %c", name, section);
			if (subsec)
				printf("%s",subsec);
			printf(" of the manual.\n");
			return;
		}
	}
	showpage(catman);
}

char *
findpage(name, sec)
char *name;
char sec;
{
	char catman[100], pagename[100], fullname[100], statpath[100];
	int last;
	DIR *dirf;
	struct dirent *dentry;
	struct stat statbuf;

	/* the name of the subdir  (man1)	*/
	strcpy(catman,"manx");
	catman[3] = sec;

	/* the name of the file  (dump.1m)	*/
	strcpy(pagename, name);
	strcat(pagename, ".x");
	last = strlen(pagename) -1;
	pagename[last] = sec;
	strcat(pagename, subsec);

	/* the name of the subdir + name  (man1/dump.1m.z)  */
	strcpy(fullname, catman);
	strcat(fullname, "/");
	strcat(fullname, pagename);
	strcat(fullname, ".z");

	/* The name of the full path  (/usr/catman/man1/dump.1m.z) */
	strcpy(statpath, manpath);
	strcat(statpath, "/");
	strcat(statpath, fullname);
	if (stat(statpath, &statbuf) >= 0) {
		return(strdup(fullname));
	}
			
	if ((dirf = opendir(catman)) == NULL) {
		return 0;
	}
	while (dentry = readdir(dirf)) {
		if (dentry->d_name[0] == '.'
			&& (dentry->d_name[1] == '\0' 
			|| dentry->d_name[1] == '.'
			&& dentry->d_name[2] == '\0')) {
			continue;	/* skip '.', and '..' */
		}
		if (strncmp(pagename, dentry->d_name, strlen(pagename)) == 0) {
			closedir(dirf);
			strcat(catman, "/");
			strcat(catman, dentry->d_name);
			return(strdup(catman));
		}
	}
	return(NULL);
}

showpage(cp)
	char *cp;
{
	char cmd[BUFSIZ];
	struct stat stbuf;

	if (stat(cp, &stbuf) >= 0) {
		sprintf(cmd, nopg? "%s %s" : "%s %s|%s", UNPACK, cp, pager);
	} else {
		fprintf(stderr, "pg: sorry, can't find your file\n");
		return;
	}
	system(cmd);
}

any(c, sp)
	register int c;
	register char *sp;
{
	register int d;

	while (d = *sp++)
		if (c == d)
			return (1);
	return (0);
}

remove()
{
	char name[15];

	sprintf(name, "/tmp/man%d", getpid());
	unlink(name);
	exit(1);
}

apropos(argc, argv)
	int argc;
	char **argv;
{
	char buf[BUFSIZ];
	char *gotit;
	register char **vp;

	if (argc == 0) {
		fprintf(stderr, "apropos what?\n");
		exit(1);
	}
	if (freopen(WHATIS, "r", stdin) == NULL) {
		perror(WHATIS);
		exit (1);
	}
	gotit = (char *) calloc(1, blklen(argv));
	while (fgets(buf, sizeof buf, stdin) != NULL)
		for (vp = argv; *vp; vp++)
			if (match(buf, *vp)) {
				printf("%s", buf);
				gotit[vp - argv] = 1;
				for (vp++; *vp; vp++)
					if (match(buf, *vp))
						gotit[vp - argv] = 1;
				break;
			}
	for (vp = argv; *vp; vp++)
		if (gotit[vp - argv] == 0)
			printf("%s: nothing appropriate\n", *vp);
}

match(buf, str)
	char *buf, *str;
{
	register char *bp;

	bp = buf;
	for (;;) {
		if (*bp == 0)
			return (0);
		if (amatch(bp, str))
			return (1);
		bp++;
	}
}

amatch(cp, dp)
	register char *cp, *dp;
{

	while (*cp && *dp && lmatch(*cp, *dp))
		cp++, dp++;
	if (*dp == 0)
		return (1);
	return (0);
}

lmatch(c, d)
	char c, d;
{

	if (c == d)
		return (1);
	if (!isalpha(c) || !isalpha(d))
		return (0);
	if (islower(c))
		c = toupper(c);
	if (islower(d))
		d = toupper(d);
	return (c == d);
}

blklen(ip)
	register int *ip;
{
	register int i = 0;

	while (*ip++)
		i++;
	return (i);
}

whatis(argc, argv)
	int argc;
	char **argv;
{
	register char **avp;

	if (argc == 0) {
		fprintf(stderr, "whatis what?\n");
		exit(1);
	}
	if (freopen(WHATIS, "r", stdin) == NULL) {
		perror(WHATIS);
		exit (1);
	}
	for (avp = argv; *avp; avp++)
		*avp = trim(*avp);
	whatisit(argv);
	exit(0);
}

whatisit(argv)
	char **argv;
{
	char buf[BUFSIZ];
	register char *gotit;
	register char **vp;

	gotit = (char *)calloc(1, blklen(argv));
	while (fgets(buf, sizeof buf, stdin) != NULL)
		for (vp = argv; *vp; vp++)
			if (wmatch(buf, *vp)) {
				printf("%s", buf);
				gotit[vp - argv] = 1;
				for (vp++; *vp; vp++)
					if (wmatch(buf, *vp))
						gotit[vp - argv] = 1;
				break;
			}
	for (vp = argv; *vp; vp++)
		if (gotit[vp - argv] == 0)
			printf("%s: not found\n", *vp);
}

wmatch(buf, str)
	char *buf, *str;
{
	register char *bp, *cp;

	bp = buf;
again:
	cp = str;
	while (*bp && *cp && lmatch(*bp, *cp))
		bp++, cp++;
	if (*cp == 0 && (*bp == '(' || *bp == ',' || *bp == '\t' || *bp == ' '))
		return (1);
	while (isalpha(*bp) || isdigit(*bp))
		bp++;
	if (*bp != ',')
		return (0);
	bp++;
	while (isspace(*bp))
		bp++;
	goto again;
}

char *
trim(cp)
	register char *cp;
{
	register char *dp;

	for (dp = cp; *dp; dp++)
		if (*dp == '/')
			cp = dp + 1;
	if (cp[0] != '.') {
		if (cp + 3 <= dp && dp[-2] == '.' && any(dp[-1], "cosa12345678npP"))
			dp[-2] = 0;
		if (cp + 4 <= dp && dp[-3] == '.' && any(dp[-2], "13") && isalpha(dp[-1]))
			dp[-3] = 0;
	}
	return (cp);
}

system(s)
char *s;
{
	int status, pid, w;
	void (*saveintr)(), (*savequit)();

	if ((pid = vfork()) == 0) {
		execl("/bin/sh", "sh", "-c", s, 0);
		_exit(127);
	}

	/* 
	 * Block these signals while waiting
	 */
	saveintr = signal (SIGINT, SIG_IGN);
	savequit = signal (SIGQUIT, SIG_IGN);

	while ((w = wait(&status)) != pid && w != -1)
		;
	/*
	 * Restore signal state before returning
	 */
	signal (SIGINT, saveintr);
	signal (SIGQUIT, savequit);

	if (w == -1)
		status = -1;
	return (status);
}

/*
 * search_alias
 *
 * Search for an alias for a manual page
 * Returns 1 for successful alias, 0 otherwise.
 */

search_alias(sec, name)
	char	sec;
	char	*name;
{
	register int n,j;
	struct  alias_t {
		char 	*name, sec, subsec[4];
		char	*a_name, a_sec, a_subsec[4];
	};
	static	struct alias_t *alias_list = NULL;
	register struct	alias_t *ap;
	FILE *f;
	char *filebytes;
	struct	stat sbuf;

	if (alias_list == NULL) {
		register char *cp, *end;
		if ((f = fopen(MANALIASES, "r")) == NULL
		  || fstat(fileno(f), &sbuf) != 0
		  || (filebytes = (char *)calloc(1, sbuf.st_size + 1)) == NULL
		  || fread(filebytes, sbuf.st_size, 1, f) != 1 
		  || (alias_list = (struct alias_t *)calloc(sbuf.st_size / 5, sizeof(struct alias_t))) == NULL)
			return 0;
		(void) fclose(f);
		end = &filebytes[sbuf.st_size];
		*end = '\n';
		cp = filebytes; 
		n = 0;
		ap = alias_list;
		while ( cp < end ) {
			/* comment lines start with '#' */
			if ( *cp == '#' ) {
				cp = strchr(cp, '\n');
				if (cp == NULL)
					break;
				cp++;
				continue;
			}
			while ( (*cp == ' ' || *cp == '\t')  && *cp != '\n')
				++cp;
			if ( *cp == '\n' ) {
				++cp;
				continue;
			}
			ap[n].name = cp++;
			while (*cp != ' ' && *cp != '\t' && *cp != '\n' && *cp !='.')
				++cp;
			if (*cp == '\n') {
bad: 				ap[--n].name = (char *)NULL;
				ap[n].sec = ap[n].subsec[0] = '\0';
				++cp;
				continue;
			}
			if (*cp == '.') {
				*cp = '\0';
				cp++;
				ap[n].sec = *cp;
				++cp;
				j=0;
				while (*cp != ' ' && *cp != '\t' && *cp != '\n') {
					ap[n].subsec[j++] = tolower(*cp);
					++cp;
				}
			}
			*cp = '\0';
			++cp;
			while ( (*cp == ' ' || *cp == '\t')  && *cp != '\n')
				++cp;
			if (*cp == '\n')
				goto bad;
			ap[n].a_name = cp;
			while (*cp != '\n' && *cp !='.')
				++cp;
			if (*cp == '\n') {
				goto bad;
			}
			if (*cp == '.') {
				*cp = '\0';
				cp++;
				ap[n].a_sec = *cp;
				++cp;
				j=0;
				while (*cp != ' ' && *cp != '\t' && *cp != '\n') {
					ap[n].a_subsec[j++] = tolower(*cp);
					++cp;
				}
			}
			*cp = '\0';
			++n;
			++cp;
		}
	}
	for (ap = alias_list; ap->name; ap++) {
		if (eq(name, ap->name)) { /* found a name match */
			/* section does not a match */
			if (sec && sec != ap->sec)
				continue;
			/* subsection does not a match */
			if ((subsec[0] != '\0') && strcmp(subsec,ap->subsec))
				continue;
			/* match, so do alias */
			if (ap->a_sec) {
				section = ap->a_sec;
				strcpy(subsec,ap->a_subsec);
			}
			alias = ap->a_name;
			return 1;
		}
	}
	return 0;
}
