xref: /netbsd-src/usr.bin/mail/lex.c (revision 8b0f9554ff8762542c4defc4f70e1eb76fb508fa)
1 /*	$NetBSD: lex.c,v 1.35 2007/10/29 23:20:38 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.35 2007/10/29 23:20:38 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