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