Home | History | Annotate | Line # | Download | only in mail
lex.c revision 1.30.2.2
      1 /*	$NetBSD: lex.c,v 1.30.2.2 2007/02/19 13:36:58 tron 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.30.2.2 2007/02/19 13:36:58 tron 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  * execute_contxt_e is in extern.h.
    471  */
    472 PUBLIC int
    473 execute(char linebuf[], enum execute_contxt_e contxt)
    474 {
    475 	char *word;
    476 	char *arglist[MAXARGC];
    477 	const struct cmd *com = NULL;
    478 	char *volatile cp;
    479 	int c;
    480 	int e = 1;
    481 
    482 	/*
    483 	 * Strip the white space away from the beginning
    484 	 * of the command, then scan out a word, which
    485 	 * consists of anything except digits and white space.
    486 	 *
    487 	 * Handle ! escapes differently to get the correct
    488 	 * lexical conventions.
    489 	 */
    490 
    491 	cp = skip_blank(linebuf);
    492 	if (*cp == '!') {
    493 		if (sourcing) {
    494 			(void)printf("Can't \"!\" while sourcing\n");
    495 			goto out;
    496 		}
    497 		(void)shell(cp + 1);
    498 		return 0;
    499 	}
    500 
    501 	word = get_cmdname(cp);
    502 	cp += strlen(word);
    503 
    504 	/*
    505 	 * Look up the command; if not found, bitch.
    506 	 * Normally, a blank command would map to the
    507 	 * first command in the table; while sourcing,
    508 	 * however, we ignore blank lines to eliminate
    509 	 * confusion.
    510 	 */
    511 
    512 	if (sourcing && *word == '\0')
    513 		return 0;
    514 	com = lex(word);
    515 	if (com == NULL) {
    516 		(void)printf("Unknown command: \"%s\"\n", word);
    517 		goto out;
    518 	}
    519 
    520 	/*
    521 	 * See if we should execute the command -- if a conditional
    522 	 * we always execute it, otherwise, check the state of cond.
    523 	 */
    524 
    525 	if ((com->c_argtype & F) == 0 && (cond & CSKIP))
    526 		return 0;
    527 
    528 	/*
    529 	 * Process the arguments to the command, depending
    530 	 * on the type he expects.  Default to an error.
    531 	 * If we are sourcing an interactive command, it's
    532 	 * an error.
    533 	 */
    534 
    535 	if (mailmode == mm_sending && (com->c_argtype & M) == 0) {
    536 		(void)printf("May not execute \"%s\" while sending\n",
    537 		    com->c_name);
    538 		goto out;
    539 	}
    540 	if (sourcing && com->c_argtype & I) {
    541 		(void)printf("May not execute \"%s\" while sourcing\n",
    542 		    com->c_name);
    543 		goto out;
    544 	}
    545 	if (readonly && com->c_argtype & W) {
    546 		(void)printf("May not execute \"%s\" -- message file is read only\n",
    547 		   com->c_name);
    548 		goto out;
    549 	}
    550 	if (contxt == ec_composing && com->c_argtype & R) {
    551 		(void)printf("Cannot recursively invoke \"%s\"\n", com->c_name);
    552 		goto out;
    553 	}
    554 
    555 	if (!sourcing && com->c_pipe && value(ENAME_INTERACTIVE) != NULL) {
    556 		if (setjmp(pipestop))
    557 			goto out;
    558 
    559 		if (setup_piping(cp, com->c_pipe) == -1)
    560 			goto out;
    561 	}
    562 	switch (com->c_argtype & ARGTYPE_MASK) {
    563 	case MSGLIST:
    564 		/*
    565 		 * A message list defaulting to nearest forward
    566 		 * legal message.
    567 		 */
    568 		if (msgvec == 0) {
    569 			(void)printf("Illegal use of \"message list\"\n");
    570 			break;
    571 		}
    572 		if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0)
    573 			break;
    574 		if (c  == 0) {
    575 			*msgvec = first(com->c_msgflag,	com->c_msgmask);
    576 			msgvec[1] = 0;
    577 		}
    578 		if (*msgvec == 0) {
    579 			(void)printf("No applicable messages\n");
    580 			break;
    581 		}
    582 		e = (*com->c_func)(msgvec);
    583 		break;
    584 
    585 	case NDMLIST:
    586 		/*
    587 		 * A message list with no defaults, but no error
    588 		 * if none exist.
    589 		 */
    590 		if (msgvec == 0) {
    591 			(void)printf("Illegal use of \"message list\"\n");
    592 			break;
    593 		}
    594 		if (getmsglist(cp, msgvec, com->c_msgflag) < 0)
    595 			break;
    596 		e = (*com->c_func)(msgvec);
    597 		break;
    598 
    599 	case STRLIST:
    600 		/*
    601 		 * Just the straight string, with
    602 		 * leading blanks removed.
    603 		 */
    604 		while (isspace((unsigned char)*cp))
    605 			cp++;
    606 		e = (*com->c_func)(cp);
    607 		break;
    608 
    609 	case RAWLIST:
    610 		/*
    611 		 * A vector of strings, in shell style.
    612 		 */
    613 		if ((c = getrawlist(cp, arglist,
    614 				sizeof arglist / sizeof *arglist)) < 0)
    615 			break;
    616 		if (c < com->c_minargs) {
    617 			(void)printf("%s requires at least %d arg(s)\n",
    618 				com->c_name, com->c_minargs);
    619 			break;
    620 		}
    621 		if (c > com->c_maxargs) {
    622 			(void)printf("%s takes no more than %d arg(s)\n",
    623 				com->c_name, com->c_maxargs);
    624 			break;
    625 		}
    626 		e = (*com->c_func)(arglist);
    627 		break;
    628 
    629 	case NOLIST:
    630 		/*
    631 		 * Just the constant zero, for exiting,
    632 		 * eg.
    633 		 */
    634 		e = (*com->c_func)(0);
    635 		break;
    636 
    637 	default:
    638 		errx(1, "Unknown argtype");
    639 	}
    640 
    641 out:
    642 	close_piping();
    643 
    644 	/*
    645 	 * Exit the current source file on
    646 	 * error.
    647 	 */
    648 	if (e) {
    649 		if (e < 0)
    650 			return 1;
    651 		if (loading)
    652 			return 1;
    653 		if (sourcing)
    654 			(void)unstack();
    655 		return 0;
    656 	}
    657 	if (com == NULL)
    658 		return 0;
    659 	if (contxt != ec_autoprint && com->c_argtype & P &&
    660 	    value(ENAME_AUTOPRINT) != NULL && (dot->m_flag & MDELETED) == 0)
    661 		(void)execute(__UNCONST("print ."), ec_autoprint);
    662 	if (!sourcing && (com->c_argtype & T) == 0)
    663 		sawcom = 1;
    664 	return 0;
    665 }
    666 
    667 
    668 /*
    669  * The following gets called on receipt of an interrupt.  This is
    670  * to abort printout of a command, mainly.
    671  * Dispatching here when command() is inactive crashes rcv.
    672  * Close all open files except 0, 1, 2, and the temporary.
    673  * Also, unstack all source files.
    674  */
    675 static int	inithdr;	/* am printing startup headers */
    676 
    677 /*ARGSUSED*/
    678 static void
    679 intr(int s __unused)
    680 {
    681 	noreset = 0;
    682 	if (!inithdr)
    683 		sawcom++;
    684 	inithdr = 0;
    685 	while (sourcing)
    686 		(void)unstack();
    687 
    688 	close_piping();
    689 	close_all_files();
    690 
    691 	if (image >= 0) {
    692 		(void)close(image);
    693 		image = -1;
    694 	}
    695 	(void)fprintf(stderr, "Interrupt\n");
    696 	reset(0);
    697 }
    698 
    699 /*
    700  * Branch here on hangup signal and simulate "exit".
    701  */
    702 /*ARGSUSED*/
    703 static void
    704 hangup(int s __unused)
    705 {
    706 	/* nothing to do? */
    707 	exit(1);
    708 }
    709 
    710 /*
    711  * Interpret user commands one by one.  If standard input is not a tty,
    712  * print no prompt.
    713  */
    714 PUBLIC void
    715 commands(void)
    716 {
    717 	int n;
    718 	char linebuf[LINESIZE];
    719 	int eofloop;
    720 
    721 	if (!sourcing) {
    722 		if (signal(SIGINT, SIG_IGN) != SIG_IGN)
    723 			(void)signal(SIGINT, intr);
    724 		if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
    725 			(void)signal(SIGHUP, hangup);
    726 		(void)signal(SIGTSTP, stop);
    727 		(void)signal(SIGTTOU, stop);
    728 		(void)signal(SIGTTIN, stop);
    729 	}
    730 	setexit();	/* defined as (void)setjmp(srbuf) in def.h */
    731 	eofloop = 0;	/* initialize this after a possible longjmp */
    732 	for (;;) {
    733 		(void)fflush(stdout);
    734 		sreset();
    735 		/*
    736 		 * Print the prompt, if needed.  Clear out
    737 		 * string space, and flush the output.
    738 		 */
    739 		if (!sourcing && value(ENAME_INTERACTIVE) != NULL) {
    740 			if ((prompt = value(ENAME_PROMPT)) == NULL)
    741 				prompt = DEFAULT_PROMPT;
    742 			prompt = smsgprintf(prompt, dot);
    743 			if ((value(ENAME_AUTOINC) != NULL) && (incfile() > 0))
    744 				(void)printf("New mail has arrived.\n");
    745 			reset_on_stop = 1;
    746 #ifndef USE_EDITLINE
    747 			(void)printf("%s", prompt);
    748 #endif
    749 		}
    750 		/*
    751 		 * Read a line of commands from the current input
    752 		 * and handle end of file specially.
    753 		 */
    754 		n = 0;
    755 		for (;;) {
    756 #ifdef USE_EDITLINE
    757 			if (!sourcing) {
    758 				char *line;
    759 				if ((line = my_gets(&elm.command, prompt, NULL)) == NULL) {
    760 					if (n == 0)
    761 						n = -1;
    762 					break;
    763 				}
    764 				(void)strlcpy(linebuf, line, sizeof(linebuf));
    765 				setscreensize();	/* so we can resize a window */
    766 			}
    767 			else {
    768 				if (mail_readline(input, &linebuf[n], LINESIZE - n) < 0) {
    769 					if (n == 0)
    770 						n = -1;
    771 					break;
    772 				}
    773 			}
    774 #else /* USE_EDITLINE */
    775 			if (mail_readline(input, &linebuf[n], LINESIZE - n) < 0) {
    776 				if (n == 0)
    777 					n = -1;
    778 				break;
    779 			}
    780 #endif /* USE_EDITLINE */
    781 
    782 			if (sourcing) {  /* allow comments in source files */
    783 				char *ptr;
    784 				if ((ptr = comment_char(linebuf)) != NULL)
    785 					*ptr = '\0';
    786 			}
    787 			if ((n = strlen(linebuf)) == 0)
    788 				break;
    789 			n--;
    790 			if (linebuf[n] != '\\')
    791 				break;
    792 			linebuf[n++] = ' ';
    793 		}
    794 		reset_on_stop = 0;
    795 		if (n < 0) {
    796 				/* eof */
    797 			if (loading)
    798 				break;
    799 			if (sourcing) {
    800 				(void)unstack();
    801 				continue;
    802 			}
    803 #ifdef USE_EDITLINE
    804 			{
    805 				char *p;
    806 				if (value(ENAME_INTERACTIVE) != NULL &&
    807 				    (p = value(ENAME_IGNOREEOF)) != NULL &&
    808 				    ++eofloop < (*p == '\0' ? 25 : atoi(p))) {
    809 					(void)printf("Use \"quit\" to quit.\n");
    810 					continue;
    811 				}
    812 			}
    813 #else
    814 			if (value(ENAME_INTERACTIVE) != NULL &&
    815 			    value(ENAME_IGNOREEOF) != NULL &&
    816 			    ++eofloop < 25) {
    817 				(void)printf("Use \"quit\" to quit.\n");
    818 				continue;
    819 			}
    820 #endif
    821 			break;
    822 		}
    823 		eofloop = 0;
    824 		if (execute(linebuf, ec_normal))
    825 			break;
    826 	}
    827 }
    828 
    829 /*
    830  * Announce information about the file we are editing.
    831  * Return a likely place to set dot.
    832  */
    833 PUBLIC int
    834 newfileinfo(int omsgCount)
    835 {
    836 	struct message *mp;
    837 	int d, n, s, t, u, mdot;
    838 	char fname[PATHSIZE];
    839 	char *ename;
    840 
    841 	/*
    842 	 * Figure out where to set the 'dot'.  Use the first new or
    843 	 * unread message.
    844 	 */
    845 	for (mp = get_abs_message(omsgCount + 1); mp;
    846 	     mp = next_abs_message(mp))
    847 		if (mp->m_flag & MNEW)
    848 			break;
    849 
    850 	if (mp == NULL)
    851 		for (mp = get_abs_message(omsgCount + 1); mp;
    852 		     mp = next_abs_message(mp))
    853 			if ((mp->m_flag & MREAD) == 0)
    854 				break;
    855 	if (mp != NULL)
    856 		mdot = get_msgnum(mp);
    857 	else
    858 		mdot = omsgCount + 1;
    859 #ifdef THREAD_SUPPORT
    860 	/*
    861 	 * See if the message is in the current thread.
    862 	 */
    863 	if (mp != NULL && get_message(1) != NULL && get_message(mdot) != mp)
    864 		mdot = 0;
    865 #endif
    866 	/*
    867 	 * Scan the message array counting the new, unread, deleted,
    868 	 * and saved messages.
    869 	 */
    870 	d = n = s = t = u = 0;
    871 	for (mp = get_abs_message(1); mp; mp = next_abs_message(mp)) {
    872 		if (mp->m_flag & MNEW)
    873 			n++;
    874 		if ((mp->m_flag & MREAD) == 0)
    875 			u++;
    876 		if (mp->m_flag & MDELETED)
    877 			d++;
    878 		if (mp->m_flag & MSAVED)
    879 			s++;
    880 		if (mp->m_flag & MTAGGED)
    881 			t++;
    882 	}
    883 	ename = mailname;
    884 	if (getfold(fname, sizeof(fname)) >= 0) {
    885 		char zname[PATHSIZE];
    886 		size_t l;
    887 		l = strlen(fname);
    888 		if (l < sizeof(fname) - 1)
    889 			fname[l++] = '/';
    890 		if (strncmp(fname, mailname, l) == 0) {
    891 			(void)snprintf(zname, sizeof(zname), "+%s",
    892 			    mailname + l);
    893 			ename = zname;
    894 		}
    895 	}
    896 	/*
    897 	 * Display the statistics.
    898 	 */
    899 	(void)printf("\"%s\": ", ename);
    900 	{
    901 		int cnt = get_abs_msgCount();
    902 		(void)printf("%d message%s", cnt, cnt == 1 ? "" : "s");
    903 	}
    904 	if (n > 0)
    905 		(void)printf(" %d new", n);
    906 	if (u-n > 0)
    907 		(void)printf(" %d unread", u);
    908 	if (t > 0)
    909 		(void)printf(" %d tagged", t);
    910 	if (d > 0)
    911 		(void)printf(" %d deleted", d);
    912 	if (s > 0)
    913 		(void)printf(" %d saved", s);
    914 	if (readonly)
    915 		(void)printf(" [Read only]");
    916 	(void)printf("\n");
    917 
    918 	return mdot;
    919 }
    920 
    921 /*
    922  * Announce the presence of the current Mail version,
    923  * give the message count, and print a header listing.
    924  */
    925 PUBLIC void
    926 announce(void)
    927 {
    928 	int vec[2], mdot;
    929 
    930 	mdot = newfileinfo(0);
    931 	vec[0] = mdot;
    932 	vec[1] = 0;
    933 	if ((dot = get_message(mdot)) == NULL)
    934 		dot = get_abs_message(1); /* make sure we get something! */
    935 	if (get_abs_msgCount() > 0 && value(ENAME_NOHEADER) == NULL) {
    936 		inithdr++;
    937 		(void)headers(vec);
    938 		inithdr = 0;
    939 	}
    940 }
    941 
    942 /*
    943  * Print the current version number.
    944  */
    945 
    946 /*ARGSUSED*/
    947 PUBLIC int
    948 pversion(void *v __unused)
    949 {
    950 	(void)printf("Version %s\n", version);
    951 	return 0;
    952 }
    953 
    954 /*
    955  * Load a file of user definitions.
    956  */
    957 PUBLIC void
    958 load(const char *name)
    959 {
    960 	FILE *in, *oldin;
    961 
    962 	if ((in = Fopen(name, "r")) == NULL)
    963 		return;
    964 	oldin = input;
    965 	input = in;
    966 	loading = 1;
    967 	sourcing = 1;
    968 	commands();
    969 	loading = 0;
    970 	sourcing = 0;
    971 	input = oldin;
    972 	(void)Fclose(in);
    973 }
    974