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