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