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