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