mk-mk.in: EXT_CFLAGS: remove .eh frames!..
[s-mailx.git] / lex.c
blobab840dbabcac43884745f94d6eceb0a1b8227c13
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 #include "nail.h"
42 #include <fcntl.h>
44 struct cmd {
45 char const *name; /* Name of command */
46 int (*func)(void*); /* Implementor of command */
47 enum argtype argtype; /* Arglist type (see below) */
48 short msgflag; /* Required flags of msgs*/
49 short msgmask; /* Relevant flags of msgs */
50 #ifdef HAVE_DOCSTRINGS
51 int docid; /* Translation id of .doc */
52 char const *doc; /* One line doc for command */
53 #endif
55 /* Yechh, can't initialize unions */
56 #define minargs msgflag /* Minimum argcount for RAWLIST */
57 #define maxargs msgmask /* Max argcount for RAWLIST */
59 struct cmd_ghost {
60 struct cmd_ghost *next;
61 struct str cmd; /* Data follows after .name */
62 char name[VFIELD_SIZE(sizeof(size_t))];
65 static int *_msgvec;
66 static int _reset_on_stop; /* do a reset() if stopped */
67 static sighandler_type _oldpipe;
68 static struct cmd_ghost *_cmd_ghosts;
69 /* _cmd_tab[] after fun protos */
71 /* Update mailname (if *name* != NULL) and displayname */
72 static void _update_mailname(char const *name);
73 #ifdef HAVE_MBLEN /* TODO unite __narrow_{pre,suf}fix() into one function! */
74 SINLINE size_t __narrow_prefix(char const *cp, size_t maxl);
75 SINLINE size_t __narrow_suffix(char const *cp, size_t cpl, size_t maxl);
76 #endif
78 /* Isolate the command from the arguments */
79 static char * _lex_isolate(char const *comm);
81 /* Get first-fit, or NULL */
82 static struct cmd const * _lex(char const *comm);
84 /* Command ghost handling */
85 static int _ghost(void *v);
86 static int _unghost(void *v);
88 /* Print a list of all commands */
89 static int _pcmdlist(void *v);
90 static int __pcmd_cmp(void const *s1, void const *s2);
92 static void stop(int s);
93 static void hangup(int s);
95 /* List of all commands */
96 static struct cmd const _cmd_tab[] = {
97 #include "cmd_tab.h"
100 #ifdef HAVE_MBLEN
101 SINLINE size_t
102 __narrow_prefix(char const *cp, size_t maxl)
104 int err;
105 size_t i, ok;
107 for (err = ok = i = 0; i < maxl;) {
108 int ml = mblen(cp, maxl - i);
109 if (ml < 0) { /* XXX _narrow_prefix(): mblen() error; action? */
110 (void)mblen(NULL, 0);
111 err = 1;
112 ml = 1;
113 } else {
114 if (! err)
115 ok = i;
116 err = 0;
117 if (ml == 0)
118 break;
120 cp += ml;
121 i += ml;
123 return ok;
126 SINLINE size_t
127 __narrow_suffix(char const *cp, size_t cpl, size_t maxl)
129 int err;
130 size_t i, ok;
132 for (err = ok = i = 0; cpl > maxl || err;) {
133 int ml = mblen(cp, cpl);
134 if (ml < 0) { /* XXX _narrow_suffix(): mblen() error; action? */
135 (void)mblen(NULL, 0);
136 err = 1;
137 ml = 1;
138 } else {
139 if (! err)
140 ok = i;
141 err = 0;
142 if (ml == 0)
143 break;
145 cp += ml;
146 i += ml;
147 cpl -= ml;
149 return ok;
151 #endif /* HAVE_MBLEN */
153 static void
154 _update_mailname(char const *name)
156 char tbuf[MAXPATHLEN], *mailp, *dispp;
157 size_t i, j;
159 /* Don't realpath(3) if it's only an update request */
160 if (name != NULL) {
161 #ifdef HAVE_REALPATH
162 enum protocol p = which_protocol(name);
163 if (p == PROTO_FILE || p == PROTO_MAILDIR) {
164 if (realpath(name, mailname) == NULL) {
165 fprintf(stderr, tr(151,
166 "Can't canonicalize `%s'\n"), name);
167 goto jleave;
169 } else
170 #endif
171 (void)n_strlcpy(mailname, name, MAXPATHLEN);
174 mailp = mailname;
175 dispp = displayname;
177 /* Don't display an absolute path but "+FOLDER" if under *folder* */
178 if (getfold(tbuf, sizeof tbuf)) {
179 i = strlen(tbuf);
180 if (i < sizeof(tbuf) - 1)
181 tbuf[i++] = '/';
182 if (strncmp(tbuf, mailp, i) == 0) {
183 mailp += i;
184 *dispp++ = '+';
188 /* We want to see the name of the folder .. on the screen */
189 i = strlen(mailp);
190 if (i < sizeof(displayname) - 1)
191 memcpy(dispp, mailp, i + 1);
192 else {
193 /* Avoid disrupting multibyte sequences (if possible) */
194 #ifndef HAVE_MBLEN
195 j = sizeof(displayname) / 3 - 1;
196 i -= sizeof(displayname) - (1/* + */ + 3) - j;
197 #else
198 j = __narrow_prefix(mailp, sizeof(displayname) / 3);
199 i = j + __narrow_suffix(mailp + j, i - j,
200 sizeof(displayname) - (1/* + */ + 3 + 1) - j);
201 #endif
202 (void)snprintf(dispp, sizeof(displayname), "%.*s...%s",
203 (int)j, mailp, mailp + i);
205 #ifdef HAVE_REALPATH
206 jleave: ;
207 #endif
210 static char *
211 _lex_isolate(char const *comm)
213 while (*comm && strchr(" \t0123456789$^.:/-+*'\",;(`", *comm) == NULL)
214 ++comm;
215 return UNCONST(comm);
218 static struct cmd const *
219 _lex(char const *comm)
221 struct cmd const *cp;
223 for (cp = _cmd_tab; cp->name != NULL; ++cp)
224 if (is_prefix(comm, cp->name))
225 goto jleave;
226 cp = NULL;
227 jleave:
228 return cp;
231 static int
232 _ghost(void *v)
234 char const **argv = (char const **)v;
235 struct cmd_ghost *lcg, *cg;
236 size_t nl, cl;
238 /* Show the list? */
239 if (*argv == NULL) {
240 printf(tr(144, "Command ghosts are:\n"));
241 for (nl = 0, cg = _cmd_ghosts; cg != NULL; cg = cg->next) {
242 cl = strlen(cg->name) + 5 + cg->cmd.l + 3;
243 if ((nl += cl) >= (size_t)scrnwidth) {
244 nl = cl;
245 printf("\n");
247 printf((cg->next != NULL ? "%s -> <%s>, " : "%s -> <%s>\n"),
248 cg->name, cg->cmd.s);
250 v = NULL;
251 goto jleave;
254 /* Request to add new ghost */
255 if (argv[1] == NULL || argv[1][0] == '\0' || argv[2] != NULL) {
256 fprintf(stderr, tr(159, "Usage: %s\n"),
257 tr(425, "Define a <ghost> of <command>, or list all ghosts"));
258 v = NULL;
259 goto jleave;
262 /* Check that we can deal with this one */
263 switch (argv[0][0]) {
264 case '|':
265 case '~':
266 case '?':
267 case '#':
268 /* FALLTHRU */
269 case '\0':
270 goto jecanon;
271 default:
272 if (argv[0] == _lex_isolate(argv[0])) {
273 jecanon:
274 fprintf(stderr, tr(151, "Can't canonicalize `%s'\n"), argv[0]);
275 v = NULL;
276 goto jleave;
278 break;
281 /* Always recreate */
282 for (lcg = NULL, cg = _cmd_ghosts; cg != NULL; lcg = cg, cg = cg->next)
283 if (strcmp(cg->name, argv[0]) == 0) {
284 if (lcg != NULL)
285 lcg->next = cg->next;
286 else
287 _cmd_ghosts = cg->next;
288 free(cg);
289 break;
292 /* Need a new one */
293 nl = strlen(argv[0]) + 1;
294 cl = strlen(argv[1]) + 1;
295 cg = smalloc(sizeof(*cg) - VFIELD_SIZEOF(struct cmd_ghost, name) + nl + cl);
296 cg->next = _cmd_ghosts;
297 memcpy(cg->name, argv[0], nl);
298 cg->cmd.s = cg->name + nl;
299 cg->cmd.l = cl - 1;
300 memcpy(cg->cmd.s, argv[1], cl);
302 _cmd_ghosts = cg;
303 jleave:
304 return (v == NULL);
307 static int
308 _unghost(void *v)
310 int rv = 0;
311 char const **argv = v, *cp;
312 struct cmd_ghost *lcg, *cg;
314 while ((cp = *argv++) != NULL) {
315 for (lcg = NULL, cg = _cmd_ghosts; cg != NULL; lcg = cg, cg = cg->next)
316 if (strcmp(cg->name, cp) == 0) {
317 if (lcg != NULL)
318 lcg->next = cg->next;
319 else
320 _cmd_ghosts = cg->next;
321 free(cg);
322 goto jouter;
324 fprintf(stderr, tr(91, "Unknown command: `%s'\n"), cp);
325 rv = 1;
326 jouter:
329 return rv;
332 static int
333 __pcmd_cmp(void const *s1, void const *s2)
335 struct cmd const * const *c1 = s1, * const *c2 = s2;
336 return (strcmp((*c1)->name, (*c2)->name));
339 static int
340 _pcmdlist(void *v)
342 struct cmd const **cpa, *cp, **cursor;
343 size_t i;
344 (void)v;
346 for (i = 0; _cmd_tab[i].name != NULL; ++i)
348 ++i;
349 cpa = ac_alloc(sizeof(cp) * i);
351 for (i = 0; (cp = _cmd_tab + i)->name != NULL; ++i)
352 cpa[i] = cp;
353 cpa[i] = NULL;
355 qsort(cpa, i, sizeof(cp), &__pcmd_cmp);
357 printf(tr(14, "Commands are:\n"));
358 for (i = 0, cursor = cpa; (cp = *cursor++) != NULL;) {
359 size_t j;
360 if (cp->func == &ccmdnotsupp)
361 continue;
362 j = strlen(cp->name) + 2;
363 if ((i += j) > 72) {
364 i = j;
365 printf("\n");
367 printf((*cursor != NULL ? "%s, " : "%s\n"), cp->name);
370 ac_free(cpa);
371 return 0;
375 * Set up editing on the given file name.
376 * If the first character of name is %, we are considered to be
377 * editing the file, otherwise we are reading our mail which has
378 * signficance for mbox and so forth.
380 * nmail: Check for new mail in the current folder only.
383 setfile(char const *name, int nmail)
385 FILE *ibuf;
386 int i, compressed = 0;
387 struct stat stb;
388 bool_t isedit;
389 char const *who = name[1] ? name + 1 : myname;
390 static int shudclob;
391 size_t offset;
392 int omsgCount = 0;
393 struct shortcut *sh;
394 struct flock flp;
396 isedit = (*name != '%' && ((sh = get_shortcut(name)) == NULL ||
397 *sh->sh_long != '%'));
398 if ((name = expand(name)) == NULL)
399 return (-1);
401 switch (which_protocol(name)) {
402 case PROTO_FILE:
403 break;
404 case PROTO_MAILDIR:
405 return (maildir_setfile(name, nmail, isedit));
406 #ifdef HAVE_POP3
407 case PROTO_POP3:
408 shudclob = 1;
409 return (pop3_setfile(name, nmail, isedit));
410 #endif
411 #ifdef HAVE_IMAP
412 case PROTO_IMAP:
413 shudclob = 1;
414 if (nmail) {
415 if (mb.mb_type == MB_CACHE)
416 return 1;
418 return imap_setfile(name, nmail, isedit);
419 #endif
420 default:
421 fprintf(stderr, tr(217, "Cannot handle protocol: %s\n"), name);
422 return (-1);
425 if ((ibuf = Zopen(name, "r", &compressed)) == NULL) {
426 if ((!isedit && errno == ENOENT) || nmail) {
427 if (nmail)
428 goto jnonmail;
429 goto nomail;
431 perror(name);
432 return(-1);
435 if (fstat(fileno(ibuf), &stb) < 0) {
436 Fclose(ibuf);
437 if (nmail)
438 goto jnonmail;
439 perror("fstat");
440 return (-1);
443 if (S_ISDIR(stb.st_mode)) {
444 Fclose(ibuf);
445 if (nmail)
446 goto jnonmail;
447 errno = EISDIR;
448 perror(name);
449 return (-1);
450 } else if (S_ISREG(stb.st_mode)) {
451 /*EMPTY*/
452 } else {
453 Fclose(ibuf);
454 if (nmail)
455 goto jnonmail;
456 errno = EINVAL;
457 perror(name);
458 return (-1);
462 * Looks like all will be well. We must now relinquish our
463 * hold on the current set of stuff. Must hold signals
464 * while we are reading the new file, else we will ruin
465 * the message[] data structure.
468 holdsigs(); /* TODO note on this one in quit.c:quit() */
469 if (shudclob && !nmail)
470 quit();
471 #ifdef HAVE_SOCKETS
472 if (!nmail && mb.mb_sock.s_fd >= 0)
473 sclose(&mb.mb_sock);
474 #endif
477 * Copy the messages into /tmp
478 * and set pointers.
481 flp.l_type = F_RDLCK;
482 flp.l_start = 0;
483 flp.l_whence = SEEK_SET;
484 if (!nmail) {
485 mb.mb_type = MB_FILE;
486 mb.mb_perm = (options & OPT_R_FLAG) ? 0 : MB_DELE|MB_EDIT;
487 mb.mb_compressed = compressed;
488 if (compressed) {
489 if (compressed & 0200)
490 mb.mb_perm = 0;
491 } else {
492 if ((i = open(name, O_WRONLY)) < 0)
493 mb.mb_perm = 0;
494 else
495 close(i);
497 if (shudclob) {
498 if (mb.mb_itf) {
499 fclose(mb.mb_itf);
500 mb.mb_itf = NULL;
502 if (mb.mb_otf) {
503 fclose(mb.mb_otf);
504 mb.mb_otf = NULL;
507 shudclob = 1;
508 edit = isedit;
509 initbox(name);
510 offset = 0;
511 flp.l_len = 0;
512 if (!edit && fcntl(fileno(ibuf), F_SETLKW, &flp) < 0) {
513 perror("Unable to lock mailbox");
514 Fclose(ibuf);
515 return -1;
517 } else /* nmail */{
518 fseek(mb.mb_otf, 0L, SEEK_END);
519 fseek(ibuf, mailsize, SEEK_SET);
520 offset = mailsize;
521 omsgCount = msgCount;
522 flp.l_len = offset;
523 if (!edit && fcntl(fileno(ibuf), F_SETLKW, &flp) < 0)
524 goto jnonmail;
526 mailsize = fsize(ibuf);
527 if (nmail && (size_t)mailsize <= offset) {
528 relsesigs();
529 goto jnonmail;
531 setptr(ibuf, offset);
532 setmsize(msgCount);
533 if (nmail && mb.mb_sorted) {
534 mb.mb_threaded = 0;
535 sort((void *)-1);
537 Fclose(ibuf);
538 relsesigs();
539 if (!nmail)
540 sawcom = FAL0;
541 if ((!edit || nmail) && msgCount == 0) {
542 jnonmail:
543 if (!nmail) {
544 if (value("emptystart") == NULL)
545 nomail: fprintf(stderr, tr(88, "No mail for %s\n"),
546 who);
548 return 1;
550 if (nmail) {
551 newmailinfo(omsgCount);
553 return(0);
557 newmailinfo(int omsgCount)
559 int mdot;
560 int i;
562 for (i = 0; i < omsgCount; i++)
563 message[i].m_flag &= ~MNEWEST;
564 if (msgCount > omsgCount) {
565 for (i = omsgCount; i < msgCount; i++)
566 message[i].m_flag |= MNEWEST;
567 printf(tr(158, "New mail has arrived.\n"));
568 if (msgCount - omsgCount == 1)
569 printf(tr(214, "Loaded 1 new message.\n"));
570 else
571 printf(tr(215, "Loaded %d new messages.\n"),
572 msgCount - omsgCount);
573 } else
574 printf(tr(224, "Loaded %d messages.\n"), msgCount);
575 callhook(mailname, 1);
576 mdot = getmdot(1);
577 if (value("header")) {
578 #ifdef HAVE_IMAP
579 if (mb.mb_type == MB_IMAP)
580 imap_getheaders(omsgCount+1, msgCount);
581 #endif
582 time_current_update(&time_current, FAL0);
583 while (++omsgCount <= msgCount)
584 if (visible(&message[omsgCount-1]))
585 printhead(omsgCount, stdout, 0);
587 return mdot;
591 * Interpret user commands one by one. If standard input is not a tty,
592 * print no prompt.
594 void
595 commands(void)
597 int eofloop = 0, n;
598 char *linebuf = NULL, *av, *nv;
599 size_t linesize = 0;
600 #ifdef HAVE_IMAP
601 int x;
602 #endif
604 if (!sourcing) {
605 if (safe_signal(SIGINT, SIG_IGN) != SIG_IGN)
606 safe_signal(SIGINT, onintr);
607 if (safe_signal(SIGHUP, SIG_IGN) != SIG_IGN)
608 safe_signal(SIGHUP, hangup);
609 /* TODO We do a lot of redundant signal handling, especially
610 * TODO with the line editor(s); try to merge this */
611 safe_signal(SIGTSTP, stop);
612 safe_signal(SIGTTOU, stop);
613 safe_signal(SIGTTIN, stop);
615 _oldpipe = safe_signal(SIGPIPE, SIG_IGN);
616 safe_signal(SIGPIPE, _oldpipe);
617 setexit();
619 for (;;) {
620 interrupts = 0;
621 handlerstacktop = NULL;
623 * Print the prompt, if needed. Clear out
624 * string space, and flush the output.
626 if (! sourcing && (options & OPT_INTERACTIVE)) {
627 av = (av = value("autoinc")) ? savestr(av) : NULL;
628 nv = (nv = value("newmail")) ? savestr(nv) : NULL;
629 if ((options & OPT_TTYIN) &&
630 (av != NULL || nv != NULL ||
631 mb.mb_type == MB_IMAP)) {
632 struct stat st;
634 n = (av && strcmp(av, "noimap") &&
635 strcmp(av, "nopoll")) |
636 (nv && strcmp(nv, "noimap") &&
637 strcmp(nv, "nopoll"));
638 #ifdef HAVE_IMAP
639 x = !(av || nv);
640 #endif
641 if ((mb.mb_type == MB_FILE &&
642 stat(mailname, &st) == 0 &&
643 st.st_size > mailsize) ||
644 #ifdef HAVE_IMAP
645 (mb.mb_type == MB_IMAP &&
646 imap_newmail(n) > x) ||
647 #endif
648 (mb.mb_type == MB_MAILDIR &&
649 n != 0)) {
650 int odot = dot - &message[0];
651 bool_t odid = did_print_dot;
653 setfile(mailname, 1);
654 if (mb.mb_type != MB_IMAP) {
655 dot = &message[odot];
656 did_print_dot = odid;
660 _reset_on_stop = 1;
661 exit_status = 0;
664 if (! sourcing) {
665 sreset();
666 /* TODO Note: this buffer may contain a password
667 * TODO We should redefine the code flow which has
668 * TODO to do that */
669 if ((nv = termios_state.ts_linebuf) != NULL) {
670 termios_state.ts_linebuf = NULL;
671 termios_state.ts_linesize = 0;
672 free(nv); /* TODO pool give-back */
674 /* TODO Due to expand-on-tab of our line editor the
675 * TODO buffer may grow */
676 if (linesize > LINESIZE * 3) {
677 free(linebuf); /* TODO pool! but what? */
678 linebuf = NULL;
679 linesize = 0;
684 * Read a line of commands from the current input
685 * and handle end of file specially.
687 n = readline_input(LNED_LF_ESC | LNED_HIST_ADD, NULL,
688 &linebuf, &linesize);
689 _reset_on_stop = 0;
690 if (n < 0) {
691 /* eof */
692 if (loading)
693 break;
694 if (sourcing) {
695 unstack();
696 continue;
698 if ((options & OPT_INTERACTIVE) &&
699 value("ignoreeof") && ++eofloop < 25) {
700 printf(tr(89, "Use `quit' to quit.\n"));
701 continue;
703 break;
706 eofloop = 0;
707 inhook = 0;
708 if (execute(linebuf, 0, n))
709 break;
710 if (exit_status != EXIT_OK && (options & OPT_BATCH_FLAG) &&
711 boption("batch-exit-on-error"))
712 break;
715 if (linebuf != NULL)
716 free(linebuf);
717 if (sourcing)
718 sreset();
722 * Execute a single command.
723 * Command functions return 0 for success, 1 for error, and -1
724 * for abort. A 1 or -1 aborts a load or source. A -1 aborts
725 * the interactive command loop.
726 * Contxt is non-zero if called while composing mail.
729 execute(char *linebuf, int contxt, size_t linesize)
731 char _wordbuf[2], *arglist[MAXARGC], *cp, *word;
732 struct cmd_ghost *cg = NULL;
733 struct cmd const *com = NULL;
734 int muvec[2], c, e = 1;
736 /* Command ghosts that refer to shell commands or macro expansion restart */
737 jrestart:
739 /* Strip the white space away from the beginning of the command */
740 for (cp = linebuf; whitechar(*cp); ++cp)
742 linesize -= (size_t)(cp - linebuf);
744 /* Ignore comments */
745 if (*cp == '#')
746 goto jleave0;
748 /* Handle ! differently to get the correct lexical conventions */
749 if (*cp == '!') {
750 if (sourcing) {
751 fprintf(stderr, tr(90, "Can't `!' while sourcing\n"));
752 goto jleave;
754 shell(++cp);
755 goto jleave0;
758 /* Isolate the actual command; since it may not necessarily be
759 * separated from the arguments (as in `p1') we need to duplicate it to
760 * be able to create a NUL terminated version.
761 * We must be aware of special one letter commands here */
762 arglist[0] = cp;
763 switch (*cp) {
764 case '|':
765 case '~':
766 case '?':
767 ++cp;
768 /* FALLTHRU */
769 case '\0':
770 break;
771 default:
772 cp = _lex_isolate(cp);
773 break;
775 c = (int)(cp - arglist[0]);
776 linesize -= c;
777 word = (c < (int)sizeof _wordbuf) ? _wordbuf : salloc(c + 1);
778 memcpy(word, arglist[0], c);
779 word[c] = '\0';
781 /* Look up the command; if not found, bitch.
782 * Normally, a blank command would map to the first command in the
783 * table; while sourcing, however, we ignore blank lines to eliminate
784 * confusion; act just the same for ghosts */
785 if ((sourcing || cg != NULL) && *word == '\0')
786 goto jleave0;
788 /* If this is the first evaluation, check command ghosts */
789 if (cg == NULL) {
790 /* TODO relink list head, so it's sort over time on usage? */
791 for (cg = _cmd_ghosts; cg != NULL; cg = cg->next)
792 if (strcmp(word, cg->name) == 0) {
793 if (linesize > 0) {
794 size_t i = cg->cmd.l;
795 linebuf = salloc(i + 1 + linesize);
796 memcpy(linebuf, cg->cmd.s, i);
797 linebuf[i++] = ' ';
798 memcpy(linebuf + i, cp, linesize);
799 linebuf[i += linesize] = '\0';
800 linesize = i;
801 } else {
802 linebuf = cg->cmd.s;
803 linesize = cg->cmd.l;
805 goto jrestart;
809 if (com == NULL)
810 com = _lex(word);
811 if (com == NULL || com->func == &ccmdnotsupp) {
812 fprintf(stderr, tr(91, "Unknown command: `%s'\n"), word);
813 if (com != NULL) {
814 ccmdnotsupp(NULL);
815 com = NULL;
817 goto jleave;
821 * See if we should execute the command -- if a conditional
822 * we always execute it, otherwise, check the state of cond.
824 if ((com->argtype & F) == 0) {
825 if ((cond == CRCV && (options & OPT_SENDMODE)) ||
826 (cond == CSEND && ! (options & OPT_SENDMODE)) ||
827 (cond == CTERM && ! (options & OPT_TTYIN)) ||
828 (cond == CNONTERM && (options & OPT_TTYIN)))
829 goto jleave0;
833 * Process the arguments to the command, depending
834 * on the type he expects. Default to an error.
835 * If we are sourcing an interactive command, it's
836 * an error.
838 if ((options & OPT_SENDMODE) && (com->argtype & M) == 0) {
839 fprintf(stderr, tr(92,
840 "May not execute `%s' while sending\n"),
841 com->name);
842 goto jleave;
844 if (sourcing && com->argtype & I) {
845 fprintf(stderr, tr(93,
846 "May not execute `%s' while sourcing\n"),
847 com->name);
848 goto jleave;
850 if ((mb.mb_perm & MB_DELE) == 0 && com->argtype & W) {
851 fprintf(stderr, tr(94, "May not execute `%s' -- "
852 "message file is read only\n"),
853 com->name);
854 goto jleave;
856 if (contxt && com->argtype & R) {
857 fprintf(stderr, tr(95,
858 "Cannot recursively invoke `%s'\n"), com->name);
859 goto jleave;
861 if (mb.mb_type == MB_VOID && com->argtype & A) {
862 fprintf(stderr, tr(257,
863 "Cannot execute `%s' without active mailbox\n"),
864 com->name);
865 goto jleave;
868 switch (com->argtype & ~(F|P|I|M|T|W|R|A)) {
869 case MSGLIST:
870 /* Message list defaulting to nearest forward legal message */
871 if (_msgvec == 0)
872 goto je96;
873 if ((c = getmsglist(cp, _msgvec, com->msgflag)) < 0)
874 break;
875 if (c == 0) {
876 *_msgvec = first(com->msgflag, com->msgmask);
877 if (*_msgvec != 0)
878 _msgvec[1] = 0;
880 if (*_msgvec == 0) {
881 if (! inhook)
882 printf(tr(97, "No applicable messages\n"));
883 break;
885 e = (*com->func)(_msgvec);
886 break;
888 case NDMLIST:
889 /* Message list with no defaults, but no error if none exist */
890 if (_msgvec == 0) {
891 je96:
892 fprintf(stderr, tr(96,
893 "Illegal use of `message list'\n"));
894 break;
896 if ((c = getmsglist(cp, _msgvec, com->msgflag)) < 0)
897 break;
898 e = (*com->func)(_msgvec);
899 break;
901 case STRLIST:
902 /* Just the straight string, with leading blanks removed */
903 while (whitechar(*cp))
904 cp++;
905 e = (*com->func)(cp);
906 break;
908 case RAWLIST:
909 case ECHOLIST:
910 /* A vector of strings, in shell style */
911 if ((c = getrawlist(cp, linesize, arglist,
912 sizeof arglist / sizeof *arglist,
913 (com->argtype&~(F|P|I|M|T|W|R|A)) == ECHOLIST)
914 ) < 0)
915 break;
916 if (c < com->minargs) {
917 fprintf(stderr, tr(99,
918 "`%s' requires at least %d arg(s)\n"),
919 com->name, com->minargs);
920 break;
922 if (c > com->maxargs) {
923 fprintf(stderr, tr(100,
924 "`%s' takes no more than %d arg(s)\n"),
925 com->name, com->maxargs);
926 break;
928 e = (*com->func)(arglist);
929 break;
931 case NOLIST:
932 /* Just the constant zero, for exiting, eg. */
933 e = (*com->func)(0);
934 break;
936 default:
937 panic(tr(101, "Unknown argument type"));
940 jleave:
941 /* Exit the current source file on error */
942 if ((exec_last_comm_error = (e != 0))) {
943 if (e < 0)
944 return 1;
945 if (loading)
946 return 1;
947 if (sourcing)
948 unstack();
949 return 0;
951 if (com == (struct cmd*)NULL )
952 return 0;
953 if (boption("autoprint") && com->argtype & P)
954 if (visible(dot)) {
955 muvec[0] = dot - &message[0] + 1;
956 muvec[1] = 0;
957 type(muvec);
959 if (! sourcing && ! inhook && (com->argtype & T) == 0)
960 sawcom = TRU1;
961 jleave0:
962 return 0;
966 * Set the size of the message vector used to construct argument
967 * lists to message list functions.
969 void
970 setmsize(int sz)
973 if (_msgvec != 0)
974 free(_msgvec);
975 _msgvec = (int*)scalloc(sz + 1, sizeof *_msgvec);
979 * The following gets called on receipt of an interrupt. This is
980 * to abort printout of a command, mainly.
981 * Dispatching here when command() is inactive crashes rcv.
982 * Close all open files except 0, 1, 2, and the temporary.
983 * Also, unstack all source files.
986 static int inithdr; /* am printing startup headers */
988 /*ARGSUSED*/
989 void
990 onintr(int s)
992 if (handlerstacktop != NULL) {
993 handlerstacktop(s);
994 return;
996 safe_signal(SIGINT, onintr);
997 noreset = 0;
998 if (!inithdr)
999 sawcom = TRU1;
1000 inithdr = 0;
1001 while (sourcing)
1002 unstack();
1004 termios_state_reset();
1005 close_all_files();
1007 if (image >= 0) {
1008 close(image);
1009 image = -1;
1011 if (interrupts != 1)
1012 fprintf(stderr, tr(102, "Interrupt\n"));
1013 safe_signal(SIGPIPE, _oldpipe);
1014 reset(0);
1018 * When we wake up after ^Z, reprint the prompt.
1020 static void
1021 stop(int s)
1023 sighandler_type old_action = safe_signal(s, SIG_DFL);
1024 sigset_t nset;
1026 sigemptyset(&nset);
1027 sigaddset(&nset, s);
1028 sigprocmask(SIG_UNBLOCK, &nset, (sigset_t *)NULL);
1029 kill(0, s);
1030 sigprocmask(SIG_BLOCK, &nset, (sigset_t *)NULL);
1031 safe_signal(s, old_action);
1032 if (_reset_on_stop) {
1033 _reset_on_stop = 0;
1034 reset(0);
1039 * Branch here on hangup signal and simulate "exit".
1041 /*ARGSUSED*/
1042 static void
1043 hangup(int s)
1045 (void)s;
1046 /* nothing to do? */
1047 exit(1);
1051 * Announce the presence of the current Mail version,
1052 * give the message count, and print a header listing.
1054 void
1055 announce(int printheaders)
1057 int vec[2], mdot;
1059 mdot = newfileinfo();
1060 vec[0] = mdot;
1061 vec[1] = 0;
1062 dot = &message[mdot - 1];
1063 if (printheaders && msgCount > 0 && value("header") != NULL) {
1064 inithdr++;
1065 headers(vec);
1066 inithdr = 0;
1071 * Announce information about the file we are editing.
1072 * Return a likely place to set dot.
1075 newfileinfo(void)
1077 struct message *mp;
1078 int u, n, mdot, d, s, hidden, moved;
1080 if (mb.mb_type == MB_VOID)
1081 return 1;
1082 mdot = getmdot(0);
1083 s = d = hidden = moved =0;
1084 for (mp = &message[0], n = 0, u = 0; mp < &message[msgCount]; mp++) {
1085 if (mp->m_flag & MNEW)
1086 n++;
1087 if ((mp->m_flag & MREAD) == 0)
1088 u++;
1089 if ((mp->m_flag & (MDELETED|MSAVED)) == (MDELETED|MSAVED))
1090 moved++;
1091 if ((mp->m_flag & (MDELETED|MSAVED)) == MDELETED)
1092 d++;
1093 if ((mp->m_flag & (MDELETED|MSAVED)) == MSAVED)
1094 s++;
1095 if (mp->m_flag & MHIDDEN)
1096 hidden++;
1098 _update_mailname(NULL);
1099 printf(tr(103, "\"%s\": "), displayname);
1100 if (msgCount == 1)
1101 printf(tr(104, "1 message"));
1102 else
1103 printf(tr(105, "%d messages"), msgCount);
1104 if (n > 0)
1105 printf(tr(106, " %d new"), n);
1106 if (u-n > 0)
1107 printf(tr(107, " %d unread"), u);
1108 if (d > 0)
1109 printf(tr(108, " %d deleted"), d);
1110 if (s > 0)
1111 printf(tr(109, " %d saved"), s);
1112 if (moved > 0)
1113 printf(tr(136, " %d moved"), moved);
1114 if (hidden > 0)
1115 printf(tr(139, " %d hidden"), hidden);
1116 if (mb.mb_type == MB_CACHE)
1117 printf(" [Disconnected]");
1118 else if (mb.mb_perm == 0)
1119 printf(tr(110, " [Read only]"));
1120 printf("\n");
1121 return(mdot);
1125 getmdot(int nmail)
1127 struct message *mp;
1128 char *cp;
1129 int mdot;
1130 enum mflag avoid = MHIDDEN|MDELETED;
1132 if (!nmail) {
1133 if (value("autothread"))
1134 thread(NULL);
1135 else if ((cp = value("autosort")) != NULL) {
1136 free(mb.mb_sorted);
1137 mb.mb_sorted = sstrdup(cp);
1138 sort(NULL);
1141 if (mb.mb_type == MB_VOID)
1142 return 1;
1143 if (nmail)
1144 for (mp = &message[0]; mp < &message[msgCount]; mp++)
1145 if ((mp->m_flag & (MNEWEST|avoid)) == MNEWEST)
1146 break;
1147 if (!nmail || mp >= &message[msgCount]) {
1148 for (mp = mb.mb_threaded ? threadroot : &message[0];
1149 mb.mb_threaded ?
1150 mp != NULL : mp < &message[msgCount];
1151 mb.mb_threaded ?
1152 mp = next_in_thread(mp) : mp++)
1153 if ((mp->m_flag & (MNEW|avoid)) == MNEW)
1154 break;
1156 if (mb.mb_threaded ? mp == NULL : mp >= &message[msgCount])
1157 for (mp = mb.mb_threaded ? threadroot : &message[0];
1158 mb.mb_threaded ? mp != NULL:
1159 mp < &message[msgCount];
1160 mb.mb_threaded ? mp = next_in_thread(mp) : mp++)
1161 if (mp->m_flag & MFLAGGED)
1162 break;
1163 if (mb.mb_threaded ? mp == NULL : mp >= &message[msgCount])
1164 for (mp = mb.mb_threaded ? threadroot : &message[0];
1165 mb.mb_threaded ? mp != NULL:
1166 mp < &message[msgCount];
1167 mb.mb_threaded ? mp = next_in_thread(mp) : mp++)
1168 if ((mp->m_flag & (MREAD|avoid)) == 0)
1169 break;
1170 if (mb.mb_threaded ? mp != NULL : mp < &message[msgCount])
1171 mdot = mp - &message[0] + 1;
1172 else if (value("showlast")) {
1173 if (mb.mb_threaded) {
1174 for (mp = this_in_thread(threadroot, -1); mp;
1175 mp = prev_in_thread(mp))
1176 if ((mp->m_flag & avoid) == 0)
1177 break;
1178 mdot = mp ? mp - &message[0] + 1 : msgCount;
1179 } else {
1180 for (mp = &message[msgCount-1]; mp >= &message[0]; mp--)
1181 if ((mp->m_flag & avoid) == 0)
1182 break;
1183 mdot = mp >= &message[0] ? mp-&message[0]+1 : msgCount;
1185 } else if (mb.mb_threaded) {
1186 for (mp = threadroot; mp; mp = next_in_thread(mp))
1187 if ((mp->m_flag & avoid) == 0)
1188 break;
1189 mdot = mp ? mp - &message[0] + 1 : 1;
1190 } else {
1191 for (mp = &message[0]; mp < &message[msgCount]; mp++)
1192 if ((mp->m_flag & avoid) == 0)
1193 break;
1194 mdot = mp < &message[msgCount] ? mp-&message[0]+1 : 1;
1196 return mdot;
1200 * Print the current version number.
1203 /*ARGSUSED*/
1205 pversion(void *v)
1207 (void)v;
1208 printf(tr(111, "Version %s\n"), version);
1209 return 0;
1212 void
1213 initbox(const char *name)
1215 char *tempMesg;
1216 int dummy;
1218 if (mb.mb_type != MB_VOID)
1219 (void)n_strlcpy(prevfile, mailname, MAXPATHLEN);
1220 _update_mailname(name != mailname ? name : NULL);
1221 if ((mb.mb_otf = Ftemp(&tempMesg, "tmpbox", "w", 0600, 0)) == NULL) {
1222 perror(tr(87, "temporary mail message file"));
1223 exit(1);
1225 (void)fcntl(fileno(mb.mb_otf), F_SETFD, FD_CLOEXEC);
1226 if ((mb.mb_itf = safe_fopen(tempMesg, "r", &dummy)) == NULL) {
1227 perror(tr(87, "temporary mail message file"));
1228 exit(1);
1230 (void)fcntl(fileno(mb.mb_itf), F_SETFD, FD_CLOEXEC);
1231 rm(tempMesg);
1232 Ftfree(&tempMesg);
1233 msgCount = 0;
1234 if (message) {
1235 free(message);
1236 message = NULL;
1237 msgspace = 0;
1239 mb.mb_threaded = 0;
1240 if (mb.mb_sorted != NULL) {
1241 free(mb.mb_sorted);
1242 mb.mb_sorted = NULL;
1244 #ifdef HAVE_IMAP
1245 mb.mb_flags = MB_NOFLAGS;
1246 #endif
1247 prevdot = NULL;
1248 dot = NULL;
1249 did_print_dot = FAL0;
1252 #ifdef HAVE_DOCSTRINGS
1253 bool_t
1254 print_comm_docstr(char const *comm)
1256 bool_t rv = FAL0;
1257 struct cmd_ghost *cg;
1258 struct cmd const *cp;
1260 /* Ghosts take precedence */
1261 for (cg = _cmd_ghosts; cg != NULL; cg = cg->next)
1262 if (strcmp(comm, cg->name) == 0) {
1263 printf("%s -> <%s>\n", comm, cg->cmd.s);
1264 rv = TRU1;
1265 goto jleave;
1268 for (cp = _cmd_tab; cp->name != NULL; ++cp) {
1269 if (cp->func == &ccmdnotsupp)
1270 continue;
1271 if (strcmp(comm, cp->name) == 0)
1272 printf("%s: %s\n", comm, tr(cp->docid, cp->doc));
1273 else if (is_prefix(comm, cp->name))
1274 printf("%s (%s): %s\n", comm, cp->name, tr(cp->docid, cp->doc));
1275 else
1276 continue;
1277 rv = TRU1;
1278 break;
1280 jleave:
1281 return rv;
1283 #endif
1285 /* vim:set fenc=utf-8:s-it-mode (TODO only partial true) */