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