lex.c revision 99112
1/*
2 * Copyright (c) 1980, 1993
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *	This product includes software developed by the University of
16 *	California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#ifndef lint
35#if 0
36static char sccsid[] = "@(#)lex.c	8.2 (Berkeley) 4/20/95";
37#endif
38#endif /* not lint */
39#include <sys/cdefs.h>
40__FBSDID("$FreeBSD: head/usr.bin/mail/lex.c 99112 2002-06-30 05:25:07Z obrien $");
41
42#include "rcv.h"
43#include <errno.h>
44#include <fcntl.h>
45#include "extern.h"
46
47/*
48 * Mail -- a mail program
49 *
50 * Lexical processing of commands.
51 */
52
53const char	*prompt = "& ";
54
55extern const struct cmd cmdtab[];
56extern const char *version;
57
58/*
59 * Set up editing on the given file name.
60 * If the first character of name is %, we are considered to be
61 * editing the file, otherwise we are reading our mail which has
62 * signficance for mbox and so forth.
63 */
64int
65setfile(name)
66	char *name;
67{
68	FILE *ibuf;
69	int i, fd;
70	struct stat stb;
71	char isedit = *name != '%' || getuserid(myname) != getuid();
72	char *who = name[1] ? name + 1 : myname;
73	char tempname[PATHSIZE];
74	static int shudclob;
75
76	if ((name = expand(name)) == NULL)
77		return (-1);
78
79	if ((ibuf = Fopen(name, "r")) == NULL) {
80		if (!isedit && errno == ENOENT)
81			goto nomail;
82		warn("%s", name);
83		return (-1);
84	}
85
86	if (fstat(fileno(ibuf), &stb) < 0) {
87		warn("fstat");
88		(void)Fclose(ibuf);
89		return (-1);
90	}
91
92	if (S_ISDIR(stb.st_mode) || !S_ISREG(stb.st_mode)) {
93		(void)Fclose(ibuf);
94		errno = S_ISDIR(stb.st_mode) ? EISDIR : EINVAL;
95		warn("%s", name);
96		return (-1);
97	}
98
99	/*
100	 * Looks like all will be well.  We must now relinquish our
101	 * hold on the current set of stuff.  Must hold signals
102	 * while we are reading the new file, else we will ruin
103	 * the message[] data structure.
104	 */
105
106	holdsigs();
107	if (shudclob)
108		quit();
109
110	/*
111	 * Copy the messages into /tmp
112	 * and set pointers.
113	 */
114
115	readonly = 0;
116	if ((i = open(name, 1)) < 0)
117		readonly++;
118	else
119		(void)close(i);
120	if (shudclob) {
121		(void)fclose(itf);
122		(void)fclose(otf);
123	}
124	shudclob = 1;
125	edit = isedit;
126	strlcpy(prevfile, mailname, sizeof(prevfile));
127	if (name != mailname)
128		strlcpy(mailname, name, sizeof(mailname));
129	mailsize = fsize(ibuf);
130	(void)snprintf(tempname, sizeof(tempname),
131	    "%s/mail.RxXXXXXXXXXX", tmpdir);
132	if ((fd = mkstemp(tempname)) == -1 || (otf = fdopen(fd, "w")) == NULL)
133		err(1, "%s", tempname);
134	(void)fcntl(fileno(otf), F_SETFD, 1);
135	if ((itf = fopen(tempname, "r")) == NULL)
136		err(1, "%s", tempname);
137	(void)fcntl(fileno(itf), F_SETFD, 1);
138	(void)rm(tempname);
139	setptr(ibuf, 0);
140	setmsize(msgCount);
141	/*
142	 * New mail may have arrived while we were reading
143	 * the mail file, so reset mailsize to be where
144	 * we really are in the file...
145	 */
146	mailsize = ftello(ibuf);
147	(void)Fclose(ibuf);
148	relsesigs();
149	sawcom = 0;
150	if (!edit && msgCount == 0) {
151nomail:
152		fprintf(stderr, "No mail for %s\n", who);
153		return (-1);
154	}
155	return (0);
156}
157
158/*
159 * Incorporate any new mail that has arrived since we first
160 * started reading mail.
161 */
162int
163incfile()
164{
165	off_t newsize;
166	int omsgCount = msgCount;
167	FILE *ibuf;
168
169	ibuf = Fopen(mailname, "r");
170	if (ibuf == NULL)
171		return (-1);
172	holdsigs();
173	newsize = fsize(ibuf);
174	if (newsize == 0)
175		return (-1);		/* mail box is now empty??? */
176	if (newsize < mailsize)
177		return (-1);		/* mail box has shrunk??? */
178	if (newsize == mailsize)
179		return (0);		/* no new mail */
180	setptr(ibuf, mailsize);
181	setmsize(msgCount);
182	mailsize = ftello(ibuf);
183	(void)Fclose(ibuf);
184	relsesigs();
185	return (msgCount - omsgCount);
186}
187
188int	*msgvec;
189int	reset_on_stop;			/* do a reset() if stopped */
190
191/*
192 * Interpret user commands one by one.  If standard input is not a tty,
193 * print no prompt.
194 */
195void
196commands()
197{
198	int n, eofloop = 0;
199	char linebuf[LINESIZE];
200
201	if (!sourcing) {
202		if (signal(SIGINT, SIG_IGN) != SIG_IGN)
203			(void)signal(SIGINT, intr);
204		if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
205			(void)signal(SIGHUP, hangup);
206		(void)signal(SIGTSTP, stop);
207		(void)signal(SIGTTOU, stop);
208		(void)signal(SIGTTIN, stop);
209	}
210	setexit();
211	for (;;) {
212		/*
213		 * Print the prompt, if needed.  Clear out
214		 * string space, and flush the output.
215		 */
216		if (!sourcing && value("interactive") != NULL) {
217			if ((value("autoinc") != NULL) && (incfile() > 0))
218				printf("New mail has arrived.\n");
219			reset_on_stop = 1;
220			printf("%s", prompt);
221		}
222		(void)fflush(stdout);
223		sreset();
224		/*
225		 * Read a line of commands from the current input
226		 * and handle end of file specially.
227		 */
228		n = 0;
229		for (;;) {
230			if (readline(input, &linebuf[n], LINESIZE - n) < 0) {
231				if (n == 0)
232					n = -1;
233				break;
234			}
235			if ((n = strlen(linebuf)) == 0)
236				break;
237			n--;
238			if (linebuf[n] != '\\')
239				break;
240			linebuf[n++] = ' ';
241		}
242		reset_on_stop = 0;
243		if (n < 0) {
244				/* eof */
245			if (loading)
246				break;
247			if (sourcing) {
248				unstack();
249				continue;
250			}
251			if (value("interactive") != NULL &&
252			    value("ignoreeof") != NULL &&
253			    ++eofloop < 25) {
254				printf("Use \"quit\" to quit.\n");
255				continue;
256			}
257			break;
258		}
259		eofloop = 0;
260		if (execute(linebuf, 0))
261			break;
262	}
263}
264
265/*
266 * Execute a single command.
267 * Command functions return 0 for success, 1 for error, and -1
268 * for abort.  A 1 or -1 aborts a load or source.  A -1 aborts
269 * the interactive command loop.
270 * Contxt is non-zero if called while composing mail.
271 */
272int
273execute(linebuf, contxt)
274	char linebuf[];
275	int contxt;
276{
277	char word[LINESIZE];
278	char *arglist[MAXARGC];
279	const struct cmd *com;
280	char *cp, *cp2;
281	int c, muvec[2];
282	int e = 1;
283
284	/*
285	 * Strip the white space away from the beginning
286	 * of the command, then scan out a word, which
287	 * consists of anything except digits and white space.
288	 *
289	 * Handle ! escapes differently to get the correct
290	 * lexical conventions.
291	 */
292
293	for (cp = linebuf; isspace((unsigned char)*cp); cp++)
294		;
295	if (*cp == '!') {
296		if (sourcing) {
297			printf("Can't \"!\" while sourcing\n");
298			goto out;
299		}
300		shell(cp+1);
301		return (0);
302	}
303	cp2 = word;
304	while (*cp != '\0' && strchr(" \t0123456789$^.:/-+*'\"", *cp) == NULL)
305		*cp2++ = *cp++;
306	*cp2 = '\0';
307
308	/*
309	 * Look up the command; if not found, bitch.
310	 * Normally, a blank command would map to the
311	 * first command in the table; while sourcing,
312	 * however, we ignore blank lines to eliminate
313	 * confusion.
314	 */
315
316	if (sourcing && *word == '\0')
317		return (0);
318	com = lex(word);
319	if (com == NULL) {
320		printf("Unknown command: \"%s\"\n", word);
321		goto out;
322	}
323
324	/*
325	 * See if we should execute the command -- if a conditional
326	 * we always execute it, otherwise, check the state of cond.
327	 */
328
329	if ((com->c_argtype & F) == 0)
330		if ((cond == CRCV && !rcvmode) || (cond == CSEND && rcvmode))
331			return (0);
332
333	/*
334	 * Process the arguments to the command, depending
335	 * on the type he expects.  Default to an error.
336	 * If we are sourcing an interactive command, it's
337	 * an error.
338	 */
339
340	if (!rcvmode && (com->c_argtype & M) == 0) {
341		printf("May not execute \"%s\" while sending\n",
342		    com->c_name);
343		goto out;
344	}
345	if (sourcing && com->c_argtype & I) {
346		printf("May not execute \"%s\" while sourcing\n",
347		    com->c_name);
348		goto out;
349	}
350	if (readonly && com->c_argtype & W) {
351		printf("May not execute \"%s\" -- message file is read only\n",
352		   com->c_name);
353		goto out;
354	}
355	if (contxt && com->c_argtype & R) {
356		printf("Cannot recursively invoke \"%s\"\n", com->c_name);
357		goto out;
358	}
359	switch (com->c_argtype & ~(F|P|I|M|T|W|R)) {
360	case MSGLIST:
361		/*
362		 * A message list defaulting to nearest forward
363		 * legal message.
364		 */
365		if (msgvec == 0) {
366			printf("Illegal use of \"message list\"\n");
367			break;
368		}
369		if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0)
370			break;
371		if (c  == 0) {
372			*msgvec = first(com->c_msgflag, com->c_msgmask);
373			msgvec[1] = 0;
374		}
375		if (*msgvec == 0) {
376			printf("No applicable messages\n");
377			break;
378		}
379		e = (*com->c_func)(msgvec);
380		break;
381
382	case NDMLIST:
383		/*
384		 * A message list with no defaults, but no error
385		 * if none exist.
386		 */
387		if (msgvec == 0) {
388			printf("Illegal use of \"message list\"\n");
389			break;
390		}
391		if (getmsglist(cp, msgvec, com->c_msgflag) < 0)
392			break;
393		e = (*com->c_func)(msgvec);
394		break;
395
396	case STRLIST:
397		/*
398		 * Just the straight string, with
399		 * leading blanks removed.
400		 */
401		while (isspace((unsigned char)*cp))
402			cp++;
403		e = (*com->c_func)(cp);
404		break;
405
406	case RAWLIST:
407		/*
408		 * A vector of strings, in shell style.
409		 */
410		if ((c = getrawlist(cp, arglist,
411		    sizeof(arglist) / sizeof(*arglist))) < 0)
412			break;
413		if (c < com->c_minargs) {
414			printf("%s requires at least %d arg(s)\n",
415			    com->c_name, com->c_minargs);
416			break;
417		}
418		if (c > com->c_maxargs) {
419			printf("%s takes no more than %d arg(s)\n",
420			    com->c_name, com->c_maxargs);
421			break;
422		}
423		e = (*com->c_func)(arglist);
424		break;
425
426	case NOLIST:
427		/*
428		 * Just the constant zero, for exiting,
429		 * eg.
430		 */
431		e = (*com->c_func)(0);
432		break;
433
434	default:
435		errx(1, "Unknown argtype");
436	}
437
438out:
439	/*
440	 * Exit the current source file on
441	 * error.
442	 */
443	if (e) {
444		if (e < 0)
445			return (1);
446		if (loading)
447			return (1);
448		if (sourcing)
449			unstack();
450		return (0);
451	}
452	if (com == NULL)
453		return (0);
454	if (value("autoprint") != NULL && com->c_argtype & P)
455		if ((dot->m_flag & MDELETED) == 0) {
456			muvec[0] = dot - &message[0] + 1;
457			muvec[1] = 0;
458			type(muvec);
459		}
460	if (!sourcing && (com->c_argtype & T) == 0)
461		sawcom = 1;
462	return (0);
463}
464
465/*
466 * Set the size of the message vector used to construct argument
467 * lists to message list functions.
468 */
469void
470setmsize(sz)
471	int sz;
472{
473
474	if (msgvec != NULL)
475		(void)free(msgvec);
476	msgvec = calloc((unsigned)(sz + 1), sizeof(*msgvec));
477}
478
479/*
480 * Find the correct command in the command table corresponding
481 * to the passed command "word"
482 */
483
484__const struct cmd *
485lex(word)
486	char word[];
487{
488	const struct cmd *cp;
489
490	/*
491	 * ignore trailing chars after `#'
492	 *
493	 * lines with beginning `#' are comments
494	 * spaces before `#' are ignored in execute()
495	 */
496
497	if (*word == '#')
498	    *(word+1) = '\0';
499
500
501	for (cp = &cmdtab[0]; cp->c_name != NULL; cp++)
502		if (isprefix(word, cp->c_name))
503			return (cp);
504	return (NULL);
505}
506
507/*
508 * Determine if as1 is a valid prefix of as2.
509 * Return true if yep.
510 */
511int
512isprefix(as1, as2)
513	const char *as1, *as2;
514{
515	const char *s1, *s2;
516
517	s1 = as1;
518	s2 = as2;
519	while (*s1++ == *s2)
520		if (*s2++ == '\0')
521			return (1);
522	return (*--s1 == '\0');
523}
524
525/*
526 * The following gets called on receipt of an interrupt.  This is
527 * to abort printout of a command, mainly.
528 * Dispatching here when command() is inactive crashes rcv.
529 * Close all open files except 0, 1, 2, and the temporary.
530 * Also, unstack all source files.
531 */
532
533int	inithdr;			/* am printing startup headers */
534
535/*ARGSUSED*/
536void
537intr(s)
538	int s;
539{
540
541	noreset = 0;
542	if (!inithdr)
543		sawcom++;
544	inithdr = 0;
545	while (sourcing)
546		unstack();
547
548	close_all_files();
549
550	if (image >= 0) {
551		(void)close(image);
552		image = -1;
553	}
554	fprintf(stderr, "Interrupt\n");
555	reset(0);
556}
557
558/*
559 * When we wake up after ^Z, reprint the prompt.
560 */
561void
562stop(s)
563	int s;
564{
565	sig_t old_action = signal(s, SIG_DFL);
566	sigset_t nset;
567
568	(void)sigemptyset(&nset);
569	(void)sigaddset(&nset, s);
570	(void)sigprocmask(SIG_UNBLOCK, &nset, NULL);
571	(void)kill(0, s);
572	(void)sigprocmask(SIG_BLOCK, &nset, NULL);
573	(void)signal(s, old_action);
574	if (reset_on_stop) {
575		reset_on_stop = 0;
576		reset(0);
577	}
578}
579
580/*
581 * Branch here on hangup signal and simulate "exit".
582 */
583/*ARGSUSED*/
584void
585hangup(s)
586	int s;
587{
588
589	/* nothing to do? */
590	exit(1);
591}
592
593/*
594 * Announce the presence of the current Mail version,
595 * give the message count, and print a header listing.
596 */
597void
598announce()
599{
600	int vec[2], mdot;
601
602	mdot = newfileinfo(0);
603	vec[0] = mdot;
604	vec[1] = 0;
605	dot = &message[mdot - 1];
606	if (msgCount > 0 && value("noheader") == NULL) {
607		inithdr++;
608		headers(vec);
609		inithdr = 0;
610	}
611}
612
613/*
614 * Announce information about the file we are editing.
615 * Return a likely place to set dot.
616 */
617int
618newfileinfo(omsgCount)
619	int omsgCount;
620{
621	struct message *mp;
622	int u, n, mdot, d, s;
623	char fname[PATHSIZE+1], zname[PATHSIZE+1], *ename;
624
625	for (mp = &message[omsgCount]; mp < &message[msgCount]; mp++)
626		if (mp->m_flag & MNEW)
627			break;
628	if (mp >= &message[msgCount])
629		for (mp = &message[omsgCount]; mp < &message[msgCount]; mp++)
630			if ((mp->m_flag & MREAD) == 0)
631				break;
632	if (mp < &message[msgCount])
633		mdot = mp - &message[0] + 1;
634	else
635		mdot = omsgCount + 1;
636	s = d = 0;
637	for (mp = &message[0], n = 0, u = 0; mp < &message[msgCount]; mp++) {
638		if (mp->m_flag & MNEW)
639			n++;
640		if ((mp->m_flag & MREAD) == 0)
641			u++;
642		if (mp->m_flag & MDELETED)
643			d++;
644		if (mp->m_flag & MSAVED)
645			s++;
646	}
647	ename = mailname;
648	if (getfold(fname, sizeof(fname) - 1) >= 0) {
649		strcat(fname, "/");
650		if (strncmp(fname, mailname, strlen(fname)) == 0) {
651			(void)snprintf(zname, sizeof(zname), "+%s",
652			    mailname + strlen(fname));
653			ename = zname;
654		}
655	}
656	printf("\"%s\": ", ename);
657	if (msgCount == 1)
658		printf("1 message");
659	else
660		printf("%d messages", msgCount);
661	if (n > 0)
662		printf(" %d new", n);
663	if (u-n > 0)
664		printf(" %d unread", u);
665	if (d > 0)
666		printf(" %d deleted", d);
667	if (s > 0)
668		printf(" %d saved", s);
669	if (readonly)
670		printf(" [Read only]");
671	printf("\n");
672	return (mdot);
673}
674
675/*
676 * Print the current version number.
677 */
678
679/*ARGSUSED*/
680int
681pversion(e)
682	int e;
683{
684
685	printf("Version %s\n", version);
686	return (0);
687}
688
689/*
690 * Load a file of user definitions.
691 */
692void
693load(name)
694	char *name;
695{
696	FILE *in, *oldin;
697
698	if ((in = Fopen(name, "r")) == NULL)
699		return;
700	oldin = input;
701	input = in;
702	loading = 1;
703	sourcing = 1;
704	commands();
705	loading = 0;
706	sourcing = 0;
707	input = oldin;
708	(void)Fclose(in);
709}
710