Home | History | Annotate | Line # | Download | only in mail
lex.c revision 1.1
      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