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