lex.c revision 1591
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] = NULL;
346		}
347		if (*msgvec == NULL) {
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	for (cp = &cmdtab[0]; cp->c_name != NOSTR; cp++)
462		if (isprefix(word, cp->c_name))
463			return(cp);
464	return(NONE);
465}
466
467/*
468 * Determine if as1 is a valid prefix of as2.
469 * Return true if yep.
470 */
471int
472isprefix(as1, as2)
473	char *as1, *as2;
474{
475	register 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		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	sigsetmask(sigblock(0) & ~sigmask(s));
528	kill(0, s);
529	sigblock(sigmask(s));
530	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") == NOSTR) {
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	register struct message *mp;
578	register int u, n, mdot, d, s;
579	char fname[BUFSIZ], zname[BUFSIZ], *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) >= 0) {
605		strcat(fname, "/");
606		if (strncmp(fname, mailname, strlen(fname)) == 0) {
607			sprintf(zname, "+%s", mailname + strlen(fname));
608			ename = zname;
609		}
610	}
611	printf("\"%s\": ", ename);
612	if (msgCount == 1)
613		printf("1 message");
614	else
615		printf("%d messages", msgCount);
616	if (n > 0)
617		printf(" %d new", n);
618	if (u-n > 0)
619		printf(" %d unread", u);
620	if (d > 0)
621		printf(" %d deleted", d);
622	if (s > 0)
623		printf(" %d saved", s);
624	if (readonly)
625		printf(" [Read only]");
626	printf("\n");
627	return(mdot);
628}
629
630/*
631 * Print the current version number.
632 */
633
634/*ARGSUSED*/
635int
636pversion(e)
637	int e;
638{
639	extern char *version;
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	register 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	Fclose(in);
665}
666