[mod.sources] A compiled 'man' program for System V

sources-request@panda.UUCP (12/26/85)

Mod.sources:  Volume 3, Issue 70
Submitted by: cbosgd!ukma!ukecc!edward (Edward C. Bennett)

	In System V (at least on our 3B20) the man(1) command is a shell
script and, because it calls nroff *every time*, it is painfully slow to
use. Even the catman(1) command, which uses preformatted and packed versions
of the man pages, takes a noticeably long time to get your data on-screen.
Furthermore, catman(1) has no easy way to keep the /usr/catman files up-
to-date. Such is the inspiration for this new version of man(1).
	This version of man(1) eliminates all these problems.  It is compiled
rather than interpreted to increase speed.
[This program compiles on BSD4.2 after changing <string.h> to <strings.h>
 and converting calls to strchr() to index()                 - moderator ]
					    It uses the /usr/catman files
to avoid repetative nroff'ing. And checks to see if the formatted version
postdates the raw version and if not, a new formatted version is created.
This automates the problem of maintaining a current manual.
	Additionally, the program allows you to select the order that the
sections are searched. This way you can put little-used sections like 5
and 8 at the end of the line. The program also looks at the environment
variable PAGER for a preferred paging program, finding none it uses a
preselected default. An option is provided to look for man pages applicable
to a given keyword.

#! /bin/sh
: This is a shell archive, meaning:
: 1. Remove everything above the '#! /bin/sh' line.
: 2. Save the resulting text in a file.
: 3. Execute the file with /bin/sh '(not csh)' to create the files:
:	'README'
:	'Makefile'
:	'man.1'
:	'man.c'
:	'mkxref.c'
:	'updateall.c'
: This archive created: 'Mon Dec 23 16:31:07 1985'
: By:	'Edward C. Bennett'
export PATH; PATH=/bin:$PATH
if test -f 'README'
then
	echo shar: will not over-write existing file "'README'"
else
cat  >'README' <<\SHAR_EOF
To set this system up:

	Check the makefile to be sure all paths are OK.

	Su to 'root' and do "make convert"

	Compile the updateall command. This should be run at an off
	hour as it takes quite awhile.
SHAR_EOF
fi
if test -f 'Makefile'
then
	echo shar: will not over-write existing file "'Makefile'"
else
cat  >'Makefile' <<\SHAR_EOF
# makefile of System V man program
#
# AUTHOR
#	Edward C. Bennett, edward@ukecc.UUCP
#
# Copyright 1985 by Edward C. Bennett
#
# Permission is given to alter this code as needed to adapt it to forign
# systems provided that this header is included and that the original
# author's name is preserved.

USRBIN=/usr/bin
MANOWN=bin

man: man.c
	cc -O man.c -o man

xref: mkxref
	mkxref > /usr/man/xref

mkxref: mkxref.c
	cc -O mkxref.c -o mkxref

updateall: updateall.c
	cc -O updateall.c -o updateall

install: man
	/etc/install -f ${USRBIN} man
	chown ${MANOWN} ${USRBIN}/man
	chmod 4755 ${USRBIN}/man

convert: man mkxref
	mv ${USRBIN}/man ${USRBIN}/oman
	mv /usr/man/u_man/man1/man.1 /usr/man/u_man/man1/oman.1
	/etc/install -f ${USRBIN} man
	chown ${MANOWN} ${USRBIN}/man
	chmod 4755 ${USRBIN}/man
	mkxref > /usr/man/xref
	chown ${MANOWN} /usr/bin/xref

clean:
	rm -f man mkxref updateall

shar:
	shar README Makefile man.1 man.c mkxref.c updateall.c > man.shar
SHAR_EOF
fi
if test -f 'man.1'
then
	echo shar: will not over-write existing file "'man.1'"
else
cat  >'man.1' <<\SHAR_EOF
.TH MAN 1 "23 December 1985"
.SH NAME
man \- print manual pages
.SH SYNOPSIS
.B man
[
.B section
]
title
.PP
.B man \-k keyword
.SH DESCRIPTION
.I Man
finds and prints pages from the on-line manual.
To speed things up,
.I man
keeps preformatted and packed versions of the manual pages
in the /usr/catman directory.
The date of the formatted version is checked against the date
of the raw version and if the raw version is newer,
a new formatted version is created with
.IR nroff (1)
and
.IR pack (1).
.PP
The environmental variable PAGER is checked for a preferred
paging program.
If none is specified,
.IR pg (1)
is used.
.PP
If
.I man
is used with the \-k option,
a list of all subject lines is searched for the given keyword.
This is useful if you don't know the name of a command,
but you know what it does.
.SH FILES
.TP 38
/usr/man/[apu]_man/man[1-8]/*
unformatted pages
.TP 38
/usr/catman/[apu]_man/man[1-8]/*.z
formatted, packed pages
.TP 38
/usr/man/xref
collection of title lines
.SH AUTHOR
Edward C. Bennett
.SH DIAGNOSTICS
Hopefully self-explanatory.
.SH BUGS
.I Man
only prints the first manual entry with the given title that it finds.
To keep things simple,
.IR nroff (1)
uses no fancy options.
The \-k option should search for multiple keywords.
SHAR_EOF
fi
if test -f 'man.c'
then
	echo shar: will not over-write existing file "'man.c'"
else
cat  >'man.c' <<\SHAR_EOF
/*
 *	man - view the on-line manual
 *
 *	man [ section ] title
 *
 *	man -k keyword
 *
 *	This manual program is designed primarily for a speed increase
 * in viewing the manual. Rather than run nroff every time a man page is
 * requested, pre-formatted packed versions of the pages are kept in the
 * /usr/catman directory and pcat is used to read them. This agrees with
 * System V's catman program. This program stats both the unformatted and
 * formatted versions of the requested page and, if the formatted page is
 * up-to-date, it is printed, otherwise a new packed page is created with
 * nroff and pack. This automates the task of keeping up-to-date man pages
 * on-line. The environmental variable PAGER is checked for a preferred
 * pager, if none is specified /usr/bin/pg is used. To keep things simple,
 * nroff uses no fancy options.
 *	With the -k option, the /usr/man/xref database is searched for the
 * given keyword.
 *
 * AUTHOR
 *	Edward C. Bennett, edward@ukecc.UUCP
 *
 * Copyright 1985 by Edward C. Bennett
 *
 * Permission is given to alter this code as needed to adapt it to forign
 * systems provided that this header is included and that the original
 * author's name is preserved.
 */
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/dir.h>
#include <string.h>

#define	DEFPAGER	"/usr/bin/pg"
#define	GREP		"/bin/grep"
#define	XREF		"/usr/man/xref"

/*
 * These are the directories under /usr/man that are to be searched for the
 * requested man page. The order in the array is the order of the search
 * so to minimize searching effort the more frequently used sections should
 * go at the top.
 */
char	*mandirs[] = {
	"u_man/man1",
	"p_man/man2",
	"p_man/man3",
	"u_man/man6",
	"p_man/man4",
	"p_man/man5",
	"a_man/man1",
	"a_man/man7",
	"a_man/man8",
};
#define	NUMDIRS		sizeof(mandirs)/sizeof(char *)

main(argc, argv)
int argc;
char **argv;
{
	char	*manpage, *pager, *section, *title, *Findpage(), *getenv();
	char	cmd[BUFSIZ], catbuf[BUFSIZ], catbufz[BUFSIZ];
	int	status;
	void	exit();
	struct	stat	manstat, catstat;

	if (!strcmp(argv[1], "-k")) {
		execl(GREP, GREP, argv[2], XREF, 0);
	}

	switch (argc) {
	case 2:
		title = argv[1];
		section = NULL;
		break;

	case 3:
		title = argv[2];
		section = argv[1];
		break;

	default:
		fprintf(stderr, "Usage: %s [ section ] title\n", argv[0]);
		exit(1);
	}

	if ((pager = getenv("PAGER")) == NULL)
		pager = DEFPAGER;

	chdir("/usr/man");

	if ((manpage = Findpage(title, section)) == NULL) {
		if (section)
			printf("No entry for %s in section %s of the manual\n", title, section);
		else
			printf("No manual entry for %s\n", title);
		exit(1);
	}

	strcpy(catbuf, "/usr/catman/");
	strcat(catbuf, manpage);
	strcpy(catbufz, catbuf);
	strcat(catbufz, ".z");

	stat(manpage, &manstat);
	if (stat(catbufz, &catstat) == -1)
		catstat.st_mtime = -1;

	if (catstat.st_mtime < manstat.st_mtime) {
		unlink(catbufz);
		printf("Reformatting page, wait...");
		fflush(stdout);
		if (fork() == 0) {
			sprintf(cmd, "nroff -man %s > %s", manpage, catbuf);
			execl("/bin/sh", "sh", "-c", cmd, 0);
		}
		wait(&status);
		printf("\nCompressing output, wait...");
		fflush(stdout);
		if (fork() == 0) {
			sprintf(cmd, "pack -f %s", catbuf);
			execl("/bin/sh", "sh", "-c", cmd, 0);
		}
		wait(&status);
	}

	/*
	 * Do this in case the user does something like 'man curses > file'
	 */
	if (isatty(1))
		sprintf(cmd, "pcat %s | %s", catbuf, pager);
	else
		sprintf(cmd, "pcat %s", catbuf);

	execl("/bin/sh", "sh", "-c", cmd, 0);
}

/*
 * Findpage - determine the pathname of the requested page
 *
 * path = Findpage(title, sect);
 *	path	is a pointer to the requested path
 *	title	is a pointer to the title of the requested page
 *	sect	is a pointer to the section to search, NULL if all sections
 *
 *	Findpage() returns a pointer to a buffer containing the path name
 * of the unformatted version of the request man page in the form
 * [apu]_man/man[1-8]/title.[1-8]. The user can specify a section to search.
 * This is needed for cases like write(1) and write(2). If 'section' is given
 * as NULL, all sections are searched. If no match for title.sect can be found,
 * NULL is returned.
 */
char *
Findpage(title, sect)
char *title, *sect;
{
	int	fd, i, len, tlen;
	long	lseek();
	static	char	manbuf[BUFSIZ];
	struct	direct	manent;

	tlen = strlen(title);
	for (i = 0; i < NUMDIRS; i++) {
		if (sect && *sect != *(mandirs[i] + 9))
			continue;

		if ((fd = open(mandirs[i], O_RDONLY)) == -1)
			continue;

		/*
		 * Skip "." and ".."
		 */
		lseek(fd, 2*sizeof(manent), 0);
		while (read(fd, &manent, sizeof(manent)) == sizeof(manent)) {
			if (manent.d_ino == 0)
				continue;

			len = (int)(strchr(manent.d_name, '.') - manent.d_name);
			if (!strncmp(manent.d_name, title, (len > tlen ? len : tlen))) {
				if (sect && strcmp(sect, manent.d_name+len+1))
					continue;

				sprintf(manbuf, "%s/%s", mandirs[i], manent.d_name);
				return(manbuf);
			}
		}
	}
	return(NULL);
}
SHAR_EOF
fi
if test -f 'mkxref.c'
then
	echo shar: will not over-write existing file "'mkxref.c'"
else
cat  >'mkxref.c' <<\SHAR_EOF

/*
 * mkxref - a program to list the titles from man pages
 *
 * All files in /usr/man/?_man/man? are searched for their title lines.
 * A title line is the first line following the ".SH NAME" line. Nroff
 * escapes are removed and the lines are written to the standard output.
 * If no title line is found in a file, the file's name is printed on
 * the standard error output.
 *
 * AUTHOR
 *	Edward C. Bennett, edward@ukecc.UUCP
 *
 * Copyright 1985 by Edward C. Bennett
 *
 * Permission is given to alter this code as needed to adapt it to forign
 * systems provided that this header is included and that the original
 * author's name is preserved.
 */
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/dir.h>
#include <fcntl.h>
#include <string.h>

char	*mandirs[] = {
	"u_man/man1",
	"p_man/man2",
	"p_man/man3",
	"u_man/man6",
	"p_man/man4",
	"p_man/man5",
	"a_man/man1",
	"a_man/man7",
	"a_man/man8",
};
#define	NUMDIRS		sizeof(mandirs)/sizeof(char *)

main(argc, argv)
int argc;
char **argv;
{
	char	manbuf[BUFSIZ];
	int	fd, i, len;
	long	lseek();
	struct	direct	manent;

	chdir("/usr/man");

	for (i = 0; i < NUMDIRS; i++) {
		if ((fd = open(mandirs[i], O_RDONLY)) == -1)
			continue;

		/*
		 * Skip "." and ".."
		 */
		lseek(fd, 2*sizeof(manent), 0);
		while (read(fd, &manent, sizeof(manent)) == sizeof(manent)) {
			if (manent.d_ino == 0)
				continue;

			sprintf(manbuf, "%s/%s", mandirs[i], manent.d_name);
			Findname(manbuf);
		}
	}
}

Findname(manfile)
char *manfile;
{
	char	*p, line[BUFSIZ], section[4];
	int	i, flag = 0;
	FILE	*fp;

	strcpy(section, strchr(manfile, '.')+1);
	if ((fp = fopen(manfile, "r")) == NULL)
		return;

	while (fgets(line, BUFSIZ, fp) != NULL) {
		if (!strncmp(line, ".SH NAME", 8)) {
			flag++;
			if ((p = fgets(line, BUFSIZ, fp)) != NULL) {
				i = 0;
				while (*p) {
					/*
					 * Remove escapes
					 */
					if (*p == '\\') {
						if (*++p == 's') {
							if (*++p == '-' || *p == '+') {
								p++;
								i--;
							}
							p++;
							i -= 2;
						}
						else if (*p == '*') {
							p += 2;
							i -= 3;
						}
						else if (*p == '&') {
							p++;
							i -= 2;
						}
						else if (*p == '-') {
							printf("(%s) ", section, ")");
							for (i += (int)(p - line) + strlen(section); i < 25; i++)
								putchar(' ');
							putchar(*p++);
						}
						else
							putchar(*p++);
					}
					else
						putchar(*p++);
				}
			}
			break;
		}
	}
	if (!flag)
		fprintf(stderr, "%s\n", manfile);
	fclose(fp);

	return;
}
SHAR_EOF
fi
if test -f 'updateall.c'
then
	echo shar: will not over-write existing file "'updateall.c'"
else
cat  >'updateall.c' <<\SHAR_EOF
/*
 * updateall - update the /usr/catman files
 *
 * All of the unformatted manual pages are checked against the copies
 * in the /usr/catman directory. If the unformatted copy is newer, a
 * new formatted and packed copy is placed in /usr/catman.
 *
 * AUTHOR
 *	Edward C. Bennett, edward@ukecc.UUCP
 *
 * Copyright 1985 by Edward C. Bennett
 *
 * Permission is given to alter this code as needed to adapt it to forign
 * systems provided that this header is included and that the original
 * author's name is preserved.
 */
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/dir.h>
#include <string.h>

char	*mandirs[] = {
	"u_man/man1",
	"p_man/man2",
	"p_man/man3",
	"u_man/man6",
	"p_man/man4",
	"p_man/man5",
	"a_man/man1",
	"a_man/man7",
	"a_man/man8",
};
#define	NUMDIRS		sizeof(mandirs)/sizeof(char *)

main()
{
	char	manbuf[BUFSIZ], cmd[BUFSIZ], catbuf[BUFSIZ], catbufz[BUFSIZ];
	int	status;
	void	exit();
	struct	stat	manstat, catstat;
	int	fd, i;
	long	lseek();
	struct	direct	manent;

	chdir("/usr/man");

	for (i = 0; i < NUMDIRS; i++) {

		if ((fd = open(mandirs[i], O_RDONLY)) == -1)
			continue;

		lseek(fd, 2*sizeof(manent), 0);
		while (read(fd, &manent, sizeof(manent)) == sizeof(manent)) {
			if (manent.d_ino == 0)
				continue;

			sprintf(manbuf, "%s/%s", mandirs[i], manent.d_name);

			strcpy(catbuf, "/usr/catman/");
			strcat(catbuf, manbuf);
			strcpy(catbufz, catbuf);
			strcat(catbufz, ".z");

			stat(manbuf, &manstat);
			if (stat(catbufz, &catstat) == -1)
				catstat.st_mtime = -1;

			if (catstat.st_mtime < manstat.st_mtime) {
				unlink(catbufz);
				if (fork() == 0) {
					sprintf(cmd, "nroff -man %s > %s", manbuf, catbuf);
					execl("/bin/sh", "sh", "-c", cmd, 0);
				}
				wait(&status);
				if (fork() == 0) {
					sprintf(cmd, "pack -f %s", catbuf);
					execl("/bin/sh", "sh", "-c", cmd, 0);
				}
				wait(&status);
			}
		}
	}
}
SHAR_EOF
fi
:	End of shell archive
exit 0

Edward C. Bennett

UUCP: ihnp4!cbosgd!ukma!ukecc!edward

/* A charter member of the Scooter bunch */

"Goodnight M.A."