#include <stdio.h>
#include <ctype.h>

/*
 * MGET -- Extract messages from a mail file containing one of the given
 * patterns.  This task acts as a simple filter.  The arguments are the
 * patterns to be matched.  These are fixed pattern strings, except for
 * an optional ^ metacharacter if the match is to be performed only at
 * the beginning of a line.  The main difference between MGET and most
 * conventional mail programs is that MGET is a filter, and can be used to
 * do context sensitive searching and retrieval of messages from very large
 * mail databases (folders).
 *
 * Syntax:
 *
 *	mget [-p] [-b beginstr] [-i] [-o] [pattern] [pattern] ...
 *
 * Switches:
 *
 *	-p		page messages (uses PAGER environment variable)
 *	-t nblks	examine only the tail (last nblks blocks) of the file
 *			  connected to the standard input
 *
 *	-np npages	number of pages to buffer in paged mode
 *
 *	-b beginstr	string which marks the beginning of a message
 *	-i		ignore case when matching the following pattern(s)
 *	-o		pass only messages which do NOT contain the pattern(s)
 *	-a		auto page advance (avoids page query), but still
 *
 * These switches are toggles, and may appear between successive pattern
 * arguments to turn the respective options on and off.
 *
 * Some examples:
 *
 * Display all messages to or from a specific user:
 *	tail -100b mbox | mget '^From eric@cfa' '^To: eric@cfa' | pg
 * Search a mail database for all messages to or from a specific site:
 *	rsh coma "zcat ~sites/sitemail.1988.Z" | mget munnari | pg
 *	cat mbox.89.* | mget -i harvard.edu | pg
 * Find all messages pertaining to one of a list of topics:
 *	cat $mdir/sunspots | mget -i nfs slip 'X terminals' | pg
 * Display the site mail for Feb 7:
 *	tail -100b ~sites/sitemail | mget 'Feb  7' | pg +/"^From "
 * Display all messages from a certain user on a specific topic:
 *	cat mbox | mget mvh@cfa | mget -i plio | pg
 * Display all messages from a user except those on a certain topic:
 *	cat mbox | mget silva -o -i imfort | pg
 * Search USENET postings rather than a unix mail box:
 *	cat comp/windows/x/* | mget -b 'Path: ' -i colormap | pg
 *
 * In these examples, pg is a file pager (e.g. "less") but of course there
 * are many other things that could be done with the mget output, e.g., one
 * could direct the output into a new mail folder.
 *
 * When used in the paged mode (-p switch) mget will pass each message to
 * the PAGER command instead of copying the message to the standard output.
 * An example of a useful PAGER is:  setenv PAGER "less -C -E -M".  In this
 * mode the messages that pass the filter given on the command line are
 * paged, with the last -np pages buffered internally by the program,
 * allowing keystrokes such as "f" and "b" to move to the next or previous
 * messages, "." to move to the earliest buffered message, "G" to move to
 * the end of file, "/" to search for a message containing a pattern, "s" to
 * save to a file, etc. (see HELP, below).
 *
 * For example,
 *
 *   alias MG 'mget -p -t 128 -np 160 < /usr/spool/mail/<username>'
 *
 * would define an alias MG which pages the last 128 blocks of the user's
 * mail file, buffering up to 160 pages in program memory (actually in
 * temporary files).  If a new message arrives while the program is running,
 * the command "G" (go to end of file) will cause any new messages to be
 * read without need to exit and restart the program.
 */

#define	SZ_MSGBUF	128000		/* max size message */
#define	SZ_PATSTR	128		/* max size pattern string */
#define	SZ_LINE		512		/* max size message line */
#define	DEFPAGER	"more"		/* default page program */
#define	DEFNPAGES	64		/* default number page buffers */
#define	MAXNPAGES	1024		/* default number page buffers */
#define	DEFTERM		"vt100"		/* default terminal type */
#define	EOS		'\0'
#define	IGNORE		1

#define	HELP \
"[q=quit,f|sp=fwd,b=bak,.=back,Ng=goto,G=toEOF,/|n=search,s=save,r=redraw]"

char	svfile[128] = "";
static	char tc[1024];
char	msg[SZ_MSGBUF+1];
int	curmsg;
char	beginmsg[SZ_PATSTR+1] = "From ";
int	ignorecase = 0;
int	omit = 0;
int	delmsg = 1;
char	*lower();
char	*mktemp(), *getenv();
static	tty_rawon(), tty_reset();
int	putch(ch) {char cch=ch; write(1,&cch,1);}


/* MGET -- Main routine.
 */
main (argc, argv)
int	argc;
char	**argv;
{
	int	passall, lastpat, pagemsg, pass, ateof, key, nb, i;
	int	npages, curpage, lowpage, newpage, skipto, notfound;
	char	*arg, *pager, *term, *lookfor;
	char	tmpdir[64], cmd[128];
	int	autoquery, ival, nblks;
	char	sval[256];
	char	prompt[80];
	FILE	*fp = stdin;

	/* Once through the argument list to set any global switches or
	 * variables.
	 */
	npages = DEFNPAGES;
	lookfor = NULL;
	autoquery = 0;
	notfound = 0;
	lastpat = -1;
	passall = 1;
	pagemsg = 0;
	newpage = 0;
	skipto = 0;
	ateof = 0;

	for (i=1;  i < argc && (arg=argv[i]) != NULL;  i++) {
	    if (strcmp (arg, "-o") == 0) {
		passall = 0;
	    } else if (strcmp (arg, "-i") == 0) {
		;
	    } else if (strcmp (arg, "-b") == 0) {
		strncpy (beginmsg, argv[++i], SZ_PATSTR);
	    } else if (strcmp (arg, "-p") == 0) {
		pagemsg++;
	    } else if (strcmp (arg, "-a") == 0) {
		autoquery++;
	    } else if (strcmp (arg, "-np") == 0) {
		npages = atoi(argv[++i]);
		if (npages < 1)
		    npages = 1;
		else if (npages > MAXNPAGES)
		    npages = MAXNPAGES;
	    } else if (strcmp (arg, "-nd") == 0) {
		delmsg = 0;
	    } else if (strcmp (arg, "-t") == 0) {
		nblks = atoi(argv[++i]);
		if (nblks < 1)
		    nblks = 1;
		fseek (fp, -nblks*1024, 2);
	    } else
		lastpat = i;
	}

	/* If paging is desired, create the page directory. */
	if (pagemsg) {
	    sprintf (tmpdir, "/tmp/mgetXXXXXX");
	    if (mktemp(tmpdir) == NULL) {
		fprintf (stderr, "cannot generate unique tmpdir name\n");
		exit (1);
	    }
	    if (mkdir (tmpdir, 0700) == -1) {
		fprintf (stderr, "cannot create directory %s\n", tmpdir);
		exit (1);
	    }

	    if ((pager = getenv("PAGER")) == NULL)
		pager = DEFPAGER;
	    if ((term = getenv("TERM")) == NULL)
		term = DEFTERM;

	    if (tgetent (tc, term) < 0) {
		fprintf (stderr, "unrecognized terminal type\n");
		exit (1);
	    }

	    curpage = 0;
	}

	/* Main loop: extract each message, search for patterns.  The switches
	 * -i and -o may be inserted in the argument list to toggle the options
	 * ignorecase (case is ignored during pattern matching if set) and
	 * omit (the message is passed to the ouput only if the given patterns
	 * are NOT present).
	 */
	while (nextmsg (fp, msg, SZ_MSGBUF) != EOF) {
	    ignorecase = 0;
	    omit = 0;
	    pass = 0;

	    /* Evaluate the specified filter expression */
	    if (lastpat < 0)
		pass++;			/* no patterns given */
	    else {
		for (i=1;  i < argc && (arg=argv[i]) != NULL;  i++) {
		    if (arg[0] == '-') {
			if (strcmp (arg, "-i") == 0) {
			    ignorecase = !ignorecase;
			    continue;
			} else if (strcmp (arg, "-o") == 0) {
			    omit = !omit;
			    if (i == 1)
				pass = 1;
			    continue;
			} else if ((strcmp (arg, "-b") == 0) ||
				   (strcmp (arg, "-t") == 0)) {
			    i++;
			    continue;
			} else
			    continue;
		    }

		    if (match (msg, ignorecase ? lower(arg) : arg)) {
			if (omit) {
			    pass = 0;
			    break;
			} else {
			    pass = 1;
			    if (passall)
				break;
			}
		    } else if (i >= lastpat && passall)
			break;
		}
	    }

	    /* If the page flag is set page the message, else copy the
	     * message on to the standard output.
	     */
	    if (pass) {
showmsg:
		if (pagemsg) {
		    char    fname[64];
		    int     fd;
		    FILE    *tp;

		    if (ateof) {
			sprintf (fname, "%s/%d", tmpdir, curpage=newpage);
			if ((fd = open (fname, 0)) == -1) {
			    fprintf (stderr, "cannot open file %s\n", fname);
			    goto cleanup;
			} else {
			    nb = read (fd, msg, SZ_MSGBUF);
			    if (nb > 0)
				msg[nb] = EOS;
			    close (fd);
			}
			skipto = 0;
			lookfor = NULL;
			curmsg = curpage;

		    } else {
			newpage++;
			curmsg = curpage = newpage;
			lowpage = newpage - npages + 1;
			if (lowpage < 1)
			    lowpage = 1;

			/* Delete oldest page. */
			if (lowpage > 1) {
			    sprintf (fname, "%s/%d", tmpdir, lowpage - 1);
			    if (delmsg)
				unlink (fname);
			}

			/* Prepare the new page. */
			sprintf (fname, "%s/%d", tmpdir, newpage);
			if ((tp = fopen (fname, "w")) == NULL) {
			    fprintf (stderr, "cannot create file %s\n", fname);
			    goto cleanup;
			} else {
			    fputs (msg, tp);
			    fclose (tp);
			}
		    }


		    /* Display the current page, query for the page
		     * positioning command, and loop until a new page
		     * is desired or quit is indicated.
		     */
		    do {
			/* Don't display anything if skipping forward. */
			if (skipto && (skipto == EOF || curpage < skipto))
			    break;

			/* Don't display anything if looking for a pattern
			 * and we haven't found it yet.
			 */
			if (lookfor && !match (msg, lookfor))
			    break;

			if (notfound) {
			    sprintf (prompt,
				"mget(%d): pattern not found (now at EOF)",
				curpage);
			    key = query (prompt, &ival, sval);
			    notfound = 0;
			    lookfor = NULL;
			    skipto = 0;
			    goto action;
			}

			/* Buffer the page. */
			if (curmsg != curpage) {
			    if ((fd = open (fname, 0)) == -1) {
				fprintf (stderr,
				    "cannot open file %s\n", fname);
				goto cleanup;
			    } else {
				nb = read (fd, msg, SZ_MSGBUF);
				if (nb > 0)
				    msg[nb] = EOS;
				curmsg = curpage;
				close (fd);
			    }
			}

			/* Display the page. */
			sprintf (fname, "%s/%d", tmpdir, curpage);
			sprintf (cmd, "%s %s", pager, fname);
			system (cmd);
			lookfor = NULL;
			skipto = 0;
again:
			/* Get command keystroke. */
			sprintf (prompt, "mget(%d)%s:", curpage,
			    (ateof && curpage == newpage) ? "(EOF)" : "");
			key = autoquery ? 'f' : query (prompt, &ival, sval);
action:
			switch (key) {
			case IGNORE:
			    goto again;
			case EOF:
			    goto cleanup;
			    break;
			case 'q':
			    key = query ("really quit? (yes):", &ival, sval);
			    if (key == '\r' || key == 'y')
				goto cleanup;
			    break;
			case '?':
			    key = query (HELP, &ival, sval);
			    goto action;
			case 'r':
			    break;
			case 'f':
			case ' ':
			    if (curpage >= newpage) {
				if (ateof)
				    goto extend;
				key = 0;
			    } else
				curpage++;
			    break;
			case 'b':
			    curpage--;
			    if (curpage < lowpage)
				curpage = lowpage;
			    break;
			case 'g':
			    skipto = ival;
			    if (skipto < newpage)
				curpage = skipto;
			    else
				curpage = newpage;
			    break;
			case 'G':
extend:		  	    if (ateof) {
				clearerr (fp);
				ateof = 0;
			    }
			    curpage = newpage;
			    skipto = EOF;
			    break;
			case '.':
			    curpage = lowpage;
			    break;

			case 'n':
			    goto search;
			    
			case '/':
			    /* Search for the given pattern. */
			    if (sval[0] == '\0')
				strcpy (sval, lookfor);
search:
			    if (curpage < newpage) {
				curpage++;
				while (curpage <= newpage) {
				    sprintf (fname, "%s/%d", tmpdir, curpage);
				    if ((fd = open (fname, 0)) == -1) {
					fprintf (stderr,
					    "cannot open %s\n",fname);
					goto cleanup;
				    } else {
					nb = read (fd, msg, SZ_MSGBUF);
					if (nb > 0)
					    msg[nb] = EOS;
					curmsg = curpage;
					close (fd);
				    }
				    if (match (msg, sval)) {
					skipto = curpage;
					goto foundone;
				    }
				    curpage++;
				    if (curpage > newpage) {
					curpage = newpage;
					break;
				    }
				}
			    }
		  	    if (ateof) {
				clearerr (fp);
				ateof = 0;
			    }
			    lookfor = sval;
			    key = NULL;
foundone:
			    break;
			case 's':
			    /* Save current message to a file. */
			    {   int newfile = (access (sval, 0) == -1);
				FILE *fp = fopen (sval, "a");
				if (fp == NULL)
				    fprintf (stdout, "- failed\n");

				else {
				    char    fname[128];
				    int     in;

				    sprintf (fname, "%s/%d", tmpdir, curpage);
				    if ((in = open (fname, 0)) < 0)
					continue;
				    if ((nb = read (in, msg, SZ_MSGBUF)) <= 0)
					continue;
				    msg[nb] = EOS;
				    fputs (msg, fp);
				    close (in);
				    fclose (fp);
				    fprintf (stdout,
					"- %d bytes %s\n", strlen(msg),
					newfile ? "written" : "appended");
				}
			    }
			    goto again;

			default:
			    putch ('\007');
			    goto again;
			}
		    } while (key);

		} else
		    fputs (msg, stdout);
	    }
	}

	ateof++;
	if (skipto == EOF) {
	    goto showmsg;
	} else if (lookfor) {
	    notfound++;
	    goto showmsg;
	} else if (pagemsg && !autoquery) {
	    key = query ("at EOF - want to quit? (yes):", &ival, sval);
	    if (!(key == '\r' || key == 'y'))
		goto showmsg;
	}

cleanup:
	if (delmsg) {
	    sprintf (cmd, "/bin/rm -rf %s", tmpdir);
	    system (cmd);
	}
}


/* NEXTMSG -- Extract the next message from the input file.  The string
 * beginmsg (e.g., "From ") marks the beginning of each message.
 */
nextmsg (fp, msg, maxch)
FILE	*fp;
char	*msg;
int	maxch;
{
	register char	*ip, *op;
	register int	n = maxch;
	static	char lbuf[SZ_LINE+1] = "";
	int	nch;

	for (ip=lbuf, op=msg;  (*op = *ip++) && --n >= 0;  op++)
	    ;
	
	nch = strlen (beginmsg);
	while (fgets (lbuf, SZ_LINE, fp) != NULL) {
	    if (op > msg && strncmp (lbuf, beginmsg, nch) == 0)
		break;
	    for (ip=lbuf;  (*op = *ip++) && --n >= 0;  op++)
		;
	    lbuf[0] = EOS;
	}

	return (op > msg ? 0 : EOF);
}


/* MATCH -- Match the given pattern against the input string.  Return 1 for
 * match.  If the pattern begins with a ^ the match will be performed only
 * at the beginning of a line.
 */
match (str, pat)
char	*str;
char	*pat;
{
	register char *ip, *pp, *ii;
	register int  ch;

	if (ignorecase) {
#	    define LC(ip)  (isupper(ch= *(ip))?tolower(ch):ch)
	    if (*pat == '^') {
		/* Match at beginning of line only. */
		pat++;
		for (ip=str;  *ip;  ) {
		    for (pp=pat;  *pp == LC(ip);  pp++, ip++)
			;
		    if (*pp == EOS)
			return (1);
		    while (*ip && *ip++ != '\n')
			;
		}

	    } else {
		/* Match fixed pattern anywhere. */
		for (ip=str;  *ip;  ip++) {
		    if (LC(ip) != *pat)
			continue;
		    for (pp=pat, ii=ip;  *pp == LC(ii);  pp++, ii++)
			;
		    if (*pp == EOS)
			return (1);
		}
	    }

	} else {
	    if (*pat == '^') {
		/* Match at beginning of line only. */
		pat++;
		for (ip=str;  *ip;  ) {
		    for (pp=pat;  *pp == *ip;  pp++, ip++)
			;
		    if (*pp == EOS)
			return (1);
		    while (*ip && *ip++ != '\n')
			;
		}

	    } else {
		/* Match fixed pattern anywhere. */
		for (ip=str;  *ip;  ip++) {
		    if (*ip != *pat)
			continue;
		    for (pp=pat, ii=ip;  *pp == *ii;  pp++, ii++)
			;
		    if (*pp == EOS)
			return (1);
		}
	    }
	}

	return (0);
}


/* LOWER -- Convert a pattern string to lower case.
 */
char *
lower (s)
char	*s;
{
	register int n, ch;
	register char *ip, *op;
	static char lwr[SZ_PATSTR+1];

	for (ip=s, op=lwr, n=SZ_PATSTR;  --n >= 0 && (ch = *ip++);  )
	    *op++ = isupper(ch) ? tolower(ch) : ch;
	*op = EOS;

	return (lwr);
}


/* QUERY -- Issue a prompt and query the user for the command keystroke.
 */
query (prompt, ival, sval)
char	*prompt;
int	*ival;
char	*sval;
{
	static  char oldpat[80];
	char	ctrl[128], *cp=ctrl;
	int	key, ch, fd, value;

	/* Output prompt. */
	cp = ctrl;
	if (tgetstr ("so", &cp) != NULL)
	    tputs (ctrl, 1, putch);
	write (1, prompt, strlen(prompt));
	cp = ctrl;
	if (tgetstr ("se", &cp) != NULL)
	    tputs (ctrl, 1, putch);

	/* Get command key in raw mode. */
	if ((fd = tty_open ("/dev/tty", 2)) == -1) {
	    fprintf (stderr, "cannot open terminal\n");
	    return (EOF);
	}
	tty_rawon (fd, 0);

	value = 0;
	sval[0] = '\0';

	while ((ch = key = tty_getc (fd)) != EOF) {
	    if (isdigit(ch))
		value = value * 10 + ch - '0';
	    else
		break;
	}

	if (key == '/') {			/* Pattern search. */
	    char    *op = sval;
erase_p:
	    /* Erase the prompt and echo the '/'. */
	    putch ('\r');
	    cp = ctrl;
	    if (tgetstr ("ce", &cp) != NULL)
		tputs (ctrl, 1, putch);
	    tty_putc (fd, ch);

	    while ((ch = tty_getc (fd)) != EOF) {
		if (ch == '\025') {
		    op = sval;
		    *op = '\0';
		    goto erase_p;
		} else if (ch == '\177' || ch == '\010') {
		    if (op > sval) {
			tty_putc (fd, '\010');
			tty_putc (fd, ' ');
			tty_putc (fd, '\010');
			--op;
		    } else {
			key = IGNORE;
			break;
		    }
		} else if (ch == '\r') {
		    *op++ = '\0';
		    break;
		} else {
		    *op++ = ch;
		    tty_putc (fd, ch);
		}
	    }
	    if (sval[0])
		strcpy (oldpat, sval);
	    else
		strcpy (sval, oldpat);

	} else if (key == 'n') {		/* repeat search */
	    strcpy (sval, oldpat);

	} else if (key == 's') {		/* Save message in a file. */
	    char    buf[256];
	    char    *op;
erase_f:
	    /* Prompt for the save file. */
	    putch ('\r');
	    cp = ctrl;
	    if (tgetstr ("ce", &cp) != NULL)
		tputs (ctrl, 1, putch);
	    sprintf (buf, "save file: %s", svfile);
	    write (1, buf, strlen(buf));

	    /* Get save file name. */
	    op = svfile + strlen(svfile);
	    while ((ch = tty_getc (fd)) != EOF) {
		if (ch == '\025') {
		    svfile[0] = '\0';
		    goto erase_f;
		} else if (ch == '\177' || ch == '\010') {
		    if (op > svfile) {
			tty_putc (fd, '\010');
			tty_putc (fd, ' ');
			tty_putc (fd, '\010');
			--op;
		    } else {
			key = IGNORE;
			break;
		    }
		} else if (ch == '\r') {
		    tty_putc (fd, ' ');
		    *op++ = '\0';
		    break;
		} else if (isprint(ch)) {
		    *op++ = ch;
		    tty_putc (fd, ch);
		}
	    }

	    strcpy (sval, svfile);
	}

	tty_close (fd);

	/* Erase the prompt. */
	if (key != 's')
	    putch ('\r');
	if (key != '/' && key != 's') {
	    cp = ctrl;
	    if (tgetstr ("ce", &cp) != NULL)
		tputs (ctrl, 1, putch);
	}

	*ival = value;
	return (key);
}


/*
 * TERMINAL handling code.
 * ---------------------------
 *
 *	  fd = tty_open (device, mode)
 *	      tty_close (fd)
 *    ch|EOF = tty_getc (fd)
 *	       tty_putc (fd, ch)
 *	      tty_rawon (fd, flags)
 *	      tty_reset (fd)
 */

#include <sys/types.h>
#include <signal.h>
#include <setjmp.h>

#ifdef SYSV
#include <termio.h>
#else
#include <sgtty.h>
#endif

# ifndef O_NDELAY
#include <fcntl.h>
# endif

#define	CTRLC	3
extern	int errno;
static	jmp_buf jmpbuf;

struct fiodes {
        int     io_flags;               /* fcntl flags                  */
        short   flags;                  /* access mode flags            */
#ifdef SYSV
#define _STDF_INIT      0,0,0, '\0','\0'
        short   tc_iflag;               /* saved SysV tty state         */
        short   tc_oflag;
        short   tc_lflag;
	char	tc_cc[NCC];
#else
#define _STDF_INIT      0
        short   sg_flags;               /* save space for stty flags    */
#endif
};

#define KF_CHARMODE     001             /* char input mode, text files  */
#define KF_NOSEEK       002             /* seeks are illegal on device  */
#define KF_NOSTTY       004             /* stty,gtty calls illegal      */
#define KF_NDELAY       010             /* nonblocking reads            */

struct	fiodes zfd;
static  int tty_fd = -1;		/* FD of tty device		*/
static  char tty_redraw = 'r';		/* screen redraw control code	*/
static  int tty_getraw = 0;		/* raw getc in progress		*/

#ifdef SYSV
static  struct termio tc_state;		/* save terminal state		*/
#else
static  short tty_flags = 0;		/* terminal driver flags	*/
#endif

typedef	int (*PFI)();
static  PFI sigint, sigterm;
static  PFI sigtstp, sigcont;
static  int tty_onsig(), tty_stop(), tty_continue();


/* TTY_OPEN -- Open the terminal.
 */
tty_open (device, mode)
char	*device;
int	mode;
{
	register int fd;

	if ((fd = open (device, mode)) == -1)
	    return (-1);

	zfd.io_flags = 0;
	zfd.flags = KF_NOSEEK;

	return (fd);
}


/* TTY_CLOSE -- Close the terminal.
 */
tty_close (fd)
int	fd;
{
	register struct fiodes *kfp = &zfd;

	if (kfp->flags & KF_CHARMODE)
	    tty_reset (fd);
	close (fd);
}


/* TTY_GETC -- Read a character from the terminal.  In the case of raw mode
 * reads, the process can be suspended and later resumed and a character
 * returned to redraw the screen.
 */
tty_getc (fd)
int	fd;
{
	char	ch;

	sigint  = (PFI) signal (SIGINT,  tty_onsig);
	sigterm = (PFI) signal (SIGTERM, tty_onsig);
	tty_getraw = 1;

	if ((ch = setjmp (jmpbuf)) == 0) {
	    if (read (fd, &ch, 1) <= 0)
		ch = EOF;
	}

	signal (SIGINT,  sigint);
	signal (SIGTERM, sigterm);
	tty_getraw = 0;

	return (ch);
}


/* TTY_PUTC -- Put a character to the terminal.
 */
tty_putc (fd, ch)
int	fd;
int	ch;
{
	char	cch = ch;
	write (fd, &cch, 1);
}


/* TTY_RAWON -- Turn on rare mode and turn off echoing and all input and
 * output character processing.  Interrupts are caught and the interrupt
 * character is returned like any other character.  Save sg_flags for
 * subsequent restoration.
 */
static
tty_rawon (fd, flags)
int	fd;			/* file descriptor */
int	flags;			/* file mode control flags */
{
	register struct	fiodes *kfp = &zfd;
	register int i;

	if (!(kfp->flags & (KF_CHARMODE|KF_NOSTTY))) {
#ifdef SYSV
	    struct  termio tc;

	    ioctl (fd, TCGETA, &tc);

	    /* Save terminal state. */
	    kfp->tc_iflag = tc.c_iflag;
	    kfp->tc_oflag = tc.c_oflag;
	    kfp->tc_lflag = tc.c_lflag;
	    kfp->flags |= KF_CHARMODE;
	    for (i=0;  i < NCC;  i++)
		kfp->tc_cc[i] = tc.c_cc[i];

	    /* Set raw mode. */
	    tc.c_iflag = 0;
	    tc.c_oflag = 0;
	    tc.c_lflag = ISIG;
	    for (i=0;  i < NCC;  i++)
		tc.c_cc[i] = 0377;
	    tc.c_cc[VMIN] = 2;
	    tc.c_cc[VTIME] = 2;
	    ioctl (fd, TCSETAF, &tc);

	    /* Save FD, state flags of raw mode tty device. */
	    tty_fd = fd;
	    tc_state = tc;
#else
	    struct  sgttyb ttystat;

	    ioctl (fd, TIOCGETP, &ttystat);
	    kfp->sg_flags = ttystat.sg_flags;
	    kfp->flags |= KF_CHARMODE;

	    /* Set raw mode in the terminal driver. */
	    ttystat.sg_flags |= CBREAK;
	    ttystat.sg_flags &= ~(ECHO|CRMOD);
	    ioctl (fd, TIOCSETN, &ttystat);

	    /* Save FD, SG_FLAGS of raw mode tty device. */
	    tty_fd = fd;
	    tty_flags = ttystat.sg_flags;
#endif

	    /* Post signal handlers to clear/restore raw mode if process is
	     * suspended.
	     */
	    sigtstp = (PFI) signal (SIGTSTP, tty_stop);
	    sigcont = (PFI) signal (SIGCONT, tty_continue);
	}

	/* Set any file descriptor flags, e.g., for nonblocking reads. */
	if ((flags & KF_NDELAY) && !(kfp->flags & KF_NDELAY)) {
	    kfp->io_flags = fcntl (fd, F_GETFL, 0);
	    fcntl (fd, F_SETFL, kfp->io_flags | O_NDELAY);
	    kfp->flags |= KF_NDELAY;
	} else if (!(flags & KF_NDELAY) && (kfp->flags & KF_NDELAY))
	    fcntl (fd, F_SETFL, kfp->io_flags);
}


/* TTY_RESET -- Clear character at a time mode on the terminal device, if in
 * effect.  This will restore normal line oriented terminal i/o, even if raw
 * mode i/o was set on the physical device when the ioctl status flags were
 * saved.
 */
static
tty_reset (fd)
int	fd;
{
	register struct	fiodes *kfp = &zfd;
	register int i;
#ifdef SYSV
	struct	termio tc;

	if (ioctl (fd, TCGETA, &tc) == -1)
	    return;

	/* If no saved status use current tty status. */
	if (!(kfp->flags & KF_CHARMODE)) {
	    kfp->tc_iflag = tc.c_iflag;
	    kfp->tc_oflag = tc.c_oflag;
	    kfp->tc_lflag = tc.c_lflag;
	    for (i=0;  i < NCC;  i++)
		kfp->tc_cc[i] = tc.c_cc[i];
	}

	/* Clear raw mode. */
	tc.c_iflag = (kfp->tc_iflag | ICRNL);
	tc.c_oflag = (kfp->tc_oflag | OPOST);
	tc.c_lflag = (kfp->tc_lflag | (ICANON|ISIG|ECHO));
	for (i=0;  i < NCC;  i++)
	    tc.c_cc[i] = kfp->tc_cc[i];

	ioctl (fd, TCSETAF, &tc);
	kfp->flags &= ~KF_CHARMODE;
#else
	struct	sgttyb ttystat;

	if (ioctl (fd, TIOCGETP, &ttystat) == -1)
	    return;

	if (!(kfp->flags & KF_CHARMODE))
	    kfp->sg_flags = ttystat.sg_flags;

	ttystat.sg_flags = (kfp->sg_flags | (ECHO|CRMOD)) & ~CBREAK;
	ioctl (fd, TIOCSETN, &ttystat);
	kfp->flags &= ~KF_CHARMODE;
#endif

	if (kfp->flags & KF_NDELAY) {
	    fcntl (fd, F_SETFL, kfp->io_flags & ~O_NDELAY);
	    kfp->flags &= ~KF_NDELAY;
	}

	signal (SIGTSTP, sigtstp);
	signal (SIGCONT, sigcont);
}


/* TTY_ONSIG -- Catch interrupt and return a nonzero status.  Active only while
 * we are reading from the terminal in raw mode.
 */
static
tty_onsig (sig, code, scp)
int	sig;			/* signal which was trapped	*/
int	code;			/* subsignal code (vax)		*/
struct	sigcontext *scp;	/* not used			*/
{
	longjmp (jmpbuf, CTRLC);
}


/* TTY_STOP -- Called when a process is suspended while the terminal is in raw
 * mode; our function is to restore the terminal to normal mode.
 */
static
tty_stop (sig, code, scp)
int	sig;			/* signal which was trapped	*/
int	code;			/* subsignal code (vax)		*/
struct	sigcontext *scp;	/* not used			*/
{
#ifdef SYSV
	register struct	fiodes *kfp = &zfd;
	register int i;
	struct	termio tc;

	if (ioctl (tty_fd, TCGETA, &tc) != -1) {
	    tc.c_iflag = (kfp->tc_iflag | ICRNL);
	    tc.c_oflag = (kfp->tc_oflag | OPOST);
	    tc.c_lflag = (kfp->tc_lflag | (ICANON|ISIG|ECHO));
	    for (i=0;  i < NCC;  i++)
		tc.c_cc[i] = kfp->tc_cc[i];
	    ioctl (tty_fd, TCSETAF, &tc);
	}
#else
	struct	sgttyb ttystat;

	if (ioctl (tty_fd, TIOCGETP, &ttystat) != -1) {
	    ttystat.sg_flags = (tty_flags | (ECHO|CRMOD)) & ~CBREAK;
	    ioctl (tty_fd, TIOCSETN, &ttystat);
	}
#endif

	kill (getpid(), SIGSTOP);
}


/* TTY_CONTINUE -- Called when execution of a process which was suspended with
 * the terminal in raw mode is resumed; our function is to restore the terminal
 * to raw mode.
 */
static
tty_continue (sig, code, scp)
int	sig;			/* signal which was trapped	*/
int	code;			/* subsignal code (vax)		*/
struct	sigcontext *scp;	/* not used			*/
{
#ifdef SYSV
	ioctl (tty_fd, TCSETAF, &tc_state);
#else
	struct	sgttyb ttystat;

	if (ioctl (tty_fd, TIOCGETP, &ttystat) != -1) {
	    ttystat.sg_flags = tty_flags;
	    ioctl (tty_fd, TIOCSETN, &ttystat);
	}
#endif
	if (tty_redraw && tty_getraw)
	    longjmp (jmpbuf, tty_redraw);
}
