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