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