Home | History | Annotate | Line # | Download | only in mail
lex.c revision 1.30
      1 /*	$NetBSD: lex.c,v 1.30 2006/11/28 18:45:32 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.30 2006/11/28 18:45:32 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 		const char *pager;
    373 		pager = value(ENAME_PAGER);
    374 		if (pager == NULL || *pager == '\0')
    375 			pager = _PATH_MORE;
    376 
    377 		if ((fout = Popen(pager, "w")) == NULL) {
    378 			warn("Popen: %s", pager);
    379 			return -1;
    380 		}
    381 	}
    382 
    383 	if (fout) {
    384 		(void)signal(SIGPIPE, brokpipe);
    385 		(void)fflush(stdout);
    386 		if ((oldfd1 = dup(1)) == -1)
    387 			err(EXIT_FAILURE, "dup failed");
    388 		if (dup2(fileno(fout), 1) == -1)
    389 			err(EXIT_FAILURE, "dup2 failed");
    390 		fp_stop = last_file;
    391 	}
    392 	return 0;
    393 }
    394 
    395 /*
    396  * This will close any piping started by setup_piping().
    397  */
    398 static void
    399 close_piping(void)
    400 {
    401 	if (oldfd1 != -1) {
    402 		(void)fflush(stdout);
    403 		if (fileno(stdout) != oldfd1 && dup2(oldfd1, 1) == -1)
    404 			err(EXIT_FAILURE, "dup2 failed");
    405 
    406 		(void)signal(SIGPIPE, SIG_IGN);
    407 		close_top_files(fp_stop);
    408 		fp_stop = NULL;
    409 		(void)close(oldfd1);
    410 		oldfd1 = -1;
    411 		(void)signal(SIGPIPE, SIG_DFL);
    412 	}
    413 }
    414 
    415 /*
    416  * Determine if as1 is a valid prefix of as2.
    417  * Return true if yep.
    418  */
    419 static int
    420 isprefix(char *as1, const char *as2)
    421 {
    422 	char *s1;
    423 	const char *s2;
    424 
    425 	s1 = as1;
    426 	s2 = as2;
    427 	while (*s1++ == *s2)
    428 		if (*s2++ == '\0')
    429 			return 1;
    430 	return *--s1 == '\0';
    431 }
    432 
    433 /*
    434  * Find the correct command in the command table corresponding
    435  * to the passed command "word"
    436  */
    437 PUBLIC const struct cmd *
    438 lex(char word[])
    439 {
    440 	const struct cmd *cp;
    441 
    442 	for (cp = &cmdtab[0]; cp->c_name != NULL; cp++)
    443 		if (isprefix(word, cp->c_name))
    444 			return cp;
    445 	return NULL;
    446 }
    447 
    448 PUBLIC char *
    449 get_cmdname(char *buf)
    450 {
    451 	char *cp;
    452 	char *cmd;
    453 	size_t len;
    454 
    455 	for (cp = buf; *cp; cp++)
    456 		if (strchr(" \t0123456789$^.:/-+*'\">|", *cp) != NULL)
    457 			break;
    458 	len = cp - buf + 1;
    459 	cmd = salloc(len);
    460 	(void)strlcpy(cmd, buf, len);
    461 	return cmd;
    462 }
    463 
    464 /*
    465  * Execute a single command.
    466  * Command functions return 0 for success, 1 for error, and -1
    467  * for abort.  A 1 or -1 aborts a load or source.  A -1 aborts
    468  * the interactive command loop.
    469  * Contxt is non-zero if called while composing mail.
    470  */
    471 PUBLIC int
    472 execute(char linebuf[], int contxt)
    473 {
    474 	char *word;
    475 	char *arglist[MAXARGC];
    476 	const struct cmd *com = NULL;
    477 	char *volatile cp;
    478 	int c;
    479 	int muvec[2];
    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 && 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 (value(ENAME_AUTOPRINT) != NULL && com->c_argtype & P)
    660 		if ((dot->m_flag & MDELETED) == 0) {
    661 			muvec[0] = get_msgnum(dot);
    662 			muvec[1] = 0;
    663 			(void)type(muvec);
    664 		}
    665 	if (!sourcing && (com->c_argtype & T) == 0)
    666 		sawcom = 1;
    667 	return 0;
    668 }
    669 
    670 
    671 /*
    672  * The following gets called on receipt of an interrupt.  This is
    673  * to abort printout of a command, mainly.
    674  * Dispatching here when command() is inactive crashes rcv.
    675  * Close all open files except 0, 1, 2, and the temporary.
    676  * Also, unstack all source files.
    677  */
    678 static int	inithdr;	/* am printing startup headers */
    679 
    680 /*ARGSUSED*/
    681 static void
    682 intr(int s __unused)
    683 {
    684 	noreset = 0;
    685 	if (!inithdr)
    686 		sawcom++;
    687 	inithdr = 0;
    688 	while (sourcing)
    689 		(void)unstack();
    690 
    691 	close_piping();
    692 	close_all_files();
    693 
    694 	if (image >= 0) {
    695 		(void)close(image);
    696 		image = -1;
    697 	}
    698 	(void)fprintf(stderr, "Interrupt\n");
    699 	reset(0);
    700 }
    701 
    702 /*
    703  * Branch here on hangup signal and simulate "exit".
    704  */
    705 /*ARGSUSED*/
    706 static void
    707 hangup(int s __unused)
    708 {
    709 	/* nothing to do? */
    710 	exit(1);
    711 }
    712 
    713 /*
    714  * Interpret user commands one by one.  If standard input is not a tty,
    715  * print no prompt.
    716  */
    717 PUBLIC void
    718 commands(void)
    719 {
    720 	int n;
    721 	char linebuf[LINESIZE];
    722 	int eofloop;
    723 
    724 	if (!sourcing) {
    725 		if (signal(SIGINT, SIG_IGN) != SIG_IGN)
    726 			(void)signal(SIGINT, intr);
    727 		if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
    728 			(void)signal(SIGHUP, hangup);
    729 		(void)signal(SIGTSTP, stop);
    730 		(void)signal(SIGTTOU, stop);
    731 		(void)signal(SIGTTIN, stop);
    732 	}
    733 	setexit();	/* defined as (void)setjmp(srbuf) in def.h */
    734 	eofloop = 0;	/* initialize this after a possible longjmp */
    735 	for (;;) {
    736 		(void)fflush(stdout);
    737 		sreset();
    738 		/*
    739 		 * Print the prompt, if needed.  Clear out
    740 		 * string space, and flush the output.
    741 		 */
    742 		if (!sourcing && value(ENAME_INTERACTIVE) != NULL) {
    743 			if ((prompt = value(ENAME_PROMPT)) == NULL)
    744 				prompt = DEFAULT_PROMPT;
    745 			prompt = smsgprintf(prompt, dot);
    746 			if ((value(ENAME_AUTOINC) != NULL) && (incfile() > 0))
    747 				(void)printf("New mail has arrived.\n");
    748 			reset_on_stop = 1;
    749 #ifndef USE_EDITLINE
    750 			(void)printf("%s", prompt);
    751 #endif
    752 		}
    753 		/*
    754 		 * Read a line of commands from the current input
    755 		 * and handle end of file specially.
    756 		 */
    757 		n = 0;
    758 		for (;;) {
    759 #ifdef USE_EDITLINE
    760 			if (!sourcing) {
    761 				char *line;
    762 				if ((line = my_gets(&elm.command, prompt, NULL)) == NULL) {
    763 					if (n == 0)
    764 						n = -1;
    765 					break;
    766 				}
    767 				(void)strlcpy(linebuf, line, sizeof(linebuf));
    768 				setscreensize();	/* so we can resize a window */
    769 			}
    770 			else {
    771 				if (mail_readline(input, &linebuf[n], LINESIZE - n) < 0) {
    772 					if (n == 0)
    773 						n = -1;
    774 					break;
    775 				}
    776 			}
    777 #else /* USE_EDITLINE */
    778 			if (mail_readline(input, &linebuf[n], LINESIZE - n) < 0) {
    779 				if (n == 0)
    780 					n = -1;
    781 				break;
    782 			}
    783 #endif /* USE_EDITLINE */
    784 
    785 			if (sourcing) {  /* allow comments in source files */
    786 				char *ptr;
    787 				if ((ptr = comment_char(linebuf)) != NULL)
    788 					*ptr = '\0';
    789 			}
    790 			if ((n = strlen(linebuf)) == 0)
    791 				break;
    792 			n--;
    793 			if (linebuf[n] != '\\')
    794 				break;
    795 			linebuf[n++] = ' ';
    796 		}
    797 		reset_on_stop = 0;
    798 		if (n < 0) {
    799 				/* eof */
    800 			if (loading)
    801 				break;
    802 			if (sourcing) {
    803 				(void)unstack();
    804 				continue;
    805 			}
    806 #ifdef USE_EDITLINE
    807 			{
    808 				char *p;
    809 				if (value(ENAME_INTERACTIVE) != NULL &&
    810 				    (p = value(ENAME_IGNOREEOF)) != NULL &&
    811 				    ++eofloop < (*p == '\0' ? 25 : atoi(p))) {
    812 					(void)printf("Use \"quit\" to quit.\n");
    813 					continue;
    814 				}
    815 			}
    816 #else
    817 			if (value(ENAME_INTERACTIVE) != NULL &&
    818 			    value(ENAME_IGNOREEOF) != NULL &&
    819 			    ++eofloop < 25) {
    820 				(void)printf("Use \"quit\" to quit.\n");
    821 				continue;
    822 			}
    823 #endif
    824 			break;
    825 		}
    826 		eofloop = 0;
    827 		if (execute(linebuf, 0))
    828 			break;
    829 	}
    830 }
    831 
    832 /*
    833  * Announce information about the file we are editing.
    834  * Return a likely place to set dot.
    835  */
    836 PUBLIC int
    837 newfileinfo(int omsgCount)
    838 {
    839 	struct message *mp;
    840 	int d, n, s, t, u, mdot;
    841 	char fname[PATHSIZE];
    842 	char *ename;
    843 
    844 	/*
    845 	 * Figure out where to set the 'dot'.  Use the first new or
    846 	 * unread message.
    847 	 */
    848 	for (mp = get_abs_message(omsgCount + 1); mp;
    849 	     mp = next_abs_message(mp))
    850 		if (mp->m_flag & MNEW)
    851 			break;
    852 
    853 	if (mp == NULL)
    854 		for (mp = get_abs_message(omsgCount + 1); mp;
    855 		     mp = next_abs_message(mp))
    856 			if ((mp->m_flag & MREAD) == 0)
    857 				break;
    858 	if (mp != NULL)
    859 		mdot = get_msgnum(mp);
    860 	else
    861 		mdot = omsgCount + 1;
    862 #ifdef THREAD_SUPPORT
    863 	/*
    864 	 * See if the message is in the current thread.
    865 	 */
    866 	if (mp != NULL && get_message(1) != NULL && get_message(mdot) != mp)
    867 		mdot = 0;
    868 #endif
    869 	/*
    870 	 * Scan the message array counting the new, unread, deleted,
    871 	 * and saved messages.
    872 	 */
    873 	d = n = s = t = u = 0;
    874 	for (mp = get_abs_message(1); mp; mp = next_abs_message(mp)) {
    875 		if (mp->m_flag & MNEW)
    876 			n++;
    877 		if ((mp->m_flag & MREAD) == 0)
    878 			u++;
    879 		if (mp->m_flag & MDELETED)
    880 			d++;
    881 		if (mp->m_flag & MSAVED)
    882 			s++;
    883 		if (mp->m_flag & MTAGGED)
    884 			t++;
    885 	}
    886 	ename = mailname;
    887 	if (getfold(fname, sizeof(fname)) >= 0) {
    888 		char zname[PATHSIZE];
    889 		size_t l;
    890 		l = strlen(fname);
    891 		if (l < sizeof(fname) - 1)
    892 			fname[l++] = '/';
    893 		if (strncmp(fname, mailname, l) == 0) {
    894 			(void)snprintf(zname, sizeof(zname), "+%s",
    895 			    mailname + l);
    896 			ename = zname;
    897 		}
    898 	}
    899 	/*
    900 	 * Display the statistics.
    901 	 */
    902 	(void)printf("\"%s\": ", ename);
    903 	{
    904 		int cnt = get_abs_msgCount();
    905 		(void)printf("%d message%s", cnt, cnt == 1 ? "" : "s");
    906 	}
    907 	if (n > 0)
    908 		(void)printf(" %d new", n);
    909 	if (u-n > 0)
    910 		(void)printf(" %d unread", u);
    911 	if (t > 0)
    912 		(void)printf(" %d tagged", t);
    913 	if (d > 0)
    914 		(void)printf(" %d deleted", d);
    915 	if (s > 0)
    916 		(void)printf(" %d saved", s);
    917 	if (readonly)
    918 		(void)printf(" [Read only]");
    919 	(void)printf("\n");
    920 
    921 	return mdot;
    922 }
    923 
    924 /*
    925  * Announce the presence of the current Mail version,
    926  * give the message count, and print a header listing.
    927  */
    928 PUBLIC void
    929 announce(void)
    930 {
    931 	int vec[2], mdot;
    932 
    933 	mdot = newfileinfo(0);
    934 	vec[0] = mdot;
    935 	vec[1] = 0;
    936 	if ((dot = get_message(mdot)) == NULL)
    937 		dot = get_abs_message(1); /* make sure we get something! */
    938 	if (get_abs_msgCount() > 0 && value(ENAME_NOHEADER) == NULL) {
    939 		inithdr++;
    940 		(void)headers(vec);
    941 		inithdr = 0;
    942 	}
    943 }
    944 
    945 /*
    946  * Print the current version number.
    947  */
    948 
    949 /*ARGSUSED*/
    950 PUBLIC int
    951 pversion(void *v __unused)
    952 {
    953 	(void)printf("Version %s\n", version);
    954 	return 0;
    955 }
    956 
    957 /*
    958  * Load a file of user definitions.
    959  */
    960 PUBLIC void
    961 load(const char *name)
    962 {
    963 	FILE *in, *oldin;
    964 
    965 	if ((in = Fopen(name, "r")) == NULL)
    966 		return;
    967 	oldin = input;
    968 	input = in;
    969 	loading = 1;
    970 	sourcing = 1;
    971 	commands();
    972 	loading = 0;
    973 	sourcing = 0;
    974 	input = oldin;
    975 	(void)Fclose(in);
    976 }
    977