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