xref: /netbsd-src/usr.bin/mail/lex.c (revision 404fbe5fb94ca1e054339640cabb2801ce52dd30)
1 /*	$NetBSD: lex.c,v 1.36 2007/12/17 22:06:00 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.36 2007/12/17 22:06:00 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 	int rval;
196 
197 	omsgCount = get_abs_msgCount();
198 
199 	ibuf = Fopen(mailname, "r");
200 	if (ibuf == NULL)
201 		return -1;
202 	holdsigs();
203 	newsize = fsize(ibuf);
204 	if (newsize == 0 ||		/* mail box is now empty??? */
205 	    newsize < mailsize) {	/* mail box has shrunk??? */
206 		rval = -1;
207 		goto done;
208 	}
209 	if (newsize == mailsize) {
210 		rval = 0;               /* no new mail */
211 		goto done;
212 	}
213 	setptr(ibuf, mailsize);		/* read in new mail */
214 	setmsize(get_abs_msgCount());	/* get the new message count */
215 	mailsize = ftell(ibuf);
216 	rval = get_abs_msgCount() - omsgCount;
217  done:
218 	(void)Fclose(ibuf);
219 	relsesigs();
220 	return rval;
221 }
222 
223 /*
224  * Return a pointer to the comment character, respecting quoting as
225  * done in getrawlist().  The comment character is ignored inside
226  * quotes.
227  */
228 static char *
229 comment_char(char *line)
230 {
231 	char *p;
232 	char quotec;
233 	quotec = '\0';
234 	for (p = line; *p; p++) {
235 		if (quotec != '\0') {
236 			if (*p == quotec)
237 				quotec = '\0';
238 		}
239 		else if (*p == '"' || *p == '\'')
240 			quotec = *p;
241 		else if (*p == COMMENT_CHAR)
242 			return p;
243 	}
244 	return NULL;
245 }
246 
247 /*
248  * When we wake up after ^Z, reprint the prompt.
249  */
250 static void
251 stop(int s)
252 {
253 	sig_t old_action = signal(s, SIG_DFL);
254 	sigset_t nset;
255 
256 	(void)sigemptyset(&nset);
257 	(void)sigaddset(&nset, s);
258 	(void)sigprocmask(SIG_UNBLOCK, &nset, NULL);
259 	(void)kill(0, s);
260 	(void)sigprocmask(SIG_BLOCK, &nset, NULL);
261 	(void)signal(s, old_action);
262 	if (reset_on_stop) {
263 		reset_on_stop = 0;
264 		reset(0);
265 	}
266 }
267 
268 
269 
270 /*
271  * Signal handler is hooked by setup_piping().
272  * Respond to a broken pipe signal --
273  * probably caused by quitting more.
274  */
275 static jmp_buf	pipestop;
276 
277 /*ARGSUSED*/
278 static void
279 brokpipe(int signo __unused)
280 {
281 	longjmp(pipestop, 1);
282 }
283 
284 /*
285  * Check the command line for any requested piping or redirection,
286  * depending on the value of 'c'.  If "enable-pipes" is set, search
287  * the command line (cp) for the first occurrence of the character 'c'
288  * that is not in a quote or (parenthese) group.
289  */
290 PUBLIC char *
291 shellpr(char *cp)
292 {
293 	int quotec;
294 	int level;
295 
296 	if (cp == NULL || value(ENAME_ENABLE_PIPES) == NULL)
297 		return NULL;
298 
299 	level = 0;
300 	quotec = 0;
301 	for (/*EMPTY*/; *cp != '\0'; cp++) {
302 		if (quotec) {
303 			if (*cp == quotec)
304 				quotec = 0;
305 			if (*cp == '\\' &&
306 			    (cp[1] == quotec || cp[1] == '\\'))
307 				cp++;
308 		}
309 		else {
310 			switch (*cp) {
311 			case '|':
312 			case '>':
313 				if (level == 0)
314 					return cp;
315 				break;
316 			case '(':
317 				level++;
318 				break;
319 			case ')':
320 				level--;
321 				break;
322 			case '"':
323 			case '\'':
324 				quotec = *cp;
325 				break;
326 			default:
327 				break;
328 			}
329 		}
330 	}
331 	return NULL;
332 }
333 
334 /*
335  * Setup any pipe or redirection that the command line indicates.
336  * If none, then setup the pager unless "pager-off" is defined.
337  */
338 static FILE *fp_stop = NULL;
339 static int oldfd1 = -1;
340 static int
341 setup_piping(char *cmdline, int c_pipe)
342 {
343 	FILE *fout;
344 	FILE *last_file;
345 	char *cp;
346 
347 	last_file = last_registered_file(0);
348 
349 	fout = NULL;
350 	if ((cp = shellpr(cmdline)) != NULL) {
351 		char c;
352 		c = *cp;
353 		*cp = '\0';
354 		cp++;
355 
356 		if (c == '|') {
357 			if ((fout = Popen(cp, "w")) == NULL) {
358 				warn("Popen: %s", cp);
359 				return -1;
360 			}
361 		}
362 		else {
363 			const char *mode;
364 			assert(c == '>');
365 			mode = *cp == '>' ? "a" : "w";
366 			if (*cp == '>')
367 				cp++;
368 
369 			cp = skip_WSP(cp);
370 			if ((fout = Fopen(cp, mode)) == NULL) {
371 				warn("Fopen: %s", cp);
372 				return -1;
373 			}
374 		}
375 
376 	}
377 	else if (value(ENAME_PAGER_OFF) == NULL && (c_pipe & C_PIPE_PAGER ||
378 		(c_pipe & C_PIPE_CRT && value(ENAME_CRT) != NULL))) {
379 		const char *pager;
380 		pager = value(ENAME_PAGER);
381 		if (pager == NULL || *pager == '\0')
382 			pager = _PATH_MORE;
383 
384 		if ((fout = Popen(pager, "w")) == NULL) {
385 			warn("Popen: %s", pager);
386 			return -1;
387 		}
388 	}
389 
390 	if (fout) {
391 		(void)signal(SIGPIPE, brokpipe);
392 		(void)fflush(stdout);
393 		if ((oldfd1 = dup(STDOUT_FILENO)) == -1)
394 			err(EXIT_FAILURE, "dup failed");
395 		if (dup2(fileno(fout), STDOUT_FILENO) == -1)
396 			err(EXIT_FAILURE, "dup2 failed");
397 		fp_stop = last_file;
398 	}
399 	return 0;
400 }
401 
402 /*
403  * This will close any piping started by setup_piping().
404  */
405 static void
406 close_piping(void)
407 {
408 	if (oldfd1 != -1) {
409 		(void)fflush(stdout);
410 		if (fileno(stdout) != oldfd1 &&
411 		    dup2(oldfd1, STDOUT_FILENO) == -1)
412 			err(EXIT_FAILURE, "dup2 failed");
413 
414 		(void)signal(SIGPIPE, SIG_IGN);
415 		close_top_files(fp_stop);
416 		fp_stop = NULL;
417 		(void)close(oldfd1);
418 		oldfd1 = -1;
419 		(void)signal(SIGPIPE, SIG_DFL);
420 	}
421 }
422 
423 /*
424  * Determine if as1 is a valid prefix of as2.
425  * Return true if yep.
426  */
427 static int
428 isprefix(char *as1, const char *as2)
429 {
430 	char *s1;
431 	const char *s2;
432 
433 	s1 = as1;
434 	s2 = as2;
435 	while (*s1++ == *s2)
436 		if (*s2++ == '\0')
437 			return 1;
438 	return *--s1 == '\0';
439 }
440 
441 /*
442  * Find the correct command in the command table corresponding
443  * to the passed command "word"
444  */
445 PUBLIC const struct cmd *
446 lex(char word[])
447 {
448 	const struct cmd *cp;
449 
450 	for (cp = &cmdtab[0]; cp->c_name != NULL; cp++)
451 		if (isprefix(word, cp->c_name))
452 			return cp;
453 	return NULL;
454 }
455 
456 PUBLIC char *
457 get_cmdname(char *buf)
458 {
459 	char *cp;
460 	char *cmd;
461 	size_t len;
462 
463 	for (cp = buf; *cp; cp++)
464 		if (strchr(" \t0123456789$^.:/-+*'\">|", *cp) != NULL)
465 			break;
466 	/* XXX - Don't miss the pipe command! */
467 	if (cp == buf && *cp == '|')
468 		cp++;
469 	len = cp - buf + 1;
470 	cmd = salloc(len);
471 	(void)strlcpy(cmd, buf, len);
472 	return cmd;
473 }
474 
475 /*
476  * Execute a single command.
477  * Command functions return 0 for success, 1 for error, and -1
478  * for abort.  A 1 or -1 aborts a load or source.  A -1 aborts
479  * the interactive command loop.
480  * execute_contxt_e is in extern.h.
481  */
482 PUBLIC int
483 execute(char linebuf[], enum execute_contxt_e contxt)
484 {
485 	char *word;
486 	char *arglist[MAXARGC];
487 	const struct cmd *com = NULL;
488 	char *volatile cp;
489 	int c;
490 	int e = 1;
491 
492 	/*
493 	 * Strip the white space away from the beginning
494 	 * of the command, then scan out a word, which
495 	 * consists of anything except digits and white space.
496 	 *
497 	 * Handle ! escapes differently to get the correct
498 	 * lexical conventions.
499 	 */
500 
501 	cp = skip_space(linebuf);
502 	if (*cp == '!') {
503 		if (sourcing) {
504 			(void)printf("Can't \"!\" while sourcing\n");
505 			goto out;
506 		}
507 		(void)shell(cp + 1);
508 		return 0;
509 	}
510 
511 	word = get_cmdname(cp);
512 	cp += strlen(word);
513 
514 	/*
515 	 * Look up the command; if not found, bitch.
516 	 * Normally, a blank command would map to the
517 	 * first command in the table; while sourcing,
518 	 * however, we ignore blank lines to eliminate
519 	 * confusion.
520 	 */
521 
522 	if (sourcing && *word == '\0')
523 		return 0;
524 	com = lex(word);
525 	if (com == NULL) {
526 		(void)printf("Unknown command: \"%s\"\n", word);
527 		goto out;
528 	}
529 
530 	/*
531 	 * See if we should execute the command -- if a conditional
532 	 * we always execute it, otherwise, check the state of cond.
533 	 */
534 
535 	if ((com->c_argtype & F) == 0 && (cond & CSKIP))
536 		return 0;
537 
538 	/*
539 	 * Process the arguments to the command, depending
540 	 * on the type he expects.  Default to an error.
541 	 * If we are sourcing an interactive command, it's
542 	 * an error.
543 	 */
544 
545 	if (mailmode == mm_sending && (com->c_argtype & M) == 0) {
546 		(void)printf("May not execute \"%s\" while sending\n",
547 		    com->c_name);
548 		goto out;
549 	}
550 	if (sourcing && com->c_argtype & I) {
551 		(void)printf("May not execute \"%s\" while sourcing\n",
552 		    com->c_name);
553 		goto out;
554 	}
555 	if (readonly && com->c_argtype & W) {
556 		(void)printf("May not execute \"%s\" -- message file is read only\n",
557 		   com->c_name);
558 		goto out;
559 	}
560 	if (contxt == ec_composing && com->c_argtype & R) {
561 		(void)printf("Cannot recursively invoke \"%s\"\n", com->c_name);
562 		goto out;
563 	}
564 
565 	if (!sourcing && com->c_pipe && value(ENAME_INTERACTIVE) != NULL) {
566 		if (setjmp(pipestop))
567 			goto out;
568 
569 		if (setup_piping(cp, com->c_pipe) == -1)
570 			goto out;
571 	}
572 	switch (com->c_argtype & ARGTYPE_MASK) {
573 	case MSGLIST:
574 		/*
575 		 * A message list defaulting to nearest forward
576 		 * legal message.
577 		 */
578 		if (msgvec == 0) {
579 			(void)printf("Illegal use of \"message list\"\n");
580 			break;
581 		}
582 		if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0)
583 			break;
584 		if (c  == 0) {
585 			*msgvec = first(com->c_msgflag,	com->c_msgmask);
586 			msgvec[1] = 0;
587 		}
588 		if (*msgvec == 0) {
589 			(void)printf("No applicable messages\n");
590 			break;
591 		}
592 		e = (*com->c_func)(msgvec);
593 		break;
594 
595 	case NDMLIST:
596 		/*
597 		 * A message list with no defaults, but no error
598 		 * if none exist.
599 		 */
600 		if (msgvec == 0) {
601 			(void)printf("Illegal use of \"message list\"\n");
602 			break;
603 		}
604 		if (getmsglist(cp, msgvec, com->c_msgflag) < 0)
605 			break;
606 		e = (*com->c_func)(msgvec);
607 		break;
608 
609 	case STRLIST:
610 		/*
611 		 * Just the straight string, with
612 		 * leading blanks removed.
613 		 */
614 		cp = skip_space(cp);
615 		e = (*com->c_func)(cp);
616 		break;
617 
618 	case RAWLIST:
619 		/*
620 		 * A vector of strings, in shell style.
621 		 */
622 		if ((c = getrawlist(cp, arglist,
623 				sizeof(arglist) / sizeof(*arglist))) < 0)
624 			break;
625 		if (c < com->c_minargs) {
626 			(void)printf("%s requires at least %d arg(s)\n",
627 				com->c_name, com->c_minargs);
628 			break;
629 		}
630 		if (c > com->c_maxargs) {
631 			(void)printf("%s takes no more than %d arg(s)\n",
632 				com->c_name, com->c_maxargs);
633 			break;
634 		}
635 		e = (*com->c_func)(arglist);
636 		break;
637 
638 	case NOLIST:
639 		/*
640 		 * Just the constant zero, for exiting,
641 		 * eg.
642 		 */
643 		e = (*com->c_func)(0);
644 		break;
645 
646 	default:
647 		errx(1, "Unknown argtype");
648 	}
649 
650 out:
651 	close_piping();
652 
653 	/*
654 	 * Exit the current source file on
655 	 * error.
656 	 */
657 	if (e) {
658 		if (e < 0)
659 			return 1;
660 		if (loading)
661 			return 1;
662 		if (sourcing)
663 			(void)unstack();
664 		return 0;
665 	}
666 	if (com == NULL)
667 		return 0;
668 	if (contxt != ec_autoprint && com->c_argtype & P &&
669 	    value(ENAME_AUTOPRINT) != NULL && (dot->m_flag & MDELETED) == 0)
670 		(void)execute(__UNCONST("print ."), ec_autoprint);
671 	if (!sourcing && (com->c_argtype & T) == 0)
672 		sawcom = 1;
673 	return 0;
674 }
675 
676 
677 /*
678  * The following gets called on receipt of an interrupt.  This is
679  * to abort printout of a command, mainly.
680  * Dispatching here when command() is inactive crashes rcv.
681  * Close all open files except 0, 1, 2, and the temporary.
682  * Also, unstack all source files.
683  */
684 static int	inithdr;	/* am printing startup headers */
685 
686 /*ARGSUSED*/
687 static void
688 intr(int s __unused)
689 {
690 	noreset = 0;
691 	if (!inithdr)
692 		sawcom++;
693 	inithdr = 0;
694 	while (sourcing)
695 		(void)unstack();
696 
697 	close_piping();
698 	close_all_files();
699 
700 	if (image >= 0) {
701 		(void)close(image);
702 		image = -1;
703 	}
704 	(void)fprintf(stderr, "Interrupt\n");
705 	reset(0);
706 }
707 
708 /*
709  * Branch here on hangup signal and simulate "exit".
710  */
711 /*ARGSUSED*/
712 static void
713 hangup(int s __unused)
714 {
715 	/* nothing to do? */
716 	exit(1);
717 }
718 
719 /*
720  * Interpret user commands one by one.  If standard input is not a tty,
721  * print no prompt.
722  */
723 PUBLIC void
724 commands(void)
725 {
726 	int n;
727 	char linebuf[LINESIZE];
728 	int eofloop;
729 
730 	if (!sourcing) {
731 		if (signal(SIGINT, SIG_IGN) != SIG_IGN)
732 			(void)signal(SIGINT, intr);
733 		if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
734 			(void)signal(SIGHUP, hangup);
735 		(void)signal(SIGTSTP, stop);
736 		(void)signal(SIGTTOU, stop);
737 		(void)signal(SIGTTIN, stop);
738 	}
739 	setexit();	/* defined as (void)setjmp(srbuf) in def.h */
740 	eofloop = 0;	/* initialize this after a possible longjmp */
741 	for (;;) {
742 		(void)fflush(stdout);
743 		sreset();
744 		/*
745 		 * Print the prompt, if needed.  Clear out
746 		 * string space, and flush the output.
747 		 */
748 		if (!sourcing && value(ENAME_INTERACTIVE) != NULL) {
749 			if ((prompt = value(ENAME_PROMPT)) == NULL)
750 				prompt = DEFAULT_PROMPT;
751 			prompt = smsgprintf(prompt, dot);
752 			if ((value(ENAME_AUTOINC) != NULL) && (incfile() > 0))
753 				(void)printf("New mail has arrived.\n");
754 			reset_on_stop = 1;
755 #ifndef USE_EDITLINE
756 			(void)printf("%s", prompt);
757 #endif
758 		}
759 		/*
760 		 * Read a line of commands from the current input
761 		 * and handle end of file specially.
762 		 */
763 		n = 0;
764 		for (;;) {
765 #ifdef USE_EDITLINE
766 			if (!sourcing) {
767 				char *line;
768 				if ((line = my_gets(&elm.command, prompt, NULL)) == NULL) {
769 					if (n == 0)
770 						n = -1;
771 					break;
772 				}
773 				(void)strlcpy(linebuf, line, sizeof(linebuf));
774 				setscreensize();	/* so we can resize a window */
775 			}
776 			else {
777 				if (mail_readline(input, &linebuf[n], LINESIZE - n) < 0) {
778 					if (n == 0)
779 						n = -1;
780 					break;
781 				}
782 			}
783 #else /* USE_EDITLINE */
784 			if (mail_readline(input, &linebuf[n], LINESIZE - n) < 0) {
785 				if (n == 0)
786 					n = -1;
787 				break;
788 			}
789 #endif /* USE_EDITLINE */
790 
791 			if (sourcing) {  /* allow comments in source files */
792 				char *ptr;
793 				if ((ptr = comment_char(linebuf)) != NULL)
794 					*ptr = '\0';
795 			}
796 			if ((n = strlen(linebuf)) == 0)
797 				break;
798 			n--;
799 			if (linebuf[n] != '\\')
800 				break;
801 			linebuf[n++] = ' ';
802 		}
803 		reset_on_stop = 0;
804 		if (n < 0) {
805 				/* eof */
806 			if (loading)
807 				break;
808 			if (sourcing) {
809 				(void)unstack();
810 				continue;
811 			}
812 #ifdef USE_EDITLINE
813 			{
814 				char *p;
815 				if (value(ENAME_INTERACTIVE) != NULL &&
816 				    (p = value(ENAME_IGNOREEOF)) != NULL &&
817 				    ++eofloop < (*p == '\0' ? 25 : atoi(p))) {
818 					(void)printf("Use \"quit\" to quit.\n");
819 					continue;
820 				}
821 			}
822 #else
823 			if (value(ENAME_INTERACTIVE) != NULL &&
824 			    value(ENAME_IGNOREEOF) != NULL &&
825 			    ++eofloop < 25) {
826 				(void)printf("Use \"quit\" to quit.\n");
827 				continue;
828 			}
829 #endif
830 			break;
831 		}
832 		eofloop = 0;
833 		if (execute(linebuf, ec_normal))
834 			break;
835 	}
836 }
837 
838 /*
839  * Announce information about the file we are editing.
840  * Return a likely place to set dot.
841  */
842 PUBLIC int
843 newfileinfo(int omsgCount)
844 {
845 	struct message *mp;
846 	int d, n, s, t, u, mdot;
847 	char fname[PATHSIZE];
848 	char *ename;
849 
850 	/*
851 	 * Figure out where to set the 'dot'.  Use the first new or
852 	 * unread message.
853 	 */
854 	for (mp = get_abs_message(omsgCount + 1); mp;
855 	     mp = next_abs_message(mp))
856 		if (mp->m_flag & MNEW)
857 			break;
858 
859 	if (mp == NULL)
860 		for (mp = get_abs_message(omsgCount + 1); mp;
861 		     mp = next_abs_message(mp))
862 			if ((mp->m_flag & MREAD) == 0)
863 				break;
864 	if (mp != NULL)
865 		mdot = get_msgnum(mp);
866 	else
867 		mdot = omsgCount + 1;
868 #ifdef THREAD_SUPPORT
869 	/*
870 	 * See if the message is in the current thread.
871 	 */
872 	if (mp != NULL && get_message(1) != NULL && get_message(mdot) != mp)
873 		mdot = 0;
874 #endif
875 	/*
876 	 * Scan the message array counting the new, unread, deleted,
877 	 * and saved messages.
878 	 */
879 	d = n = s = t = u = 0;
880 	for (mp = get_abs_message(1); mp; mp = next_abs_message(mp)) {
881 		if (mp->m_flag & MNEW)
882 			n++;
883 		if ((mp->m_flag & MREAD) == 0)
884 			u++;
885 		if (mp->m_flag & MDELETED)
886 			d++;
887 		if (mp->m_flag & MSAVED)
888 			s++;
889 		if (mp->m_flag & MTAGGED)
890 			t++;
891 	}
892 	ename = mailname;
893 	if (getfold(fname, sizeof(fname)) >= 0) {
894 		char zname[PATHSIZE];
895 		size_t l;
896 		l = strlen(fname);
897 		if (l < sizeof(fname) - 1)
898 			fname[l++] = '/';
899 		if (strncmp(fname, mailname, l) == 0) {
900 			(void)snprintf(zname, sizeof(zname), "+%s",
901 			    mailname + l);
902 			ename = zname;
903 		}
904 	}
905 	/*
906 	 * Display the statistics.
907 	 */
908 	(void)printf("\"%s\": ", ename);
909 	{
910 		int cnt = get_abs_msgCount();
911 		(void)printf("%d message%s", cnt, cnt == 1 ? "" : "s");
912 	}
913 	if (n > 0)
914 		(void)printf(" %d new", n);
915 	if (u-n > 0)
916 		(void)printf(" %d unread", u);
917 	if (t > 0)
918 		(void)printf(" %d tagged", t);
919 	if (d > 0)
920 		(void)printf(" %d deleted", d);
921 	if (s > 0)
922 		(void)printf(" %d saved", s);
923 	if (readonly)
924 		(void)printf(" [Read only]");
925 	(void)printf("\n");
926 
927 	return mdot;
928 }
929 
930 /*
931  * Announce the presence of the current Mail version,
932  * give the message count, and print a header listing.
933  */
934 PUBLIC void
935 announce(void)
936 {
937 	int vec[2], mdot;
938 
939 	mdot = newfileinfo(0);
940 	vec[0] = mdot;
941 	vec[1] = 0;
942 	if ((dot = get_message(mdot)) == NULL)
943 		dot = get_abs_message(1); /* make sure we get something! */
944 	if (get_abs_msgCount() > 0 && value(ENAME_NOHEADER) == NULL) {
945 		inithdr++;
946 		(void)headers(vec);
947 		inithdr = 0;
948 	}
949 }
950 
951 /*
952  * Print the current version number.
953  */
954 
955 /*ARGSUSED*/
956 PUBLIC int
957 pversion(void *v __unused)
958 {
959 	(void)printf("Version %s\n", version);
960 	return 0;
961 }
962 
963 /*
964  * Load a file of user definitions.
965  */
966 PUBLIC void
967 load(const char *name)
968 {
969 	FILE *in, *oldin;
970 
971 	if ((in = Fopen(name, "r")) == NULL)
972 		return;
973 	oldin = input;
974 	input = in;
975 	loading = 1;
976 	sourcing = 1;
977 	commands();
978 	loading = 0;
979 	sourcing = 0;
980 	input = oldin;
981 	(void)Fclose(in);
982 }
983