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