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