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