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