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