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