xref: /netbsd-src/usr.bin/mail/lex.c (revision a2bd7fea898123b237ab60a3404189867f088cfc)
1 /*	$NetBSD: lex.c,v 1.47 2024/08/17 19:18:10 andvar 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.47 2024/08/17 19:18:10 andvar Exp $");
38 #endif
39 #endif /* not lint */
40 
41 #include <assert.h>
42 #include <util.h>
43 
44 #include "rcv.h"
45 #include "extern.h"
46 #ifdef USE_EDITLINE
47 #include "complete.h"
48 #endif
49 #include "format.h"
50 #include "sig.h"
51 #include "thread.h"
52 
53 /*
54  * Mail -- a mail program
55  *
56  * Lexical processing of commands.
57  */
58 
59 static const char *prompt = DEFAULT_PROMPT;
60 static int	*msgvec;
61 static int	inithdr;		/* Am printing startup headers. */
62 static jmp_buf	jmpbuf;			/* The reset jmpbuf */
63 static int	reset_on_stop;		/* To do job control longjmp. */
64 
65 #ifdef DEBUG_FILE_LEAK
66 struct glue {
67 	struct  glue *next;
68 	int     niobs;
69 	FILE    *iobs;
70 };
71 extern struct glue __sglue;
72 
73 static int open_fd_cnt;
74 static int open_fp_cnt;
75 
76 static int
77 file_count(void)
78 {
79 	struct glue *gp;
80 	FILE *fp;
81 	int n;
82 	int cnt;
83 
84 	cnt = 0;
85 	for (gp = &__sglue; gp; gp = gp->next) {
86 		for (fp = gp->iobs, n = gp->niobs; --n >= 0; fp++)
87 			if (fp->_flags)
88 				cnt++;
89 	}
90 	return cnt;
91 }
92 
93 static int
94 fds_count(void)
95 {
96 	int maxfd;
97 	int cnt;
98 	int fd;
99 
100 	maxfd = fcntl(0, F_MAXFD);
101 	if (maxfd == -1) {
102 		warn("fcntl");
103 		return -1;
104 	}
105 
106 	cnt = 0;
107 	for (fd = 0; fd <= maxfd; fd++) {
108 		struct stat sb;
109 
110 		if (fstat(fd, &sb) != -1)
111 			cnt++;
112 		else if (errno != EBADF
113 #ifdef BROKEN_CLONE_STAT /* see PRs 37878 and 37550 */
114 		    && errno != EOPNOTSUPP
115 #endif
116 			)
117 			warn("fstat(%d): errno=%d", fd, errno);
118 	}
119 	return cnt;
120 }
121 
122 static void
123 file_leak_init(void)
124 {
125 	open_fd_cnt = fds_count();
126 	open_fp_cnt = file_count();
127 }
128 
129 static void
130 file_leak_check(void)
131 {
132 	if (open_fp_cnt != file_count() ||
133 	    open_fd_cnt != fds_count()) {
134 		(void)printf("FILE LEAK WARNING: "
135 		    "fp-count: %d (%d)  "
136 		    "fd-count: %d (%d)  max-fd: %d\n",
137 		    file_count(), open_fp_cnt,
138 		    fds_count(), open_fd_cnt,
139 		    fcntl(0, F_MAXFD));
140 	}
141 }
142 #endif /* DEBUG_FILE_LEAK */
143 
144 static void
145 update_mailname(const char *name)
146 {
147 	char tbuf[PATHSIZE];
148 	size_t l;
149 
150 	/* Don't realpath(3) if it's only an update request */
151 	if (name != NULL && realpath(name, mailname) == NULL) {
152 		warn("Can't canonicalize `%s'", name);
153 		return;
154 	}
155 
156 	if (getfold(tbuf, sizeof(tbuf)) >= 0) {
157 		l = strlen(tbuf);
158 		if (l < sizeof(tbuf) - 1)
159 			tbuf[l++] = '/';
160 		if (strncmp(tbuf, mailname, l) == 0) {
161 			char const *sep = "", *cp = mailname + l;
162 
163 			l = strlen(cp);
164 			if (l >= sizeof(displayname)) {
165 				cp += l;
166 				cp -= sizeof(displayname) - 5;
167 				sep = "...";
168 			}
169 			(void)snprintf(displayname, sizeof(displayname),
170 			    "+%s%.*s", sep,
171 			    (int)(sizeof(displayname) - 1 - strlen(sep)), cp);
172 			return;
173 		}
174 	}
175 
176 	l = strlen(mailname);
177 	if (l < sizeof(displayname))
178 		strcpy(displayname, mailname);
179 	else {
180 		l -= sizeof(displayname) - 4 - sizeof(displayname) / 3;
181 		(void)snprintf(displayname, sizeof(displayname), "%.*s...%s",
182 			(int)sizeof(displayname) / 3, mailname, mailname + l);
183 	}
184 }
185 
186 /*
187  * Set the size of the message vector used to construct argument
188  * lists to message list functions.
189  */
190 static void
191 setmsize(int sz)
192 {
193 	if (msgvec != 0)
194 		free(msgvec);
195 	msgvec = ecalloc((size_t) (sz + 1), sizeof(*msgvec));
196 }
197 
198 /*
199  * Set up editing on the given file name.
200  * If the first character of name is %, we are considered to be
201  * editing the file, otherwise we are reading our mail which has
202  * significance for mbox and so forth.
203  */
204 PUBLIC int
205 setfile(const char *name)
206 {
207 	FILE *ibuf;
208 	int i, fd;
209 	struct stat stb;
210 	char isedit = *name != '%' || getuserid(myname) != (int)getuid();
211 	const char *who = name[1] ? name + 1 : myname;
212 	static int shudclob;
213 	char tempname[PATHSIZE];
214 
215 	if ((name = expand(name)) == NULL)
216 		return -1;
217 
218 	if ((ibuf = Fopen(name, "ref")) == NULL) {
219 		if (!isedit && errno == ENOENT)
220 			goto nomail;
221 		warn("Can't open `%s'", name);
222 		return -1;
223 	}
224 
225 	if (fstat(fileno(ibuf), &stb) < 0) {
226 		warn("fstat");
227 		(void)Fclose(ibuf);
228 		return -1;
229 	}
230 
231 	switch (stb.st_mode & S_IFMT) {
232 	case S_IFDIR:
233 		(void)Fclose(ibuf);
234 		errno = EISDIR;
235 		warn("%s", name);
236 		return -1;
237 
238 	case S_IFREG:
239 		break;
240 
241 	default:
242 		(void)Fclose(ibuf);
243 		errno = EINVAL;
244 		warn("%s", name);
245 		return -1;
246 	}
247 
248 	/*
249 	 * Looks like all will be well.  We must now relinquish our
250 	 * hold on the current set of stuff.  Must hold signals
251 	 * while we are reading the new file, else we will ruin
252 	 * the message[] data structure.
253 	 */
254 
255 	sig_check();
256 	sig_hold();
257 	if (shudclob)
258 		quit(jmpbuf);
259 
260 	/*
261 	 * Copy the messages into /tmp
262 	 * and set pointers.
263 	 */
264 
265 	readonly = 0;
266 	if ((i = open(name, O_WRONLY)) < 0)
267 		readonly++;
268 	else
269 		(void)close(i);
270 	if (shudclob) {
271 		(void)fclose(itf);
272 		(void)fclose(otf);
273 	}
274 	shudclob = 1;
275 	edit = isedit;
276 	(void)strcpy(prevfile, mailname);
277 	update_mailname(name != mailname ? name : NULL);
278 	mailsize = fsize(ibuf);
279 	(void)snprintf(tempname, sizeof(tempname),
280 	    "%s/mail.RxXXXXXXXXXX", tmpdir);
281 	if ((fd = mkstemp(tempname)) == -1 ||
282 	    (otf = fdopen(fd, "wef")) == NULL)
283 		err(EXIT_FAILURE, "Can't create tmp file `%s'", tempname);
284 	if ((itf = fopen(tempname, "ref")) == NULL)
285 		err(EXIT_FAILURE, "Can't create tmp file `%s'", tempname);
286 	(void)rm(tempname);
287 	setptr(ibuf, (off_t)0);
288 	setmsize(get_abs_msgCount());
289 	/*
290 	 * New mail may have arrived while we were reading
291 	 * the mail file, so reset mailsize to be where
292 	 * we really are in the file...
293 	 */
294 	mailsize = ftell(ibuf);
295 	(void)Fclose(ibuf);
296 	sig_release();
297 	sig_check();
298 	sawcom = 0;
299 	if (!edit && get_abs_msgCount() == 0) {
300 nomail:
301 		(void)fprintf(stderr, "No mail for %s\n", who);
302 		return -1;
303 	}
304 	return 0;
305 }
306 
307 /*
308  * Incorporate any new mail that has arrived since we first
309  * started reading mail.
310  */
311 PUBLIC int
312 incfile(void)
313 {
314 	off_t newsize;
315 	int omsgCount;
316 	FILE *ibuf;
317 	int rval;
318 
319 	omsgCount = get_abs_msgCount();
320 
321 	ibuf = Fopen(mailname, "ref");
322 	if (ibuf == NULL)
323 		return -1;
324 	sig_check();
325 	sig_hold();
326 	newsize = fsize(ibuf);
327 	if (newsize == 0 ||		/* mail box is now empty??? */
328 	    newsize < mailsize) {	/* mail box has shrunk??? */
329 		rval = -1;
330 		goto done;
331 	}
332 	if (newsize == mailsize) {
333 		rval = 0;               /* no new mail */
334 		goto done;
335 	}
336 	setptr(ibuf, mailsize);		/* read in new mail */
337 	setmsize(get_abs_msgCount());	/* get the new message count */
338 	mailsize = ftell(ibuf);
339 	rval = get_abs_msgCount() - omsgCount;
340  done:
341 	(void)Fclose(ibuf);
342 	sig_release();
343 	sig_check();
344 	return rval;
345 }
346 
347 /*
348  * Return a pointer to the comment character, respecting quoting as
349  * done in getrawlist().  The comment character is ignored inside
350  * quotes.
351  */
352 static char *
353 comment_char(char *line)
354 {
355 	char *p;
356 	char quotec;
357 	quotec = '\0';
358 	for (p = line; *p; p++) {
359 		if (quotec != '\0') {
360 			if (*p == quotec)
361 				quotec = '\0';
362 		}
363 		else if (*p == '"' || *p == '\'')
364 			quotec = *p;
365 		else if (*p == COMMENT_CHAR)
366 			return p;
367 	}
368 	return NULL;
369 }
370 
371 /*
372  * Signal handler is hooked by setup_piping().
373  * Respond to a broken pipe signal --
374  * probably caused by quitting more.
375  */
376 static jmp_buf	pipestop;
377 
378 /*ARGSUSED*/
379 __dead static void
380 lex_brokpipe(int signo)
381 {
382 
383 	longjmp(pipestop, signo);
384 }
385 
386 /*
387  * Check the command line for any requested piping or redirection,
388  * depending on the value of 'c'.  If "enable-pipes" is set, search
389  * the command line (cp) for the first occurrence of the character 'c'
390  * that is not in a quote or (parenthese) group.
391  */
392 PUBLIC char *
393 shellpr(char *cp)
394 {
395 	int quotec;
396 	int level;
397 
398 	if (cp == NULL || value(ENAME_ENABLE_PIPES) == NULL)
399 		return NULL;
400 
401 	level = 0;
402 	quotec = 0;
403 	for (/*EMPTY*/; *cp != '\0'; cp++) {
404 		if (quotec) {
405 			if (*cp == quotec)
406 				quotec = 0;
407 			if (*cp == '\\' &&
408 			    (cp[1] == quotec || cp[1] == '\\'))
409 				cp++;
410 		}
411 		else {
412 			switch (*cp) {
413 			case '|':
414 			case '>':
415 				if (level == 0)
416 					return cp;
417 				break;
418 			case '(':
419 				level++;
420 				break;
421 			case ')':
422 				level--;
423 				break;
424 			case '"':
425 			case '\'':
426 				quotec = *cp;
427 				break;
428 			default:
429 				break;
430 			}
431 		}
432 	}
433 	return NULL;
434 }
435 
436 static int
437 do_paging(const char *cmd, int c_pipe)
438 {
439 	char *cp, *p;
440 
441 	if (value(ENAME_PAGER_OFF) != NULL)
442 		return 0;
443 
444 	if (c_pipe & C_PIPE_PAGER)
445 		return 1;
446 
447 	if (c_pipe & C_PIPE_CRT && value(ENAME_CRT) != NULL)
448 		return 1;
449 
450 	if ((cp = value(ENAME_PAGE_ALSO)) == NULL)
451 		return 0;
452 
453 	if ((p = strcasestr(cp, cmd)) == NULL)
454 		return 0;
455 
456 	if (p != cp && p[-1] != ',' && !is_WSP(p[-1]))
457 		return 0;
458 
459 	p += strlen(cmd);
460 
461 	return (*p == '\0' || *p == ',' || is_WSP(*p));
462 }
463 
464 /*
465  * Setup any pipe or redirection that the command line indicates.
466  * If none, then setup the pager unless "pager-off" is defined.
467  */
468 static FILE *fp_stop = NULL;
469 static int oldfd1 = -1;
470 static sig_t old_sigpipe;
471 
472 static int
473 setup_piping(const char *cmd, char *cmdline, int c_pipe)
474 {
475 	FILE *fout;
476 	FILE *last_file;
477 	char *cp;
478 
479 	sig_check();
480 
481 	last_file = last_registered_file(0);
482 
483 	fout = NULL;
484 	if ((cp = shellpr(cmdline)) != NULL) {
485 		char c;
486 		c = *cp;
487 		*cp = '\0';
488 		cp++;
489 
490 		if (c == '|') {
491 			if ((fout = Popen(cp, "we")) == NULL) {
492 				warn("Popen: %s", cp);
493 				return -1;
494 			}
495 		}
496 		else {
497 			const char *mode;
498 			assert(c == '>');
499 			mode = *cp == '>' ? "ae" : "we";
500 			if (*cp == '>')
501 				cp++;
502 
503 			cp = skip_WSP(cp);
504 			if ((fout = Fopen(cp, mode)) == NULL) {
505 				warn("Fopen: %s", cp);
506 				return -1;
507 			}
508 		}
509 
510 	}
511 	else if (do_paging(cmd, c_pipe)) {
512 		const char *pager;
513 		pager = value(ENAME_PAGER);
514 		if (pager == NULL || *pager == '\0')
515 			pager = _PATH_MORE;
516 
517 		if ((fout = Popen(pager, "we")) == NULL) {
518 			warn("Popen: %s", pager);
519 			return -1;
520 		}
521 	}
522 
523 	if (fout) {
524 		old_sigpipe = sig_signal(SIGPIPE, lex_brokpipe);
525 		(void)fflush(stdout);
526 		if ((oldfd1 = dup(STDOUT_FILENO)) == -1)
527 			err(EXIT_FAILURE, "dup failed");
528 		if (dup2(fileno(fout), STDOUT_FILENO) == -1)
529 			err(EXIT_FAILURE, "dup2 failed");
530 		fp_stop = last_file;
531 	}
532 	return 0;
533 }
534 
535 /*
536  * This will close any piping started by setup_piping().
537  */
538 static void
539 close_piping(void)
540 {
541 	sigset_t oset;
542 	struct sigaction osa;
543 
544 	if (oldfd1 != -1) {
545 		(void)fflush(stdout);
546 		if (fileno(stdout) != oldfd1 &&
547 		    dup2(oldfd1, STDOUT_FILENO) == -1)
548 			err(EXIT_FAILURE, "dup2 failed");
549 
550 		(void)sig_ignore(SIGPIPE, &osa, &oset);
551 
552 		close_top_files(fp_stop);
553 		fp_stop = NULL;
554 		(void)close(oldfd1);
555 		oldfd1 = -1;
556 
557 		(void)sig_signal(SIGPIPE, old_sigpipe);
558 		(void)sig_restore(SIGPIPE, &osa, &oset);
559 	}
560 	sig_check();
561 }
562 
563 /*
564  * Determine if as1 is a valid prefix of as2.
565  * Return true if yep.
566  */
567 static int
568 isprefix(char *as1, const char *as2)
569 {
570 	char *s1;
571 	const char *s2;
572 
573 	s1 = as1;
574 	s2 = as2;
575 	while (*s1++ == *s2)
576 		if (*s2++ == '\0')
577 			return 1;
578 	return *--s1 == '\0';
579 }
580 
581 /*
582  * Find the correct command in the command table corresponding
583  * to the passed command "word"
584  */
585 PUBLIC const struct cmd *
586 lex(char word[])
587 {
588 	const struct cmd *cp;
589 
590 	for (cp = &cmdtab[0]; cp->c_name != NULL; cp++)
591 		if (isprefix(word, cp->c_name))
592 			return cp;
593 	return NULL;
594 }
595 
596 PUBLIC char *
597 get_cmdname(char *buf)
598 {
599 	char *cp;
600 	char *cmd;
601 	size_t len;
602 
603 	for (cp = buf; *cp; cp++)
604 		if (strchr(" \t0123456789$^.:/-+*'\">|", *cp) != NULL)
605 			break;
606 	/* XXX - Don't miss the pipe command! */
607 	if (cp == buf && *cp == '|')
608 		cp++;
609 	len = cp - buf + 1;
610 	cmd = salloc(len);
611 	(void)strlcpy(cmd, buf, len);
612 	return cmd;
613 }
614 
615 /*
616  * Execute a single command.
617  * Command functions return 0 for success, 1 for error, and -1
618  * for abort.  A 1 or -1 aborts a load or source.  A -1 aborts
619  * the interactive command loop.
620  * execute_contxt_e is in extern.h.
621  */
622 PUBLIC int
623 execute(char linebuf[], enum execute_contxt_e contxt)
624 {
625 	char *word;
626 	char *arglist[MAXARGC];
627 	const struct cmd * volatile com = NULL;
628 	char *volatile cp;
629 	int retval;
630 	int c;
631 	volatile int e = 1;
632 
633 	/*
634 	 * Strip the white space away from the beginning
635 	 * of the command, then scan out a word, which
636 	 * consists of anything except digits and white space.
637 	 *
638 	 * Handle ! escapes differently to get the correct
639 	 * lexical conventions.
640 	 */
641 
642 	cp = skip_space(linebuf);
643 	if (*cp == '!') {
644 		if (sourcing) {
645 			(void)printf("Can't \"!\" while sourcing\n");
646 			goto out;
647 		}
648 		(void)shell(cp + 1);
649 		return 0;
650 	}
651 
652 	word = get_cmdname(cp);
653 	cp += strlen(word);
654 
655 	/*
656 	 * Look up the command; if not found, bitch.
657 	 * Normally, a blank command would map to the
658 	 * first command in the table; while sourcing,
659 	 * however, we ignore blank lines to eliminate
660 	 * confusion.
661 	 */
662 
663 	if (sourcing && *word == '\0')
664 		return 0;
665 	com = lex(word);
666 	if (com == NULL) {
667 		(void)printf("Unknown command: \"%s\"\n", word);
668 		goto out;
669 	}
670 
671 	/*
672 	 * See if we should execute the command -- if a conditional
673 	 * we always execute it, otherwise, check the state of cond.
674 	 */
675 
676 	if ((com->c_argtype & F) == 0 && (cond & CSKIP))
677 		return 0;
678 
679 	/*
680 	 * Process the arguments to the command, depending
681 	 * on the type he expects.  Default to an error.
682 	 * If we are sourcing an interactive command, it's
683 	 * an error.
684 	 */
685 
686 	if (mailmode == mm_sending && (com->c_argtype & M) == 0) {
687 		(void)printf("May not execute \"%s\" while sending\n",
688 		    com->c_name);
689 		goto out;
690 	}
691 	if (sourcing && com->c_argtype & I) {
692 		(void)printf("May not execute \"%s\" while sourcing\n",
693 		    com->c_name);
694 		goto out;
695 	}
696 	if (readonly && com->c_argtype & W) {
697 		(void)printf("May not execute \"%s\" -- message file is read only\n",
698 		   com->c_name);
699 		goto out;
700 	}
701 	if (contxt == ec_composing && com->c_argtype & R) {
702 		(void)printf("Cannot recursively invoke \"%s\"\n", com->c_name);
703 		goto out;
704 	}
705 
706 	if (!sourcing && com->c_pipe && value(ENAME_INTERACTIVE) != NULL) {
707 
708 		sig_check();
709 		if (setjmp(pipestop))
710 			goto out;
711 
712 		if (setup_piping(com->c_name, cp, com->c_pipe) == -1)
713 			goto out;
714 	}
715 	switch (com->c_argtype & ARGTYPE_MASK) {
716 	case MSGLIST:
717 		/*
718 		 * A message list defaulting to nearest forward
719 		 * legal message.
720 		 */
721 		if (msgvec == 0) {
722 			(void)printf("Illegal use of \"message list\"\n");
723 			break;
724 		}
725 		if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0)
726 			break;
727 		if (c  == 0) {
728 			*msgvec = first(com->c_msgflag,	com->c_msgmask);
729 			msgvec[1] = 0;
730 		}
731 		if (*msgvec == 0) {
732 			(void)printf("No applicable messages\n");
733 			break;
734 		}
735 		e = (*com->c_func)(msgvec);
736 		break;
737 
738 	case NDMLIST:
739 		/*
740 		 * A message list with no defaults, but no error
741 		 * if none exist.
742 		 */
743 		if (msgvec == 0) {
744 			(void)printf("Illegal use of \"message list\"\n");
745 			break;
746 		}
747 		if (getmsglist(cp, msgvec, com->c_msgflag) < 0)
748 			break;
749 		e = (*com->c_func)(msgvec);
750 		break;
751 
752 	case STRLIST:
753 		/*
754 		 * Just the straight string, with
755 		 * leading blanks removed.
756 		 */
757 		cp = skip_space(cp);
758 		e = (*com->c_func)(cp);
759 		break;
760 
761 	case RAWLIST:
762 		/*
763 		 * A vector of strings, in shell style.
764 		 */
765 		if ((c = getrawlist(cp, arglist, (int)__arraycount(arglist))) < 0)
766 			break;
767 		if (c < com->c_minargs) {
768 			(void)printf("%s requires at least %d arg(s)\n",
769 				com->c_name, com->c_minargs);
770 			break;
771 		}
772 		if (c > com->c_maxargs) {
773 			(void)printf("%s takes no more than %d arg(s)\n",
774 				com->c_name, com->c_maxargs);
775 			break;
776 		}
777 		e = (*com->c_func)(arglist);
778 		break;
779 
780 	case NOLIST:
781 		/*
782 		 * Just the constant zero, for exiting,
783 		 * eg.
784 		 */
785 		e = (*com->c_func)(0);
786 		break;
787 
788 	default:
789 		errx(EXIT_FAILURE, "Unknown argtype");
790 	}
791 
792 out:
793 	close_piping();
794 
795 	/*
796 	 * Exit the current source file on
797 	 * error.
798 	 */
799 	retval = 0;
800 	if (e) {
801 		if (e < 0)
802 			retval = 1;
803 		else if (loading)
804 			retval = 1;
805 		else if (sourcing)
806 			(void)unstack();
807 	}
808 	else if (com != NULL) {
809 		if (contxt != ec_autoprint && com->c_argtype & P &&
810 		    value(ENAME_AUTOPRINT) != NULL &&
811 		    (dot->m_flag & MDELETED) == 0)
812 			(void)execute(__UNCONST("print ."), ec_autoprint);
813 		if (!sourcing && (com->c_argtype & T) == 0)
814 			sawcom = 1;
815 	}
816 	sig_check();
817 	return retval;
818 }
819 
820 /*
821  * The following gets called on receipt of an interrupt.  This is
822  * to abort printout of a command, mainly.
823  * Dispatching here when commands() is inactive crashes rcv.
824  * Close all open files except 0, 1, 2, and the temporary.
825  * Also, unstack all source files.
826  */
827 __dead static void
828 lex_intr(int signo)
829 {
830 
831 	noreset = 0;
832 	if (!inithdr)
833 		sawcom++;
834 	inithdr = 0;
835 	while (sourcing)
836 		(void)unstack();
837 
838 	close_piping();
839 	close_all_files();
840 
841 	if (image >= 0) {
842 		(void)close(image);
843 		image = -1;
844 	}
845 	(void)fprintf(stderr, "Interrupt\n");
846 	longjmp(jmpbuf, signo);
847 }
848 
849 /*
850  * Branch here on hangup signal and simulate "exit".
851  */
852 /*ARGSUSED*/
853 __dead static void
854 lex_hangup(int s __unused)
855 {
856 
857 	/* nothing to do? */
858 	exit(EXIT_FAILURE);
859 }
860 
861 /*
862  * When we wake up after ^Z, reprint the prompt.
863  *
864  * NOTE: EditLine deals with the prompt and job control, so with it
865  * this does nothing, i.e., reset_on_stop == 0.
866  */
867 static void
868 lex_stop(int signo)
869 {
870 
871 	if (reset_on_stop) {
872 		reset_on_stop = 0;
873 		longjmp(jmpbuf, signo);
874 	}
875 }
876 
877 /*
878  * Interpret user commands one by one.  If standard input is not a tty,
879  * print no prompt.
880  */
881 PUBLIC void
882 commands(void)
883 {
884 	int n;
885 	char linebuf[LINESIZE];
886 	int eofloop;
887 
888 #ifdef DEBUG_FILE_LEAK
889 	file_leak_init();
890 #endif
891 
892 	if (!sourcing) {
893 		sig_check();
894 
895 		sig_hold();
896 		(void)sig_signal(SIGINT,  lex_intr);
897 		(void)sig_signal(SIGHUP,  lex_hangup);
898 		(void)sig_signal(SIGTSTP, lex_stop);
899 		(void)sig_signal(SIGTTOU, lex_stop);
900 		(void)sig_signal(SIGTTIN, lex_stop);
901 		sig_release();
902 	}
903 
904 	(void)setjmp(jmpbuf);	/* "reset" location if we got an interrupt */
905 
906 	eofloop = 0;	/* initialize this after a possible longjmp */
907 	for (;;) {
908 		sig_check();
909 		(void)fflush(stdout);
910 		sreset();
911 		/*
912 		 * Print the prompt, if needed.  Clear out
913 		 * string space, and flush the output.
914 		 */
915 		if (!sourcing && value(ENAME_INTERACTIVE) != NULL) {
916 			if ((prompt = value(ENAME_PROMPT)) == NULL)
917 				prompt = DEFAULT_PROMPT;
918 			prompt = smsgprintf(prompt, dot);
919 			if ((value(ENAME_AUTOINC) != NULL) && (incfile() > 0))
920 				(void)printf("New mail has arrived.\n");
921 
922 #ifndef USE_EDITLINE
923 			reset_on_stop = 1;	/* enable job control longjmp */
924 			(void)printf("%s", prompt);
925 #endif
926 		}
927 #ifdef DEBUG_FILE_LEAK
928 		file_leak_check();
929 #endif
930 		/*
931 		 * Read a line of commands from the current input
932 		 * and handle end of file specially.
933 		 */
934 		n = 0;
935 		for (;;) {
936 			sig_check();
937 #ifdef USE_EDITLINE
938 			if (!sourcing) {
939 				char *line;
940 
941 				line = my_gets(&elm.command, prompt, NULL);
942 				if (line == NULL) {
943 					if (n == 0)
944 						n = -1;
945 					break;
946 				}
947 				(void)strlcpy(linebuf, line, sizeof(linebuf));
948 			}
949 			else {
950 				if (readline(input, &linebuf[n], LINESIZE - n, 0) < 0) {
951 					if (n == 0)
952 						n = -1;
953 					break;
954 				}
955 			}
956 #else /* USE_EDITLINE */
957 			if (readline(input, &linebuf[n], LINESIZE - n, reset_on_stop) < 0) {
958 				if (n == 0)
959 					n = -1;
960 				break;
961 			}
962 #endif /* USE_EDITLINE */
963 			if (!sourcing)
964 				setscreensize(); /* so we can resize window */
965 
966 			if (sourcing) {  /* allow comments in source files */
967 				char *ptr;
968 				if ((ptr = comment_char(linebuf)) != NULL)
969 					*ptr = '\0';
970 			}
971 			if ((n = (int)strlen(linebuf)) == 0)
972 				break;
973 			n--;
974 			if (linebuf[n] != '\\')
975 				break;
976 			linebuf[n++] = ' ';
977 		}
978 #ifndef USE_EDITLINE
979 		sig_check();
980 		reset_on_stop = 0;	/* disable job control longjmp */
981 #endif
982 		if (n < 0) {
983 			char *p;
984 
985 			/* eof */
986 			if (loading)
987 				break;
988 			if (sourcing) {
989 				(void)unstack();
990 				continue;
991 			}
992 			if (value(ENAME_INTERACTIVE) != NULL &&
993 			    (p = value(ENAME_IGNOREEOF)) != NULL &&
994 			    ++eofloop < (*p == '\0' ? 25 : atoi(p))) {
995 				(void)printf("Use \"quit\" to quit.\n");
996 				continue;
997 			}
998 			break;
999 		}
1000 		eofloop = 0;
1001 		if (execute(linebuf, ec_normal))
1002 			break;
1003 	}
1004 }
1005 
1006 /*
1007  * Announce information about the file we are editing.
1008  * Return a likely place to set dot.
1009  */
1010 PUBLIC int
1011 newfileinfo(int omsgCount)
1012 {
1013 	struct message *mp;
1014 	int d, n, s, t, u, mdot;
1015 
1016 	/*
1017 	 * Figure out where to set the 'dot'.  Use the first new or
1018 	 * unread message.
1019 	 */
1020 	for (mp = get_abs_message(omsgCount + 1); mp;
1021 	     mp = next_abs_message(mp))
1022 		if (mp->m_flag & MNEW)
1023 			break;
1024 
1025 	if (mp == NULL)
1026 		for (mp = get_abs_message(omsgCount + 1); mp;
1027 		     mp = next_abs_message(mp))
1028 			if ((mp->m_flag & MREAD) == 0)
1029 				break;
1030 	if (mp != NULL)
1031 		mdot = get_msgnum(mp);
1032 	else
1033 		mdot = omsgCount + 1;
1034 #ifdef THREAD_SUPPORT
1035 	/*
1036 	 * See if the message is in the current thread.
1037 	 */
1038 	if (mp != NULL && get_message(1) != NULL && get_message(mdot) != mp)
1039 		mdot = 0;
1040 #endif
1041 	/*
1042 	 * Scan the message array counting the new, unread, deleted,
1043 	 * and saved messages.
1044 	 */
1045 	d = n = s = t = u = 0;
1046 	for (mp = get_abs_message(1); mp; mp = next_abs_message(mp)) {
1047 		if (mp->m_flag & MNEW)
1048 			n++;
1049 		if ((mp->m_flag & MREAD) == 0)
1050 			u++;
1051 		if (mp->m_flag & MDELETED)
1052 			d++;
1053 		if (mp->m_flag & MSAVED)
1054 			s++;
1055 		if (mp->m_flag & MTAGGED)
1056 			t++;
1057 	}
1058 	/*
1059 	 * Display the statistics.
1060 	 */
1061 	update_mailname(NULL);
1062 	(void)printf("\"%s\": ", displayname);
1063 	{
1064 		int cnt = get_abs_msgCount();
1065 		(void)printf("%d message%s", cnt, cnt == 1 ? "" : "s");
1066 	}
1067 	if (n > 0)
1068 		(void)printf(" %d new", n);
1069 	if (u-n > 0)
1070 		(void)printf(" %d unread", u);
1071 	if (t > 0)
1072 		(void)printf(" %d tagged", t);
1073 	if (d > 0)
1074 		(void)printf(" %d deleted", d);
1075 	if (s > 0)
1076 		(void)printf(" %d saved", s);
1077 	if (readonly)
1078 		(void)printf(" [Read only]");
1079 	(void)printf("\n");
1080 
1081 	return mdot;
1082 }
1083 
1084 /*
1085  * Announce the presence of the current Mail version,
1086  * give the message count, and print a header listing.
1087  */
1088 PUBLIC void
1089 announce(void)
1090 {
1091 	int vec[2], mdot;
1092 
1093 	mdot = newfileinfo(0);
1094 	vec[0] = mdot;
1095 	vec[1] = 0;
1096 	if ((dot = get_message(mdot)) == NULL)
1097 		dot = get_abs_message(1); /* make sure we get something! */
1098 	if (get_abs_msgCount() > 0 && value(ENAME_NOHEADER) == NULL) {
1099 		inithdr++;
1100 		(void)headers(vec);
1101 		inithdr = 0;
1102 	}
1103 }
1104 
1105 /*
1106  * Print the current version number.
1107  */
1108 
1109 /*ARGSUSED*/
1110 PUBLIC int
1111 pversion(void *v __unused)
1112 {
1113 	(void)printf("Version %s\n", version);
1114 	return 0;
1115 }
1116 
1117 /*
1118  * Load a file of user definitions.
1119  */
1120 PUBLIC void
1121 load(const char *name)
1122 {
1123 	FILE *in, *oldin;
1124 
1125 	if ((in = Fopen(name, "ref")) == NULL)
1126 		return;
1127 	oldin = input;
1128 	input = in;
1129 	loading = 1;
1130 	sourcing = 1;
1131 	commands();
1132 	loading = 0;
1133 	sourcing = 0;
1134 	input = oldin;
1135 	(void)Fclose(in);
1136 }
1137