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