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