[comp.sources.unix] v14i041: Mail User's Shell, version 6.0, Part09/14

rsalz@bbn.com (Rich Salz) (04/15/88)

Submitted-by: island!argv@sun.com (Dan Heller)
Posting-number: Volume 14, Issue 41
Archive-name: mush6.0/part09

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 9 (of 14)."
# Contents:  loop.c tool_help
# Wrapped by rsalz@fig.bbn.com on Wed Apr 13 20:04:51 1988
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'loop.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'loop.c'\"
else
echo shar: Extracting \"'loop.c'\" \(24591 characters\)
sed "s/^X//" >'loop.c' <<'END_OF_FILE'
X/* loop.c 	(c) copyright 1986 (Dan Heller) */
X
X/*
X * Here is where the main loop for text mode exists. Also, all the
X * history is kept here and all the command parsing and execution
X * and alias expansion in or out of text/graphics mode is done here.
X */
X
X#include "mush.h"
X
X#ifdef BSD
X#include <sys/wait.h>
X#else
X#ifndef SYSV
X#include <wait.h>
X#endif /* SYSV */
X#endif /* BSD */
X
X#define ever (;;)
X#define MAXARGS		100
X#define isdelimeter(c)	(index(" \t;|", c))
X
char *alias_expand(), *hist_expand(), *reference_hist(), *hist_from_str();
char **calloc();
X
struct history {
X    int histno;
X    char **argv;
X    struct history *prev;
X    struct history *next;
X};
static struct history *hist_head, *hist_tail;
struct history *malloc();
X#define NULL_HIST	(struct history *)0
X
static char *last_aliased;
static int hist_size, print_only;
X
do_loop()
X{
X    register char *p, **argv;
X    char	  **last_argv = DUBL_NULL, line[256];
X    int   	  argc, c = (iscurses - 1);
X    struct history *new;
X#ifdef CURSES
X    int		  save_echo_flg = FALSE;
X#endif /* CURSES */
X
X    /* catch the right signals -- see main.c for other signal catching */
X    (void) signal(SIGINT, catch);
X    (void) signal(SIGQUIT, catch);
X    (void) signal(SIGHUP, catch);
X    (void) signal(SIGTERM, catch);
X    (void) signal(SIGCHLD, sigchldcatcher);
X    (void) signal(SIGPIPE, SIG_IGN); /* if pager is terminated before end */
X
X    turnoff(glob_flags, IGN_SIGS);
X    if (hist_size == 0) /* if user didn't set history in .rc file */
X	hist_size = 1;
X
X    for ever {
X	if (setjmp(jmpbuf)) {
X	    Debug("jumped back to main loop (%s: %d)\n", __FILE__,__LINE__);
X#ifdef CURSES
X	    if (c > 0) { /* don't pass last command back to curses_command() */
X		iscurses = TRUE;
X		c = hit_return();
X	    }
X#endif /* CURSES */
X	}
X#ifdef CURSES
X	if (iscurses || c > -1) {
X	    /* if !iscurses, we know that we returned from a curses-based
X	     * call and we really ARE still in curses. Reset tty modes!
X	     */
X	    if (ison(glob_flags, ECHO_FLAG)) {
X		turnoff(glob_flags, ECHO_FLAG);
X		echo_off();
X		save_echo_flg = TRUE;
X	    }
X	    if (!iscurses) {
X		iscurses = TRUE;
X		c = hit_return();
X	    }
X	    if ((c = curses_command(c)) == -1 && save_echo_flg) {
X		echo_on();
X		turnon(glob_flags, ECHO_FLAG);
X		save_echo_flg = FALSE;
X	    }
X	    continue;
X	}
X#endif /* CURSES */
X	clear_msg_list(msg_list);
X	(void) check_new_mail();
X
X	/* print a prompt according to printf like format:
X	 * (current message, deleted, unread, etc) are found in mail_status.
X	 */
X	mail_status(1);
X	if (Getstr(line, 256, 0) > -1)
X	    p = line;
X	else {
X	    if (p = do_set(set_options, "ignoreeof")) {
X		if (!*p)
X		    continue;
X		else
X		    p = strcpy(line, p); /* so processing won't destroy var */
X	    } else {
X		(void) quit(0, DUBL_NULL);
X		continue; /* quit may return if new mail arrives */
X	    }
X	}
X
X	skipspaces(0);
X	if (!*p && !(p = do_set(set_options, "newline"))) {
X	    (void) readmsg(0, DUBL_NULL, msg_list);
X	    continue;
X	}
X	if (!*p) /* if newline is set, but no value, then continue */
X	    continue;
X
X	/* upon error, argc = -1 -- still save in history so user can
X	 * modify syntax error. if !argv, error is too severe.  We pass
X	 * the last command typed in last_argv for history reference, and
X	 * get back the current command _as typed_ (unexpanded by aliases
X	 * or history) in last_argv.
X	 */
X	if (!(argv = make_command(p, &last_argv, &argc)))
X	    continue;
X	/* now save the new argv in the newly created history structure */
X	if (!(new = malloc(sizeof (struct history))))
X	    error("can't increment history");
X	else {
X	    new->histno = ++hist_no;
X	    new->argv = last_argv; /* this is the command _as typed_ */
X	    new->next = NULL_HIST;
X	    new->prev = hist_head;
X	    /* if first command, the tail of the list is "new" because
X	     * nothing is in the list.  If not the first command, the
X	     * head of the list's "next" pointer points to the new command.
X	     */
X	    if (hist_head)
X		hist_head->next = new;
X	    else
X		hist_tail = new;
X	    hist_head = new;
X	}
X	/*
X	 * truncate the history list to the size of the history.
X	 * Free the outdated command (argv) and move the tail closer to front.
X	 * use a while loop in case the last command reset histsize to "small"
X	 */
X	while (hist_head->histno - hist_tail->histno >= hist_size) {
X	    hist_tail = hist_tail->next;
X	    free_vec(hist_tail->prev->argv);
X	    xfree(hist_tail->prev);
X	    hist_tail->prev = NULL_HIST;
X	}
X
X	if (print_only) {
X	    print_only = 0;
X	    free_vec(argv);
X	} else if (argc > -1)
X	    (void) do_command(argc, argv, msg_list);
X    }
X}
X
X/* make a command from "buf".
X * first, expand history references. make an argv from that and save
X * in last_argv (to be passed back and stored in history). After that,
X * THEN expand aliases. return that argv to be executed as a command.
X */
char **
make_command(start, last_argv, argc)
register char *start, ***last_argv;
int *argc;
X{
X    register char *p, **tmp;
X    char buf[BUFSIZ];
X
X    if (!last_argv)
X	tmp = DUBL_NULL;
X    else
X	tmp = *last_argv;
X    /* first expand history -- (here's where argc gets set)
X     * pass the buffer, the history list to reference if \!* (or whatever)
X     * result in static buffer (pointed to by p) -- even if history parsing is
X     * ignored, do this to remove \'s behind !'s and verifying matching quotes
X     */
X    if (!(p = hist_expand(start, tmp, argc)) || Strcpy(buf, p) > BUFSIZ)
X	return DUBL_NULL;
X    /* if history was referenced in the command, echo new command */
X    if (*argc)
X	puts(buf);
X
X    /* argc may == -1; ignore this error for now but catch it later */
X    if (!(tmp = mk_argv(buf, argc, 0)))
X	return DUBL_NULL;
X
X    /* save this as the command typed */
X    if (last_argv)
X	*last_argv = tmp;
X
X    /* expand all aliases (recursively)
X     * pass _this_ command (as typed and without aliases) to let aliases
X     * with "!*" be able to reference the command line just typed.
X     */
X    if (alias_stuff(buf, *argc, tmp) == -1)
X	return DUBL_NULL;
X
X    /* now, expand variable references and make another argv */
X    if (!variable_expand(buf))
X	return DUBL_NULL;
X
X    if (!last_argv)
X	free_vec(tmp);
X
X    /* with everything expanded, build final argv from new buffer
X     * Note that backslashes and quotes still exist. Those are removed
X     * because argument final is 1.
X     */
X    tmp = mk_argv(buf, argc, 1);
X    return tmp;
X}
X
X/*
X * do the command specified by the argument vector, argv.
X * First check to see if argc < 0. If so, someone called this
X * command and they should not have! make_command() will return
X * an argv but it will set argc to -1 if there's a sytanx error.
X */
do_command(argc, argv, list)
char **argv, list[];
X{
X    register char *p;
X    char **tmp = argv;
X    int i, status;
X    long do_pipe = ison(glob_flags, DO_PIPE);
X
X    turnoff(glob_flags, IS_PIPE);
X
X    if (argc <= 0) {
X	turnoff(glob_flags, DO_PIPE);
X	return -1;
X    }
X
X    clear_msg_list(list);
X
X    for (i = 0; do_pipe >= 0 && argc; argc--) {
X	p = argv[i];
X	if (!strcmp(p, "|") || !strcmp(p, ";")) {
X	    if (do_pipe = (*p == '|'))
X		turnon(glob_flags, DO_PIPE);
X	    argv[i] = NULL;
X	    /* if piping, then don't call next command if this one fails. */
X	    if ((status = exec_argv(i, argv, list)) <= -1 && do_pipe) {
X		print("Broken pipe.\n");
X		do_pipe = -1, turnoff(glob_flags, DO_PIPE);
X	    }
X	    /* if command failed and piping, or command worked and not piping */
X	    if (do_pipe <= 0)
X		status = 0, clear_msg_list(list);
X	    /* else command worked and piping: set is_pipe */
X	    else if (!status)
X		turnon(glob_flags, IS_PIPE), turnoff(glob_flags, DO_PIPE);
X	    argv[i] = p;
X	    argv += (i+1);
X	    i = 0;
X	} else
X	    i++;
X    }
X    if (do_pipe >= 0)
X	status = exec_argv(i, argv, list);
X    Debug("freeing: "), print_argv(tmp);
X    free_vec(tmp);
X    turnoff(glob_flags, DO_PIPE);
X    return status;
X}
X
exec_argv(argc, argv, list)
register char **argv, list[];
X{
X    register int n;
X
X    if (!argv || !*argv || **argv == '\\' && !*++*argv) {
X	if (ison(glob_flags, IS_PIPE) || ison(glob_flags, DO_PIPE))
X	    print("Invalid null command.\n");
X	return -1;
X    }
X    Debug("executing: "), print_argv(argv);
X
X    /* if interrupted during execution of a command, return -1 */
X    if (isoff(glob_flags, IGN_SIGS) && setjmp(jmpbuf)) {
X	Debug("jumped back to exec_argv (%s: %d)\n", __FILE__, __LINE__);
X	return -1;
X    }
X
X    /* standard commands */
X    for (n = 0; cmds[n].command; n++)
X	if (!strcmp(argv[0], cmds[n].command))
X	    return (*cmds[n].func)(argc, argv, list);
X
X    /* ucb-Mail compatible commands */
X    for (n = 0; ucb_cmds[n].command; n++)
X	if (!strcmp(argv[0], ucb_cmds[n].command))
X	    return (*ucb_cmds[n].func)(argc, argv, list);
X
X    /* for hidden, undocumented commands */
X    for (n = 0; hidden_cmds[n].command; n++)
X	if (!strcmp(argv[0], hidden_cmds[n].command))
X	    return (*hidden_cmds[n].func)(argc, argv, list);
X
X#ifdef SUNTOOL
X    /* check tool-only commands */
X    if (istool)
X	for (n = 0; fkey_cmds[n].command; n++)
X	    if (!strcmp(argv[0], fkey_cmds[n].command))
X		return (*fkey_cmds[n].func)(argc, argv);
X#endif /* SUNTOOL */
X
X    if ((isdigit(**argv) || index("^.*$-`{}", **argv))
X			&& (n = get_msg_list(argv, list)) != 0) {
X	if (n > 0 && isoff(glob_flags, DO_PIPE))
X	    for (n = 0; n < msg_cnt; n++)
X		if (msg_bit(list, n)) {
X		    display_msg((current_msg = n), (long)0);
X		    unset_msg_bit(list, n);
X		}
X	return 0;
X    } else if (strlen(*argv) == 1 && index("$^.", **argv)) {
X	if (!msg_cnt)
X	    print("No messages.");
X	else {
X	    if (**argv != '.')
X		current_msg = (**argv == '$') ? msg_cnt-1 : 0;
X	    set_msg_bit(list, current_msg);
X	    display_msg(current_msg, (long)0);
X	}
X	return 0;
X    }
X    /* get_msg_list will set the current message bit if nothing parsed */
X    unset_msg_bit(list, current_msg);
X
X    if (!istool && do_set(set_options, "unix")) {
X	if (ison(glob_flags, IS_PIPE) || ison(glob_flags, DO_PIPE))
X	    print("There is no piping to or from UNIX commands.\n");
X	else
X	    execute(argv);  /* try to execute a unix command */
X	return -1; /* doesn't affect messages! */
X    }
X
X    print("%s: command not found.\n", *argv);
X    if (!istool)
X	print("type '?' for valid commands, or type `help'\n");
X    return -1;
X}
X
X/* recursively look for aliases on a command line.  aliases may
X * reference other aliases.
X */
alias_stuff(b, argc, Argv)
register char 	*b, **Argv;
X{
X    register char 	*p, **argv = DUBL_NULL;
X    register int 	n = 0, i = 0, Argc;
X    static int 		loops;
X    int 		dummy;
X
X    if (++loops == 20) {
X	print("Alias loop.\n");
X	return -1;
X    }
X    for (Argc = 0; Argc < argc; Argc++) {
X	register char *h = Argv[n + ++i];
X	register char *p2 = "";
X
X	/* we've hit a command separator or the end of the line */
X	if (h && strcmp(h, ";") && strcmp(h, "|"))
X	    continue;
X
X	/* create a new argv containing this (possible subset) of argv */
X	if (!(argv = calloc((unsigned)(i+1), sizeof (char *))))
X	    continue;
X	while (i--)
X	    strdup(argv[i], Argv[n+i]);
X
X	if ((!last_aliased || strcmp(last_aliased, argv[0]))
X			&& (p = alias_expand(argv[0]))) {
X	    /* if history was referenced, ignore the rest of argv
X	     * else copy all of argv onto the end of the buffer.
X	     */
X	    if (!(p2 = hist_expand(p, argv, &dummy)))
X		break;
X	    if (!dummy)
X		(void) argv_to_string(p2+strlen(p2), argv+1);
X	    if (Strcpy(b, p2) > BUFSIZ) {
X		print("Not enough buffer space.\n");
X		break;
X	    }
X	    /* release old argv and build a new one based on new string */
X	    free_vec(argv);
X	    if (!(argv = mk_argv(b, &dummy, 0)))
X		break;
X	    if (alias_stuff(b, dummy, argv) == -1)
X		break;
X	} else
X	    b = argv_to_string(b, argv);
X	xfree(last_aliased), last_aliased = NULL;
X	free_vec(argv);
X	b += strlen(b);
X	if (h) {
X	    p2 = h;
X	    while (++Argc < argc && (h = Argv[Argc]))
X		if (strcmp(h, ";") && strcmp(h, "|"))
X		    break;
X	    b += strlen(sprintf(b, " %s ", p2));
X	    n = Argc--;
X	}
X	i = 0;
X    }
X    xfree(last_aliased), last_aliased = NULL;
X    --loops;
X    if (Argc < argc) {
X	free_vec(argv);
X	return -1;
X    }
X    return 0;
X}
X
char *
alias_expand(cmd)
register char *cmd;
X{
X    register char *p;
X    register int x;
X
X    if (!(p = do_set(functions, cmd)))
X	return NULL;
X    last_aliased = savestr(cmd); /* to be freed elsewhere; don't strdup! */
X    if (isoff(glob_flags, WARNING))
X	return p;
X    for (x = 0; cmds[x].command; x++)
X	if (!strcmp(cmd, cmds[x].command)) {
X	    wprint("(real command: \"%s\" aliased to \"%s\")\n", cmd, p);
X	    return p;
X	}
X    for (x = 0; ucb_cmds[x].command; x++)
X	if (!strcmp(cmd, ucb_cmds[x].command)) {
X	    wprint("(ucb-command: \"%s\" aliased to \"%s\")\n", cmd, p);
X	    return p;
X	}
X    return p;
X}
X
X/* expand history references and separate message lists from other tokens */
char *
hist_expand(str, argv, hist_was_referenced)
register char *str, **argv;
register int *hist_was_referenced;
X{
X    static char   buf[BUFSIZ];
X    register int  b = 0, inquotes = 0;
X    int 	  first_space = 0, ignore_bang;
X
X    ignore_bang = (ison(glob_flags, IGN_BANG) ||
X		   do_set(set_options, "ignore_bang"));
X
X    if (hist_was_referenced)
X	*hist_was_referenced = 0;
X    while (*str) {
X	while (!inquotes && isspace(*str))
X	    str++;
X	do  {
X	    if (!*str)
X		break;
X	    if (b >= BUFSIZ-1) {
X		print("argument list too long.\n");
X		return NULL;
X	    }
X	    if ((buf[b] = *str++) == '\'') {
X		/* make sure there's a match! */
X		inquotes = !inquotes;
X	    }
X	    if (!first_space && !inquotes && index("0123456789{}*$", buf[b])
X			     && b && !index("0123456789{}- \t", buf[b-1])) {
X		buf[b+1] = buf[b];
X		buf[b++] = ' ';
X		while ((buf[++b] = *str++) && index("0123456789-,${}", buf[b]))
X		    ;
X		if (!buf[b])
X		    str--;
X		first_space++;
X	    }
X	    /* check for (;) (|) or any other delimeter and separate it from
X	     * other tokens.
X	     */
X	    if (!inquotes && buf[b] != '\0' && isdelimeter(buf[b]) &&
X		(b < 0 || buf[b-1] != '\\')) {
X		if (b && !isspace(buf[b-1]))
X		    buf[b+1] = buf[b], buf[b++] = ' ';
X		b++;
X		break;
X	    }
X	    /* if double-quotes, just copy byte by byte, char by char ... */
X	    if (buf[b] == '"') {
X		int B = b;
X		while ((buf[++B] = *str++) && buf[B] != '"')
X		    ;
X		if (buf[B])
X		    b = B;
X		else
X		    str--;
X		b++;
X		continue;
X	    }
X	    if (buf[b] == '\\') {
X		if ((buf[++b] = *str) == '!')
X		    buf[--b] = '!';
X		++str;
X	    } else if (buf[b] == '!' && *str && *str != '\\' && !isspace(*str)
X			&& !ignore_bang) {
X		char word[BUFSIZ];
X		if (!(str = reference_hist(str, word, argv)))
X		    return NULL;
X		if (hist_was_referenced)
X		    *hist_was_referenced = 1;
X		if (strlen(word) + b >= BUFSIZ) {
X		    print("argument list too long.\n");
X		    return NULL;
X		}
X		b += Strcpy(&buf[b], word) - 1;
X	    }
X	    b++;
X	} while (*str && (!isdelimeter(*str) || str[-1] == '\\'));
X	if (!inquotes)
X	    first_space++, buf[b++] = ' ';
X    }
X    buf[b] = 0;
X    return buf;
X}
X
X/*
X * find mush variable references and expand them to their values.
X * variables are preceded by a '$' and cannot be within single
X * quotes.  Only if expansion has been made do we copy buf back into str.
X * RETURN 0 on failure, 1 on success.
X */
variable_expand(str)
register char *str;
X{
X    register int     b = 0;
X    char             buf[BUFSIZ], *start = str;
X    int		     expanded = 0;
X
X    while (*str) {
X	if (*str == '~' && (str == start || isspace(*(str-1)))) {
X	    register char *p = any(str, " \t"), *tmp;
X	    int x = 1;
X	    if (p)
X		*p = 0;
X	    tmp = getpath(str, &x);
X	    /* if error, print message and return 0 */
X	    if (x == -1) {
X		wprint("%s: %s\n", str, tmp);
X		return 0;
X	    }
X	    b += Strcpy(buf+b, tmp);
X	    if (p)
X		*p = ' ', str = p;
X	    else
X		str += strlen(str);
X	    expanded = 1;
X	}
X	/* if single-quotes, just copy byte by byte, char by char ... */
X	if ((buf[b] = *str++) == '\'') {
X	    while ((buf[++b] = *str++) && buf[b] != '\'')
X		;
X	    if (!buf[b])
X		str--;
X	}
X	/* If $ is eoln, continue.  Variables must start with a `$'
X	 * and continue with {, _, a-z, A-Z or it is not a variable.      }
X	 */
X	if (buf[b] == '$' &&
X	    (isalpha(*str) || *str == '{' || *str == '_'))  /* } */  {
X	    register char c, *p, *var, *end;
X
X	    if (*(end = var = str) == '{')  /* } */   {
X		if (!isalpha(*++str) && *str != '_') {
X		    print("Illegal variable name.\n");
X		    return 0;
X		}
X		if (!(end = index(var, '}'))) /* { */   {
X		    print("Unmatched '{'.\n"); /* } */
X		    return 0;
X		}
X		*end++ = 0;
X	    } else
X		while (isalnum(*++end) || *end == '_')
X		    ;
X	    /* advance "str" to the next parse-point, replace the end
X	     * of "var" (end) with a null, and save char in `c'
X	     */
X	    c = *(str = end), *end = 0;
X
X	    /* get the value of the variable. */
X	    if (p = do_set(set_options, var))
X		b += Strcpy(buf+b, p);
X	    else {
X		print("%s: undefined variable\n", var);
X		return 0;
X	    }
X	    expanded = 1;
X	    *str = c; /* replace the null with the old character */
X	} else
X	    b++;
X    }
X    buf[b] = 0;
X    if (expanded) /* if any expansions were done, copy back into orig buf */
X	(void) strcpy(start, buf);
X    return 1;
X}
X
X/* make an vector of space delimeted character strings out of string "str".
X * place in "argc" the number of args made. If final is true, then remove
X * quotes and backslants according to standard.
X */
char **
mk_argv(str, argc, final)
register char *str;
register int *argc;
X{
X    register char	*s, *p;
X    register int	tmp, err = 0;
X    char		*newargv[MAXARGS], **argv, *p2, c;
X
X    *argc = 0;
X    while (*str) {
X	while (isspace(*str))
X	    ++str;
X	if (*str) {		/* found beginning of a word */
X	    s = p = str;
X	    do  {
X		if (p - s >= BUFSIZ-1) {
X		    print("argument list too long.\n");
X		    return DUBL_NULL;
X		}
X		if ((*p = *str++) == '\\') {
X		    if (final && (*str == ';' || *str == '|'))
X			/* make ";" look like " ;" */
X			*p = ' ';
X		    if (*++p = *str) /* assign and compare to NULL */
X			str++;
X		    continue;
X		}
X		if (p2 = index("\"'", *p)) {
X		    register char c2 = *p2;
X		    /* you can't escape quotes inside quotes of the same type */
X		    if (!(p2 = index(str, c2))) {
X			if (final)
X			    print("Unmatched %c.\n", c2);
X			err++;
X			p2 = str;
X		    }
X		    tmp = (int)(p2 - str) + 1; /* take upto & include quote */
X		    (void) strncpy(p + !final, str, tmp);
X		    p += tmp - 2 * final; /* change final to a boolean */
X		    if (*(str = p2))
X			str++;
X		}
X	    } while (++p, *str && (!isdelimeter(*str) || str[-1] == '\\'));
X	    if (c = *str) /* set c = *str, check for null */
X		str++;
X	    *p = 0;
X	    if (*s) {
X		newargv[*argc] = savestr(s);
X		(*argc)++;
X	    }
X	    *p = c;
X	}
X    }
X    if (!*argc)
X	return DUBL_NULL;
X    /* newargv[*argc] = NULL; */
X    if (!(argv = calloc((unsigned)((*argc)+1), sizeof(char *)))) {
X	perror("mk_argv: calloc");
X	return DUBL_NULL;
X    }
X    for (tmp = 0; tmp < *argc; tmp++)
X	argv[tmp] = newargv[tmp];
X    if (err)
X	*argc = -1;
X    return argv;
X}
X
X/*
X * reference previous history from syntax of str and place result into buf
X * We know we've got a history reference -- we're passed the string starting
X * the first char AFTER the '!' (which indicates history reference)
X */
char *
reference_hist(str, buf, hist_ref)
register char *str, *buf, **hist_ref;
X{
X    int 	   relative = *str == '-'; /* relative from current hist_no */
X    int 	   old_hist, argstart = 0, lastarg, argend = 0, n = 0;
X    register char  *p, **argv = hist_ref;
X    struct history *hist;
X
X    buf[0] = 0;
X    if (index("!:$*", *str)) {
X	old_hist = hist_no;
X	if (*str == '!')
X	    str++;
X    } else if (isdigit(*(str + relative)))
X	str = my_atoi(str + relative, &old_hist);
X    else if (!(p = hist_from_str(str, &old_hist)))
X	return NULL;
X    else
X	str = p;
X    if (relative)
X	old_hist = (hist_no-old_hist) + 1;
X    if (old_hist == hist_no) {
X	if (!(argv = hist_ref))
X	    print("You haven't done anything yet!\n");
X    } else {
X	if (old_hist <= hist_no-hist_size || old_hist > hist_no ||
X	    old_hist <= 0) {
X	    if (old_hist <= 0)
X		print("You haven't done that many commands, yet.\n");
X	    else
X		print("Event %d %s.\n", old_hist,
X		    (old_hist > hist_no)? "hasn't happened yet": "expired");
X	    return NULL;
X	}
X	hist = hist_head;
X	while (hist && hist->histno != old_hist)
X	    hist = hist->prev;
X	if (hist)
X	    argv = hist->argv;
X    }
X    if (!argv)
X	return NULL;
X    while (argv[argend+1])
X	argend++;
X    lastarg = argend;
X    if (*str && index(":$*-", *str)) {
X	int isrange;
X	if (*str == ':' && isdigit(*++str))
X	    str = my_atoi(str, &argstart);
X	if (isrange = (*str == '-'))
X	    str++;
X	if (!isdigit(*str)) {
X	    if (*str == 'p')
X		str++, print_only = 1;
X	    else if (!*str || isdelimeter(*str))
X		if (isrange)
X		    argend--; /* unspecified end of range implies last-1 arg */
X		else
X		    argend = argstart; /* no range specified; use arg given */
X	    else {
X		if (*str == '*')
X		    if (argv[0])
X			argstart = 1, argend = ++lastarg;
X		    else
X			argstart = 0;
X		else if (*str == '$' && !isrange)
X		    argstart = argend;
X		else if (*str != '$')
X		    print("%c: unknown arguement selector.\n", *str);
X		str++;
X	    }
X	} else
X	    str = my_atoi(str, &argend);
X    }
X    if (argstart > lastarg || argend > lastarg || argstart > argend) {
X	print("Bad argument selector.\n");
X	return NULL;
X    }
X    while (argstart <= argend) {
X	n += Strcpy(&buf[n], argv[argstart++]);
X	buf[n++] = ' ';
X    }
X    buf[--n] = 0;
X    return str;
X}
X
X/* find a history command that contains the string "str"
X * place that history number in "hist" and return the end of the string
X * parsed: !?foo (find command with "foo" in it) !?foo?bar (same, but add "bar")
X * in the second example, return the pointer to "bar"
X */
char *
hist_from_str(str, hist_number)
register char *str;
register int *hist_number;
X{
X    register char *p = NULL, c = 0;
X    int 	  full_search = 0, len, found;
X    char 	  buf[BUFSIZ];
X    struct history *hist;
X#ifndef REGCMP
X    extern char   *re_comp();
X#else
X    extern char   *regcmp();
X#endif /* REGCMP */
X
X    if (*str == '?') {
X	if (p = index(++str, '?'))
X	    *p++ = 0;
X	else
X	    p = str + strlen(str);
X	full_search = 1;
X    } else if (*str == '{')
X	if (!(p = index(str, '}'))) {   /* { */
X	    print("Unmatched '}'");
X	    return NULL;
X	} else
X	    *p++ = 0, ++str;
X    else
X	p = str;
X    while (*p && *p != ':' && !isspace(*p))
X	p++;
X    c = *p, *p = 0;
X    if (*str) {
X#ifndef REGCMP
X	if (re_comp(str))
X#else
X	if (!regcmp(str, NULL))
X#endif /* REGCMP */
X	    return NULL;
X    } else {
X	*hist_number = hist_no;
X	return p;
X    }
X    len = strlen(str);
X    /* move thru the history in reverse searching for a string match. */
X    for (hist = hist_head; hist; hist = hist->prev) {
X	if (full_search) {
X	    (void) argv_to_string(buf, hist->argv);
X	    Debug("Checking for (%s) in (#%d: %s)\n", str, hist->histno, buf);
X	}
X	if (!full_search) {
X	    (void) strcpy(buf, hist->argv[0]);
X	    Debug("Checking for (%s) in (#%d: %*s)\n",
X		str, hist->histno, len, buf);
X	    found = !strncmp(buf, str, len);
X	} else
X	    found =
X#ifndef REGCMP
X		re_exec(buf)
X#else
X		!!regex(str, buf, NULL) /* convert to boolean value */
X#endif /* REGCMP */
X				== 1;
X	if (found) {
X	    *hist_number = hist->histno;
X	    Debug("Found it in history #%d\n", *hist_number);
X	    *p = c;
X	    return p;
X	}
X    }
X    print("%s: event not found\n", str);
X    return NULL;
X}
X
disp_hist(n, argv)  /* argc not used -- use space for the variable, "n" */
register int n;
char **argv;
X{
X    register int	list_num = TRUE, num_of_hists = hist_size;
X    register int	reverse = FALSE;
X    struct history	*hist = hist_tail;
X
X    while (*++argv && *argv[0] == '-') {
X	n = 1;
X	do  switch(argv[0][n]) {
X		case 'h': list_num = FALSE;
X		when 'r': reverse = TRUE; hist = hist_head;
X		otherwise: print("usage: history [-h] [-r] [#histories]\n");
X			   return -1;
X	    }
X	while (argv[0][++n]);
X    }
X    if (*argv)
X	if (!isdigit(**argv)) {
X	    print("history: badly formed number\n");
X	    return -1;
X	} else
X	    num_of_hists = atoi(*argv);
X
X    if (num_of_hists > hist_size || num_of_hists > hist_no)
X	num_of_hists = min(hist_size, hist_no);
X
X    if (!reverse)
X	while (hist_no - hist->histno > num_of_hists) {
X	    printf("skipping %d\n", hist->histno);
X	    hist = hist->next;
X	}
X
X    do_pager(NULL, TRUE);
X    for (n = 0; n < num_of_hists && hist; n++) {
X	char buf[256];
X	if (list_num)
X	    do_pager(sprintf(buf, "%4.d  ", hist->histno), FALSE);
X	(void) argv_to_string(buf, hist->argv);
X	(void) do_pager(buf, FALSE);
X	if (do_pager("\n", FALSE) == -1)
X	    break;
X	hist = (reverse)? hist->prev : hist->next;
X    }
X    do_pager(NULL, FALSE);
X    return -1;
X}
X
init_history(newsize)
X{
X    if ((hist_size = newsize) < 1)
X	hist_size = 1;
X}
END_OF_FILE
if test 24591 -ne `wc -c <'loop.c'`; then
    echo shar: \"'loop.c'\" unpacked with wrong size!
fi
# end of 'loop.c'
fi
if test -f 'tool_help' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'tool_help'\"
else
echo shar: Extracting \"'tool_help'\" \(23733 characters\)
sed "s/^X//" >'tool_help' <<'END_OF_FILE'
X@(#)tool_help	(c) copyright 10/18/86 (Dan Heller)
X
X%general%
X
X      IF ALL ELSE FAILS, READ THE DIRECTIONS!
X
This famous  quote applies here more  than ever. If
you are unfamiliar with this mailtool, get yourself
acquainted with it by choosing  HELP options in all
menu items. If you get frustrated or confused about
how to use or run a command, or if you want to know
how something  works or get to know quick shortcuts
in achieving tasks, it is  advisable to look at the
Help option available with the item.
X
Since there  are many different  options to some of
the commands in  mailtool, explanation  of  options
for the commands can be found by choosing the RIGHT
mouse button over an item. This will display a menu
of options for the command. One of the menu options
will almost always be a Help option.
X
Give yourself a head start, try selecting this same
item with the RIGHT mouse button.  When you do, you
will given some more help topics to choose from.
X%%
X
X%help%
Help was designed for users to get  help from anywhere on  the mailtool
window.  The RIGHT mouse button may be selected on virtually every -
X on any of the windows on the entire tool and  a menu will appear.
The last item in almost  every menu  is a "help" item.  You will get an
appropriate help message describing what you can do at the position you
are in on the mailtool window.
X
If a help  message isn't much help, it may be more helpful to reference
a different help item which  describes in more detail  what you want to
know.  For example, reading the help for  "folder" will help you better
understand  the method in  which mail messages are stored than it would
if you had read the help message for "save" first.
X%%
X
X%mouse%
The mouse is an image (cursor) which moves across the screen. Its
position indicates which window is to receive input when you type
or click a mouse button.
X
The mouse may take upon  different images which  indicate various
things. When the image looks like a "coffee cup",  Mushtool is in
the process of doing something,  like sending mail, or reading in
new mail.  In this event, you should wait till the cursor returns
to its normal state before attempting to do anything else. Go get
some coffee.
X
When the mouse looks like a pair of glasses, you are reading mail
and when it looks like a pencil, you  are editing a letter.  When
in the  Header Window, the cursor will look like the mouse device
that you hold  with the buttons  flashing on and off.  This is to
remind  you that you  can use each button  to do different tasks.
X
In one window, the Main Panel Window, the cursor image looks like
an envelope.  Placing the cursor over "Panel Items" and selecting
the LEFT button will do that command.  Selecting the RIGHT  mouse
button will give a menu of options to choose from. In most cases,
Help is  available and the end of each menu list to help you with
the proper use of Panel Items.
X
When you are asked a yes or no question, choosing either the LEFT
or RIGHT mouse buttons is the same as typing "y" or "n".
X%%
X
X%respond%
This item responds to mail in 4 ways.
In all cases, at least one recipient of your message will be the
sender of the message you are responding to. If a subject was in
the author's letter, then it will be used as your subject.
X
The first and most used method of response is to the author of the
message only.  Selecting this item with the LEFT mouse button will
use this method for responding to mail.
X
If you want to include a copy of the author's message, then choose
the menu item which  says to include the message.  If you wish for
all the recipients of the message to receive a copy of your reply,
then choosing the third item will include them.
X
The fourth menu item will mail to the author and everyone listed on
the To and Cc lines of the message, and include the message you are
responding to in your text.
X
In such cases where you include the message you are responding to,
the included message will be indented by "> " to identify it from
your message. If you would like to have a string otherby selecting  the left  mouse button on the "folder"  item and TYPING
the exact  name of the folder  you wish to access.  The "pathname" to
the folder may start with a tilde (~) indicating your home directory.
Or, it may  contain a plus sign before the  name indicating your Mail
directory (+reports, for example).  Alternatively, you  can type  '%'
to access your system Mailbox, the place where all your mail is first
delivered.  And finally, you can type '#' to indicate  the previously
accessed folder.  See the help for "chdir" for more information.
X%%
X
X%chdir%
This is used to just change working directories.
Your  working directory contains files and other
directories.  Files can be "mail folders"  which
contain mail  messages.  You can change to other
directories using some of the following methods:
X
You can select from the menu, HOME or Mail, which
are your home and default mail directories. Or,
select the left mouse button and TYPE in the name
of the directory you would like to change to.
X
Typed names may have the following syntax:
X
X~[/subpath]   will change to your home directory and
X              a path below that, if specified. Also,
X              you can specify other users: ~username
X+[subpath]    This is your default Mail directory.
X%%
X
X%save%
You may save messages in  two ways. The most commonly used method is to
save messages to  your mailbox folder  ("mbox") in your home directory.
If you use mail very frequently and save large amounts of mail, you can
save messages to other folders for better organization.
X
Usually, when messages are saved, mushtool  marks them for deletion for
the next update.  If you don't want to have saved messages deleted, you
must undelete them or set the variable "keepsave" in the options screen.
X
There is a text item  in the Main Panel Window which allows you to type
the  name of the file to save a message.  Select the  LEFT mouse button
over the "Save" item, and type the filename and hit return. If there is
no filename specified, then messages are saved to your mbox file.
X
You can specify a range, or group of messages to save by typing a range
in the  Header Panel Window.  If there is a  message list in that panel
item, then the range of messages specified there will be saved. If not,
the current message will be saved.
X
For additional information, see the help option for Folders.
X%%
X
X%quit%
There are various ways in which you may be finished with mailtool.
The most commonly used method  is to simply "close" the tool to an
iconic form. This means that you haven't really quit, but you have
merely put it on "hold" till later.  It will become an icon on the
side or corner of the screen and appear to sit and do nothing.  To
close the tool to icon form, there are two methods which have will
have two different effects.
X
The first method is to select  this panel item with the left mouse
button.  This  will update your current  folder (deleting messages
marked for deletion) and close the tool.  The second method  is to
use the tool manager around the perimeter of the window and select
X"close".  This will close the tool without updating your mailfile.
X
Whenever the mailtool is  in the "closed"  state, it  periodically
checks your mail and updates your folder with the  new mail. While
mailtool is in iconic form, it will display the number of messages
you have in the current folder.
X
There are two equally similar methods of exiting  mailtool, rather
than just closing to an icon:  you may select the second menu item
in the menu given by this panel item or you may use the tool mana-
X "quit" item.
X
Using the tool manager's quit will exit the tool without updating
your folder whereas the panel item's menu selection will have the
mailtool prompt you whether to update the current folder or not.
X%%
X
X%help_menu_help_msg%
Selecting an item within this menu will
give you help on that item. If you want
to execute the action, choose the other
menu by placing the mouse over the menu
BEHIND this menu, continue to keep your
RIGHT mouse button depressed and select
the LEFT mouse button  over the menu on
the bottom and select that action.
X%%
X
X%msg_menu%
When given menu in the Header Window, you will have a choice
of actions to take.  The message may not be the current one,
it may be  any message that  appears in  the Headers Window.
The "title" of the menu  will indicate which message you are
referring to.
X
At this point, you can select actions to take. You can Read,
Delete, Undelete, Save, Reply to, or Print messages. Most of
these are self explanatory, but if you need help with one of
these, place the  mouse over the menu BEHIND the given menu,
continue to have the RIGHT mouse button depressed and select
the LEFT mouse button over the Help Menu.
X
This action toggles the menus such  that you can change back
and forth between these menus. The menu you are on will tell
which action to take on that message.  In  either case,  you
place the mouse over the action to take, and,  if you are on
the help menu, help will be given regarding that  particular
action. If not in  the help menu,  then that specific action
will actually be taken.
X%%
X
X%edit%
Choosing this item with the LEFT mouse button in
the Main Panel Window or in the Menu item will
allow you to access a full-screen editor. The
editor which you will use is indicated when you
select the "opts" item in the Main Panel Window.
X
While you are typing a letter, you can specify
explicitly which editor to use by typing (on a
line by itself) "~v editor". Type "~?" on a line
by itself while typing to see a list of valid
X"~commands".
X
Upon exiting the editor, you can continue typing
and even reenter the editor if you like in the
same manner.
X%%
X
X%update%
This item will update the current folder you are using.
Changes are updated to the folder; that is, deleted mail
is removed and all other mail is copied back to the folder
unless otherwise specified. See the help in "folder" for
more information on folders.
X
If new mail has arrived, it will incorporate it. Otherwise,
new mail is incorporated every two minutes or so, if some
comes in.
X%%
X
X%headers%
The message headers are displayed in their own separate window.
The "current" message is usually displayed in either BOLD or
REVERSE text. This "highlighted" message is the one which is
displayed at the bottom, larger window. In the message window,
each message is displayed in the following format:
the message number is displayed first; if it is the "current"
message, then there is a '>' sign.
The next character is the 'status' character:
X    'N' -- New (and unread)
X    'U' -- not new, but still Unread
X    '*' -- delete messages (set show_deleted)
X    'P' -- preserve in spoolfile.
X    'O' -- Old message which has also been Read.
If there is just a space (no character), the message is new, but
you've already read it. You should explicitly save or delete these.
X
Following that is the Author of the message and/or all or part of
his network address and login name.  Following that is the number
of lines the message is. In quotes is all or part of the "Subject"
X(if one was specified).
X
To read a message, select either the READ item in the main panel
subwindow or move the mouse over the message header you want to read
and press the LEFT mouse button. Or, the MIDDLE mouse button will
delete that message. Choosing the RIGHT mouse button will give you
a menu of things to do then. Included in the menu, is a help item
which describes the selections in the menu.
X%%
X
X%preserve%
Usually, after you read mail and you "update" or quit  mailtool, unread
messages are copied back into your system mailbox, deleted messages are
removed, and messages which have been read but not deleted are saved in
your "mbox" file.  Specifying "hold" prevents this from ever happening,
but you can mark specific messages to be held in your system mailbox by
preserving them.
X%%
X
X%compose%
When you start to compose a letter for mailing,
you will be prompted  for the login name(s), of
whom  you want to mail, the (optional)  subject
of the message, and an  optional list of carbon
copy recipients.  This is an additional list of
login names  who will be  mailed copies of your
message.
X
After that, anything you type will be added to
your  message.  If you select the  RIGHT mouse
button in the window in  which you are type to
get a menu of  things to do.  You may enter an
editor if your message needs to be modified in
more detail.
X
When you're through with your message, you can
send it by typing (on a line by itself) "." or
X^D.  Or, you can select  the Send item  in the
Main Panel Window  and your mail will be sent.
You cannot send mail while still in an editor;
you must exit the editor first.
X
If you have the option "autoedit" set, you are
automatically put into an editor when you want
to compose or  whenever you reply to a letter.
In this case, whenever  you're through editing
the letter, you will be put back into the main
editing mode  where you terminate and send the
letter using any of the above methods.
X%%
X
X%next%
You can page through all your messages by selecting  "Next" after reading
each message. The same effect is gotten when you select the "Delete" item
when the option,  "autoprint" is set to be true  (see "opts") except that
the current message is deleted before the next one is displayed. Deleting
mail which is  not important  helps the  efficiency of mailtool and reduces
unnecessary use of system resources.
X
In the Header Window, you will notice the cursor looks like the mouse you
use.  The blinking buttons on the mouse image remind you that you can use
any of the  three buttons  at any  time.  When you  move the mouse over a
message and choose a button, the message under the  mouse is going to be
the one affected.  Choosing left button will read the message, the middle
button will delete it, and the right button will give you a menu.
X%%
X
X%aliases%
Aliases are used as a method of mailing to users with long addresses using
short names. For example, if you wanted to mail to
X    argv@spam.istc.sri.com
but didn't want to type that all the time, then you could make an alias by
selecting the alias menu item that specifies "adding alias" and then TYPE:
X    Dan argv@spam.istc.sri.com
If you want to mail to a list of people and do so frequently enough to want
an alias name for the whole list, then  you would type something like this:
X    project-group fred mary bob@foo-bar herb sly@baz.bitnet
X
To mail to an "alias" you would compose a letter and address the letter:
X
To: Dan
Subject: Alias example
Cc: project-group
X(rest of letter)
X%%
X
X%alts%
X"Alternates" are alternate names for YOU.  In messages you
receive, your account will appear on the "To" or "Cc" list.
When you REPLY to those messages, mailtool will construct
a message header for your letter which will contain the To
and Cc lists of recipients from the original message. You
would probably want your name taken off the list so you do
not mail yourself a copy of your own message. If you have
other account names or accounts on other machines, you can
let mailtool know what those mail addresses are so they can
be removed from the lists as well.
X
Note, that if YOU add your name MANUALLY (type it yourself)
to either of the lists, it will not be removed.
X
You can set such a list in your .mailrc file in your home
directory by adding the line:
X
alts hostname1 hostname2 ...
X
If you prefer to not have your name removed from lists when
responding to mail, set the option "metoo" and this prevents
the need for alternates and your name will never be removed.
X%%
X
X%opts%
To set or unset options and their values, move the mouse over
the option of your choice and select the LEFT button to toggle
true/false values. If an option requires a string value, you
must type the value, so select the LEFT button to reference
the option, and then type away.  Use a Carriage Return to enter
the final value for the option.
X
You may select the RIGHT mouse button anywhere in the window
to give a menu which consists of saving options permanently,
reading in previous settings (from ~/.mailrc), and other things.
X%%
X
X%ignore%
When readset a key for multiple commands, separate the commands with
semicolons:
X
L9:  update ; close
X
This example would update your mailbox (committing changes)
and close the tool to an icon.
X%%
X
X%message range%
You can specify a large group of messages using a combination of special
symbols in addition to numbers.  For example, if you wish to save all of
the messages, then you can use `*' to represent them all. If you were to
type the  "star" and select the Save  menu option for "save range", then
you would save ALL the messages you have (including deleted ones).
X
If you would like to save messages 4 through 9, then you would specify:
X4-9
If you want to specify the messages between 2 and 32 except for messages
X6, 8 and message 12-14, you would type:
X2-32 {6,8,12-14}
Commas or spaces can be used to separate numbers.
X
Note that you cannot specify negated messages without first specifying
normal messages; e.g. {2-5} 1-11   doesn't make sense.
X%%
X
X%sort%
Sorting messages can   be accomplished by  selecting one  of the
menu items in this panel item.  By default (using the LEFT mouse
button),  sorting is  done by  message status.  New messages are
first, followed  by unread messages,  old/read messages, replied
to  messages,  and  finally deleted messages.  You may also sort
messages by author, date, or subject by selecting the menu item.
X%%
END_OF_FILE
if test 23733 -ne `wc -c <'tool_help'`; then
    echo shar: \"'tool_help'\" unpacked with wrong size!
fi
# end of 'tool_help'
fi
echo shar: End of archive 9 \(of 14\).
cp /dev/null ark9isdone
MISSING=""
for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 14 archives.
    rm -f ark[1-9]isdone ark[1-9][0-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0
-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.