NEWS: update for s-nail-14_5_2-sort.patch
[s-mailx.git] / lex.c
blob922faf816eec373a73de9f99afc08e1a0a8673af
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 - 2014 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_C90AMEND1 /* TODO unite __narrow_{pre,suf}fix() into one fun! */
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 /* Print the binaries compiled-in features */
96 static int _features(void *v);
98 /* Print the binaries version number */
99 static int _version(void *v);
101 static void stop(int s);
102 static void hangup(int s);
104 /* List of all commands */
105 static struct cmd const _cmd_tab[] = {
106 #include "cmd_tab.h"
109 #ifdef HAVE_C90AMEND1
110 SINLINE size_t
111 __narrow_prefix(char const *cp, size_t maxl)
113 int err;
114 size_t i, ok;
116 for (err = ok = i = 0; i < maxl;) {
117 int ml = mblen(cp, maxl - i);
118 if (ml < 0) { /* XXX _narrow_prefix(): mblen() error; action? */
119 (void)mblen(NULL, 0);
120 err = 1;
121 ml = 1;
122 } else {
123 if (! err)
124 ok = i;
125 err = 0;
126 if (ml == 0)
127 break;
129 cp += ml;
130 i += ml;
132 return ok;
135 SINLINE size_t
136 __narrow_suffix(char const *cp, size_t cpl, size_t maxl)
138 int err;
139 size_t i, ok;
141 for (err = ok = i = 0; cpl > maxl || err;) {
142 int ml = mblen(cp, cpl);
143 if (ml < 0) { /* XXX _narrow_suffix(): mblen() error; action? */
144 (void)mblen(NULL, 0);
145 err = 1;
146 ml = 1;
147 } else {
148 if (! err)
149 ok = i;
150 err = 0;
151 if (ml == 0)
152 break;
154 cp += ml;
155 i += ml;
156 cpl -= ml;
158 return ok;
160 #endif /* HAVE_C90AMEND1 */
162 static bool_t
163 _update_mailname(char const *name)
165 char tbuf[MAXPATHLEN], *mailp, *dispp;
166 size_t i, j;
167 bool_t rv;
169 /* Don't realpath(3) if it's only an update request */
170 if (name != NULL) {
171 #ifdef HAVE_REALPATH
172 enum protocol p = which_protocol(name);
173 if (p == PROTO_FILE || p == PROTO_MAILDIR) {
174 if (realpath(name, mailname) == NULL) {
175 fprintf(stderr, tr(151, "Can't canonicalize `%s'\n"), name);
176 rv = FAL0;
177 goto jleave;
179 } else
180 #endif
181 n_strlcpy(mailname, name, sizeof(mailname));
184 mailp = mailname;
185 dispp = displayname;
187 /* Don't display an absolute path but "+FOLDER" if under *folder* */
188 if (getfold(tbuf, sizeof tbuf)) {
189 i = strlen(tbuf);
190 if (i < sizeof(tbuf) - 1)
191 tbuf[i++] = '/';
192 if (strncmp(tbuf, mailp, i) == 0) {
193 mailp += i;
194 *dispp++ = '+';
198 /* We want to see the name of the folder .. on the screen */
199 i = strlen(mailp);
200 if ((rv = (i < sizeof(displayname) - 1)))
201 memcpy(dispp, mailp, i + 1);
202 else {
203 /* Avoid disrupting multibyte sequences (if possible) */
204 #ifndef HAVE_C90AMEND1
205 j = sizeof(displayname) / 3 - 1;
206 i -= sizeof(displayname) - (1/* + */ + 3) - j;
207 #else
208 j = __narrow_prefix(mailp, sizeof(displayname) / 3);
209 i = j + __narrow_suffix(mailp + j, i - j,
210 sizeof(displayname) - (1/* + */ + 3 + 1) - j);
211 #endif
212 snprintf(dispp, sizeof(displayname), "%.*s...%s",
213 (int)j, mailp, mailp + i);
215 #ifdef HAVE_REALPATH
216 jleave:
217 #endif
218 return rv;
221 static char *
222 _lex_isolate(char const *comm)
224 while (*comm && strchr(" \t0123456789$^.:/-+*'\",;(`", *comm) == NULL)
225 ++comm;
226 return UNCONST(comm);
229 static struct cmd const *
230 _lex(char const *comm) /* TODO **command hashtable**! linear list search!!! */
232 struct cmd const *cp;
234 for (cp = _cmd_tab; cp->name != NULL; ++cp)
235 if (*comm == *cp->name && is_prefix(comm, cp->name))
236 goto jleave;
237 cp = NULL;
238 jleave:
239 return cp;
242 static int
243 _ghost(void *v)
245 char const **argv = (char const **)v;
246 struct cmd_ghost *lcg, *cg;
247 size_t nl, cl;
249 /* Show the list? */
250 if (*argv == NULL) {
251 printf(tr(144, "Command ghosts are:\n"));
252 for (nl = 0, cg = _cmd_ghosts; cg != NULL; cg = cg->next) {
253 cl = strlen(cg->name) + 5 + cg->cmd.l + 3;
254 if ((nl += cl) >= (size_t)scrnwidth) {
255 nl = cl;
256 printf("\n");
258 printf((cg->next != NULL ? "%s -> <%s>, " : "%s -> <%s>\n"),
259 cg->name, cg->cmd.s);
261 v = NULL;
262 goto jleave;
265 /* Request to add new ghost */
266 if (argv[1] == NULL || argv[1][0] == '\0' || argv[2] != NULL) {
267 fprintf(stderr, tr(159, "Usage: %s\n"),
268 tr(425, "Define a <ghost> of <command>, or list all ghosts"));
269 v = NULL;
270 goto jleave;
273 /* Check that we can deal with this one */
274 switch (argv[0][0]) {
275 case '|':
276 case '~':
277 case '?':
278 case '#':
279 /* FALLTHRU */
280 case '\0':
281 goto jecanon;
282 default:
283 if (argv[0] == _lex_isolate(argv[0])) {
284 jecanon:
285 fprintf(stderr, tr(151, "Can't canonicalize `%s'\n"), argv[0]);
286 v = NULL;
287 goto jleave;
289 break;
292 /* Always recreate */
293 for (lcg = NULL, cg = _cmd_ghosts; cg != NULL; lcg = cg, cg = cg->next)
294 if (strcmp(cg->name, argv[0]) == 0) {
295 if (lcg != NULL)
296 lcg->next = cg->next;
297 else
298 _cmd_ghosts = cg->next;
299 free(cg);
300 break;
303 /* Need a new one */
304 nl = strlen(argv[0]) + 1;
305 cl = strlen(argv[1]) + 1;
306 cg = smalloc(sizeof(*cg) - VFIELD_SIZEOF(struct cmd_ghost, name) + nl + cl);
307 cg->next = _cmd_ghosts;
308 memcpy(cg->name, argv[0], nl);
309 cg->cmd.s = cg->name + nl;
310 cg->cmd.l = cl - 1;
311 memcpy(cg->cmd.s, argv[1], cl);
313 _cmd_ghosts = cg;
314 jleave:
315 return (v == NULL);
318 static int
319 _unghost(void *v)
321 int rv = 0;
322 char const **argv = v, *cp;
323 struct cmd_ghost *lcg, *cg;
325 while ((cp = *argv++) != NULL) {
326 for (lcg = NULL, cg = _cmd_ghosts; cg != NULL; lcg = cg, cg = cg->next)
327 if (strcmp(cg->name, cp) == 0) {
328 if (lcg != NULL)
329 lcg->next = cg->next;
330 else
331 _cmd_ghosts = cg->next;
332 free(cg);
333 goto jouter;
335 fprintf(stderr, tr(91, "Unknown command: `%s'\n"), cp);
336 rv = 1;
337 jouter:
340 return rv;
343 static int
344 __pcmd_cmp(void const *s1, void const *s2)
346 struct cmd const * const *c1 = s1, * const *c2 = s2;
347 return (strcmp((*c1)->name, (*c2)->name));
350 static int
351 _pcmdlist(void *v)
353 struct cmd const **cpa, *cp, **cursor;
354 size_t i;
355 (void)v;
357 for (i = 0; _cmd_tab[i].name != NULL; ++i)
359 ++i;
360 cpa = ac_alloc(sizeof(cp) * i);
362 for (i = 0; (cp = _cmd_tab + i)->name != NULL; ++i)
363 cpa[i] = cp;
364 cpa[i] = NULL;
366 qsort(cpa, i, sizeof(cp), &__pcmd_cmp);
368 printf(tr(14, "Commands are:\n"));
369 for (i = 0, cursor = cpa; (cp = *cursor++) != NULL;) {
370 size_t j;
371 if (cp->func == &ccmdnotsupp)
372 continue;
373 j = strlen(cp->name) + 2;
374 if ((i += j) > 72) {
375 i = j;
376 printf("\n");
378 printf((*cursor != NULL ? "%s, " : "%s\n"), cp->name);
381 ac_free(cpa);
382 return 0;
385 static int
386 _features(void *v)
388 UNUSED(v);
389 printf(tr(523, "Features: %s\n"), features);
390 return 0;
393 static int
394 _version(void *v)
396 UNUSED(v);
397 printf(tr(111, "Version %s\n"), version);
398 return 0;
402 * Set up editing on the given file name.
403 * If the first character of name is %, we are considered to be
404 * editing the file, otherwise we are reading our mail which has
405 * signficance for mbox and so forth.
407 * nmail: Check for new mail in the current folder only.
409 FL int
410 setfile(char const *name, int nmail)
412 FILE *ibuf;
413 int i, compressed = 0;
414 struct stat stb;
415 bool_t isedit;
416 char const *who = name[1] ? name + 1 : myname;
417 static int shudclob;
418 size_t offset;
419 int omsgCount = 0;
420 struct shortcut *sh;
421 struct flock flp;
423 /* Note we don't 'userid(myname) != getuid()', preliminary steps are usually
424 * necessary to make a mailbox accessible by a different user, and if that
425 * has happened, let's just let the usual file perms decide */
426 isedit = (*name != '%' && ((sh = get_shortcut(name)) == NULL ||
427 *sh->sh_long != '%'));
428 if ((name = expand(name)) == NULL)
429 return (-1);
431 switch (which_protocol(name)) {
432 case PROTO_FILE:
433 break;
434 case PROTO_MAILDIR:
435 return (maildir_setfile(name, nmail, isedit));
436 #ifdef HAVE_POP3
437 case PROTO_POP3:
438 shudclob = 1;
439 return (pop3_setfile(name, nmail, isedit));
440 #endif
441 #ifdef HAVE_IMAP
442 case PROTO_IMAP:
443 shudclob = 1;
444 if (nmail) {
445 if (mb.mb_type == MB_CACHE)
446 return 1;
448 return imap_setfile(name, nmail, isedit);
449 #endif
450 default:
451 fprintf(stderr, tr(217, "Cannot handle protocol: %s\n"), name);
452 return (-1);
455 if ((ibuf = Zopen(name, "r", &compressed)) == NULL) {
456 if ((!isedit && errno == ENOENT) || nmail) {
457 if (nmail)
458 goto jnonmail;
459 goto nomail;
461 perror(name);
462 return(-1);
465 if (fstat(fileno(ibuf), &stb) < 0) {
466 Fclose(ibuf);
467 if (nmail)
468 goto jnonmail;
469 perror("fstat");
470 return (-1);
473 if (S_ISDIR(stb.st_mode)) {
474 Fclose(ibuf);
475 if (nmail)
476 goto jnonmail;
477 errno = EISDIR;
478 perror(name);
479 return (-1);
480 } else if (S_ISREG(stb.st_mode)) {
481 /*EMPTY*/
482 } else {
483 Fclose(ibuf);
484 if (nmail)
485 goto jnonmail;
486 errno = EINVAL;
487 perror(name);
488 return (-1);
492 * Looks like all will be well. We must now relinquish our
493 * hold on the current set of stuff. Must hold signals
494 * while we are reading the new file, else we will ruin
495 * the message[] data structure.
498 hold_sigs(); /* TODO note on this one in quit.c:quit() */
499 if (shudclob && !nmail)
500 quit();
501 #ifdef HAVE_SOCKETS
502 if (!nmail && mb.mb_sock.s_fd >= 0)
503 sclose(&mb.mb_sock);
504 #endif
507 * Copy the messages into /tmp
508 * and set pointers.
511 flp.l_type = F_RDLCK;
512 flp.l_start = 0;
513 flp.l_whence = SEEK_SET;
514 if (!nmail) {
515 mb.mb_type = MB_FILE;
516 mb.mb_perm = (options & OPT_R_FLAG) ? 0 : MB_DELE|MB_EDIT;
517 mb.mb_compressed = compressed;
518 if (compressed) {
519 if (compressed & 0200)
520 mb.mb_perm = 0;
521 } else {
522 if ((i = open(name, O_WRONLY)) < 0)
523 mb.mb_perm = 0;
524 else
525 close(i);
527 if (shudclob) {
528 if (mb.mb_itf) {
529 fclose(mb.mb_itf);
530 mb.mb_itf = NULL;
532 if (mb.mb_otf) {
533 fclose(mb.mb_otf);
534 mb.mb_otf = NULL;
537 shudclob = 1;
538 edit = isedit;
539 initbox(name);
540 offset = 0;
541 flp.l_len = 0;
542 if (!edit && fcntl(fileno(ibuf), F_SETLKW, &flp) < 0) {
543 perror("Unable to lock mailbox");
544 Fclose(ibuf);
545 return -1;
547 } else /* nmail */{
548 fseek(mb.mb_otf, 0L, SEEK_END);
549 fseek(ibuf, mailsize, SEEK_SET);
550 offset = mailsize;
551 omsgCount = msgCount;
552 flp.l_len = offset;
553 if (!edit && fcntl(fileno(ibuf), F_SETLKW, &flp) < 0)
554 goto jnonmail;
556 mailsize = fsize(ibuf);
557 if (nmail && (size_t)mailsize <= offset) {
558 rele_sigs();
559 goto jnonmail;
561 setptr(ibuf, offset);
562 setmsize(msgCount);
563 if (nmail && mb.mb_sorted) {
564 mb.mb_threaded = 0;
565 sort((void *)-1);
567 Fclose(ibuf);
568 rele_sigs();
569 if (!nmail)
570 sawcom = FAL0;
571 if ((!edit || nmail) && msgCount == 0) {
572 jnonmail:
573 if (!nmail) {
574 if (!ok_blook(emptystart))
575 nomail: fprintf(stderr, tr(88, "No mail for %s\n"),
576 who);
578 return 1;
580 if (nmail)
581 newmailinfo(omsgCount);
582 return 0;
585 FL int
586 newmailinfo(int omsgCount)
588 int mdot;
589 int i;
591 for (i = 0; i < omsgCount; i++)
592 message[i].m_flag &= ~MNEWEST;
593 if (msgCount > omsgCount) {
594 for (i = omsgCount; i < msgCount; i++)
595 message[i].m_flag |= MNEWEST;
596 printf(tr(158, "New mail has arrived.\n"));
597 if (msgCount - omsgCount == 1)
598 printf(tr(214, "Loaded 1 new message.\n"));
599 else
600 printf(tr(215, "Loaded %d new messages.\n"),
601 msgCount - omsgCount);
602 } else
603 printf(tr(224, "Loaded %d messages.\n"), msgCount);
604 callhook(mailname, 1);
605 mdot = getmdot(1);
606 if (ok_blook(header))
607 print_headers(omsgCount + 1, msgCount);
608 return mdot;
611 FL void
612 commands(void)
614 struct eval_ctx ev;
615 int n;
617 if (!sourcing) {
618 if (safe_signal(SIGINT, SIG_IGN) != SIG_IGN)
619 safe_signal(SIGINT, onintr);
620 if (safe_signal(SIGHUP, SIG_IGN) != SIG_IGN)
621 safe_signal(SIGHUP, hangup);
622 /* TODO We do a lot of redundant signal handling, especially
623 * TODO with the command line editor(s); try to merge this */
624 safe_signal(SIGTSTP, stop);
625 safe_signal(SIGTTOU, stop);
626 safe_signal(SIGTTIN, stop);
628 _oldpipe = safe_signal(SIGPIPE, SIG_IGN);
629 safe_signal(SIGPIPE, _oldpipe);
631 memset(&ev, 0, sizeof ev);
633 setexit();
634 for (;;) {
635 interrupts = 0;
636 handlerstacktop = NULL;
638 if (!sourcing && (options & OPT_INTERACTIVE)) {
639 char *cp;
641 if ((cp = ok_vlook(newmail)) == NULL)
642 cp = ok_vlook(autoinc); /* TODO legacy */
643 if ((options & OPT_TTYIN) && (cp != NULL || mb.mb_type == MB_IMAP)) {
644 struct stat st;
646 n = (cp != NULL && strcmp(cp, "noimap") && strcmp(cp, "nopoll"));
647 if ((mb.mb_type == MB_FILE && stat(mailname, &st) == 0 &&
648 st.st_size > mailsize) ||
649 #ifdef HAVE_IMAP
650 (mb.mb_type == MB_IMAP && imap_newmail(n) > (cp == NULL)) ||
651 #endif
652 (mb.mb_type == MB_MAILDIR && n != 0)) {
653 size_t odot = PTR2SIZE(dot - message);
654 bool_t odid = did_print_dot;
656 setfile(mailname, 1);
657 if (mb.mb_type != MB_IMAP) {
658 dot = message + odot;
659 did_print_dot = odid;
664 _reset_on_stop = 1;
665 exit_status = 0;
668 #ifdef HAVE_COLOUR
669 colour_table = NULL; /* XXX intermediate hack */
670 #endif
671 sreset(sourcing);
672 if (!sourcing) {
673 char *cp;
675 /* TODO Note: this buffer may contain a password. We should redefine
676 * TODO the code flow which has to do that */
677 if ((cp = termios_state.ts_linebuf) != NULL) {
678 termios_state.ts_linebuf = NULL;
679 termios_state.ts_linesize = 0;
680 free(cp); /* TODO pool give-back */
682 /* TODO Due to expand-on-tab of NCL the buffer may grow */
683 if (ev.ev_line.l > LINESIZE * 3) {
684 free(ev.ev_line.s); /* TODO pool! but what? */
685 ev.ev_line.s = NULL;
686 ev.ev_line.l = 0;
690 /* Read a line of commands and handle end of file specially */
691 jreadline:
692 n = readline_input(NULL, TRU1, &ev.ev_line.s, &ev.ev_line.l,
693 ev.ev_new_content);
694 _reset_on_stop = 0;
695 if (n < 0) {
696 /* EOF */
697 if (loading)
698 break;
699 if (sourcing) {
700 unstack();
701 continue;
703 if ((options & OPT_INTERACTIVE) && ok_blook(ignoreeof)) {
704 printf(tr(89, "Use `quit' to quit.\n"));
705 continue;
707 break;
710 inhook = 0;
711 if (evaluate(&ev))
712 break;
713 if (exit_status != EXIT_OK && (options & OPT_BATCH_FLAG) &&
714 ok_blook(batch_exit_on_error))
715 break;
716 if (!sourcing && (options & OPT_INTERACTIVE)) {
717 if (ev.ev_new_content != NULL)
718 goto jreadline;
719 if (ev.ev_add_history)
720 tty_addhist(ev.ev_line.s);
724 if (ev.ev_line.s != NULL)
725 free(ev.ev_line.s);
726 if (sourcing)
727 sreset(FAL0);
730 FL int
731 execute(char *linebuf, int contxt, size_t linesize) /* XXX LEGACY */
733 struct eval_ctx ev;
734 #ifdef HAVE_COLOUR
735 struct colour_table *ct_save;
736 #endif
737 int rv;
739 /* TODO Maybe recursion from within collect.c! As long as we don't have
740 * TODO a value carrier that transports the entire state of a recursion
741 * TODO we need to save away also the colour table */
742 #ifdef HAVE_COLOUR
743 ct_save = colour_table;
744 colour_table = NULL;
745 #endif
747 memset(&ev, 0, sizeof ev);
748 ev.ev_line.s = linebuf;
749 ev.ev_line.l = linesize;
750 ev.ev_is_recursive = (contxt != 0);
751 rv = evaluate(&ev);
753 #ifdef HAVE_COLOUR
754 colour_table = ct_save;
755 #endif
757 return rv;
760 FL int
761 evaluate(struct eval_ctx *evp)
763 struct str line;
764 char _wordbuf[2], *arglist[MAXARGC], *cp, *word;
765 struct cmd_ghost *cg = NULL;
766 struct cmd const *com = NULL;
767 int muvec[2], c, e = 1;
769 line = evp->ev_line; /* XXX don't change original (buffer pointer) */
770 evp->ev_add_history = FAL0;
771 evp->ev_new_content = NULL;
773 /* Command ghosts that refer to shell commands or macro expansion restart */
774 jrestart:
776 /* Strip the white space away from the beginning of the command */
777 for (cp = line.s; whitechar(*cp); ++cp)
779 line.l -= PTR2SIZE(cp - line.s);
781 /* Ignore comments */
782 if (*cp == '#')
783 goto jleave0;
785 /* Handle ! differently to get the correct lexical conventions */
786 if (*cp == '!') {
787 if (sourcing) {
788 fprintf(stderr, tr(90, "Can't `!' while sourcing\n"));
789 goto jleave;
791 shell(++cp);
792 evp->ev_add_history = TRU1;
793 goto jleave0;
796 /* Isolate the actual command; since it may not necessarily be
797 * separated from the arguments (as in `p1') we need to duplicate it to
798 * be able to create a NUL terminated version.
799 * We must be aware of several special one letter commands here */
800 arglist[0] = cp;
801 switch (*cp) {
802 case '|':
803 case '~':
804 case '?':
805 ++cp;
806 /* FALLTHRU */
807 case '\0':
808 break;
809 default:
810 cp = _lex_isolate(cp);
811 break;
813 c = (int)PTR2SIZE(cp - arglist[0]);
814 line.l -= c;
815 word = (c < (int)sizeof _wordbuf) ? _wordbuf : salloc(c + 1);
816 memcpy(word, arglist[0], c);
817 word[c] = '\0';
819 /* Look up the command; if not found, bitch.
820 * Normally, a blank command would map to the first command in the
821 * table; while sourcing, however, we ignore blank lines to eliminate
822 * confusion; act just the same for ghosts */
823 if (*word == '\0') {
824 if (sourcing || cg != NULL)
825 goto jleave0;
826 com = _cmd_tab + 0;
827 goto jexec;
830 /* If this is the first evaluation, check command ghosts */
831 if (cg == NULL) {
832 /* TODO relink list head, so it's sorted on usage over time?
833 * TODO in fact, there should be one hashmap over all commands and ghosts
834 * TODO so that the lookup could be made much more efficient than it is
835 * TODO now (two adjacent list searches! */
836 for (cg = _cmd_ghosts; cg != NULL; cg = cg->next)
837 if (strcmp(word, cg->name) == 0) {
838 if (line.l > 0) {
839 size_t i = cg->cmd.l;
840 line.s = salloc(i + 1 + line.l +1);
841 memcpy(line.s, cg->cmd.s, i);
842 line.s[i++] = ' ';
843 memcpy(line.s + i, cp, line.l);
844 line.s[i += line.l] = '\0';
845 line.l = i;
846 } else {
847 line.s = cg->cmd.s;
848 line.l = cg->cmd.l;
850 goto jrestart;
854 if ((com = _lex(word)) == NULL || com->func == &ccmdnotsupp) {
855 fprintf(stderr, tr(91, "Unknown command: `%s'\n"), word);
856 if (com != NULL) {
857 ccmdnotsupp(NULL);
858 com = NULL;
860 goto jleave;
863 /* See if we should execute the command -- if a conditional we always
864 * execute it, otherwise, check the state of cond */
865 jexec:
866 if (!(com->argtype & ARG_F)) {
867 switch (cond_state) {
868 case COND_RCV:
869 if (options & OPT_SENDMODE)
870 goto jleave0;
871 break;
872 case COND_SEND:
873 if (!(options & OPT_SENDMODE))
874 goto jleave0;
875 break;
876 case COND_TERM:
877 if (!(options & OPT_TTYIN))
878 goto jleave0;
879 break;
880 case COND_NOTERM:
881 if (options & OPT_TTYIN)
882 goto jleave0;
883 break;
884 case COND_ANY:
885 case COND_EXEC:
886 break;
887 case COND_NOEXEC:
888 goto jleave0;
892 /* Process the arguments to the command, depending on the type he expects.
893 * Default to an error.
894 * If we are sourcing an interactive command, it's an error */
895 if ((options & OPT_SENDMODE) && !(com->argtype & ARG_M)) {
896 fprintf(stderr, tr(92, "May not execute `%s' while sending\n"),
897 com->name);
898 goto jleave;
900 if (sourcing && (com->argtype & ARG_I)) {
901 fprintf(stderr, tr(93, "May not execute `%s' while sourcing\n"),
902 com->name);
903 goto jleave;
905 if (!(mb.mb_perm & MB_DELE) && (com->argtype & ARG_W)) {
906 fprintf(stderr, tr(94, "May not execute `%s' -- "
907 "message file is read only\n"), com->name);
908 goto jleave;
910 if (evp->ev_is_recursive && (com->argtype & ARG_R)) {
911 fprintf(stderr, tr(95, "Cannot recursively invoke `%s'\n"), com->name);
912 goto jleave;
914 if (mb.mb_type == MB_VOID && (com->argtype & ARG_A)) {
915 fprintf(stderr, tr(257, "Cannot execute `%s' without active mailbox\n"),
916 com->name);
917 goto jleave;
920 if (com->argtype & ARG_V)
921 temporary_arg_v_store = NULL;
923 switch (com->argtype & ARG_ARGMASK) {
924 case ARG_MSGLIST:
925 /* Message list defaulting to nearest forward legal message */
926 if (_msgvec == 0)
927 goto je96;
928 if ((c = getmsglist(cp, _msgvec, com->msgflag)) < 0)
929 break;
930 if (c == 0) {
931 *_msgvec = first(com->msgflag, com->msgmask);
932 if (*_msgvec != 0)
933 _msgvec[1] = 0;
935 if (*_msgvec == 0) {
936 if (!inhook)
937 printf(tr(97, "No applicable messages\n"));
938 break;
940 e = (*com->func)(_msgvec);
941 break;
943 case ARG_NDMLIST:
944 /* Message list with no defaults, but no error if none exist */
945 if (_msgvec == 0) {
946 je96:
947 fprintf(stderr, tr(96, "Illegal use of `message list'\n"));
948 break;
950 if ((c = getmsglist(cp, _msgvec, com->msgflag)) < 0)
951 break;
952 e = (*com->func)(_msgvec);
953 break;
955 case ARG_STRLIST:
956 /* Just the straight string, with leading blanks removed */
957 while (whitechar(*cp))
958 cp++;
959 e = (*com->func)(cp);
960 break;
962 case ARG_RAWLIST:
963 case ARG_ECHOLIST:
964 /* A vector of strings, in shell style */
965 if ((c = getrawlist(cp, line.l, arglist, NELEM(arglist),
966 ((com->argtype & ARG_ARGMASK) == ARG_ECHOLIST))) < 0)
967 break;
968 if (c < com->minargs) {
969 fprintf(stderr, tr(99, "`%s' requires at least %d arg(s)\n"),
970 com->name, com->minargs);
971 break;
973 if (c > com->maxargs) {
974 fprintf(stderr, tr(100, "`%s' takes no more than %d arg(s)\n"),
975 com->name, com->maxargs);
976 break;
978 e = (*com->func)(arglist);
979 break;
981 case ARG_NOLIST:
982 /* Just the constant zero, for exiting, eg. */
983 e = (*com->func)(0);
984 break;
986 default:
987 panic(tr(101, "Unknown argument type"));
990 if (e == 0 && (com->argtype & ARG_V) &&
991 (cp = temporary_arg_v_store) != NULL) {
992 temporary_arg_v_store = NULL;
993 evp->ev_new_content = cp;
994 goto jleave0;
996 if (!(com->argtype & ARG_H) && !list_saw_numbers)
997 evp->ev_add_history = TRU1;
999 jleave:
1000 /* Exit the current source file on error */
1001 if ((exec_last_comm_error = (e != 0))) {
1002 if (e < 0)
1003 return 1;
1004 if (loading)
1005 return 1;
1006 if (sourcing)
1007 unstack();
1008 return 0;
1010 if (com == NULL)
1011 return 0;
1012 if ((com->argtype & ARG_P) && ok_blook(autoprint))
1013 if (visible(dot)) {
1014 muvec[0] = (int)PTR2SIZE(dot - message + 1);
1015 muvec[1] = 0;
1016 type(muvec);
1018 if (!sourcing && !inhook && (com->argtype & ARG_T) == 0)
1019 sawcom = TRU1;
1020 jleave0:
1021 exec_last_comm_error = 0;
1022 return 0;
1026 * Set the size of the message vector used to construct argument
1027 * lists to message list functions.
1029 FL void
1030 setmsize(int sz)
1033 if (_msgvec != 0)
1034 free(_msgvec);
1035 _msgvec = (int*)scalloc(sz + 1, sizeof *_msgvec);
1039 * The following gets called on receipt of an interrupt. This is
1040 * to abort printout of a command, mainly.
1041 * Dispatching here when command() is inactive crashes rcv.
1042 * Close all open files except 0, 1, 2, and the temporary.
1043 * Also, unstack all source files.
1046 static int inithdr; /* am printing startup headers */
1048 /*ARGSUSED*/
1049 FL void
1050 onintr(int s)
1052 if (handlerstacktop != NULL) {
1053 handlerstacktop(s);
1054 return;
1056 safe_signal(SIGINT, onintr);
1057 noreset = 0;
1058 if (!inithdr)
1059 sawcom = TRU1;
1060 inithdr = 0;
1061 while (sourcing)
1062 unstack();
1064 termios_state_reset();
1065 close_all_files();
1067 if (image >= 0) {
1068 close(image);
1069 image = -1;
1071 if (interrupts != 1)
1072 fprintf(stderr, tr(102, "Interrupt\n"));
1073 safe_signal(SIGPIPE, _oldpipe);
1074 reset(0);
1078 * When we wake up after ^Z, reprint the prompt.
1080 static void
1081 stop(int s)
1083 sighandler_type old_action = safe_signal(s, SIG_DFL);
1084 sigset_t nset;
1086 sigemptyset(&nset);
1087 sigaddset(&nset, s);
1088 sigprocmask(SIG_UNBLOCK, &nset, (sigset_t *)NULL);
1089 kill(0, s);
1090 sigprocmask(SIG_BLOCK, &nset, (sigset_t *)NULL);
1091 safe_signal(s, old_action);
1092 if (_reset_on_stop) {
1093 _reset_on_stop = 0;
1094 reset(0);
1099 * Branch here on hangup signal and simulate "exit".
1101 /*ARGSUSED*/
1102 static void
1103 hangup(int s)
1105 (void)s;
1106 /* nothing to do? */
1107 exit(1);
1111 * Announce the presence of the current Mail version,
1112 * give the message count, and print a header listing.
1114 FL void
1115 announce(int printheaders)
1117 int vec[2], mdot;
1119 mdot = newfileinfo();
1120 vec[0] = mdot;
1121 vec[1] = 0;
1122 dot = &message[mdot - 1];
1123 if (printheaders && msgCount > 0 && ok_blook(header)) {
1124 inithdr++;
1125 headers(vec);
1126 inithdr = 0;
1131 * Announce information about the file we are editing.
1132 * Return a likely place to set dot.
1134 FL int
1135 newfileinfo(void)
1137 struct message *mp;
1138 int u, n, mdot, d, s, hidden, moved;
1140 if (mb.mb_type == MB_VOID)
1141 return 1;
1142 mdot = getmdot(0);
1143 s = d = hidden = moved =0;
1144 for (mp = &message[0], n = 0, u = 0; mp < &message[msgCount]; mp++) {
1145 if (mp->m_flag & MNEW)
1146 n++;
1147 if ((mp->m_flag & MREAD) == 0)
1148 u++;
1149 if ((mp->m_flag & (MDELETED|MSAVED)) == (MDELETED|MSAVED))
1150 moved++;
1151 if ((mp->m_flag & (MDELETED|MSAVED)) == MDELETED)
1152 d++;
1153 if ((mp->m_flag & (MDELETED|MSAVED)) == MSAVED)
1154 s++;
1155 if (mp->m_flag & MHIDDEN)
1156 hidden++;
1159 /* If displayname gets truncated the user effectively has no option to see
1160 * the full pathname of the mailbox, so print it at least for '? fi' */
1161 printf(tr(103, "\"%s\": "),
1162 (_update_mailname(NULL) ? displayname : mailname));
1163 if (msgCount == 1)
1164 printf(tr(104, "1 message"));
1165 else
1166 printf(tr(105, "%d messages"), msgCount);
1167 if (n > 0)
1168 printf(tr(106, " %d new"), n);
1169 if (u-n > 0)
1170 printf(tr(107, " %d unread"), u);
1171 if (d > 0)
1172 printf(tr(108, " %d deleted"), d);
1173 if (s > 0)
1174 printf(tr(109, " %d saved"), s);
1175 if (moved > 0)
1176 printf(tr(136, " %d moved"), moved);
1177 if (hidden > 0)
1178 printf(tr(139, " %d hidden"), hidden);
1179 if (mb.mb_type == MB_CACHE)
1180 printf(" [Disconnected]");
1181 else if (mb.mb_perm == 0)
1182 printf(tr(110, " [Read only]"));
1183 printf("\n");
1184 return(mdot);
1187 FL int
1188 getmdot(int nmail)
1190 struct message *mp;
1191 char *cp;
1192 int mdot;
1193 enum mflag avoid = MHIDDEN|MDELETED;
1195 if (!nmail) {
1196 if (ok_blook(autothread))
1197 thread(NULL);
1198 else if ((cp = ok_vlook(autosort)) != NULL) {
1199 free(mb.mb_sorted);
1200 mb.mb_sorted = sstrdup(cp);
1201 sort(NULL);
1204 if (mb.mb_type == MB_VOID)
1205 return 1;
1206 if (nmail)
1207 for (mp = &message[0]; mp < &message[msgCount]; mp++)
1208 if ((mp->m_flag & (MNEWEST|avoid)) == MNEWEST)
1209 break;
1210 if (!nmail || mp >= &message[msgCount]) {
1211 for (mp = mb.mb_threaded ? threadroot : &message[0];
1212 mb.mb_threaded ?
1213 mp != NULL : mp < &message[msgCount];
1214 mb.mb_threaded ?
1215 mp = next_in_thread(mp) : mp++)
1216 if ((mp->m_flag & (MNEW|avoid)) == MNEW)
1217 break;
1219 if (mb.mb_threaded ? mp == NULL : mp >= &message[msgCount])
1220 for (mp = mb.mb_threaded ? threadroot : &message[0];
1221 mb.mb_threaded ? mp != NULL:
1222 mp < &message[msgCount];
1223 mb.mb_threaded ? mp = next_in_thread(mp) : mp++)
1224 if (mp->m_flag & MFLAGGED)
1225 break;
1226 if (mb.mb_threaded ? mp == NULL : mp >= &message[msgCount])
1227 for (mp = mb.mb_threaded ? threadroot : &message[0];
1228 mb.mb_threaded ? mp != NULL:
1229 mp < &message[msgCount];
1230 mb.mb_threaded ? mp = next_in_thread(mp) : mp++)
1231 if ((mp->m_flag & (MREAD|avoid)) == 0)
1232 break;
1233 if (mb.mb_threaded ? mp != NULL : mp < &message[msgCount])
1234 mdot = mp - &message[0] + 1;
1235 else if (ok_blook(showlast)) {
1236 if (mb.mb_threaded) {
1237 for (mp = this_in_thread(threadroot, -1); mp;
1238 mp = prev_in_thread(mp))
1239 if ((mp->m_flag & avoid) == 0)
1240 break;
1241 mdot = mp ? mp - &message[0] + 1 : msgCount;
1242 } else {
1243 for (mp = &message[msgCount-1]; mp >= &message[0]; mp--)
1244 if ((mp->m_flag & avoid) == 0)
1245 break;
1246 mdot = mp >= &message[0] ? mp-&message[0]+1 : msgCount;
1248 } else if (mb.mb_threaded) {
1249 for (mp = threadroot; mp; mp = next_in_thread(mp))
1250 if ((mp->m_flag & avoid) == 0)
1251 break;
1252 mdot = mp ? mp - &message[0] + 1 : 1;
1253 } else {
1254 for (mp = &message[0]; mp < &message[msgCount]; mp++)
1255 if ((mp->m_flag & avoid) == 0)
1256 break;
1257 mdot = mp < &message[msgCount] ? mp-&message[0]+1 : 1;
1259 return mdot;
1262 FL void
1263 initbox(const char *name)
1265 char *tempMesg;
1266 int dummy;
1268 if (mb.mb_type != MB_VOID)
1269 (void)n_strlcpy(prevfile, mailname, MAXPATHLEN);
1270 _update_mailname(name != mailname ? name : NULL);
1271 if ((mb.mb_otf = Ftemp(&tempMesg, "tmpbox", "w", 0600, 0)) == NULL) {
1272 perror(tr(87, "temporary mail message file"));
1273 exit(1);
1275 (void)fcntl(fileno(mb.mb_otf), F_SETFD, FD_CLOEXEC);
1276 if ((mb.mb_itf = safe_fopen(tempMesg, "r", &dummy)) == NULL) {
1277 perror(tr(87, "temporary mail message file"));
1278 exit(1);
1280 (void)fcntl(fileno(mb.mb_itf), F_SETFD, FD_CLOEXEC);
1281 rm(tempMesg);
1282 Ftfree(&tempMesg);
1283 msgCount = 0;
1284 if (message) {
1285 free(message);
1286 message = NULL;
1287 msgspace = 0;
1289 mb.mb_threaded = 0;
1290 if (mb.mb_sorted != NULL) {
1291 free(mb.mb_sorted);
1292 mb.mb_sorted = NULL;
1294 #ifdef HAVE_IMAP
1295 mb.mb_flags = MB_NOFLAGS;
1296 #endif
1297 prevdot = NULL;
1298 dot = NULL;
1299 did_print_dot = FAL0;
1302 #ifdef HAVE_DOCSTRINGS
1303 FL bool_t
1304 print_comm_docstr(char const *comm)
1306 bool_t rv = FAL0;
1307 struct cmd_ghost *cg;
1308 struct cmd const *cp;
1310 /* Ghosts take precedence */
1311 for (cg = _cmd_ghosts; cg != NULL; cg = cg->next)
1312 if (strcmp(comm, cg->name) == 0) {
1313 printf("%s -> <%s>\n", comm, cg->cmd.s);
1314 rv = TRU1;
1315 goto jleave;
1318 for (cp = _cmd_tab; cp->name != NULL; ++cp) {
1319 if (cp->func == &ccmdnotsupp)
1320 continue;
1321 if (strcmp(comm, cp->name) == 0)
1322 printf("%s: %s\n", comm, tr(cp->docid, cp->doc));
1323 else if (is_prefix(comm, cp->name))
1324 printf("%s (%s): %s\n", comm, cp->name, tr(cp->docid, cp->doc));
1325 else
1326 continue;
1327 rv = TRU1;
1328 break;
1330 jleave:
1331 return rv;
1333 #endif
1335 /* vim:set fenc=utf-8:s-it-mode (TODO only partial true) */