1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ (Lexical processing of) Commands, and the event loops.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
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
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. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 #ifndef HAVE_AMALGAMATION
43 char const *name
; /* Name of command */
44 int (*func
)(void*); /* Implementor of command */
45 enum argtype argtype
; /* Arglist type (see below) */
46 short msgflag
; /* Required flags of msgs */
47 short msgmask
; /* Relevant flags of msgs */
48 #ifdef HAVE_DOCSTRINGS
49 char const *doc
; /* One line doc for command */
52 /* Yechh, can't initialize unions */
53 #define minargs msgflag /* Minimum argcount for RAWLIST */
54 #define maxargs msgmask /* Max argcount for RAWLIST */
57 struct cmd_ghost
*next
;
58 struct str cmd
; /* Data follows after .name */
59 char name
[VFIELD_SIZE(0)];
62 static int _reset_on_stop
; /* do a reset() if stopped */
63 static sighandler_type _oldpipe
;
64 static struct cmd_ghost
*_cmd_ghosts
;
65 /* _cmd_tab[] after fun protos */
67 /* Isolate the command from the arguments */
68 static char * _lex_isolate(char const *comm
);
70 /* Get first-fit, or NULL */
71 static struct cmd
const * _lex(char const *comm
);
73 /* Command ghost handling */
74 static int _c_ghost(void *v
);
75 static int _c_unghost(void *v
);
77 /* Print a list of all commands */
78 static int _c_list(void *v
);
80 static int __pcmd_cmp(void const *s1
, void const *s2
);
83 static int _c_quit(void *v
);
85 /* Print the binaries compiled-in features */
86 static int _c_features(void *v
);
88 /* Print the binaries version number */
89 static int _c_version(void *v
);
91 /* When we wake up after ^Z, reprint the prompt */
92 static void stop(int s
);
94 /* Branch here on hangup signal and simulate "exit" */
95 static void hangup(int s
);
97 /* List of all commands, and list of commands which are specially treated
98 * and deduced in execute(), but we need a list for _c_list() and
99 * print_comm_docstr() */
100 #ifdef HAVE_DOCSTRINGS
105 static struct cmd
const _cmd_tab
[] = {
108 _special_cmd_tab
[] = {
110 DS(N_("\"Comment command\": ignore remaining (continuable) line")) },
112 DS(N_("Print out the preceding message")) }
117 _lex_isolate(char const *comm
)
120 while (*comm
!= '\0' &&
121 strchr("~|? \t0123456789&%@$^.:/-+*'\",;(`", *comm
) == NULL
)
124 return UNCONST(comm
);
127 static struct cmd
const *
128 _lex(char const *comm
) /* TODO **command hashtable**! linear list search!!! */
130 struct cmd
const *cp
, *cpmax
;
133 for (cp
= cpmax
= _cmd_tab
, cpmax
+= NELEM(_cmd_tab
); cp
!= cpmax
; ++cp
)
134 if (*comm
== *cp
->name
&& is_prefix(comm
, cp
->name
))
145 char const **argv
= v
;
146 struct cmd_ghost
*lcg
, *cg
;
155 if ((fp
= Ftmp(NULL
, "ghost", OF_RDWR
| OF_UNLINK
| OF_REGISTER
)) == NULL
)
157 for (i
= 0, cg
= _cmd_ghosts
; cg
!= NULL
; cg
= cg
->next
)
158 fprintf(fp
, "ghost %s \"%s\"\n", cg
->name
, string_quote(cg
->cmd
.s
));
160 page_or_print(fp
, i
);
166 /* Verify the ghost name is a valid one */
167 if (*argv
[0] == '\0' || *_lex_isolate(argv
[0]) != '\0') {
168 n_err(_("`ghost': can't canonicalize \"%s\"\n"), argv
[0]);
173 /* Show command of single ghost? */
174 if (argv
[1] == NULL
) {
175 for (cg
= _cmd_ghosts
; cg
!= NULL
; cg
= cg
->next
)
176 if (!strcmp(argv
[0], cg
->name
)) {
177 printf("ghost %s \"%s\"\n", cg
->name
, string_quote(cg
->cmd
.s
));
180 n_err(_("`ghost': no such alias: \"%s\"\n"), argv
[0]);
185 /* Define command for ghost: verify command content */
186 for (cl
= 0, i
= 1; (cp
= UNCONST(argv
[i
])) != NULL
; ++i
)
188 cl
+= strlen(cp
) + 1; /* SP or NUL */
190 n_err(_("`ghost': empty command arguments after \"%s\"\n"), argv
[0]);
195 /* If the ghost already exists, recreate */
196 for (lcg
= NULL
, cg
= _cmd_ghosts
; cg
!= NULL
; lcg
= cg
, cg
= cg
->next
)
197 if (!strcmp(cg
->name
, argv
[0])) {
199 lcg
->next
= cg
->next
;
201 _cmd_ghosts
= cg
->next
;
206 nl
= strlen(argv
[0]) +1;
207 cg
= smalloc(sizeof(*cg
) - VFIELD_SIZEOF(struct cmd_ghost
, name
) + nl
+ cl
);
208 cg
->next
= _cmd_ghosts
;
210 memcpy(cg
->name
, argv
[0], nl
);
211 cp
= cg
->cmd
.s
= cg
->name
+ nl
;
213 while (*++argv
!= NULL
) {
216 memcpy(cp
, *argv
, i
);
231 char const **argv
= v
, *cp
;
232 struct cmd_ghost
*lcg
, *cg
;
235 while ((cp
= *argv
++) != NULL
) {
236 if (cp
[0] == '*' && cp
[1] == '\0') {
237 while ((cg
= _cmd_ghosts
) != NULL
) {
238 _cmd_ghosts
= cg
->next
;
242 for (lcg
= NULL
, cg
= _cmd_ghosts
; cg
!= NULL
; lcg
= cg
, cg
= cg
->next
)
243 if (!strcmp(cg
->name
, cp
)) {
245 lcg
->next
= cg
->next
;
247 _cmd_ghosts
= cg
->next
;
251 n_err(_("`unghost': no such alias: \"%s\"\n"), cp
);
262 __pcmd_cmp(void const *s1
, void const *s2
)
264 struct cmd
const * const *c1
= s1
, * const *c2
= s2
;
268 rv
= strcmp((*c1
)->name
, (*c2
)->name
);
276 struct cmd
const **cpa
, *cp
, **cursor
;
281 i
= NELEM(_cmd_tab
) + NELEM(_special_cmd_tab
) + 1;
282 cpa
= ac_alloc(sizeof(cp
) * i
);
284 for (i
= 0; i
< NELEM(_cmd_tab
); ++i
)
285 cpa
[i
] = _cmd_tab
+ i
;
289 for (j
= 0; j
< NELEM(_special_cmd_tab
); ++i
, ++j
)
290 cpa
[i
] = _special_cmd_tab
+ j
;
294 qsort(cpa
, i
, sizeof(cp
), &__pcmd_cmp
);
296 printf(_("Commands are:\n"));
297 for (i
= 0, cursor
= cpa
; (cp
= *cursor
++) != NULL
;) {
299 if (cp
->func
== &c_cmdnotsupp
)
301 j
= strlen(cp
->name
) + 2;
306 printf((*cursor
!= NULL
? "%s, " : "%s\n"), cp
->name
);
321 /* If we are PS_SOURCING, then return 1 so evaluate() can handle it.
322 * Otherwise return -1 to abort command loop */
323 rv
= (pstate
& PS_SOURCING
) ? 1 : -1;
333 printf(_("Features: %s\n"), ok_vlook(features
));
343 printf(_("Version %s\n"), ok_vlook(version
));
351 sighandler_type old_action
;
353 NYD_X
; /* Signal handler */
355 old_action
= safe_signal(s
, SIG_DFL
);
359 sigprocmask(SIG_UNBLOCK
, &nset
, NULL
);
361 sigprocmask(SIG_BLOCK
, &nset
, NULL
);
362 safe_signal(s
, old_action
);
363 if (_reset_on_stop
) {
365 n_TERMCAP_RESUME(TRU1
);
373 NYD_X
; /* Signal handler */
384 bool_t
volatile rv
= TRU1
;
387 if (!(pstate
& PS_SOURCING
)) {
388 if (safe_signal(SIGINT
, SIG_IGN
) != SIG_IGN
)
389 safe_signal(SIGINT
, onintr
);
390 if (safe_signal(SIGHUP
, SIG_IGN
) != SIG_IGN
)
391 safe_signal(SIGHUP
, hangup
);
392 /* TODO We do a lot of redundant signal handling, especially
393 * TODO with the command line editor(s); try to merge this */
394 safe_signal(SIGTSTP
, stop
);
395 safe_signal(SIGTTOU
, stop
);
396 safe_signal(SIGTTIN
, stop
);
398 _oldpipe
= safe_signal(SIGPIPE
, SIG_IGN
);
399 safe_signal(SIGPIPE
, _oldpipe
);
401 memset(&ev
, 0, sizeof ev
);
405 char *temporary_orig_line
; /* XXX eval_ctx.ev_line not yet constant */
407 n_COLOUR( n_colour_env_pop(TRU1
); )
409 /* TODO Unless we have our signal manager (or however we do it) child
410 * TODO processes may have time slots where their execution isn't
411 * TODO protected by signal handlers (in between start and setup
412 * TODO completed). close_all_files() is only called from onintr()
413 * TODO so those may linger possibly forever */
414 if (!(pstate
& PS_SOURCING
))
418 handlerstacktop
= NULL
;
420 temporary_localopts_free(); /* XXX intermediate hack */
421 sreset((pstate
& PS_SOURCING
) != 0);
422 if (!(pstate
& PS_SOURCING
)) {
425 /* TODO Note: this buffer may contain a password. We should redefine
426 * TODO the code flow which has to do that */
427 if ((cp
= termios_state
.ts_linebuf
) != NULL
) {
428 termios_state
.ts_linebuf
= NULL
;
429 termios_state
.ts_linesize
= 0;
430 free(cp
); /* TODO pool give-back */
432 /* TODO Due to expand-on-tab of NCL the buffer may grow */
433 if (ev
.ev_line
.l
> LINESIZE
* 3) {
434 free(ev
.ev_line
.s
); /* TODO pool! but what? */
436 ev
.ev_line
.l
= ev
.ev_line_size
= 0;
440 if (!(pstate
& PS_SOURCING
) && (options
& OPT_INTERACTIVE
)) {
443 cp
= ok_vlook(newmail
);
444 if ((options
& OPT_TTYIN
) && cp
!= NULL
) {
447 n
= (cp
!= NULL
&& strcmp(cp
, "nopoll"));
448 if ((mb
.mb_type
== MB_FILE
&& !stat(mailname
, &st
) &&
449 st
.st_size
> mailsize
) ||
450 (mb
.mb_type
== MB_MAILDIR
&& n
!= 0)) {
451 size_t odot
= PTR2SIZE(dot
- message
);
452 ui32_t odid
= (pstate
& PS_DID_PRINT_DOT
);
454 if (setfile(mailname
,
456 ((mb
.mb_perm
& MB_DELE
) ? 0 : FEDIT_RDONLY
)) < 0) {
457 exit_status
|= EXIT_ERR
;
461 dot
= message
+ odot
;
467 exit_status
= EXIT_OK
;
470 /* Read a line of commands and handle end of file specially */
472 ev
.ev_line
.l
= ev
.ev_line_size
;
473 n
= readline_input(NULL
, TRU1
, &ev
.ev_line
.s
, &ev
.ev_line
.l
,
475 ev
.ev_line_size
= (ui32_t
)ev
.ev_line
.l
;
476 ev
.ev_line
.l
= (ui32_t
)n
;
480 if (pstate
& PS_LOADING
)
482 if (pstate
& PS_SOURCING
) {
486 if ((options
& OPT_INTERACTIVE
) && ok_blook(ignoreeof
)) {
487 printf(_("*ignoreeof* set, use `quit' to quit.\n"));
493 temporary_orig_line
= ((pstate
& PS_SOURCING
) ||
494 !(options
& OPT_INTERACTIVE
)) ? NULL
495 : savestrbuf(ev
.ev_line
.s
, ev
.ev_line
.l
);
496 pstate
&= ~PS_HOOK_MASK
;
498 if (pstate
& PS_LOADING
) /* TODO mess; join PS_EVAL_ERROR.. */
502 if ((options
& OPT_BATCH_FLAG
) && ok_blook(batch_exit_on_error
)) {
503 if (exit_status
!= EXIT_OK
)
505 if ((pstate
& (PS_IN_LOAD
| PS_EVAL_ERROR
)) == PS_EVAL_ERROR
) {
506 exit_status
= EXIT_ERR
;
510 if (!(pstate
& PS_SOURCING
) && (options
& OPT_INTERACTIVE
)) {
511 if (ev
.ev_new_content
!= NULL
)
513 /* That *can* happen since evaluate() unstack()s on error! */
514 if (temporary_orig_line
!= NULL
)
515 n_tty_addhist(temporary_orig_line
, !ev
.ev_add_history
);
519 if (ev
.ev_line
.s
!= NULL
)
521 if (pstate
& PS_SOURCING
)
528 execute(char *linebuf
, size_t linesize
) /* XXX LEGACY */
534 memset(&ev
, 0, sizeof ev
);
535 ev
.ev_line
.s
= linebuf
;
536 ev
.ev_line
.l
= linesize
;
537 ev
.ev_is_recursive
= TRU1
;
538 n_COLOUR( n_colour_env_push(); )
540 n_COLOUR( n_colour_env_pop(FAL0
); )
547 evaluate(struct eval_ctx
*evp
)
550 char _wordbuf
[2], *arglist
[MAXARGC
], *cp
, *word
;
551 struct cmd_ghost
*cg
= NULL
;
552 struct cmd
const *com
= NULL
;
553 int muvec
[2], c
, e
= 1;
556 line
= evp
->ev_line
; /* XXX don't change original (buffer pointer) */
557 evp
->ev_add_history
= FAL0
;
558 evp
->ev_new_content
= NULL
;
560 /* Command ghosts that refer to shell commands or macro expansion restart */
563 /* Strip the white space away from the beginning of the command */
564 for (cp
= line
.s
; whitechar(*cp
); ++cp
)
566 line
.l
-= PTR2SIZE(cp
- line
.s
);
568 /* Ignore comments */
572 /* Handle ! differently to get the correct lexical conventions */
574 if (pstate
& PS_SOURCING
) {
575 n_err(_("Can't `!' while `source'ing\n"));
579 evp
->ev_add_history
= TRU1
;
583 /* Isolate the actual command; since it may not necessarily be
584 * separated from the arguments (as in `p1') we need to duplicate it to
585 * be able to create a NUL terminated version.
586 * We must be aware of several special one letter commands here */
588 if ((cp
= _lex_isolate(cp
)) == arglist
[0] &&
589 (*cp
== '|' || *cp
== '~' || *cp
== '?'))
591 c
= (int)PTR2SIZE(cp
- arglist
[0]);
593 word
= UICMP(z
, c
, <, sizeof _wordbuf
) ? _wordbuf
: salloc(c
+1);
594 memcpy(word
, arglist
[0], c
);
597 /* Look up the command; if not found, bitch.
598 * Normally, a blank command would map to the first command in the
599 * table; while PS_SOURCING, however, we ignore blank lines to eliminate
600 * confusion; act just the same for ghosts */
602 if ((pstate
& PS_SOURCING
) || cg
!= NULL
)
608 /* If this is the first evaluation, check command ghosts */
610 /* TODO relink list head, so it's sorted on usage over time?
611 * TODO in fact, there should be one hashmap over all commands and ghosts
612 * TODO so that the lookup could be made much more efficient than it is
613 * TODO now (two adjacent list searches! */
614 for (cg
= _cmd_ghosts
; cg
!= NULL
; cg
= cg
->next
)
615 if (!strcmp(word
, cg
->name
)) {
617 size_t i
= cg
->cmd
.l
;
618 line
.s
= salloc(i
+ line
.l
+1);
619 memcpy(line
.s
, cg
->cmd
.s
, i
);
620 memcpy(line
.s
+ i
, cp
, line
.l
);
621 line
.s
[i
+= line
.l
] = '\0';
631 if ((com
= _lex(word
)) == NULL
|| com
->func
== &c_cmdnotsupp
) {
632 bool_t s
= condstack_isskip();
633 if (!s
|| (options
& OPT_D_V
))
634 n_err(_("Unknown command%s: `%s'\n"),
635 (s
? _(" (conditionally ignored)") : ""), word
);
645 /* See if we should execute the command -- if a conditional we always
646 * execute it, otherwise, check the state of cond */
648 if (!(com
->argtype
& ARG_F
) && condstack_isskip())
651 /* Process the arguments to the command, depending on the type it expects,
652 * default to error. If we're PS_SOURCING an interactive command: error */
653 if ((options
& OPT_SENDMODE
) && !(com
->argtype
& ARG_M
)) {
654 n_err(_("May not execute `%s' while sending\n"), com
->name
);
657 if ((pstate
& PS_SOURCING
) && (com
->argtype
& ARG_I
)) {
658 n_err(_("May not execute `%s' while `source'ing\n"), com
->name
);
661 if (!(mb
.mb_perm
& MB_DELE
) && (com
->argtype
& ARG_W
)) {
662 n_err(_("May not execute `%s' -- message file is read only\n"),
666 if (evp
->ev_is_recursive
&& (com
->argtype
& ARG_R
)) {
667 n_err(_("Cannot recursively invoke `%s'\n"), com
->name
);
670 if (mb
.mb_type
== MB_VOID
&& (com
->argtype
& ARG_A
)) {
671 n_err(_("Cannot execute `%s' without active mailbox\n"), com
->name
);
674 if (com
->argtype
& ARG_O
)
675 OBSOLETE2(_("this command will be removed"), com
->name
);
677 if (com
->argtype
& ARG_V
)
678 temporary_arg_v_store
= NULL
;
680 switch (com
->argtype
& ARG_ARGMASK
) {
682 /* Message list defaulting to nearest forward legal message */
683 if (n_msgvec
== NULL
)
685 if ((c
= getmsglist(cp
, n_msgvec
, com
->msgflag
)) < 0)
688 *n_msgvec
= first(com
->msgflag
, com
->msgmask
);
692 if (*n_msgvec
== 0) {
693 if (!(pstate
& PS_HOOK_MASK
))
694 printf(_("No applicable messages\n"));
697 e
= (*com
->func
)(n_msgvec
);
701 /* Message list with no defaults, but no error if none exist */
702 if (n_msgvec
== NULL
) {
704 n_err(_("Invalid use of \"message list\"\n"));
707 if ((c
= getmsglist(cp
, n_msgvec
, com
->msgflag
)) < 0)
709 e
= (*com
->func
)(n_msgvec
);
713 /* Just the straight string, with leading blanks removed */
714 while (whitechar(*cp
))
716 e
= (*com
->func
)(cp
);
721 /* A vector of strings, in shell style */
722 if ((c
= getrawlist(cp
, line
.l
, arglist
, NELEM(arglist
),
723 ((com
->argtype
& ARG_ARGMASK
) == ARG_ECHOLIST
))) < 0)
725 if (c
< com
->minargs
) {
726 n_err(_("`%s' requires at least %d arg(s)\n"),
727 com
->name
, com
->minargs
);
730 if (c
> com
->maxargs
) {
731 n_err(_("`%s' takes no more than %d arg(s)\n"),
732 com
->name
, com
->maxargs
);
735 e
= (*com
->func
)(arglist
);
739 /* Just the constant zero, for exiting, eg. */
744 n_panic(_("Unknown argument type"));
747 if (e
== 0 && (com
->argtype
& ARG_V
) &&
748 (cp
= temporary_arg_v_store
) != NULL
) {
749 temporary_arg_v_store
= NULL
;
750 evp
->ev_new_content
= cp
;
753 if (!(com
->argtype
& ARG_H
) && !(pstate
& PS_MSGLIST_SAW_NO
))
754 evp
->ev_add_history
= TRU1
;
757 /* Exit the current source file on error TODO what a mess! */
759 pstate
&= ~PS_EVAL_ERROR
;
761 pstate
|= PS_EVAL_ERROR
;
762 if (e
< 0 || (pstate
& PS_LOADING
)) {
766 if (pstate
& PS_SOURCING
)
772 if ((com
->argtype
& ARG_P
) && ok_blook(autoprint
))
774 muvec
[0] = (int)PTR2SIZE(dot
- message
+ 1);
776 c_type(muvec
); /* TODO what if error? re-eval! */
778 if (!(pstate
& (PS_SOURCING
| PS_HOOK_MASK
)) && !(com
->argtype
& ARG_T
))
779 pstate
|= PS_SAW_COMMAND
;
781 pstate
&= ~PS_EVAL_ERROR
;
792 NYD_X
; /* Signal handler */
794 if (handlerstacktop
!= NULL
) {
798 safe_signal(SIGINT
, onintr
);
800 while (pstate
& PS_SOURCING
)
803 termios_state_reset();
811 n_err_sighdl(_("Interrupt\n"));
812 safe_signal(SIGPIPE
, _oldpipe
);
816 #ifdef HAVE_DOCSTRINGS
818 print_comm_docstr(char const *comm
)
820 struct cmd_ghost
const *cg
;
821 struct cmd
const *cp
, *cpmax
;
825 /* Ghosts take precedence */
826 for (cg
= _cmd_ghosts
; cg
!= NULL
; cg
= cg
->next
)
827 if (!strcmp(comm
, cg
->name
)) {
828 printf("%s -> ", comm
);
833 cpmax
= (cp
= _cmd_tab
) + NELEM(_cmd_tab
);
835 for (; cp
< cpmax
; ++cp
) {
836 if (!strcmp(comm
, cp
->name
))
837 printf("%s: %s\n", comm
, V_(cp
->doc
));
838 else if (is_prefix(comm
, cp
->name
))
839 printf("%s (%s): %s\n", comm
, cp
->name
, V_(cp
->doc
));
845 if (!rv
&& cpmax
== _cmd_tab
+ NELEM(_cmd_tab
)) {
846 cpmax
= (cp
= _special_cmd_tab
) + NELEM(_special_cmd_tab
);
850 if (!rv
&& cg
!= NULL
) {
851 printf("\"%s\"\n", comm
);
857 #endif /* HAVE_DOCSTRINGS */