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