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