[comp.sources.misc] v18i067: mush - Mail User's Shell, Part10/22

argv@zipcode.com (Dan Heller) (04/22/91)

Submitted-by: Dan Heller <argv@zipcode.com>
Posting-number: Volume 18, Issue 67
Archive-name: mush/part10
Supersedes: mush: Volume 12, Issue 28-47

#!/bin/sh
# do not concatenate these parts, unpack them in order with /bin/sh
# file hdrs.c continued
#
if test ! -r _shar_seq_.tmp; then
	echo 'Please unpack part 1 first!'
	exit 1
fi
(read Scheck
 if test "$Scheck" != 10; then
	echo Please unpack part "$Scheck" next!
	exit 1
 else
	exit 0
 fi
) < _shar_seq_.tmp || exit 1
if test ! -f _shar_wnt_.tmp; then
	echo 'x - still skipping hdrs.c'
else
echo 'x - continuing file hdrs.c'
sed 's/^X//' << 'SHAR_EOF' >> 'hdrs.c' &&
X	    *b++ = ',', *b++ = ' ';
X	    p[lim] = '\0'; /* prevent overflow */
X	    (void) strcpy(b, p);
X	}
X    }
X    fix_up_addr(buf);
X    /* p2 used to save boolean value of $metoo */
X    if (!(p2 = do_set(set_options, "metoo"))) {
X	/* Save the original name/addr in case it is the only one */
X	(void) get_name_n_addr(buf, name, addr);
X	take_me_off(buf);
X    }
X    for (p = buf; *p == ',' || isspace(*p); p++)
X	;
X    if (!*p)
X	if (p2) /* take_me_off() was not done */
X	    (void) strcpy(buf, login);
X	else
X	    (void) sprintf(buf, "%s <%s>", name, addr);
X    return buf;
}
X
char *
subject_to(n, buf)
register char *buf;
{
X    register char *p;
X    buf[0] = 0; /* make sure it's already null terminated */
X    if (!(p = header_field(n, "subject")))
X	return NULL;
X    if (lcase_strncmp(p, "Re:", 3))
X	(void) strcpy(buf, "Re: ");
X    return strcat(buf, p);
}
X
char *
cc_to(n, buf)
register char *buf;
{
X    register char *p;
X    buf[0] = 0; /* make sure it's already null terminated */
X    if (!(p = header_field(n, "cc")))
X	return NULL;
X    fix_up_addr(p);
X    if (!do_set(set_options, "metoo"))
X	take_me_off(p);
X    return strcpy(buf, p);
}
SHAR_EOF
echo 'File hdrs.c is complete' &&
chmod 0644 hdrs.c ||
echo 'restore of hdrs.c failed'
Wc_c="`wc -c < 'hdrs.c'`"
test 22622 -eq "$Wc_c" ||
	echo 'hdrs.c: original size 22622, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= init.c ==============
if test -f 'init.c' -a X"$1" != X"-c"; then
	echo 'x - skipping init.c (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting init.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'init.c' &&
/* init.c	(c) copyright 1986 (Dan Heller) */
X
/* init.c -- functions and whatnot that initialize everything */
#include "mush.h"
#include <pwd.h>
X
#ifdef BSD
#include <netdb.h>
#endif /* BSD */
X
#if defined(SYSV) && !defined(HPUX)
#include <sys/utsname.h>
#endif /* SYSV && !HPUX */
X
void
init()
{
X    char 		*home, *realname, *argv[4];
X    extern char		*getlogin();
X    char		buf[MAXPATHLEN];
#if defined(SYSV) && !defined(HPUX)
X    extern struct passwd *getpwuid();  /* sys-v forgot this in pwd.h! */
X    struct utsname ourhost;
#else
X    char ourhost[128];
#endif /* SYSV && !HPUX */
X    register char 	*p;
X    struct passwd 	*entry;
X    int			cnt;
#if defined(BSD) || defined(HPUX)
X    struct hostent 	*hp;
#endif /* BSD || HPUX */
X
X    home = getenv("HOME");
X    if (realname = getenv("NAME")) {
X	(void) strcpy(buf, realname);
X    }
X    argv[1] = "=";
X    argv[3] = NULL;
X
X    if (!(entry = getpwuid(getuid())))
X	if (p = getlogin())
X	    strdup(login, p);
X	else {
X	    login = "unknown";
X	    print("I don't know you, but that's ok.\n");
X	}
X    else {
X	strdup(login, entry->pw_name);
X	if (!home || !*home)
X	    home = entry->pw_dir;
X	if (!realname && (realname = entry->pw_gecos)) {
X	    if (p = index(realname, ','))
X		*p = 0;
X	    for (p = buf; *realname; realname++)
X		if (*realname == '&')
X		    *p++ = upper(*login), p += Strcpy(p, login+1);
X		else
X		    *p++ = *realname;
X	    *p = 0;
X	}
X	endpwent();
X    }
X    if (!home || !*home || Access(home, W_OK)) {
X	if (home && *home)
X	    error(home);
X	else
X	    print("No home!? ");
X	print_more("Using \"%s\" as home.\n", ALTERNATE_HOME);
X    } else {
X	argv[0] = "home";
X	argv[2] = home;
X	(void) add_option(&set_options, argv);
X    }
X    if (realname && *realname) {
X	/* realname has already been copied to buf */
X	argv[0] = "realname";
X	argv[2] = buf;
X	(void) add_option(&set_options, argv);
X    }
X    crt = 24;
X    screen = 18;
X    wrapcolumn = 0; /* Default is no wrap */
X    escape = DEF_ESCAPE;
X    prompt = DEF_PROMPT;
X
#if defined(BSD) || defined(HPUX)
X    (void) gethostname(ourhost, sizeof ourhost);
X    if (!(hp = gethostbyname(ourhost))) {
X	if (ourname = (char **)calloc((unsigned)2, sizeof (char *)))
X	    strdup(ourname[0], ourhost);
X    } else {
X	int n = 0;
X	cnt = 2; /* 1 for ourhost and 1 for NULL terminator */
X	for (p = hp->h_name; p && *p; p = hp->h_aliases[n++])
X	    if (strcmp(ourhost, p)) /* if host name is different */
X		cnt++;
X	if (ourname = (char **)calloc((unsigned)cnt, sizeof (char *))) {
X	    ourname[--cnt] = NULL;
X	    for (p = hp->h_name; p && *p && n >= 0; p = hp->h_aliases[--n])
X		if (strcmp(ourhost, p)) /* if host name is different */
X		    ourname[--cnt] = savestr(p);
X	    strdup(ourname[0], ourhost); /* cnt better be 0! */
X	}
X    }
#endif /* BSD || HPUX */
#if defined(SYSV) && !defined(HPUX)
X    if (ourname = (char **)calloc((unsigned)2, sizeof (char *))) {
X	if ((uname (&ourhost) >= 0) && (*ourhost.nodename))
X	    ourname[0] = savestr(ourhost.nodename);
X	else {
X	    /* Try to use uuname -l to get host's name if uname didn't work */
X	    char buff[50];
X	    char *p;
X	    FILE *F;
X
X	    if (F = popen("exec uuname -l", "r")) {
X		if ((fgets(buff, sizeof buff, F) == buff) &&
X			(p = strchr(buff, '\n'))) {
X		    *p = '\0';		/* eliminate newline */
X		    ourname[0] = savestr (buff);
X		}
X	    (void)pclose(F);
X	    }
X	}
X    }
#endif /* SYSV && !HPUX */
X    if (ourname && ourname[0]) {
X	for (p = buf, cnt = 0; ourname[cnt]; cnt++) {
X	    if (cnt)
X		*p++ = ' ';
X	    p += Strcpy(p, ourname[cnt]);
X	}
X	argv[0] = "hostname";
X	argv[2] = buf;
X	(void) add_option(&set_options, argv);
X    }
X
X    init_bindings();
}
X
/*
X * Source a file, or just the default file.  Since sourcing files
X * means reading possible aliases, don't expand the ! as history
X * by setting the IGN_BANG flag.  Since a command in the sourced file
X * may call source on another file, this routine may be called from
X * within itself.  Continue to ignore ! chars by setting save_bang (local).
X *
X * Try opening the file passed to us.  If not given, check for the correct
X * .rc file which is found in the user's home dir.
X *
X * return -1 for filesystem errors, -2 for attempting to read a directory.
X */
source(argc, argv)
char **argv;
{
X    register char *p;
X    FILE 	 *fp;
X    char 	  file[MAXPATHLEN];
X    u_long	  save_bang = ison(glob_flags, IGN_BANG);
X    int		  line_no = 0;
X
X    if (argc && *++argv && !strcmp(*argv, "-?"))
X	return help(0, "source", cmd_help);
X    if (argc && *argv)
X	(void) strcpy(file, *argv);
X    else if ((p = getenv("MUSHRC")) && *p || (p = getenv("MAILRC")) && *p)
X	(void) strcpy(file, p);
X    else {
X	char *home = do_set(set_options, "home");
X	if (!home || !*home)
X	    home = ALTERNATE_HOME;
X	if (Access(sprintf(file, "%s/%s", home, MAILRC), R_OK)
X		&& Access(sprintf(file, "%s/%s", home, ALTERNATE_RC), R_OK))
X	    if (argc || argv)
X		(void) strcpy(file, DEFAULT_RC);
X	    else
X		return -1;
X    }
X
X    argc = 0; /* don't ignore ENOENT */
X    p = getpath(file, &argc);
X    /* Try the ALT_DEF_RC if DEFAULT_RC fails */
X    if (argc && !strcmp(file, DEFAULT_RC)) {
X	argc = 0; /* don't ignore ENOENT */
X	(void) strcpy(file, ALT_DEF_RC);
X	p = getpath(file, &argc);
X    }
X    if (argc) {
X	/* Don't print error messages for missing default files */
X	if (strcmp(file, ALT_DEF_RC))
X	    if (argc == -1) {
X		print("%s: %s\n", file, p);
X		return -1;
X	    } else {
X		print("%s is a directory.\n", file);
X		return -2;
X	    }
X	return -1;
X    }
X    if (!(fp = fopen(p, "r"))) {
X	if (errno != ENOENT)
X	    error("Can't open %s", p);
X	return -1;
X    }
X    turnon(glob_flags, IGN_BANG); /* ignore ! when reading record files */
X    (void) strcpy(file, p);
X    (void) src_parse(file, fp, 0, 0, &line_no);
X    /* if we entered the routine ignoring !, leave it that way. */
X    if (!save_bang)
X	turnoff(glob_flags, IGN_BANG);
X    /* Sourcing might change things, so abort pipes/macros */
X    return 0 - (in_pipe() || in_macro());
}
X
/*
X * Do the actual file parsing for source().  The first argument should
X * be the name of the file referenced by the second argument.  The third
X * argument is used for handling nested if_else_endif expressions.  The
X * fourth argument is used to keep track of the recursion depth, and the
X * last argument keeps track of the line number in the current file.
X *
X * This function calls itself recursively.  It also calls do_command(),
X * which may in turn call source() recursively.
X *
X * If-then-else nesting algorithm:
X *  On any "if" (whether parsing or not), increment if_else
X *  On true "if" when parsing, evaluate by recursion
X *  On false "if" when parsing, set find_else equal to if_else
X *  On any "if" when not parsing, set find_endif equal to if_else
X *  On "else", invert parsing only when find_else equals if_else
X *  When "if" was false and there is nesting, recur for "else"
X *  Skip nested "if...endif" when find_else or find_endif true
X *  On "endif" or when recursion returns, decrement if_else
X *  On "endif", test both find_endif and find_else against if_else:
X *   when either matches, reset that one;
X *   when the lesser (less nested) matches, resume parsing
X *  On "endif", when if_else hits 0, continue (depth 0) or return
X */
src_parse(file, fp, if_else, depth, line_no)
char	*file;
FILE	*fp;
int 	 if_else, depth, *line_no;
{
X    register char *p, *p2, **newargv;
X    static int    exited;
X    int 	  parsing = 1, cont_line = 0;
X    int		  find_else = 0, find_endif = 0;
X    char 	  line[BUFSIZ];
X    int		  argc;
X
X    exited = 0;
X
X    while (p = fgets(&line[cont_line], BUFSIZ - cont_line, fp)) {
X	(*line_no)++;
X	if (*(p2 = no_newln(p)) == '\\') {
X	    *p2++ = ' ';
X	    cont_line = p2 - line;
X	    continue;
X	} else
X	    cont_line = 0;
X	/* don't consider comments (#) in lines. check if # is within quotes */
X	if (p = any(line, "\"'#\\")) {
X	    register int balanced = 1;
X	    do {
X		if (*p == '\\' && p[1])
X		    p = any(p+2, "\"'#\\");
X		else if (*p != '#') {
X		    /* first find matching quote */
X		    register char *quote = index(p+1, *p);
X		    if (!quote) {
X			print("%s: line %d: unbalanced %c.\n",
X				file, *line_no, *p);
X			balanced = 0;
X		    } else
X			p = any(quote+1, "\"'#\\");
X		}
X	    } while (p && *p != '#' && balanced);
X	    if (!balanced)
X		continue;
X	    if (p && *p == '#')
X		*p = 0; /* found a Comment: null terminate line at comment */
X	}
X	if (!*line || !parsing && !(newargv = mk_argv(line, &argc, 0))
X	|| parsing && !(newargv = make_command(line, TRPL_NULL, &argc))) {
X	    if (!strncmp(line, "if", 2))
X		find_else = ++if_else, parsing = FALSE;
X	    continue;
X	}
X	if (!strcmp(newargv[0], "endif")) {
X	    if (!if_else)
X		print("%s: line %d: endif with no \"if\".\n", file, *line_no);
X	    else {
X		/* If looking for an else or endif, reset parsing */
X		if (find_endif && find_endif == if_else) {
X		    if (find_endif <= find_else || !find_else)
X			parsing = 1, find_else = 0;
X		    find_endif = 0;
X		}
X		/* Note: find_else never < find_endif */
X		if (find_else && find_else == if_else)
X		    parsing = !parsing, find_else = 0;
X		/* Decrement if_else and check depth */
X		if (--if_else == 0)
X		    /* Resume parsing if at the top */
X		    if (depth == 0)
X			parsing = 1;
X		    /* Return if not at the top */
X		    else
X			return 1;
X	    }
X	    goto bad;
X	} else if (!strcmp(newargv[0], "else")) {
X	    if (!if_else)
X		print("%s: line %d: if-less \"else\".\n", file, *line_no);
X	    /* If inside an else, ignore nested else;
X	     *  otherwise, recur when if_else > 1 */
X	    else if (!find_else && !find_endif && !parsing) {
X		parsing = src_parse(file, fp, 1, depth + 1, line_no);
X		--if_else;
X	    } else if (find_else == if_else || if_else == 1) {
X		find_else = 0;
X		parsing = !parsing;
X		if (!parsing)
X		    find_endif = if_else;
X	    }
X	    goto bad;
X	} else if (!strcmp(newargv[0], "if")) {
X	    /* if statements are of the form:
X	     *     if expr
X	     *     if !expr  or  if ! expr
X	     *     if expr == expr   or   if expr != expr
X	     */
X	    int equals = TRUE, pattern = FALSE;
X	    register char *lhs = newargv[1], *rhs = NULL;
X
X	    if_else++;
X	    /* If parsing, set parsing to 0 until
X	     *  evaluating the "if" proves otherwise.
X	     * If not parsing, skip to the "endif".
X	     */
X	    if (parsing)
X		parsing = 0;
X	    else {
X		if (!find_endif)
X		    find_endif = if_else;
X		goto bad;
X	    }
X	    if (!lhs || !*lhs) {
X		print("%s: line %d: if what?\n", file, *line_no);
X		goto bad;
X	    }
X	    /* "lhs" is the left hand side of the equation
X	     * In this instance, we're doing case 2 above (check for negation).
X	     */
X	    if (*lhs == '!') {
X		if (!*++lhs && !(lhs = newargv[2])) {
X		    print("%s: line %d: syntax error: \"if ! <what?>\"\n",
X			file, *line_no);
X		    goto bad;
X		}
X		equals = FALSE;
X	    }
X	    if (*lhs == '-' && (lhs[1] == 'e' || lhs[1] == 'z') && !lhs[2]) {
X		char *path;
X		int n = 1; /* ignore ENOENT, I'll handle it here */
X		struct stat statb;
X
X		/* check for existence or zero-length folders/files */
X		if (argc > 3 + (!equals)) {
X		    print("%s: line %d: if %s \"filename\"\n",
X			file, *line_no, lhs);
X		    goto bad;
X		}
X		path = getpath(newargv[argc-1], &n);
X		parsing = !equals ^ (n == -1 || n == 1 && lhs[1] == 'e' ||
X		    !stat(path, &statb) && (lhs[1] == 'e' || !statb.st_size));
X	    } else {
X		if (equals && argc > 2) {
X		    if (argc != 4) {
X			print("%s: %d: argument count error: %d args.\n",
X			    file, *line_no, argc);
X			goto bad;
X		    }
X		    /* now check newargv[2] for == or != or =~ or !~ */
X		    if (!strcmp(newargv[2], "!=") ||
X			    (pattern = !strcmp(newargv[2], "!~")))
X			equals = !equals;
X		    else if (!strcmp(newargv[2], "=~"))
X			pattern = TRUE;
X		    else if (strcmp(newargv[2], "==")) {
X			print("%s: %d: use `==' or `!=' only.\n",
X				file, *line_no);
X			goto bad;
X		    }
X		    rhs = newargv[3];
X		}
X		if (rhs) {
X		    /* Some fun tricks with booleans here.
X		     * Extra ! ops make sure all == are on 0 or 1;
X		     * aside from that, we want (glob == equals)
X		     * or (!strcmp == equals).  Make sense?  
X		     */
X		    if (pattern && !glob(lhs,rhs) == !equals)
X			parsing = 1;
X		    else if (!pattern && !strcmp(lhs, rhs) == !!equals)
X			parsing = 1;
X		} else if (isdigit(*lhs))
X		    parsing = !!(atoi(lhs) ? equals : !equals);
X		else if (!strcmp(lhs, "redirect") && (!isatty(0) != !equals)
X			  /* (ison(glob_flags, REDIRECT) && equals ||
X			   isoff(glob_flags, REDIRECT) && !equals) */
X		    || !strcmp(lhs, "is_shell") && (!is_shell == !equals)
X		    || !strcmp(lhs, "is_sending") &&
X			  (ison(glob_flags, IS_SENDING) && equals ||
X			   isoff(glob_flags, IS_SENDING) && !equals)
X		    || !strcmp(lhs, "hdrs_only") &&
X			  (hdrs_only && equals || !hdrs_only && !equals)
X		    || !strcmp(lhs, "istool") &&
X			  (istool && equals || !istool && !equals)
X		    || !strcmp(lhs, "iscurses") &&
X			  ((iscurses || ison(glob_flags, PRE_CURSES)) && equals
X			  || (isoff(glob_flags, PRE_CURSES) &&
X			      !iscurses && !equals)))
X			parsing = 1;
X	    }
X	    if (parsing) {
X		parsing = src_parse(file, fp, 1, depth + 1, line_no);
X		--if_else;
X	    }
X	    else
X		find_else = if_else; /* Look for a matching else */
bad:
X	    free_vec(newargv);
X	    continue;
X	}
X	if (parsing && argc > 0)
X	    if (!strcmp(newargv[0], "exit")) {
X		if_else = find_else = find_endif = 0;
X		exited = 1;
X		break;
X	    } else {
X		(void) do_command(argc, newargv, msg_list);
X		exited = 0;
X	    }
X	else
X	    free_vec(newargv);
X    }
X    if (if_else && !exited)
X	print("%s: missing endif\n", file);
X    if (depth == 0)
X	(void) fclose(fp);
X    else
X	(void) fseek(fp, 0L, 2); /* Skip ahead to the end */
X    return 0;
}
SHAR_EOF
chmod 0644 init.c ||
echo 'restore of init.c failed'
Wc_c="`wc -c < 'init.c'`"
test 13558 -eq "$Wc_c" ||
	echo 'init.c: original size 13558, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= lock.c ==============
if test -f 'lock.c' -a X"$1" != X"-c"; then
	echo 'x - skipping lock.c (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting lock.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'lock.c' &&
/*
X * lock.c -- deal with file locking on various architectures and UNIXs.
X * dot_lock() creates a file with the same name as the parameter passed
X * with the appendage ".lock" -- this is to be compatible with certain
X * systems that don't use flock or lockf or whatever they have available
X * that they don't use.
X */
X
#ifdef USG
#include <unistd.h>
#endif /* USG */
#include "mush.h"
#if defined(SYSV) && !defined(USG)
#include <sys/locking.h>
#endif /* SYSV && !USG */
X
#ifdef DOT_LOCK
extern int sgid;
#ifdef BSD
extern int rgid;
#endif /* BSD */
X
dot_lock(filename)
char *filename;
{
X    char buf[MAXPATHLEN];
X    int lockfd, cnt = 0;
X    SIGRET (*oldint)(), (*oldquit)();
X
#ifdef SYSV
X    /* Only the spoolfile needs to be dot_locked -- other files are
X     * handled by lock_fopen, below.  To avoid collisions with 14-char
X     * file name limits, we allow dot_locking ONLY of the spoolfile.
X     */
X    if (strcmp(spoolfile, filename) != 0)
X	return 0;
#endif
#ifdef BSD
X    setregid(rgid, sgid);
#else /* BSD */
X    setgid(sgid);
#endif /* BSD */
#ifdef M_XENIX
X    (void) sprintf(buf, "/tmp/%.10s.mlk", login);
#else /* M_XENIX */
X    (void) sprintf(buf, "%s.lock", filename);
#endif /* M_XENIX */
X    on_intr();
X    while ((lockfd = open(buf, O_CREAT|O_WRONLY|O_EXCL, 0444)) == -1) {
X	if (errno != EEXIST) {
X	    error("unable to lock %s", filename);
X	    break;
X	}
X	if (cnt++ == 0)
X	    print("%s already locked, waiting", filename);
X	else
X	    print_more(".");
X	sleep(1);
X	if (ison(glob_flags, WAS_INTR)) {
X	    print_more("\nAborted.\n");
X	    break;
X	}
X    }
X    off_intr();
X    if (lockfd != -1) {
X	if (cnt)
X	    print("done.\n");
X	(void) close(lockfd);
X    }
#ifdef BSD
X    setregid(sgid, rgid);
#else
X    setgid(getgid());
#endif /* BSD */
X    return lockfd == -1? -1 : 0;
}
#endif /* DOT_LOCK */
X
#ifdef SYSV
X
/*
X * Define some BSD names for the SYSV world
X */
#ifdef USG
#define LOCK_SH F_RDLCK
#define LOCK_EX F_WRLCK
#define LOCK_UN F_UNLCK
#else /* USG */
#define LOCK_SH LK_LOCK
#define LOCK_EX LK_LOCK
#define LOCK_UN LK_UNLCK
#endif /* USG */
#define LOCK_NB 0	/* Always non-blocking in this case */
X
#ifdef EWOULDBLOCK
#undef EWOULDBLOCK
#endif /* EWOULDBLOCK */
#ifdef M_UNIX
#define EWOULDBLOCK	EACCESS	/* SCO bug that may eventually be fixed */
#else /* !M_UNIX */
#define EWOULDBLOCK	EAGAIN
#endif /* M_UNIX */
X
#ifndef F_SETLKW
#define F_SETLKW F_SETLK
#endif /* F_SETLKW */
X
flock(fd, op)
int fd, op;
{
#ifndef USG
X    (void) locking(fd, op, 0); /* old xenix (sys III) */
X    return 0;
#else
X    struct flock l;
X
X    l.l_len = 0L;
X    l.l_start = 0L;
X    l.l_whence = 1;
X    l.l_type = op;
X
X    return fcntl(fd, F_SETLKW, &l);
#endif /* USG */
}
X
#endif /* SYSV */
X
static struct options *exclude_list;
X
/* Quick'n'dirty test to avoid opening the same file multiple times.
X * Fails if we aren't passed full paths or if the file is known by
X * more than one name, but you can't have everything.
X */
static FILE *
exclusive_fopen(filename, mode)
char *filename, *mode;
{
X    struct options *tmp;
X    FILE *fp;
X    
X    for (tmp = exclude_list; tmp; tmp = tmp->next)
X	if (strcmp(tmp->option, filename) == 0) {
X	    errno = EWOULDBLOCK;
X	    return NULL_FILE;
X	}
X    if (!(fp = mask_fopen(filename, mode)))
X	return NULL_FILE;
X    if (tmp = (struct options *)malloc(sizeof(struct options))) {
X	tmp->option = savestr(filename);
X	tmp->value = (char *)fp;
X	/*
X	 * NOTE: The LCKDFLDIR code below depends on this stackwise
X	 * insertion to be able to close/reopen the file pointer.
X	 * These routines therefore cannot cleanly be used outside
X	 * of lock_fopen() and close_lock(), which handle LCKDFLDIR.
X	 */
X	tmp->next = exclude_list;
X	exclude_list = tmp;
X	return fp;
X    } else
X	(void) fclose(fp);
X    return NULL_FILE;
}
X
static int
exclusive_fclose(fileptr)
FILE *fileptr;
{
X    struct options *tmp1, *tmp2;
X    int n = 0;
X    
X    for (tmp1 = tmp2 = exclude_list; tmp1; tmp2 = tmp1, tmp1 = tmp1->next)
X	if ((FILE *)(tmp1->value) == fileptr) {
X	    if (tmp1 == tmp2)
X		exclude_list = tmp1->next;
X	    else
X		tmp2->next = tmp1->next;
X	    xfree(tmp1->option);
#ifndef LCKDFLDIR
X	    /* LCKDFLDIR needs lk_fclose(), so let caller do it */
X	    n = fclose(fileptr);
#endif /* !LCKDFLDIR */
X	    xfree(tmp1);
X	    break;
X	}
X    return n;
}
X
FILE *
lock_fopen(filename, mode)
char *filename;
char *mode;
{
X    FILE *mail_fp = NULL_FILE;
X    struct options exclude;
X    int fd, lk;
X    int cnt = 0;
X    SIGRET (*oldint)(), (*oldquit)();
#ifdef LCKDFLDIR
X    extern FILE *lk_fopen();
#endif /* !LCKDFLDIR */
X
X    if (debug && do_set(set_options, "deadlock")) {
X	(void) un_set(&set_options, "deadlock");
X	return NULL_FILE;
X    }
X
#ifdef DOT_LOCK
X    if (dot_lock(filename) == 0)
#endif /* DOT_LOCK */
X    mail_fp = exclusive_fopen(filename, mode);
X    if (!mail_fp)
X	return NULL_FILE;
X    fd = fileno(mail_fp);
X
X    if (mode[0] != 'r' || mode[1] == '+')
X	lk = LOCK_EX | LOCK_NB;
X    else
X	lk = LOCK_SH | LOCK_NB;
X
X    on_intr();
#ifdef LCKDFLDIR
X    (void) fclose(mail_fp);
X    while (isoff(glob_flags, WAS_INTR))
X	if (mail_fp = lk_fopen(filename, mode, NULL, NULL, 0)) {
X	    /* See note in exclusive_fopen() above */
X	    exclude_list->value = (char *)mail_fp;
X	    break;
X	} else /* uses the open brace below the #endif LCKDFLDIR */
#else /* !LCKDFLDIR */
X    while (isoff(glob_flags, WAS_INTR) && flock(fd, lk))
#endif /* LCKDFLDIR */
X    {
#ifdef LCKDFLDIR
X	if (Access(filename, any(mode, "aw+") ? W_OK : R_OK) == 0)
#else /* !LCKDFLDIR */
X	if (errno == EWOULDBLOCK)
#endif /* LCKDFLDIR */
X	{
X	    if (isoff(glob_flags, REDIRECT))
X		if (!cnt++)
X		    print("\nwaiting to lock");
X		else
X		    print(".");
X	} else {
X	    error("Unable to lock \"%s\"", filename);
X	    exclusive_fclose(mail_fp);
X	    off_intr();
X	    return NULL_FILE;
X	}
X	(void) fflush(stdout);
X	sleep(1);
X    }
X    if (cnt)
X	print("\n");
X    cnt = (ison(glob_flags, WAS_INTR) != 0);
X    off_intr();
X    if (cnt) {
X	exclusive_fclose(mail_fp);
X	return NULL_FILE;
X    }
X    return mail_fp;
}
X
/*ARGSUSED*/
close_lock(filename, fp)
char *filename;
FILE *fp;
#ifdef LCKDFLDIR
{
X    (void) exclusive_fclose(fp); /* Only removes the list elem */
X    return lk_fclose(fp, filename, NULL, NULL);
}
#else /* !LCKDFLDIR */
{
#ifdef DOT_LOCK
X    char buf[MAXPATHLEN];
#endif /* DOT_LOCK */
X
X    fflush(fp);
#ifdef DOT_LOCK
#ifdef BSD
X    setregid(rgid, sgid);
#else
X    setgid(sgid);
#endif /* BSD */
#ifdef SYSV
X    if (strcmp(spoolfile, filename) == 0)
#endif /* SYSV */
#ifdef M_XENIX
X    (void) unlink(sprintf(buf, "/tmp/%.10s.mlk", login));
#else /* M_XENIX */
X    {
X	/* If the file was locked through open_file(), we may not have
X	 * a complete pathname to work with here.  Expand it and test
X	 * whether we need to unlink at all.  This should really be
X	 * handled by having open_file() return the name it used, but
X	 * that breaks too many other things at the moment.
X	 */
X	int isdir = 0;
X	char *p = getpath(sprintf(buf, "%s.lock", filename), &isdir);
X	if (isdir == 0)
X	    (void) unlink(p);
X    }
#endif /* M_XENIX */
#ifdef BSD
X    setregid(sgid, rgid);
#else
X    setgid(getgid());
#endif /* BSD */
#endif /* DOT_LOCK */
X
X    (void) flock(fileno(fp), LOCK_UN);
X    return exclusive_fclose(fp);
}
#endif /* LCKDFLDIR */
SHAR_EOF
chmod 0644 lock.c ||
echo 'restore of lock.c failed'
Wc_c="`wc -c < 'lock.c'`"
test 7206 -eq "$Wc_c" ||
	echo 'lock.c: original size 7206, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= loop.c ==============
if test -f 'loop.c' -a X"$1" != X"-c"; then
	echo 'x - skipping loop.c (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting loop.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'loop.c' &&
/* loop.c 	(c) copyright 1986 (Dan Heller) */
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
#include "mush.h"
#include "version.h"
X
#ifdef BSD
#include <sys/wait.h>
#else
#ifndef SYSV
#include <wait.h>
#endif /* SYSV */
#endif /* BSD */
X
#define ever (;;)
#define MAXARGS		100
#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;
};
static struct history *hist_head, *hist_tail;
#define malloc(n)	(struct history *)calloc((unsigned)1,(unsigned)(n))
#define NULL_HIST	(struct history *)0
X
static char *last_aliased;
static int hist_size, print_only;
X
do_loop()
{
X    register char *p, **argv;
X    char	  **last_argv = DUBL_NULL, line[256];
X    int   	  argc, c = (iscurses - 1);
#ifdef CURSES
X    int		  save_echo_flg = FALSE;
#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,
#ifndef SYSV
X			   sigchldcatcher
#else /* SYSV */
X			   SIG_DFL
#endif /* SYSV */
X			   );
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__);
#ifdef CURSES
X	    if (c > 0) { /* don't pass last command back to curses_command() */
X		iscurses = TRUE;
X		c = hit_return();
X	    }
#endif /* CURSES */
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 < 0)
X		c = 0;
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	}
#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, sizeof(line), 0) > -1)
X	    p = line;
X	else {
X	    if (isatty(0) && (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		putchar('\n');
X		(void) mush_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 old argv in a newly created history structure */
X	(void) add_history(0, last_argv); /* argc is currently ignored */
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
/* Add a command to the history list
X */
/*ARGSUSED*/
add_history(un_used, argv)
char **argv;
{
X    struct history *new;
X
X    if (!(new = malloc(sizeof (struct history))))
X	error("can't increment history");
X    else {
X	new->histno = ++hist_no;
X	new->argv = 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((char *) (hist_tail->prev));
X	hist_tail->prev = NULL_HIST;
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    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) > sizeof buf)
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    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
/* Return values from commands, see check_internal() */
static int last_status;			/* Changes after every command */
static char last_output[MAXMSGS];	/* Changes after SUCCESSFUL command */
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 syntax error.
X */
do_command(argc, argv, list)
char **argv, list[];
{
X    register char *p;
X    char **tmp = argv, *next_cmd = NULL;
X    int i, status = 0;
X    long do_pipe = ison(glob_flags, DO_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	/* mk_argv inserts a boolean in argv[i][2] for separators */
X	if ((!strcmp(p, "|") || !strcmp(p, ";")) && p[2]) {
X	    if (do_pipe = (*p == '|'))
X		turnon(glob_flags, DO_PIPE);
X	    else if (next_cmd = argv[i+1])
X		argv[i+1] = NULL, argc--;
X	    argv[i] = NULL;
X	    if ((status = exec_argv(i, argv, list)) <= -1)
X		mac_flush();
X	    else
X		list_to_str(list, last_output);
X	    turnon(glob_flags, IGN_SIGS); /* prevent longjmp */
X	    /* if piping, then don't call next command if this one failed. */
X	    if (status <= -1 && do_pipe) {
X		print("Broken pipe.\n");
X		do_pipe = -1, turnoff(glob_flags, DO_PIPE);
X	    }
X	    last_status = status;
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	    turnoff(glob_flags, IGN_SIGS);
X	} else
X	    i++;
X    }
X    if (*argv && do_pipe >= 0) {
X	status = exec_argv(i, argv, list);
X	turnon(glob_flags, IGN_SIGS);
X	if (status < 0) {
X	    mac_flush();
X	} else
X	    list_to_str(list, last_output);
X	last_status = status;
X    }
X    Debug("freeing: "), print_argv(tmp);
X    free_vec(tmp);
X    turnoff(glob_flags, DO_PIPE), turnoff(glob_flags, IS_PIPE);
X    if (next_cmd) {
X	if (tmp = mk_argv(next_cmd, &argc, 1)) {
X	    turnoff(glob_flags, IGN_SIGS);
X	    status = do_command(argc, tmp, list);
X	    turnon(glob_flags, IGN_SIGS);
X	} else
X	    status = argc;
X	xfree(next_cmd);
X    }
X    turnoff(glob_flags, IGN_SIGS);
X    return status;
}
X
exec_argv(argc, argv, list)
register char **argv, list[];
{
X    register int n;
X
X    if (!argv || !*argv || argv[0][0] == '\\' && !argv[0][1]) {
X	if (ison(glob_flags, IS_PIPE))
X	    print("Invalid null command.\n");
X	else if (ison(glob_flags, DO_PIPE)) {
X	    set_msg_bit(list, current_msg);
X	    return 0;
X	}
X	return -1;
X    } else if (argv[0][0] == '\\') {
X	/* Can't change *argv (breaks free_vec),
X	 *  so shift to remove the backslash
X	 */
X	for (n = 0; argv[0][n]; n++)
X	    argv[0][n] = argv[0][n+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    n = -1; /* default to failure */
X    if ((isdigit(**argv) || index("^.*$-`{}", **argv))
X			&& (n = get_msg_list(argv, list)) != 0) {
X	if (n < 0)
X	    return -1;
X	else if (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 {
X	/* get_msg_list will set the current message bit if nothing parsed */
X	if (n == 0)
X	    unset_msg_bit(list, current_msg);
X	if (strlen(*argv) == 1 && index("$^.", **argv)) {
X	    if (!msg_cnt) {
X		print("No messages.");
X		return -1;
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    }
X
X    if (!istool && do_set(set_options, "unix")) {
X	if (ison(glob_flags, IS_PIPE)) {
X	    return pipe_msg(argc, argv, list);
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
/* 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    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	int sep;
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 = (char **)calloc((unsigned)(i+1), sizeof (char *))))
X	    continue;
X	sep = n + i;
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	    b += strlen(sprintf(b, " %s ", h));
X	    while (++Argc < argc && (h = Argv[Argc]))
X		if (Argc > sep && strcmp(h, ";"))
X		    break;
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
char *
alias_expand(cmd)
register char *cmd;
{
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
static int nonobang;
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    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    nonobang = !!do_set(set_options, "nonobang");
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 >= sizeof(buf)-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 delimiter 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 (!isspace(buf[b]))
X		    first_space = -1; /* resume msg-list separation */
X		if (b && !isspace(buf[b-1]))
X		    buf[b+1] = buf[b], buf[b++] = ' ';
X		b++;
X		break;
X	    }
X	    /*
X	     * If double-quotes, just copy byte by byte, char by char,
X	     *  but do remove backslashes from in front of !s
X	     */
X	    if (!inquotes && buf[b] == '"') {
X		int B = b;
X		while ((buf[++B] = *str++) && buf[B] != '"')
X		    if (*str == '!' && buf[B] == '\\')
X			buf[B] = '!', str++;
X		if (buf[B])
X		    b = B;
X		else
X		    str--;
X		b++;
X		continue;
X	    }
X	    if (buf[b] == '\\') {
X		first_space = 1;	/* don't split escaped words */
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], *s;
X		if (!(s = reference_hist(str, word, argv))) {
X		    if (!nonobang)
X			return NULL;
X		} else {
X		    str = s;
X		    if (hist_was_referenced)
X			*hist_was_referenced = 1;
X		    if (strlen(word) + b >= sizeof buf) {
X			print("argument list too long.\n");
X			return NULL;
X		    }
X		    b += Strcpy(&buf[b], word) - 1;
X		}
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 * expand references to internal variables.  This allows such things
X * as $iscurses, $hdrs_only, etc. to work correctly.
X */
char *
check_internal(str)
register char *str;
{
X    int ret_val = -1;
X    static char version[80], get_status[4];
X
X    if (!strcmp(str, "iscurses"))
X	ret_val = (iscurses || ison(glob_flags, PRE_CURSES));
X    else if (!strcmp(str, "istool"))
X	ret_val = istool;
X    else if (!strcmp(str, "hdrs_only"))
X	ret_val = (hdrs_only && *hdrs_only);
X    else if (!strcmp(str, "is_shell"))
X	ret_val = is_shell;
X    else if (!strcmp(str, "is_sending"))
X	ret_val = (ison(glob_flags, IS_SENDING) != 0);
X    else if (!strcmp(str, "redirect"))
X	ret_val = (isatty(0) != 0);
X    else if (!strcmp(str, "thisfolder"))
X	return (mailfile && *mailfile) ? mailfile : NULL;
X    else if (!strcmp(str, "status"))
X	return sprintf(get_status, "%d", last_status);
X    else if (!strcmp(str, "output"))
X	return last_output;
X    else if (!strcmp(str, "version")) {
X	/* Create the version string ONCE, then re-use it. */
X	if (!*version)
X	    (void) sprintf(version, "%s (%d.%s.%d %s)",
X		      MUSHNAME, RELEASE, REVISION, PATCHLEVEL, RELEASE_DATE);
X	return version;
X    }
X
X    return ret_val > 0 ? "1" : ret_val == 0? "0" : NULL;
}
X
/*
X * Parse and expand a single variable reference.  Variable references
X * begin with a '$' and thereafter look like any of:
X *	$	$$ is the pid of the current process
X *	[%x]	$[%x] expands %x as a hdr_format character ($%x is same)
X *	(%x)	$(%x) expands %x as a prompt format character
X *	name	Value of variable "name" (error if not set)
X *	v:x	Modified expansion; v is any of above, x is any of
X *			h	head of a file pathname
X *			t	tail of a file pathname
X *			l	value converted to lowercase
X *			u	value converted to uppercase
X *	 		q	quote against further expansion (not yet)
X *		      <num>	select the <num>th space-separated field
X *	?name	Set/unset truth value of "name"
X *	{v}	Separate v (any of above) from surrounding text
X * A variable name may include alphabetics, numbers, or underscores but
X * must begin with an alphabetic or underscore.
X */
varexp(ref)
struct expand *ref;
{
X    char *str = ref->orig, c, *p, *var, *end = NULL, *op = NULL;
X    int do_bool, do_fmt = 0, expanded = 0;
X
X    if (*str == '$') {
X	/* Allow a $ all by itself to stand */
X	if (!*++str || isspace(*str)) {
X	    ref->exp = savestr("$");
X	    ref->rest = str;
X	    return 1;
X	}
X	/* Handle $?{name} for backwards compatibility */
X	if (do_bool = (*str == '?'))
X	    str++;
X	if (*str == '{')
X	    if (p = index(str + 1, '}')) {
X		var = str + 1;
X		end = p;
X	    } else
X		goto bad_var;
X	else
X	    var = str;
X	/* Handle $?name and ${?name} (normal cases) */
X	if (*var == '?') {
X	    if (do_bool) /* backwards compatibility clash */
X		goto bad_var;
X	    ++var, do_bool = 1;
X	}
X	switch (*var) {
X	    case '$':
X		if (str[0] == '{' && str[2] != '}')
X		    goto bad_var;
X		else {
X		    char buf[16];
X		    (void) sprintf(buf, "%d", getpid());
X		    ref->exp = savestr(buf);
X		    ref->rest = (end ? end : var) + 1;
X		    return 1;
X		}
X	    when '%':
X		for (p = var + 1; *p && !index(" \t\n;|\"'$", *p); p++)
X		    if (*p == ':') {
X			if (!do_bool && !op) {
X			    op = p;
X			    do_fmt = p - var;
X			} else
X			    break;
X		    }
X		if (!do_fmt)
X		    do_fmt = p - var;
X		end = p;
X	    when '[': case '(':  /*)*/
X		p = any(var, *var == '(' ? ") \t\n" : "] \t\n");
X		if (!p || isspace(*p))
X		    goto bad_var;
X		if (end && p > end)
X		    goto bad_var;
X		else {
X		    var++;
X		    do_fmt = p - var;
X		    if (*++p == ':')
X			op = p;
X		    else
X			end = p;
X		}
X		/* fall through */
X	    default:
X		if (!do_fmt && !isalpha(*var) && *var != '_')
X		    goto bad_var;
X		if (!end)
X		    end = var + strlen(var);
X		for (p = (op ? op : var + do_fmt) + 1; p < end; p++)
X		    if (!do_bool && !op && *p == ':') {
X			op = p;
X		    } else if (!isalnum(*p) && *p != '_') {
X			if (*str == '{') /*}*/
X			    goto bad_var;
X			end = p;
X			break;
X		    }
X		if (op && op > end)
X		    op = NULL;
X	}
X	/* replace the end of "var" (end) with a nul,
X	 * and save char in `c'.  Similarly chop at op.
X	 */
X	c = *end, *end = 0;
X	if (op)
X	    *op++ = 0;
X
X	if (!do_fmt && debug > 3)
X	    printf("expanding (%s) ", var);
X
X	/* get the value of the variable. */
X	if (do_fmt) {
X	    char c1 = var[do_fmt];
X	    var[do_fmt] = 0;
X	    if (debug > 3)
X		printf("expanding (%s) ", var);
X	    if (/*(*/ ')' == c1)
X		p = format_prompt(current_msg, var);
X	    else
X		p = format_hdr(current_msg, var, FALSE) + 9;
X	    var[do_fmt] = c1;
X	} else if (!(p = check_internal(var)))
X	    p = do_set(set_options, var);
X	if (do_bool) {
X	    ref->exp = savestr((p && (*p || !do_fmt)) ? "1" : "0");
X	    expanded = 1;
X	    if (debug > 3)
X		printf("--> (%s)\n", p);
X	} else if (p) {
X	    if (debug > 3)
X		printf("--> (%s)", p);
X	    if (op && isdigit(*op)) {
X		int varc, ix = atoi(op) - 1;
X		char **varv = mk_argv(p, &varc, FALSE);
X		/* Ignore non-fatal errors like unmatched quotes */
X		if (varv && varc < 0)
X		    for (varc = 0; varv[varc]; varc++)
X			;
X		if (ix < 0 || varc <= ix || !varv)
X		    ref->exp = savestr("");
X		else
X		    ref->exp = savestr(varv[ix]);
X		expanded = 1;
X		free_vec(varv);
X	    } else if (op) {
X		char *p2 = rindex(p, '/');
X		expanded = (*op == 'h' || *op == 't');
X		if (*op == 't' && p2)
X		    p = p2 + 1;
X		else if (*op == 'h' && p2)
X		    *p2 = 0;
X		ref->exp = savestr(p);
X		if (*op == 'h' && p2)
X		    *p2 = '/';
X		else if (*op == 'l' || *op == 'u') {
X		    expanded = 1;
X		    for (p = ref->exp; *p; p++)
X			if (*op == 'u')
X			    Upper(*p);
X			else
X			    Lower(*p);
X		}
X		if (!expanded) {
X		    print("Unknown colon modifier :%c.\n", *op);
X		    xfree(ref->exp);
X		} else
X		    if (debug > 3)
X			printf("--> (%s)\n", p);
X	    } else {
X		ref->exp = savestr(p);
X		expanded = 1;
X		if (debug > 3)
X		    printf("\n");
X	    }
X	} else {
X	    print("%s: undefined variable\n", var);
X	    expanded = 0;
X	}
X	*end = c; /* replace the null with the old character */
X	if (op)
X	    *--op = ':'; /* Put back the colon */
X	ref->rest = end + (*str == '{'); /* } */
X    }
X    return expanded;
bad_var:
X    print("Illegal variable name.\n");
X    return 0;
}
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 * We expand only as far as the first unprotected `;' separator in str,
X * to get the right behavior when multiple commands are on one line.
X * RETURN 0 on failure, 1 on success.
X */
variable_expand(str)
register char *str;
{
X    register int     b = 0, inquotes = 0;
X    char             buf[BUFSIZ], *start = str;
X    int		     expanded = 0;
X
X    while (*str && b < sizeof buf - 1) {
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++) == '\'' && !inquotes) {
X	    while ((buf[++b] = *str++) && buf[b] != '\'')
X		;
X	    if (!buf[b])
X		str--;
X	} else if (!inquotes && buf[b] == '\\' && *str) {
X	    buf[++b] = *str++;
X	    b++;
X	    continue;
X	} else if (buf[b] == '"')
X	    inquotes = !inquotes;
X	/* If $ is eol, 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] == '$' && *str) {
X	    struct expand expansion;
X	    expansion.orig = str - 1;
X	    if (varexp(&expansion)) {
X		b += Strcpy(&buf[b], expansion.exp);
X		xfree(expansion.exp);
X		str = expansion.rest;
X		expanded = 1;
X	    } else
X		return 0;
X	} else if (!inquotes && buf[b] == ';') {
X	    while (buf[++b] = *str++)
X		;
X	    b++;
X	    break;
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    if (debug > 3)
X	printf("expanded to: %s\n", start);
X    return 1;
}
X
/* make an argv of space delimited character strings out of string "str".
X * place in "argc" the number of args made.  If final is true, then expand
X * variables and file names and remove quotes and backslants according to
X * standard.
X */
char **
mk_argv(str, argc, final)
register char *str;
int *argc;
{
X    register char	*s = NULL, *p;
X    register int	tmp, err = 0, unq_sep = 0;
X    char		*newargv[MAXARGS], **argv, *p2, c, buf[BUFSIZ];
X
X    if (debug > 3)
X	(void) printf("Working on: %s\n",str);
SHAR_EOF
true || echo 'restore of loop.c failed'
fi
echo 'End of  part 10'
echo 'File loop.c is continued in part 11'
echo 11 > _shar_seq_.tmp
exit 0
exit 0 # Just in case...
-- 
Kent Landfield                   INTERNET: kent@sparky.IMD.Sterling.COM
Sterling Software, IMD           UUCP:     uunet!sparky!kent
Phone:    (402) 291-8300         FAX:      (402) 291-4362
Please send comp.sources.misc-related mail to kent@uunet.uu.net.