Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
att
GitHub Repository: att/ast
Path: blob/master/src/cmd/mailx/lex.c
1808 views
1
/***********************************************************************
2
* *
3
* This software is part of the BSD package *
4
*Copyright (c) 1978-2012 The Regents of the University of California an*
5
* *
6
* Redistribution and use in source and binary forms, with or *
7
* without modification, are permitted provided that the following *
8
* conditions are met: *
9
* *
10
* 1. Redistributions of source code must retain the above *
11
* copyright notice, this list of conditions and the *
12
* following disclaimer. *
13
* *
14
* 2. Redistributions in binary form must reproduce the above *
15
* copyright notice, this list of conditions and the *
16
* following disclaimer in the documentation and/or other *
17
* materials provided with the distribution. *
18
* *
19
* 3. Neither the name of The Regents of the University of California*
20
* names of its contributors may be used to endorse or *
21
* promote products derived from this software without *
22
* specific prior written permission. *
23
* *
24
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND *
25
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, *
26
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF *
27
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE *
28
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS *
29
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, *
30
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED *
31
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, *
32
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON *
33
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, *
34
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY *
35
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE *
36
* POSSIBILITY OF SUCH DAMAGE. *
37
* *
38
* Redistribution and use in source and binary forms, with or without *
39
* modification, are permitted provided that the following conditions *
40
* are met: *
41
* 1. Redistributions of source code must retain the above copyright *
42
* notice, this list of conditions and the following disclaimer. *
43
* 2. Redistributions in binary form must reproduce the above copyright *
44
* notice, this list of conditions and the following disclaimer in *
45
* the documentation and/or other materials provided with the *
46
* distribution. *
47
* 3. Neither the name of the University nor the names of its *
48
* contributors may be used to endorse or promote products derived *
49
* from this software without specific prior written permission. *
50
* *
51
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" *
52
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED *
53
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
54
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS *
55
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, *
56
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
57
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF *
58
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND *
59
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, *
60
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT *
61
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF *
62
* SUCH DAMAGE. *
63
* *
64
* Kurt Shoens (UCB) *
65
* gsf *
66
* *
67
***********************************************************************/
68
#pragma prototyped
69
/*
70
* Mail -- a mail program
71
*
72
* Lexical processing of commands.
73
*/
74
75
#include "mailx.h"
76
77
/*
78
* Inititialize the folder tmp file pointers.
79
*/
80
void
81
settmp(const char* name, int dir)
82
{
83
int fd;
84
85
if (name) {
86
if (state.msg.op || state.folder == FMH)
87
quit();
88
state.readonly = 0;
89
if (dir) {
90
if (eaccess(name, W_OK))
91
state.readonly = 1;
92
}
93
else if ((fd = open(name, O_WRONLY|O_BINARY|O_cloexec)) < 0)
94
state.readonly = 1;
95
else
96
close(fd);
97
if (state.msg.ap) {
98
fileclose(state.msg.ap);
99
state.msg.ap = 0;
100
}
101
if (state.msg.ip) {
102
fileclose(state.msg.ip);
103
state.msg.ip = 0;
104
}
105
if (state.msg.op) {
106
fileclose(state.msg.op);
107
state.msg.op = 0;
108
}
109
strncopy(state.path.prev, state.path.mail, sizeof(state.path.prev));
110
if (name != state.path.mail)
111
strncopy(state.path.mail, name, sizeof(state.path.mail));
112
}
113
if (!dir) {
114
if (!(state.msg.op = fileopen(state.tmp.mesg, "EIw")))
115
exit(1);
116
if (!(state.msg.ip = fileopen(state.tmp.mesg, "EIr")))
117
exit(1);
118
rm(state.tmp.mesg);
119
}
120
}
121
122
/*
123
* Set up editing on the given folder.
124
* If the first character of name is not %, we are considered to be
125
* editing the folder, otherwise we are reading our mail which has
126
* signficance for mbox and so forth.
127
*/
128
int
129
setfolder(char* name)
130
{
131
FILE* ibuf;
132
int isedit = *name != '%';
133
char* who = name[1] ? name + 1 : state.var.user;
134
135
if (!(name = expand(name, 1)))
136
return -1;
137
state.msg.inbox = 0;
138
if (imap_name(name)) {
139
imap_setptr(name, isedit);
140
return 0;
141
}
142
if (state.folder == FIMAP)
143
imap_setptr((char*)0, 0);
144
if (!(ibuf = fileopen(name, "Rr"))) {
145
if (state.var.justcheck)
146
exit(1);
147
if (!isedit && errno == ENOENT)
148
goto nomail;
149
if (errno == EISDIR) {
150
mh_setptr(name, isedit);
151
return 0;
152
}
153
note(SYSTEM, "%s", name);
154
return -1;
155
}
156
if (!state.openstat.st_size) {
157
if (state.var.justcheck)
158
exit(1);
159
if (isedit)
160
note(0, "%s: empty file", name);
161
else {
162
fileclose(ibuf);
163
goto nomail;
164
}
165
}
166
if (S_ISDIR(state.openstat.st_mode)) {
167
mh_setptr(name, isedit);
168
return 0;
169
}
170
if (state.var.justcheck)
171
exit(0);
172
173
/*
174
* Looks like all will be well. We must now relinquish our
175
* hold on the current set of stuff. Must hold signals
176
* while we are reading the new file, else we will ruin
177
* the message[] data structure.
178
*/
179
180
holdsigs();
181
settmp(name, 0);
182
state.edit = isedit;
183
state.mailsize = filesize(ibuf);
184
setptr(ibuf, 0);
185
/*
186
* New mail has arrived while we were reading up the mail file,
187
* so reset mailsize to be where we really are in the file...
188
*/
189
state.mailsize = ftell(ibuf);
190
fileclose(ibuf);
191
relsesigs();
192
state.sawcom = 0;
193
if (!state.edit && !state.msg.count) {
194
nomail:
195
if (state.var.justcheck)
196
exit(1);
197
if (!state.var.justheaders) {
198
if (strchr(name, '/') || strchr(name, '\\'))
199
note(ERROR, "No mail for %s", who);
200
else
201
note(ERROR, "No mail", who);
202
}
203
return -1;
204
}
205
return 0;
206
}
207
208
/*
209
* Incorporate any new mail that has arrived since we first
210
* started reading mail.
211
*/
212
int
213
incfile(void)
214
{
215
int newsize;
216
int msgcount = state.msg.count;
217
FILE* fp;
218
219
if (!(fp = fileopen(state.path.mail, "r")))
220
return -1;
221
holdsigs();
222
if (!(newsize = filesize(fp)))
223
return -1; /* mail box is now empty??? */
224
if (newsize < state.mailsize)
225
return -1; /* mail box has shrunk??? */
226
if (newsize == state.mailsize)
227
return 0; /* no new mail */
228
setptr(fp, state.mailsize);
229
state.mailsize = ftell(fp);
230
fileclose(fp);
231
relsesigs();
232
return state.msg.count - msgcount;
233
}
234
235
/*
236
* The following gets called on receipt of an interrupt. This is
237
* to abort printout of a command, mainly.
238
* Dispatching here when command() is inactive crashes rcv.
239
* Close all open files except 0, 1, 2, and the temporary.
240
* Also, unstack all source files.
241
*/
242
static void
243
intr(int sig)
244
{
245
state.noreset = 0;
246
if (state.startup)
247
state.startup = 0;
248
else
249
state.sawcom++;
250
while (state.sourcing)
251
unstack();
252
fileclear();
253
note(0, "Interrupt");
254
reset(sig);
255
}
256
257
/*
258
* When we wake up after ^Z, reprint the prompt.
259
*/
260
static void
261
stop(int sig)
262
{
263
sig_t old_action = signal(sig, SIG_DFL);
264
265
kill(getpid(), sig);
266
signal(sig, old_action);
267
if (state.stopreset) {
268
state.stopreset = 0;
269
reset(sig);
270
}
271
}
272
273
/*
274
* Branch here on hangup signal and simulate "exit".
275
*/
276
/*ARGSUSED*/
277
static void
278
hangup(int sig)
279
{
280
/* nothing to do? */
281
exit(1);
282
}
283
284
/*
285
* Interpret user commands one by one. If standard input is not a tty,
286
* print no prompt.
287
*/
288
void
289
commands(void)
290
{
291
register int n;
292
int eofloop = 0;
293
char linebuf[LINESIZE];
294
295
if (!state.sourcing) {
296
if (signal(SIGINT, SIG_IGN) != SIG_IGN)
297
signal(SIGINT, intr);
298
if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
299
signal(SIGHUP, hangup);
300
#if SIGTSTP
301
signal(SIGTSTP, stop);
302
#endif
303
#if SIGTTOU
304
signal(SIGTTOU, stop);
305
#endif
306
#if SIGTTIN
307
signal(SIGTTIN, stop);
308
#endif
309
}
310
setexit();
311
for (;;) {
312
/*
313
* Print the prompt, if needed. Clear out
314
* string space, and flush the output.
315
*/
316
fflush(stdout);
317
moretop();
318
if (!state.sourcing && state.var.interactive) {
319
if (state.var.autoinc && incfile() > 0)
320
note(0, "New mail has arrived");
321
state.stopreset = 1;
322
if (state.var.coprocess)
323
note(0, "");
324
else if (state.var.prompt)
325
note(PROMPT, "%s", state.var.prompt);
326
}
327
sreset();
328
/*
329
* Read a line of commands from the current input
330
* and handle end of file specially.
331
*/
332
n = 0;
333
for (;;) {
334
if (readline(state.input, &linebuf[n], LINESIZE - n) < 0) {
335
if (!n)
336
n = -1;
337
break;
338
}
339
if (!(n = strlen(linebuf)))
340
break;
341
n--;
342
if (linebuf[n] != '\\')
343
break;
344
linebuf[n++] = ' ';
345
}
346
state.stopreset = 0;
347
if (n < 0) {
348
/* eof */
349
if (state.loading)
350
break;
351
if (state.sourcing) {
352
unstack();
353
continue;
354
}
355
if (state.var.interactive &&
356
state.var.ignoreeof &&
357
++eofloop < 25) {
358
note(0, "Use \"quit\" to quit");
359
continue;
360
}
361
break;
362
}
363
eofloop = 0;
364
if (execute(linebuf, 0))
365
break;
366
}
367
}
368
369
/*
370
* Execute a single command.
371
* Command functions return 0 for success, 1 for error, and -1
372
* for abort. A 1 or -1 aborts a load or source. A -1 aborts
373
* the interactive command loop.
374
* Contxt is non-zero if called while composing mail.
375
*/
376
int
377
execute(char* linebuf, int contxt)
378
{
379
struct cmd* com;
380
register char* s;
381
register int c;
382
register char* a;
383
char* next;
384
struct argvec vec;
385
int e = 1;
386
387
/*
388
* Strip the white space away from the beginning of the command.
389
*/
390
391
for (s = linebuf; isspace(*s); s++);
392
393
/*
394
* Look up the command and execute.
395
*/
396
397
switch (*s) {
398
case '!':
399
a = "shell";
400
s++;
401
break;
402
case '#':
403
return 0;
404
case '=':
405
a = "dot";
406
s++;
407
break;
408
case '?':
409
a = "help";
410
s++;
411
break;
412
case '|':
413
a = "pipe";
414
s++;
415
break;
416
default:
417
if (isalpha(*s)) {
418
a = s;
419
s = 0;
420
}
421
else {
422
if (state.sourcing)
423
return 0;
424
a = "next";
425
}
426
break;
427
}
428
if (!(com = (struct cmd*)strpsearch(state.cmdtab, state.cmdnum, sizeof(struct cmd), a, &next))) {
429
note(0, "\"%s\": unknown command", a);
430
goto out;
431
}
432
state.cmd = com;
433
if (state.clobber = *next == '!')
434
next++;
435
if (!s)
436
s = next;
437
while (isspace(*s))
438
s++;
439
440
/*
441
* See if we should execute the command -- if a conditional
442
* we always execute it, otherwise, check the state of cond.
443
*/
444
445
if (!(com->c_argtype & C) && state.cond && state.cond != state.mode)
446
return 0;
447
448
/*
449
* Process the arguments to the command, depending
450
* on the type expected. Default to an error.
451
* If we are sourcing an interactive command, it's
452
* an error.
453
*/
454
455
if (state.mode == SEND && !(com->c_argtype & M)) {
456
note(0, "Cannot execute \"%s\" while sending",
457
com->c_name);
458
goto out;
459
}
460
if (state.sourcing && (com->c_argtype & I)) {
461
note(0, "Cannot execute \"%s\" while sourcing",
462
com->c_name);
463
goto out;
464
}
465
if (state.readonly && (com->c_argtype & W)) {
466
note(0, "Cannot execute \"%s\" -- message file is read only",
467
com->c_name);
468
goto out;
469
}
470
if (contxt && (com->c_argtype & R)) {
471
note(0, "Cannot recursively invoke \"%s\"", com->c_name);
472
goto out;
473
}
474
switch (com->c_argtype & LISTMASK) {
475
case MSGLIST:
476
/*
477
* A message list defaulting to nearest forward
478
* legal message.
479
*/
480
if (!state.msg.list) {
481
note(0, "Invalid use of \"message list\"");
482
break;
483
}
484
if ((c = getmsglist(s, com->c_msgflag)) < 0)
485
break;
486
if (!c) {
487
state.msg.list->m_index = first(com->c_msgflag, com->c_msgmask);
488
(state.msg.list + 1)->m_index = 0;
489
}
490
if (!state.msg.list->m_index) {
491
note(0, "No applicable messages");
492
break;
493
}
494
e = (*com->c_func)(state.msg.list);
495
break;
496
497
case NDMLIST:
498
/*
499
* A message list with no defaults, but no error
500
* if none exist.
501
*/
502
if (!state.msg.list) {
503
note(0, "Invalid use of \"message list\"");
504
break;
505
}
506
if (getmsglist(s, com->c_msgflag) < 0)
507
break;
508
e = (*com->c_func)(state.msg.list);
509
break;
510
511
case STRLIST:
512
/*
513
* Just the straight string.
514
*/
515
e = (*com->c_func)(s);
516
break;
517
518
case RAWLIST:
519
/*
520
* A vector of strings, in shell style.
521
*/
522
initargs(&vec);
523
getargs(&vec, s);
524
if ((c = endargs(&vec)) < 0)
525
break;
526
if (c < com->c_minargs) {
527
note(0, "%s requires %s %d arg%s",
528
com->c_name,
529
com->c_minargs == com->c_maxargs ? "exactly" : "at least",
530
com->c_minargs,
531
com->c_minargs == 1 ? "" : "s");
532
break;
533
}
534
if (c > com->c_maxargs) {
535
note(0, "%s takes no more than %d arg%s",
536
com->c_name,
537
com->c_maxargs,
538
com->c_maxargs == 1 ? "" : "s");
539
break;
540
}
541
e = (*com->c_func)(vec.argv);
542
break;
543
544
case NOLIST:
545
/*
546
* Just the constant zero, for exiting, e.g.
547
*/
548
e = (*com->c_func)(0);
549
break;
550
551
default:
552
note(PANIC, "0x%08x: unknown argtype", com->c_argtype & LISTMASK);
553
}
554
555
out:
556
/*
557
* Exit the current source file on error.
558
*/
559
if (e) {
560
if (e < 0 || state.loading)
561
return 1;
562
if (state.sourcing)
563
unstack();
564
return 0;
565
}
566
if (state.var.autoprint && (com->c_argtype & P))
567
if (!(state.msg.dot->m_flag & (MDELETE|MNONE|MSPAM))) {
568
state.msg.list->m_index = state.msg.dot - state.msg.list + 1;
569
(state.msg.list + 1)->m_index = 0;
570
type(state.msg.list);
571
}
572
if (!state.sourcing && !(com->c_argtype & Z))
573
state.sawcom = 1;
574
return 0;
575
}
576
577
/*
578
* Announce information about the current folder.
579
* msgcount is state.msg.count before the file was read.
580
* Return a likely place to set dot.
581
*/
582
struct msg*
583
folderinfo(int msgcount)
584
{
585
register struct msg* mp;
586
register int d;
587
register int m;
588
register int n;
589
register int s;
590
register int u;
591
int i;
592
struct msg* dot;
593
char* name;
594
char buf[LINESIZE];
595
596
if (state.var.justheaders && state.var.header)
597
return state.msg.list;
598
if (dot = state.msg.context)
599
state.msg.context = 0;
600
else if (msgcount > 0 && state.msg.dot < state.msg.list + msgcount - 1)
601
dot = state.msg.dot;
602
else {
603
for (mp = state.msg.list + msgcount; mp < state.msg.list + state.msg.count; mp++)
604
if (mp->m_flag & MNEW)
605
break;
606
if (mp >= state.msg.list + state.msg.count)
607
for (mp = state.msg.list + msgcount; mp < state.msg.list + state.msg.count; mp++)
608
if (!(mp->m_flag & MREAD))
609
break;
610
if (mp < state.msg.list + state.msg.count)
611
dot = mp;
612
else if (msgcount <= 0 && state.var.recent)
613
dot = state.msg.list + state.msg.count - 1;
614
else {
615
dot = state.msg.list;
616
if (!state.msg.count)
617
dot += msgcount - 1;
618
}
619
}
620
d = m = n = s = u = 0;
621
for (mp = state.msg.list; mp < state.msg.list + state.msg.count; mp++) {
622
if (!(mp->m_flag & MNONE)) {
623
m++;
624
if (mp->m_flag & MDELETE)
625
d++;
626
else if (mp->m_flag & MSAVE)
627
s++;
628
else if (!(mp->m_flag & (MREAD|MNEW)))
629
u++;
630
else if ((mp->m_flag & (MREAD|MNEW)) == MNEW)
631
n++;
632
}
633
}
634
name = state.path.mail;
635
if (getfolder(buf, sizeof(buf)) >= 0) {
636
i = strlen(buf);
637
buf[i++] = '/';
638
if (!strncmp(state.path.mail, buf, i))
639
sfsprintf(name = buf, sizeof(buf), "+%s", state.path.mail + i);
640
}
641
printf("\"%s\": %d message%s", name, m, m == 1 ? "" : "s");
642
if (msgcount > 0 && state.msg.count > msgcount)
643
printf(" %d incorporated", state.msg.count - msgcount);
644
if (n > 0)
645
printf(" %d new", n);
646
if (u > 0)
647
printf(" %d unread", u);
648
if (d > 0)
649
printf(" %d deleted", d);
650
if (s > 0)
651
printf(" %d saved", s);
652
switch (state.folder) {
653
case FIMAP:
654
printf(" [imap]");
655
break;
656
case FMH:
657
printf(" [mh]");
658
break;
659
}
660
if (state.readonly)
661
printf(" [Read only]");
662
printf("\n");
663
return dot;
664
}
665
666
/*
667
* Announce the presence of the current Mail version,
668
* give the message count, and print a header listing.
669
*/
670
void
671
announce()
672
{
673
state.msg.dot = folderinfo(0);
674
if (state.msg.list) {
675
state.msg.list->m_index = state.msg.dot - state.msg.list + 1;
676
(state.msg.list + 1)->m_index = 0;
677
if (state.msg.count > 0 && state.var.header) {
678
state.startup = 1;
679
headers(state.msg.list);
680
state.startup = 0;
681
}
682
}
683
}
684
685
/*
686
* Print the current version number.
687
*/
688
689
/*ARGSUSED*/
690
int
691
version(void* a)
692
{
693
note(0, "Version %s", state.version);
694
return 0;
695
}
696
697
/*
698
* Print the license and disclaimer.
699
*/
700
701
/*ARGSUSED*/
702
int
703
license(void* a)
704
{
705
version(a);
706
note(0, "\n%s", state.license);
707
return 0;
708
}
709
710
/*
711
* Load a file of user definitions.
712
*/
713
void
714
load(char* name)
715
{
716
register FILE* fp;
717
register FILE* input;
718
719
if (!(fp = fileopen(name, "r")))
720
return;
721
input = state.input;
722
state.input = fp;
723
state.loading = 1;
724
state.sourcing = 1;
725
commands();
726
state.loading = 0;
727
state.sourcing = 0;
728
state.input = input;
729
fileclose(fp);
730
}
731
732