Home | History | Annotate | Line # | Download | only in mail
lex.c revision 1.34
      1 /*	$NetBSD: lex.c,v 1.34 2007/10/23 14:58:44 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.34 2007/10/23 14:58:44 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, O_WRONLY)) < 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_WSP(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(STDOUT_FILENO)) == -1)
    388 			err(EXIT_FAILURE, "dup failed");
    389 		if (dup2(fileno(fout), STDOUT_FILENO) == -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 &&
    405 		    dup2(oldfd1, STDOUT_FILENO) == -1)
    406 			err(EXIT_FAILURE, "dup2 failed");
    407 
    408 		(void)signal(SIGPIPE, SIG_IGN);
    409 		close_top_files(fp_stop);
    410 		fp_stop = NULL;
    411 		(void)close(oldfd1);
    412 		oldfd1 = -1;
    413 		(void)signal(SIGPIPE, SIG_DFL);
    414 	}
    415 }
    416 
    417 /*
    418  * Determine if as1 is a valid prefix of as2.
    419  * Return true if yep.
    420  */
    421 static int
    422 isprefix(char *as1, const char *as2)
    423 {
    424 	char *s1;
    425 	const char *s2;
    426 
    427 	s1 = as1;
    428 	s2 = as2;
    429 	while (*s1++ == *s2)
    430 		if (*s2++ == '\0')
    431 			return 1;
    432 	return *--s1 == '\0';
    433 }
    434 
    435 /*
    436  * Find the correct command in the command table corresponding
    437  * to the passed command "word"
    438  */
    439 PUBLIC const struct cmd *
    440 lex(char word[])
    441 {
    442 	const struct cmd *cp;
    443 
    444 	for (cp = &cmdtab[0]; cp->c_name != NULL; cp++)
    445 		if (isprefix(word, cp->c_name))
    446 			return cp;
    447 	return NULL;
    448 }
    449 
    450 PUBLIC char *
    451 get_cmdname(char *buf)
    452 {
    453 	char *cp;
    454 	char *cmd;
    455 	size_t len;
    456 
    457 	for (cp = buf; *cp; cp++)
    458 		if (strchr(" \t0123456789$^.:/-+*'\">|", *cp) != NULL)
    459 			break;
    460 	/* XXX - Don't miss the pipe command! */
    461 	if (cp == buf && *cp == '|')
    462 		cp++;
    463 	len = cp - buf + 1;
    464 	cmd = salloc(len);
    465 	(void)strlcpy(cmd, buf, len);
    466 	return cmd;
    467 }
    468 
    469 /*
    470  * Execute a single command.
    471  * Command functions return 0 for success, 1 for error, and -1
    472  * for abort.  A 1 or -1 aborts a load or source.  A -1 aborts
    473  * the interactive command loop.
    474  * execute_contxt_e is in extern.h.
    475  */
    476 PUBLIC int
    477 execute(char linebuf[], enum execute_contxt_e contxt)
    478 {
    479 	char *word;
    480 	char *arglist[MAXARGC];
    481 	const struct cmd *com = NULL;
    482 	char *volatile cp;
    483 	int c;
    484 	int e = 1;
    485 
    486 	/*
    487 	 * Strip the white space away from the beginning
    488 	 * of the command, then scan out a word, which
    489 	 * consists of anything except digits and white space.
    490 	 *
    491 	 * Handle ! escapes differently to get the correct
    492 	 * lexical conventions.
    493 	 */
    494 
    495 	cp = skip_space(linebuf);
    496 	if (*cp == '!') {
    497 		if (sourcing) {
    498 			(void)printf("Can't \"!\" while sourcing\n");
    499 			goto out;
    500 		}
    501 		(void)shell(cp + 1);
    502 		return 0;
    503 	}
    504 
    505 	word = get_cmdname(cp);
    506 	cp += strlen(word);
    507 
    508 	/*
    509 	 * Look up the command; if not found, bitch.
    510 	 * Normally, a blank command would map to the
    511 	 * first command in the table; while sourcing,
    512 	 * however, we ignore blank lines to eliminate
    513 	 * confusion.
    514 	 */
    515 
    516 	if (sourcing && *word == '\0')
    517 		return 0;
    518 	com = lex(word);
    519 	if (com == NULL) {
    520 		(void)printf("Unknown command: \"%s\"\n", word);
    521 		goto out;
    522 	}
    523 
    524 	/*
    525 	 * See if we should execute the command -- if a conditional
    526 	 * we always execute it, otherwise, check the state of cond.
    527 	 */
    528 
    529 	if ((com->c_argtype & F) == 0 && (cond & CSKIP))
    530 		return 0;
    531 
    532 	/*
    533 	 * Process the arguments to the command, depending
    534 	 * on the type he expects.  Default to an error.
    535 	 * If we are sourcing an interactive command, it's
    536 	 * an error.
    537 	 */
    538 
    539 	if (mailmode == mm_sending && (com->c_argtype & M) == 0) {
    540 		(void)printf("May not execute \"%s\" while sending\n",
    541 		    com->c_name);
    542 		goto out;
    543 	}
    544 	if (sourcing && com->c_argtype & I) {
    545 		(void)printf("May not execute \"%s\" while sourcing\n",
    546 		    com->c_name);
    547 		goto out;
    548 	}
    549 	if (readonly && com->c_argtype & W) {
    550 		(void)printf("May not execute \"%s\" -- message file is read only\n",
    551 		   com->c_name);
    552 		goto out;
    553 	}
    554 	if (contxt == ec_composing && com->c_argtype & R) {
    555 		(void)printf("Cannot recursively invoke \"%s\"\n", com->c_name);
    556 		goto out;
    557 	}
    558 
    559 	if (!sourcing && com->c_pipe && value(ENAME_INTERACTIVE) != NULL) {
    560 		if (setjmp(pipestop))
    561 			goto out;
    562 
    563 		if (setup_piping(cp, com->c_pipe) == -1)
    564 			goto out;
    565 	}
    566 	switch (com->c_argtype & ARGTYPE_MASK) {
    567 	case MSGLIST:
    568 		/*
    569 		 * A message list defaulting to nearest forward
    570 		 * legal message.
    571 		 */
    572 		if (msgvec == 0) {
    573 			(void)printf("Illegal use of \"message list\"\n");
    574 			break;
    575 		}
    576 		if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0)
    577 			break;
    578 		if (c  == 0) {
    579 			*msgvec = first(com->c_msgflag,	com->c_msgmask);
    580 			msgvec[1] = 0;
    581 		}
    582 		if (*msgvec == 0) {
    583 			(void)printf("No applicable messages\n");
    584 			break;
    585 		}
    586 		e = (*com->c_func)(msgvec);
    587 		break;
    588 
    589 	case NDMLIST:
    590 		/*
    591 		 * A message list with no defaults, but no error
    592 		 * if none exist.
    593 		 */
    594 		if (msgvec == 0) {
    595 			(void)printf("Illegal use of \"message list\"\n");
    596 			break;
    597 		}
    598 		if (getmsglist(cp, msgvec, com->c_msgflag) < 0)
    599 			break;
    600 		e = (*com->c_func)(msgvec);
    601 		break;
    602 
    603 	case STRLIST:
    604 		/*
    605 		 * Just the straight string, with
    606 		 * leading blanks removed.
    607 		 */
    608 		cp = skip_space(cp);
    609 		e = (*com->c_func)(cp);
    610 		break;
    611 
    612 	case RAWLIST:
    613 		/*
    614 		 * A vector of strings, in shell style.
    615 		 */
    616 		if ((c = getrawlist(cp, arglist,
    617 				sizeof arglist / sizeof *arglist)) < 0)
    618 			break;
    619 		if (c < com->c_minargs) {
    620 			(void)printf("%s requires at least %d arg(s)\n",
    621 				com->c_name, com->c_minargs);
    622 			break;
    623 		}
    624 		if (c > com->c_maxargs) {
    625 			(void)printf("%s takes no more than %d arg(s)\n",
    626 				com->c_name, com->c_maxargs);
    627 			break;
    628 		}
    629 		e = (*com->c_func)(arglist);
    630 		break;
    631 
    632 	case NOLIST:
    633 		/*
    634 		 * Just the constant zero, for exiting,
    635 		 * eg.
    636 		 */
    637 		e = (*com->c_func)(0);
    638 		break;
    639 
    640 	default:
    641 		errx(1, "Unknown argtype");
    642 	}
    643 
    644 out:
    645 	close_piping();
    646 
    647 	/*
    648 	 * Exit the current source file on
    649 	 * error.
    650 	 */
    651 	if (e) {
    652 		if (e < 0)
    653 			return 1;
    654 		if (loading)
    655 			return 1;
    656 		if (sourcing)
    657 			(void)unstack();
    658 		return 0;
    659 	}
    660 	if (com == NULL)
    661 		return 0;
    662 	if (contxt != ec_autoprint && com->c_argtype & P &&
    663 	    value(ENAME_AUTOPRINT) != NULL && (dot->m_flag & MDELETED) == 0)
    664 		(void)execute(__UNCONST("print ."), ec_autoprint);
    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, ec_normal))
    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