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

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

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

#!/bin/sh
# do not concatenate these parts, unpack them in order with /bin/sh
# file expr.c continued
#
if test ! -r _shar_seq_.tmp; then
	echo 'Please unpack part 1 first!'
	exit 1
fi
(read Scheck
 if test "$Scheck" != 8; 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 expr.c'
else
echo 'x - continuing file expr.c'
sed 's/^X//' << 'SHAR_EOF' >> 'expr.c' &&
X */
char *
eval_expr(p, new_list)
register char *p, new_list[];
{
X    register char *p2, **argv;
X    int 	  argc;
X    u_long	  save_flags = glob_flags;
X
X    if (!(p2 = index(++p, '`'))) {
X	print("unmatched backquote (`)\n");
X	return NULL;
X    }
X    *p2 = 0;
X
X    skipspaces(0);
X    if (!*p) {
X	print("Invalid null command\n");
X	return NULL;
X    }
X    turnon(glob_flags, DO_PIPE);
X    /* ignore sigs only because if user interrupts the do_command,
X     * the longjmp will corrupt the stack and the program is hosed.
X     * fix is to have layers of jmp_bufs to return to different levels.
X     */
X    turnon(glob_flags, IGN_SIGS);
X    if (*p && (argv = make_command(p, TRPL_NULL, &argc)))
X	(void) do_command(argc, argv, new_list);
X    glob_flags = save_flags;
X    *p2 = '`';
X    return p2+1;
}
SHAR_EOF
echo 'File expr.c is complete' &&
chmod 0644 expr.c ||
echo 'restore of expr.c failed'
Wc_c="`wc -c < 'expr.c'`"
test 4685 -eq "$Wc_c" ||
	echo 'expr.c: original size 4685, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= file.c ==============
if test -f 'file.c' -a X"$1" != X"-c"; then
	echo 'x - skipping file.c (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting file.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'file.c' &&
/* file.c -- Copyright (1988) Dan Heller */
X
#include "mush.h"
#include <pwd.h>
X
/* takes string 'p' and address of int (isdir).  If p uses the ~ to reference
X * a home directory of some sort, then expand it.  find out what sort of
X * file final path is. set isdir to 1 if a directory, 0 if not, -1 on error
X * return final path. If an error occurs, return string indicating error.
X * if isdir has a value of 1 when passed, it ignores "No such file or directory"
X */
char *
getpath(p, isdir)
register char *p;
int *isdir;
{
X    static char buf[MAXPATHLEN];
X    struct stat stat_buf;
X
X    if (p != buf) { /* Just in case */
X	if (!p || !*p || !strcmp(p, "~")) {
X	    char *home = do_set(set_options, "home");
X	    if (!home || !*home)
X		home = ALTERNATE_HOME;
X	    (void) strcpy(buf, home);  /* no arg means home */
X	} else if (*p == '~') {
X	    if (p[1] != '/') {
X		/* not our home, but someone else's
X		 * look for ~user or ~user/subpath
X		 * if '/' exists, separate into tmp="user" p="subpath"
X		 */
X		struct passwd *ent, *getpwnam();
X		char *p2 = p+1;
X		if (p = index(p2, '/'))
X		    *p++ = 0;
X		if (!(ent = getpwnam(p2))) {
X		    *isdir = -1;
X		    return sprintf(buf, "no such user: %s", p2);
X		}
X		/* append subpath to pathname */
X		if (p && *p)
X		    (void) sprintf(buf, "%s/%s", ent->pw_dir, p);
X		/* if *p == NULL, pathname is done (buf), set isdir = 1 */
X		else {
X		    *isdir = 1;
X		    return strcpy(buf, ent->pw_dir);
X		}
X	    } else {
X		char *home = do_set(set_options, "home");
X		if (!home || !*home)
X		    home = ALTERNATE_HOME;
X		(void) sprintf(buf, "%s/%s", home, p+2);
X	    }
X	} else if (*p == '%') {
X	    /* if %user, append user name... else, it's just us */
X	    if (!*++p || *p == ' ' || *p == '\t')
X		(void) strcpy(buf, spoolfile);
X	    else
#ifndef HOMEMAIL
X		(void) sprintf(buf, "%s/%s", MAILDIR, p);
#else /* HOMEMAIL */
X	    {
X		/* If it's NOT us, recur to get the path for ~user/MAILFILE */
X		int t_isdir = *isdir;
X		char *t, tmp[MAXPATHLEN];
X		(void) sprintf(tmp, "~%s/%s", p, MAILFILE);
X		t = getpath(tmp, &t_isdir);
X		if (t_isdir == -1) {
X		    *isdir = -1;
X		    return t;
X		}
X		/* strcpy(buf, t); --buf already has info because it's static */
X	    }
#endif /* HOMEMAIL */
X	} else if (*p == '+') {
X	    register char *p2 = do_set(set_options, "folder");
X	    if (!p2 || !*p2)
X		p2 = DEF_FOLDER;
X	    if (*++p)
X		(void) sprintf(buf, "%s/%s", p2, p);
X	    else
X		(void) strcpy(buf, p2);
X	    if (*buf != '/') {
X		int t_isdir = *isdir;
X		char *t, tmp[MAXPATHLEN];
X		if (*buf != '~')
X		    (void) sprintf(tmp, "~/%s", buf);
X		else
X		    (void) strcpy(tmp, buf);
X		t = getpath(tmp, &t_isdir);
X		if (t_isdir == -1) {
X		    *isdir = -1;
X		    return t;
X		}
X		/* strcpy(buf, t); --buf already has info because it's static */
X	    }
X	} else {  /* allow \ to escape the special chars, +, %, ~ */
X	    if (*p == '\\')
X		p++;
X	    (void) strcpy(buf, p);
X	}
X    }
X    if (stat(buf, &stat_buf)) {
X	(void) access(buf, F_OK); /* set errno to the "real" reason */
X	if (errno == ENOENT && *isdir == 1) {
X	    *isdir = 0; /* say it's a regular file even tho it doesn't exist */
X	    return buf; /* it may be wanted for creating */
X	}
X	*isdir = -1;
X	return sys_errlist[errno];
X    }
X    *isdir = ((stat_buf.st_mode & S_IFMT) == S_IFDIR);
X    return buf;
}
X
/*
X * Given a (possibly NULL or empty) string, return the name of a a valid
X * directory.  The string may contain the usual filename metachars (see
X * above).  Returns the current user's home directory if the input string
X * does not refer to a directory, the ALTERNATE_HOME if the user's home
X * directory cannot be found, or NULL if none of the above are accessible.
X *
X * NOTE:  Returns the getpath() static buffer, so the same caveats apply.
X */
char *
getdir(path)
char *path;
{
X    int isdir = 0;
X
X    /* getpath() already handles the NULL and empty cases */
X    if (!(path = getpath(path, &isdir)) || isdir != 1) {
X	isdir = 0;
X	path = getpath(ALTERNATE_HOME, &isdir);
X	if (isdir != 1)
X	    path = NULL;
X    }
X    return path;
}
X
/*
X * Given a filename[pointer] (p), a file pointer, and a mode, file_to_fp
X * opens the file with the mode.
X * If the mode is "r" then we read the file into the file pointer at the
X * end (fseek(fp, 2, 0)).  If the file is opened for writing, then read
X * from the beginning of fp and write it into the file.
X * This is usually called to read .signatures into messages (thus,
X * opening .signature with "r" and writing to the end of fp which is probably
X * the sendmail process or the message file pointer) or to write fortunes into
X * the message buffer: reading fp (the popened fortune) and writing into file.
X */
file_to_fp(p, fp, mode)
register char *p;
register FILE *fp;
char *mode;
{
X    int 	x = 1;
X    char 	*file, buf[BUFSIZ];
X    FILE 	*tmp_fp;
X
X    if (!p || !*p) {
X	print("specify filename");
X	return -1;
X    }
X    /* Special case for IS_SENDING && !IS_GETTING should eventually go away */
X    if (ison(glob_flags, IS_SENDING) && isoff(glob_flags, IS_GETTING) &&
X	    strcmp(p, "-") == 0) {
X	file = p;
X	if (*mode == 'r')
X	    tmp_fp = stdin;
X	else
X	    tmp_fp = stdout;
X    } else {
X	file = getpath(p, &x);
X	if (x == -1) { /* on error, file contains error message */
X	    wprint(file);
X	    return -1;
X	}
X	wprint("%s: ", file);
X	if (x) {
X	    /* if x == 1, then path is a directory */
X	    wprint("is a directory.\n");
X	    return -1;
X	} else if (!(tmp_fp = fopen(file, mode))) {
X	    wprint("%s\n", sys_errlist[errno]);
X	    return -1;
X	}
X    }
X    if (*mode != 'r') {
X	rewind(fp);
X	for(x = 0; fgets(buf, BUFSIZ, fp); x++)
X	    (void) fputs(buf, tmp_fp);
X    } else {
X	for(x = 0; fgets(buf, BUFSIZ, tmp_fp); x++)
X	    (void) fputs(buf, fp);
X	(void) fflush(fp);
X    }
X    wprint("%s%d line%s\n", (*mode == 'a')? "added ": "",
X				  x, (x == 1)? "": "s");
X    if (file != p || strcmp(file, "-") != 0)
X	(void) fclose(tmp_fp);
X    return 0;
}
X
/* clear all contents of the file.  Careful that the file is opened for
X * _writing_ --tempfile is opened for reading, so don't try to empty it
X * if you're using ftruncate.   Return -1 on error, 0 on success.
X */
emptyfile(fp, fname)
register FILE **fp;
register char *fname;
{
X    Debug("Emptying \"%s\"\n", fname);
#ifndef SYSV
X    return ftruncate(fileno(*fp), 0L);
#else
X    {
X	int omask = umask(077), ret;
X	(void) fclose(*fp);
X	if (!(*fp = fopen(fname, "w")))
X	    ret = -1;
X	else
X	    ret = 0;
X	(void) umask(omask);
X	return ret;
X    }
#endif /* SYSV */
}
X
/*
X * Finds out how many file descriptors are opened.  Useful for making sure
X * no files got opened in subprocedures which were not subsequently closed.
X * If argc is 0, returns the number of available fds.
X */
nopenfiles(argc)
{
#ifdef MAXFILES
X    register int size = MAXFILES;
#else
X    register int size = getdtablesize();
#endif /* MAXFILES */
X    register int nfiles = 0, totalfiles = size;
X
X    if (argc > 1)
X	return -1;
X
X    if (argc == 1)
X	wprint("open file descriptors:");
X    while (--size >= 0)
X	if (fcntl(size, F_GETFL, 0) != -1) {
X	    if (argc == 1)
X		wprint(" %d", size);
X	    ++nfiles;
X	}
X    if (argc == 1) {
X	wprint("\n");
X	return 0;
X    }
X    return totalfiles - nfiles;
}
X
/*
X * Close all "extraneous" file descriptors; return the number closed
X */
closefileds (n)
{
X    register int nfiles = 0;
#ifdef MAXFILES
X    register int size = MAXFILES;
#else
X    register int size = getdtablesize();
#endif /* MAXFILES */
X
X    while (--size >= n)
X	if (fcntl(size, F_GETFL, 0) != -1) {
X	    (void) close(size);
X	    ++nfiles;
X	}
X    return nfiles;
}
X
/*
X * Open a path for writing or appending -- return a FILE pointer.
X * If program is TRUE, then use popen, not fopen and don't check 
X * to see if the file is writable.  If program is FALSE and lockit
X * is TRUE, then lock on open.
X */
FILE *
open_file(p, program, lockit)
register char *p;
{
X    register FILE *newfile = NULL_FILE;
X    register char *tmp;
X    int x = 1;
X
X    if (program)
X	tmp = p, x = 0;
X    else
X	tmp = getpath(p, &x);
X    if (x == 1)
X	print("%s is a directory.\n", tmp);
X    else if (x == -1)
X	print("%s: %s\n", p, tmp);
X    else {
X	register char *mode = NULL;
X	/* if it doesn't exist open for "w" */
X	if (program || Access(tmp, F_OK))
X	    mode = "w";
X	/* if we can't write to it, forget it */
X	else if (Access(tmp, W_OK))
X	    error(tmp);
X	else
X	    mode = "a";
X	if (mode)
X	    if (program) {
X		if (!(newfile = popen(tmp, mode)))
X		    error("Can't execute %s\n", tmp);
X	    } else if (lockit) {
X		/* Lock on open */
X		if (!(newfile = lock_fopen(tmp, mode)))
X		    error("Can't write to %s", tmp);
X	    } else {
X		/* Ordinary open */
X		if (!(newfile = mask_fopen(tmp, mode)))
X		    error("Can't write to %s", tmp);
X	    }
X	    if (newfile != NULL_FILE)
X		Debug("Successfully opened %s\n", tmp);
X    }
X    return newfile;
}
X
/*
X * Open each file in the vector names[] and place the corresponding
X * file descriptor in files[].  If the file is really a program (pipe),
X * delete the name after opening; otherwise lock the file.
X * Tokens beginning with a "/, ~, or + are files; tokens beginning
X * with a | are programs.
X */
open_list(names, files, size)
char *names[];
FILE *files[];
{
X    register int total = 0, prog;
X    register char *fpath;
X
X    Debug("opening "), print_argv(names);
X    for (total = 0; size && total < size; ) {
X	fpath = names[total] + (prog = (names[total][0] == '|'));
X	/* open_file() locks the file here only if prog is false */
X	if ((files[total] = open_file(fpath, prog, TRUE))) {
X	    if (prog) {
X		xfree(names[total]);
X		names[total++] = NULL;
X	    } else {
X		/* Seek to end of file AFTER locking */
X		(void) fseek(files[total++], 0L, 2);
X	    }
X	} else {
X	    Debug("Failed to open %s\n", names[total]);
X	    /* Swap the failed file with the last in the list */
X	    if (size--) {
X		xfree(names[total]);
X		names[total] = names[size];
X		names[size] = NULL;
X	    }
X	}
X    }
X    return size;
}
X
/*
X * find_files gets a set of addresses and an array of
X * char pointers and the maximum size that array can be.
X * The object is to find the files or programs listed in "s".  If the
X * size is 0, then just extract the file names and give error messages
X * for each one since they will not be opened. Return the number of
X * files found and delete all files from the list in * "s".
X * The string "s" is modified to be a list of address -- all names AND
X * files are stripped out of the list.
X * The force parameter causes names to be interpreted as files even if
X * they would normally appear to be addresses.
X */
find_files(s, names, size, force)
register char *s;
char *names[];
{
X    register int     total = 0;
X    char 	     file[MAXPATHLEN], buf[HDRSIZ], *start = s, c;
X    register char    *p, *b = buf, *fpath;
X
X    do  {
X	if (!(p = get_name_n_addr(s, NULL, file)))
X	    break;
X	c = *p, *p = 0;
X	/* See if it's a file.  This doesn't get written back
X	 * onto "buf" since it is supposed to be extracted anyway.
X	 * The check for '@' in names beginning with '/' is to
X	 * avoid mis-identifying X.400 addresses as file names.
X	 */
X	if (force || *file == '+' || *file == '~' ||
X		*file == '|' || *file == '/' && !index(file, '@')) {
X	    int isdir;
X	    /* open either "file" or &file[1] */
X	    if (*file == '|') {
X		isdir = 0;
X		fpath = file;
X	    } else {
X		isdir = 1;
X		/* if successful, getpath will reset isdir to 0 */
X		fpath = getpath(file, &isdir);
X	    }
X	    if (!isdir) {
X		if (size && total < size)
X		    names[total++] = savestr(fpath);
X		else
X		    print("No open space for %s\n", file);
X	    } else if (isdir == 1)
X		print("%s: is a directory\n", file);
X	    else
X		print("%s: %s\n", file, fpath);
X	} else {
X	    b += Strcpy(b, s);
X	    *b++ = ',', *b++ = ' ';
X	}
X	for (*p = c, s = p; *s == ',' || isspace(*s); s++)
X	    ;
X    } while (*s);
X    for (*b-- = 0; b > buf && (*b == ',' || isspace(*b)); b--)
X	*b = 0;
X    (void) strcpy(start, buf);
X    names[total] = NULL; /* for free_vec() */
X    return total;
}
X
/*
X * access(2) has an undocumented feature which ignores suid.  If you are
X * su'ed and try to read your mail, you will be unable to because access()
X * will give the illusion that you cannot read/write to your mbox.  Solve
X * the problem by using stat() instead.
X */
Access(file, mode)
register char *file;
{
X    struct stat buf;
X
X    if (stat(file, &buf) == -1)
X	return -1;
X    if (mode == R_OK)
X	return (buf.st_mode & 0400)? 0 : -1;
X    if (mode == W_OK)
X	return (buf.st_mode & 0200)? 0 : -1;
X    return 0;
}
X
/*
X * Open a file for read/write/whatever but make sure umask is rw by user only.
X */
FILE *
mask_fopen(file, mode)
char *file, *mode;
{
X    int omask = umask(077);
X    FILE *fp = fopen(file, mode);
X    (void) umask(omask);
X    return fp;
}
X
/*
X * Shorten a file name, replacing its full path name with one using an
X *  accepted mush abbreviation:
X *	~	home directory
X *	+	folder directory
X *  For files in the current directory, the path is simply skipped.
X * Returns a pointer into a static buffer holding the trimmed path.
X */
char *
trim_filename(name)
char *name;
{
X    static char buf[MAXPATHLEN];
X    char *fldr = do_set(set_options, "folder"),
X	 *home = do_set(set_options, "home");
X    int len;
X
X    /* Handling $folder is tough, because if it is not set then we should
X     * trim DEF_FOLDER; but DEF_FOLDER may not be a full path, and we can't
X     * call getpath() because the "name" parameter may point to gepath()'s
X     * static buffer.  So we handle the special case of DEF_FOLDER starting
X     * with a tilde ($home), and forget about it otherwise.  Yuck.
X     */
X    if ((!fldr || !*fldr) && (fldr = DEF_FOLDER) && *fldr == '~' && home) {
X	(void) sprintf(buf, "%s%s", home, fldr + 1);
X	fldr = buf;  /* buf will get overwritten again below */
X    }
X    /* One more special case: if $folder and $home are the same, then we
X     * trim as $home, otherwise we trim as $folder.  This prevents strange
X     * contractions like "+.cshrc" for "~/.cshrc".
X     */
X    if ((!home || strcmp(home, fldr)) && (len = strlen(fldr)) &&
X	    !strncmp(fldr, name, len) && (name[len] == '/' || !name[len])) {
X	buf[0] = '+';
X	if (name[len] && name[len + 1])
X	    (void) strcpy(buf + 1, name + len + 1);
X	else
X	    buf[1] = 0;
X	return buf;
X    } else if (home && (len = strlen(home)) && !strncmp(home, name, len) &&
X	    (name[len] == '/' || !name[len])) {
X	buf[0] = '~';
X	(void) strcpy(buf + 1, name + len);
X	return buf;
X    } else if ((fldr = do_set(set_options, "cwd")) &&
X	    (len = strlen(fldr)) && !strncmp(fldr, name, len) &&
X	    name[len] == '/')
X	return strcpy(buf, name + len + 1);
X    return strcpy(buf, name);
}
SHAR_EOF
chmod 0644 file.c ||
echo 'restore of file.c failed'
Wc_c="`wc -c < 'file.c'`"
test 14503 -eq "$Wc_c" ||
	echo 'file.c: original size 14503, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= fkeys.c ==============
if test -f 'fkeys.c' -a X"$1" != X"-c"; then
	echo 'x - skipping fkeys.c (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting fkeys.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'fkeys.c' &&
/* @(#)fkeys.c		(c) copyright 10/18/86 (Dan Heller) */
X
#include "mush.h"
X
#define L(n)		KEY_LEFTFIRST+(n)-1
#define R(n)		KEY_RIGHTFIRST+(n)-1
#define F(n)		KEY_TOPFIRST+(n)-1
#define BREAK_KEY	KEY_TOPLAST
X
static int func_key();
X
Notify_value
fkey_interposer(client, event, arg, type)
Frame client;
Event *event;
Notify_arg arg;
Notify_event_type type;
{
X    if ((event_is_key_left(event) || event_is_key_right(event) ||
X	event_is_key_top(event)) &&
X	event_is_down(event) && func_key(event_id(event)))
X	    return NOTIFY_DONE;
X
X    return notify_next_event_func(client, event, arg, type);
}
X
/*
X * Execute commands defined by a function key.
X * Left keys:
X * L1 = (null)  can't be set
X * L2 ... L10
X * Top function keys
X * F1 ... F9, BREAK/backspace (key not definable)
X * Right function keys
X * R1 ... R15
X * Usually, the last Function key displays the others' settings.
X */
static int
func_key(key)
register int key;
{
X    register char **argv, *p;
X    char buf[256];
X    int n;
X
X    if (key >= KEY_LEFTFIRST && key <= KEY_LEFTLAST)
X	buf[0] = 'L', n = key - KEY_LEFTFIRST;
X    else if (key >= KEY_TOPFIRST && key <= KEY_TOPLAST)
X	buf[0] = 'F', n = key - KEY_TOPFIRST;
X    else if (key >= KEY_RIGHTFIRST && key <= KEY_RIGHTLAST)
X	buf[0] = 'R', n = key - KEY_RIGHTFIRST;
X    (void) sprintf(buf+1, "%d", n+1);
X
X    if (!(p = do_set(fkeys, buf))) {
X	if (!chk_option("quiet", "fkey"))
X	    wprint("Function key \"%s\" not set.\n", buf);
X	return FALSE;
X    }
X    /* make_command will screw up "p", so copy it first */
X    (void) strcpy(buf, p);
X    Debug("(%s) \"%s\": ", key, p), turnon(glob_flags, CONT_PRNT);
X    if (argv = make_command(buf, TRPL_NULL, &n))
X	(void) do_command(n, argv, msg_list);
X    return TRUE;
}
SHAR_EOF
chmod 0644 fkeys.c ||
echo 'restore of fkeys.c failed'
Wc_c="`wc -c < 'fkeys.c'`"
test 1717 -eq "$Wc_c" ||
	echo 'fkeys.c: original size 1717, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= folders.c ==============
if test -f 'folders.c' -a X"$1" != X"-c"; then
	echo 'x - skipping folders.c (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting folders.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'folders.c' &&
/* @(#)folders.c	(c) copyright 10/18/86 (Dan Heller) */
X
#include "mush.h"
X
static char oldfolder[MAXPATHLEN];
X
/* folder %[user]  --new mailfile is the spool/mail/login file [user].
X * folder #  --new mailfile is the folder previous to the current folder
X * folder &  --new mailfile is ~/mbox (or whatever "mbox" is set to)
X * folder +file --new mailfile is in the directory "folder"; name is 'file'
X * folder "path" --full path name or the one in current working directory.
X *
X * in all cases, changes are updated unless a '!' is specified after the
X * folder command (e.g. "f!", "folder !" "fo!" .. all permutations)
X * as usual, if new mail has arrived before the file is copied back, then
X * user will be notified beforehand.
X *
X * RETURN -1 on error -- else return 0. All bits in msg_list are set to true.
X */
folder(argc, argv, list)
register char **argv;
char list[];
{
X    int n, updating = !strcmp(*argv, "update"), do_read_only = 0, no_hdrs = 0;
X    char *tmp, *newfolder = NULL, buf[MAXPATHLEN];
X    struct stat statbuf;
X    extern long last_spool_size;
X
X    if (ison(glob_flags, IS_PIPE)) {
X	print("You can't pipe to the %s command.\n", *argv);
X	return -1;
X    } else if (ison(glob_flags, IS_SENDING)) {
X	print("You can't use the %s command when sending.\n", *argv);
X	return -1;
X    } else if (!tempfile || !*tempfile) {
X	print("You can't use the %s command in init files.\n", *argv);
X	return -1;
X    }
X    while (*++argv && (**argv == '-' || **argv == '!'))
X	if (!strcmp(*argv, "-N"))
X	    no_hdrs = !iscurses;
X	else if (!updating && !strcmp(*argv, "-n"))
X	    turnoff(glob_flags, DO_UPDATE);
X	else if (!strcmp(*argv, "-r"))
X	    do_read_only = 1;
X	else if (!strcmp(*argv, "!")) {
X	    if (updating)
X		turnon(glob_flags, DO_UPDATE);	/* useful? */
X	    else
X		turnoff(glob_flags, DO_UPDATE);
X	} else
X	    return help(0, "folder", cmd_help);
X
X    if (updating) {
X	(void) strcpy(buf, mailfile);
X	if (ison(glob_flags, READ_ONLY))
X	    do_read_only = 1;
X    } else {
X	if (!*argv) {
X	    mail_status(0);
X	    return 0;
X	}
X	if (!strcmp(*argv, "#"))
X	    if (!*oldfolder) {
X		print("No previous folder\n");
X		return -1;
X	    } else
X		newfolder = oldfolder;
X	else if (!strcmp(*argv, "&")) {
X	    if (!(newfolder = do_set(set_options, "mbox")) || !*newfolder)
X		newfolder = DEF_MBOX;
X	} else
X	    newfolder = *argv;
X	n = 0;
X	tmp = getpath(newfolder, &n);
X	if (n == -1) {
X	    print("%s: %s\n", newfolder, tmp);
X	    return -1;
X	} else if (n == 1) {
X	    print("%s: is a directory\n", tmp);
X	    return -1;
X	}
X	/* strcpy so copyback() below (which calls getpath) doesn't change
X	 * the data that tmp intended to point to.  Get the cwd if necessary.
X	 */
X	n = 0;
X	if (*tmp != '/') {
X	    if (!GetCwd(buf, sizeof buf)) {
X		error("getcwd: %s",buf);
X		return -1;
X	    }
X	    n = strlen(buf);
X	    buf[n++] = '/';
X	}
X	(void) strcpy(&buf[n], tmp);
X    }
#ifdef SUNTOOL
X    if (istool > 1)
X	timeout_cursors(TRUE);
#endif /* SUNTOOL */
X    if (stat(buf, &statbuf) == -1 || !(statbuf.st_mode & 0400)) {
X	error("Unable to read %s", buf);
#ifdef SUNTOOL
X	if (istool > 1)
X	    timeout_cursors(FALSE);
#endif /* SUNTOOL */
X	return -1;
X    }
X    /* If the file can't be opened for writing, autoset READ_ONLY */
X    if (!(statbuf.st_mode & 0200))
X	do_read_only = 1;
X
X    if (!(n=copyback(updating?"Update folder?":"Change anyway?",!updating))) {
#ifdef SUNTOOL
X	if (istool > 1)
X	    timeout_cursors(FALSE);
#endif /* SUNTOOL */
X	/* an error occured updating the folder */
X	return -1;
X    }
X    turnoff(glob_flags, CORRUPTED);	/* copyback() was successful */
X    /* Assure that both oldfolder and mailfile are full paths */
X    if (strcmp(mailfile, buf) || !*oldfolder) {
X	n = 1; /* force load of new folder */
X	if (!updating)
X	    (void) strcpy(oldfolder, *oldfolder? mailfile : buf);
X	strdup(mailfile, buf);
X    }
X    do_read_only? turnon(glob_flags,READ_ONLY) : turnoff(glob_flags,READ_ONLY);
X    last_size = spool_size = 0L;
X    while (msg_cnt--) {
X	xfree(msg[msg_cnt].m_date_recv);
X	xfree(msg[msg_cnt].m_date_sent);
X	msg[msg_cnt].m_date_recv = msg[msg_cnt].m_date_sent = NO_STRING;
X    }
X    msg_cnt = 0, msg[0].m_offset = 0L;
X    turnoff(glob_flags, CONT_PRNT);
X
X    turnon(glob_flags, IGN_SIGS);
X    /* clear the tempfile */
X    if (tmpf)
X	(void) fclose(tmpf);
X    if (!do_read_only) {
X	if (!(tmpf = mask_fopen(tempfile, "w"))) {
X	    error("error truncating %s", tempfile);
X	    turnoff(glob_flags, IGN_SIGS);
#ifdef SUNTOOL
X	    if (istool > 1)
X		timeout_cursors(FALSE);
#endif /* SUNTOOL */
X	    return -1;
X	}
X    }
X    /* Don't reload the folder if it was removed */
X    if (n > 0) {
X	if (load_folder(mailfile, TRUE, NULL) < 1) {
X	    last_msg_cnt = 0;
X	    last_size = statbuf.st_size; /* Disable check_new_mail() */
X	    turnoff(glob_flags, IGN_SIGS);
#ifdef SUNTOOL
X	    if (istool > 1)
X		timeout_cursors(FALSE);
#endif /* SUNTOOL */
X	    return -1;
X	}
X	if (do_read_only && !(tmpf = fopen(mailfile, "r"))) {
X	    error(mailfile);
X	    turnoff(glob_flags, IGN_SIGS);
#ifdef SUNTOOL
X	    if (istool > 1)
X		timeout_cursors(FALSE);
#endif /* SUNTOOL */
X	    return -1;
X	}
X    }
X    last_msg_cnt = msg_cnt;  /* for check_new_mail */
X    /* Prevent both bogus "new mail" messages and missed new mail */
X    last_size = msg[msg_cnt].m_offset;
X    if (!strcmp(mailfile, spoolfile))
X	spool_size = last_spool_size = last_size;
#ifdef SUNTOOL
X    if (istool) {
X	extern Panel_item folder_text_item;
X	Rect *rect = (Rect *)window_get(hdr_sw, WIN_RECT);
X	(void) pw_rop(hdr_win, 0,0, rect->r_width, rect->r_height, PIX_CLR,
X	    (struct pixrect *) 0,0,0);
X	panel_set_value(folder_text_item, mailfile);
X    }
#endif /* SUNTOOL */
X
X    if (!updating || current_msg >= msg_cnt)
X	current_msg = (msg_cnt? 0 : -1);
X    turnoff(glob_flags, IGN_SIGS);
X
X    /* now sort messages according a user-defined default */
X    if (!updating && msg_cnt > 1 && !strcmp(mailfile, spoolfile) &&
X		(tmp = do_set(set_options, "sort"))) {
X	(void) sprintf(buf, "sort %s", tmp);
X	if ((argv = mk_argv(buf, &argc, TRUE)) && argc > 0) {
X	    /* msg_list can't be null for do_command and since we're not
X	     * interested in the result, call sort directly
X	     */
X	    (void) sort(argc, argv, NULL);
X	    free_vec(argv);
X	    if (!updating)
X		current_msg = 0;	/* Sort may move the current message */
X	}
X    }
X    turnoff(glob_flags, DO_UPDATE);
X
X    /* go to first NEW message */
X    for (n = 0; n < msg_cnt && ison(msg[n].m_flags, OLD); n++)
X	;
X    if (n == msg_cnt) {
X	turnoff(glob_flags, NEW_MAIL);
X	if (!updating) {
X	    /* no new message found -- try first unread message */
X	    for (n = 0; n < msg_cnt && isoff(msg[n].m_flags, UNREAD); n++)
X		;
X	}
X    } else {
X	turnon(glob_flags, NEW_MAIL);
X	/* default for toolmode is true */
X	if (istool && !chk_option("quiet", "tool"))
X	    bell();
X    }
X    if (msg_cnt && (!updating || current_msg < 0))
X	current_msg = (n == msg_cnt ? 0 : n);
X
X    if ((!istool || istool && !msg_cnt) && !iscurses)
X	mail_status(0);
X    /* be quiet if we're piping */
X    if (!istool && !updating && !no_hdrs && msg_cnt
X	    && isoff(glob_flags, DO_PIPE))
X	(void) cmd_line(sprintf(buf, "headers %d", current_msg+1), msg_list);
#ifdef SUNTOOL
X    if (istool > 1) {
X	if (!msg_cnt)
X	    print("No Mail in %s\n", mailfile);
X	if (msg_cnt) {
X	    display_msg(current_msg, (long)0);
X	    do_hdrs(0, DUBL_NULL, NULL);
X	    /* Automatic display should not "touch" this message */
X	    turnoff(msg[current_msg].m_flags, DO_UPDATE);
X	    /* don't update folder just because a message is displayed */
X	    turnoff(glob_flags, DO_UPDATE);
X	}
X	timeout_cursors(FALSE);
X    }
#endif /* SUNTOOL */
X    if (list) {
X	clear_msg_list(list);
X	bitput(list, list, msg_cnt, =~); /* macro */
X    }
X    return 0;
}
X
folders(argc, argv)
register char **argv;
{
X    register char *p;
X    char buf[128], unused[MAXMSGS_BITS];
X
X    if (argv && argv[1] && !strcmp(argv[1], "-?"))
X	return help(0, "folders", cmd_help);
X
X    if (!(p = do_set(set_options, "folder")) || !*p)
X	p = DEF_FOLDER;
X    (void) sprintf(buf, "ls -FR %s", p);
X    if (argv = make_command(buf, TRPL_NULL, &argc))
X	return do_command(argc, argv, unused);
X    return -1;
}
X
/*
X * Determine whether a file could be a folder.  If prompt is non-NULL,
X * ask the user whether we should treat the file as a folder anyway.
X */
test_folder(name, prompt)
char *name, *prompt;
{
X    char line[BUFSIZ], *p;
X    FILE *fp = fopen(name, "r");
X    int retval = FALSE;
X
X    if (!fp)
X	return 0;
X    if (fgets(line, sizeof line - 1, fp)) {
#ifndef MSG_SEPARATOR
X	if (p = any(line, " \t")) {
X	    skipspaces(1);
X	    p = any(p, " \t");
X	}
X	if (p && !strncmp(line, "From ", 5) && (p = parse_date(p + 1)))
#else /* MSG_SEPARATOR */
X	if (!strncmp(line, MSG_SEPARATOR, strlen(MSG_SEPARATOR)))
#endif /* MSG_SEPARATOR */
X	    retval = TRUE;
X    } else
X	retval = TRUE;	/* Empty files are legitimate folders */
X    (void) fclose(fp);
X    if (prompt && !retval) {
X	char buf[BUFSIZ];
#ifdef SUNTOOL
X	if (istool) {
X	    (void) sprintf(buf, "\"%s\": %s", name, prompt);
X	    return ask(buf);
X	}
#endif /* SUNTOOL */
X	print("\"%s\": %s [n] ", name, prompt);
X	buf[0] = 0;
X	retval = (Getstr(buf, sizeof (buf), 0) && lower(*buf) == 'y');
X    }
X    return retval;
}
X
/* merge_folders filename  -- concatenate the folder specified by filename
X *                            to the current folder.
X *
X * RETURN -1 on error -- else return 0.  A bit in msg_list is set to true
X * for each of the "new" messages read in to the current folder.
X */
merge_folders(n, argv, list)
register char **argv, list[];
{
X    int no_hdrs = 0, newest_msg;
X    long orig_offset;
X    char *tmp, *newfolder = NULL, buf[MAXPATHLEN];
X
X    if (ison(glob_flags, IS_PIPE)) {
X	print("You can't pipe to the %s command.\n", *argv);
X	return -1;
X    } else if (ison(glob_flags, IS_SENDING)) {
X	print("You can't use the %s command while sending.\n", *argv);
X	return -1;
X    }
X
X    while (*++argv && **argv == '-')
X	if (!strcmp(*argv, "-?"))
X	    return help(0, "merge", cmd_help);
X	else if (!strcmp(*argv, "-N"))
X	    no_hdrs = !(iscurses || ison(glob_flags, PRE_CURSES));
X
X    if (!*argv)
X	return 0;
X
X    if (ison(glob_flags, READ_ONLY)) {
X	print("Folder is read-only.\n");
X	return -1;
X    }
X
X    if (!strcmp(*argv, "#"))
X	if (!*oldfolder) {
X	    print("No previous folder\n");
X	    return -1;
X	} else
X	    newfolder = oldfolder;
X    else if (!strcmp(*argv, "&")) {
X	if (!(newfolder = do_set(set_options, "mbox")) || !*newfolder)
X	    newfolder = DEF_MBOX;
X    } else
X	newfolder = *argv;
X    n = 0;
X    tmp = getpath(newfolder, &n);
X    if (n == -1) {
X	print("%s: %s\n", newfolder, tmp);
X	return -1;
X    } else if (n == 1) {
X	print("%s: is a directory\n", tmp);
X	return -1;
X    }
X
X    turnon(glob_flags, IGN_SIGS);
X    orig_offset = msg[msg_cnt].m_offset;
X    (void) load_folder(tmp, 2, list);
X    msg[msg_cnt].m_offset = orig_offset;
X    newest_msg = last_msg_cnt;
X    Debug("newest_msg = %d\n", newest_msg);
X    last_msg_cnt = msg_cnt;  /* for check_new_mail */
X    Debug("msg_cnt = %d\n", msg_cnt);
X    if (current_msg < 0)
X	current_msg = 0;
X    (void) mail_size();
X    turnoff(glob_flags, IGN_SIGS);
X
X    if ((!istool || istool && !msg_cnt)
X	    && !iscurses && !ison(glob_flags, PRE_CURSES))
X	mail_status(0);
X    /* be quiet if we're piping or if told not to show headers */
X    if ((istool || !no_hdrs) && isoff(glob_flags, DO_PIPE)
X	    && newest_msg < msg_cnt)
X	(void) cmd_line(sprintf(buf, "headers %d", newest_msg + 1), NULL);
X    return 0;
}
X
/*
X * Default digest article separator
X */
#define ARTICLE_SEP "--------"
X
/*
X * Undigestify messages.  If a message is in digest-format, there are many
X * messages within this message which are to be extracted.  Kinda like a
X * folder within a folder.  By default, this routine will create a new
X * folder that contains the new messages.  -m option will merge the new
X * messages into the current folder.
X */
do_undigest(n, argv, list)
char *argv[], list[];
{
X    int r, articles = 0, merge = 0, appending = 0;
X    char buf[MAXPATHLEN], cmdbuf[MAXPATHLEN], newlist[MAXMSGS_BITS], *dir;
X    char *art_sep = ARTICLE_SEP, *mktemp();
X    FILE *fp;
X
X    while (argv && *++argv && **argv == '-') {
X	switch(argv[0][1]) {
X	    case 'm':
X		if (ison(glob_flags, READ_ONLY)) {
X		    print("Folder is read only.\n");
X		    return -1;
X		}
X		merge++;
X	    when 'p':
X		if (*++argv)
X		    art_sep = *argv;
X		else {
X		    print("Specify separator pattern with -p.\n");
X		    return -1;
X		}
X	    otherwise: return help(0, "undigest", cmd_help);
X	}
X    }
X
X    if ((n = get_msg_list(argv, list)) == -1)
X	return -1;
X
X    argv += n;
X
X    if (*argv) {
X	int isdir = 1; /* Ignore file nonexistance errors */
X	(void) strcpy(buf, getpath(*argv, &isdir));
X	if (isdir < 0) {
X	    print("%s: %s\n", *argv, buf);
X	    return -1;
X	} else if (isdir == 1) {
X	    print("%s: is a directory\n", buf);
X	    return -1;
X	}
X    } else {
X	register char *p, *p2;
X	if (Access(dir = ".", W_OK) == 0 ||
X		(dir = do_set(set_options, "folder")) ||
X		(dir = do_set(set_options, "tmpdir")))
X	    dir = getdir(dir); /* expand metachars */
X	if (!dir)
alted:
X	    dir = ALTERNATE_HOME;
X	for (n = 0; n < msg_cnt; n++)
X	    if (msg_bit(list, n))
X		break;
X
X	if (!(p = header_field(n, "subject")))
X	    (void) mktemp(sprintf(buf, "%s/digestXXXXX", dir));
X	else {
X	    if (!lcase_strncmp(p, "re: ", 4))
X		p += 4;
X	    for (p2 = p; *p2; p2++)
X		if (!isalnum(*p2) && *p2 != '-' && *p2 != '.') {
X		    *p2 = 0;
X		    break;
X		}
X	    p2 = buf + Strcpy(buf, dir);
X	    *p2++ = '/';
X	    (void) strcpy(p2, p);
X	}
X    }
X
X    if (!Access(buf, W_OK))
X	appending = ((fp = mask_fopen(buf, "a")) != NULL_FILE);
X    else
X	fp = mask_fopen(buf, "w");
X    if (!fp) {
X	if (!*argv && strcmp(dir, ALTERNATE_HOME))
X	    goto alted;
X	error("can't create %s", buf);
X	return -1;
X    }
X
X    for (n = 0; n < msg_cnt; n++) {
X	if (!msg_bit(list, n))
X	    continue;
X
X	print("undigesting message %d\n", n+1);
X	/* copy message into file making sure all headers exist. */
X	r = undigest(n, fp, art_sep);
X	if (r <= 0)
X	    break;
X	articles += r;
X    }
X    (void) fclose(fp);
X    if (r <= 0) {
X	if (!appending)
X	    (void) unlink(buf);
X	return -1;
X    }
X    if (merge) {
X	(void) cmd_line(sprintf(cmdbuf, "\\merge -N %s", buf), newlist);
X	(void) unlink(buf);
X	print("Merged in %d messages.\n", articles);
X    } else
X	print("Added %d messages to \"%s\".\n", articles, buf);
X    clear_msg_list(list);
X    for (n = 0; n < msg_cnt; n++)
X	if (msg_bit(newlist, n))
X	    set_msg_bit(list, n);
X    return 0;
}
X
/*
X * split digest-message 'n' to file "fp" using article separator "sep".
X * return number of articles copied or -1 if system error on fputs.
X * A digest is a folder-in-a-message in a special, semi-standard form.
X */
undigest(n, fp, sep)
int n;
FILE *fp;
char *sep;
{
X    int  art_cnt = 0, on_hdr = -1; /* on_hdr is -1 if hdr not yet found */
X    int  sep_len = (sep ? strlen(sep) : strlen(sep = ARTICLE_SEP));
X    long get_hdr = 0L;
X    char from[HDRSIZ], line[HDRSIZ], last_sep[HDRSIZ];
X    char from_hdr[256], afrom[256], adate[64];
X    char *fdate = "Xxx Xxx 00 00:00:00 0000"; /* Dummy date in ctime form */
X    SIGRET (*oldint)(), (*oldquit)();
X
X    if (!msg_get(n, from, sizeof from)) {
X	error("Unable to find msg %d", n+1);
X	return -1;
X    }
#ifndef MSG_SEPARATOR
X    else {
X	char *p = from + 5;
X	skipspaces(0);
X	p = index(p, ' ');
X	if (p) {
X	    skipspaces(0);
X	    fdate = p;
X	}
X	if (fputs(from, fp) == EOF)
X	    return -1;
X    }
#endif /* !MSG_SEPARATOR */
X
X    on_intr();
X    *afrom = *adate = *last_sep = '\0';
X    while (ftell(tmpf) < msg[n].m_offset + msg[n].m_size &&
X	   fgets(line, sizeof (line), tmpf)) {
X	if (ison(glob_flags, WAS_INTR))
X	    goto handle_error;
X	if (*line == '\n' && on_hdr > 0)    /* blank line -- end of header */
X	    on_hdr = 0;
X
X	/* Check for the beginning of a digest article */
X	if (!strncmp(line, sep, sep_len)) {
X	    if (get_hdr) {
X		if (do_set(set_options, "warning"))
X		    print("Article with no header? (added to article #%d)\n",
X				art_cnt);
X		/* Don't start a new message for whatever this is,
X		 * just fseek back and keep appending to the last one.
X		 */
X		if (fseek(tmpf, get_hdr, L_SET) < 0 ||
X			fputs(last_sep, fp) == EOF) {
X		    art_cnt = -1;
X		    goto handle_error;
X		}
X		get_hdr = 0L;
X		on_hdr = 0;
X	    } else {
X		(void) strcpy(last_sep, line);
X		get_hdr = ftell(tmpf);
X		*afrom = *adate = '\0';
X		on_hdr = -1;	/* Haven't found the new header yet */
X	    }
X	    continue;
X	}
X
X	if (get_hdr) {
X	    char *p = *line == '>' ? line + 1 : line;
X	    if (*line == '\n') {
X		if (*afrom || *adate) {
X		    (void) fseek(tmpf, get_hdr, L_SET);
X		    /* Terminate the previous article */
X		    art_cnt++;
#ifdef MSG_SEPARATOR
#ifdef END_MSG_SEP
X		    if (fputs(END_MSG_SEP, fp) == EOF) {
X			art_cnt = -1;
X			goto handle_error;
X		    }
#endif /* END_MSG_SEP */
#ifdef MMDF
X		    /* MMDF has a newline in MSG_SEPARATOR */
X		    if (fputs(MSG_SEPARATOR, fp) == EOF)
#else /* !MMDF */
X		    /* Other MSG_SEPARATORs need a newline */
X		    if (fputs(MSG_SEPARATOR, fp) == EOF ||
X			    fputc('\n', fp) == EOF)
#endif /* MMDF */
#else /* !MSG_SEPARATOR */
X		    /* Everybody else needs a From_ line */
X		    if (fprintf(fp, "From %s  %s", *afrom ? afrom : "unknown",
X				*adate ? date_to_ctime(adate) : fdate) == EOF)
#endif /* MSG_SEPARATOR */
X		    {
X			art_cnt = -1;
X			goto handle_error;
X		    }
X		    /* Make sure there is a From: without a leading > */
X		    if (*afrom && *from_hdr && fputs(from_hdr, fp) == EOF) {
X			art_cnt = -1;
X			goto handle_error;
X		    }
X		    get_hdr = 0L;
X		} else if (on_hdr < 0)
X		    /* Skip blanks between "--------" and the hdr */
X		    get_hdr = ftell(tmpf);
X	    } else if (on_hdr < 0)
X		on_hdr = 1;
X	    if (on_hdr > 0 && !strncmp(p, "From: ", 6)) {
X		(void) get_name_n_addr(p + 6, NULL, afrom);
X		(void) no_newln(afrom);
X		/* Get the From: minus the leading > */
X		if (p != line)
X		    (void) strcpy(from_hdr, p);
X		else /* We don't need From: twice! */
X		    *from_hdr = '\0';
X	    } else if (on_hdr > 0 && !strncmp(line, "Date: ", 6)) {
X		if (p = parse_date(line+6))
X		    (void) strcpy(adate, p);
X	    } else if (on_hdr > 0 && !lcase_strncmp(line, "end", 3)) {
X		if (!*afrom && !*adate)
X		    break;
X	    }
X	} else if (fputs(line, fp) == EOF) {
X	    /* Pipe broken, out of file space, etc */
X	    art_cnt = -1;
X	    goto handle_error;
X	}
X    }
X    ++art_cnt;
#ifdef END_MSG_SEP
X    if (art_cnt > 0 && fputs(END_MSG_SEP, fp) == EOF) {
X	art_cnt = -1;
X	goto handle_error;
X    }
#endif /* END_MSG_SEP */
X    /* If we're still looking for a header, there is some stuff left
X     * at the end of the digest.  Create an extra article for it.
X     */
X    if (get_hdr) {
X	char *p;
X	(void) fseek(tmpf, get_hdr, L_SET);
X	if (ftell(tmpf) >= msg[n].m_offset + msg[n].m_size)
X	    goto handle_error;
#ifdef MSG_SEPARATOR
#ifdef MMDF
X	if (fputs(MSG_SEPARATOR, fp) == EOF)
#else /* !MMDF */
X	if (fputs(MSG_SEPARATOR, fp) == EOF ||
X		fputc('\n', fp) == EOF)
#endif /* MMDF */
#else /* !MSG_SEPARATOR */
X	if (fputs(from, fp) == EOF)
#endif /* MSG_SEPARATOR */
X	    art_cnt = -1;
X	if (!(p = header_field(n, "from")))
X	    p = "Mush-Undigest (Real author unknown)";
X	if (fprintf(fp, "From: %s\n", p) == EOF)
X	    art_cnt = -1;
X	if (!(p = header_field(n, "date")))
X	    p = fdate, (void) no_newln(p);
X	if (fprintf(fp, "Date: %s\n", p) == EOF)
X	    art_cnt = -1;
X	if (!(p = header_field(n, "subject")))
X	    p = "Digest";
X	if (fprintf(fp, "Subject: Trailing part of %s\n\n", p) == EOF)
X	    art_cnt = -1;
X	/* header_field() moves the pointer, so seek again */
X	(void) fseek(tmpf, get_hdr, L_SET);
X	while (art_cnt > 0 && ftell(tmpf) < msg[n].m_offset + msg[n].m_size
X		&& fgets(line, sizeof (line), tmpf)) {
X	    if (fputs(line, fp) == EOF)
X		art_cnt = -1;
#ifdef END_MSG_SEP
X	    if (!strncmp(line, END_MSG_SEP, strlen(END_MSG_SEP)))
X		break;
#endif /* END_MSG_SEP */
X	}
X	/* The END_MSG_SEP, if any, of the digest will have been output
X	 * by the while loop above, so we don't need to add one here.
X	 */
X	++art_cnt;
X    }
handle_error:
X    if (art_cnt == -1)
X	error("cannot completely undigest");
X    else if (ison(glob_flags, WAS_INTR))
X	art_cnt = -1;
X    off_intr();
X    return art_cnt;
}
SHAR_EOF
chmod 0644 folders.c ||
echo 'restore of folders.c failed'
Wc_c="`wc -c < 'folders.c'`"
test 20079 -eq "$Wc_c" ||
	echo 'folders.c: original size 20079, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= glob.c ==============
if test -f 'glob.c' -a X"$1" != X"-c"; then
	echo 'x - skipping glob.c (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting glob.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'glob.c' &&
#include "mush.h"
#include "glob.h"
X
/*
X * Buried somewhere in here is the skeleton of a pattern matcher posted
X * by koblas@mips.COM (David Koblas).  It has been hacked almost beyond
X * recognition to handle more complex patterns, and directory search has
X * been added (patterns are split at '/' characters when file globbing).
X */
X
#ifdef TEST	/* Define TEST to build a stand-alone file globbing program */
X
extern char *malloc(), *realloc();
X
#define getpath(x,y) (*(y) = 0, (x))
#define Access access
#define Strcpy(x,y) (strcpy(x,y), strlen(x))
#define savestr(x)  (strcpy(malloc(strlen(x)+1),x))
#ifndef max
#define max(x,y) ((x) > (y) ? (x) : (y))
#endif /* max */
#ifndef min
#define min(x,y) ((x) > (y) ? (y) : (x))
#endif /* min */
#define xfree free
#undef wprint
#define wprint printf
#define debug 0
#undef sprintf
X
#define TESTGLOB(str1,str2) \
X	printf("%s %s = %s\n",str1,str2,glob(str1,str2)?"TRUE":"FALSE")
X
main(argc, argv)
int argc;
char **argv;
{
X    char **e;
X    int f;
X
X    if (argc > 1)
X	while (*++argv) {
X	    (void) printf("%s -->\n", *argv);
X	    if (f = filexp(*argv, &e)) {
X		columnate(f, e, 0);
X	    }
X	}
#ifdef TEST2	/* Define TEST2 to automatically run these test cases */
X    TESTGLOB("abcdefg", "abcdefg");
X    TESTGLOB("abcdefg", "a?cd?fg");
X    TESTGLOB("abcdefg", "ab[cde]defg");
X    TESTGLOB("abcdefg", "ab[a-z]defg");
X    TESTGLOB("abcdefg", "ab[a-z]defg");
X    TESTGLOB("ab]defg", "ab[a]c]defg");
X    TESTGLOB("ab]defg", "ab[a\\]c]defg");
X    TESTGLOB("abcdefg", "ab*fg");
X    TESTGLOB("./bc/def/gh/ij", "*de*");
X    TESTGLOB("./der/den/deq/der/", "*deq*");
X    TESTGLOB("./bc/def/gh/ij", "*ij");
X    TESTGLOB("./ij", ".?ij");
X    TESTGLOB("./bc/def/gh/ij", "./*");
X    TESTGLOB("abcdef", "*def");
X    TESTGLOB("abcdef", "*abcdef");
X    TESTGLOB("abcdef", "abc*");
X    TESTGLOB("abcdef", "abcdef*");
X    TESTGLOB("abcdef", "*?*{xxx,,yy}");
X    TESTGLOB("abcdef", "abcde{f}");
X    TESTGLOB("abcdef", "abcdef{xxx,,yyy}");
X    TESTGLOB("abcdef", "abc{def,qwrx}");
X    TESTGLOB("abcdef", "abc{ab,def,qwrx}");
X    TESTGLOB("abcdef", "{naqrwer,fuwnwer,as,abc,a}{ab,def,qwrx}");
X    TESTGLOB("abcdef", "{naqrwer,*,as,abc,a}{ab,def,qwrx}");
X    TESTGLOB("abcdef", "{{a*,b*},as,a}{ab,def,qwrx}");
X    TESTGLOB("abcdef", "{{c*,b*},as,a}{ab,def,qwrx}");
X    TESTGLOB("abcdef", "{{c*,?b*},as,a}{ab,def,qwrx}");
X    TESTGLOB("abcdef", "{naqrwer,fuwnwer,as,abc,a}{ab,d*f,qwrx}");
#endif /* TEST2 */
}
X
char *
any(s1, s2)
register char *s1, *s2;
{
X    register char *p;
X    if (!s1 || !*s1 || !s2 || !*s2)
X	return 0;
X    for( ; *s1; s1++) {
X	for(p = s2; *p; p++)
X	    if (*p == *s1)
X		return s1;
X    }
X    return 0;
}
X
#endif /* TEST */
X
/*
X * Make a string into a one-element vector
X */
char **
unitv(s)
char *s;
{
X    char **v;
X
X    if (v = (char **)malloc((unsigned)(2 * sizeof(char *)))) {
X	v[0] = savestr(s);
X	v[1] = NULL;
X    }
X    return v;
}
X
/*
X * Append one vector to another
X */
catv(s1, v1, s2, v2)
int s1, s2;
char ***v1, **v2;
{
X    int i;
X
X    if (s1 < 0 || !v1)
X	return -1;
X    if (s2 < 0 || !v2)
X	return s1;
X
X    /* realloc(NULL, size) should be legal, but Sun doesn't support it. */
X    if (*v1)
X        *v1 = (char **)realloc(*v1,(unsigned)((s1+s2+1) * sizeof(char **)));
X    else
X        *v1 = (char **)malloc((unsigned)((s1+s2+1) * sizeof(char **)));
X
X    if (*v1) {
X	for (i = 0; i < s2 && v2[i]; i++)
X	    (*v1)[s1 + i] = v2[i]; 
X	(*v1)[s1 + i] = NULL;
X	xfree(v2);
X	return s1 + i;
X    }
X    return -1;
}
X
/*
X * A duplicate-eliminating comparison for sorting.  It treats an empty
X * string as greater than any other string, and forces empty one of any
X * pair of of equal strings.  Two passes are sufficient to move the empty
X * strings to the end where they can be deleted by the calling function.
X *
X * This is NOT compatible with the ANSI C qsort(), which requires that the
X * comparison function will not modify its arguments!
X */
uniqcmp(p1, p2)
char **p1, **p2;
{
X    int cmp;
X
X    if (**p1 && !**p2)
X	return -1;
X    if (**p2 && !**p1)
X	return 1;
X    if (cmp = strcmp(*p1, *p2))
X	return cmp;
X    **p2 = 0;
X    return -1;
}
X
/*
X * Expand a pattern into a list of file names.  Returns the number of
X * matches.  As in csh, names generated from pattern sets are returned
X * even if there are no actual matches.
X */
filexp(pat, exp)
char *pat, ***exp;
{
X    char **t1, **t2;
X    int n, new, cnt;
X
X    if (!exp)
X	return -1;
X    if (!pat || !*pat)
X	return 0;
X
X    if ((n = sxp(pat, &t1)) > 0)
X	cnt = 0;
X    else
X	return n;
X    *exp = DUBL_NULL;
X    while (n--)
X	if ((new = fxp(t1[n], &t2)) > 0 || new++ == 0 && t2)
X	    cnt = catv(cnt, exp, new, t2);
X    if (cnt > 1) {
X	/* Two sort passes to eliminate duplicates -- see uniqcmp() */
X	qsort((char *)*exp, cnt, sizeof(char *), uniqcmp);
X	qsort((char *)*exp, cnt, sizeof(char *), uniqcmp);
X	while (!(*exp)[cnt - 1][0]) {
X	    xfree((*exp)[--cnt]);
X	    (*exp)[cnt] = NULL;
X	}
X    }
X    return cnt;
}
X
/*
X * Expand a filename with globbing chars into a list of matching filenames.
X * Pattern set notatation which crosses directories is not handled, e.g.
X * "fi{le/exp,nger/h}and" will NOT expand to "file/expand finger/hand".
X * Such patterns must be pre-expanded by sxp() before calling fxp().
X *
X * The list of expansions is placed in *exp, and the number of matches
X * is returned, or -1 on an error.
X */
fxp(name, exp)
char *name, ***exp;
{
X    char *p;
X    int isdir;
X
X    if (!exp)
X	return -1;
X
X    isdir = 1; /* ignore no such file */
X    p = getpath(name, &isdir);
X    if (isdir < 0)
X	return -1;
X    else if (isdir)
X	return ((*exp = unitv(p)) ? 1 : -1);
X    return pglob(p, 0, exp);
}
X
/*
X * Match all globbings in a path.  Mutually recursive with dglob(), below.
X * The first "skip" characters of the path are not globbed, see dglob().
X *
X * Returns the number of matches, or -1 on an error.  *exp is set to the
X * list of matches.
X *
X * If the path has no metachars, it is returned in *exp whether it matches
X * a real file or not.  This allows patterns built by sxp() to be recognized
X * and returned even when there are no matches (ala csh generation of names
X * from pattern sets).  pglob() still returns zero in this case.
X */
pglob(path, skip, exp)
char *path, ***exp;
int skip;
{
X    char *t, *t2;
X    int ret = 0;
X
X    if (!path || !exp || skip < 0)
X	return -1;
X    *exp = DUBL_NULL; /* Must be null in case of zero matches and no sets */
X
X    for (t = t2 = path + skip; (t2 = any(t2, META)) && *t2 == '/'; t = t2++)
X	;
X    if (!t2) {
X	ret = ((*exp = unitv(path)) ? 1 : -1);
X	if (ret > 0 && Access(path, F_OK) < 0)
X	    ret = 0;
X    } else {
X	if (t2 = index(t + 1, '/'))
X	    *t2++ = 0;
X	if (*t == '/') {
X	    *t++ = 0;
X	    if (!*path)
X		ret = dglob("/", t, t2, exp);
X	    else
X		ret = dglob(path, t, t2, exp);
X	} else {
X	    ret = dglob("", t, t2, exp);
X	}
X    }
X    return ret;
}
X
/*
X * Search a directory (possibly recursively) for glob matches.
X * Argument pat1 is a pattern to be matched in this directory,
X * and pat2 is a pattern to be matched in matched subdirectories.
X *
X * Matches are returned through *exp.
X */
dglob(dir, pat1, pat2, exp)
char *dir, *pat1, *pat2, ***exp;
{
X    DIR *dirp;
X    struct dirent *dp;
X    char *b, *d, buf[MAXPATHLEN], **tmp;
X    int n, ret = 0, skip;
X
X    if (!dir || !exp)
X	return -1;
X    d = (*dir ? dir : ".");
X    if (!(dirp = opendir(d)))
X	return -1;
X    b = buf + Strcpy(buf, dir);
X    if (b > buf && *(b - 1) != '/')
X	*b++ = '/';
X    skip = b - buf; /* We know this much matches, don't glob it again */
X    while (ret >= 0 && (dp = readdir(dirp))) {
X	if (fglob(dp->d_name, pat1)) {
X	    if (pat2) {
X		(void) sprintf(b, "%s/%s", dp->d_name, pat2);
X		n = pglob(buf, skip, &tmp);
X		ret = catv(ret, exp, n, tmp);
X	    } else {
X		(void) strcpy(b, dp->d_name);
X		ret = catv(ret, exp, 1, unitv(buf));
X	    }
X	}
X    }
X    closedir(dirp);
X    return ret;
}
X
/*
X * Match file names.  This means that metachars do not match leading ".".
X */
fglob(str, pat)
char *str, *pat;
{
X    if (!str || !pat || *str == '.' && *pat != '.')
X	return FALSE;
X    else
X	return glob(str, pat);
}
X
/*
X * Match two concatenated patterns.  Mainly for use by sglob().
X */
static
glob2(str, pat1, pat2)
char *str, *pat1, *pat2;
{
X    char buf[MAXPATHLEN];
X
X    if (!str || !pat1 && !pat2)
X	return FALSE;
X    (void) sprintf(buf, "%s%s", pat1? pat1 : "", pat2? pat2 : "");
X    return glob(str, buf);
}
X
/*
X * The basic globbing matcher.
X *
X * "*"           = match 0 or more occurances of anything
X * "[abc]"       = match any of "abc" (ranges supported)
X * "{xx,yy,...}" = match any of "xx", "yy", ... where
X *                 "xx", "yy" can be any pattern or empty
X * "?"           = match any character
X */
glob(str, pat)
char *str, *pat;
{
X    int done = FALSE, ret = FALSE;
X
X    if (!str || !pat)
X	return FALSE;
X
X    while (*pat && !done && (*str || (*pat == '{' || *pat == '*'))) /*}*/ {
X	/*
X	 * First look for a literal match, stepping over backslashes
X	 * in the pattern to match against the "protected" character.
X	 * Ordering and precendence are important in this expression!
X	 */
X	if (*pat == '\\' && *str == *++pat || *str == *pat) {
X	    str++;
X	    pat++;
X	} else switch (*pat++) {
X	    case '*':	/* Match any string */
X		if (!*pat) {
X		    while (*str)
X			str++;
X		    break;
X		}
X		/*
X		 * Try the rest of the glob against every
X		 * possible suffix of the string.  A bit
X		 * inefficient in cases that eventually fail.
X		 */
X		while (*str && !(ret = glob(str++, pat)))
X		    ;
X		return ret;
X		break;
X	    case '[':	/* Match a set */
X	    repeat:
X		/* If we've hit the end of the set, give up. */
SHAR_EOF
true || echo 'restore of glob.c failed'
fi
echo 'End of  part 8'
echo 'File glob.c is continued in part 9'
echo 9 > _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.