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