
/*
 * mshcmds.c -- command handlers in msh
 *
 * This code is Copyright (c) 2002, by the authors of nmh.  See the
 * COPYRIGHT file in the root directory of the nmh distribution for
 * complete copyright information.
 */

#include <h/mh.h>
#include <h/signals.h>
#include <h/dropsbr.h>
#include <h/fmt_scan.h>
#include <h/scansbr.h>
#include <h/tws.h>
#include <h/mts.h>
#include <h/msh.h>
#include <h/picksbr.h>
#include <h/utils.h>


static char delim3[] = "-------";	/* from burst.c */

static int mhlnum;
static FILE *mhlfp;

/*
 * Type for a compare function for qsort.  This keeps
 * the compiler happy.
 */
typedef int (*qsort_comp) (const void *, const void *);

/*
 * static prototypes
 */
static int burst (struct Msg *, int, int, int, int);
static void forw (char *, char *, int, char **);
static void rmm (void);
static void show (int);
static int eom_action (int);
static FILE *mhl_action (char *);
static int ask (int);
static int is_nontext (int);
static int get_fields (char *, char *, int, struct Msg *);
static int msgsort (struct Msg *, struct Msg *);
static int subsort (struct Msg *, struct Msg *);
static char *sosmash (char *, char *);
static int process (int, char *, int, char **);
static void copy_message (int, FILE *);
static void copy_digest (int, FILE *);

extern m_getfld_state_t gstate;	/* use the gstate in scansbr.c */

void
forkcmd (char **args, char *pgm)
{
    int child_id;
    char *vec[MAXARGS];

    vec[0] = r1bindex (pgm, '/');
    copyip (args, vec + 1, MAXARGS - 1);

    if (fmsh) {
	context_del (pfolder);
	context_replace (pfolder, fmsh);/* update current folder   */
	seq_save (mp);
	context_save ();		/* save the context file   */
    }
    fflush (stdout);
    switch (child_id = fork ()) {
	case NOTOK: 
	    advise ("fork", "unable to");
	    return;

	case OK: 
	    closefds (3);
	    SIGNAL (SIGINT, istat);
	    SIGNAL (SIGQUIT, qstat);

	    execvp (pgm, vec);
	    fprintf (stderr, "unable to exec ");
	    perror (cmd_name);
	    _exit (1);

	default: 
	    pidXwait (child_id, NULL);
	    break;
    }
    if (fmsh) {			/* assume the worst case */
	mp->msgflags |= MODIFIED;
	modified++;
    }
}


#define DIST_SWITCHES \
    X("annotate", 0, DIANSW) \
    X("noannotate", 0, DINANSW) \
    X("draftfolder +folder", 0, DIDFSW) \
    X("draftmessage msg", 0, DIDMSW) \
    X("nodraftfolder", 0, DINDFSW) \
    X("editor editor", 0, DIEDTSW) \
    X("noedit", 0, DINEDSW) \
    X("form formfile", 0, DIFRMSW) \
    X("inplace", 0, DIINSW) \
    X("noinplace", 0, DININSW) \
    X("whatnowproc program", 0, DIWHTSW) \
    X("nowhatnowproc", 0, DINWTSW) \
    X("help", 0, DIHELP) \

#define X(sw, minchars, id) id,
DEFINE_SWITCH_ENUM(DIST);
#undef X

#define X(sw, minchars, id) { sw, minchars, id },
DEFINE_SWITCH_ARRAY(DIST, distswit);
#undef X


void
distcmd (char **args)
{
    int vecp = 1;
    char *cp, *msg = NULL;
    char buf[BUFSIZ], *vec[MAXARGS];

    if (fmsh) {
	forkcmd (args, cmd_name);
	return;
    }

    while ((cp = *args++)) {
	if (*cp == '-')
	    switch (smatch (++cp, distswit)) {
		case AMBIGSW: 
		    ambigsw (cp, distswit);
		    return;
		case UNKWNSW: 
		    fprintf (stderr, "-%s unknown\n", cp);
		    return;
		case DIHELP: 
		    snprintf (buf, sizeof(buf), "%s [msgs] [switches]", cmd_name);
		    print_help (buf, distswit, 1);
		    return;

		case DIANSW:	/* not implemented */
		case DINANSW: 
		case DIINSW: 
		case DININSW: 
		    continue;

		case DINDFSW:
		case DINEDSW:
		case DINWTSW:
		    vec[vecp++] = --cp;
		    continue;

		case DIEDTSW: 
		case DIFRMSW: 
		case DIDFSW:
		case DIDMSW:
		case DIWHTSW:
		    vec[vecp++] = --cp;
		    if (!(cp = *args++) || *cp == '-') {
			advise (NULL, "missing argument to %s", args[-2]);
			return;
		    }
		    vec[vecp++] = cp;
		    continue;
	    }
	if (*cp == '+' || *cp == '@') {
	    advise (NULL, "sorry, no folders allowed!");
	    return;
	}
	else
	    if (msg) {
		advise (NULL, "only one message at a time!");
		return;
	    }
	    else
		msg = cp;
    }

    vec[0] = cmd_name;
    vec[vecp++] = "-file";
    vec[vecp] = NULL;
    if (!msg)
	msg = "cur";
    if (!m_convert (mp, msg))
	return;
    seq_setprev (mp);

    if (mp->numsel > 1) {
	advise (NULL, "only one message at a time!");
	return;
    }
    process (mp->hghsel, cmd_name, vecp, vec);
    seq_setcur (mp, mp->hghsel);
}


#define EXPLODE_SWITCHES \
    X("inplace", 0, EXINSW) \
    X("noinplace", 0, EXNINSW) \
    X("quiet", 0, EXQISW) \
    X("noquiet", 0, EXNQISW) \
    X("verbose", 0, EXVBSW) \
    X("noverbose", 0, EXNVBSW) \
    X("help", 0, EXHELP) \

#define X(sw, minchars, id) id,
DEFINE_SWITCH_ENUM(EXPLODE);
#undef X

#define X(sw, minchars, id) { sw, minchars, id },
DEFINE_SWITCH_ARRAY(EXPLODE, explswit);
#undef X


void
explcmd (char **args)
{
    int inplace = 0, quietsw = 0, verbosw = 0;
    int msgp = 0, hi, msgnum;
    char *cp, buf[BUFSIZ], *msgs[MAXARGS];
    struct Msg *smsgs;

    if (fmsh) {
	forkcmd (args, cmd_name);
	return;
    }

    while ((cp = *args++)) {
	if (*cp == '-')
	    switch (smatch (++cp, explswit)) {
		case AMBIGSW: 
		    ambigsw (cp, explswit);
		    return;
		case UNKWNSW: 
		    fprintf (stderr, "-%s unknown\n", cp);
		    return;
		case EXHELP: 
		    snprintf (buf, sizeof(buf), "%s [msgs] [switches]", cmd_name);
		    print_help (buf, explswit, 1);
		    return;

		case EXINSW: 
		    inplace++;
		    continue;
		case EXNINSW: 
		    inplace = 0;
		    continue;
		case EXQISW: 
		    quietsw++;
		    continue;
		case EXNQISW: 
		    quietsw = 0;
		    continue;
		case EXVBSW: 
		    verbosw++;
		    continue;
		case EXNVBSW: 
		    verbosw = 0;
		    continue;
	    }
	if (*cp == '+' || *cp == '@') {
	    advise (NULL, "sorry, no folders allowed!");
	    return;
	}
	else
	    msgs[msgp++] = cp;
    }

    if (!msgp)
	msgs[msgp++] = "cur";
    for (msgnum = 0; msgnum < msgp; msgnum++)
	if (!m_convert (mp, msgs[msgnum]))
	    return;
    seq_setprev (mp);

    smsgs = (struct Msg *)
		calloc ((size_t) (MAXFOLDER + 2), sizeof *smsgs);
    if (smsgs == NULL)
	adios (NULL, "unable to allocate folder storage");

    hi = mp->hghmsg + 1;
    interrupted = 0;
    for (msgnum = mp->lowsel;
	    msgnum <= mp->hghsel && !interrupted;
	    msgnum++)
	if (is_selected (mp, msgnum))
	    if (burst (smsgs, msgnum, inplace, quietsw, verbosw) != OK)
		break;

    free ((char *) smsgs);

    if (inplace)
	seq_setcur (mp, mp->lowsel);
    else
	if (hi <= mp->hghmsg)
	    seq_setcur (mp, hi);

    mp->msgflags |= MODIFIED;
    modified++;
}


static int
burst (struct Msg *smsgs, int msgnum, int inplace, int quietsw, int verbosw)
{
    int i, j, ld3, wasdlm, msgp;
    long pos;
    char c, buffer[BUFSIZ];
    register FILE *zp;

    ld3 = strlen (delim3);

    if (Msgs[msgnum].m_scanl) {
	free (Msgs[msgnum].m_scanl);
	Msgs[msgnum].m_scanl = NULL;
    }

    pos = ftell (zp = msh_ready (msgnum, 1));
    for (msgp = 0; msgp <= MAXFOLDER;) {
	while (fgets (buffer, sizeof buffer, zp) != NULL
		&& buffer[0] == '\n'
		&& pos < Msgs[msgnum].m_stop)
	    pos += (long) strlen (buffer);
	if (feof (zp) || pos >= Msgs[msgnum].m_stop)
	    break;
	fseek (zp, pos, SEEK_SET);
	smsgs[msgp].m_start = pos;

	for (c = 0;
		pos < Msgs[msgnum].m_stop
		&& fgets (buffer, sizeof buffer, zp) != NULL;
		c = buffer[0])
	    if (strncmp (buffer, delim3, ld3) == 0
		    && (msgp == 1 || c == '\n')
		    && peekc (zp) == '\n')
		break;
	    else
		pos += (long) strlen (buffer);

	wasdlm = strncmp (buffer, delim3, ld3) == 0;
	if (smsgs[msgp].m_start != pos)
	    smsgs[msgp++].m_stop = (c == '\n' && wasdlm) ? pos - 1 : pos;
	if (feof (zp) || pos >= Msgs[msgnum].m_stop) {
	    if (wasdlm)
		smsgs[msgp - 1].m_stop -= ((long) strlen (buffer) + 1);
	    break;
	}
	pos += (long) strlen (buffer);
    }

    switch (msgp--) {		/* toss "End of XXX Digest" */
	case 0: 
	    adios (NULL, "burst() botch -- you lose big");

	case 1: 
	    if (!quietsw)
		printf ("message %d not in digest format\n", msgnum);
	    return OK;

	default: 
	    if (verbosw)
		printf ("%d message%s exploded from digest %d\n",
			msgp, msgp != 1 ? "s" : "", msgnum);
	    break;
    }

    if ((i = msgp + mp->hghmsg) > MAXFOLDER) {
	advise (NULL, "more than %d messages", MAXFOLDER);
	return NOTOK;
    }
    if (!(mp = folder_realloc (mp, mp->lowoff, i)))
	adios (NULL, "unable to allocate folder storage");

    j = mp->hghmsg;
    mp->hghmsg += msgp;
    mp->nummsg += msgp;
    if (mp->hghsel > msgnum)
	mp->hghsel += msgp;

    if (inplace)
	for (i = mp->hghmsg; j > msgnum; i--, j--) {
	    if (verbosw)
		printf ("message %d becomes message %d\n", j, i);

	    Msgs[i].m_bboard_id = Msgs[j].m_bboard_id;
	    Msgs[i].m_top = Msgs[j].m_top;
	    Msgs[i].m_start = Msgs[j].m_start;
	    Msgs[i].m_stop = Msgs[j].m_stop;
	    Msgs[i].m_scanl = NULL;
	    if (Msgs[j].m_scanl) {
		free (Msgs[j].m_scanl);
		Msgs[j].m_scanl = NULL;
	    }
	    copy_msg_flags (mp, i, j);
	}

    if (Msgs[msgnum].m_bboard_id == 0)
	readid (msgnum);

    unset_selected (mp, msgnum);
    i = inplace ? msgnum + msgp : mp->hghmsg;
    for (j = msgp; j >= (inplace ? 0 : 1); i--, j--) {
	if (verbosw && i != msgnum)
	    printf ("message %d of digest %d becomes message %d\n",
		    j, msgnum, i);

	Msgs[i].m_bboard_id = Msgs[msgnum].m_bboard_id;
	Msgs[i].m_top = Msgs[j].m_top;
	Msgs[i].m_start = smsgs[j].m_start;
	Msgs[i].m_stop = smsgs[j].m_stop;
	Msgs[i].m_scanl = NULL;
	copy_msg_flags (mp, i, msgnum);
    }

    return OK;
}


#define FILE_SWITCHES \
    X("draft", 0, FIDRFT) \
    X("link", 0, FILINK) \
    X("nolink", 0, FINLINK) \
    X("preserve", 0, FIPRES) \
    X("nopreserve", 0, FINPRES) \
    X("src +folder", 0, FISRC) \
    X("file file", 0, FIFILE) \
    X("rmmproc program", 0, FIPROC) \
    X("normmproc", 0, FINPRC) \
    X("help", 0, FIHELP) \

#define X(sw, minchars, id) id,
DEFINE_SWITCH_ENUM(FILE);
#undef X

#define X(sw, minchars, id) { sw, minchars, id },
DEFINE_SWITCH_ARRAY(FILE, fileswit);
#undef X


void
filecmd (char **args)
{
    int	linksw = 0, msgp = 0;
    int vecp = 1, i, msgnum;
    char *cp, buf[BUFSIZ];
    char *msgs[MAXARGS], *vec[MAXARGS];

    if (fmsh) {
	forkcmd (args, cmd_name);
	return;
    }

    while ((cp = *args++)) {
	if (*cp == '-')
	    switch (i = smatch (++cp, fileswit)) {
		case AMBIGSW: 
		    ambigsw (cp, fileswit);
		    return;
		case UNKWNSW: 
		    fprintf (stderr, "-%s unknown\n", cp);
		    return;
		case FIHELP: 
		    snprintf (buf, sizeof(buf), "%s +folder... [msgs] [switches]", cmd_name);
		    print_help (buf, fileswit, 1);
		    return;

		case FILINK:
		    linksw++;
		    continue;
		case FINLINK: 
		    linksw = 0;
		    continue;

		case FIPRES: 
		case FINPRES: 
		    continue;

		case FISRC: 
		case FIDRFT:
		case FIFILE: 
		case FIPROC:
		case FINPRC:
		    advise (NULL, "sorry, -%s not allowed!", fileswit[i].sw);
		    return;
	    }
	if (*cp == '+' || *cp == '@')
	    vec[vecp++] = cp;
	else
	    msgs[msgp++] = cp;
    }

    vec[0] = cmd_name;
    vec[vecp++] = "-file";
    vec[vecp] = NULL;
    if (!msgp)
	msgs[msgp++] = "cur";
    for (msgnum = 0; msgnum < msgp; msgnum++)
	if (!m_convert (mp, msgs[msgnum]))
	    return;
    seq_setprev (mp);

    interrupted = 0;
    for (msgnum = mp->lowsel;
	    msgnum <= mp->hghsel && !interrupted;
	    msgnum++)
	if (is_selected (mp, msgnum))
	    if (process (msgnum, fileproc, vecp, vec)) {
		unset_selected (mp, msgnum);
		mp->numsel--;
	    }

    if (mp->numsel != mp->nummsg || linksw)
	seq_setcur (mp, mp->hghsel);
    if (!linksw)
	rmm ();
}


int
filehak (char **args)
{
    int	result, vecp = 0;
    char *cp, *cwd, *vec[MAXARGS];

    while ((cp = *args++)) {
	if (*cp == '-')
	    switch (smatch (++cp, fileswit)) {
		case AMBIGSW: 
		case UNKWNSW: 
		case FIHELP: 
		    return NOTOK;

		case FILINK:
		case FINLINK: 
		case FIPRES: 
		case FINPRES: 
		    continue;

		case FISRC: 
		case FIDRFT:
		case FIFILE: 
		    return NOTOK;
	    }
	if (*cp == '+' || *cp == '@')
	    vec[vecp++] = cp;
    }
    vec[vecp] = NULL;

    result = NOTOK;
    cwd = NULL;
    for (vecp = 0; (cp = vec[vecp]) && result == NOTOK; vecp++) {
	if (cwd == NULL)
	    cwd = getcpy (pwd ());
	chdir (m_maildir (""));
	cp = pluspath (cp);
	if (access (m_maildir (cp), F_OK) == NOTOK)
	    result = OK;
	free (cp);
    }
    if (cwd)
	chdir (cwd);

    return result;
}


#define FOLDER_SWITCHES \
    X("all", 0, FLALSW) \
    X("fast", 0, FLFASW) \
    X("nofast", 0, FLNFASW) \
    X("header", 0, FLHDSW) \
    X("noheader", 0, FLNHDSW) \
    X("pack", 0, FLPKSW) \
    X("nopack", 0, FLNPKSW) \
    X("recurse", 0, FLRCSW) \
    X("norecurse", 0, FLNRCSW) \
    X("total", 0, FLTLSW) \
    X("nototal", 0, FLNTLSW) \
    X("print", 0, FLPRSW) \
    X("push", 0, FLPUSW) \
    X("pop", 0, FLPOSW) \
    X("list", 0, FLLISW) \
    X("help", 0, FLHELP) \

#define X(sw, minchars, id) id,
DEFINE_SWITCH_ENUM(FOLDER);
#undef X

#define X(sw, minchars, id) { sw, minchars, id },
DEFINE_SWITCH_ARRAY(FOLDER, foldswit);
#undef X


void
foldcmd (char **args)
{
    int fastsw = 0, headersw = 0, packsw = 0;
    int hole, msgnum;
    char *cp, *folder = NULL, *msg = NULL;
    char buf[BUFSIZ], **vec = args;

    if (args == NULL)
	goto fast;

    while ((cp = *args++)) {
	if (*cp == '-')
	    switch (smatch (++cp, foldswit)) {
		case AMBIGSW: 
		    ambigsw (cp, foldswit);
		    return;
		case UNKWNSW: 
		    fprintf (stderr, "-%s unknown\n", cp);
		    return;
		case FLHELP: 
		    snprintf (buf, sizeof(buf), "%s [+folder] [msg] [switches]", cmd_name);
		    print_help (buf, foldswit, 1);
		    return;

		case FLALSW:	/* not implemented */
		case FLRCSW: 
		case FLNRCSW: 
		case FLTLSW: 
		case FLNTLSW: 
		case FLPRSW:
		case FLPUSW:
		case FLPOSW:
		case FLLISW:
		    continue;

		case FLFASW: 
		    fastsw++;
		    continue;
		case FLNFASW: 
		    fastsw = 0;
		    continue;
		case FLHDSW: 
		    headersw++;
		    continue;
		case FLNHDSW: 
		    headersw = 0;
		    continue;
		case FLPKSW: 
		    packsw++;
		    continue;
		case FLNPKSW: 
		    packsw = 0;
		    continue;
	    }
	if (*cp == '+' || *cp == '@') {
	    if (folder) {
		advise (NULL, "only one folder at a time!\n");
		return;
	    }
	    else
		folder = fmsh ? pluspath (cp)
			    : cp + 1;
	}
	else
	    if (msg) {
		advise (NULL, "only one message at a time!\n");
		return;
	    }
	    else
		msg = cp;
    }

    if (folder) {
	if (*folder == 0) {
	    advise (NULL, "null folder names are not permitted");
	    return;
	}
	if (fmsh) {
	    if (access (m_maildir (folder), R_OK) == NOTOK) {
		advise (folder, "unable to read");
		return;
	    }
	}
	else {
	    strncpy (buf, folder, sizeof(buf));
	    if (expand (buf) == NOTOK)
		return;
	    folder = buf;
	    if (access (folder, R_OK) == NOTOK) {
		advise (folder, "unable to read");
		return;
	    }
	}
	m_reset ();

	if (fmsh)
	    fsetup (folder);
	else
	    setup (folder);
	readids (0);
	display_info (0);
    }

    if (msg) {
	if (!m_convert (mp, msg))
	    return;
	seq_setprev (mp);

	if (mp->numsel > 1) {
	    advise (NULL, "only one message at a time!");
	    return;
	}
	seq_setcur (mp, mp->hghsel);
    }

    if (packsw) {
	if (fmsh) {
	    forkcmd (vec, cmd_name);
	    return;
	}

	if (mp->lowoff > 1 && !(mp = folder_realloc (mp, 1, mp->hghmsg)))
	    adios (NULL, "unable to allocate folder storage");

	for (msgnum = mp->lowmsg, hole = 1; msgnum <= mp->hghmsg; msgnum++)
	    if (does_exist (mp, msgnum)) {
		if (msgnum != hole) {
		    Msgs[hole].m_bboard_id = Msgs[msgnum].m_bboard_id;
		    Msgs[hole].m_top = Msgs[msgnum].m_top;
		    Msgs[hole].m_start = Msgs[msgnum].m_start;
		    Msgs[hole].m_stop = Msgs[msgnum].m_stop;
		    Msgs[hole].m_scanl = NULL;
		    if (Msgs[msgnum].m_scanl) {
			free (Msgs[msgnum].m_scanl);
			Msgs[msgnum].m_scanl = NULL;
		    }
		    copy_msg_flags (mp, hole, msgnum);
		    if (mp->curmsg == msgnum)
			seq_setcur (mp, hole);
		}
		hole++;
	    }
	if (mp->nummsg > 0) {
	    mp->lowmsg = 1;
	    mp->hghmsg = hole - 1;
	}
	mp->msgflags |= MODIFIED;
	modified++;
    }

fast: ;
    if (fastsw)
	printf ("%s\n", fmsh ? fmsh : mp->foldpath);
    else {
	if (headersw)
	    printf ("\t\tFolder  %*s# of messages (%*srange%*s); cur%*smsg\n",
		DMAXFOLDER, "", DMAXFOLDER - 2, "", DMAXFOLDER - 2, "",
		DMAXFOLDER - 2, "");
	printf (args ? "%22s  " : "%s ", fmsh ? fmsh : mp->foldpath);

	/* check for empty folder */
	if (mp->nummsg == 0) {
	    printf ("has   no messages%*s",
		    mp->msgflags & OTHERS ? DMAXFOLDER * 2 + 4 : 0, "");
	} else {
	    printf ("has %*d message%s (%*d-%*d)",
		    DMAXFOLDER, mp->nummsg, mp->nummsg != 1 ? "s" : "",
		    DMAXFOLDER, mp->lowmsg, DMAXFOLDER, mp->hghmsg);
	    if (mp->curmsg >= mp->lowmsg
		    && mp->curmsg <= mp->hghmsg)
		printf ("; cur=%*d", DMAXFOLDER, mp->curmsg);
	}
	printf (".\n");
    }
}


#define FORW_SWITCHES \
    X("annotate", 0, FOANSW) \
    X("noannotate", 0, FONANSW) \
    X("draftfolder +folder", 0, FODFSW) \
    X("draftmessage msg", 0, FODMSW) \
    X("nodraftfolder", 0, FONDFSW) \
    X("editor editor", 0, FOEDTSW) \
    X("noedit", 0, FONEDSW) \
    X("filter filterfile", 0, FOFTRSW) \
    X("form formfile", 0, FOFRMSW) \
    X("format", 5, FOFTSW) \
    X("noformat", 7, FONFTSW) \
    X("inplace", 0, FOINSW) \
    X("noinplace", 0, FONINSW) \
    X("mime", 0, FOMISW) \
    X("nomime", 0, FONMISW) \
    X("whatnowproc program", 0, FOWHTSW) \
    X("nowhatnow", 0, FONWTSW) \
    X("help", 0, FOHELP) \

#define X(sw, minchars, id) id,
DEFINE_SWITCH_ENUM(FORW);
#undef X

#define X(sw, minchars, id) { sw, minchars, id },
DEFINE_SWITCH_ARRAY(FORW, forwswit);
#undef X


void
forwcmd (char **args)
{
    int	msgp = 0, vecp = 1, msgnum;
    char *cp, *filter = NULL, buf[BUFSIZ];
    char *msgs[MAXARGS], *vec[MAXARGS];
    char *tfile = NULL;
    char tmpfil[BUFSIZ];

    if (fmsh) {
	forkcmd (args, cmd_name);
	return;
    }

    while ((cp = *args++)) {
	if (*cp == '-')
	    switch (smatch (++cp, forwswit)) {
		case AMBIGSW: 
		    ambigsw (cp, forwswit);
		    return;
		case UNKWNSW: 
		    fprintf (stderr, "-%s unknown\n", cp);
		    return;
		case FOHELP: 
		    snprintf (buf, sizeof(buf), "%s [msgs] [switches]", cmd_name);
		    print_help (buf, forwswit, 1);
		    return;

		case FOANSW:	/* not implemented */
		case FONANSW: 
		case FOINSW: 
		case FONINSW: 
		case FOMISW: 
		case FONMISW: 
		    continue;

		case FONDFSW:
		case FONEDSW:
		case FONWTSW:
		    vec[vecp++] = --cp;
		    continue;

		case FOEDTSW: 
		case FOFRMSW: 
		case FODFSW:
		case FODMSW:
		case FOWHTSW:
		    vec[vecp++] = --cp;
		    if (!(cp = *args++) || *cp == '-') {
			advise (NULL, "missing argument to %s", args[-2]);
			return;
		    }
		    vec[vecp++] = cp;
		    continue;
		case FOFTRSW: 
		    if (!(filter = *args++) || *filter == '-') {
			advise (NULL, "missing argument to %s", args[-2]);
			return;
		    }
		    continue;
		case FOFTSW: 
		    if (access (filter = myfilter, R_OK) == NOTOK) {
			advise (filter, "unable to read default filter file");
			return;
		    }
		    continue;
		case FONFTSW: 
		    filter = NULL;
		    continue;
	    }
	if (*cp == '+' || *cp == '@') {
	    advise (NULL, "sorry, no folders allowed!");
	    return;
	}
	else
	    msgs[msgp++] = cp;
    }

					/* foil search of .mh_profile */
    snprintf (buf, sizeof(buf), "%sXXXXXX", invo_name);

    tfile = m_mktemp(buf, NULL, NULL);
    if (tfile == NULL) adios("forwcmd", "unable to create temporary file");
    strncpy (tmpfil, tfile, sizeof(tmpfil));
    vec[0] = tmpfil;

    vec[vecp++] = "-file";
    vec[vecp] = NULL;
    if (!msgp)
	msgs[msgp++] = "cur";
    for (msgnum = 0; msgnum < msgp; msgnum++)
	if (!m_convert (mp, msgs[msgnum]))
	    return;
    seq_setprev (mp);

    if (filter) {
	strncpy (buf, filter, sizeof(buf));
	if (expand (buf) == NOTOK)
	    return;
	if (access (filter = getcpy (etcpath (buf)), R_OK) == NOTOK) {
	    advise (filter, "unable to read");
	    free (filter);
	    return;
	}
    }
    forw (cmd_name, filter, vecp, vec);
    seq_setcur (mp, mp->hghsel);
    if (filter)
	free (filter);
}


static void
forw (char *proc, char *filter, int vecp, char **vec)
{
    int i, child_id, msgnum, msgcnt;
    char tmpfil[BUFSIZ], *args[MAXARGS];
    FILE *out;
    char *tfile = NULL;

    tfile = m_mktemp2(NULL, invo_name, NULL, NULL);
    if (tfile == NULL) adios("forw", "unable to create temporary file");
    strncpy (tmpfil, tfile, sizeof(tmpfil));

    interrupted = 0;
    if (filter)
	switch (child_id = fork ()) {
	    case NOTOK: 
		advise ("fork", "unable to");
		return;

	    case OK: 		/* "trust me" */
		if (freopen (tmpfil, "w", stdout) == NULL) {
		    fprintf (stderr, "unable to create ");
		    perror (tmpfil);
		    _exit (1);
		}
		args[0] = r1bindex (mhlproc, '/');
		i = 1;
		args[i++] = "-forwall";
		args[i++] = "-form";
		args[i++] = filter;
		for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++)
		    if (is_selected (mp, msgnum))
			args[i++] = getcpy (m_name (msgnum));
		args[i] = NULL;
		mhlsbr (i, args, mhl_action);
		scan_eom_action ((int (*) ()) 0);
		fclose (stdout);
		_exit (0);

	    default: 
		if (pidXwait (child_id, NULL))
		    interrupted++;
		break;
	}
    else {
	if ((out = fopen (tmpfil, "w")) == NULL) {
	    advise (tmpfil, "unable to create temporary file");
	    return;
	}

	msgcnt = 1;
	for (msgnum = mp->lowsel;
		msgnum <= mp->hghsel && !interrupted;
		msgnum++)
	    if (is_selected (mp, msgnum)) {
		fprintf (out, "\n\n-------");
		if (msgnum == mp->lowsel)
		    fprintf (out, " Forwarded Message%s",
			    mp->numsel > 1 ? "s" : "");
		else
		    fprintf (out, " Message %d", msgcnt);
		fprintf (out, "\n\n");
		copy_digest (msgnum, out);
		msgcnt++;
	    }

	fprintf (out, "\n\n------- End of Forwarded Message%s\n",
		mp->numsel > 1 ? "s" : "");
	fclose (out);
    }

    fflush (stdout);
    if (!interrupted)
	switch (child_id = fork ()) {
	    case NOTOK: 
		advise ("fork", "unable to");
		break;

	    case OK: 
		closefds (3);
		SIGNAL (SIGINT, istat);
		SIGNAL (SIGQUIT, qstat);

		vec[vecp++] = tmpfil;
		vec[vecp] = NULL;

		execvp (proc, vec);
		fprintf (stderr, "unable to exec ");
		perror (proc);
		_exit (1);

	    default: 
		pidXwait (child_id, NULL);
		break;
	}

    (void) m_unlink (tmpfil);
}


static char *hlpmsg[] = {
    "The %s program emulates many of the commands found in the nmh",
    "system.  Instead of operating on nmh folders, commands to %s concern",
    "a single file.",
    "",
    "To see the list of commands available, just type a ``?'' followed by",
    "the RETURN key.  To find out what switches each command takes, type",
    "the name of the command followed by ``-help''.  To leave %s, use the",
    "``quit'' command.",
    "",
    "Although a lot of nmh commands are found in %s, not all are fully",
    "implemented.  %s will always recognize all legal switches for a",
    "given command though, and will let you know when you ask for an",
    "option that it is unable to perform.",
    "",
    "Running %s is fun, but using nmh from your shell is far superior.",
    "After you have familiarized yourself with the nmh style by using %s,",
    "you should try using nmh from the shell.  You can still use %s for",
    "message files that aren't in nmh format, such as BBoard files.",
    NULL
};


void
helpcmd (char **args)
{
    int i;
    NMH_UNUSED (args);

    for (i = 0; hlpmsg[i]; i++) {
	printf (hlpmsg[i], invo_name);
	putchar ('\n');
    }
}


#define MARK_SWITCHES \
    X("add", 0, MADDSW) \
    X("delete", 0, MDELSW) \
    X("list", 0, MLSTSW) \
    X("sequence name", 0, MSEQSW) \
    X("public", 0, MPUBSW) \
    X("nopublic", 0, MNPUBSW) \
    X("zero", 0, MZERSW) \
    X("nozero", 0, MNZERSW) \
    X("help", 0, MHELP) \
    X("debug", -5, MDBUGSW) \

#define X(sw, minchars, id) id,
DEFINE_SWITCH_ENUM(MARK);
#undef X

#define X(sw, minchars, id) { sw, minchars, id },
DEFINE_SWITCH_ARRAY(MARK, markswit);
#undef X


void
markcmd (char **args)
{
    int addsw = 0, deletesw = 0, debugsw = 0;
    int listsw = 0, zerosw = 0;
    size_t seqp = 0;
    int msgp = 0, msgnum;
    char *cp, buf[BUFSIZ];
    char *seqs[NUMATTRS + 1], *msgs[MAXARGS];

    while ((cp = *args++)) {
	if (*cp == '-') {
	    switch (smatch (++cp, markswit)) {
		case AMBIGSW: 
		    ambigsw (cp, markswit);
		    return;
		case UNKWNSW: 
		    fprintf (stderr, "-%s unknown\n", cp);
		    return;
		case MHELP: 
		    snprintf (buf, sizeof(buf), "%s [msgs] [switches]", cmd_name);
		    print_help (buf, markswit, 1);
		    return;

		case MADDSW: 
		    addsw++;
		    deletesw = listsw = 0;
		    continue;
		case MDELSW: 
		    deletesw++;
		    addsw = listsw = 0;
		    continue;
		case MLSTSW: 
		    listsw++;
		    addsw = deletesw = 0;
		    continue;

		case MSEQSW: 
		    if (!(cp = *args++) || *cp == '-') {
			advise (NULL, "missing argument to %s", args[-2]);
			return;
		    }
		    if (seqp < NUMATTRS)
			seqs[seqp++] = cp;
		    else {
			advise (NULL, "only %d sequences allowed!", NUMATTRS);
			return;
		    }
		    continue;

		case MPUBSW: 	/* not implemented */
		case MNPUBSW: 
		    continue;

		case MDBUGSW: 
		    debugsw++;
		    continue;

		case MZERSW: 
		    zerosw++;
		    continue;
		case MNZERSW: 
		    zerosw = 0;
		    continue;
	    }
	}
	if (*cp == '+' || *cp == '@') {
	    advise (NULL, "sorry, no folders allowed!");
	    return;
	} else {
	    msgs[msgp++] = cp;
	}
    }

    if (!addsw && !deletesw && !listsw) {
	if (seqp)
	    addsw++;
	else
	    if (debugsw)
		listsw++;
	    else {
		seqs[seqp++] = "unseen";
		deletesw++;
		zerosw = 0;
		if (!msgp)
		    msgs[msgp++] = "all";
	    }
    }

    if (!msgp)
	msgs[msgp++] = listsw ? "all" :"cur";
    for (msgnum = 0; msgnum < msgp; msgnum++)
	if (!m_convert (mp, msgs[msgnum]))
	    return;

    if (debugsw) {
	printf ("invo_name=%s mypath=%s defpath=%s\n",
		invo_name, mypath, defpath);
	printf ("ctxpath=%s context flags=%s\n",
		ctxpath, snprintb (buf, sizeof(buf), (unsigned) ctxflags, DBITS));
	printf ("foldpath=%s flags=%s\n",
		mp->foldpath,
		snprintb (buf, sizeof(buf), (unsigned) mp->msgflags, FBITS));
	printf ("hghmsg=%d lowmsg=%d nummsg=%d curmsg=%d\n",
		mp->hghmsg, mp->lowmsg, mp->nummsg, mp->curmsg);
	printf ("lowsel=%d hghsel=%d numsel=%d\n",
		mp->lowsel, mp->hghsel, mp->numsel);
	printf ("lowoff=%d hghoff=%d\n", mp->lowoff, mp->hghoff);
    }

    if (seqp == 0 && (addsw || deletesw)) {
	advise (NULL, "-%s requires at least one -sequence argument",
		addsw ? "add" : "delete");
	return;
    }
    seqs[seqp] = NULL;

    if (addsw) {
	for (seqp = 0; seqs[seqp]; seqp++)
	    if (!seq_addsel (mp, seqs[seqp], 0, zerosw))
		return;
    }

    if (deletesw) {
	for (seqp = 0; seqs[seqp]; seqp++)
	    if (!seq_delsel (mp, seqs[seqp], 0, zerosw))
		return;
    }

    /* Listing messages in sequences */
    if (listsw) {
	if (seqp) {
	    /* list the given sequences */
	    for (seqp = 0; seqs[seqp]; seqp++)
		seq_print (mp, seqs[seqp]);
	} else {
	    /* else list them all */
	    seq_printall (mp);
	}

	interrupted = 0;
	if (debugsw)
	    for (msgnum = mp->lowsel;
		    msgnum <= mp->hghsel && !interrupted;
		    msgnum++)
		if (is_selected (mp, msgnum)) {
		    printf ("%*d: id=%d top=%d start=%ld stop=%ld %s\n",
			DMAXFOLDER,
			msgnum,
			Msgs[msgnum].m_bboard_id,
			Msgs[msgnum].m_top,
			(long) Msgs[msgnum].m_start,
			(long) Msgs[msgnum].m_stop,
			snprintb (buf, sizeof(buf),
				  (unsigned) *bvector_bits (msgstat (mp,
                                                                     msgnum)),
				  seq_bits (mp)));
		    if (Msgs[msgnum].m_scanl)
			printf ("%s", Msgs[msgnum].m_scanl);
		}			    
    }
}


#define MHN_SWITCHES \
    X("auto", 0, MHNAUTOSW) \
    X("noauto", 0, MHNNAUTOSW) \
    X("debug", -5, MHNDEBUGSW) \
    X("form formfile", 4, MHNFORMSW) \
    X("headers", 0, MHNHEADSW) \
    X("noheaders", 0, MHNNHEADSW) \
    X("list", 0, MHNLISTSW) \
    X("nolist", 0, MHNNLISTSW) \
    X("part number", 0, MHNPARTSW) \
    X("realsize", 0, MHNSIZESW) \
    X("norealsize", 0, MHNNSIZESW) \
    X("rfc934mode", 0, MHNRFC934SW) \
    X("norfc934mode", 0, MHNNRFC934SW) \
    X("serialonly", 0, MHNSERIALSW) \
    X("noserialonly", 0, MHNNSERIALSW) \
    X("show", 0, MHNSHOWSW) \
    X("noshow", 0, MHNNSHOWSW) \
    X("store", 0, MHNSTORESW) \
    X("nostore", 0, MHNNSTORESW) \
    X("type content", 0, MHNTYPESW) \
    X("verbose", 0, MHNVERBSW) \
    X("noverbose", 0, MHNNVERBSW) \
    X("help", 0, MHNHELPSW) \
    X("moreproc program", -4, MHNPROGSW) \
    X("nomoreproc", -3, MHNNPROGSW) \
    X("length lines", -4, MHNLENSW) \
    X("width columns", -4, MHNWIDSW) \

#define X(sw, minchars, id) id,
DEFINE_SWITCH_ENUM(MHN);
#undef X

#define X(sw, minchars, id) { sw, minchars, id },
DEFINE_SWITCH_ARRAY(MHN, mhnswit);
#undef X


void
mhncmd (char **args)
{
    int msgp = 0, vecp = 1;
    int msgnum;
    char *cp, buf[BUFSIZ];
    char *msgs[MAXARGS], *vec[MAXARGS];

    if (fmsh) {
	forkcmd (args, cmd_name);
	return;
    }
    while ((cp = *args++)) {
	if (*cp == '-') {
	    switch (smatch (++cp, mhnswit)) {
		case AMBIGSW: 
		    ambigsw (cp, mhnswit);
		    return;
		case UNKWNSW: 
		    fprintf (stderr, "-%s unknown\n", cp);
		    return;
		case MHNHELPSW:
		    snprintf (buf, sizeof(buf), "%s [msgs] [switches]", cmd_name);
		    print_help (buf, mhnswit, 1);
		    return;

		case MHNAUTOSW:
		case MHNNAUTOSW:
		case MHNDEBUGSW:
		case MHNHEADSW:
		case MHNNHEADSW:
		case MHNLISTSW:
		case MHNNLISTSW:
		case MHNSIZESW:
		case MHNNSIZESW:
		case MHNRFC934SW:
		case MHNNRFC934SW:
		case MHNSERIALSW:
		case MHNNSERIALSW:
		case MHNSHOWSW:
		case MHNNSHOWSW:
		case MHNSTORESW:
		case MHNNSTORESW:
		case MHNVERBSW:
		case MHNNVERBSW:
		case MHNNPROGSW:
		    vec[vecp++] = --cp;
		    continue;

		case MHNFORMSW:
		case MHNPARTSW:
		case MHNTYPESW:
		case MHNPROGSW:
		case MHNLENSW:
		case MHNWIDSW:
		    vec[vecp++] = --cp;
		    if (!(cp = *args++) || *cp == '-') {
			advise (NULL, "missing argument to %s", args[-2]);
			return;
		    }
		    vec[vecp++] = cp;
		    continue;
	    }
	}
	if (*cp == '+' || *cp == '@') {
	    advise (NULL, "sorry, no folders allowed!");
	    return;
	} else {
	    msgs[msgp++] = cp;
	}
    }

    vec[0] = cmd_name;
    vec[vecp++] = "-file";
    vec[vecp] = NULL;
    if (!msgp)
	msgs[msgp++] = "cur";
    for (msgnum = 0; msgnum < msgp; msgnum++)
	if (!m_convert (mp, msgs[msgnum]))
	    return;
    seq_setprev (mp);

    interrupted = 0;
    for (msgnum = mp->lowsel;
	    msgnum <= mp->hghsel && !interrupted;
	    msgnum++)
	if (is_selected (mp, msgnum))
	    if (process (msgnum, cmd_name, vecp, vec)) {
		unset_selected (mp, msgnum);
		mp->numsel--;
	    }

    seq_setcur (mp, mp->hghsel);
}


#define PACK_SWITCHES \
    X("file name", 0, PAFISW) \
    X("help", 0, PAHELP) \

#define X(sw, minchars, id) id,
DEFINE_SWITCH_ENUM(PACK);
#undef X

#define X(sw, minchars, id) { sw, minchars, id },
DEFINE_SWITCH_ARRAY(PACK, packswit);
#undef X

static int mbx_style = MMDF_FORMAT;

void
packcmd (char **args)
{
    int msgp = 0, md, msgnum;
    char *cp, *file = NULL;
    char buf[BUFSIZ], *msgs[MAXARGS];
    struct stat st;

    if (fmsh) {
	forkcmd (args, cmd_name);
	return;
    }

    while ((cp = *args++)) {
	if (*cp == '-')
	    switch (smatch (++cp, packswit)) {
		case AMBIGSW: 
		    ambigsw (cp, packswit);
		    return;
		case UNKWNSW: 
		    fprintf (stderr, "-%s unknown\n", cp);
		    return;
		case PAHELP: 
		    snprintf (buf, sizeof(buf), "%s [msgs] [switches]", cmd_name);
		    print_help (buf, packswit, 1);
		    return;

		case PAFISW: 
		    if (!(file = *args++) || *file == '-') {
			advise (NULL, "missing argument to %s", args[-2]);
			return;
		    }
		    continue;
	    }
	if (*cp == '+' || *cp == '@') {
	    advise (NULL, "sorry, no folders allowed!");
	    return;
	}
	else
	    msgs[msgp++] = cp;
    }

    if (!file)
	file = "./msgbox";
    file = path (file, TFILE);
    if (stat (file, &st) == NOTOK) {
	if (errno != ENOENT) {
	    advise (file, "error on file");
	    goto done_pack;
	}
	md = getanswer (cp = concat ("Create file \"", file, "\"? ", NULL));
	free (cp);
	if (!md)
	    goto done_pack;
    }

    if (!msgp)
	msgs[msgp++] = "all";
    for (msgnum = 0; msgnum < msgp; msgnum++)
	if (!m_convert (mp, msgs[msgnum]))
	    goto done_pack;
    seq_setprev (mp);

    if ((md = mbx_open (file, mbx_style, getuid (), getgid (), m_gmprot ())) == NOTOK) {
	advise (file, "unable to open");
	goto done_pack;
    }
    for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++)
	if (is_selected (mp, msgnum))
	    if (pack (file, md, msgnum) == NOTOK)
		break;
    mbx_close (file, md);

    if (mp->hghsel != mp->curmsg)
	seq_setcur (mp, mp->lowsel);

done_pack: ;
    free (file);
}


int
pack (char *mailbox, int md, int msgnum)
{
    register FILE *zp;

    if (Msgs[msgnum].m_bboard_id == 0)
	readid (msgnum);

    zp = msh_ready (msgnum, 1);
    return mbx_write (mailbox, md, zp, Msgs[msgnum].m_bboard_id,
	    0L, ftell (zp), Msgs[msgnum].m_stop, 1, 1);
}


int
packhak (char **args)
{
    int	result;
    char *cp, *file = NULL;

    while ((cp = *args++)) {
	if (*cp == '-')
	    switch (smatch (++cp, packswit)) {
		case AMBIGSW: 
		case UNKWNSW: 
		case PAHELP: 
		    return NOTOK;

		case PAFISW: 
		    if (!(file = *args++) || *file == '-') 
			return NOTOK;
		    continue;
	    }
	if (*cp == '+' || *cp == '@')
	    return NOTOK;
    }

    file = path (file ? file : "./msgbox", TFILE);
    result = access (file, F_OK) == NOTOK ? OK : NOTOK;
    free (file);

    return result;
}


#define PICK_SWITCHES \
    X("and", 0, PIANSW) \
    X("or", 0, PIORSW) \
    X("not", 0, PINTSW) \
    X("lbrace", 0, PILBSW) \
    X("rbrace", 0, PIRBSW) \
    X("cc  pattern", 0, PICCSW) \
    X("date  pattern", 0, PIDASW) \
    X("from  pattern", 0, PIFRSW) \
    X("search  pattern", 0, PISESW) \
    X("subject  pattern", 0, PISUSW) \
    X("to  pattern", 0, PITOSW) \
    X("-othercomponent  pattern", 15, PIOTSW) \
    X("after date", 0, PIAFSW) \
    X("before date", 0, PIBFSW) \
    X("datefield field", 5, PIDFSW) \
    X("sequence name", 0, PISQSW) \
    X("public", 0, PIPUSW) \
    X("nopublic", 0, PINPUSW) \
    X("zero", 0, PIZRSW) \
    X("nozero", 0, PINZRSW) \
    X("list", 0, PILISW) \
    X("nolist", 0, PINLISW) \
    X("help", 0, PIHELP) \

#define X(sw, minchars, id) id,
DEFINE_SWITCH_ENUM(PICK);
#undef X

#define X(sw, minchars, id) { sw, minchars, id },
DEFINE_SWITCH_ARRAY(PICK, pickswit);
#undef X


void
pickcmd (char **args)
{
    int zerosw = 1, msgp = 0;
    size_t seqp = 0;
    int vecp = 0, hi, lo, msgnum;
    char *cp, buf[BUFSIZ], *msgs[MAXARGS];
    char *seqs[NUMATTRS], *vec[MAXARGS];
    register FILE *zp;

    while ((cp = *args++)) {
	if (*cp == '-') {
	    if (*++cp == '-') {
		vec[vecp++] = --cp;
		goto pattern;
	    }
	    switch (smatch (cp, pickswit)) {
		case AMBIGSW: 
		    ambigsw (cp, pickswit);
		    return;
		case UNKWNSW: 
		    fprintf (stderr, "-%s unknown\n", cp);
		    return;
		case PIHELP: 
		    snprintf (buf, sizeof(buf), "%s [msgs] [switches]", cmd_name);
		    print_help (buf, pickswit, 1);
		    return;

		case PICCSW: 
		case PIDASW: 
		case PIFRSW: 
		case PISUSW: 
		case PITOSW: 
		case PIDFSW: 
		case PIAFSW: 
		case PIBFSW: 
		case PISESW: 
		    vec[vecp++] = --cp;
pattern: ;
		    if (!(cp = *args++)) {/* allow -xyz arguments */
			advise (NULL, "missing argument to %s", args[-2]);
			return;
		    }
		    vec[vecp++] = cp;
		    continue;
		case PIOTSW: 
		    advise (NULL, "internal error!");
		    return;
		case PIANSW: 
		case PIORSW: 
		case PINTSW: 
		case PILBSW: 
		case PIRBSW: 
		    vec[vecp++] = --cp;
		    continue;

		case PISQSW: 
		    if (!(cp = *args++) || *cp == '-') {
			advise (NULL, "missing argument to %s", args[-2]);
			return;
		    }
		    if (seqp < NUMATTRS)
			seqs[seqp++] = cp;
		    else {
			advise (NULL, "only %d sequences allowed!", NUMATTRS);
			return;
		    }
		    continue;
		case PIZRSW: 
		    zerosw++;
		    continue;
		case PINZRSW: 
		    zerosw = 0;
		    continue;

		case PIPUSW: 	/* not implemented */
		case PINPUSW: 
		case PILISW: 
		case PINLISW: 
		    continue;
	    }
	}
	if (*cp == '+' || *cp == '@') {
	    advise (NULL, "sorry, no folders allowed!");
	    return;
	}
	else
	    msgs[msgp++] = cp;
    }
    vec[vecp] = NULL;

    if (!msgp)
	msgs[msgp++] = "all";
    for (msgnum = 0; msgnum < msgp; msgnum++)
	if (!m_convert (mp, msgs[msgnum]))
	    return;
    seq_setprev (mp);

    interrupted = 0;
    if (!pcompile (vec, NULL))
	return;

    lo = mp->lowsel;
    hi = mp->hghsel;

    for (msgnum = mp->lowsel;
	    msgnum <= mp->hghsel && !interrupted;
	    msgnum++)
	if (is_selected (mp, msgnum)) {
	    zp = msh_ready (msgnum, 1);
	    if (pmatches (zp, msgnum, fmsh ? 0L : Msgs[msgnum].m_start,
			fmsh ? 0L : Msgs[msgnum].m_stop)) {
		if (msgnum < lo)
		    lo = msgnum;
		if (msgnum > hi)
		    hi = msgnum;
	    }
	    else {
		unset_selected (mp, msgnum);
		mp->numsel--;
	    }
	}

    if (interrupted)
	return;

    mp->lowsel = lo;
    mp->hghsel = hi;

    if (mp->numsel <= 0) {
	advise (NULL, "no messages match specification");
	return;
    }

    seqs[seqp] = NULL;
    for (seqp = 0; seqs[seqp]; seqp++)
	if (!seq_addsel (mp, seqs[seqp], 0, zerosw))
	    return;

    printf ("%d hit%s\n", mp->numsel, mp->numsel == 1 ? "" : "s");
}


#define REPL_SWITCHES \
    X("annotate", 0, REANSW) \
    X("noannotate", 0, RENANSW) \
    X("cc type", 0, RECCSW) \
    X("nocc type", 0, RENCCSW) \
    X("draftfolder +folder", 0, REDFSW) \
    X("draftmessage msg", 0, REDMSW) \
    X("nodraftfolder", 0, RENDFSW) \
    X("editor editor", 0, REEDTSW) \
    X("noedit", 0, RENEDSW) \
    X("fcc +folder", 0, REFCCSW) \
    X("filter filterfile", 0, REFLTSW) \
    X("form formfile", 0, REFRMSW) \
    X("inplace", 0, REINSW) \
    X("noinplace", 0, RENINSW) \
    X("query", 0, REQUSW) \
    X("noquery", 0, RENQUSW) \
    X("whatnowproc program", 0, REWHTSW) \
    X("nowhatnow", 0, RENWTSW) \
    X("width columns", 0, REWIDSW) \
    X("help", 0, REHELP) \

#define X(sw, minchars, id) id,
DEFINE_SWITCH_ENUM(REPL);
#undef X

#define X(sw, minchars, id) { sw, minchars, id },
DEFINE_SWITCH_ARRAY(REPL, replswit);
#undef X


void
replcmd (char **args)
{
    int vecp = 1;
    char *cp, *msg = NULL;
    char buf[BUFSIZ], *vec[MAXARGS];

    if (fmsh) {
	forkcmd (args, cmd_name);
	return;
    }

    while ((cp = *args++)) {
	if (*cp == '-')
	    switch (smatch (++cp, replswit)) {
		case AMBIGSW: 
		    ambigsw (cp, replswit);
		    return;
		case UNKWNSW: 
		    fprintf (stderr, "-%s unknown\n", cp);
		    return;
		case REHELP: 
		    snprintf (buf, sizeof(buf), "%s [msgs] [switches]", cmd_name);
		    print_help (buf, replswit, 1);
		    return;

		case REANSW:	/* not implemented */
		case RENANSW: 
		case REINSW: 
		case RENINSW: 
		    continue;

		case REQUSW:
		case RENQUSW:
		case RENDFSW:
		case RENEDSW:
		case RENWTSW:
		    vec[vecp++] = --cp;
		    continue;

		case RECCSW: 
		case RENCCSW: 
		case REEDTSW: 
		case REFCCSW: 
		case REFLTSW:
		case REFRMSW: 
		case REWIDSW: 
		case REDFSW:
		case REDMSW:
		case REWHTSW:
		    vec[vecp++] = --cp;
		    if (!(cp = *args++) || *cp == '-') {
			advise (NULL, "missing argument to %s", args[-2]);
			return;
		    }
		    vec[vecp++] = cp;
		    continue;
	    }
	if (*cp == '+' || *cp == '@') {
	    advise (NULL, "sorry, no folders allowed!");
	    return;
	}
	else
	    if (msg) {
		advise (NULL, "only one message at a time!");
		return;
	    }
	    else
		msg = cp;
    }

    vec[0] = cmd_name;
    vec[vecp++] = "-file";
    vec[vecp] = NULL;
    if (!msg)
	msg = "cur";
    if (!m_convert (mp, msg))
	return;
    seq_setprev (mp);

    if (mp->numsel > 1) {
	advise (NULL, "only one message at a time!");
	return;
    }
    process (mp->hghsel, cmd_name, vecp, vec);
    seq_setcur (mp, mp->hghsel);
}


#define RMM_SWITCHES \
    X("help", 0, RMHELP) \

#define X(sw, minchars, id) id,
DEFINE_SWITCH_ENUM(RMM);
#undef X

#define X(sw, minchars, id) { sw, minchars, id },
DEFINE_SWITCH_ARRAY(RMM, rmmswit);
#undef X


void
rmmcmd (char **args)
{
    int	msgp = 0, msgnum;
    char *cp, buf[BUFSIZ], *msgs[MAXARGS];

    while ((cp = *args++)) {
	if (*cp == '-')
	    switch (smatch (++cp, rmmswit)) {
		case AMBIGSW: 
		    ambigsw (cp, rmmswit);
		    return;
		case UNKWNSW: 
		    fprintf (stderr, "-%s unknown\n", cp);
		    return;
		case RMHELP: 
		    snprintf (buf, sizeof(buf), "%s [msgs] [switches]", cmd_name);
		    print_help (buf, rmmswit, 1);
		    return;
	    }
	if (*cp == '+' || *cp == '@') {
	    advise (NULL, "sorry, no folders allowed!");
	    return;
	}
	else
	    msgs[msgp++] = cp;
    }

    if (!msgp)
	msgs[msgp++] = "cur";
    for (msgnum = 0; msgnum < msgp; msgnum++)
	if (!m_convert (mp, msgs[msgnum]))
	    return;
    seq_setprev (mp);

    rmm ();
}


static void
rmm (void)
{
    register int msgnum, vecp;
    register char *cp;
    char buffer[BUFSIZ], *vec[MAXARGS];

    if (fmsh) {
	if (rmmproc) {
	    if (mp->numsel > MAXARGS - 1) {
		advise (NULL, "more than %d messages for %s exec",
			MAXARGS - 1, rmmproc);
		return;
	    }
	    vecp = 0;
	    for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++)
		if (is_selected (mp, msgnum))
		    vec[vecp++] = getcpy (m_name (msgnum));
	    vec[vecp] = NULL;
	    forkcmd (vec, rmmproc);
	    for (vecp = 0; vec[vecp]; vecp++)
		free (vec[vecp]);
	}
	else
	    for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++)
		if (is_selected (mp, msgnum)) {
		    strncpy (buffer, m_backup (cp = m_name (msgnum)), sizeof(buffer));
		    if (rename (cp, buffer) == NOTOK)
			admonish (buffer, "unable to rename %s to", cp);
		}
    }

    for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++)
	if (is_selected (mp, msgnum)) {
	    set_deleted (mp, msgnum);
	    unset_exists (mp, msgnum);
	}

    if ((mp->nummsg -= mp->numsel) <= 0) {
	if (fmsh)
	    admonish (NULL, "no messages remaining in +%s", fmsh);
	else
	    admonish (NULL, "no messages remaining in %s", mp->foldpath);
	mp->lowmsg = mp->hghmsg = mp->nummsg = 0;
    }
    if (mp->lowsel == mp->lowmsg) {
	for (msgnum = mp->lowmsg + 1; msgnum <= mp->hghmsg; msgnum++)
	    if (does_exist (mp, msgnum))
		break;
	mp->lowmsg = msgnum;
    }
    if (mp->hghsel == mp->hghmsg) {
	for (msgnum = mp->hghmsg - 1; msgnum >= mp->lowmsg; msgnum--)
	    if (does_exist (mp, msgnum))
		break;
	mp->hghmsg = msgnum;
    }

    mp->msgflags |= MODIFIED;
    modified++;
}


#define SCAN_SWITCHES \
    X("clear", 0, SCCLR) \
    X("noclear", 0, SCNCLR) \
    X("form formatfile", 0, SCFORM) \
    X("format string", 5, SCFMT) \
    X("header", 0, SCHEAD) \
    X("noheader", 0, SCNHEAD) \
    X("width columns", 0, SCWID) \
    X("help", 0, SCHELP) \

#define X(sw, minchars, id) id,
DEFINE_SWITCH_ENUM(SCAN);
#undef X

#define X(sw, minchars, id) { sw, minchars, id },
DEFINE_SWITCH_ARRAY(SCAN, scanswit);
#undef X


void
scancmd (char **args)
{
#define	equiv(a,b)	(a ? b && !strcmp (a, b) : !b)

    int clearsw = 0, headersw = 0, width = 0, msgp = 0;
    int msgnum, optim, state;
    char *cp, *form = NULL, *format = NULL;
    char buf[BUFSIZ], *nfs, *msgs[MAXARGS];
    register FILE *zp;
    static int s_optim = 0;
    static char *s_form = NULL, *s_format = NULL;

    while ((cp = *args++)) {
	if (*cp == '-')
	    switch (smatch (++cp, scanswit)) {
		case AMBIGSW: 
		    ambigsw (cp, scanswit);
		    return;
		case UNKWNSW: 
		    fprintf (stderr, "-%s unknown\n", cp);
		    return;
		case SCHELP: 
		    snprintf (buf, sizeof(buf), "%s [msgs] [switches]", cmd_name);
		    print_help (buf, scanswit, 1);
		    return;

		case SCCLR: 
		    clearsw++;
		    continue;
		case SCNCLR: 
		    clearsw = 0;
		    continue;
		case SCHEAD: 
		    headersw++;
		    continue;
		case SCNHEAD: 
		    headersw = 0;
		    continue;
		case SCFORM: 
		    if (!(form = *args++) || *form == '-') {
			advise (NULL, "missing argument to %s", args[-2]);
			return;
		    }
		    format = NULL;
		    continue;
		case SCFMT: 
		    if (!(format = *args++) || *format == '-') {
			advise (NULL, "missing argument to %s", args[-2]);
			return;
		    }
		    form = NULL;
		    continue;
		case SCWID: 
		    if (!(cp = *args++) || *cp == '-') {
			advise (NULL, "missing argument to %s", args[-2]);
			return;
		    }
		    width = atoi (cp);
		    continue;
	    }
	if (*cp == '+' || *cp == '@') {
	    advise (NULL, "sorry, no folders allowed!");
	    return;
	}
	else
	    msgs[msgp++] = cp;
    }

    if (!msgp)
	msgs[msgp++] = "all";
    for (msgnum = 0; msgnum < msgp; msgnum++)
	if (!m_convert (mp, msgs[msgnum]))
	    return;
    seq_setprev (mp);

    /* Get new format string */
    nfs = new_fs (form, format, FORMAT);

    /* force scansbr to (re)compile format */
    if (scanl) {
	free (scanl);
	scanl = NULL;
    }

    if (s_optim == 0) {
	s_optim = optim = 1;
	s_form = form ? getcpy (form) : NULL;
	s_format = format ? getcpy (format) : NULL;

    }
    else
	optim = equiv (s_form, form) && equiv (s_format, format);

    interrupted = 0;
    for (msgnum = mp->lowsel;
	    msgnum <= mp->hghsel && !interrupted;
	    msgnum++)
	if (is_selected (mp, msgnum)) {
	    if (optim && Msgs[msgnum].m_scanl)
		printf ("%s", Msgs[msgnum].m_scanl);
	    else {

		zp = msh_ready (msgnum, 0);
		switch (state = scan (zp, msgnum, 0, nfs, width,
			msgnum == mp->curmsg,
			is_unseen (mp, msgnum),
			headersw ? (fmsh ? fmsh : mp->foldpath) : NULL,
			fmsh ? 0L : (long) (Msgs[msgnum].m_stop - Msgs[msgnum].m_start),
			1)) {
		    case SCNMSG:
		    case SCNENC:
		    case SCNERR:
			if (optim)
			    Msgs[msgnum].m_scanl = getcpy (scanl);
			break;

		    default:
			advise (NULL, "scan() botch (%d)", state);
			return;

		    case SCNEOF:
			printf ("%*d  empty\n", DMAXFOLDER, msgnum);
			break;
		    }
	    }
	    headersw = 0;
	}

    if (clearsw)
	nmh_clear_screen ();
}


#define SHOW_SWITCHES \
    X("draft", 5, SHDRAFT) \
    X("form formfile", 4, SHFORM) \
    X("moreproc program", 4, SHPROG) \
    X("nomoreproc", 3, SHNPROG) \
    X("length lines", 4, SHLEN) \
    X("width columns", 4, SHWID) \
    X("showproc program", 4, SHSHOW) \
    X("noshowproc", 3, SHNSHOW) \
    X("header", 4, SHHEAD) \
    X("noheader", 3, SHNHEAD) \
    X("help", 0, SHHELP) \

#define X(sw, minchars, id) id,
DEFINE_SWITCH_ENUM(SHOW);
#undef X

#define X(sw, minchars, id) { sw, minchars, id },
DEFINE_SWITCH_ARRAY(SHOW, showswit);
#undef X


void
showcmd (char **args)
{
    int	headersw = 1, nshow = 0, msgp = 0, vecp = 1;
    int mhl = 0, seqnum = -1, mode = 0, i, msgnum;
    char *cp, *proc = showproc, buf[BUFSIZ];
    char *msgs[MAXARGS], *vec[MAXARGS];

    if (!strcasecmp (cmd_name, "next"))
	mode = 1;
    else
	if (!strcasecmp (cmd_name, "prev"))
	    mode = -1;
    while ((cp = *args++)) {
	if (*cp == '-')
	    switch (i = smatch (++cp, showswit)) {
		case AMBIGSW: 
		    ambigsw (cp, showswit);
		    return;
		case UNKWNSW: 
		case SHNPROG:
		    vec[vecp++] = --cp;
		    continue;
		case SHHELP: 
		    snprintf (buf, sizeof(buf), "%s %s[switches] [switches for showproc]",
			    cmd_name, mode ? NULL : "[msgs] ");
		    print_help (buf, showswit, 1);
		    return;

		case SHFORM: 
		case SHPROG:
		case SHLEN:
		case SHWID:
		    vec[vecp++] = --cp;
		    if (!(cp = *args++) || *cp == '-') {
			advise (NULL, "missing argument to %s", args[-2]);
			return;
		    }
		    vec[vecp++] = cp;
		    continue;
		case SHHEAD: 
		    headersw++;
		    continue;
		case SHNHEAD: 
		    headersw = 0;
		    continue;
		case SHSHOW: 
		    if (!(proc = *args++) || *proc == '-') {
			advise (NULL, "missing argument to %s", args[-2]);
			return;
		    }
		    nshow = 0;
		    continue;
		case SHNSHOW: 
		    nshow++;
		    continue;

		case SHDRAFT: 
		    advise (NULL, "sorry, -%s not allowed!", showswit[i].sw);
		    return;
	    }
	if (*cp == '+' || *cp == '@') {
	    advise (NULL, "sorry, no folders allowed!");
	    return;
	}
	else
	    if (mode) {
		fprintf (stderr,
			"usage: %s [switches] [switches for showproc]\n",
			cmd_name);
		return;
	    }
	    else
		msgs[msgp++] = cp;
    }
    vec[vecp] = NULL;

    if (!msgp)
	msgs[msgp++] = mode > 0 ? "next" : mode < 0 ? "prev" : "cur";
    for (msgnum = 0; msgnum < msgp; msgnum++)
	if (!m_convert (mp, msgs[msgnum]))
	    return;
    seq_setprev (mp);

    if (!nshow)
	for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++)
	    if (is_selected (mp, msgnum) && is_nontext (msgnum)) {
		proc = showmimeproc;
		vec[vecp++] = "-file";
		vec[vecp] = NULL;
		goto finish;
	    }

    if (nshow)
	proc = catproc;
    else
	if (strcmp (showproc, "mhl") == 0) {
	    proc = mhlproc;
	    mhl++;
	}

finish: ;
    seqnum = seq_getnum (mp, "unseen");
    vec[0] = r1bindex (proc, '/');
    if (mhl) {
	msgp = vecp;
	for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++)
	    if (is_selected (mp, msgnum)) {
		vec[vecp++] = getcpy (m_name (msgnum));
		if (seqnum != -1)
		    seq_delmsg (mp, "unseen", msgnum);
	    }
	vec[vecp] = NULL;
	if (mp->numsel == 1 && headersw)
	    show (mp->lowsel);
	mhlsbr (vecp, vec, mhl_action);
	scan_eom_action ((int (*)()) 0);
	while (msgp < vecp)
	    free (vec[msgp++]);
    } else {
	interrupted = 0;
	for (msgnum = mp->lowsel;
		msgnum <= mp->hghsel && !interrupted;
		msgnum++)
	    if (is_selected (mp, msgnum)) {
		switch (ask (msgnum)) {
		    case NOTOK: /* QUIT */
			break;

		    case OK: 	/* INTR */
			continue;

		    default:
			if (mp->numsel == 1 && headersw)
			    show (msgnum);
			if (nshow)
			    copy_message (msgnum, stdout);
			else
			    process (msgnum, proc, vecp, vec);

			if (seqnum != -1)
			    seq_delmsg (mp, "unseen", msgnum);
			continue;
		}
		break;
	    }
    }

    seq_setcur (mp, mp->hghsel);
}


static void
show (int msgnum)
{
    if (Msgs[msgnum].m_bboard_id == 0)
	readid (msgnum);

    printf ("(Message %d", msgnum);
    if (Msgs[msgnum].m_bboard_id > 0)
	printf (", %s: %d", BBoard_ID, Msgs[msgnum].m_bboard_id);
    printf (")\n");
}



static int
eom_action (int c)
{
    NMH_UNUSED (c);

    return (ftell (mhlfp) >= Msgs[mhlnum].m_stop);
}


static FILE *
mhl_action (char *name)
{
    int msgnum;

    if ((msgnum = m_atoi (name)) < mp->lowmsg
	    || msgnum > mp->hghmsg
	    || !does_exist (mp, msgnum))
	return NULL;
    mhlnum = msgnum;

    mhlfp = msh_ready (msgnum, 1);
    if (!fmsh)
	scan_eom_action (eom_action);

    return mhlfp;
}



static int
ask (int msgnum)
{
    char buf[BUFSIZ];

    if (mp->numsel == 1 || !interactive || redirected)
	return DONE;

    if (SOprintf ("Press <return> to list \"%d\"...", msgnum)) {
	if (mp->lowsel != msgnum)
	    printf ("\n\n\n");
	printf ("Press <return> to list \"%d\"...", msgnum);
    }
    fflush (stdout);
    buf[0] = 0;

    read (fileno (stdout), buf, sizeof buf);

    if (strchr(buf, '\n') == NULL)
	putchar ('\n');

    if (told_to_quit) {
	told_to_quit = interrupted = 0;
	return NOTOK;
    }
    if (interrupted) {
	interrupted = 0;
	return OK;
    }

    return DONE;
}


#include <h/mime.h>

static int
is_nontext (int msgnum)
{
    int	result, state;
    char *bp, *dp, *cp;
    char buf[BUFSIZ], name[NAMESZ];
    FILE *fp;

    if (Msgs[msgnum].m_flags & MHNCHK)
	return (Msgs[msgnum].m_flags & MHNYES);
    Msgs[msgnum].m_flags |= MHNCHK;

    fp = msh_ready (msgnum, 1);

    for (;;) {
	int bufsz = sizeof buf;
	switch (state = m_getfld (&gstate, name, buf, &bufsz, fp)) {
	case FLD:
	case FLDPLUS:
	    /*
	     * Check Content-Type field
	     */
	    if (!strcasecmp (name, TYPE_FIELD)) {
		int passno;
		char c;

		cp = add (buf, NULL);
		while (state == FLDPLUS) {
		    bufsz = sizeof buf;
		    state = m_getfld (&gstate, name, buf, &bufsz, fp);
		    cp = add (buf, cp);
		}
		bp = cp;
		passno = 1;

again:
		for (; isspace ((unsigned char) *bp); bp++)
		    continue;
		if (*bp == '(') {
		    int	i;

		    for (bp++, i = 0;;) {
			switch (*bp++) {
			case '\0':
invalid:
			    result = 0;
			    goto out;
			case '\\':
			    if (*bp++ == '\0')
				goto invalid;
			    continue;
			case '(':
			    i++;
			    /* and fall... */
			default:
			    continue;
			case ')':
			    if (--i < 0)
				break;
			    continue;
			}
			break;
		    }
		}
		if (passno == 2) {
		    if (*bp != '/')
			goto invalid;
		    bp++;
		    passno = 3;
		    goto again;
		}
		for (dp = bp; istoken (*dp); dp++)
		    continue;
		c = *dp;
		*dp = '\0';
		if (!*bp)
		    goto invalid;
		if (passno > 1) {
		    if ((result = (strcasecmp (bp, "plain") != 0)))
			goto out;
		    *dp = c;
		    for (dp++; isspace ((unsigned char) *dp); dp++)
			continue;
		    if (*dp) {
			if ((result = !uprf (dp, "charset")))
			    goto out;
			dp += sizeof "charset" - 1;
			while (isspace ((unsigned char) *dp))
			    dp++;
			if (*dp++ != '=')
			    goto invalid;
			while (isspace ((unsigned char) *dp))
			    dp++;
			if (*dp == '"') {
			    if ((bp = strchr(++dp, '"')))
				*bp = '\0';
			} else {
			    for (bp = dp; *bp; bp++)
				if (isspace ((unsigned char) *bp)) {
				    *bp = '\0';
				    break;
				}
			}
		    } else {
			/* Default character set */
			dp = "US-ASCII";
		    }
		    /* Check the character set */
		    result = !check_charset (dp, strlen (dp));
		} else {
		    if (!(result = (strcasecmp (bp, "text") != 0))) {
			*dp = c;
			bp = dp;
			passno = 2;
			goto again;
		    }
		}
out:
		free (cp);
		if (result) {
		    Msgs[msgnum].m_flags |= MHNYES;
		    return result;
		}
		break;
	    }

	    /*
	     * Check Content-Transfer-Encoding field
	     */
	    if (!strcasecmp (name, ENCODING_FIELD)) {
		cp = add (buf, NULL);
		while (state == FLDPLUS) {
		    bufsz = sizeof buf;
		    state = m_getfld (&gstate, name, buf, &bufsz, fp);
		    cp = add (buf, cp);
		}
		for (bp = cp; isspace ((unsigned char) *bp); bp++)
		    continue;
		for (dp = bp; istoken (*dp); dp++)
		    continue;
		*dp = '\0';
		result = (strcasecmp (bp, "7bit")
		       && strcasecmp (bp, "8bit")
		       && strcasecmp (bp, "binary"));

		free (cp);
		if (result) {
		    Msgs[msgnum].m_flags |= MHNYES;
		    return result;
		}
		break;
	    }

	    /*
	     * Just skip the rest of this header
	     * field and go to next one.
	     */
	    while (state == FLDPLUS) {
		bufsz = sizeof buf;
		state = m_getfld (&gstate, name, buf, &bufsz, fp);
	    }
	    break;

	    /*
	     * We've passed the message header,
	     * so message is just text.
	     */
	default:
	    return 0;
	}
    }
}


#define SORT_SWITCHES \
    X("datefield field", 0, SODATE) \
    X("textfield field", 0, SOSUBJ) \
    X("notextfield", 0, SONSUBJ) \
    X("limit days", 0, SOLIMT) \
    X("nolimit", 0, SONLIMT) \
    X("verbose", 0, SOVERB) \
    X("noverbose", 0, SONVERB) \
    X("help", 0, SOHELP) \

#define X(sw, minchars, id) id,
DEFINE_SWITCH_ENUM(SORT);
#undef X

#define X(sw, minchars, id) { sw, minchars, id },
DEFINE_SWITCH_ARRAY(SORT, sortswit);
#undef X


void
sortcmd (char **args)
{
    int msgp = 0, msgnum;
    char *cp, *datesw = NULL, *subjsw = NULL;
    char buf[BUFSIZ], *msgs[MAXARGS];
    struct tws tb;

    if (fmsh) {
	forkcmd (args, cmd_name);
	return;
    }

    while ((cp = *args++)) {
	if (*cp == '-')
	    switch (smatch (++cp, sortswit)) {
		case AMBIGSW: 
		    ambigsw (cp, sortswit);
		    return;
		case UNKWNSW: 
		    fprintf (stderr, "-%s unknown\n", cp);
		    return;
		case SOHELP: 
		    snprintf (buf, sizeof(buf), "%s [msgs] [switches]", cmd_name);
		    print_help (buf, sortswit, 1);
		    return;

		case SODATE: 
		    if (datesw) {
			advise (NULL, "only one date field at a time!");
			return;
		    }
		    if (!(datesw = *args++) || *datesw == '-') {
			advise (NULL, "missing argument to %s", args[-2]);
			return;
		    }
		    continue;

		case SOSUBJ:
		    if (subjsw) {
			advise (NULL, "only one text field at a time!");
			return;
		    }
		    if (!(subjsw = *args++) || *subjsw == '-') {
			advise (NULL, "missing argument to %s", args[-2]);
			return;
		    }
		    continue;
		case SONSUBJ:
		    subjsw = (char *)0;
		    continue;

		case SOLIMT:		/* too hard */
		    if (!(cp = *args++) || *cp == '-') {
			advise (NULL, "missing argument to %s", args[-2]);
			return;
		    }
		case SONLIMT:
		case SOVERB: 		/* not implemented */
		case SONVERB: 
		    continue;
	    }
	if (*cp == '+' || *cp == '@') {
	    advise (NULL, "sorry, no folders allowed!");
	    return;
	}
	else
	    msgs[msgp++] = cp;
    }

    if (!msgp)
	msgs[msgp++] = "all";
    if (!datesw)
	datesw = "Date";
    for (msgnum = 0; msgnum < msgp; msgnum++)
	if (!m_convert (mp, msgs[msgnum]))
	    return;
    seq_setprev (mp);

    twscopy (&tb, dlocaltimenow ());

    for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
	if (Msgs[msgnum].m_scanl) {
	    free (Msgs[msgnum].m_scanl);
	    Msgs[msgnum].m_scanl = NULL;
	}
	if (is_selected (mp, msgnum)) {
	    if (get_fields (datesw, subjsw, msgnum, &Msgs[msgnum]))
		twscopy (&Msgs[msgnum].m_tb,
			msgnum != mp->lowsel ? &Msgs[msgnum - 1].m_tb : &tb);
	}
	else			/* m_scaln is already NULL */
	    twscopy (&Msgs[msgnum].m_tb, &tb);
	Msgs[msgnum].m_stats = bvector_create (0);
	bvector_copy (Msgs[msgnum].m_stats, msgstat (mp, msgnum));
	if (mp->curmsg == msgnum)
            bvector_set (Msgs[msgnum].m_stats, CUR);
    }

    qsort ((char *) &Msgs[mp->lowsel], mp->hghsel - mp->lowsel + 1,
	   sizeof(struct Msg), (qsort_comp) (subjsw ? subsort : msgsort));

    for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
	if (subjsw && Msgs[msgnum].m_scanl) {
	    free (Msgs[msgnum].m_scanl);	/* from subjsort */
	    Msgs[msgnum].m_scanl = NULL;
	}
	bvector_clear (Msgs[msgnum].m_stats, CUR);
	bvector_copy (msgstat (mp, msgnum), Msgs[msgnum].m_stats);
	if (bvector_at (Msgs[msgnum].m_stats, CUR))
	    seq_setcur (mp, msgnum);
    }
	    
    mp->msgflags |= MODIFIED;
    modified++;
}


/* 
 * get_fields - parse message, and get date and subject if needed.
 * We'll use the msgp->m_tb tws struct for the date, and overload
 * the msgp->m_scanl field with our subject string.
 */
static int
get_fields (char *datesw, char *subjsw, int msgnum, struct Msg *msgp)
{
    int	state, gotdate = 0;
    char *bp, buf[BUFSIZ], name[NAMESZ];
    struct tws *tw = (struct tws *) 0;
    register FILE *zp;

    zp = msh_ready (msgnum, 0);

    for (;;) {
	int bufsz = sizeof buf;
	switch (state = m_getfld (&gstate, name, buf, &bufsz, zp)) {
	    case FLD: 
	    case FLDPLUS: 
		if (!strcasecmp (name, datesw)) {
		    bp = getcpy (buf);
		    while (state == FLDPLUS) {
			bufsz = sizeof buf;
			state = m_getfld (&gstate, name, buf, &bufsz, zp);
			bp = add (buf, bp);
		    }
		    if ((tw = dparsetime (bp)) == NULL)
			admonish (NULL,
				"unable to parse %s field in message %d",
				datesw, msgnum);
		    else
			twscopy (&(msgp->m_tb), tw);
		    free (bp);
		    if (!subjsw)	/* not using this, or already done */
			break;		/* all done! */
		    gotdate++;
		}
		else if (subjsw && !strcasecmp(name, subjsw)) {
		    bp = getcpy (buf);
		    while (state == FLDPLUS) {
			bufsz = sizeof buf;
			state = m_getfld (&gstate, name, buf, &bufsz, zp);
			bp = add (buf, bp);
		    }
		    msgp->m_scanl = sosmash(subjsw, bp);
		    if (gotdate)
			break;		/* date done so we're done */
		    else
			subjsw = (char *)0;/* subject done, need date */
		} else {
		    while (state == FLDPLUS) {	/* flush this one */
			bufsz = sizeof buf;
			state = m_getfld (&gstate, name, buf, &bufsz, zp);
		    }
		}
		continue;

	    case BODY: 
	    case FILEEOF: 
		break;

	    case LENERR: 
	    case FMTERR: 
		admonish (NULL, "format error in message %d", msgnum);
		if (msgp->m_scanl) {	/* this might need free'd */
		    free (msgp->m_scanl); /* probably can't use subj anyway */
		    msgp->m_scanl = NULL;
		}
		return NOTOK;

	    default: 
		adios (NULL, "internal error -- you lose");
	}
	break;
    }
    if (tw)
	return OK;	/* not an error if subj not found */

    admonish (NULL, "no %s field in message %d", datesw, msgnum);
    return NOTOK;	/* NOTOK means use some other date */
}


/*
 * sort routines
 */

static int
msgsort (struct Msg *a, struct Msg *b)
{
    return twsort (&a->m_tb, &b->m_tb);
}


static int
subsort (struct Msg *a, struct Msg *b)
{
	register int i;

	if (a->m_scanl && b->m_scanl)
	    if ((i = strcmp (a->m_scanl, b->m_scanl)))
		return (i);

	return twsort (&a->m_tb, &b->m_tb);
}


/*
 * try to make the subject "canonical": delete leading "re:", everything
 * but letters & smash letters to lower case. 
 */
static char *
sosmash (char *subj, char *s)
{
    register char *cp, *dp;
    register unsigned char c;

    if (s) {
	cp = s;
	dp = s;	/* dst pointer */
	if (!strcasecmp (subj, "subject"))
	    while ((c = *cp)) {
		if (! isspace(c)) {
		    if(uprf(cp, "re:"))
			cp += 2;
		    else {
			if (isalnum(c))
			    *dp++ = isupper(c) ? tolower(c) : c;
			break;
		    }
		}
		cp++;
	    }
	while ((c = *cp++)) {
	    if (isalnum(c))
		*dp++ = isupper(c) ? tolower(c) : c;

	}
	*dp = '\0';
    }
    return s;
}


static int
process (int msgnum, char *proc, int vecp, char **vec)
{
    int	child_id, status;
    char tmpfil[BUFSIZ];
    FILE *out;
    char *cp;

    if (fmsh) {
	strncpy (tmpfil, m_name (msgnum), sizeof(tmpfil));
	context_del (pfolder);
	context_replace (pfolder, fmsh);/* update current folder   */
	seq_save (mp);
	context_save ();		/* save the context file   */
	goto ready;
    }

    cp = m_mktemp(invo_name, NULL, &out);
    if (cp == NULL) {
        /* Try again, but try to create under /tmp */
	int olderr = errno;
        cp = m_mktemp2(NULL, invo_name, NULL, &out);
        if (cp == NULL) {
	    errno = olderr;
	    advise (NULL, "unable to create temporary file");
	    return NOTOK;
	}
    }
    copy_message (msgnum, out);
    fclose (out);
    strncpy(tmpfil, cp, sizeof(tmpfil));

ready: ;
    fflush (stdout);
    switch (child_id = fork ()) {
	case NOTOK: 
	    advise ("fork", "unable to");
	    status = NOTOK;
	    break;
	    
	case OK: 
	    closefds (3);
	    SIGNAL (SIGINT, istat);
	    SIGNAL (SIGQUIT, qstat);

	    vec[vecp++] = tmpfil;
	    vec[vecp] = NULL;

	    execvp (proc, vec);
	    fprintf (stderr, "unable to exec ");
	    perror (proc);
	    _exit (1);

	default: 
	    status = pidXwait (child_id, NULL);
	    break;
    }

    if (!fmsh)
	(void) m_unlink (tmpfil);
    return status;
}


static void
copy_message (int msgnum, FILE *out)
{
    long pos;
    static char buffer[BUFSIZ];
    register FILE *zp;

    zp = msh_ready (msgnum, 1);
    if (fmsh) {
	while (fgets (buffer, sizeof buffer, zp) != NULL) {
	    fputs (buffer, out);
	    if (interrupted && out == stdout)
		break;
	}
    }
    else {
	pos = ftell (zp);
	while (fgets (buffer, sizeof buffer, zp) != NULL
		&& pos < Msgs[msgnum].m_stop) {
	    fputs (buffer, out);
	    pos += (long) strlen (buffer);
	    if (interrupted && out == stdout)
		break;
	}
    }
}


static void
copy_digest (int msgnum, FILE *out)
{
    char c;
    long pos = 0L;
    static char buffer[BUFSIZ];
    register FILE *zp;

    c = '\n';
    zp = msh_ready (msgnum, 1);
    if (!fmsh)
	pos = ftell (zp);
    while (fgets (buffer, sizeof buffer, zp) != NULL
	    && !fmsh && pos < Msgs[msgnum].m_stop) {
	if (c == '\n' && *buffer == '-')
	    fputc (' ', out);
	fputs (buffer, out);
	c = buffer[strlen (buffer) - 1];
	if (!fmsh)
	    pos += (long) strlen (buffer);
	if (interrupted && out == stdout)
	    break;
    }
}
