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