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