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