popen.c: some cleanup, use other wait_child() from now on
[s-mailx.git] / lex.c
blob49364862ec0cfe16d11896ad1fd0950971f1795f
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ (Lexical processing of) Commands, and the event mainloop.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2013 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
40 #ifndef HAVE_AMALGAMATION
41 # include "nail.h"
42 #endif
44 #include <fcntl.h>
46 struct cmd {
47 char const *name; /* Name of command */
48 int (*func)(void*); /* Implementor of command */
49 enum argtype argtype; /* Arglist type (see below) */
50 short msgflag; /* Required flags of msgs*/
51 short msgmask; /* Relevant flags of msgs */
52 #ifdef HAVE_DOCSTRINGS
53 int docid; /* Translation id of .doc */
54 char const *doc; /* One line doc for command */
55 #endif
57 /* Yechh, can't initialize unions */
58 #define minargs msgflag /* Minimum argcount for RAWLIST */
59 #define maxargs msgmask /* Max argcount for RAWLIST */
61 struct cmd_ghost {
62 struct cmd_ghost *next;
63 struct str cmd; /* Data follows after .name */
64 char name[VFIELD_SIZE(sizeof(size_t))];
67 static int *_msgvec;
68 static int _reset_on_stop; /* do a reset() if stopped */
69 static sighandler_type _oldpipe;
70 static struct cmd_ghost *_cmd_ghosts;
71 /* _cmd_tab[] after fun protos */
73 /* Update mailname (if name != NULL) and displayname, return wether displayname
74 * was large enough to swallow mailname */
75 static bool_t _update_mailname(char const *name);
76 #ifdef HAVE_MBLEN /* TODO unite __narrow_{pre,suf}fix() into one function! */
77 SINLINE size_t __narrow_prefix(char const *cp, size_t maxl);
78 SINLINE size_t __narrow_suffix(char const *cp, size_t cpl, size_t maxl);
79 #endif
81 /* Isolate the command from the arguments */
82 static char * _lex_isolate(char const *comm);
84 /* Get first-fit, or NULL */
85 static struct cmd const * _lex(char const *comm);
87 /* Command ghost handling */
88 static int _ghost(void *v);
89 static int _unghost(void *v);
91 /* Print a list of all commands */
92 static int _pcmdlist(void *v);
93 static int __pcmd_cmp(void const *s1, void const *s2);
95 static void stop(int s);
96 static void hangup(int s);
98 /* List of all commands */
99 static struct cmd const _cmd_tab[] = {
100 #include "cmd_tab.h"
103 #ifdef HAVE_MBLEN
104 SINLINE size_t
105 __narrow_prefix(char const *cp, size_t maxl)
107 int err;
108 size_t i, ok;
110 for (err = ok = i = 0; i < maxl;) {
111 int ml = mblen(cp, maxl - i);
112 if (ml < 0) { /* XXX _narrow_prefix(): mblen() error; action? */
113 (void)mblen(NULL, 0);
114 err = 1;
115 ml = 1;
116 } else {
117 if (! err)
118 ok = i;
119 err = 0;
120 if (ml == 0)
121 break;
123 cp += ml;
124 i += ml;
126 return ok;
129 SINLINE size_t
130 __narrow_suffix(char const *cp, size_t cpl, size_t maxl)
132 int err;
133 size_t i, ok;
135 for (err = ok = i = 0; cpl > maxl || err;) {
136 int ml = mblen(cp, cpl);
137 if (ml < 0) { /* XXX _narrow_suffix(): mblen() error; action? */
138 (void)mblen(NULL, 0);
139 err = 1;
140 ml = 1;
141 } else {
142 if (! err)
143 ok = i;
144 err = 0;
145 if (ml == 0)
146 break;
148 cp += ml;
149 i += ml;
150 cpl -= ml;
152 return ok;
154 #endif /* HAVE_MBLEN */
156 static bool_t
157 _update_mailname(char const *name)
159 char tbuf[MAXPATHLEN], *mailp, *dispp;
160 size_t i, j;
161 bool_t rv;
163 /* Don't realpath(3) if it's only an update request */
164 if (name != NULL) {
165 #ifdef HAVE_REALPATH
166 enum protocol p = which_protocol(name);
167 if (p == PROTO_FILE || p == PROTO_MAILDIR) {
168 if (realpath(name, mailname) == NULL) {
169 fprintf(stderr, tr(151, "Can't canonicalize `%s'\n"), name);
170 rv = FAL0;
171 goto jleave;
173 } else
174 #endif
175 n_strlcpy(mailname, name, sizeof(mailname));
178 mailp = mailname;
179 dispp = displayname;
181 /* Don't display an absolute path but "+FOLDER" if under *folder* */
182 if (getfold(tbuf, sizeof tbuf)) {
183 i = strlen(tbuf);
184 if (i < sizeof(tbuf) - 1)
185 tbuf[i++] = '/';
186 if (strncmp(tbuf, mailp, i) == 0) {
187 mailp += i;
188 *dispp++ = '+';
192 /* We want to see the name of the folder .. on the screen */
193 i = strlen(mailp);
194 if ((rv = (i < sizeof(displayname) - 1)))
195 memcpy(dispp, mailp, i + 1);
196 else {
197 /* Avoid disrupting multibyte sequences (if possible) */
198 #ifndef HAVE_MBLEN
199 j = sizeof(displayname) / 3 - 1;
200 i -= sizeof(displayname) - (1/* + */ + 3) - j;
201 #else
202 j = __narrow_prefix(mailp, sizeof(displayname) / 3);
203 i = j + __narrow_suffix(mailp + j, i - j,
204 sizeof(displayname) - (1/* + */ + 3 + 1) - j);
205 #endif
206 snprintf(dispp, sizeof(displayname), "%.*s...%s",
207 (int)j, mailp, mailp + i);
209 #ifdef HAVE_REALPATH
210 jleave:
211 #endif
212 return rv;
215 static char *
216 _lex_isolate(char const *comm)
218 while (*comm && strchr(" \t0123456789$^.:/-+*'\",;(`", *comm) == NULL)
219 ++comm;
220 return UNCONST(comm);
223 static struct cmd const *
224 _lex(char const *comm)
226 struct cmd const *cp;
228 for (cp = _cmd_tab; cp->name != NULL; ++cp)
229 if (is_prefix(comm, cp->name))
230 goto jleave;
231 cp = NULL;
232 jleave:
233 return cp;
236 static int
237 _ghost(void *v)
239 char const **argv = (char const **)v;
240 struct cmd_ghost *lcg, *cg;
241 size_t nl, cl;
243 /* Show the list? */
244 if (*argv == NULL) {
245 printf(tr(144, "Command ghosts are:\n"));
246 for (nl = 0, cg = _cmd_ghosts; cg != NULL; cg = cg->next) {
247 cl = strlen(cg->name) + 5 + cg->cmd.l + 3;
248 if ((nl += cl) >= (size_t)scrnwidth) {
249 nl = cl;
250 printf("\n");
252 printf((cg->next != NULL ? "%s -> <%s>, " : "%s -> <%s>\n"),
253 cg->name, cg->cmd.s);
255 v = NULL;
256 goto jleave;
259 /* Request to add new ghost */
260 if (argv[1] == NULL || argv[1][0] == '\0' || argv[2] != NULL) {
261 fprintf(stderr, tr(159, "Usage: %s\n"),
262 tr(425, "Define a <ghost> of <command>, or list all ghosts"));
263 v = NULL;
264 goto jleave;
267 /* Check that we can deal with this one */
268 switch (argv[0][0]) {
269 case '|':
270 case '~':
271 case '?':
272 case '#':
273 /* FALLTHRU */
274 case '\0':
275 goto jecanon;
276 default:
277 if (argv[0] == _lex_isolate(argv[0])) {
278 jecanon:
279 fprintf(stderr, tr(151, "Can't canonicalize `%s'\n"), argv[0]);
280 v = NULL;
281 goto jleave;
283 break;
286 /* Always recreate */
287 for (lcg = NULL, cg = _cmd_ghosts; cg != NULL; lcg = cg, cg = cg->next)
288 if (strcmp(cg->name, argv[0]) == 0) {
289 if (lcg != NULL)
290 lcg->next = cg->next;
291 else
292 _cmd_ghosts = cg->next;
293 free(cg);
294 break;
297 /* Need a new one */
298 nl = strlen(argv[0]) + 1;
299 cl = strlen(argv[1]) + 1;
300 cg = smalloc(sizeof(*cg) - VFIELD_SIZEOF(struct cmd_ghost, name) + nl + cl);
301 cg->next = _cmd_ghosts;
302 memcpy(cg->name, argv[0], nl);
303 cg->cmd.s = cg->name + nl;
304 cg->cmd.l = cl - 1;
305 memcpy(cg->cmd.s, argv[1], cl);
307 _cmd_ghosts = cg;
308 jleave:
309 return (v == NULL);
312 static int
313 _unghost(void *v)
315 int rv = 0;
316 char const **argv = v, *cp;
317 struct cmd_ghost *lcg, *cg;
319 while ((cp = *argv++) != NULL) {
320 for (lcg = NULL, cg = _cmd_ghosts; cg != NULL; lcg = cg, cg = cg->next)
321 if (strcmp(cg->name, cp) == 0) {
322 if (lcg != NULL)
323 lcg->next = cg->next;
324 else
325 _cmd_ghosts = cg->next;
326 free(cg);
327 goto jouter;
329 fprintf(stderr, tr(91, "Unknown command: `%s'\n"), cp);
330 rv = 1;
331 jouter:
334 return rv;
337 static int
338 __pcmd_cmp(void const *s1, void const *s2)
340 struct cmd const * const *c1 = s1, * const *c2 = s2;
341 return (strcmp((*c1)->name, (*c2)->name));
344 static int
345 _pcmdlist(void *v)
347 struct cmd const **cpa, *cp, **cursor;
348 size_t i;
349 (void)v;
351 for (i = 0; _cmd_tab[i].name != NULL; ++i)
353 ++i;
354 cpa = ac_alloc(sizeof(cp) * i);
356 for (i = 0; (cp = _cmd_tab + i)->name != NULL; ++i)
357 cpa[i] = cp;
358 cpa[i] = NULL;
360 qsort(cpa, i, sizeof(cp), &__pcmd_cmp);
362 printf(tr(14, "Commands are:\n"));
363 for (i = 0, cursor = cpa; (cp = *cursor++) != NULL;) {
364 size_t j;
365 if (cp->func == &ccmdnotsupp)
366 continue;
367 j = strlen(cp->name) + 2;
368 if ((i += j) > 72) {
369 i = j;
370 printf("\n");
372 printf((*cursor != NULL ? "%s, " : "%s\n"), cp->name);
375 ac_free(cpa);
376 return 0;
380 * Set up editing on the given file name.
381 * If the first character of name is %, we are considered to be
382 * editing the file, otherwise we are reading our mail which has
383 * signficance for mbox and so forth.
385 * nmail: Check for new mail in the current folder only.
387 FL int
388 setfile(char const *name, int nmail)
390 FILE *ibuf;
391 int i, compressed = 0;
392 struct stat stb;
393 bool_t isedit;
394 char const *who = name[1] ? name + 1 : myname;
395 static int shudclob;
396 size_t offset;
397 int omsgCount = 0;
398 struct shortcut *sh;
399 struct flock flp;
401 /* Note we don't 'userid(myname) != getuid()', preliminary steps are usually
402 * necessary to make a mailbox accessible by a different user, and if that
403 * has happened, let's just let the usual file perms decide */
404 isedit = (*name != '%' && ((sh = get_shortcut(name)) == NULL ||
405 *sh->sh_long != '%'));
406 if ((name = expand(name)) == NULL)
407 return (-1);
409 switch (which_protocol(name)) {
410 case PROTO_FILE:
411 break;
412 case PROTO_MAILDIR:
413 return (maildir_setfile(name, nmail, isedit));
414 #ifdef HAVE_POP3
415 case PROTO_POP3:
416 shudclob = 1;
417 return (pop3_setfile(name, nmail, isedit));
418 #endif
419 #ifdef HAVE_IMAP
420 case PROTO_IMAP:
421 shudclob = 1;
422 if (nmail) {
423 if (mb.mb_type == MB_CACHE)
424 return 1;
426 return imap_setfile(name, nmail, isedit);
427 #endif
428 default:
429 fprintf(stderr, tr(217, "Cannot handle protocol: %s\n"), name);
430 return (-1);
433 if ((ibuf = Zopen(name, "r", &compressed)) == NULL) {
434 if ((!isedit && errno == ENOENT) || nmail) {
435 if (nmail)
436 goto jnonmail;
437 goto nomail;
439 perror(name);
440 return(-1);
443 if (fstat(fileno(ibuf), &stb) < 0) {
444 Fclose(ibuf);
445 if (nmail)
446 goto jnonmail;
447 perror("fstat");
448 return (-1);
451 if (S_ISDIR(stb.st_mode)) {
452 Fclose(ibuf);
453 if (nmail)
454 goto jnonmail;
455 errno = EISDIR;
456 perror(name);
457 return (-1);
458 } else if (S_ISREG(stb.st_mode)) {
459 /*EMPTY*/
460 } else {
461 Fclose(ibuf);
462 if (nmail)
463 goto jnonmail;
464 errno = EINVAL;
465 perror(name);
466 return (-1);
470 * Looks like all will be well. We must now relinquish our
471 * hold on the current set of stuff. Must hold signals
472 * while we are reading the new file, else we will ruin
473 * the message[] data structure.
476 hold_sigs(); /* TODO note on this one in quit.c:quit() */
477 if (shudclob && !nmail)
478 quit();
479 #ifdef HAVE_SOCKETS
480 if (!nmail && mb.mb_sock.s_fd >= 0)
481 sclose(&mb.mb_sock);
482 #endif
485 * Copy the messages into /tmp
486 * and set pointers.
489 flp.l_type = F_RDLCK;
490 flp.l_start = 0;
491 flp.l_whence = SEEK_SET;
492 if (!nmail) {
493 mb.mb_type = MB_FILE;
494 mb.mb_perm = (options & OPT_R_FLAG) ? 0 : MB_DELE|MB_EDIT;
495 mb.mb_compressed = compressed;
496 if (compressed) {
497 if (compressed & 0200)
498 mb.mb_perm = 0;
499 } else {
500 if ((i = open(name, O_WRONLY)) < 0)
501 mb.mb_perm = 0;
502 else
503 close(i);
505 if (shudclob) {
506 if (mb.mb_itf) {
507 fclose(mb.mb_itf);
508 mb.mb_itf = NULL;
510 if (mb.mb_otf) {
511 fclose(mb.mb_otf);
512 mb.mb_otf = NULL;
515 shudclob = 1;
516 edit = isedit;
517 initbox(name);
518 offset = 0;
519 flp.l_len = 0;
520 if (!edit && fcntl(fileno(ibuf), F_SETLKW, &flp) < 0) {
521 perror("Unable to lock mailbox");
522 Fclose(ibuf);
523 return -1;
525 } else /* nmail */{
526 fseek(mb.mb_otf, 0L, SEEK_END);
527 fseek(ibuf, mailsize, SEEK_SET);
528 offset = mailsize;
529 omsgCount = msgCount;
530 flp.l_len = offset;
531 if (!edit && fcntl(fileno(ibuf), F_SETLKW, &flp) < 0)
532 goto jnonmail;
534 mailsize = fsize(ibuf);
535 if (nmail && (size_t)mailsize <= offset) {
536 rele_sigs();
537 goto jnonmail;
539 setptr(ibuf, offset);
540 setmsize(msgCount);
541 if (nmail && mb.mb_sorted) {
542 mb.mb_threaded = 0;
543 sort((void *)-1);
545 Fclose(ibuf);
546 rele_sigs();
547 if (!nmail)
548 sawcom = FAL0;
549 if ((!edit || nmail) && msgCount == 0) {
550 jnonmail:
551 if (!nmail) {
552 if (value("emptystart") == NULL)
553 nomail: fprintf(stderr, tr(88, "No mail for %s\n"),
554 who);
556 return 1;
558 if (nmail) {
559 newmailinfo(omsgCount);
561 return(0);
564 FL int
565 newmailinfo(int omsgCount)
567 int mdot;
568 int i;
570 for (i = 0; i < omsgCount; i++)
571 message[i].m_flag &= ~MNEWEST;
572 if (msgCount > omsgCount) {
573 for (i = omsgCount; i < msgCount; i++)
574 message[i].m_flag |= MNEWEST;
575 printf(tr(158, "New mail has arrived.\n"));
576 if (msgCount - omsgCount == 1)
577 printf(tr(214, "Loaded 1 new message.\n"));
578 else
579 printf(tr(215, "Loaded %d new messages.\n"),
580 msgCount - omsgCount);
581 } else
582 printf(tr(224, "Loaded %d messages.\n"), msgCount);
583 callhook(mailname, 1);
584 mdot = getmdot(1);
585 if (value("header")) {
586 #ifdef HAVE_IMAP
587 if (mb.mb_type == MB_IMAP)
588 imap_getheaders(omsgCount+1, msgCount);
589 #endif
590 time_current_update(&time_current, FAL0);
591 while (++omsgCount <= msgCount)
592 if (visible(&message[omsgCount-1]))
593 printhead(omsgCount, stdout, 0);
595 return mdot;
599 * Interpret user commands one by one. If standard input is not a tty,
600 * print no prompt.
602 FL void
603 commands(void)
605 int eofloop = 0, n;
606 char *linebuf = NULL, *av, *nv;
607 size_t linesize = 0;
608 #ifdef HAVE_IMAP
609 int x;
610 #endif
612 if (!sourcing) {
613 if (safe_signal(SIGINT, SIG_IGN) != SIG_IGN)
614 safe_signal(SIGINT, onintr);
615 if (safe_signal(SIGHUP, SIG_IGN) != SIG_IGN)
616 safe_signal(SIGHUP, hangup);
617 /* TODO We do a lot of redundant signal handling, especially
618 * TODO with the command line editor(s); try to merge this */
619 safe_signal(SIGTSTP, stop);
620 safe_signal(SIGTTOU, stop);
621 safe_signal(SIGTTIN, stop);
623 _oldpipe = safe_signal(SIGPIPE, SIG_IGN);
624 safe_signal(SIGPIPE, _oldpipe);
625 setexit();
627 for (;;) {
628 interrupts = 0;
629 handlerstacktop = NULL;
631 * Print the prompt, if needed. Clear out
632 * string space, and flush the output.
634 if (! sourcing && (options & OPT_INTERACTIVE)) {
635 av = (av = value("autoinc")) ? savestr(av) : NULL;
636 nv = (nv = value("newmail")) ? savestr(nv) : NULL;
637 if ((options & OPT_TTYIN) &&
638 (av != NULL || nv != NULL ||
639 mb.mb_type == MB_IMAP)) {
640 struct stat st;
642 n = (av && strcmp(av, "noimap") &&
643 strcmp(av, "nopoll")) |
644 (nv && strcmp(nv, "noimap") &&
645 strcmp(nv, "nopoll"));
646 #ifdef HAVE_IMAP
647 x = !(av || nv);
648 #endif
649 if ((mb.mb_type == MB_FILE &&
650 stat(mailname, &st) == 0 &&
651 st.st_size > mailsize) ||
652 #ifdef HAVE_IMAP
653 (mb.mb_type == MB_IMAP &&
654 imap_newmail(n) > x) ||
655 #endif
656 (mb.mb_type == MB_MAILDIR &&
657 n != 0)) {
658 int odot = dot - &message[0];
659 bool_t odid = did_print_dot;
661 setfile(mailname, 1);
662 if (mb.mb_type != MB_IMAP) {
663 dot = &message[odot];
664 did_print_dot = odid;
668 _reset_on_stop = 1;
669 exit_status = 0;
672 sreset(sourcing);
673 if (!sourcing) {
674 /* TODO Note: this buffer may contain a password
675 * TODO We should redefine the code flow which has
676 * TODO to do that */
677 if ((nv = termios_state.ts_linebuf) != NULL) {
678 termios_state.ts_linebuf = NULL;
679 termios_state.ts_linesize = 0;
680 free(nv); /* TODO pool give-back */
682 /* TODO Due to expand-on-tab of our line editor the
683 * TODO buffer may grow */
684 if (linesize > LINESIZE * 3) {
685 free(linebuf); /* TODO pool! but what? */
686 linebuf = NULL;
687 linesize = 0;
692 * Read a line of commands from the current input
693 * and handle end of file specially.
695 n = readline_input(LNED_LF_ESC | LNED_HIST_ADD, NULL,
696 &linebuf, &linesize);
697 _reset_on_stop = 0;
698 if (n < 0) {
699 /* eof */
700 if (loading)
701 break;
702 if (sourcing) {
703 unstack();
704 continue;
706 if ((options & OPT_INTERACTIVE) &&
707 value("ignoreeof") && ++eofloop < 25) {
708 printf(tr(89, "Use `quit' to quit.\n"));
709 continue;
711 break;
714 eofloop = 0;
715 inhook = 0;
716 if (execute(linebuf, 0, n))
717 break;
718 if (exit_status != EXIT_OK && (options & OPT_BATCH_FLAG) &&
719 boption("batch-exit-on-error"))
720 break;
723 if (linebuf != NULL)
724 free(linebuf);
725 if (sourcing)
726 sreset(FAL0);
730 * Execute a single command.
731 * Command functions return 0 for success, 1 for error, and -1
732 * for abort. A 1 or -1 aborts a load or source. A -1 aborts
733 * the interactive command loop.
734 * Contxt is non-zero if called while composing mail.
736 FL int
737 execute(char *linebuf, int contxt, size_t linesize)
739 char _wordbuf[2], *arglist[MAXARGC], *cp, *word;
740 struct cmd_ghost *cg = NULL;
741 struct cmd const *com = NULL;
742 int muvec[2], c, e = 1;
744 /* Command ghosts that refer to shell commands or macro expansion restart */
745 jrestart:
747 /* Strip the white space away from the beginning of the command */
748 for (cp = linebuf; whitechar(*cp); ++cp)
750 linesize -= (size_t)(cp - linebuf);
752 /* Ignore comments */
753 if (*cp == '#')
754 goto jleave0;
756 /* Handle ! differently to get the correct lexical conventions */
757 if (*cp == '!') {
758 if (sourcing) {
759 fprintf(stderr, tr(90, "Can't `!' while sourcing\n"));
760 goto jleave;
762 shell(++cp);
763 goto jleave0;
766 /* Isolate the actual command; since it may not necessarily be
767 * separated from the arguments (as in `p1') we need to duplicate it to
768 * be able to create a NUL terminated version.
769 * We must be aware of special one letter commands here */
770 arglist[0] = cp;
771 switch (*cp) {
772 case '|':
773 case '~':
774 case '?':
775 ++cp;
776 /* FALLTHRU */
777 case '\0':
778 break;
779 default:
780 cp = _lex_isolate(cp);
781 break;
783 c = (int)(cp - arglist[0]);
784 linesize -= c;
785 word = (c < (int)sizeof _wordbuf) ? _wordbuf : salloc(c + 1);
786 memcpy(word, arglist[0], c);
787 word[c] = '\0';
789 /* Look up the command; if not found, bitch.
790 * Normally, a blank command would map to the first command in the
791 * table; while sourcing, however, we ignore blank lines to eliminate
792 * confusion; act just the same for ghosts */
793 if ((sourcing || cg != NULL) && *word == '\0')
794 goto jleave0;
796 /* If this is the first evaluation, check command ghosts */
797 if (cg == NULL) {
798 /* TODO relink list head, so it's sort over time on usage? */
799 for (cg = _cmd_ghosts; cg != NULL; cg = cg->next)
800 if (strcmp(word, cg->name) == 0) {
801 if (linesize > 0) {
802 size_t i = cg->cmd.l;
803 linebuf = salloc(i + 1 + linesize);
804 memcpy(linebuf, cg->cmd.s, i);
805 linebuf[i++] = ' ';
806 memcpy(linebuf + i, cp, linesize);
807 linebuf[i += linesize] = '\0';
808 linesize = i;
809 } else {
810 linebuf = cg->cmd.s;
811 linesize = cg->cmd.l;
813 goto jrestart;
817 if (com == NULL)
818 com = _lex(word);
819 if (com == NULL || com->func == &ccmdnotsupp) {
820 fprintf(stderr, tr(91, "Unknown command: `%s'\n"), word);
821 if (com != NULL) {
822 ccmdnotsupp(NULL);
823 com = NULL;
825 goto jleave;
829 * See if we should execute the command -- if a conditional
830 * we always execute it, otherwise, check the state of cond.
832 if ((com->argtype & F) == 0) {
833 if ((cond == CRCV && (options & OPT_SENDMODE)) ||
834 (cond == CSEND && ! (options & OPT_SENDMODE)) ||
835 (cond == CTERM && ! (options & OPT_TTYIN)) ||
836 (cond == CNONTERM && (options & OPT_TTYIN)))
837 goto jleave0;
841 * Process the arguments to the command, depending
842 * on the type he expects. Default to an error.
843 * If we are sourcing an interactive command, it's
844 * an error.
846 if ((options & OPT_SENDMODE) && (com->argtype & M) == 0) {
847 fprintf(stderr, tr(92,
848 "May not execute `%s' while sending\n"),
849 com->name);
850 goto jleave;
852 if (sourcing && com->argtype & I) {
853 fprintf(stderr, tr(93,
854 "May not execute `%s' while sourcing\n"),
855 com->name);
856 goto jleave;
858 if ((mb.mb_perm & MB_DELE) == 0 && com->argtype & W) {
859 fprintf(stderr, tr(94, "May not execute `%s' -- "
860 "message file is read only\n"),
861 com->name);
862 goto jleave;
864 if (contxt && com->argtype & R) {
865 fprintf(stderr, tr(95,
866 "Cannot recursively invoke `%s'\n"), com->name);
867 goto jleave;
869 if (mb.mb_type == MB_VOID && com->argtype & A) {
870 fprintf(stderr, tr(257,
871 "Cannot execute `%s' without active mailbox\n"),
872 com->name);
873 goto jleave;
876 switch (com->argtype & ~(F|P|I|M|T|W|R|A)) {
877 case MSGLIST:
878 /* Message list defaulting to nearest forward legal message */
879 if (_msgvec == 0)
880 goto je96;
881 if ((c = getmsglist(cp, _msgvec, com->msgflag)) < 0)
882 break;
883 if (c == 0) {
884 *_msgvec = first(com->msgflag, com->msgmask);
885 if (*_msgvec != 0)
886 _msgvec[1] = 0;
888 if (*_msgvec == 0) {
889 if (! inhook)
890 printf(tr(97, "No applicable messages\n"));
891 break;
893 e = (*com->func)(_msgvec);
894 break;
896 case NDMLIST:
897 /* Message list with no defaults, but no error if none exist */
898 if (_msgvec == 0) {
899 je96:
900 fprintf(stderr, tr(96,
901 "Illegal use of `message list'\n"));
902 break;
904 if ((c = getmsglist(cp, _msgvec, com->msgflag)) < 0)
905 break;
906 e = (*com->func)(_msgvec);
907 break;
909 case STRLIST:
910 /* Just the straight string, with leading blanks removed */
911 while (whitechar(*cp))
912 cp++;
913 e = (*com->func)(cp);
914 break;
916 case RAWLIST:
917 case ECHOLIST:
918 /* A vector of strings, in shell style */
919 if ((c = getrawlist(cp, linesize, arglist,
920 sizeof arglist / sizeof *arglist,
921 (com->argtype&~(F|P|I|M|T|W|R|A)) == ECHOLIST)
922 ) < 0)
923 break;
924 if (c < com->minargs) {
925 fprintf(stderr, tr(99,
926 "`%s' requires at least %d arg(s)\n"),
927 com->name, com->minargs);
928 break;
930 if (c > com->maxargs) {
931 fprintf(stderr, tr(100,
932 "`%s' takes no more than %d arg(s)\n"),
933 com->name, com->maxargs);
934 break;
936 e = (*com->func)(arglist);
937 break;
939 case NOLIST:
940 /* Just the constant zero, for exiting, eg. */
941 e = (*com->func)(0);
942 break;
944 default:
945 panic(tr(101, "Unknown argument type"));
948 jleave:
949 /* Exit the current source file on error */
950 if ((exec_last_comm_error = (e != 0))) {
951 if (e < 0)
952 return 1;
953 if (loading)
954 return 1;
955 if (sourcing)
956 unstack();
957 return 0;
959 if (com == (struct cmd*)NULL )
960 return 0;
961 if (boption("autoprint") && com->argtype & P)
962 if (visible(dot)) {
963 muvec[0] = dot - &message[0] + 1;
964 muvec[1] = 0;
965 type(muvec);
967 if (! sourcing && ! inhook && (com->argtype & T) == 0)
968 sawcom = TRU1;
969 jleave0:
970 return 0;
974 * Set the size of the message vector used to construct argument
975 * lists to message list functions.
977 FL void
978 setmsize(int sz)
981 if (_msgvec != 0)
982 free(_msgvec);
983 _msgvec = (int*)scalloc(sz + 1, sizeof *_msgvec);
987 * The following gets called on receipt of an interrupt. This is
988 * to abort printout of a command, mainly.
989 * Dispatching here when command() is inactive crashes rcv.
990 * Close all open files except 0, 1, 2, and the temporary.
991 * Also, unstack all source files.
994 static int inithdr; /* am printing startup headers */
996 /*ARGSUSED*/
997 FL void
998 onintr(int s)
1000 if (handlerstacktop != NULL) {
1001 handlerstacktop(s);
1002 return;
1004 safe_signal(SIGINT, onintr);
1005 noreset = 0;
1006 if (!inithdr)
1007 sawcom = TRU1;
1008 inithdr = 0;
1009 while (sourcing)
1010 unstack();
1012 termios_state_reset();
1013 close_all_files();
1015 if (image >= 0) {
1016 close(image);
1017 image = -1;
1019 if (interrupts != 1)
1020 fprintf(stderr, tr(102, "Interrupt\n"));
1021 safe_signal(SIGPIPE, _oldpipe);
1022 reset(0);
1026 * When we wake up after ^Z, reprint the prompt.
1028 static void
1029 stop(int s)
1031 sighandler_type old_action = safe_signal(s, SIG_DFL);
1032 sigset_t nset;
1034 sigemptyset(&nset);
1035 sigaddset(&nset, s);
1036 sigprocmask(SIG_UNBLOCK, &nset, (sigset_t *)NULL);
1037 kill(0, s);
1038 sigprocmask(SIG_BLOCK, &nset, (sigset_t *)NULL);
1039 safe_signal(s, old_action);
1040 if (_reset_on_stop) {
1041 _reset_on_stop = 0;
1042 reset(0);
1047 * Branch here on hangup signal and simulate "exit".
1049 /*ARGSUSED*/
1050 static void
1051 hangup(int s)
1053 (void)s;
1054 /* nothing to do? */
1055 exit(1);
1059 * Announce the presence of the current Mail version,
1060 * give the message count, and print a header listing.
1062 FL void
1063 announce(int printheaders)
1065 int vec[2], mdot;
1067 mdot = newfileinfo();
1068 vec[0] = mdot;
1069 vec[1] = 0;
1070 dot = &message[mdot - 1];
1071 if (printheaders && msgCount > 0 && value("header") != NULL) {
1072 inithdr++;
1073 headers(vec);
1074 inithdr = 0;
1079 * Announce information about the file we are editing.
1080 * Return a likely place to set dot.
1082 FL int
1083 newfileinfo(void)
1085 struct message *mp;
1086 int u, n, mdot, d, s, hidden, moved;
1088 if (mb.mb_type == MB_VOID)
1089 return 1;
1090 mdot = getmdot(0);
1091 s = d = hidden = moved =0;
1092 for (mp = &message[0], n = 0, u = 0; mp < &message[msgCount]; mp++) {
1093 if (mp->m_flag & MNEW)
1094 n++;
1095 if ((mp->m_flag & MREAD) == 0)
1096 u++;
1097 if ((mp->m_flag & (MDELETED|MSAVED)) == (MDELETED|MSAVED))
1098 moved++;
1099 if ((mp->m_flag & (MDELETED|MSAVED)) == MDELETED)
1100 d++;
1101 if ((mp->m_flag & (MDELETED|MSAVED)) == MSAVED)
1102 s++;
1103 if (mp->m_flag & MHIDDEN)
1104 hidden++;
1107 /* If displayname gets truncated the user effectively has no option to see
1108 * the full pathname of the mailbox, so print it at least for '? fi' */
1109 printf(tr(103, "\"%s\": "),
1110 (_update_mailname(NULL) ? displayname : mailname));
1111 if (msgCount == 1)
1112 printf(tr(104, "1 message"));
1113 else
1114 printf(tr(105, "%d messages"), msgCount);
1115 if (n > 0)
1116 printf(tr(106, " %d new"), n);
1117 if (u-n > 0)
1118 printf(tr(107, " %d unread"), u);
1119 if (d > 0)
1120 printf(tr(108, " %d deleted"), d);
1121 if (s > 0)
1122 printf(tr(109, " %d saved"), s);
1123 if (moved > 0)
1124 printf(tr(136, " %d moved"), moved);
1125 if (hidden > 0)
1126 printf(tr(139, " %d hidden"), hidden);
1127 if (mb.mb_type == MB_CACHE)
1128 printf(" [Disconnected]");
1129 else if (mb.mb_perm == 0)
1130 printf(tr(110, " [Read only]"));
1131 printf("\n");
1132 return(mdot);
1135 FL int
1136 getmdot(int nmail)
1138 struct message *mp;
1139 char *cp;
1140 int mdot;
1141 enum mflag avoid = MHIDDEN|MDELETED;
1143 if (!nmail) {
1144 if (value("autothread"))
1145 thread(NULL);
1146 else if ((cp = value("autosort")) != NULL) {
1147 free(mb.mb_sorted);
1148 mb.mb_sorted = sstrdup(cp);
1149 sort(NULL);
1152 if (mb.mb_type == MB_VOID)
1153 return 1;
1154 if (nmail)
1155 for (mp = &message[0]; mp < &message[msgCount]; mp++)
1156 if ((mp->m_flag & (MNEWEST|avoid)) == MNEWEST)
1157 break;
1158 if (!nmail || mp >= &message[msgCount]) {
1159 for (mp = mb.mb_threaded ? threadroot : &message[0];
1160 mb.mb_threaded ?
1161 mp != NULL : mp < &message[msgCount];
1162 mb.mb_threaded ?
1163 mp = next_in_thread(mp) : mp++)
1164 if ((mp->m_flag & (MNEW|avoid)) == MNEW)
1165 break;
1167 if (mb.mb_threaded ? mp == NULL : mp >= &message[msgCount])
1168 for (mp = mb.mb_threaded ? threadroot : &message[0];
1169 mb.mb_threaded ? mp != NULL:
1170 mp < &message[msgCount];
1171 mb.mb_threaded ? mp = next_in_thread(mp) : mp++)
1172 if (mp->m_flag & MFLAGGED)
1173 break;
1174 if (mb.mb_threaded ? mp == NULL : mp >= &message[msgCount])
1175 for (mp = mb.mb_threaded ? threadroot : &message[0];
1176 mb.mb_threaded ? mp != NULL:
1177 mp < &message[msgCount];
1178 mb.mb_threaded ? mp = next_in_thread(mp) : mp++)
1179 if ((mp->m_flag & (MREAD|avoid)) == 0)
1180 break;
1181 if (mb.mb_threaded ? mp != NULL : mp < &message[msgCount])
1182 mdot = mp - &message[0] + 1;
1183 else if (value("showlast")) {
1184 if (mb.mb_threaded) {
1185 for (mp = this_in_thread(threadroot, -1); mp;
1186 mp = prev_in_thread(mp))
1187 if ((mp->m_flag & avoid) == 0)
1188 break;
1189 mdot = mp ? mp - &message[0] + 1 : msgCount;
1190 } else {
1191 for (mp = &message[msgCount-1]; mp >= &message[0]; mp--)
1192 if ((mp->m_flag & avoid) == 0)
1193 break;
1194 mdot = mp >= &message[0] ? mp-&message[0]+1 : msgCount;
1196 } else if (mb.mb_threaded) {
1197 for (mp = threadroot; mp; mp = next_in_thread(mp))
1198 if ((mp->m_flag & avoid) == 0)
1199 break;
1200 mdot = mp ? mp - &message[0] + 1 : 1;
1201 } else {
1202 for (mp = &message[0]; mp < &message[msgCount]; mp++)
1203 if ((mp->m_flag & avoid) == 0)
1204 break;
1205 mdot = mp < &message[msgCount] ? mp-&message[0]+1 : 1;
1207 return mdot;
1211 * Print the current version number.
1214 /*ARGSUSED*/
1215 FL int
1216 pversion(void *v)
1218 (void)v;
1219 printf(tr(111, "Version %s\n"), version);
1220 return 0;
1223 FL void
1224 initbox(const char *name)
1226 char *tempMesg;
1227 int dummy;
1229 if (mb.mb_type != MB_VOID)
1230 (void)n_strlcpy(prevfile, mailname, MAXPATHLEN);
1231 _update_mailname(name != mailname ? name : NULL);
1232 if ((mb.mb_otf = Ftemp(&tempMesg, "tmpbox", "w", 0600, 0)) == NULL) {
1233 perror(tr(87, "temporary mail message file"));
1234 exit(1);
1236 (void)fcntl(fileno(mb.mb_otf), F_SETFD, FD_CLOEXEC);
1237 if ((mb.mb_itf = safe_fopen(tempMesg, "r", &dummy)) == NULL) {
1238 perror(tr(87, "temporary mail message file"));
1239 exit(1);
1241 (void)fcntl(fileno(mb.mb_itf), F_SETFD, FD_CLOEXEC);
1242 rm(tempMesg);
1243 Ftfree(&tempMesg);
1244 msgCount = 0;
1245 if (message) {
1246 free(message);
1247 message = NULL;
1248 msgspace = 0;
1250 mb.mb_threaded = 0;
1251 if (mb.mb_sorted != NULL) {
1252 free(mb.mb_sorted);
1253 mb.mb_sorted = NULL;
1255 #ifdef HAVE_IMAP
1256 mb.mb_flags = MB_NOFLAGS;
1257 #endif
1258 prevdot = NULL;
1259 dot = NULL;
1260 did_print_dot = FAL0;
1263 #ifdef HAVE_DOCSTRINGS
1264 FL bool_t
1265 print_comm_docstr(char const *comm)
1267 bool_t rv = FAL0;
1268 struct cmd_ghost *cg;
1269 struct cmd const *cp;
1271 /* Ghosts take precedence */
1272 for (cg = _cmd_ghosts; cg != NULL; cg = cg->next)
1273 if (strcmp(comm, cg->name) == 0) {
1274 printf("%s -> <%s>\n", comm, cg->cmd.s);
1275 rv = TRU1;
1276 goto jleave;
1279 for (cp = _cmd_tab; cp->name != NULL; ++cp) {
1280 if (cp->func == &ccmdnotsupp)
1281 continue;
1282 if (strcmp(comm, cp->name) == 0)
1283 printf("%s: %s\n", comm, tr(cp->docid, cp->doc));
1284 else if (is_prefix(comm, cp->name))
1285 printf("%s (%s): %s\n", comm, cp->name, tr(cp->docid, cp->doc));
1286 else
1287 continue;
1288 rv = TRU1;
1289 break;
1291 jleave:
1292 return rv;
1294 #endif
1296 /* vim:set fenc=utf-8:s-it-mode (TODO only partial true) */