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