lex.c revision 81586
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.1 (Berkeley) 6/6/93";
37#endif
38static const char rcsid[] =
39  "$FreeBSD: head/usr.bin/mail/lex.c 81586 2001-08-13 14:06:34Z ru $";
40#endif /* not lint */
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 != '%';
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);
140	setmsize(msgCount);
141	(void)Fclose(ibuf);
142	relsesigs();
143	sawcom = 0;
144	if (!edit && msgCount == 0) {
145nomail:
146		fprintf(stderr, "No mail for %s\n", who);
147		return (-1);
148	}
149	return (0);
150}
151
152int	*msgvec;
153int	reset_on_stop;			/* do a reset() if stopped */
154
155/*
156 * Interpret user commands one by one.  If standard input is not a tty,
157 * print no prompt.
158 */
159void
160commands()
161{
162	int n, eofloop = 0;
163	char linebuf[LINESIZE];
164
165	if (!sourcing) {
166		if (signal(SIGINT, SIG_IGN) != SIG_IGN)
167			(void)signal(SIGINT, intr);
168		if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
169			(void)signal(SIGHUP, hangup);
170		(void)signal(SIGTSTP, stop);
171		(void)signal(SIGTTOU, stop);
172		(void)signal(SIGTTIN, stop);
173	}
174	setexit();
175	for (;;) {
176		/*
177		 * Print the prompt, if needed.  Clear out
178		 * string space, and flush the output.
179		 */
180		if (!sourcing && value("interactive") != NULL) {
181			reset_on_stop = 1;
182			printf("%s", prompt);
183		}
184		(void)fflush(stdout);
185		sreset();
186		/*
187		 * Read a line of commands from the current input
188		 * and handle end of file specially.
189		 */
190		n = 0;
191		for (;;) {
192			if (readline(input, &linebuf[n], LINESIZE - n) < 0) {
193				if (n == 0)
194					n = -1;
195				break;
196			}
197			if ((n = strlen(linebuf)) == 0)
198				break;
199			n--;
200			if (linebuf[n] != '\\')
201				break;
202			linebuf[n++] = ' ';
203		}
204		reset_on_stop = 0;
205		if (n < 0) {
206				/* eof */
207			if (loading)
208				break;
209			if (sourcing) {
210				unstack();
211				continue;
212			}
213			if (value("interactive") != NULL &&
214			    value("ignoreeof") != NULL &&
215			    ++eofloop < 25) {
216				printf("Use \"quit\" to quit.\n");
217				continue;
218			}
219			break;
220		}
221		eofloop = 0;
222		if (execute(linebuf, 0))
223			break;
224	}
225}
226
227/*
228 * Execute a single command.
229 * Command functions return 0 for success, 1 for error, and -1
230 * for abort.  A 1 or -1 aborts a load or source.  A -1 aborts
231 * the interactive command loop.
232 * Contxt is non-zero if called while composing mail.
233 */
234int
235execute(linebuf, contxt)
236	char linebuf[];
237	int contxt;
238{
239	char word[LINESIZE];
240	char *arglist[MAXARGC];
241	const struct cmd *com;
242	char *cp, *cp2;
243	int c, muvec[2];
244	int e = 1;
245
246	/*
247	 * Strip the white space away from the beginning
248	 * of the command, then scan out a word, which
249	 * consists of anything except digits and white space.
250	 *
251	 * Handle ! escapes differently to get the correct
252	 * lexical conventions.
253	 */
254
255	for (cp = linebuf; isspace(*cp); cp++)
256		;
257	if (*cp == '!') {
258		if (sourcing) {
259			printf("Can't \"!\" while sourcing\n");
260			goto out;
261		}
262		shell(cp+1);
263		return (0);
264	}
265	cp2 = word;
266	while (*cp != '\0' && strchr(" \t0123456789$^.:/-+*'\"", *cp) == NULL)
267		*cp2++ = *cp++;
268	*cp2 = '\0';
269
270	/*
271	 * Look up the command; if not found, bitch.
272	 * Normally, a blank command would map to the
273	 * first command in the table; while sourcing,
274	 * however, we ignore blank lines to eliminate
275	 * confusion.
276	 */
277
278	if (sourcing && *word == '\0')
279		return (0);
280	com = lex(word);
281	if (com == NULL) {
282		printf("Unknown command: \"%s\"\n", word);
283		goto out;
284	}
285
286	/*
287	 * See if we should execute the command -- if a conditional
288	 * we always execute it, otherwise, check the state of cond.
289	 */
290
291	if ((com->c_argtype & F) == 0)
292		if ((cond == CRCV && !rcvmode) || (cond == CSEND && rcvmode))
293			return (0);
294
295	/*
296	 * Process the arguments to the command, depending
297	 * on the type he expects.  Default to an error.
298	 * If we are sourcing an interactive command, it's
299	 * an error.
300	 */
301
302	if (!rcvmode && (com->c_argtype & M) == 0) {
303		printf("May not execute \"%s\" while sending\n",
304		    com->c_name);
305		goto out;
306	}
307	if (sourcing && com->c_argtype & I) {
308		printf("May not execute \"%s\" while sourcing\n",
309		    com->c_name);
310		goto out;
311	}
312	if (readonly && com->c_argtype & W) {
313		printf("May not execute \"%s\" -- message file is read only\n",
314		   com->c_name);
315		goto out;
316	}
317	if (contxt && com->c_argtype & R) {
318		printf("Cannot recursively invoke \"%s\"\n", com->c_name);
319		goto out;
320	}
321	switch (com->c_argtype & ~(F|P|I|M|T|W|R)) {
322	case MSGLIST:
323		/*
324		 * A message list defaulting to nearest forward
325		 * legal message.
326		 */
327		if (msgvec == 0) {
328			printf("Illegal use of \"message list\"\n");
329			break;
330		}
331		if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0)
332			break;
333		if (c  == 0) {
334			*msgvec = first(com->c_msgflag, com->c_msgmask);
335			msgvec[1] = 0;
336		}
337		if (*msgvec == 0) {
338			printf("No applicable messages\n");
339			break;
340		}
341		e = (*com->c_func)(msgvec);
342		break;
343
344	case NDMLIST:
345		/*
346		 * A message list with no defaults, but no error
347		 * if none exist.
348		 */
349		if (msgvec == 0) {
350			printf("Illegal use of \"message list\"\n");
351			break;
352		}
353		if (getmsglist(cp, msgvec, com->c_msgflag) < 0)
354			break;
355		e = (*com->c_func)(msgvec);
356		break;
357
358	case STRLIST:
359		/*
360		 * Just the straight string, with
361		 * leading blanks removed.
362		 */
363		while (isspace(*cp))
364			cp++;
365		e = (*com->c_func)(cp);
366		break;
367
368	case RAWLIST:
369		/*
370		 * A vector of strings, in shell style.
371		 */
372		if ((c = getrawlist(cp, arglist,
373		    sizeof(arglist) / sizeof(*arglist))) < 0)
374			break;
375		if (c < com->c_minargs) {
376			printf("%s requires at least %d arg(s)\n",
377			    com->c_name, com->c_minargs);
378			break;
379		}
380		if (c > com->c_maxargs) {
381			printf("%s takes no more than %d arg(s)\n",
382			    com->c_name, com->c_maxargs);
383			break;
384		}
385		e = (*com->c_func)(arglist);
386		break;
387
388	case NOLIST:
389		/*
390		 * Just the constant zero, for exiting,
391		 * eg.
392		 */
393		e = (*com->c_func)(0);
394		break;
395
396	default:
397		errx(1, "Unknown argtype");
398	}
399
400out:
401	/*
402	 * Exit the current source file on
403	 * error.
404	 */
405	if (e) {
406		if (e < 0)
407			return (1);
408		if (loading)
409			return (1);
410		if (sourcing)
411			unstack();
412		return (0);
413	}
414	if (value("autoprint") != NULL && com->c_argtype & P)
415		if ((dot->m_flag & MDELETED) == 0) {
416			muvec[0] = dot - &message[0] + 1;
417			muvec[1] = 0;
418			type(muvec);
419		}
420	if (!sourcing && (com->c_argtype & T) == 0)
421		sawcom = 1;
422	return (0);
423}
424
425/*
426 * Set the size of the message vector used to construct argument
427 * lists to message list functions.
428 */
429void
430setmsize(sz)
431	int sz;
432{
433
434	if (msgvec != 0)
435		(void)free(msgvec);
436	msgvec = calloc((unsigned)(sz + 1), sizeof(*msgvec));
437}
438
439/*
440 * Find the correct command in the command table corresponding
441 * to the passed command "word"
442 */
443
444__const struct cmd *
445lex(word)
446	char word[];
447{
448	const struct cmd *cp;
449
450	/*
451	 * ignore trailing chars after `#'
452	 *
453	 * lines with beginning `#' are comments
454	 * spaces before `#' are ignored in execute()
455	 */
456
457	if (*word == '#')
458	    *(word+1) = '\0';
459
460
461	for (cp = &cmdtab[0]; cp->c_name != NULL; cp++)
462		if (isprefix(word, cp->c_name))
463			return (cp);
464	return (NULL);
465}
466
467/*
468 * Determine if as1 is a valid prefix of as2.
469 * Return true if yep.
470 */
471int
472isprefix(as1, as2)
473	const char *as1, *as2;
474{
475	const char *s1, *s2;
476
477	s1 = as1;
478	s2 = as2;
479	while (*s1++ == *s2)
480		if (*s2++ == '\0')
481			return (1);
482	return (*--s1 == '\0');
483}
484
485/*
486 * The following gets called on receipt of an interrupt.  This is
487 * to abort printout of a command, mainly.
488 * Dispatching here when command() is inactive crashes rcv.
489 * Close all open files except 0, 1, 2, and the temporary.
490 * Also, unstack all source files.
491 */
492
493int	inithdr;			/* am printing startup headers */
494
495/*ARGSUSED*/
496void
497intr(s)
498	int s;
499{
500
501	noreset = 0;
502	if (!inithdr)
503		sawcom++;
504	inithdr = 0;
505	while (sourcing)
506		unstack();
507
508	close_all_files();
509
510	if (image >= 0) {
511		(void)close(image);
512		image = -1;
513	}
514	fprintf(stderr, "Interrupt\n");
515	reset(0);
516}
517
518/*
519 * When we wake up after ^Z, reprint the prompt.
520 */
521void
522stop(s)
523	int s;
524{
525	sig_t old_action = signal(s, SIG_DFL);
526
527	(void)sigsetmask(sigblock(0) & ~sigmask(s));
528	(void)kill(0, s);
529	(void)sigblock(sigmask(s));
530	(void)signal(s, old_action);
531	if (reset_on_stop) {
532		reset_on_stop = 0;
533		reset(0);
534	}
535}
536
537/*
538 * Branch here on hangup signal and simulate "exit".
539 */
540/*ARGSUSED*/
541void
542hangup(s)
543	int s;
544{
545
546	/* nothing to do? */
547	exit(1);
548}
549
550/*
551 * Announce the presence of the current Mail version,
552 * give the message count, and print a header listing.
553 */
554void
555announce()
556{
557	int vec[2], mdot;
558
559	mdot = newfileinfo();
560	vec[0] = mdot;
561	vec[1] = 0;
562	dot = &message[mdot - 1];
563	if (msgCount > 0 && value("noheader") == NULL) {
564		inithdr++;
565		headers(vec);
566		inithdr = 0;
567	}
568}
569
570/*
571 * Announce information about the file we are editing.
572 * Return a likely place to set dot.
573 */
574int
575newfileinfo()
576{
577	struct message *mp;
578	int u, n, mdot, d, s;
579	char fname[PATHSIZE+1], zname[PATHSIZE+1], *ename;
580
581	for (mp = &message[0]; mp < &message[msgCount]; mp++)
582		if (mp->m_flag & MNEW)
583			break;
584	if (mp >= &message[msgCount])
585		for (mp = &message[0]; mp < &message[msgCount]; mp++)
586			if ((mp->m_flag & MREAD) == 0)
587				break;
588	if (mp < &message[msgCount])
589		mdot = mp - &message[0] + 1;
590	else
591		mdot = 1;
592	s = d = 0;
593	for (mp = &message[0], n = 0, u = 0; mp < &message[msgCount]; mp++) {
594		if (mp->m_flag & MNEW)
595			n++;
596		if ((mp->m_flag & MREAD) == 0)
597			u++;
598		if (mp->m_flag & MDELETED)
599			d++;
600		if (mp->m_flag & MSAVED)
601			s++;
602	}
603	ename = mailname;
604	if (getfold(fname, sizeof(fname) - 1) >= 0) {
605		strcat(fname, "/");
606		if (strncmp(fname, mailname, strlen(fname)) == 0) {
607			(void)snprintf(zname, sizeof(zname), "+%s",
608			    mailname + strlen(fname));
609			ename = zname;
610		}
611	}
612	printf("\"%s\": ", ename);
613	if (msgCount == 1)
614		printf("1 message");
615	else
616		printf("%d messages", msgCount);
617	if (n > 0)
618		printf(" %d new", n);
619	if (u-n > 0)
620		printf(" %d unread", u);
621	if (d > 0)
622		printf(" %d deleted", d);
623	if (s > 0)
624		printf(" %d saved", s);
625	if (readonly)
626		printf(" [Read only]");
627	printf("\n");
628	return (mdot);
629}
630
631/*
632 * Print the current version number.
633 */
634
635/*ARGSUSED*/
636int
637pversion(e)
638	int e;
639{
640
641	printf("Version %s\n", version);
642	return (0);
643}
644
645/*
646 * Load a file of user definitions.
647 */
648void
649load(name)
650	char *name;
651{
652	FILE *in, *oldin;
653
654	if ((in = Fopen(name, "r")) == NULL)
655		return;
656	oldin = input;
657	input = in;
658	loading = 1;
659	sourcing = 1;
660	commands();
661	loading = 0;
662	sourcing = 0;
663	input = oldin;
664	(void)Fclose(in);
665}
666