
/*
 * msh.c -- The nmh shell
 *
 * $Id$
 */

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

#include <h/mh.h>
#include <h/signals.h>
#include <h/dropsbr.h>
#include <h/formatsbr.h>
#include <h/scansbr.h>
#include <zotnet/tws/tws.h>
#include <zotnet/mts/mts.h>

#ifdef SYS5
# include <termio.h>
# include <sys/ioctl.h>
#else
# include <sgtty.h>
#endif

#include <pwd.h>
#include <setjmp.h>
#include <signal.h>
#include <h/mshsbr.h>
#include <h/vmhsbr.h>

#ifdef MIME
# define MIMEminc(a) (a)
#else
# define MIMEminc(a)  0
#endif

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

static struct swit switches[] = {
#define	IDSW                  0
    { "idstart number", -7 },		/* interface from bbc */
#define	FDSW                  1
    { "idstop number", -6 },		/*  .. */
#define	QDSW                  2
    { "idquit number", -6 },		/*  .. */
#define	NMSW                  3
    { "idname BBoard", -6 },		/*  .. */
#define	PRMPTSW               4
    { "prompt string", 0 },
#define	SCANSW                5
    { "scan", 0 },
#define	NSCANSW               6
    { "noscan", 0 },
#define	READSW                7
    { "vmhread fd", -7 },
#define	WRITESW               8
    { "vmhwrite fd", -8 },	
#define	PREADSW               9
    { "popread fd", -7 },
#define	PWRITSW              10
    { "popwrite fd", -8 },
#define	TCURSW               11
    { "topcur", 0 },
#define	NTCURSW              12
    { "notopcur", 0 },
#define VERSIONSW            13
    { "version", 0 },
#define	HELPSW               14
    { "help", 4 },
    { NULL, 0 }
};

/*
 * 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;

#ifdef BPOP
int pmsh = 0;			/* BPOP enabled */
extern char response[];
#endif	/* BPOP */

/*
 * 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 */

#ifdef BSD42
int should_intr;		/* signal handler should interrupt call */
jmp_buf sigenv;			/* the environment pointer              */
#endif

/*
 * prototypes
 */
int SOprintf (char *, ...);  /* from termsbr.c */
int sc_width (void);         /* from termsbr.c */
void fsetup (char *);
void setup (char *);
FILE *msh_ready (int, int);
int readids (int);
int readid (int);
void display_info (int);
int expand (char *);
void m_reset (void);
void m_setcur (struct msgs *, int);
void padios (char *, char *, ...);
void padvise (char *, char *, ...);


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

#ifdef BPOP
# ifdef NNTP
static int pop_statmsg (char *);
# endif /* NNTP */
static int read_pop (void);
static int pop_action (char *);
#endif /* BPOP */

static void m_gMsgs (int);
FILE *msh_ready (int, 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 int finaux_io (struct Cmd *);
static void m_init (void);
static RETSIGTYPE intrser (int);
static RETSIGTYPE pipeser (int);
static RETSIGTYPE quitser (int);
static RETSIGTYPE 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);


main (int argc, char **argv)
{
    int	id = 0, scansw = 0, vmh1 = 0, vmh2 = 0;
    char *cp, *file = NULL, *folder = NULL;
    char **ap, **argp, buf[80];
    char *arguments[MAXARGS];
#ifdef	BPOP
    int	pmsh1 = 0, pmsh2 = 0;
#endif

#ifdef LOCALE
	setlocale(LC_ALL, "");
#endif
    invo_name = r1bindex (argv[0], '/');
    mts_init (invo_name);
    if ((cp = m_find (invo_name)) != NULL) {
	ap = brkstring (cp = getcpy (cp), " ", "\n");
	ap = copyip (ap, arguments);
    }
    else
	ap = arguments;
    copyip (argv + 1, ap);
    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: 
		    sprintf (buf, "%s [switches] file", invo_name);
		    print_help (buf, switches);
		    done (1);
		case VERSIONSW:
		    print_version(invo_name);
		    done (1);

		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]);
#ifdef	BPOP
		    if ((pmsh1 = atoi (cp)) < 1)
			adios (NULL, "bad argument %s %s", argp[-2], cp);
#endif	/* BPOP */
		    continue;
		case PWRITSW: 
		    if (!(cp = *argp++) || *cp == '-')
			adios (NULL, "missing argument to %s", argp[-2]);
#ifdef	BPOP
		    if ((pmsh2 = atoi (cp)) < 1)
			adios (NULL, "bad argument %s %s", argp[-2], cp);
#endif	/* BPOP */
		    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 = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF);
	}
	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");
    strcpy (myfilter, etcpath (mhlforward));
#ifdef	FIOCLEX
    if (pfd > 1)
	ioctl (pfd, FIOCLEX, NULL);
#endif	/* FIOCLEX */

#ifdef	BSD42
    should_intr = 0;
#endif	/* BSD42 */
    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 */
    }

#ifdef	BPOP
    if (pmsh = pmsh1 && pmsh2) {
	cp = getenv ("MHPOPDEBUG");
#ifdef	NNTP
	if (pop_set (pmsh1, pmsh2, cp && *cp, myname) == NOTOK)
#else	/* NNTP */
	if (pop_set (pmsh1, pmsh2, cp && *cp) == NOTOK)
#endif	/* NNTP */
	    padios (NULL, "%s", response);
	if (folder)
	    file = folder, folder = NULL;
    }
#endif	/* BPOP */

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

    msh (id > 0 ? scansw : 0);

    m_reset ();
    
    done (0);
}


static struct swit mshcmds[] = {
#define	ADVCMD	0
    { "advance", -7 },
#define	ALICMD	1
    { "ali", 0 },
#define	EXPLCMD	2
    { "burst", 0 },
#define	COMPCMD	3
    { "comp", 0 },
#define	DISTCMD	4
    { "dist", 0 },
#define	EXITCMD	5
    { "exit", 0 },
#define	FOLDCMD	6
    { "folder", 0 },
#define	FORWCMD	7
    { "forw", 0 },
#define	HELPCMD	8
    { "help", 0 },
#define	INCMD	9
    { "inc", 0 },
#define	MARKCMD	10
    { "mark", 0 },
#define	MAILCMD	11
    { "mhmail", 0 },
#define	MHNCMD	12
    { "mhn", MIMEminc(-3) },
#define	MSGKCMD	13
    { "msgchk", 0 },
#define	NEXTCMD	14
    { "next", 0 },
#define	PACKCMD	15
    { "packf", 0 },
#define	PICKCMD	16
    { "pick", 0 },
#define	PREVCMD	17
    { "prev", 0 },
#define	QUITCMD	18
    { "quit", 0 },
#define	FILECMD	19
    { "refile", 0 },
#define	REPLCMD	20
    { "repl", 0 },
#define	RMMCMD	21
    { "rmm", 0 },
#define	SCANCMD	22
    { "scan", 0 },
#define	SENDCMD	23
    { "send", 0 },
#define	SHOWCMD	24
    { "show", 0 },
#define	SORTCMD	25
    { "sortm", 0 },
#define	WHATCMD	26
    { "whatnow", 0 },
#define	WHOMCMD	27
    { "whom", 0 },
    { NULL, 0 }
};


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;

    sprintf (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 = m_find (cmd_name)) != NULL) {
		    ap = brkstring (cp = getcpy (cp), " ", "\n");
		    ap = copyip (ap, vec);
		}
		else
		    ap = vec;
		break;

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

	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:
#ifdef	MIME
		if (!vmh || ttyN (cmdp) != NOTOK)
		    mhncmd (vec);
#endif
		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");
    if (!(mp = m_gmsg (folder)))
	padios (NULL, "unable to read folder %s", folder);
    if (mp->hghmsg == 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;
#ifdef BPOP
    char tmpfil[BUFSIZ];
#endif

#ifdef	BPOP
    if (pmsh) {
	strcpy (tmpfil, m_tmpfil (invo_name));
	if ((fp = fopen (tmpfil, "w+")) == NULL)
	    padios (tmpfil, "unable to create");
	unlink (tmpfil);
    }
    else
#endif	/* BPOP */
    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);

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

    mp->hghmsg = msgp;
    mp->nummsg = msgp;
    mp->lowmsg = 1;
    mp->curmsg = 0;

    mp->foldpath = getcpy (myname ? myname : file);
    mp->msgflags = 0;
#ifdef	BPOP
    if (pmsh)
	mp->msgflags |= READONLY;
    else {
#endif	/* BPOP */
	stat (file, &st);
	if (st.st_uid != getuid () || access (file, W_OK) == NOTOK)
	    mp->msgflags |= READONLY;
#ifdef	BPOP
    }
#endif	/* BPOP */
    mp->lowoff = 1;
    mp->hghoff = mp->hghmsg + 1;

#ifdef	MTR
    mp->msgstats = (short *)
		calloc ((size_t) 1, MHSIZEX (mp, mp->lowmsg, mp->hghmsg));
    if (mp->msgstats == NULL)
	padios (NULL, "unable to allocate messages storage");
    mp->msgstats = (mp->msgbase = mp->msgstats) - mp->lowoff;
    if (mp->msgstats < (short *)0)
	padios (NULL, "setup() botch -- you lose big");
#endif	/* MTR */
#ifdef	BPOP
    if (pmsh) {
#ifndef	NNTP
	for (i = mp->lowmsg; i <= mp->hghmsg; i++) {
	    Msgs[i].m_top = i;
	    mp->msgstats[i] = EXISTS | VIRTUAL;
	}
#else	/* NNTP */
	for (i = mp->lowmsg; i <= mp->hghmsg; i++) {
	    if (Msgs[i].m_top)			/* set in read_pop() */
		mp->msgstats[i] = EXISTS | VIRTUAL;
	}
#endif	/* NNTP */
    }
    else
#endif	/* BPOP */
    for (i = mp->lowmsg; i <= mp->hghmsg; i++)
	mp->msgstats[i] = EXISTS;
    m_init ();

    mp->msgattrs[0] = getcpy ("unseen");
    mp->msgattrs[1] = NULL;

    m_unknown (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;

#ifdef	BPOP
    if (pmsh)
	return read_pop ();
#endif	/* BPOP */

    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;

#ifdef	BPOP
    if (pmsh)
	return (msgp - 1);
#endif	/* BPOP */

    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);
}


#ifdef	BPOP
#ifdef	NNTP
static int pop_base = 0;

static int
pop_statmsg (char *s)
{
    register int i, n;

    n = (i = atoi (s)) - pop_base;	 /* s="nnn header-line..." */
    Msgs[n].m_top = Msgs[n].m_bboard_id = i;
}

#endif	/* NNTP */

static int
read_pop (void)
{
    int	nmsgs, nbytes;

    if (pop_stat (&nmsgs, &nbytes) == NOTOK)
	padios (NULL, "%s", response);

    m_gMsgs (nmsgs);

#ifdef	NNTP	/* this makes read_pop() do some real work... */
    pop_base = nbytes - 1; 	/* nmsgs=last-first+1, nbytes=first */
    pop_exists (pop_statmsg);
#endif	/* NNTP */
    return nmsgs;
}


static int
pop_action (char *s)
{
    fprintf (yp, "%s\n", s);
}
#endif	/* BPOP */


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 *) realloc ((char *) Msgs, (size_t) (nmsgs + 2) * sizeof *Msgs);
    if (Msgs == NULL)
	padios (NULL, "unable to reallocate Msgs structure");
    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;
    long pos1, pos2;
    char *cp, tmpfil[BUFSIZ];

    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), 0)) == 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, 0);
	return yp;
    }

#ifdef	BPOP
    if (pmsh && (mp->msgstats[msgnum] & VIRTUAL)) {
	if (Msgs[msgnum].m_top == 0)
	    padios (NULL, "msh_ready (%d, %d) botch", msgnum, full);
	if (!full) {
	    strcpy (tmpfil, m_tmpfil (invo_name));
	    if ((yp = fopen (tmpfil, "w+")) == NULL)
		padios (tmpfil, "unable to create");
	    unlink (tmpfil);

	    if (pop_top (Msgs[msgnum].m_top, 4, pop_action) == NOTOK)
		padios (NULL, "%s", response);

	    m_eomsbr ((int (*)()) 0);	/* XXX */
	    msg_style = MS_DEFAULT;	/*  .. */
	    fseek (yp, 0L, 0);
	    return yp;
	}

	fseek (fp, 0L, 2);
	fwrite (mmdlm1, 1, strlen (mmdlm1), fp);
	if (fflush (fp))
	    padios ("temporary file", "write error on");
	fseek (fp, 0L, 2);
	pos1 = ftell (fp);

	yp = fp;
	if (pop_retr (Msgs[msgnum].m_top, pop_action) == NOTOK)
	    padios (NULL, "%s", response);
	yp = NULL;

	fseek (fp, 0L, 2);
	pos2 = ftell (fp);
	fwrite (mmdlm2, 1, strlen (mmdlm2), fp);
	if (fflush (fp))
	    padios ("temporary file", "write error on");

	Msgs[msgnum].m_start = pos1;
	Msgs[msgnum].m_stop = pos2;

	mp->msgstats[msgnum] &= ~VIRTUAL;
    }
#endif	/* BPOP */

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


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

#ifdef	BPOP
    if (pmsh)
	return 0;
#endif	/* BPOP */

    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;
	m_fmsg (mp);

	if (!(mp = m_gmsg (fmsh)))
	    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 = m_remsg (mp, 0, msgp)) == NULL)
	padios (NULL, "unable to allocate folder storage");

    low = mp->hghmsg + 1, hgh = msgp;
    flags = scansw ? m_seqflag (mp, "unseen") : 0;
    for (i = mp->hghmsg + 1; i <= msgp; i++) {
	mp->msgstats[i] = EXISTS | flags;
	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];

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


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

    if ((cp = m_find (cmd_name = "scan")) != NULL) {
	ap = brkstring (cp = getcpy (cp), " ", "\n");
	ap = copyip (ap, vec);
    }
    else
	ap = vec;
    *ap++ = arg;
    *ap = NULL;
    m_init ();
    scancmd (vec);
    if (cp != NULL)
	free (cp);
}


int
readids (int id)
{
    register int cur, flags, i, msgnum;

    if (mp->curmsg == 0)
	m_setcur (mp, mp->lowmsg);
    if (id <= 0 || (flags = m_seqflag (mp, "unseen")) == 0)
	return;

    for (msgnum = mp->hghmsg; msgnum >= mp->lowmsg; msgnum--)
	mp->msgstats[msgnum] |= flags;

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

	for (msgnum = mp->hghmsg; msgnum >= mp->lowmsg; msgnum--)
	  if (mp->msgstats[msgnum] & EXISTS)		/* FIX */
	    if ((i = readid (msgnum)) > 0 && i < id) {
		cur = msgnum + 1;
		mp->msgstats[msgnum] &= ~flags;
		break;
	    }
	for (i = mp->lowmsg; i < msgnum; i++)
	    mp->msgstats[i] &= ~flags;

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

	m_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;
#ifdef	BPOP
    int	arg1, arg2, arg3;
#endif

    if (Msgs[msgnum].m_bboard_id)
	return Msgs[msgnum].m_bboard_id;
#ifdef	BPOP
    if (pmsh) {
	if (Msgs[msgnum].m_top == 0)
	    padios (NULL, "readid (%d) botch", msgnum);
	if (pop_list (Msgs[msgnum].m_top, (int *) 0, &arg1, &arg2, &arg3) == OK
		&& arg3 > 0)
	    return (Msgs[msgnum].m_bboard_id = arg3);
    }
#endif	/* BPOP */

    zp = msh_ready (msgnum, 0);
    for (state = FLD;;)
	switch (state = m_getfld (state, name, buf, sizeof buf, zp)) {
	    case FLD: 
	    case FLDEOF: 
	    case FLDPLUS: 
		if (uleq (name, BBoard_ID)) {
		    bp = getcpy (buf);
		    while (state == FLDPLUS) {
			state = m_getfld (state, name, buf, sizeof buf, 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)
		    state = m_getfld (state, name, buf, sizeof buf, zp);
		if (state != FLDEOF)
		    continue;

	    default: 
		return 0;
	}
}


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

    interactive = isatty (fileno (stdout));
    if (sp == NULL) {
	if ((sd = dup (fileno (stdout))) == NOTOK)
	    padios ("standard output", "unable to dup");
#ifndef	BSD42			/* XXX */
#ifdef	FIOCLEX
	ioctl (sd, FIOCLEX, NULL);
#endif	/* FIOCLEX */
#endif	/* not BSD42 */
	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 ((flags = m_seqflag (mp, "unseen"))
	    && scansw
	    && (mp->msgstats[mp->hghmsg] & flags))
	scanstring ("unseen");
}


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

    if (pfd <= 1)
	return;

    if (flags = m_seqflag (mp, "unseen"))
	for (msgnum = mp->hghmsg; msgnum >= mp->lowmsg; msgnum--)
	    if (!(mp->msgstats[msgnum] & flags)) {
		if (Msgs[msgnum].m_bboard_id == 0)
		    readid (msgnum);
		if ((i = Msgs[msgnum].m_bboard_id) > 0)
		    break;
	    }

    sprintf (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;

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

    if (vmh) 
	ttyNaux (NULLCMD, "FAST");
    cp = NULL;
    if ((dp = lkfopen (mp->foldpath, "r")) == 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");
	    unlink (map_name (mp->foldpath));/* XXX */
	}
	goto release;
    }

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

    for (msgnum = mp->lowmsg; msgnum <= mp->hghmsg; msgnum++)
	if (mp->msgstats[msgnum] & EXISTS
		&& pack (tmpfil, md, msgnum) == NOTOK) {
	    mbx_close (tmpfil, md);
	    unlink (tmpfil);
	    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 {
	strcpy (map1, map_name (tmpfil));
	strcpy (map2, map_name (mp->foldpath));

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

release: ;
    if (cp)
	free (cp);
    lkfclose (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;
#ifdef BSD42
	switch (setjmp (sigenv)) {
	    case OK:
		should_intr = 1;
		break;

	    default:
		should_intr = 0;
		if (interrupted && !told_to_quit) {
		    putchar ('\n');
		    continue;
		}
		if (ppid > 0)
		    kill (ppid, SIGEMT);
		return EOF;
	}
#endif	/* BSD42 */
	if (interactive) {
	    printf ("%s", prompt);
	    fflush (stdout);
	}
	for (cp = buffer; (i = getchar ()) != '\n';) {
#ifndef	BSD42
	    if (interrupted && !told_to_quit) {
		buffer[0] = NULL;
		putchar ('\n');
		break;
	    }
	    if (told_to_quit || i == EOF) {
		if (ppid > 0)
		    kill (ppid, SIGEMT);
		return EOF;
	    }
#else /* BSD42 */
	    if (i == EOF)
		longjmp (sigenv, DONE);
#endif /* BSD42 */
	    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, "");
	    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: 
#ifdef	BSD42
		should_intr = 0;
#endif	/* BSD42 */
		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)
		    kill (ppid, SIGEMT);
		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 (c))
	    break;
    if (c == 0) {
	if (vmh)
	    fmt2peer (RC_EOF, "null command");
	return NOTOK;
    }

    while (c = *cp++) {
	if (isspace (c)) {
	    while (isspace (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 (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 (*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;
	}

    sprintf (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 int
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++)
	mp->msgstats[msgnum] &= ~SELECTED;
    mp->lowsel = mp->hghsel = mp->numsel = 0;
}


void
m_reset (void)
{
    write_ids ();
    m_fmsg (mp);
    myname = NULL;
#ifdef	BPOP
    if (pmsh) {
	pop_done ();
	pmsh = 0;
    }
#endif	/* BPOP */
}


void
m_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 RETSIGTYPE
intrser (int i)
{
#ifndef RELIABLE_SIGNALS
    SIGNAL (SIGINT, intrser);
#endif

    discard (stdout);
    interrupted++;

#ifdef BSD42
    if (should_intr)
	longjmp (sigenv, NOTOK);
#endif
}


static RETSIGTYPE
pipeser (int i)
{
#ifndef RELIABLE_SIGNALS
    SIGNAL (SIGPIPE, pipeser);
#endif

    if (broken_pipe++ == 0)
	fprintf (stderr, "broken pipe\n");
    told_to_quit++;
    interrupted++;

#ifdef BSD42
    if (should_intr)
	longjmp (sigenv, NOTOK);
#endif
}


static RETSIGTYPE
quitser (int i)
{
#ifndef RELIABLE_SIGNALS
    SIGNAL (SIGQUIT, quitser);
#endif

    told_to_quit++;
    interrupted++;

#ifdef BSD42
    if (should_intr)
	longjmp (sigenv, NOTOK);
#endif
}


static RETSIGTYPE
alrmser (int 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 (*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 (*bp))
		bp++;
	    while (isspace (*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 (*bp))
		    bp++;
		while (isspace (*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 */
    }
}


static int
pQRY (char *str, int scansw)
{
    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 (mp->msgstats[msgnum] & EXISTS)
	    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 (mp->msgstats[msgnum] & EXISTS) {
	    i = msgnum;
	    if (j == 0)
		j = msgnum;
	    if (n++ >= k)
		break;
	}
    for (msgnum = mp->curmsg + 1; msgnum <= mp->hghmsg; msgnum++)
	if (mp->msgstats[msgnum] & EXISTS) {
	    if (i == 0)
		i = msgnum;
	    j = msgnum;
	    if (n++ >= windows[SCAN])
		break;
	}
    if (!topcur
	    && lo > 0
	    && hi > 0
	    && mp->msgstats[lo] & EXISTS
	    && mp->msgstats[hi] & EXISTS
	    && (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, "");
		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;

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

    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 */
    }
}


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);

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

    vmhpid = OK;

    sprintf (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", 0)) != 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);

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

    sprintf (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", 0)) != 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);
}
