|
|
BSD 4.3
/* Copyright (c) 1983 Regents of the University of California */
#ifndef lint
static char sccsid[] = "@(#)help.c 1.2 (Berkeley) 8/14/85";
#endif not lint
/*
* help - an easy way to find and use information
*
* Usage: see definition of error()
*
* Files: /usr/lib/help root help subsystem
* /usr/lib/help/log log of system activity
* /usr/lib/help/maint help maintenance scripts
* /usr/lib/help/config defines the help system network
* /usr/lib/help/src nroff sources for /usr/lib/help/cat
* /usr/lib/help/cat root of system help topic files
* "/index_{help,man,doc} topics created by mkhelpindex
* "/general description of 'help'
* "/* all other files are 'help' text files
*
* Author: John Kunze, UCB (sorting routines based on David Wasley's originals)
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/dir.h>
#include <setjmp.h>
#include <signal.h>
/* #include <whoami.h> */ /* this would have defined BSD4_2 */
#define BSD4_2 1
#include <ctype.h>
#include <stdio.h>
/* user-instruction return codes */
#define LIST_I 1
#define PASS_I 2
#define SAVE_I 3
#define TYPE_I 4
#define JUNK_I 5
#define BACK_I 6
#define LPRT_I 7
#define HELP_I 8
#define ROOT_I 9
#define YELL_I 10
#define QUIT_I 11
#define FIND_I 12
#define FLAG_I 13
#define NOOP_I 14
#define GOT_ONE 15
/* symbols and macros */
#define HDBSIZE 256
#define HVSIZE 10
#define HELPROOT "/usr/lib/help/cat"
#define TOPICINDEX "index_help"
#define HELPMAINT "../maint/do."
#ifdef notdef
#define MANINDEX "/usr/lib/whatis"
#else
#define MANINDEX { "/usr/lib/whatis", "/usr/man/whatis", NULL }
#endif
#define DOCINDEX "/usr/lib/help/cat/index_doc"
#define HELPSAVE "helpsave"
#ifdef notdef
#define MAINTAINER "help@ucbopal"
#else
#define MAINTAINER "help"
#endif
#define HELPLOG "/usr/lib/help/log"
#define DEFSHELL "/bin/csh"
#define PERROR { perror("help"); exit(1); }
#define PUTNL { putchar('\n'); fflush(stdout); }
#define EXISTS(s) (access(s, 0) == 0)
#define EXECABLE(s) (access(s, 1) == 0)
#define WRITABLE(s) (access(s, 2) == 0)
#define READABLE(s) (access(s, 4) == 0)
#define DOT(s) ((s)[0]=='.'&&(s)[1]==0 ? 1 : 0)
#define DOTDOT(s) ((s)[0]=='.'&&(s)[1]=='.'&&(s)[2]==0 ? 1 : 0)
#define ROOT(s) ((s)[0]=='/'&&(s)[1]==0 ? 1 : 0)
#define isspecial(a) (a == '\0' || a == '+' || a == '>' || a == '|')
#define lcase(a) (isupper(a) ? tolower(a) : a)
/* signal handling interface */
#if BSD4_2
struct sigvec vec;
#define GET_SIGPIPE { vec.sv_handler = onintr; sigvec(SIGPIPE, &vec, 0); }
#define SET_SIGPIPE { vec.sv_handler = SIG_DFL; sigvec(SIGPIPE, &vec, 0); }
#define NO_RUPTS { vec.sv_handler = SIG_IGN; sigvec(SIGINT, &vec, 0); }
#define OK_RUPTS { vec.sv_handler = SIG_DFL; sigvec(SIGINT, &vec, 0); }
#else
#define GET_SIGPIPE signal(SIGPIPE, onintr)
#define SET_SIGPIPE signal(SIGPIPE, SIG_DFL)
#define NO_RUPTS signal(SIGINT, SIG_IGN)
#define OK_RUPTS signal(SIGINT, SIG_DFL)
#ifndef MAXNAMLEN
#define MAXNAMLEN 255
#endif
#endif
/* miscellaneous globals */
char hdbuf[HDBSIZE]; /* names of top level directores */
char *hvec[HVSIZE]; /* pointers to top level directories */
char **argp; /* pointer to topic arguments */
char *dirlist; /* unparsed list of root directories */
char cwd[MAXNAMLEN]; /* current directory */
char *dot, *dotdot; /* tail parts of current and parent dirs */
char *subdir; /* current subdirectory names */
short dirlevel = 0; /* depth from root of current directory */
short keeppag; /* for the >& command to keep pagination */
char *shell, shellprompt; /* shell and its prompt */
char helpprompt[100]; /* help prompt string */
char indexprompt[100]; /* help-index prompt string */
/*
* Topic names at the top (zero-th) directory level are stored permanently
* as null terminated strings in the first segment of topicbuf, each of which
* is pointed to by a pointer in the first segment of tptrs. When a subtopic
* at any directory level is under inspection, the second segment of topicbuf,
* beginning with topicbuf[rtlen], contains the subtopic names, and the second
* segment of tptrs, beginning with tptrs[subt], contains pointers to them.
* At all times, tptrs[nt] contains zero to mark the end of the active segment.
*/
char topicbuf[4096]; /* null-terminated topic names */
char *tptrs[256]; /* pointers to topic names */
char **topics; /* points to topics or subtopics */
int nt = 0, tlen = 0; /* number and total length of topics */
int subt; /* subtopic index in tptrs */
int rtlen; /* length of root topics names */
int nhits = 0, hit = -1; /* number and index of matched topics */
/*
* Index references are stored in indexbuf, those for "help" preceding those
* for "man", which start at iptrs[mansegment] and precede those for off-line
* references starting at iptrs[docsegment]. Each iptrs[i] points to a pair
* of null-terminated strings containing the first and second halves of a line.
*/
char *indexbuf; /* names of index references */
char **iptrs; /* pointers to index references */
int ni = 0, ilen = 0; /* number and length of index refs */
int inhits = 0, ihit = -1; /* number and index of matched index refs */
char *isrc, *idst; /* partial match of index entry */
int mansegment; /* beginning of UPM refs segment */
int docsegment; /* beginning of off-line refs segment */
char line[BUFSIZ]; /* raw user instruction */
char *src, *dst, *dstarg; /* source and dst parts of an instruction */
char fname[BUFSIZ]; /* full path name(s) of topic file */
short fnamect; /* number of files in fname */
short interactive, iflag; /* interactive session flag */
short number, quiet; /* numbers accepted/printed, terse prompt */
char *more_d; /* pointer to value of MORE env. variable */
char *progname; /* name (argv[0]) of invoking program */
char *maintkey; /* help maintenance key */
/* miscellaneous routines */
char *getenv(), *strcpy(), *malloc(), *index(), *rindex();
FILE *outpipe();
main(argc,argv)
int argc;
char **argv;
{
register int ins; /* current user instruction */
register int junkcount = 0; /* how many times in a row bad ins. */
setbuf(stdout, malloc(BUFSIZ)); /* speed up standard output */
setbuf(stderr, malloc(BUFSIZ)); /* speed up error output */
getoptions(argc, argv); /* parse options */
setgetenv(); /* make directory list, environment */
/*
* main loop: get instruction, execute
*/
for (ins = startup(); ins != QUIT_I; ins = nextins()) {
if (ins != JUNK_I)
junkcount = 0;
switch (ins) {
case LIST_I:
list();
break;
case TYPE_I:
if (isadir(fname)) {
chwd(src);
list();
}
else
page();
log('=');
break;
case BACK_I:
chwd("..");
list();
break;
case ROOT_I:
chwd("/");
list();
break;
case SAVE_I:
save();
log('>');
break;
case LPRT_I:
lprt();
log('|');
break;
case PASS_I:
fflush(stdout);
pass(src);
break;
case FLAG_I:
flag(src, dst);
break;
case JUNK_I:
printf("\nI%sdon't understand.\n",
(junkcount++ ? " still " : " "));
if (junkcount == 2)
list();
else if (junkcount > 2)
comlist();
break;
case HELP_I:
comlist();
break;
case YELL_I:
yell();
break;
case FIND_I:
find(src);
log('+');
break;
case NOOP_I:
break;
default:
puts("Unknown instruction - please report this.");
exit(0);
break;
}
}
puts("Bye.");
}
save() /* save a help file "src" in user file "dst" */
{
register char *p;
register FILE *destfile;
register int lcount;
char c;
p = (EXISTS(dst) ? "appended" : "new file");
if ((destfile = fopen(dst, "a")) == NULL)
perror(dst);
lcount = putfiles(NULL, destfile);
fclose(destfile);
printf("\nTopic \"%s\" is saved in \"%s\" (%s: %d lines).\n",
topics[hit], dst, p, lcount);
}
lprt() /* lineprint (dst) all args in fname */
{
register int i = 0;
register char *fn;
char *ap[HVSIZE]; /* arg pointers */
ap[i++] = dst;
if (strcmp(dst, "ipr") == 0) /* kludge to force -p with ipr */
ap[i++] = "-p";
if (dstarg) /* kludge to allow an option to lpr */
ap[i++] = dstarg;
fn = fname;
while (fnamect--) {
ap[i++] = fn;
fn += strlen(fn) + 1;
}
ap[i] = 0;
if (!fork()) {
fputs("\n>> Executing [", stdout);
for (i = 0; ap[i]; fputs(ap[i++], stdout))
putchar(' ');
puts(" ] ...");
fflush(stdout);
execvp(dst, ap);
PERROR;
}
NO_RUPTS;
wait(0);
puts(">> Done.");
OK_RUPTS;
}
yell() /* send complaints or other input to the MAINTAINER of help */
{
if (!fork()) {
printf("\n%s\n%s\n%s\n",
"Please enter your remarks. The only way I will know you're done is if",
"you enter a . (period) or control-d on a line by itself. Don't forget!");
fflush(stdout);
execlp("mail", "mail", MAINTAINER, 0);
PERROR;
}
NO_RUPTS;
wait(0);
puts("\nDuly noted.");
OK_RUPTS;
}
log(insc) /* to turn logging off, deny write access of HELPLOG */
char insc; /* instruction code character to be written */
{
long logtime;
char *ctime();
FILE *lp;
if ((lp = fopen(HELPLOG, "a")) == NULL)
return;
time(&logtime);
fprintf(lp, "%.12s %c %s\n", ctime(&logtime) + 4, insc, src);
fclose(lp);
}
getoptions(ac, av) /* get command-line options */
int ac; /* spaces need not separate -[dpm] from next arg */
register char **av;
{
register char *p;
interactive = isatty(1);
progname = *av;
for (p = progname; *p; p++);
for (p--; p >= progname && *p != '/'; p--);
progname = ++p; /* set progname to its tail part */
while (**++av == '-')
switch (*(p = *av + 1)) {
case 'd':
if (*++p || *++av)
dirlist = (*p ? p : *av);
else
error("Directory list must follow -d.");
break;
case 'p':
if (*++p || *++av)
progname = (*p ? p : *av);
else
error("Prompt string must follow -p.");
break;
case 'm':
if (*++p || *++av)
maintkey = (*p ? p : *av);
else {
maintkey = "default";
av--;
}
break;
case 'i':
iflag = interactive = 1;
break;
case 'n':
number = 1;
break;
case 'q':
quiet = 1;
break;
default:
printf("Unknown option -%c.\n", *p);
break;
}
argp = av;
}
error(msg) /* print a message for command-line errors */
char *msg;
{
if (*msg)
fprintf(stderr, "%s\n", msg);
fprintf(stderr, "Usage: help [ options ] [ topic [ subtopic [ subsubtopic [...] ] ] ]\n");
fprintf(stderr, "Options are:\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n",
"-d dirlist override the default pool of help files",
"-m key do the help maintenance function given by key",
"-p prompt override the default prompt",
"-i force help to be interactive",
"-n use numbers in topic and index listings",
"-q suppress the instruction line before prompting");
fprintf(stderr, "To get started just type \"help\".\n");
exit(1);
}
helpmaint(key, dir, av) /* invoke maintenance script */
char *key; /* key specifying action */
char *dir; /* first writable dir in HELPPOOL */
char **av; /* topics, if any */
{
char s[BUFSIZ];
char *argv[BUFSIZ];
register char **vp = argv;
sprintf(s, "%s/%s%s", dir, HELPMAINT, key);
if (!READABLE(s)) {
printf("I don't know how to do \"%s\".\n", key);
fflush(stdout);
sprintf(s, "%s/%s%s", dir, HELPMAINT, "default");
}
*vp++ = "csh";
*vp++ = "-f";
*vp++ = s;
*vp++ = dir;
while (*av)
*vp++ = *av++;
*vp = 0;
execv("/bin/csh", argv);
PERROR;
}
setgetenv() /* get directory list and shell, and set more -d for man */
{
register char *p = dirlist, **vp;
register int i = 0;
char **myenv; char *t; int moredef = 0;
extern char **environ;
for (vp = environ; *vp; vp++)
if (strncmp(*vp, "HELPPOOL=", 9) == 0 && !p)
p = *vp + 9;
else if (strncmp(*vp, "SHELL=", 6) == 0)
shell = *vp + 6;
else if (strncmp(*vp, "MORE=", 5) == 0)
moredef++;
if (p) {
t = &hdbuf[0];
while (*p) {
hvec[i++] = t;
while (*p == ':' || isspace(*p))
p++;
while (*p && *p != ':' && !isspace(*p))
*t++ = *p++;
*t++ = 0;
}
}
if (!dirlist)
hvec[i++] = HELPROOT;
hvec[i] = 0;
if (!shell)
shell = DEFSHELL;
shellprompt = (strcmp(shell, DEFSHELL) == 0 ? '%' : '$');
if (number)
sprintf(helpprompt,
"\nTo see a topic, type its name or number, and RETURN; '%c' to quit, '?' for help.", shellprompt);
else
sprintf(helpprompt,
"\nTo display a topic, type its name, and RETURN; type '%c' to quit, '?' for help.", shellprompt);
if (number)
sprintf(indexprompt,
"\nTo display a subject, type its name or number, and RETURN; type '?' for help.", shellprompt);
else
sprintf(indexprompt,
"\nTo display a subject, type its name, and RETURN; type '?' for help.", shellprompt);
if (moredef)
return;
myenv = (char **) malloc((vp - environ + 2) * sizeof (char *));
if (!myenv)
PERROR;
*myenv = (quiet ? "MORE= " : "MORE=-d");
more_d = *myenv + 5; /* points to "-d" or " " */
for (i = 0, vp = myenv + 1; environ[i]; i++, vp++)
*vp = environ[i];
environ = myenv;
}
startup() /* get topic named by args, return first instruction */
{
register int i, ins;
char **t, **u;
if (maintkey) {
for (i = 0; hvec[i]; i++)
if (WRITABLE(hvec[i]) && isadir(hvec[i]))
break;
if (!hvec[i]) {
fprintf(stderr, "You need write permission in at least one directory in HELPPOOL.\n");
exit(1);
}
helpmaint(maintkey, hvec[i], argp); /* no return */
}
for (i = 0; hvec[i]; i++) /* collect first level (root) */
getfiles(hvec[i]); /* topics to be kept permanently */
rtlen = tlen; /* save root topic sizes */
topics = tptrs; /* active topic segment */
vsort(tptrs); /* sort -- replace dups with zero */
t = u = tptrs; /* kill off zeros */
while (t < tptrs + nt)
if (!*t)
t++;
else
*u++ = *t++;
*u = 0; /* mark new end of first segment */
nt = u - tptrs; /* so tptrs[nt-1] is nil */
subt = nt + 1; /* mark start of subtopic segment */
for (; *argp; argp++) { /* go through topic arguments */
if (!match(*argp)) /* if no match, try something else */
if ((ins = whatnext(*argp)) != GOT_ONE)
return ins; /* user can escape this way */
if (!chwd(topics[hit])) /* if match, assume it's a directory */
break; /* not a directory, must be a file */
}
if (!*argp)
return LIST_I;
src = topics[hit];
makefname(dirlevel, topics[hit]);
page();
if (!iflag)
exit(0);
interactive = 1;
return NOOP_I;
}
whatnext(s) /* match s with a file or find out what to do */
char *s; /* if success, global src set from s */
{
static char word[MAXNAMLEN];
char rbuf[10];
int wlen;
strcpy(word, s);
src = word;
do {
if (!interactive) {
printf("\nThere is no topic \"%s\". I'm looking in the index.\n", word);
fflush(stdout);
return FIND_I;
}
else if (nhits > 1) {
printf("\nNot precise enough. Enter more letters, or RETURN: %s", word);
fflush(stdout);
wlen = strlen(word);
if (gets(word + wlen) == NULL)
return QUIT_I;
if (strlen(word) <= wlen) /* no new letters */
return NOOP_I;
}
else {
printf("\nThere is no topic \"%s\". Shall I look in the index? ", word);
fflush(stdout);
if (gets(rbuf) == NULL)
return QUIT_I;
if (*rbuf == 'y')
return FIND_I;
return NOOP_I;
}
} while (!match(word));
src = topics[hit];
return GOT_ONE;
}
char *cwdend = cwd; /* end of current directory string */
chwd(s) /* change directory routine */
register char *s;
{
register char *p;
register int i;
if (DOT(s))
return 1;
if (DOTDOT(s)) {
if (dirlevel == 0)
return !printf("\nYou're at the top level already.\n");
while (*--cwdend != '/');
*cwdend = 0;
dirlevel--;
}
else if (ROOT(s)) {
if (dirlevel == 0)
return !printf("\nYou're at the top level already.\n");
dirlevel = 0;
}
else if (dirlevel > 0) {
strcat(strcat(cwdend, "/"), s);
if (!EXECABLE(cwd) || !isadir(cwd))
return *cwdend = 0;
while (*++cwdend);
dirlevel++;
}
else {
for (i = 0; hvec[i]; i++) {
cwdend = strcpy(cwd, hvec[i]);
while (*++cwdend);
subdir = cwdend;
strcat(strcat(cwdend, "/"), s);
if (EXECABLE(cwd) && isadir(cwd))
break;
*cwdend = 0;
subdir = 0;
}
if (!hvec[i])
return 0;
while (*++cwdend);
dirlevel++;
}
/*
* reclaim subtopic storage, get new topics
*/
nhits = 0; hit = -1; tlen = rtlen;
if (dirlevel > 0) {
nt = subt;
topics = tptrs + subt;
getfiles(cwd);
vsort(topics);
}
else {
nt = subt - 1;
topics = tptrs;
subdir = 0;
}
return 1;
}
nextins() /* sets up globals: src, dst, and fname */
{
register char *p, *s;
register int ins, got_one = 0;
char c = 0;
/*
* initialize fname, src, dst, and keeppag; get instruction
*/
if (!interactive)
return QUIT_I;
fname[0] = 0;
src = dst = dstarg = 0;
keeppag = 0;
prompt();
if (gets(line) == NULL)
return QUIT_I;
/*
* trim blanks from end and beginning of line
*/
for (p = line+strlen(line)-1; isspace(*p) && p >= line; p--);
if (p < line)
return NOOP_I;
*++p = 0;
for (p = line; isspace(*p); p++);
/*
* parse zero operand instructions
*/
if (*p == '?')
return HELP_I;
if (*p == '/')
return ROOT_I;
if (DOT(p))
return LIST_I;
if (DOTDOT(p))
return BACK_I;
if (*p == '%' || *p == '$')
return QUIT_I;
if (*p == '<')
return YELL_I;
/*
* other instructions
*/
if (*p == '!') {
src = ++p;
return PASS_I;
}
if (*p == '*') {
for (p++; isspace(*p); p++);
if (*p)
src = p;
for (; *p && !isspace(*p); p++);
if (*p)
*p++ = 0;
for (; *p && isspace(*p); p++);
if (*p)
dst = p;
return FLAG_I;
}
if (*p == '=') { /* = as topic */
p++;
if (hit < 0)
return JUNK_I;
}
else if (number && isdigit(*p)) {
for (s = p; *p; p++)
if (!isdigit(*p))
break;
hit = atoi(s) - 1;
if (hit < 0 || hit >= (dirlevel == 0 ? nt : nt - subt)) {
printf("\nThere is no topic numbered %d.\n", atoi(s));
return NOOP_I;
}
got_one++;
src = topics[hit];
makefname(dirlevel, topics[hit]);
for (; isspace(*p); p++);
}
else if (isalpha(*p) || *p == '.' || *p == '-') { /* put topic name in src */
src = p;
for (; !isspecial(*p) && !isspace(*p); p++);
for (; isspace(*p); p++)
*p = 0; /* make sure it ends */
}
c = *p;
*p++ = 0;
if (!src) { /* no topic, see if default exists */
if (hit < 0) {
printf("\nYou need to give a topic name for that.");
return JUNK_I;
}
src = topics[hit];
}
if (c == '>' || c == '|') { /* more args allowed */
for (; isspace(*p); p++);
if (*p == '&') {
keeppag = 1;
for (p++; isspace(*p); p++);
}
if (!*p)
strcat(p, (c == '>' ? HELPSAVE : "lpr"));
dst = p;
for (; *p && !isspace(*p); p++);
for (; *p && isspace(*p); p++)
*p = 0; /* terminate dst */
if (*p) {
dstarg = p;
for (; *p && !isspace(*p); p++);
*p = 0; /* terminate dstarg */
}
}
/*
* instructions requiring src
*/
if (c == '+')
return FIND_I;
if (!got_one) {
if (!match(src) && (ins = whatnext(src)) != GOT_ONE)
return ins;
src = topics[hit];
makefname(dirlevel, topics[hit]);
}
if (!c)
return TYPE_I;
if (c == '|')
return LPRT_I;
if (c == '>')
return SAVE_I;
return JUNK_I;
}
prompt() /* prompt user */
{
register char *p;
if (!quiet)
fputs(helpprompt, stdout);
fputs("\n(", stdout);
fputs(progname, stdout);
if (subdir)
for (p = subdir; *p; p++)
if (*p == '/')
putchar(' ');
else
putchar(*p);
fputs(") ", stdout);
fflush(stdout);
}
substr(s, abbr) /* returns 1 if abbr abbreviates s */
register char *s, *abbr;
{
for (; *s == *abbr && *abbr; s++, abbr++);
return !*abbr;
}
fsubstr(s, abbr) /* returns 1 if abbr abbreviates lcased s */
register char *s, *abbr;
{
for (; lcase(*s) == *abbr && *abbr; s++, abbr++);
return !*abbr;
}
getfiles(dname) /* fill topicbuf and tptrs */
char *dname;
{
struct direct dbuf;
register struct direct *ep = &dbuf; /* directory entry pointer */
register int i;
#if BSD4_2
DIR *dp;
#define OPENDIR(s) ((dp = opendir(s)) != NULL)
#define DIRLOOP(s) for (s = readdir(dp); s != NULL; s = readdir(dp))
#define PATHSIZE 256
#define PATHSIZE 256
#define MAXDLEN ep->d_namlen
#define CLOSEDIR closedir(dp)
#else
int fd;
#define OPENDIR(s) ((fd = open(s, 0)) >= 0)
#define DIRLOOP(s) while (read(fd, s, sizeof *s) == sizeof *s)
#define MAXDLEN DIRSIZ
#define CLOSEDIR close(fd)
#endif
if (!OPENDIR(dname))
return perror(dname);
tptrs[nt] = &topicbuf[tlen];
DIRLOOP(ep) {
if (ep->d_name[0] == NULL || ep->d_ino == 0
|| DOT(ep->d_name) || DOTDOT(ep->d_name))
continue;
tptrs[nt++] = &topicbuf[tlen];
for (i = 0; i < MAXDLEN && ep->d_name[i]; tlen++, i++)
topicbuf[tlen] = ep->d_name[i];
topicbuf[tlen++] = 0;
}
tptrs[nt] = 0;
CLOSEDIR;
}
isadir(name)
char *name;
{
struct stat buf;
stat(name, &buf);
return buf.st_mode & S_IFDIR;
}
jmp_buf jmpenv;
onintr() /* catch broken pipe signals */
{
NO_RUPTS;
SET_SIGPIPE;
longjmp(jmpenv, 1);
}
wrapup(fp) /* close a file pointer and wait for child */
FILE *fp;
{
fclose(fp);
wait(0);
OK_RUPTS;
}
int firstime = 1; /* for first topic listing */
list() /* list topics in 4 columns */
{
register int col, row, i, last, nrows;
FILE *more;
fflush(stdout);
NO_RUPTS;
if ((more = outpipe()) == NULL)
PERROR;
if (setjmp(jmpenv)) {
wrapup(more);
return;
}
GET_SIGPIPE;
if (firstime) {
fprintf(more, "\n%s\n%s\n",
"Here is a list of topics I know about.",
"If you don't see the topic you want, I can look for it in the index.");
if (match("general"))
fprintf(more, "For a general introduction, please see \"general\" below.\n");
firstime = 0;
}
fputc('\n', more);
last = (dirlevel > 0 ? nt - subt : nt);
nrows = last / 4 + (last % 4 != 0 ? 1 : 0);
for (row = 0; row < nrows; row++)
for (i = row, col = 0; col < 4; i += nrows, col++) {
if (i >= last) {
fputc('\n', more);
col = 3;
}
else if (number)
fprintf(more, "%3d%c%-14.14s %c",
i + 1,
(hit == i ? '=' : ' '),
topics[i], (col == 3 ? '\n' : ' '));
else
fprintf(more, "%c%-17.17s %c",
(hit == i ? '=' : ' '),
topics[i], (col == 3 ? '\n' : ' '));
}
wrapup(more);
}
#define IBSIZE 16384
#define IPSIZE 512
find(s)
char *s;
{
register char *p;
register int i;
FILE *fp, *popen();
char sbuf[BUFSIZ];
if (!iptrs) { /* malloc storage once and for all */
indexbuf = (char *) malloc(IBSIZE + BUFSIZ);
iptrs = (char **) malloc((IPSIZE + 32) * sizeof(char *));
if (iptrs == 0 || indexbuf == 0) {
fprintf(stderr, "No index space.\n");
exit(1);
}
}
ni = 0; ilen = 0;
for (i = 0; hvec[i]; i++) {
sprintf(sbuf, "%s/%s", hvec[i], TOPICINDEX);
if ((fp = fopen(sbuf, "r")) == NULL) {
if (strcmp(hvec[i], HELPROOT) == 0) {
perror(sbuf);
exit(1);
}
continue;
}
getrefs(fp, 0);
fclose(fp);
}
#ifdef notdef
if ((fp = fopen(MANINDEX, "r")) == NULL) {
perror(MANINDEX);
exit(1);
}
mansegment = ni;
getrefs(fp, 1);
fclose(fp);
#else
{
static char *manindex[] = MANINDEX;
register char **mi;
mansegment = ni;
for (mi = manindex; *mi != NULL; mi++)
if ((fp = fopen(*mi, "r")) != NULL) {
getrefs(fp, 1);
fclose(fp);
break;
}
}
#endif
docsegment = ni;
if ((fp = fopen(DOCINDEX, "r")) != NULL) {
getrefs(fp, 0);
fclose(fp);
}
if (ni == 0)
return printf("\nNo relevant material; your request has been logged.\n");
putrefs();
if (!interactive)
exit(0);
while ((i = selectref()) != QUIT_I)
switch (i) {
case LIST_I:
putrefs();
break;
case HELP_I:
icomlist();
break;
case ROOT_I:
chwd("/");
case BACK_I:
list();
return;
case YELL_I:
yell();
break;
case PASS_I:
fflush(stdout);
pass(isrc);
break;
case FLAG_I:
flag(isrc, idst);
break;
default:
break;
}
puts("Bye.");
exit(0);
}
icomlist()
{
if (number)
puts("\nTo see a subject, type its name, a unique abbreviation, or its number.");
else
puts("\nTo see a subject, type its name or a unique abbreviation.");
puts("Other commands are:");
printf(" %c quit from help and return to the shell (control-d works also)\n", shellprompt);
printf(" subject display a \"subject\", whose name%syou supply\n",
(number ? " or number " : " "));
puts(" ? display this command list");
puts(" . list subject references found");
puts(" .. go back to the previous list of help topics");
puts(" / back up to and list the top level of topics");
puts(" < send comments or other input to the maintainer of help");
puts(" !command do a Unix command and then return to help");
puts(" * flag on/off set a \"flag\" on or off to adjust the behavior of help");
puts(" (type * by itself for a list of flags you can use)");
puts("The Unix command in brackets below each subject will display the same");
puts("information that I do. Sometimes information exists only off-line and I");
puts("have nothing to show you; try the local distributor of printed documentation.");
}
getrefs(fp, upm) /* get references to src from indexes qq.v. */
FILE *fp;
int upm; /* whether looking at upm database "whatis" */
{
/*
* indexbuf str0\0str1\0str2\0str3\0 ... str(ni-1)\0
* iptrs ^ ^ ^ ^ ... ^ 0
*/
register char *p, *ref;
char s[MAXNAMLEN]; /* lower case version of src */
char t[BUFSIZ]; /* temporary line buffer */
int preamble = !upm;
if (ilen > IBSIZE || ni > IPSIZE)
return puts("Index space full.");
for (p = src, ref = s; *p; p++, ref++) /* ref becomes lower case */
*ref = lcase(*p); /* version of src */
*ref = 0;
ref = s;
iptrs[ni] = &indexbuf[ilen];
while (fgets(t, BUFSIZ, fp) != NULL) {
if (preamble) { /* indexes all have preamble to skip */
if ((p = index(t, '-')) && fsubstr(p, "------"))
preamble = 0; /* preamble over */
continue;
}
for (p = t; *p; p++)
if (lcase(*p) == *ref && fsubstr(p, ref))
break;
if (!*p)
continue;
iptrs[ni++] = &indexbuf[ilen];
for (p = t; *p && isspace(*p); p++);
for (; *p && *p != ' '; ilen++, p++)
indexbuf[ilen] = *p;
if (upm)
for (; *p && *p != '\t'; ilen++, p++)
if (*p == '-' && *(p + 1) == ' ')
break; /* cover glitches in MANINDEX */
else
indexbuf[ilen] = *p;
indexbuf[ilen++] = 0;
for (; *p && isspace(*p); p++);
if (upm && *p == '-' && *(p + 1) == ' ')
p += 2;
iptrs[ni++] = &indexbuf[ilen];
for (; *p; ilen++, p++)
indexbuf[ilen] = *p;
indexbuf[ilen++] = 0;
}
iptrs[ni] = 0;
fclose(fp);
}
putrefs() /* list references stored in iptrs */
{
register int i;
register char *p, *format;
FILE *more;
NO_RUPTS;
if ((more = outpipe()) == NULL)
PERROR;
if (setjmp(jmpenv)) {
wrapup(more);
return;
}
GET_SIGPIPE;
format = (number ? "%3d %s\t\t[ " : "%s\t\t[ ");
fprintf(more, "\nThese subjects appear to be related to \"%s\".\n\n", src);
for (i = 0; i < ni; i += 2) {
if (number)
fprintf(more, format, (i / 2 + 1), iptrs[i + 1]);
else
fprintf(more, format, iptrs[i + 1]);
if (i < mansegment) {
fputs("help ", more);
for (p = iptrs[i]; *p; p++)
putc((*p == '/' ? ' ' : *p), more);
}
else if (i >= docsegment)
fprintf(more, "Off-line only document: %s", iptrs[i]);
else {
fputs("man ", more);
for (p = iptrs[i]; *p && *p != '('; p++);
for (p++; *p != ')'; p++)
putc(lcase(*p), more);
putc(' ', more);
for (p = iptrs[i]; *p != ',' && *p != ' '; p++)
putc(*p, more);
}
fputs(" ]\n", more);
}
wrapup(more);
}
FILE *
outpipe() /* return a file descriptor pointing to "more" */
{
int fildes[2];
FILE *fp, *fdopen();
if (pipe(fildes) == -1)
PERROR;
if (!fork()) {
OK_RUPTS;
close(fildes[1]);
fclose(stdin);
if (dup(fildes[0]) == -1)
PERROR;
close(fildes[0]);
execlp("more", "more", "-s", 0);
PERROR;
}
close(fildes[0]);
return fdopen(fildes[1], "w");
}
iprompt() /* prompt user - index version */
{
if (!quiet)
fputs(indexprompt, stdout);
printf("\n(%s-index %s) ", progname, src);
fflush(stdout);
}
selectref() /* read user instruction for indexing */
{
register char *p, *s;
register int ins;
char sbuf[BUFSIZ];
isrc = idst = 0;
iprompt();
if (gets(sbuf) == NULL)
exit(0);
for (p = sbuf+strlen(sbuf)-1; isspace(*p) && p >= sbuf; p--);
if (p < sbuf)
return NOOP_I;
*++p = 0; /* blanks now trimmed */
for (p = sbuf; isspace(*p); p++);
if (*p == '%' || *p == '$')
return QUIT_I;
if (DOT(p))
return LIST_I;
if (DOTDOT(p))
return BACK_I;
if (*p == '?')
return HELP_I;
if (*p == '/')
return ROOT_I;
if (*p == '<')
return YELL_I;
if (*p == '!') {
isrc = ++p;
return PASS_I;
}
if (*p == '*') {
for (p++; isspace(*p); p++);
if (*p)
isrc = p;
for (; *p && !isspace(*p); p++);
if (*p)
*p++ = 0;
for (; *p && isspace(*p); p++);
if (*p)
idst = p;
return FLAG_I;
}
for (s = p; *s; s++)
if (!isdigit(*s))
break;
if (!*s && number) {
ihit = 2 * atoi(p) - 1;
if (ihit < 1 || ihit > ni) {
printf("\nThere is no subject numbered %d.\n", atoi(p));
return NOOP_I;
}
}
else
if ((ins = iwhatnext(p)) != GOT_ONE)
return ins;
if (ihit < mansegment) {
makefname(0, iptrs[ihit - 1]);
page();
return NOOP_I;
}
if (ihit >= docsegment && docsegment > 0) {
puts("\nSorry, that reference is not available on the computer.");
return NOOP_I;
}
if (!fork()) {
for (s = sbuf, p = iptrs[ihit - 1]; *p != ' ' && *p != ','; p++)
*s++ = *p;
for (; *p != '('; p++);
for (*s++ = 0, p++; *p != ')'; p++)
*s++ = lcase(*p);
for (*s-- = 0; *s; s--);
execlp("man", "man", ++s, sbuf, 0);
PERROR;
}
NO_RUPTS;
wait(0);
OK_RUPTS;
return NOOP_I;
}
iwhatnext(s) /* indexing version of whatnext */
char *s;
{
static char word[MAXNAMLEN];
int wlen;
strcpy(word, s);
isrc = word;
while (!imatch(word))
if (inhits > 1) {
printf("\nNot precise enough. Enter more letters, or RETURN: %s", word);
fflush(stdout);
wlen = strlen(word);
if (gets(word + wlen) == NULL)
return QUIT_I;
if (strlen(word) <= wlen) /* no new letters */
return NOOP_I;
}
else {
printf("\nThere is no subject \"%s\".\n", word);
return NOOP_I;
}
isrc = iptrs[ihit];
return GOT_ONE;
}
imatch(abbr) /* indexing version of match (on unsorted list) */
char *abbr;
{
register char **t;
register char *p = abbr;
register char **last;
last = iptrs + (docsegment < 0 ? ni : docsegment);
inhits = 0;
for (t = iptrs + 1; t < last; t += 2)
if (**t != *p) /* quickly check first character */
continue;
else if (substr(*t, abbr)) {
inhits++;
ihit = t - iptrs;
if (strcmp(*t, abbr) == 0)
return (inhits = 1);
}
return (inhits == 1);
}
makefname(dirlev, tail) /* build fname from cwd and tail, return no. matched */
int dirlev; /* directory level */
char *tail; /* tail of pathname to use */
{
register int i;
register char *p;
if (dirlev > 0) {
sprintf(fname, "%s/%s", cwd, tail);
fnamect = (EXISTS(fname) ? 1 : 0);
return fnamect;
}
fnamect = 0; /* count of number of dirs. where tail exists */
p = fname; /* full names of files with tails as above */
for (i = 0; hvec[i]; i++) {
sprintf(p, "%s/%s", hvec[i], tail);
if (EXISTS(p)) {
fnamect++;
p += strlen(p) + 1;
}
}
return fnamect;
}
pass(s) /* replace = with fname and send to system */
register char *s; /* allow \= to pass as =, but \x passes as \x */
{
register char *p;
register int escaped = 0;
char buf[BUFSIZ];
PUTNL;
for (p = buf; *s; s++) {
if (escaped) {
if (*s != '=')
*p++ = '\\';
*p++ = *s;
escaped = 0;
}
else if (*s == '\\')
escaped = 1;
else if (*s == '=' && hit >= 0) {
makefname(dirlevel, topics[hit]);
strcpy(p, fname);
for (; *p; p++);
}
else
*p++ = *s;
}
*p = 0;
if (!fork()) {
putchar('\n');
execl(shell, shell, "-c", buf, 0);
PERROR;
}
NO_RUPTS;
wait(0);
OK_RUPTS;
}
flag(f, val) /* set flag on or off; 0 in src and dst gives help */
char *f;
char *val;
{
if (!f) {
puts("\nCurrent flag settings and their meanings are:");
printf(" number\t%suse numbers in topic and index listings\n",
(number ? "on\t" : "off\tdo not "));
printf(" quiet \t%ssuppress the instruction line before prompting\n",
(quiet ? "on\t" : "off\tdo not "));
return;
}
if (substr("number", f)) {
printf("\nnumber: was %s,", (number ? "on" : "off"));
if (!val)
number = (number ? 0 : 1); /* toggle */
else
number = (val[1] == 'n' ? 1 : 0);
printf(" is %s.\n", (number ? "on" : "off"));
}
else if (substr("quiet", f)) {
printf("\nquiet: was %s,", (quiet ? "on" : "off"));
if (!val)
quiet = (quiet ? 0 : 1); /* toggle */
else
quiet = (val[1] == 'n' ? 1 : 0);
printf(" is %s.\n", (quiet ? "on" : "off"));
if (more_d)
strcpy(more_d, (quiet ? " " : "-d"));
}
else
puts("\nThat is not a flag I know about. Type * for a complete list.");
}
page() /* print a help file with more or run program */
{
char c[2];
FILE *fp, *more;
if ((fp = fopen(fname, "r")) == NULL) {
perror(fname);
return; /* try to continue on this error */
}
c[0] = getc(fp); /* check first 2 characters of first file */
c[1] = getc(fp); /* looking for magic characters and numbers */
/* (should check more than just the first) */
if (runs(c)) { /* if a program, run it and then return */
fclose(fp);
OK_RUPTS;
return;
}
rewind(fp);
NO_RUPTS;
if ((more = outpipe()) == NULL)
PERROR;
if (setjmp(jmpenv)) {
fclose(fp);
wrapup(more);
return;
}
GET_SIGPIPE;
putfiles(fp, more);
fclose(fp);
wrapup(more);
}
#define isblank(s) (*s == '\n')
#define sqspace(s) { if (!isblank(s) || !wasblank) fputs(s, out); else linect--; wasblank = isblank(s); }
putfiles(in, out) /* print file(s) onto out file */
FILE *in; /* first of the input files */
FILE *out;
{
register char *fn = fname;
register int linectsum = 0;
while (fnamect--) {
if (in == NULL && (in = fopen(fn, "r")) == NULL)
perror(fn);
linectsum += filter(in, out);
fclose(in);
in = NULL;
fn += strlen(fn) + 1;
}
return linectsum;
}
filter(in, out) /* filter out multiple blank lines and page banners */
FILE *in;
FILE *out;
{
char lbuf[BUFSIZ];
register int lineno, wasblank;
register char *s = lbuf;
int linect, i;
char *p;
/* check page one for proper headers; if none then keep pagination */
wasblank = 0;
for (lineno = 1; fgets(s, BUFSIZ, in) != NULL; lineno++)
if (!isblank(s) || lineno >= 4)
break;
if (!(lineno == 4 &&
(((p = index(s, 'H')) && substr(p, "HELP")) /* help */
|| index(s, ')') != rindex(s, ')')))) /* man */
keeppag = 1; /* criteria not met */
if (lineno == 1 && (*s = '#' || *s == ':')) {
keeppag = 1; /* this is a script file */
lineno = 0;
fputc('\n', out);
}
else {
for (i = lineno - 1; i; i--)
if (keeppag)
fputc('\n', out);
else
sqspace("\n");
if (keeppag)
fputs(s, out);
else
sqspace(s);
}
linect = lineno;
while (fgets(s, BUFSIZ, in) != NULL) {
lineno++, linect++;
if (lineno > 66)
lineno = 1;
if (keeppag) /* this global overrides our page 1 analysis */
fputs(s, out);
else if (lineno > 7 && lineno < 60 /* skip page banners */
|| linect < 8) /* let first 7 go */
sqspace(s)
}
return linect;
}
runs(c) /* run program or script named by fname, else return 0 */
char c[];
{
int *magic = (int *)c;
if (c[0] != '#' && c[0] != ':'
&& *magic != 0413 && *magic != 0410 && *magic != 0407)
return 0;
if (!fork()) {
if (*magic == 0413 || *magic == 0410 || *magic == 0407)
execv(fname, 0);
else if (c[0] == '#')
execlp("csh", "csh", "-f", fname, 0);
else
execlp("sh", "sh", fname, 0);
perror(fname);
}
NO_RUPTS;
wait(0);
OK_RUPTS;
return 1;
}
comlist() /* list help instructions available */
{
if (number)
puts("\nTo see a topic, type its name, a unique abbreviation, or its number.");
else
puts("\nTo see a topic, type its name or a unique abbreviation.");
puts("Here is a list of commands:");
printf(" %c quit from help and return to the shell (control-d works also)\n", shellprompt);
printf(" topic display a \"topic\", whose name%syou supply\n",
(number ? " or number " : " "));
puts(" topic + see what more is known about a topic");
puts(" topic > file save a topic in a file (you supply the name \"file\")");
puts(" topic | lpr paginate and print a topic on the lineprinter");
puts(" topic >& file save a topic in a file with pagination");
puts(" ? display this command list");
puts(" . list topics at the current level");
puts(" .. back up to and list the next higher level of topics");
puts(" / back up to and list the top level of topics");
puts(" < send comments or other input to the maintainer of help");
puts(" !command do a Unix command and then return to help");
puts(" * flag on/off set a \"flag\" on or off to adjust the behavior of help");
puts(" (type * by itself for a list of flags you can use)");
puts("If you enter no topic in a command or just an equals sign (=),");
puts("the most recent topic at this level is used.");
}
match(abbr) /* find a match for abbr in current directory */
char *abbr;
{
register char **t;
register char *p = abbr;
nhits = 0;
for (t = topics; *t; t++) /* find first string beginning */
if (**t == *p) /* with same letter */
break;
for (; *t && **t == *p; t++)
if (substr(*t, abbr)) {
nhits++;
hit = t - topics;
if (strcmp(*t, abbr) == 0)
return (nhits = 1);
}
return (nhits == 1);
}
/*
* radix sort of an alphanumeric list, identical keys deleted
* Originally by D. Wasley, July 1980
*/
#define MSB 0100
vsort(list)
char *list[];
{
char **endlist = tptrs + nt - 1;
int offset = 0;
if (endlist > list)
_sortb(list, endlist, 0); /* recursive sort */
}
_sortb(list, endlist, offset)
char **list, **endlist; int offset;
{
register char **high, c;
_sortr(list, endlist, offset, MSB); /* radix sort on this char */
while (list < endlist) { /* now sort each sublist that */
c = (*list)[offset]; /* starts with a common char */
high = list;
while ((*++high)[offset] == c && high <= endlist) ;
if (high - list > 1) {
if (c)
_sortb(list, high-1, offset+1);
else /* kill off identical keys */
for (list++; list < high; list++)
*list = 0;
}
list = high;
}
}
_sortr(list, endlist, offset, mask)
int offset, mask; char **list, **endlist;
{
register char **low, **high, *temp;
low = list;
high = endlist;
while (low < high) {
while (low < endlist && ((*low)[offset] & mask) == 0)
low++;
while (high > list && ((*high)[offset] & mask) != 0)
high--;
if (high > low) {
temp = *high;
*high = *low;
*low = temp;
}
}
if ((mask >>= 1) != 0) { /* redefine mask and sort sublists */
if (endlist > low)
_sortr(low, endlist, offset, mask);
if (high > list)
_sortr(list, high, offset, mask);
}
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.