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