
/*
 * msh.c -- The nmh shell
 *
 * 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.
 */

/*
 * TODO:
 *    Keep more status information in maildrop map
 */

#include <h/mh.h>
#include <fcntl.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/utils.h>

#include <termios.h>

#include <pwd.h>
#include <setjmp.h>
#include <h/msh.h>
#include <h/vmhsbr.h>

#define	QUOTE	'\\'		/* sigh */

#define MSH_SWITCHES \
    X("idstart number", -7, IDSW) /* interface from bbc */ \
    X("idstop number", -6, FDSW) /*  .. */ \
    X("idquit number", -6, QDSW) /*  .. */ \
    X("idname BBoard", -6, NMSW) /*  .. */ \
    X("prompt string", 0, PRMPTSW) \
    X("scan", 0, SCANSW) \
    X("noscan", 0, NSCANSW) \
    X("vmhread fd", -7, READSW) \
    X("vmhwrite fd", -8, WRITESW) \
    X("popread fd", -7, PREADSW) \
    X("popwrite fd", -8, PWRITSW) \
    X("topcur", 0, TCURSW) \
    X("notopcur", 0, NTCURSW) \
    X("version", 0, VERSIONSW) \
    X("help", 0, HELPSW) \

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

#define X(sw, minchars, id) { sw, minchars, id },
DEFINE_SWITCH_ARRAY(MSH, switches);
#undef X

static int mbx_style = MMDF_FORMAT;

/*
 * FOLDER
 */
char*fmsh = NULL;			/* folder instead of file              */
int modified;				/* command modified folder             */
struct msgs *mp;			/* used a lot                          */
static int nMsgs = 0;
struct Msg *Msgs = NULL;		/* Msgs[0] not used                    */
static FILE *fp;			/* input file                          */
static FILE *yp = NULL;			/* temporary file                      */
static int mode;			/* mode of file                        */
static int numfds = 0;			/* number of files cached              */
static int maxfds = 0;			/* number of files cached to be cached */
static time_t mtime = (time_t) 0;	/* mtime of file                       */

/*
 * VMH
 */
#define	ALARM	((unsigned int) 10)
#define	ttyN(c)	ttyNaux ((c), NULL)

static int vmh = 0;

static int vmhpid = OK;
static int vmhfd0;
static int vmhfd1;
static int vmhfd2;

static int vmhtty = NOTOK;

#define	SCAN	1
#define	STATUS	2
#define	DISPLAY	3
#define	NWIN	DISPLAY

static int topcur = 0;

static int numwins = 0;
static int windows[NWIN + 1];

static jmp_buf peerenv;

/*
 * PARENT
 */
static int pfd = NOTOK;		/* fd parent is reading from */
static int ppid = 0;		/* pid of parent             */

/*
 * COMMAND
 */
int interactive;		/* running from a /dev/tty */
int redirected;			/* re-directing output     */
FILE *sp = NULL;		/* original stdout         */

char *cmd_name;			/* command being run   */
char myfilter[BUFSIZ];		/* path to mhl.forward */

static char *myprompt = "(%s) ";/* prompting string */

/*
 * BBOARDS
 */
static int gap;			/* gap in BBoard-ID:s */
static char *myname = NULL;	/* BBoard name        */
char *BBoard_ID = "BBoard-ID";	/* BBoard-ID constant */

/*
 * SIGNALS
 */
SIGNAL_HANDLER istat;		/* original SIGINT  */
static SIGNAL_HANDLER pstat;	/* current SIGPIPE  */
SIGNAL_HANDLER qstat;		/* original SIGQUIT */

#ifdef SIGTSTP
SIGNAL_HANDLER tstat;		/* original SIGTSTP */
#endif

int interrupted;		/* SIGINT detected  */
int broken_pipe;		/* SIGPIPE detected */
int told_to_quit;		/* SIGQUIT detected */

/*
 * prototypes
 */
void fsetup (char *);
void setup (char *);
void readids (int);
int readid (int);
void display_info (int);
int expand (char *);
void m_reset (void);
void seq_setcur (struct msgs *, int);
void padios (char *, char *, ...);
void padvise (char *, char *, ...);

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


/*
 * static prototypes
 */
static void msh (int);
static int read_map (char *, long);
static int read_file (long, int);

static void m_gMsgs (int);
static int check_folder (int);
static void scanrange (int, int);
static void scanstring (char *);
static void write_ids (void);
static void quit (void);
static int getargs (char *, struct swit *, struct Cmd *);
static int getcmds (struct swit *, struct Cmd *, int);
static int parse (char *, struct Cmd *);
static int init_io (struct Cmd *, int);
static int initaux_io (struct Cmd *);
static void fin_io (struct Cmd *, int);
static void finaux_io (struct Cmd *);
static void m_init (void);
static void intrser (int);
static void pipeser (int);
static void quitser (int);
static void alrmser (int);
static int pINI (void);
static int pQRY (char *, int);
static int pQRY1 (int);
static int pQRY2 (void);
static int pCMD (char *, struct swit *, struct Cmd *);
static int pFIN (void);
static int peerwait (void);
static int ttyNaux (struct Cmd *, char *);
static int ttyR (struct Cmd *);
static int winN (struct Cmd *, int, int);
static int winR (struct Cmd *);
static int winX (int);


int
main (int argc, char **argv)
{
    int	id = 0, scansw = 0, vmh1 = 0, vmh2 = 0;
    char *cp, *file = NULL, *folder = NULL;
    char **argp, **arguments, buf[BUFSIZ];

    if (nmh_init(argv[0], 1)) { return 1; }

    mts_init (invo_name);
    arguments = getarguments (invo_name, argc,argv, 1);
    argp = arguments;

    while ((cp = *argp++)) {
	if (*cp == '-')
	    switch (smatch (++cp, switches)) {
		case AMBIGSW: 
		    ambigsw (cp, switches);
		    done (1);
		case UNKWNSW: 
		    adios (NULL, "-%s unknown", cp);

		case HELPSW: 
		    snprintf (buf, sizeof(buf), "%s [switches] file", invo_name);
		    print_help (buf, switches, 1);
		    done (0);
		case VERSIONSW:
		    print_version(invo_name);
		    done (0);

		case IDSW: 
		    if (!(cp = *argp++) || *cp == '-')
			adios (NULL, "missing argument to %s", argp[-2]);
		    if ((id = atoi (cp)) < 1)
			adios (NULL, "bad argument %s %s", argp[-2], cp);
		    continue;
		case FDSW: 
		    if (!(cp = *argp++) || *cp == '-')
			adios (NULL, "missing argument to %s", argp[-2]);
		    if ((pfd = atoi (cp)) <= 1)
			adios (NULL, "bad argument %s %s", argp[-2], cp);
		    continue;
		case QDSW: 
		    if (!(cp = *argp++) || *cp == '-')
			adios (NULL, "missing argument to %s", argp[-2]);
		    if ((ppid = atoi (cp)) <= 1)
			adios (NULL, "bad argument %s %s", argp[-2], cp);
		    continue;
		case NMSW:
		    if (!(myname = *argp++) || *myname == '-')
			adios (NULL, "missing argument to %s", argp[-2]);
		    continue;

		case SCANSW: 
		    scansw++;
		    continue;
		case NSCANSW: 
		    scansw = 0;
		    continue;

		case PRMPTSW:
		    if (!(myprompt = *argp++) || *myprompt == '-')
			adios (NULL, "missing argument to %s", argp[-2]);
		    continue;

		case READSW: 
		    if (!(cp = *argp++) || *cp == '-')
			adios (NULL, "missing argument to %s", argp[-2]);
		    if ((vmh1 = atoi (cp)) < 1)
			adios (NULL, "bad argument %s %s", argp[-2], cp);
		    continue;
		case WRITESW: 
		    if (!(cp = *argp++) || *cp == '-')
			adios (NULL, "missing argument to %s", argp[-2]);
		    if ((vmh2 = atoi (cp)) < 1)
			adios (NULL, "bad argument %s %s", argp[-2], cp);
		    continue;

		case PREADSW: 
		    if (!(cp = *argp++) || *cp == '-')
			adios (NULL, "missing argument to %s", argp[-2]);
		    continue;
		case PWRITSW: 
		    if (!(cp = *argp++) || *cp == '-')
			adios (NULL, "missing argument to %s", argp[-2]);
		    continue;

		case TCURSW:
		    topcur++;
		    continue;
		case NTCURSW:
		    topcur = 0;
		    continue;
	    }
	if (*cp == '+' || *cp == '@') {
	    if (folder)
		adios (NULL, "only one folder at a time!");
	    else
		folder = pluspath (cp);
	}
	else
	    if (file)
		adios (NULL, "only one file at a time!");
	    else
		file = cp;
    }

    if (!file && !folder)
	file = "./msgbox";
    if (file && folder)
	adios (NULL, "use a file or a folder, not both");
    strncpy (myfilter, etcpath (mhlforward), sizeof(myfilter));
#ifdef FIOCLEX
    if (pfd > 1)
	ioctl (pfd, FIOCLEX, NULL);
#endif /* FIOCLEX */

    istat = SIGNAL2 (SIGINT, intrser);
    qstat = SIGNAL2 (SIGQUIT, quitser);

    sc_width ();		/* MAGIC... */

    if ((vmh = vmh1 && vmh2)) {
	rcinit (vmh1, vmh2);
	pINI ();
	SIGNAL (SIGINT, SIG_IGN);
	SIGNAL (SIGQUIT, SIG_IGN);
#ifdef SIGTSTP
	tstat = SIGNAL (SIGTSTP, SIG_IGN);
#endif /* SIGTSTP */
    }

    if (folder)
	fsetup (folder);
    else
	setup (file);
    readids (id);
    display_info (id > 0 ? scansw : 0);

    msh (id > 0 ? scansw : 0);
    scan_finished ();

    m_reset ();
    
    done (0);
    return 1;
}


#define MSHCMDS_SWITCHES \
    X("advance", -7, ADVCMD) \
    X("ali", 0, ALICMD) \
    X("burst", 0, EXPLCMD) \
    X("comp", 0, COMPCMD) \
    X("dist", 0, DISTCMD) \
    X("exit", 0, EXITCMD) \
    X("folder", 0, FOLDCMD) \
    X("forw", 0, FORWCMD) \
    X("help", 0, HELPCMD) \
    X("inc", 0, INCMD) \
    X("mark", 0, MARKCMD) \
    X("mhmail", 0, MAILCMD) \
    X("mhn", 0, MHNCMD) \
    X("msgchk", 0, MSGKCMD) \
    X("next", 0, NEXTCMD) \
    X("packf", 0, PACKCMD) \
    X("pick", 0, PICKCMD) \
    X("prev", 0, PREVCMD) \
    X("quit", 0, QUITCMD) \
    X("refile", 0, FILECMD) \
    X("repl", 0, REPLCMD) \
    X("rmm", 0, RMMCMD) \
    X("scan", 0, SCANCMD) \
    X("send", 0, SENDCMD) \
    X("show", 0, SHOWCMD) \
    X("sortm", 0, SORTCMD) \
    X("whatnow", 0, WHATCMD) \
    X("whom", 0, WHOMCMD) \

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

#define X(sw, minchars, id) { sw, minchars, id },
DEFINE_SWITCH_ARRAY(MSHCMDS, mshcmds);
#undef X


static void
msh (int scansw)
{
    int i;
    register char *cp, **ap;
    char prompt[BUFSIZ], *vec[MAXARGS];
    struct Cmd typein;
    register struct Cmd *cmdp;
    static int once_only = ADVCMD;

    snprintf (prompt, sizeof(prompt), myprompt, invo_name);
    cmdp = &typein;

    for (;;) {
	if (yp) {
	    fclose (yp);
	    yp = NULL;
	}
	if (vmh) {
	    if ((i = getcmds (mshcmds, cmdp, scansw)) == EOF) {
		rcdone ();
		return;
	    }
	} else {
	    check_folder (scansw);
	    if ((i = getargs (prompt, mshcmds, cmdp)) == EOF) {
		putchar ('\n');
		return;
	    }
	}
	cmd_name = mshcmds[i].sw;

	switch (i) {
	    case QUITCMD: 
		quit ();
		return;

	    case ADVCMD:
		if (once_only == ADVCMD)
		    once_only = i = SHOWCMD;
		else
		    i = mp->curmsg != mp->hghmsg ? NEXTCMD : EXITCMD;
		cmd_name = mshcmds[i].sw;
		/* and fall... */

	    case EXITCMD:
	    case EXPLCMD: 
	    case FOLDCMD: 
	    case FORWCMD: 	/* sigh */
	    case MARKCMD: 
	    case NEXTCMD: 
	    case PACKCMD: 
	    case PICKCMD: 
	    case PREVCMD: 
	    case RMMCMD: 
	    case SHOWCMD: 
	    case SCANCMD: 
	    case SORTCMD: 
		if ((cp = context_find (cmd_name))) {
		    cp = getcpy (cp);
		    ap = brkstring (cp, " ", "\n");
		    ap = copyip (ap, vec, MAXARGS);
		} else {
		    ap = vec;
		}
		break;

	    default: 
		cp = NULL;
		ap = vec;
		break;
	}
	copyip (cmdp->args + 1, ap, MAXARGS);

	m_init ();

	if (!vmh && init_io (cmdp, vmh) == NOTOK) {
	    if (cp != NULL)
		free (cp);
	    continue;
	}
	modified = 0;
	redirected = vmh || cmdp->direction != STDIO;

	switch (i) {
	    case ALICMD: 
	    case COMPCMD: 
	    case INCMD: 
	    case MAILCMD: 
	    case MSGKCMD: 
	    case SENDCMD: 
	    case WHATCMD: 
	    case WHOMCMD: 
		if (!vmh || ttyN (cmdp) != NOTOK)
		    forkcmd (vec, cmd_name);
		break;

	    case DISTCMD: 
		if (!vmh || ttyN (cmdp) != NOTOK)
		    distcmd (vec);
		break;

	    case EXPLCMD: 
		if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK)
		    explcmd (vec);
		break;

	    case FILECMD: 
		if (!vmh 
			|| (filehak (vec) == OK ? ttyN (cmdp)
					: winN (cmdp, DISPLAY, 1)) != NOTOK)
		    filecmd (vec);
		break;

	    case FOLDCMD: 
		if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK)
		    foldcmd (vec);
		break;

	    case FORWCMD: 
		if (!vmh || ttyN (cmdp) != NOTOK)
		    forwcmd (vec);
		break;

	    case HELPCMD: 
		if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK)
		    helpcmd (vec);
		break;

	    case EXITCMD:
	    case MARKCMD: 
		if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK)
		    markcmd (vec);
		break;

	    case MHNCMD:
		if (!vmh || ttyN (cmdp) != NOTOK)
		    mhncmd (vec);
		break;

	    case NEXTCMD: 
	    case PREVCMD: 
	    case SHOWCMD: 
		if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK)
		    showcmd (vec);
		break;

	    case PACKCMD: 
		if (!vmh 
			|| (packhak (vec) == OK ? ttyN (cmdp)
					: winN (cmdp, DISPLAY, 1)) != NOTOK)
		    packcmd (vec);
		break;

	    case PICKCMD: 
		if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK)
		    pickcmd (vec);
		break;

	    case REPLCMD: 
		if (!vmh || ttyN (cmdp) != NOTOK)
		    replcmd (vec);
		break;

	    case RMMCMD: 
		if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK)
		    rmmcmd (vec);
		break;

	    case SCANCMD: 
		if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK)
		    scancmd (vec);
		break;

	    case SORTCMD: 
		if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK)
		    sortcmd (vec);
		break;

	    default: 
		padios (NULL, "no dispatch for %s", cmd_name);
	}

	if (vmh) {
	    if (vmhtty != NOTOK)
		ttyR (cmdp);
	    if (vmhpid > OK)
		winR (cmdp);
	}
	else
	    fin_io (cmdp, vmh);
	if (cp != NULL)
	    free (cp);
	if (i == EXITCMD) {
	    quit ();
	    return;
	}
    }
}


void
fsetup (char *folder)
{
    register int msgnum;
    char *maildir;
    struct stat st;

    maildir = m_maildir (folder);
    if (chdir (maildir) == NOTOK)
	padios (maildir, "unable to change directory to");

    /* read folder and create message structure */
    if (!(mp = folder_read (folder, 0)))
	padios (NULL, "unable to read folder %s", folder);

    /* check for empty folder */
    if (mp->nummsg == 0)
	padios (NULL, "no messages in %s", folder);

    mode = m_gmprot ();
    mtime = stat (mp->foldpath, &st) != NOTOK ? st.st_mtime : 0;

    m_gMsgs (mp->hghmsg);

    for (msgnum = mp->lowmsg; msgnum <= mp->hghmsg; msgnum++) {
	Msgs[msgnum].m_bboard_id = 0;
	Msgs[msgnum].m_top = NOTOK;
	Msgs[msgnum].m_start = Msgs[msgnum].m_stop = 0L;
	Msgs[msgnum].m_scanl = NULL;
    }

    m_init ();

    fmsh = getcpy (folder);

    maxfds = OPEN_MAX / 2;

    if ((maxfds -= 2) < 1)
	maxfds = 1;
}


void
setup (char *file)
{
    int i, msgp;
    struct stat st;
    if ((fp = fopen (file, "r")) == NULL)
	padios (file, "unable to read");
#ifdef FIOCLEX
    ioctl (fileno (fp), FIOCLEX, NULL);
#endif /* FIOCLEX */
    if (fstat (fileno (fp), &st) != NOTOK) {
	mode = (int) (st.st_mode & 0777), mtime = st.st_mtime;
	msgp = read_map (file, (long) st.st_size);
    }
    else {
	mode = m_gmprot (), mtime = 0;
	msgp = 0;
    }

    if ((msgp = read_file (msgp ? Msgs[msgp].m_stop : 0L, msgp + 1)) < 1)
	padios (NULL, "no messages in %s", myname ? myname : file);

    if (!(mp = (struct msgs  *) calloc ((size_t) 1, sizeof(*mp))))
	padios (NULL, "unable to allocate folder storage");

    if (!(mp->msgstats = calloc ((size_t) msgp + 3, sizeof(*(mp->msgstats)))))
	padios (NULL, "unable to allocate message status storage");

    mp->hghmsg = msgp;
    mp->nummsg = msgp;
    mp->lowmsg = 1;
    mp->curmsg = 0;
    mp->foldpath = getcpy (myname ? myname : file);
    clear_folder_flags (mp);

	stat (file, &st);
	if (st.st_uid != getuid () || access (file, W_OK) == NOTOK)
	    set_readonly (mp);

    mp->lowoff = 1;
    mp->hghoff = mp->hghmsg + 1;

    for (i = mp->lowmsg; i <= mp->hghmsg; i++) {
	clear_msg_flags (mp, i);
	set_exists (mp, i);
    }
    m_init ();

    svector_push_back (mp->msgattrs, getcpy ("unseen"));

    scan_detect_mbox_style (fp);		/* the MAGIC invocation */
    if (fmsh) {
	free (fmsh);
	fmsh = NULL;
    }
}


static int
read_map (char *file, long size)
{
    register int i, msgp;
    register struct drop *dp, *mp;
    struct drop *rp;

    if ((i = map_read (file, size, &rp, 1)) == 0)
	return 0;

    m_gMsgs (i);

    msgp = 1;
    for (dp = rp + 1; i-- > 0; msgp++, dp++) {
	mp = &Msgs[msgp].m_drop;
	mp->d_id = dp->d_id;
	mp->d_size = dp->d_size;
	mp->d_start = dp->d_start;
	mp->d_stop = dp->d_stop;
	Msgs[msgp].m_scanl = NULL;
    }
    free ((char *) rp);

    return (msgp - 1);
}


static int
read_file (long pos, int msgp)
{
    register int i;
    register struct drop *dp, *mp;
    struct drop *rp;

    if ((i = mbx_read (fp, pos, &rp, 1)) <= 0)
	return (msgp - 1);

    m_gMsgs ((msgp - 1) + i);

    for (dp = rp; i-- > 0; msgp++, dp++) {
	mp = &Msgs[msgp].m_drop;
	mp->d_id = 0;
	mp->d_size = dp->d_size;
	mp->d_start = dp->d_start;
	mp->d_stop = dp->d_stop;
	Msgs[msgp].m_scanl = NULL;
    }
    free ((char *) rp);

    return (msgp - 1);
}


static void
m_gMsgs (int n)
{
    int	nmsgs;

    if (Msgs == NULL) {
	nMsgs = n + MAXFOLDER / 2;
	Msgs = (struct Msg *) calloc ((size_t) (nMsgs + 2), sizeof *Msgs);
	if (Msgs == NULL)
	    padios (NULL, "unable to allocate Msgs structure");
	return;
    }

    if (nMsgs >= n)
	return;

    nmsgs = nMsgs + n + MAXFOLDER / 2;
    Msgs = (struct Msg *) mh_xrealloc ((char *) Msgs, (size_t) (nmsgs + 2) * sizeof *Msgs);
    memset((char *) (Msgs + nMsgs + 2), 0, (size_t) ((nmsgs - nMsgs) * sizeof *Msgs));

    nMsgs = nmsgs;
}


FILE *
msh_ready (int msgnum, int full)
{
    register int msgp;
    int fd;
    char *cp;
    NMH_UNUSED (full);

    if (yp) {
	fclose (yp);
	yp = NULL;
    }

    if (fmsh) {
	if ((fd = Msgs[msgnum].m_top) == NOTOK) {
	    if (numfds >= maxfds)
		for (msgp = mp->lowmsg; msgp <= mp->hghmsg; msgp++)
		    if (Msgs[msgp].m_top != NOTOK) {
			close (Msgs[msgp].m_top);
			Msgs[msgp].m_top = NOTOK;
			numfds--;
			break;
		    }

	    if ((fd = open (cp = m_name (msgnum), O_RDONLY)) == NOTOK)
		padios (cp, "unable to open message");
	    Msgs[msgnum].m_top = fd;
	    numfds++;
	}

	if ((fd = dup (fd)) == NOTOK)
	    padios ("cached message", "unable to dup");
	if ((yp = fdopen (fd, "r")) == NULL)
	    padios (NULL, "unable to fdopen cached message");
	fseek (yp, 0L, SEEK_SET);
	return yp;
    }

    scan_reset_m_getfld_state ();
    scan_eom_action ((int (*)()) 0);	/* XXX */
    fseek (fp, Msgs[msgnum].m_start, SEEK_SET);
    return fp;
}


static int
check_folder (int scansw)
{
    int seqnum, i, low, hgh, msgp;
    struct stat st;

    if (fmsh) {
	if (stat (mp->foldpath, &st) == NOTOK)
	    padios (mp->foldpath, "unable to stat");
	if (mtime == st.st_mtime)
	    return 0;
	mtime = st.st_mtime;

	low = mp->hghmsg + 1;
	folder_free (mp);		/* free folder/message structure */

	if (!(mp = folder_read (fmsh, 0)))
	    padios (NULL, "unable to re-read folder %s", fmsh);

	hgh = mp->hghmsg;

	for (msgp = mp->lowmsg; msgp <= mp->hghmsg; msgp++) {
	    if (Msgs[msgp].m_top != NOTOK) {
		close (Msgs[msgp].m_top);
		Msgs[msgp].m_top = NOTOK;
		numfds--;
	    }
	    if (Msgs[msgp].m_scanl) {
		free (Msgs[msgp].m_scanl);
		Msgs[msgp].m_scanl = NULL;
	    }
	}

	m_init ();

	if (modified || low > hgh)
	    return 1;
	goto check_vmh;
    }
    if (fstat (fileno (fp), &st) == NOTOK)
	padios (mp->foldpath, "unable to fstat");
    if (mtime == st.st_mtime)
	return 0;
    mode = (int) (st.st_mode & 0777);
    mtime = st.st_mtime;

    if ((msgp = read_file (Msgs[mp->hghmsg].m_stop, mp->hghmsg + 1)) < 1)
	padios (NULL, "no messages in %s", mp->foldpath);	/* XXX */
    if (msgp >= MAXFOLDER)
	padios (NULL, "more than %d messages in %s", MAXFOLDER,
		mp->foldpath);
    if (msgp <= mp->hghmsg)
	return 0;		/* XXX */

    if (!(mp = folder_realloc (mp, mp->lowoff, msgp)))
	padios (NULL, "unable to allocate folder storage");

    low = mp->hghmsg + 1, hgh = msgp;
    seqnum = scansw ? seq_getnum (mp, "unseen") : -1;
    for (i = mp->hghmsg + 1; i <= msgp; i++) {
	set_exists(mp, i);
	if (seqnum != -1)
	    add_sequence(mp, seqnum, i);
	mp->nummsg++;
    }
    mp->hghmsg = msgp;
    m_init ();

check_vmh: ;
    if (vmh)
	return 1;

    advise (NULL, "new messages have arrived!\007");
    if (scansw)
	scanrange (low, hgh);

    return 1;
}


static void
scanrange (int low, int hgh)
{
    char buffer[BUFSIZ];

    snprintf (buffer, sizeof(buffer), "%d-%d", low, hgh);
    scanstring (buffer);
}


static void
scanstring (char *arg)
{
    char *cp, **ap, *vec[MAXARGS];

    /*
     * This should be replace with a call to getarguments()
     */
    if ((cp = context_find (cmd_name = "scan"))) {
	cp = getcpy (cp);
	ap = brkstring (cp, " ", "\n");
	ap = copyip (ap, vec, MAXARGS);
    } else {
	ap = vec;
    }
    *ap++ = arg;
    *ap = NULL;
    m_init ();
    scancmd (vec);
    if (cp != NULL)
	free (cp);
}


void
readids (int id)
{
    register int cur, seqnum, i=0, msgnum;

    if (mp->curmsg == 0)
	seq_setcur (mp, mp->lowmsg);
    if (id <= 0 || (seqnum = seq_getnum (mp, "unseen")) == -1)
	return;

    for (msgnum = mp->hghmsg; msgnum >= mp->lowmsg; msgnum--)
	add_sequence(mp, seqnum, msgnum);

    if (id != 1) {
	cur = mp->curmsg;

	for (msgnum = mp->hghmsg; msgnum >= mp->lowmsg; msgnum--)
	  if (does_exist(mp, msgnum))		/* FIX */
	    if ((i = readid (msgnum)) > 0 && i < id) {
		cur = msgnum + 1;
		clear_sequence(mp, seqnum, msgnum);
		break;
	    }
	for (i = mp->lowmsg; i < msgnum; i++)
	    clear_sequence(mp, seqnum, i);

	if (cur > mp->hghmsg)
	    cur = mp->hghmsg;

	seq_setcur (mp, cur);
    }

    if ((gap = 1 < id && id < (i = readid (mp->lowmsg)) ? id : 0) && !vmh)
	advise (NULL, "gap in ID:s, last seen %d, lowest present %d\n",
		id - 1, i);
}


int
readid (int msgnum)
{
    int i, state;
    char *bp, buf[BUFSIZ], name[NAMESZ];
    register FILE *zp;

    if (Msgs[msgnum].m_bboard_id)
	return Msgs[msgnum].m_bboard_id;

    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, BBoard_ID)) {
		    bp = getcpy (buf);
		    while (state == FLDPLUS) {
			bufsz = sizeof buf;
			state = m_getfld (&gstate, name, buf, &bufsz, zp);
			bp = add (buf, bp);
		    }
		    i = atoi (bp);
		    free (bp);
		    if (i > 0)
			return (Msgs[msgnum].m_bboard_id = i);
		    else
			continue;
		}
		while (state == FLDPLUS) {
		    bufsz = sizeof buf;
		    state = m_getfld (&gstate, name, buf, &bufsz, zp);
		}
		continue;

	    default: 
		return 0;
	}
    }
}


void
display_info (int scansw)
{
    int seqnum, sd;

    interactive = isatty (fileno (stdout));
    if (sp == NULL) {
	if ((sd = dup (fileno (stdout))) == NOTOK)
	    padios ("standard output", "unable to dup");
#ifdef FIOCLEX
	ioctl (sd, FIOCLEX, NULL);
#endif /* FIOCLEX */
	if ((sp = fdopen (sd, "w")) == NULL)
	    padios ("standard output", "unable to fdopen");
    }

    m_putenv ("mhfolder", mp->foldpath);
    if (vmh)
	return;

    if (myname) {
	printf ("Reading ");
	if (SOprintf ("%s", myname))
	    printf ("%s", myname);
	printf (", currently at message %d of %d\n",
		mp->curmsg, mp->hghmsg);
    }
    else {
	printf ("Reading ");
	if (fmsh)
	    printf ("+%s", fmsh);
	else
	    printf ("%s", mp->foldpath);
	printf (", currently at message %d of %d\n",
		mp->curmsg, mp->hghmsg);
    }

    if (((seqnum = seq_getnum (mp, "unseen")) != -1)
	    && scansw
	    && in_sequence(mp, seqnum, mp->hghmsg))
	scanstring ("unseen");
}


static void
write_ids (void)
{
    int i = 0, seqnum, msgnum;
    char buffer[80];

    if (pfd <= 1)
	return;

    if ((seqnum = seq_getnum (mp, "unseen")) != -1)
	for (msgnum = mp->hghmsg; msgnum >= mp->lowmsg; msgnum--)
	    if (!in_sequence(mp, seqnum, msgnum)) {
		if (Msgs[msgnum].m_bboard_id == 0)
		    readid (msgnum);
		if ((i = Msgs[msgnum].m_bboard_id) > 0)
		    break;
	    }

    snprintf (buffer, sizeof(buffer), "%d %d\n", i, Msgs[mp->hghmsg].m_bboard_id);
    write (pfd, buffer, sizeof(buffer));
    close (pfd);
    pfd = NOTOK;
}


static void
quit (void)
{
    int i, md, msgnum;
    char *cp, tmpfil[BUFSIZ];
    char map1[BUFSIZ], map2[BUFSIZ];
    struct stat st;
    FILE *dp;
    int failed_to_lock = 0;

    if (!(mp->msgflags & MODIFIED) || is_readonly(mp) || fmsh) {
	    if (vmh)
		rc2peer (RC_FIN, 0, NULL);
	return;
    }

    if (vmh) 
	ttyNaux (NULLCMD, "FAST");
    cp = NULL;
    if ((dp = lkfopendata (mp->foldpath, "r", &failed_to_lock)) == NULL) {
	advise (mp->foldpath, "unable to lock");
	if (vmh) {
	    ttyR (NULLCMD);
	    pFIN ();
	}	
	return;
    }
    if (fstat (fileno (dp), &st) == NOTOK) {
	advise (mp->foldpath, "unable to stat");
	goto release;
    }
    if (mtime != st.st_mtime) {
	advise (NULL, "new messages have arrived, no update");
	goto release;
    }
    mode = (int) (st.st_mode & 0777);

    if (mp->nummsg == 0) {
	cp = concat ("Zero file \"", mp->foldpath, "\"? ", NULL);
	if (getanswer (cp)) {
	    if ((i = creat (mp->foldpath, mode)) != NOTOK)
		close (i);
	    else
		advise (mp->foldpath, "error zero'ing");
	    (void) m_unlink (map_name (mp->foldpath));/* XXX */
	}
	goto release;
    }

    cp = concat ("Update file \"", mp->foldpath, "\"? ", NULL);
    if (!getanswer (cp))
	goto release;
    strncpy (tmpfil, m_backup (mp->foldpath), sizeof(tmpfil));
    if ((md = mbx_open (tmpfil, mbx_style, st.st_uid, st.st_gid, mode)) == NOTOK) {
	advise (tmpfil, "unable to open");
	goto release;
    }

    for (msgnum = mp->lowmsg; msgnum <= mp->hghmsg; msgnum++)
	if (does_exist(mp, msgnum) && pack (tmpfil, md, msgnum) == NOTOK) {
	    mbx_close (tmpfil, md);
	    (void) m_unlink (tmpfil);
	    (void) m_unlink (map_name (tmpfil));
	    goto release;
	}
    mbx_close (tmpfil, md);

    if (rename (tmpfil, mp->foldpath) == NOTOK)
	admonish (mp->foldpath, "unable to rename %s to", tmpfil);
    else {
	strncpy (map1, map_name (tmpfil), sizeof(map1));
	strncpy (map2, map_name (mp->foldpath), sizeof(map2));

	if (rename (map1, map2) == NOTOK) {
	    admonish (map2, "unable to rename %s to", map1);
	    (void) m_unlink (map1);
	    (void) m_unlink (map2);
	}
    }

release: ;
    if (cp)
	free (cp);
    lkfclosedata (dp, mp->foldpath);
    if (vmh) {
	ttyR (NULLCMD);
	pFIN ();
    }
}


static int
getargs (char *prompt, struct swit *sw, struct Cmd *cmdp)
{
    int i;
    char *cp;
    static char buffer[BUFSIZ];

    told_to_quit = 0;
    for (;;) {
	interrupted = 0;
	if (interactive) {
	    printf ("%s", prompt);
	    fflush (stdout);
	}
	for (cp = buffer; (i = getchar ()) != '\n';) {
	    if (interrupted && !told_to_quit) {
		buffer[0] = '\0';
		putchar ('\n');
		break;
	    }
	    if (told_to_quit || i == EOF) {
		if (ppid > 0)
#ifdef SIGEMT
		    kill (ppid, SIGEMT);
#else
		    kill (ppid, SIGTERM);
#endif
		return EOF;
	    }
	    if (cp < &buffer[sizeof buffer - 2])
		*cp++ = i;
	}
	*cp = 0;

	if (buffer[0] == 0)
	    continue;
	if (buffer[0] == '?') {
	    printf ("commands:\n");
	    print_sw (ALL, sw, "", stdout);
	    printf ("type CTRL-D or use ``quit'' to leave %s\n",
		    invo_name);
	    continue;
	}

	if (parse (buffer, cmdp) == NOTOK)
	    continue;

	switch (i = smatch (cmdp->args[0], sw)) {
	    case AMBIGSW: 
		ambigsw (cmdp->args[0], sw);
		continue;
	    case UNKWNSW: 
		printf ("say what: ``%s'' -- type ? (or help) for help\n",
			cmdp->args[0]);
		continue;
	    default: 
		return i;
	}
    }
}


static int
getcmds (struct swit *sw, struct Cmd *cmdp, int scansw)
{
    int i;
    struct record rcs, *rc;

    rc = &rcs;
    initrc (rc);

    for (;;)
	switch (peer2rc (rc)) {
	    case RC_QRY: 
		pQRY (rc->rc_data, scansw);
		break;

	    case RC_CMD: 
		if ((i = pCMD (rc->rc_data, sw, cmdp)) != NOTOK)
		    return i;
		break;

	    case RC_FIN: 
		if (ppid > 0)
#ifdef SIGEMT
		    kill (ppid, SIGEMT);
#else
		    kill (ppid, SIGTERM);
#endif
		return EOF;

	    case RC_XXX: 
		padios (NULL, "%s", rc->rc_data);

	    default: 
		fmt2peer (RC_ERR, "pLOOP protocol screw-up");
		done (1);
	}
}


static int
parse (char *buffer, struct Cmd *cmdp)
{
    int argp = 0;
    char c, *cp, *pp;

    cmdp->line[0] = 0;
    pp = cmdp->args[argp++] = cmdp->line;
    cmdp->redirect = NULL;
    cmdp->direction = STDIO;
    cmdp->stream = NULL;

    for (cp = buffer; (c = *cp); cp++) {
	if (!isspace ((unsigned char) c))
	    break;
    }
    if (c == '\0') {
	if (vmh)
	    fmt2peer (RC_EOF, "null command");
	return NOTOK;
    }

    while ((c = *cp++)) {
	if (isspace ((unsigned char) c)) {
	    while (isspace ((unsigned char) c))
		c = *cp++;
	    if (c == 0)
		break;
	    *pp++ = 0;
	    cmdp->args[argp++] = pp;
	    *pp = 0;
	}

	switch (c) {
	    case '"': 
		for (;;) {
		    switch (c = *cp++) {
			case 0: 
			    padvise (NULL, "unmatched \"");
			    return NOTOK;
			case '"': 
			    break;
			case QUOTE: 
			    if ((c = *cp++) == 0)
				goto no_quoting;
			default: 
			    *pp++ = c;
			    continue;
		    }
		    break;
		}
		continue;

	    case QUOTE: 
		if ((c = *cp++) == 0) {
	    no_quoting: ;
		    padvise (NULL, "the newline character can not be quoted");
		    return NOTOK;
		}

	    default: ;
		*pp++ = c;
		continue;

	    case '>': 
	    case '|': 
		if (pp == cmdp->line) {
		    padvise (NULL, "invalid null command");
		    return NOTOK;
		}
		if (*cmdp->args[argp - 1] == 0)
		    argp--;
		cmdp->direction = c == '>' ? CRTIO : PIPIO;
		if (cmdp->direction == CRTIO && (c = *cp) == '>') {
		    cmdp->direction = APPIO;
		    cp++;
		}
		cmdp->redirect = pp + 1;/* sigh */
		for (; (c = *cp); cp++)
		    if (!isspace ((unsigned char) c))
			break;
		if (c == 0) {
		    padvise (NULL, cmdp->direction != PIPIO
			    ? "missing name for redirect"
			    : "invalid null command");
		    return NOTOK;
		}
		strcpy (cmdp->redirect, cp);
		if (cmdp->direction != PIPIO) {
		    for (; *cp; cp++)
			if (isspace ((unsigned char) *cp)) {
			    padvise (NULL, "bad name for redirect");
			    return NOTOK;
			}
		    if (expand (cmdp->redirect) == NOTOK)
			return NOTOK;
		}
		break;
	}
	break;
    }

    *pp++ = 0;
    cmdp->args[argp] = NULL;

    return OK;
}


int
expand (char *redirect)
{
    char *cp, *pp;
    char path[BUFSIZ];
    struct passwd  *pw;

    if (*redirect != '~')
	return OK;

    if ((cp = strchr(pp = redirect + 1, '/')))
	*cp++ = 0;
    if (*pp == 0)
	pp = mypath;
    else
	if ((pw = getpwnam (pp)))
	    pp = pw->pw_dir;
	else {
	    padvise (NULL, "unknown user: %s", pp);
	    return NOTOK;
	}

    snprintf (path, sizeof(path), "%s/%s", pp, cp ? cp : "");
    strcpy (redirect, path);
    return OK;
}


static int
init_io (struct Cmd *cmdp, int vio)
{
    int io, result;

    io = vmh;

    vmh = vio;
    result = initaux_io (cmdp);
    vmh = io;

    return result;
}


static int
initaux_io (struct Cmd *cmdp)
{
    char *mode;

    switch (cmdp->direction) {
	case STDIO: 
	    return OK;

	case CRTIO: 
	case APPIO: 
	    mode = cmdp->direction == CRTIO ? "write" : "append";
	    if ((cmdp->stream = fopen (cmdp->redirect, mode)) == NULL) {
		padvise (cmdp->redirect, "unable to %s ", mode);
		cmdp->direction = STDIO;
		return NOTOK;
	    }
	    break;

	case PIPIO: 
	    if ((cmdp->stream = popen (cmdp->redirect, "w")) == NULL) {
		padvise (cmdp->redirect, "unable to pipe");
		cmdp->direction = STDIO;
		return NOTOK;
	    }
	    SIGNAL (SIGPIPE, pipeser);
	    broken_pipe = 0;
	    break;

	default: 
	    padios (NULL, "unknown redirection for command");
    }

    fflush (stdout);
    if (dup2 (fileno (cmdp->stream), fileno (stdout)) == NOTOK)
	padios ("standard output", "unable to dup2");
    clearerr (stdout);

    return OK;
}


static void
fin_io (struct Cmd *cmdp, int vio)
{
    int io;

    io = vmh;
    vmh = vio;
    finaux_io (cmdp);
    vmh = io;
}


static void
finaux_io (struct Cmd *cmdp)
{
    switch (cmdp->direction) {
	case STDIO: 
	    return;

	case CRTIO: 
	case APPIO: 
	    fflush (stdout);
	    close (fileno (stdout));
	    if (ferror (stdout))
		padvise (NULL, "problems writing %s", cmdp->redirect);
	    fclose (cmdp->stream);
	    break;

	case PIPIO: 
	    fflush (stdout);
	    close (fileno (stdout));
	    pclose (cmdp->stream);
	    SIGNAL (SIGPIPE, SIG_DFL);
	    break;

	default: 
	    padios (NULL, "unknown redirection for command");
    }

    if (dup2 (fileno (sp), fileno (stdout)) == NOTOK)
	padios ("standard output", "unable to dup2");
    clearerr (stdout);

    cmdp->direction = STDIO;
}


static void
m_init (void)
{
    int msgnum;

    for (msgnum = mp->lowmsg; msgnum <= mp->hghmsg; msgnum++)
	unset_selected (mp, msgnum);
    mp->lowsel = mp->hghsel = mp->numsel = 0;
}


void
m_reset (void)
{
    write_ids ();
    folder_free (mp);	/* free folder/message structure */
    myname = NULL;
}


void
seq_setcur (struct msgs *mp, int msgnum)
{
    if (mp->curmsg == msgnum)
	return;

    if (mp->curmsg && Msgs[mp->curmsg].m_scanl) {
	free (Msgs[mp->curmsg].m_scanl);
	Msgs[mp->curmsg].m_scanl = NULL;
    }
    if (Msgs[msgnum].m_scanl) {
	free (Msgs[msgnum].m_scanl);
	Msgs[msgnum].m_scanl = NULL;
    }

    mp->curmsg = msgnum;
}



static void
intrser (int i)
{
    NMH_UNUSED (i);
    discard (stdout);
    interrupted++;
}


static void
pipeser (int i)
{
    NMH_UNUSED (i);
    if (broken_pipe++ == 0)
	fprintf (stderr, "broken pipe\n");
    told_to_quit++;
    interrupted++;
}


static void
quitser (int i)
{
    NMH_UNUSED (i);
    told_to_quit++;
    interrupted++;
}


static void
alrmser (int i)
{
    NMH_UNUSED (i);
    longjmp (peerenv, DONE);
}


static int
pINI (void)
{
    int i, vrsn;
    char *bp;
    struct record rcs, *rc;

    rc = &rcs;
    initrc (rc);

    switch (peer2rc (rc)) {
	case RC_INI: 
	    bp = rc->rc_data;
	    while (isspace ((unsigned char) *bp))
		bp++;
	    if (sscanf (bp, "%d", &vrsn) != 1) {
	bad_init: ;
		fmt2peer (RC_ERR, "bad init \"%s\"", rc->rc_data);
		done (1);
	    }
	    if (vrsn != RC_VRSN) {
		fmt2peer (RC_ERR, "version %d unsupported", vrsn);
		done (1);
	    }

	    while (*bp && !isspace ((unsigned char) *bp))
		bp++;
	    while (isspace ((unsigned char) *bp))
		bp++;
	    if (sscanf (bp, "%d", &numwins) != 1 || numwins <= 0)
		goto bad_init;
	    if (numwins > NWIN)
		numwins = NWIN;

	    for (i = 1; i <= numwins; i++) {
		while (*bp && !isspace ((unsigned char) *bp))
		    bp++;
		while (isspace ((unsigned char) *bp))
		    bp++;
		if (sscanf (bp, "%d", &windows[i]) != 1 || windows[i] <= 0)
		    goto bad_init;
	    }
	    rc2peer (RC_ACK, 0, NULL);
	    return OK;

	case RC_XXX: 
	    padios (NULL, "%s", rc->rc_data);

	default: 
	    fmt2peer (RC_ERR, "pINI protocol screw-up");
	    done (1);		/* NOTREACHED */
    }

    return 1;  /* dead code to satisfy the compiler */
}


static int
pQRY (char *str, int scansw)
{
    NMH_UNUSED (str);
    if (pQRY1 (scansw) == NOTOK || pQRY2 () == NOTOK)
	return NOTOK;

    rc2peer (RC_EOF, 0, NULL);
    return OK;
}
	

static int
pQRY1 (int scansw)
{
    int oldhgh;
    static int lastlow = 0,
               lastcur = 0,
               lasthgh = 0,
               lastnum = 0;

    oldhgh = mp->hghmsg;
    if (check_folder (scansw) && oldhgh < mp->hghmsg) {
	switch (winX (STATUS)) {
	    case NOTOK: 
		return NOTOK;

	    case OK: 
		printf ("new messages have arrived!");
		fflush (stdout);
		fflush (stderr);
		_exit (0);	/* NOTREACHED */

	    default: 
		lastlow = lastcur = lasthgh = lastnum = 0;
		break;
	}

	switch (winX (DISPLAY)) {
	    case NOTOK: 
		return NOTOK;

	    case OK: 
		scanrange (oldhgh + 1, mp->hghmsg);
		fflush (stdout);
		fflush (stderr);
		_exit (0);	/* NOTREACHED */

	    default: 
		break;
	}
	return OK;
    }

    if (gap)
	switch (winX (STATUS)) {
	    case NOTOK:
		return NOTOK;

	    case OK:
		printf ("%s: gap in ID:s, last seen %d, lowest present %d\n",
		    myname ? myname : fmsh ? fmsh : mp->foldpath, gap - 1,
		    readid (mp->lowmsg));
		fflush (stdout);
		fflush (stderr);
		_exit (0);	/* NOTREACHED */

	    default:
		gap = 0;
		return OK;
	}

    if (mp->lowmsg != lastlow
	    || mp->curmsg != lastcur
	    || mp->hghmsg != lasthgh
	    || mp->nummsg != lastnum)
	switch (winX (STATUS)) {
	    case NOTOK: 
		return NOTOK;

	    case OK: 
		foldcmd (NULL);
		fflush (stdout);
		fflush (stderr);
		_exit (0);	/* NOTREACHED */

	    default: 
		lastlow = mp->lowmsg;
		lastcur = mp->curmsg;
		lasthgh = mp->hghmsg;
		lastnum = mp->nummsg;
		return OK;
	}

    return OK;
}


static int
pQRY2 (void)
{
    int i, j, k, msgnum, n;
    static int cur = 0,
               num = 0,
               lo = 0,
               hi = 0;

    if (mp->nummsg == 0 && mp->nummsg != num)
	switch (winX (SCAN)) {
	    case NOTOK: 
		return NOTOK;

	    case OK: 
		printf ("empty!");
		fflush (stdout);
		fflush (stderr);
		_exit (0);	/* NOTREACHED */

	    default: 
		num = mp->nummsg;
		return OK;
	}
    num = mp->nummsg;

    i = 0;
    j = (k = windows[SCAN]) / 2;
    for (msgnum = mp->curmsg; msgnum <= mp->hghmsg; msgnum++)
	if (does_exist (mp, msgnum))
	    i++;
    if (i-- > 0) {
	if (topcur)
	    k = i >= k ? 1 : k - i;
	else
	    k -= i > j ? j : i;
    }

    i = j = 0;
    n = 1;
    for (msgnum = mp->curmsg; msgnum >= mp->lowmsg; msgnum--)
	if (does_exist (mp, msgnum)) {
	    i = msgnum;
	    if (j == 0)
		j = msgnum;
	    if (n++ >= k)
		break;
	}
    for (msgnum = mp->curmsg + 1; msgnum <= mp->hghmsg; msgnum++)
	if (does_exist (mp, msgnum)) {
	    if (i == 0)
		i = msgnum;
	    j = msgnum;
	    if (n++ >= windows[SCAN])
		break;
	}
    if (!topcur
	    && lo > 0
	    && hi > 0
	    && does_exist (mp, lo)
	    && does_exist (mp, hi)
	    && (lo < mp->curmsg
		    || (lo == mp->curmsg && lo == mp->lowmsg))
	    && (mp->curmsg < hi
		    || (hi == mp->curmsg && hi == mp->hghmsg))
	    && hi - lo == j - i)
	i = lo, j = hi;

    if (mp->curmsg != cur || modified)
	switch (winN (NULLCMD, SCAN, 0)) {
	    case NOTOK: 
		return NOTOK;

	    case OK:
		return OK;

	    default: 
		scanrange (lo = i, hi = j);
		cur = mp->curmsg;
		winR (NULLCMD);
		return OK;
	}

    return OK;
}


static int
pCMD (char *str, struct swit *sw, struct Cmd *cmdp)
{
    int i;

    if (*str == '?')
	switch (winX (DISPLAY)) {
	    case NOTOK: 
		return NOTOK;

	    case OK: 
		printf ("commands:\n");
		print_sw (ALL, sw, "", stdout);
		printf ("type ``quit'' to leave %s\n", invo_name);
		fflush (stdout);
		fflush (stderr);
		_exit (0);	/* NOTREACHED */

	    default: 
		rc2peer (RC_EOF, 0, NULL);
		return NOTOK;
	}

    if (parse (str, cmdp) == NOTOK)
	return NOTOK;

    switch (i = smatch (cmdp->args[0], sw)) {
	case AMBIGSW: 
	    switch (winX (DISPLAY)) {
		case NOTOK: 
		    return NOTOK;

		case OK: 
		    ambigsw (cmdp->args[0], sw);
		    fflush (stdout);
		    fflush (stderr);
		    _exit (0);	/* NOTREACHED */

		default: 
		    rc2peer (RC_EOF, 0, NULL);
		    return NOTOK;
	    }

	case UNKWNSW: 
	    fmt2peer (RC_ERR,
		    "say what: ``%s'' -- type ? (or help) for help",
		    cmdp->args[0]);
	    return NOTOK;

	default: 
	    return i;
    }
}


static int
pFIN (void)
{
    int status;

    switch (setjmp (peerenv)) {
	case OK: 
	    SIGNAL (SIGALRM, alrmser);
	    alarm (ALARM);

	    status = peerwait ();

	    alarm (0);
	    return status;

	default: 
	    return NOTOK;
    }
}


static int
peerwait (void)
{
    struct record rcs, *rc;

    rc = &rcs;
    initrc (rc);

    switch (peer2rc (rc)) {
	case RC_QRY: 
	case RC_CMD: 
	    rc2peer (RC_FIN, 0, NULL);
	    return OK;

	case RC_XXX: 
	    advise (NULL, "%s", rc->rc_data);
	    return NOTOK;

	default: 
	    fmt2peer (RC_FIN, "pLOOP protocol screw-up");
	    return NOTOK;
    }
}


static int
ttyNaux (struct Cmd *cmdp, char *s)
{
    struct record rcs, *rc;

    rc = &rcs;
    initrc (rc);

    if (cmdp && init_io (cmdp, vmh) == NOTOK)
	return NOTOK;

    /* XXX: fseek() too tricky for our own good */
    if (!fmsh)
	fseek (fp, 0L, SEEK_SET);

    vmhtty = NOTOK;
    switch (rc2rc (RC_TTY, s ? strlen (s) : 0, s, rc)) {
	case RC_ACK: 
	    vmhtty = OK;	/* fall */
	case RC_ERR: 
	    break;

	case RC_XXX: 
	    padios (NULL, "%s", rc->rc_data);/* NOTREACHED */

	default: 
	    fmt2peer (RC_ERR, "pTTY protocol screw-up");
	    done (1);		/* NOTREACHED */
    }

#ifdef SIGTSTP
    SIGNAL (SIGTSTP, tstat);
#endif
    return vmhtty;
}


static int
ttyR (struct Cmd *cmdp)
{
    struct record rcs, *rc;

    rc = &rcs;

#ifdef SIGTSTP
    SIGNAL (SIGTSTP, SIG_IGN);
#endif

    if (vmhtty != OK)
	return NOTOK;

    initrc (rc);

    if (cmdp)
	fin_io (cmdp, 0);

    vmhtty = NOTOK;
    switch (rc2rc (RC_EOF, 0, NULL, rc)) {
	case RC_ACK: 
	    rc2peer (RC_EOF, 0, NULL);
	    return OK;

	case RC_XXX: 
	    padios (NULL, "%s", rc->rc_data);/* NOTREACHED */

	default: 
	    fmt2peer (RC_ERR, "pTTY protocol screw-up");
	    done (1);		/* NOTREACHED */
    }

    return 1;  /* dead code to satisfy compiler */
}


static int
winN (struct Cmd *cmdp, int n, int eof)
{
    int i, pd[2];
    char buffer[BUFSIZ];
    struct record rcs, *rc;

    rc = &rcs;
    if (vmhpid == NOTOK)
	return OK;

    initrc (rc);

    /* XXX: fseek() too tricky for our own good */
    if (!fmsh)
	fseek (fp, 0L, SEEK_SET);

    vmhpid = OK;

    snprintf (buffer, sizeof(buffer), "%d", n);
    switch (str2rc (RC_WIN, buffer, rc)) {
	case RC_ACK: 
	    break;

	case RC_ERR: 
	    return NOTOK;

	case RC_XXX: 
	    padios (NULL, "%s", rc->rc_data);

	default: 
	    fmt2peer (RC_ERR, "pWIN protocol screw-up");
	    done (1);
    }

    if (pipe (pd) == NOTOK) {
	err2peer (RC_ERR, "pipe", "unable to");
	return NOTOK;
    }

    switch (vmhpid = fork()) {
	case NOTOK: 
	    err2peer (RC_ERR, "fork", "unable to");
	    close (pd[0]);
	    close (pd[1]);
	    return NOTOK;

	case OK: 
	    close (pd[1]);
	    SIGNAL (SIGPIPE, SIG_IGN);
	    while ((i = read (pd[0], buffer, sizeof buffer)) > 0)
		switch (rc2rc (RC_DATA, i, buffer, rc)) {
		    case RC_ACK: 
			break;

		    case RC_ERR: 
			_exit (1);

		    case RC_XXX: 
			advise (NULL, "%s", rc->rc_data);
			_exit (2);

		    default: 
			fmt2peer (RC_ERR, "pWIN protocol screw-up");
			_exit (2);
		}
	    if (i == OK)
		switch (rc2rc (RC_EOF, 0, NULL, rc)) {
		    case RC_ACK: 
			if (eof)
			    rc2peer (RC_EOF, 0, NULL);
			i = 0;
			break;

		    case RC_XXX: 
			advise (NULL, "%s", rc->rc_data);
			i = 2;
			break;

		    default: 
			fmt2peer (RC_ERR, "pWIN protocol screw-up");
			i = 2;
			break;
		}
	    if (i == NOTOK)
		err2peer (RC_ERR, "pipe", "error reading from");
	    close (pd[0]);
	    _exit (i != NOTOK ? i : 1);

	default: 
	    if ((vmhfd0 = dup (fileno (stdin))) == NOTOK)
		padios ("standard input", "unable to dup");
	    if ((vmhfd1 = dup (fileno (stdout))) == NOTOK)
		padios ("standard output", "unable to dup");
	    if ((vmhfd2 = dup (fileno (stderr))) == NOTOK)
		padios ("diagnostic output", "unable to dup");

	    close (0);
	    if ((i = open ("/dev/null", O_RDONLY)) != NOTOK && i != fileno (stdin)) {
		dup2 (i, fileno (stdin));
		close (i);
	    }

	    fflush (stdout);
	    if (dup2 (pd[1], fileno (stdout)) == NOTOK)
		padios ("standard output", "unable to dup2");
	    clearerr (stdout);

	    fflush (stderr);
	    if (dup2 (pd[1], fileno (stderr)) == NOTOK)
		padios ("diagnostic output", "unable to dup2");
	    clearerr (stderr);

	    if (cmdp && init_io (cmdp, 0) == NOTOK)
		return NOTOK;
	    pstat = SIGNAL (SIGPIPE, pipeser);
	    broken_pipe = 1;

	    close (pd[0]);
	    close (pd[1]);

	    return vmhpid;
    }
}


static int
winR (struct Cmd *cmdp)
{
    int status;

    if (vmhpid <= OK)
	return NOTOK;

    if (cmdp)
	fin_io (cmdp, 0);

    if (dup2 (vmhfd0, fileno (stdin)) == NOTOK)
	padios ("standard input", "unable to dup2");
    clearerr (stdin);
    close (vmhfd0);

    fflush (stdout);
    if (dup2 (vmhfd1, fileno (stdout)) == NOTOK)
	padios ("standard output", "unable to dup2");
    clearerr (stdout);
    close (vmhfd1);

    fflush (stderr);
    if (dup2 (vmhfd2, fileno (stderr)) == NOTOK)
	padios ("diagnostic output", "unable to dup2");
    clearerr (stderr);
    close (vmhfd2);

    SIGNAL (SIGPIPE, pstat);

    if ((status = pidwait (vmhpid, OK)) == 2)
	done (1);

    vmhpid = OK;
    return (status == 0 ? OK : NOTOK);
}


static int
winX (int n)
{
    int i, pid, pd[2];
    char buffer[BUFSIZ];
    struct record rcs, *rc;

    rc = &rcs;
    initrc (rc);

    /* XXX: fseek() too tricky for our own good */
    if (!fmsh)
	fseek (fp, 0L, SEEK_SET);

    snprintf (buffer, sizeof(buffer), "%d", n);
    switch (str2rc (RC_WIN, buffer, rc)) {
	case RC_ACK: 
	    break;

	case RC_ERR: 
	    return NOTOK;

	case RC_XXX: 
	    padios (NULL, "%s", rc->rc_data);

	default: 
	    fmt2peer (RC_ERR, "pWIN protocol screw-up");
	    done (1);
    }

    if (pipe (pd) == NOTOK) {
	err2peer (RC_ERR, "pipe", "unable to");
	return NOTOK;
    }

    switch (pid = fork ()) {
	case NOTOK: 
	    err2peer (RC_ERR, "fork", "unable to");
	    close (pd[0]);
	    close (pd[1]);
	    return NOTOK;

	case OK: 
	    close (fileno (stdin));
	    if ((i = open ("/dev/null", O_RDONLY)) != NOTOK && i != fileno (stdin)) {
		dup2 (i, fileno (stdin));
		close (i);
	    }
	    dup2 (pd[1], fileno (stdout));
	    dup2 (pd[1], fileno (stderr));
	    close (pd[0]);
	    close (pd[1]);
	    vmhpid = NOTOK;
	    return OK;

	default: 
	    close (pd[1]);
	    while ((i = read (pd[0], buffer, sizeof buffer)) > 0)
		switch (rc2rc (RC_DATA, i, buffer, rc)) {
		    case RC_ACK: 
			break;

		    case RC_ERR: 
			close (pd[0]);
			pidwait (pid, OK);
			return NOTOK;

		    case RC_XXX: 
			padios (NULL, "%s", rc->rc_data);

		    default: 
			fmt2peer (RC_ERR, "pWIN protocol screw-up");
			done (1);
		}
	    if (i == OK)
		switch (rc2rc (RC_EOF, 0, NULL, rc)) {
		    case RC_ACK: 
			break;

		    case RC_XXX: 
			padios (NULL, "%s", rc->rc_data);

		    default: 
			fmt2peer (RC_ERR, "pWIN protocol screw-up");
			done (1);
		}
	    if (i == NOTOK)
		err2peer (RC_ERR, "pipe", "error reading from");

	    close (pd[0]);
	    pidwait (pid, OK);
	    return (i != NOTOK ? pid : NOTOK);
    }
}


void
padios (char *what, char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    if (vmh) {
	verr2peer (RC_FIN, what, fmt, ap);
	rcdone ();
    } else {
	advertise (what, NULL, fmt, ap);
    }
    va_end(ap);

    done (1);
}


void
padvise (char *what, char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    if (vmh) {
	verr2peer (RC_ERR, what, fmt, ap);
    } else {
	advertise (what, NULL, fmt, ap);
    }
    va_end(ap);
}
