Home | History | Annotate | Line # | Download | only in mail
lex.c revision 1.31
      1 /*	$NetBSD: lex.c,v 1.31 2006/12/06 16:26:24 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1980, 1993
      5  *	The Regents of the University of California.  All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  * 3. Neither the name of the University nor the names of its contributors
     16  *    may be used to endorse or promote products derived from this software
     17  *    without specific prior written permission.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     29  * SUCH DAMAGE.
     30  */
     31 
     32 #include <sys/cdefs.h>
     33 #ifndef lint
     34 #if 0
     35 static char sccsid[] = "@(#)lex.c	8.2 (Berkeley) 4/20/95";
     36 #else
     37 __RCSID("$NetBSD: lex.c,v 1.31 2006/12/06 16:26:24 christos Exp $");
     38 #endif
     39 #endif /* not lint */
     40 
     41 #include <assert.h>
     42 
     43 #include "rcv.h"
     44 #include <util.h>
     45 #include "extern.h"
     46 #ifdef USE_EDITLINE
     47 #include "complete.h"
     48 #endif
     49 #include "format.h"
     50 #include "thread.h"
     51 
     52 /*
     53  * Mail -- a mail program
     54  *
     55  * Lexical processing of commands.
     56  */
     57 
     58 static const char *prompt = DEFAULT_PROMPT;
     59 static int	*msgvec;
     60 static int	reset_on_stop;		/* do a reset() if stopped */
     61 
     62 
     63 /*
     64  * Set the size of the message vector used to construct argument
     65  * lists to message list functions.
     66  */
     67 static void
     68 setmsize(int sz)
     69 {
     70 	if (msgvec != 0)
     71 		free(msgvec);
     72 	msgvec = ecalloc((size_t) (sz + 1), sizeof *msgvec);
     73 }
     74 
     75 /*
     76  * Set up editing on the given file name.
     77  * If the first character of name is %, we are considered to be
     78  * editing the file, otherwise we are reading our mail which has
     79  * signficance for mbox and so forth.
     80  */
     81 PUBLIC int
     82 setfile(const char *name)
     83 {
     84 	FILE *ibuf;
     85 	int i, fd;
     86 	struct stat stb;
     87 	char isedit = *name != '%' || getuserid(myname) != (int)getuid();
     88 	const char *who = name[1] ? name + 1 : myname;
     89 	static int shudclob;
     90 	char tempname[PATHSIZE];
     91 
     92 	if ((name = expand(name)) == NULL)
     93 		return -1;
     94 
     95 	if ((ibuf = Fopen(name, "r")) == NULL) {
     96 		if (!isedit && errno == ENOENT)
     97 			goto nomail;
     98 		warn("%s", name);
     99 		return -1;
    100 	}
    101 
    102 	if (fstat(fileno(ibuf), &stb) < 0) {
    103 		warn("fstat");
    104 		(void)Fclose(ibuf);
    105 		return -1;
    106 	}
    107 
    108 	switch (stb.st_mode & S_IFMT) {
    109 	case S_IFDIR:
    110 		(void)Fclose(ibuf);
    111 		errno = EISDIR;
    112 		warn("%s", name);
    113 		return -1;
    114 
    115 	case S_IFREG:
    116 		break;
    117 
    118 	default:
    119 		(void)Fclose(ibuf);
    120 		errno = EINVAL;
    121 		warn("%s", name);
    122 		return -1;
    123 	}
    124 
    125 	/*
    126 	 * Looks like all will be well.  We must now relinquish our
    127 	 * hold on the current set of stuff.  Must hold signals
    128 	 * while we are reading the new file, else we will ruin
    129 	 * the message[] data structure.
    130 	 */
    131 
    132 	holdsigs();
    133 	if (shudclob)
    134 		quit();
    135 
    136 	/*
    137 	 * Copy the messages into /tmp
    138 	 * and set pointers.
    139 	 */
    140 
    141 	readonly = 0;
    142 	if ((i = open(name, 1)) < 0)
    143 		readonly++;
    144 	else
    145 		(void)close(i);
    146 	if (shudclob) {
    147 		(void)fclose(itf);
    148 		(void)fclose(otf);
    149 	}
    150 	shudclob = 1;
    151 	edit = isedit;
    152 	(void)strcpy(prevfile, mailname);
    153 	if (name != mailname)
    154 		(void)strcpy(mailname, name);
    155 	mailsize = fsize(ibuf);
    156 	(void)snprintf(tempname, sizeof(tempname),
    157 	    "%s/mail.RxXXXXXXXXXX", tmpdir);
    158 	if ((fd = mkstemp(tempname)) == -1 ||
    159 	    (otf = fdopen(fd, "w")) == NULL)
    160 		err(1, "%s", tempname);
    161 	(void)fcntl(fileno(otf), F_SETFD, FD_CLOEXEC);
    162 	if ((itf = fopen(tempname, "r")) == NULL)
    163 		err(1, "%s", tempname);
    164 	(void)fcntl(fileno(itf), F_SETFD, FD_CLOEXEC);
    165 	(void)rm(tempname);
    166 	setptr(ibuf, (off_t)0);
    167 	setmsize(get_abs_msgCount());
    168 	/*
    169 	 * New mail may have arrived while we were reading
    170 	 * the mail file, so reset mailsize to be where
    171 	 * we really are in the file...
    172 	 */
    173 	mailsize = ftell(ibuf);
    174 	(void)Fclose(ibuf);
    175 	relsesigs();
    176 	sawcom = 0;
    177 	if (!edit && get_abs_msgCount() == 0) {
    178 nomail:
    179 		(void)fprintf(stderr, "No mail for %s\n", who);
    180 		return -1;
    181 	}
    182 	return 0;
    183 }
    184 
    185 /*
    186  * Incorporate any new mail that has arrived since we first
    187  * started reading mail.
    188  */
    189 PUBLIC int
    190 incfile(void)
    191 {
    192 	off_t newsize;
    193 	int omsgCount;
    194 	FILE *ibuf;
    195 
    196 	omsgCount = get_abs_msgCount();
    197 
    198 	ibuf = Fopen(mailname, "r");
    199 	if (ibuf == NULL)
    200 		return -1;
    201 	holdsigs();
    202 	newsize = fsize(ibuf);
    203 	if (newsize == 0)
    204 		return -1;		/* mail box is now empty??? */
    205 	if (newsize < mailsize)
    206 		return -1;              /* mail box has shrunk??? */
    207 	if (newsize == mailsize)
    208 		return 0;               /* no new mail */
    209 	setptr(ibuf, mailsize);		/* read in new mail */
    210 	setmsize(get_abs_msgCount());	/* get the new message count */
    211 	mailsize = ftell(ibuf);
    212 	(void)Fclose(ibuf);
    213 	relsesigs();
    214 	return get_abs_msgCount() - omsgCount;
    215 }
    216 
    217 /*
    218  * Return a pointer to the comment character, respecting quoting as
    219  * done in getrawlist().  The comment character is ignored inside
    220  * quotes.
    221  */
    222 static char *
    223 comment_char(char *line)
    224 {
    225 	char *p;
    226 	char quotec;
    227 	quotec = '\0';
    228 	for (p = line; *p; p++) {
    229 		if (quotec != '\0') {
    230 			if (*p == quotec)
    231 				quotec = '\0';
    232 		}
    233 		else if (*p == '"' || *p == '\'')
    234 			quotec = *p;
    235 		else if (*p == COMMENT_CHAR)
    236 			return p;
    237 	}
    238 	return NULL;
    239 }
    240 
    241 /*
    242  * When we wake up after ^Z, reprint the prompt.
    243  */
    244 static void
    245 stop(int s)
    246 {
    247 	sig_t old_action = signal(s, SIG_DFL);
    248 	sigset_t nset;
    249 
    250 	(void)sigemptyset(&nset);
    251 	(void)sigaddset(&nset, s);
    252 	(void)sigprocmask(SIG_UNBLOCK, &nset, NULL);
    253 	(void)kill(0, s);
    254 	(void)sigprocmask(SIG_BLOCK, &nset, NULL);
    255 	(void)signal(s, old_action);
    256 	if (reset_on_stop) {
    257 		reset_on_stop = 0;
    258 		reset(0);
    259 	}
    260 }
    261 
    262 
    263 
    264 /*
    265  * Signal handler is hooked by setup_piping().
    266  * Respond to a broken pipe signal --
    267  * probably caused by quitting more.
    268  */
    269 static jmp_buf	pipestop;
    270 
    271 /*ARGSUSED*/
    272 static void
    273 brokpipe(int signo __unused)
    274 {
    275 	longjmp(pipestop, 1);
    276 }
    277 
    278 /*
    279  * Check the command line for any requested piping or redirection,
    280  * depending on the value of 'c'.  If "enable-pipes" is set, search
    281  * the command line (cp) for the first occurrence of the character 'c'
    282  * that is not in a quote or (parenthese) group.
    283  */
    284 PUBLIC char *
    285 shellpr(char *cp)
    286 {
    287 	int quotec;
    288 	int level;
    289 
    290 	if (cp == NULL || value(ENAME_ENABLE_PIPES) == NULL)
    291 		return NULL;
    292 
    293 	level = 0;
    294 	quotec = 0;
    295 	for (/*EMPTY*/; *cp != '\0'; cp++) {
    296 		if (quotec) {
    297 			if (*cp == quotec)
    298 				quotec = 0;
    299 			if (*cp == '\\' &&
    300 			    (cp[1] == quotec || cp[1] == '\\'))
    301 				cp++;
    302 		}
    303 		else {
    304 			switch (*cp) {
    305 			case '|':
    306 			case '>':
    307 				if (level == 0)
    308 					return cp;
    309 				break;
    310 			case '(':
    311 				level++;
    312 				break;
    313 			case ')':
    314 				level--;
    315 				break;
    316 			case '"':
    317 			case '\'':
    318 				quotec = *cp;
    319 				break;
    320 			default:
    321 				break;
    322 			}
    323 		}
    324 	}
    325 	return NULL;
    326 }
    327 
    328 /*
    329  * Setup any pipe or redirection that the command line indicates.
    330  * If none, then setup the pager unless "pager-off" is defined.
    331  */
    332 static FILE *fp_stop = NULL;
    333 static int oldfd1 = -1;
    334 static int
    335 setup_piping(char *cmdline, int c_pipe)
    336 {
    337 	FILE *fout;
    338 	FILE *last_file;
    339 	char *cp;
    340 
    341 	last_file = last_registered_file(0);
    342 
    343 	fout = NULL;
    344 	if ((cp = shellpr(cmdline)) != NULL) {
    345 		char c;
    346 		c = *cp;
    347 		*cp = '\0';
    348 		cp++;
    349 
    350 		if (c == '|') {
    351 			if ((fout = Popen(cp, "w")) == NULL) {
    352 				warn("Popen: %s", cp);
    353 				return -1;
    354 			}
    355 		}
    356 		else {
    357 			const char *mode;
    358 			assert(c == '>');
    359 			mode = *cp == '>' ? "a" : "w";
    360 			if (*cp == '>')
    361 				cp++;
    362 
    363 			cp = skip_blank(cp);
    364 			if ((fout = Fopen(cp, mode)) == NULL) {
    365 				warn("Fopen: %s", cp);
    366 				return -1;
    367 			}
    368 		}
    369 
    370 	}
    371 	else if (value(ENAME_PAGER_OFF) == NULL && (c_pipe & C_PIPE_PAGER ||
    372 		(c_pipe & C_PIPE_CRT && value(ENAME_CRT) != NULL))) {
    373 		const char *pager;
    374 		pager = value(ENAME_PAGER);
    375 		if (pager == NULL || *pager == '\0')
    376 			pager = _PATH_MORE;
    377 
    378 		if ((fout = Popen(pager, "w")) == NULL) {
    379 			warn("Popen: %s", pager);
    380 			return -1;
    381 		}
    382 	}
    383 
    384 	if (fout) {
    385 		(void)signal(SIGPIPE, brokpipe);
    386 		(void)fflush(stdout);
    387 		if ((oldfd1 = dup(1)) == -1)
    388 			err(EXIT_FAILURE, "dup failed");
    389 		if (dup2(fileno(fout), 1) == -1)
    390 			err(EXIT_FAILURE, "dup2 failed");
    391 		fp_stop = last_file;
    392 	}
    393 	return 0;
    394 }
    395 
    396 /*
    397  * This will close any piping started by setup_piping().
    398  */
    399 static void
    400 close_piping(void)
    401 {
    402 	if (oldfd1 != -1) {
    403 		(void)fflush(stdout);
    404 		if (fileno(stdout) != oldfd1 && dup2(oldfd1, 1) == -1)
    405 			err(EXIT_FAILURE, "dup2 failed");
    406 
    407 		(void)signal(SIGPIPE, SIG_IGN);
    408 		close_top_files(fp_stop);
    409 		fp_stop = NULL;
    410 		(void)close(oldfd1);
    411 		oldfd1 = -1;
    412 		(void)signal(SIGPIPE, SIG_DFL);
    413 	}
    414 }
    415 
    416 /*
    417  * Determine if as1 is a valid prefix of as2.
    418  * Return true if yep.
    419  */
    420 static int
    421 isprefix(char *as1, const char *as2)
    422 {
    423 	char *s1;
    424 	const char *s2;
    425 
    426 	s1 = as1;
    427 	s2 = as2;
    428 	while (*s1++ == *s2)
    429 		if (*s2++ == '\0')
    430 			return 1;
    431 	return *--s1 == '\0';
    432 }
    433 
    434 /*
    435  * Find the correct command in the command table corresponding
    436  * to the passed command "word"
    437  */
    438 PUBLIC const struct cmd *
    439 lex(char word[])
    440 {
    441 	const struct cmd *cp;
    442 
    443 	for (cp = &cmdtab[0]; cp->c_name != NULL; cp++)
    444 		if (isprefix(word, cp->c_name))
    445 			return cp;
    446 	return NULL;
    447 }
    448 
    449 PUBLIC char *
    450 get_cmdname(char *buf)
    451 {
    452 	char *cp;
    453 	char *cmd;
    454 	size_t len;
    455 
    456 	for (cp = buf; *cp; cp++)
    457 		if (strchr(" \t0123456789$^.:/-+*'\">|", *cp) != NULL)
    458 			break;
    459 	len = cp - buf + 1;
    460 	cmd = salloc(len);
    461 	(void)strlcpy(cmd, buf, len);
    462 	return cmd;
    463 }
    464 
    465 /*
    466  * Execute a single command.
    467  * Command functions return 0 for success, 1 for error, and -1
    468  * for abort.  A 1 or -1 aborts a load or source.  A -1 aborts
    469  * the interactive command loop.
    470  * Contxt is non-zero if called while composing mail.
    471  */
    472 PUBLIC int
    473 execute(char linebuf[], int contxt)
    474 {
    475 	char *word;
    476 	char *arglist[MAXARGC];
    477 	const struct cmd *com = NULL;
    478 	char *volatile cp;
    479 	int c;
    480 	int muvec[2];
    481 	int e = 1;
    482 
    483 	/*
    484 	 * Strip the white space away from the beginning
    485 	 * of the command, then scan out a word, which
    486 	 * consists of anything except digits and white space.
    487 	 *
    488 	 * Handle ! escapes differently to get the correct
    489 	 * lexical conventions.
    490 	 */
    491 
    492 	cp = skip_blank(linebuf);
    493 	if (*cp == '!') {
    494 		if (sourcing) {
    495 			(void)printf("Can't \"!\" while sourcing\n");
    496 			goto out;
    497 		}
    498 		(void)shell(cp + 1);
    499 		return 0;
    500 	}
    501 
    502 	word = get_cmdname(cp);
    503 	cp += strlen(word);
    504 
    505 	/*
    506 	 * Look up the command; if not found, bitch.
    507 	 * Normally, a blank command would map to the
    508 	 * first command in the table; while sourcing,
    509 	 * however, we ignore blank lines to eliminate
    510 	 * confusion.
    511 	 */
    512 
    513 	if (sourcing && *word == '\0')
    514 		return 0;
    515 	com = lex(word);
    516 	if (com == NULL) {
    517 		(void)printf("Unknown command: \"%s\"\n", word);
    518 		goto out;
    519 	}
    520 
    521 	/*
    522 	 * See if we should execute the command -- if a conditional
    523 	 * we always execute it, otherwise, check the state of cond.
    524 	 */
    525 
    526 	if ((com->c_argtype & F) == 0 && (cond & CSKIP))
    527 		return 0;
    528 
    529 	/*
    530 	 * Process the arguments to the command, depending
    531 	 * on the type he expects.  Default to an error.
    532 	 * If we are sourcing an interactive command, it's
    533 	 * an error.
    534 	 */
    535 
    536 	if (mailmode == mm_sending && (com->c_argtype & M) == 0) {
    537 		(void)printf("May not execute \"%s\" while sending\n",
    538 		    com->c_name);
    539 		goto out;
    540 	}
    541 	if (sourcing && com->c_argtype & I) {
    542 		(void)printf("May not execute \"%s\" while sourcing\n",
    543 		    com->c_name);
    544 		goto out;
    545 	}
    546 	if (readonly && com->c_argtype & W) {
    547 		(void)printf("May not execute \"%s\" -- message file is read only\n",
    548 		   com->c_name);
    549 		goto out;
    550 	}
    551 	if (contxt && com->c_argtype & R) {
    552 		(void)printf("Cannot recursively invoke \"%s\"\n", com->c_name);
    553 		goto out;
    554 	}
    555 
    556 	if (!sourcing && com->c_pipe && value(ENAME_INTERACTIVE) != NULL) {
    557 		if (setjmp(pipestop))
    558 			goto out;
    559 
    560 		if (setup_piping(cp, com->c_pipe) == -1)
    561 			goto out;
    562 	}
    563 	switch (com->c_argtype & ARGTYPE_MASK) {
    564 	case MSGLIST:
    565 		/*
    566 		 * A message list defaulting to nearest forward
    567 		 * legal message.
    568 		 */
    569 		if (msgvec == 0) {
    570 			(void)printf("Illegal use of \"message list\"\n");
    571 			break;
    572 		}
    573 		if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0)
    574 			break;
    575 		if (c  == 0) {
    576 			*msgvec = first(com->c_msgflag,	com->c_msgmask);
    577 			msgvec[1] = 0;
    578 		}
    579 		if (*msgvec == 0) {
    580 			(void)printf("No applicable messages\n");
    581 			break;
    582 		}
    583 		e = (*com->c_func)(msgvec);
    584 		break;
    585 
    586 	case NDMLIST:
    587 		/*
    588 		 * A message list with no defaults, but no error
    589 		 * if none exist.
    590 		 */
    591 		if (msgvec == 0) {
    592 			(void)printf("Illegal use of \"message list\"\n");
    593 			break;
    594 		}
    595 		if (getmsglist(cp, msgvec, com->c_msgflag) < 0)
    596 			break;
    597 		e = (*com->c_func)(msgvec);
    598 		break;
    599 
    600 	case STRLIST:
    601 		/*
    602 		 * Just the straight string, with
    603 		 * leading blanks removed.
    604 		 */
    605 		while (isspace((unsigned char)*cp))
    606 			cp++;
    607 		e = (*com->c_func)(cp);
    608 		break;
    609 
    610 	case RAWLIST:
    611 		/*
    612 		 * A vector of strings, in shell style.
    613 		 */
    614 		if ((c = getrawlist(cp, arglist,
    615 				sizeof arglist / sizeof *arglist)) < 0)
    616 			break;
    617 		if (c < com->c_minargs) {
    618 			(void)printf("%s requires at least %d arg(s)\n",
    619 				com->c_name, com->c_minargs);
    620 			break;
    621 		}
    622 		if (c > com->c_maxargs) {
    623 			(void)printf("%s takes no more than %d arg(s)\n",
    624 				com->c_name, com->c_maxargs);
    625 			break;
    626 		}
    627 		e = (*com->c_func)(arglist);
    628 		break;
    629 
    630 	case NOLIST:
    631 		/*
    632 		 * Just the constant zero, for exiting,
    633 		 * eg.
    634 		 */
    635 		e = (*com->c_func)(0);
    636 		break;
    637 
    638 	default:
    639 		errx(1, "Unknown argtype");
    640 	}
    641 
    642 out:
    643 	close_piping();
    644 
    645 	/*
    646 	 * Exit the current source file on
    647 	 * error.
    648 	 */
    649 	if (e) {
    650 		if (e < 0)
    651 			return 1;
    652 		if (loading)
    653 			return 1;
    654 		if (sourcing)
    655 			(void)unstack();
    656 		return 0;
    657 	}
    658 	if (com == NULL)
    659 		return 0;
    660 	if (value(ENAME_AUTOPRINT) != NULL && com->c_argtype & P)
    661 		if ((dot->m_flag & MDELETED) == 0) {
    662 			muvec[0] = get_msgnum(dot);
    663 			muvec[1] = 0;
    664 			(void)type(muvec);
    665 		}
    666 	if (!sourcing && (com->c_argtype & T) == 0)
    667 		sawcom = 1;
    668 	return 0;
    669 }
    670 
    671 
    672 /*
    673  * The following gets called on receipt of an interrupt.  This is
    674  * to abort printout of a command, mainly.
    675  * Dispatching here when command() is inactive crashes rcv.
    676  * Close all open files except 0, 1, 2, and the temporary.
    677  * Also, unstack all source files.
    678  */
    679 static int	inithdr;	/* am printing startup headers */
    680 
    681 /*ARGSUSED*/
    682 static void
    683 intr(int s __unused)
    684 {
    685 	noreset = 0;
    686 	if (!inithdr)
    687 		sawcom++;
    688 	inithdr = 0;
    689 	while (sourcing)
    690 		(void)unstack();
    691 
    692 	close_piping();
    693 	close_all_files();
    694 
    695 	if (image >= 0) {
    696 		(void)close(image);
    697 		image = -1;
    698 	}
    699 	(void)fprintf(stderr, "Interrupt\n");
    700 	reset(0);
    701 }
    702 
    703 /*
    704  * Branch here on hangup signal and simulate "exit".
    705  */
    706 /*ARGSUSED*/
    707 static void
    708 hangup(int s __unused)
    709 {
    710 	/* nothing to do? */
    711 	exit(1);
    712 }
    713 
    714 /*
    715  * Interpret user commands one by one.  If standard input is not a tty,
    716  * print no prompt.
    717  */
    718 PUBLIC void
    719 commands(void)
    720 {
    721 	int n;
    722 	char linebuf[LINESIZE];
    723 	int eofloop;
    724 
    725 	if (!sourcing) {
    726 		if (signal(SIGINT, SIG_IGN) != SIG_IGN)
    727 			(void)signal(SIGINT, intr);
    728 		if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
    729 			(void)signal(SIGHUP, hangup);
    730 		(void)signal(SIGTSTP, stop);
    731 		(void)signal(SIGTTOU, stop);
    732 		(void)signal(SIGTTIN, stop);
    733 	}
    734 	setexit();	/* defined as (void)setjmp(srbuf) in def.h */
    735 	eofloop = 0;	/* initialize this after a possible longjmp */
    736 	for (;;) {
    737 		(void)fflush(stdout);
    738 		sreset();
    739 		/*
    740 		 * Print the prompt, if needed.  Clear out
    741 		 * string space, and flush the output.
    742 		 */
    743 		if (!sourcing && value(ENAME_INTERACTIVE) != NULL) {
    744 			if ((prompt = value(ENAME_PROMPT)) == NULL)
    745 				prompt = DEFAULT_PROMPT;
    746 			prompt = smsgprintf(prompt, dot);
    747 			if ((value(ENAME_AUTOINC) != NULL) && (incfile() > 0))
    748 				(void)printf("New mail has arrived.\n");
    749 			reset_on_stop = 1;
    750 #ifndef USE_EDITLINE
    751 			(void)printf("%s", prompt);
    752 #endif
    753 		}
    754 		/*
    755 		 * Read a line of commands from the current input
    756 		 * and handle end of file specially.
    757 		 */
    758 		n = 0;
    759 		for (;;) {
    760 #ifdef USE_EDITLINE
    761 			if (!sourcing) {
    762 				char *line;
    763 				if ((line = my_gets(&elm.command, prompt, NULL)) == NULL) {
    764 					if (n == 0)
    765 						n = -1;
    766 					break;
    767 				}
    768 				(void)strlcpy(linebuf, line, sizeof(linebuf));
    769 				setscreensize();	/* so we can resize a window */
    770 			}
    771 			else {
    772 				if (mail_readline(input, &linebuf[n], LINESIZE - n) < 0) {
    773 					if (n == 0)
    774 						n = -1;
    775 					break;
    776 				}
    777 			}
    778 #else /* USE_EDITLINE */
    779 			if (mail_readline(input, &linebuf[n], LINESIZE - n) < 0) {
    780 				if (n == 0)
    781 					n = -1;
    782 				break;
    783 			}
    784 #endif /* USE_EDITLINE */
    785 
    786 			if (sourcing) {  /* allow comments in source files */
    787 				char *ptr;
    788 				if ((ptr = comment_char(linebuf)) != NULL)
    789 					*ptr = '\0';
    790 			}
    791 			if ((n = strlen(linebuf)) == 0)
    792 				break;
    793 			n--;
    794 			if (linebuf[n] != '\\')
    795 				break;
    796 			linebuf[n++] = ' ';
    797 		}
    798 		reset_on_stop = 0;
    799 		if (n < 0) {
    800 				/* eof */
    801 			if (loading)
    802 				break;
    803 			if (sourcing) {
    804 				(void)unstack();
    805 				continue;
    806 			}
    807 #ifdef USE_EDITLINE
    808 			{
    809 				char *p;
    810 				if (value(ENAME_INTERACTIVE) != NULL &&
    811 				    (p = value(ENAME_IGNOREEOF)) != NULL &&
    812 				    ++eofloop < (*p == '\0' ? 25 : atoi(p))) {
    813 					(void)printf("Use \"quit\" to quit.\n");
    814 					continue;
    815 				}
    816 			}
    817 #else
    818 			if (value(ENAME_INTERACTIVE) != NULL &&
    819 			    value(ENAME_IGNOREEOF) != NULL &&
    820 			    ++eofloop < 25) {
    821 				(void)printf("Use \"quit\" to quit.\n");
    822 				continue;
    823 			}
    824 #endif
    825 			break;
    826 		}
    827 		eofloop = 0;
    828 		if (execute(linebuf, 0))
    829 			break;
    830 	}
    831 }
    832 
    833 /*
    834  * Announce information about the file we are editing.
    835  * Return a likely place to set dot.
    836  */
    837 PUBLIC int
    838 newfileinfo(int omsgCount)
    839 {
    840 	struct message *mp;
    841 	int d, n, s, t, u, mdot;
    842 	char fname[PATHSIZE];
    843 	char *ename;
    844 
    845 	/*
    846 	 * Figure out where to set the 'dot'.  Use the first new or
    847 	 * unread message.
    848 	 */
    849 	for (mp = get_abs_message(omsgCount + 1); mp;
    850 	     mp = next_abs_message(mp))
    851 		if (mp->m_flag & MNEW)
    852 			break;
    853 
    854 	if (mp == NULL)
    855 		for (mp = get_abs_message(omsgCount + 1); mp;
    856 		     mp = next_abs_message(mp))
    857 			if ((mp->m_flag & MREAD) == 0)
    858 				break;
    859 	if (mp != NULL)
    860 		mdot = get_msgnum(mp);
    861 	else
    862 		mdot = omsgCount + 1;
    863 #ifdef THREAD_SUPPORT
    864 	/*
    865 	 * See if the message is in the current thread.
    866 	 */
    867 	if (mp != NULL && get_message(1) != NULL && get_message(mdot) != mp)
    868 		mdot = 0;
    869 #endif
    870 	/*
    871 	 * Scan the message array counting the new, unread, deleted,
    872 	 * and saved messages.
    873 	 */
    874 	d = n = s = t = u = 0;
    875 	for (mp = get_abs_message(1); mp; mp = next_abs_message(mp)) {
    876 		if (mp->m_flag & MNEW)
    877 			n++;
    878 		if ((mp->m_flag & MREAD) == 0)
    879 			u++;
    880 		if (mp->m_flag & MDELETED)
    881 			d++;
    882 		if (mp->m_flag & MSAVED)
    883 			s++;
    884 		if (mp->m_flag & MTAGGED)
    885 			t++;
    886 	}
    887 	ename = mailname;
    888 	if (getfold(fname, sizeof(fname)) >= 0) {
    889 		char zname[PATHSIZE];
    890 		size_t l;
    891 		l = strlen(fname);
    892 		if (l < sizeof(fname) - 1)
    893 			fname[l++] = '/';
    894 		if (strncmp(fname, mailname, l) == 0) {
    895 			(void)snprintf(zname, sizeof(zname), "+%s",
    896 			    mailname + l);
    897 			ename = zname;
    898 		}
    899 	}
    900 	/*
    901 	 * Display the statistics.
    902 	 */
    903 	(void)printf("\"%s\": ", ename);
    904 	{
    905 		int cnt = get_abs_msgCount();
    906 		(void)printf("%d message%s", cnt, cnt == 1 ? "" : "s");
    907 	}
    908 	if (n > 0)
    909 		(void)printf(" %d new", n);
    910 	if (u-n > 0)
    911 		(void)printf(" %d unread", u);
    912 	if (t > 0)
    913 		(void)printf(" %d tagged", t);
    914 	if (d > 0)
    915 		(void)printf(" %d deleted", d);
    916 	if (s > 0)
    917 		(void)printf(" %d saved", s);
    918 	if (readonly)
    919 		(void)printf(" [Read only]");
    920 	(void)printf("\n");
    921 
    922 	return mdot;
    923 }
    924 
    925 /*
    926  * Announce the presence of the current Mail version,
    927  * give the message count, and print a header listing.
    928  */
    929 PUBLIC void
    930 announce(void)
    931 {
    932 	int vec[2], mdot;
    933 
    934 	mdot = newfileinfo(0);
    935 	vec[0] = mdot;
    936 	vec[1] = 0;
    937 	if ((dot = get_message(mdot)) == NULL)
    938 		dot = get_abs_message(1); /* make sure we get something! */
    939 	if (get_abs_msgCount() > 0 && value(ENAME_NOHEADER) == NULL) {
    940 		inithdr++;
    941 		(void)headers(vec);
    942 		inithdr = 0;
    943 	}
    944 }
    945 
    946 /*
    947  * Print the current version number.
    948  */
    949 
    950 /*ARGSUSED*/
    951 PUBLIC int
    952 pversion(void *v __unused)
    953 {
    954 	(void)printf("Version %s\n", version);
    955 	return 0;
    956 }
    957 
    958 /*
    959  * Load a file of user definitions.
    960  */
    961 PUBLIC void
    962 load(const char *name)
    963 {
    964 	FILE *in, *oldin;
    965 
    966 	if ((in = Fopen(name, "r")) == NULL)
    967 		return;
    968 	oldin = input;
    969 	input = in;
    970 	loading = 1;
    971 	sourcing = 1;
    972 	commands();
    973 	loading = 0;
    974 	sourcing = 0;
    975 	input = oldin;
    976 	(void)Fclose(in);
    977 }
    978