1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2  *@ Program input of all sorts, input lexing, event loops, command evaluation.
3  *@ TODO - _PS_ERR_EXIT_* and _PSO_EXIT_* mixup is a mess: TERRIBLE!
4  *@ TODO - sigs_hold_all() most often on, especially robot mode: TERRIBLE!
5  *@ TODO - go_input(): with IO::Device we could have CStringListDevice, for
6  *@ TODO   example to handle injections, and also `readctl' channels!
7  *@ TODO   (Including sh(1)ell HERE strings and such.)
8  *
9  * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
10  * Copyright (c) 2012 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
11  * SPDX-License-Identifier: BSD-3-Clause TODO ISC
12  */
13 /*
14  * Copyright (c) 1980, 1993
15  *      The Regents of the University of California.  All rights reserved.
16  *
17  * Redistribution and use in source and binary forms, with or without
18  * modification, are permitted provided that the following conditions
19  * are met:
20  * 1. Redistributions of source code must retain the above copyright
21  *    notice, this list of conditions and the following disclaimer.
22  * 2. Redistributions in binary form must reproduce the above copyright
23  *    notice, this list of conditions and the following disclaimer in the
24  *    documentation and/or other materials provided with the distribution.
25  * 3. Neither the name of the University nor the names of its contributors
26  *    may be used to endorse or promote products derived from this software
27  *    without specific prior written permission.
28  *
29  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
30  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
32  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
33  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
34  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
35  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
37  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
38  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
39  * SUCH DAMAGE.
40  */
41 #undef su_FILE
42 #define su_FILE go
43 #define mx_SOURCE
44 
45 #ifndef mx_HAVE_AMALGAMATION
46 # include "mx/nail.h"
47 #endif
48 
49 #include <su/cs.h>
50 #include <su/cs-dict.h>
51 #include <su/icodec.h>
52 #include <su/mem.h>
53 
54 #include "mx/child.h"
55 #include "mx/cmd.h"
56 #include "mx/cmd-commandalias.h"
57 #include "mx/colour.h"
58 #include "mx/dig-msg.h"
59 #include "mx/file-streams.h"
60 #include "mx/sigs.h"
61 #include "mx/termios.h"
62 #include "mx/tty.h"
63 #include "mx/ui-str.h"
64 
65 /* TODO fake */
66 #include "su/code-in.h"
67 
68 enum a_go_flags{
69    a_GO_NONE,
70    a_GO_FREE = 1u<<0,         /* Structure was allocated, n_free() it */
71    a_GO_PIPE = 1u<<1,         /* Open on a pipe */
72    a_GO_FILE = 1u<<2,         /* Loading or sourcing a file */
73    a_GO_MACRO = 1u<<3,        /* Running a macro */
74    a_GO_MACRO_FREE_DATA = 1u<<4, /* Lines are allocated, n_free() once done */
75    /* TODO For simplicity this is yet _MACRO plus specialization overlay
76     * TODO (_X_OPTION, _BLTIN_RC, _CMD) -- should be types on their own! */
77    a_GO_MACRO_X_OPTION = 1u<<5, /* Macro indeed command line -X option */
78    a_GO_MACRO_BLTIN_RC = 1u<<6, /* Macro indeed command line -:x option */
79    a_GO_MACRO_CMD = 1u<<7,    /* Macro indeed single-line: ~:COMMAND */
80    /* TODO a_GO_SPLICE: the right way to support *on-compose-splice(-shell)?*
81     * TODO would be a command_loop object that emits an on_read_line event, and
82     * TODO have a special handler for the compose mode; with that, then,
83     * TODO _event_loop() would not call _evaluate() but CTX->on_read_line,
84     * TODO and _evaluate() would be the standard impl.,
85     * TODO whereas the COMMAND ESCAPE switch in collect.c would be another one.
86     * TODO With this generic accmacvar.c:temporary_compose_mode_hook_call()
87     * TODO could be dropped, and n_go_macro() could become extended,
88     * TODO and/or we would add a n_go_anything(), which would allow special
89     * TODO input handlers, special I/O input and output, special `localopts'
90     * TODO etc., to be glued to the new execution context.  And all I/O all
91     * TODO over this software should not use stdin/stdout, but CTX->in/out.
92     * TODO The pstate must be a property of the current execution context, too.
93     * TODO This not today. :(  For now we invent a special SPLICE execution
94     * TODO context overlay that at least allows to temporarily modify the
95     * TODO global pstate, and the global stdin and stdout pointers.  HACK!
96     * TODO This splice thing is very special and has to go again.  HACK!!
97     * TODO a_go_input() will drop it once it sees EOF (HACK!), but care for
98     * TODO jumps must be taken by splice creators.  HACK!!!  But works. ;} */
99    a_GO_SPLICE = 1u<<8,
100    /* If it is none of those, it must be the outermost, the global one */
101    a_GO_TYPE_MASK = a_GO_PIPE | a_GO_FILE | a_GO_MACRO |
102          /* a_GO_MACRO_X_OPTION | a_GO_MACRO_BLTIN_RC | a_GO_MACRO_CMD | */
103          a_GO_SPLICE,
104 
105    a_GO_FORCE_EOF = 1u<<14,    /* go_input() shall return EOF next */
106    a_GO_IS_EOF = 1u<<15,
107 
108    a_GO_SUPER_MACRO = 1u<<16, /* *Not* inheriting n_PS_SOURCING state */
109    /* This context has inherited the memory bag from its parent.
110     * In practice only used for resource file loading and -X args, which enter
111     * a top level n_go_main_loop() and should (re)use the in practice already
112     * allocated memory bag of the global context.
113     * The bag memory is reset after use. */
114    a_GO_MEMBAG_INHERITED = 1u<<17,
115 
116    /* This context has inherited the entire data context from its parent */
117    a_GO_DATACTX_INHERITED = 1u<<18,
118 
119    a_GO_XCALL_IS_CALL = 1u<<24,  /* n_GO_INPUT_NO_XCALL */
120    /* `xcall' optimization barrier: n_go_macro() has been finished with
121     * a `xcall' request, and `xcall' set this in the parent a_go_input of the
122     * said n_go_macro() to indicate a barrier: we teardown the a_go_input of
123     * the n_go_macro() away after leaving its _event_loop(), but then,
124     * back in n_go_macro(), that enters a for(;;) loop that directly calls
125     * c_call() -- our `xcall' stack avoidance optimization --, yet this call
126     * will itself end up in a new n_go_macro(), and if that again ends up with
127     * `xcall' this should teardown and leave its own n_go_macro(), unrolling
128     * the stack "up to the barrier level", but which effectively still is the
129     * n_go_macro() that lost its a_go_input and is looping the `xcall'
130     * optimization loop.  If no `xcall' is desired that loop is simply left and
131     * the _event_loop() of the outer a_go_ctx will perform a loop tick and
132     * clear this bit again OR become teardown itself */
133    a_GO_XCALL_LOOP = 1u<<25,  /* `xcall' optimization barrier level */
134    a_GO_XCALL_LOOP_ERROR = 1u<<26, /* .. state machine error transporter */
135    a_GO_XCALL_LOOP_MASK = a_GO_XCALL_LOOP | a_GO_XCALL_LOOP_ERROR
136 };
137 
138 enum a_go_cleanup_mode{
139    a_GO_CLEANUP_UNWIND = 1u<<0,     /* Teardown all ctxs except outermost */
140    a_GO_CLEANUP_TEARDOWN = 1u<<1,   /* Teardown current context */
141    a_GO_CLEANUP_LOOPTICK = 1u<<2,   /* Normal looptick cleanup */
142    a_GO_CLEANUP_MODE_MASK = su_BITENUM_MASK(0, 2),
143 
144    a_GO_CLEANUP_ERROR = 1u<<8,      /* Error occurred on level */
145    a_GO_CLEANUP_SIGINT = 1u<<9,     /* Interrupt signal received */
146    a_GO_CLEANUP_HOLDALLSIGS = 1u<<10 /* sigs_all_hol() active TODO */
147 };
148 
149 enum a_go_hist_flags{
150    a_GO_HIST_NONE = 0,
151    a_GO_HIST_ADD = 1u<<0,
152    a_GO_HIST_GABBY = 1u<<1,
153    a_GO_HIST_GABBY_ERROR = 1u<<2,
154    a_GO_HIST_INIT = 1u<<3
155 };
156 
157 struct a_go_eval_ctx{
158    struct str gec_line; /* The terminated data to _evaluate() */
159    u32 gec_line_size; /* May be used to store line memory size */
160    boole gec_ever_seen; /* Has ever been used (main_loop() only) */
161    boole gec_ignerr; /* Implicit `ignerr' prefix */
162    u8 gec__dummy[1];
163    u8 gec_hist_flags; /* enum a_go_hist_flags */
164    char const *gec_hist_cmd; /* If a_GO_HIST_ADD only, cmd and args */
165    char const *gec_hist_args;
166 };
167 
168 struct a_go_input_inject{
169    struct a_go_input_inject *gii_next;
170    uz gii_len;
171    boole gii_commit;
172    boole gii_no_history;
173    char gii_dat[VFIELD_SIZE(6)];
174 };
175 
176 struct a_go_ctx{
177    struct a_go_ctx *gc_outer;
178    sigset_t gc_osigmask;
179    u32 gc_flags;           /* enum a_go_flags */
180    u32 gc_loff;            /* Pseudo (macro): index in .gc_lines */
181    char **gc_lines;           /* Pseudo content, lines unfolded */
182    FILE *gc_file;             /* File we were in, if applicable */
183    struct a_go_input_inject *gc_inject; /* To be consumed first */
184    void (*gc_on_finalize)(void *);
185    void *gc_finalize_arg;
186    sigjmp_buf gc_eloop_jmp;   /* TODO one day...  for _event_loop() */
187    /* SPLICE hacks: saved stdin/stdout, saved pstate */
188    FILE *gc_splice_stdin;
189    FILE *gc_splice_stdout;
190    u32 gc_splice_psonce;
191    u8 gc_splice__dummy[4];
192    struct n_go_data_ctx gc_data;
193    char gc_name[VFIELD_SIZE(0)]; /* Name of file or macro */
194 };
195 
196 struct a_go_readctl_ctx{ /* TODO localize readctl_read_overlay: OnForkEvent! */
197    struct a_go_readctl_ctx *grc_last;
198    struct a_go_readctl_ctx *grc_next;
199    char const *grc_expand;          /* If filename based, expanded string */
200    FILE *grc_fp;
201    s32 grc_fd;                   /* Based upon file-descriptor */
202    char grc_name[VFIELD_SIZE(4)]; /* User input for identification purposes */
203 };
204 
205 static char const * const a_go_bltin_rc_lines[] = {
206 #include "gen-bltin-rc.h" /* */
207 };
208 
209 static n_sighdl_t a_go_oldpipe;
210 
211 /* Our current execution context, and the buffer backing the outermost level */
212 static struct a_go_ctx *a_go_ctx;
213 
214 #define a_GO_MAINCTX_NAME "top level/main loop"
215 static union{
216    u64 align;
217    char uf[VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
218          sizeof(a_GO_MAINCTX_NAME)];
219 } a_go__mainctx_b;
220 
221 /* `xcall' stack-avoidance bypass optimization.  This actually is
222  * a cmd_arg_save_to_heap() buffer with cmd_arg_ctx.cac_indat misused to
223  * point to the a_go_ctx to unroll up to */
224 static void *a_go_xcall;
225 
226 static sigjmp_buf a_go_srbuf; /* TODO GET RID */
227 
228 /* n_PS_STATE_PENDMASK requires some actions */
229 static void a_go_update_pstate(void);
230 
231 /* Evaluate a single command */
232 static boole a_go_evaluate(struct a_go_eval_ctx *gecp);
233 
234 /* Branch here on hangup signal and simulate "exit" */
235 static void a_go_hangup(int s);
236 
237 /* The following gets called on receipt of an interrupt */
238 static void a_go_onintr(int s);
239 
240 /* Cleanup current execution context, update the program state.
241  * If _CLEANUP_ERROR is set then we don't alert and error out if the stack
242  * doesn't exist at all, unless _CLEANUP_HOLDALLSIGS we sigs_all_hold() */
243 static void a_go_cleanup(enum a_go_cleanup_mode gcm);
244 
245 /* `source' and `source_if' (if silent_open_error: no pipes allowed, then).
246  * Returns FAL0 if file is somehow not usable (unless silent_open_error) or
247  * upon evaluation error, and TRU1 on success */
248 static boole a_go_file(char const *file, boole silent_open_error);
249 
250 /* System resource file load()ing or -X command line option array traversal */
251 static boole a_go_load(struct a_go_ctx *gcp);
252 
253 /* A simplified command loop for recursed state machines */
254 static boole a_go_event_loop(struct a_go_ctx *gcp, enum n_go_input_flags gif);
255 
256 static void
a_go_update_pstate(void)257 a_go_update_pstate(void){
258    boole act;
259    NYD_IN;
260 
261    act = ((n_pstate & n_PS_SIGWINCH_PEND) != 0);
262    n_pstate &= ~n_PS_PSTATE_PENDMASK;
263 
264    if(act){
265       char buf[32];
266 
267       snprintf(buf, sizeof buf, "%u", mx_termios_dimen.tiosd_real_width);
268       ok_vset(COLUMNS, buf);
269       snprintf(buf, sizeof buf, "%u", mx_termios_dimen.tiosd_real_height);
270       ok_vset(LINES, buf);
271    }
272    NYD_OU;
273 }
274 
275 static boole
a_go_evaluate(struct a_go_eval_ctx * gecp)276 a_go_evaluate(struct a_go_eval_ctx *gecp){
277    /* TODO old style(9), but also old code */
278    /* TODO a_go_evaluate() should be split in multiple subfunctions,
279     * TODO `eval' should be a prefix, etc., a cmd_ctx should be passed along;
280     * TODO this also affects history handling, as below!  etc etc */
281    struct str line;
282    struct n_string s_b, *s;
283    char _wordbuf[2], *argv_stack[3], **argv_base, **argvp, *vput, *cp, *word;
284    char const *alias_name, *emsg;
285    struct mx_cmd_desc const *cdp;
286    s32 nerrn, nexn;     /* TODO n_pstate_ex_no -> s64! */
287    int rv, c;
288    enum{
289       a_NONE = 0,
290       a_ALIAS_MASK = su_BITENUM_MASK(0, 2), /* Alias recursion counter bits */
291       a_NOPREFIX = 1u<<4, /* Modifier prefix not allowed right now */
292       a_NOALIAS = 1u<<5, /* "No alias!" expansion modifier */
293       a_IGNERR = 1u<<6, /* ignerr modifier prefix */
294       a_LOCAL = 1u<<7, /* local modifier prefix */
295       a_SCOPE = 1u<<8, /* TODO scope modifier prefix */
296       a_U = 1u<<9, /* TODO UTF-8 modifier prefix */
297       a_VPUT = 1u<<10, /* vput modifier prefix */
298       a_WYSH = 1u<<11, /* XXX v15+ drop wysh modifier prefix */
299       a_MODE_MASK = su_BITENUM_MASK(5, 11),
300       a_NO_ERRNO = 1u<<16, /* Don't set n_pstate_err_no */
301       a_IS_SKIP = 1u<<17, /* Conditional active, is skipping */
302       a_IS_EMPTY = 1u<<18 /* The empty command */
303    } flags;
304    NYD_IN;
305 
306    if(!(n_psonce & n_PSO_EXIT_MASK) && !(n_pstate & n_PS_ERR_EXIT_MASK))
307       n_exit_status = n_EXIT_OK;
308 
309    flags = ((n_cnd_if_exists() == TRUM1 ? a_IS_SKIP : a_NONE) |
310          (gecp->gec_ignerr ? a_IGNERR : a_NONE));
311    rv = 1;
312    nerrn = su_ERR_NONE;
313    nexn = n_EXIT_OK;
314    cdp = NULL;
315    vput = NULL;
316    alias_name = NULL;
317    line = gecp->gec_line; /* TODO const-ify original (buffer)! */
318    ASSERT(line.s[line.l] == '\0');
319 
320    if(line.l > 0 && su_cs_is_space(line.s[0]))
321       gecp->gec_hist_flags = a_GO_HIST_NONE;
322    else if(gecp->gec_hist_flags & a_GO_HIST_ADD)
323       gecp->gec_hist_cmd = gecp->gec_hist_args = NULL;
324    s = NULL;
325 
326    /* Aliases that refer to shell commands or macro expansion restart */
327 jrestart:
328    if(n_str_trim_ifs(&line, TRU1)->l == 0){
329       line.s[0] = '\0';
330       flags |= a_IS_EMPTY;
331       cdp = mx_cmd_default();
332       gecp->gec_hist_flags = a_GO_HIST_NONE;
333       goto jexec;
334    }
335    (cp = line.s)[line.l] = '\0';
336 
337    /* No-expansion modifier? */
338    if(!(flags & a_NOPREFIX) && *cp == '\\'){
339       line.s = ++cp;
340       --line.l;
341       flags |= a_NOALIAS;
342    }
343 
344    /* Note: adding more special treatments must be reflected in the `help' etc.
345     * output in cmd.c! */
346 
347    /* Ignore null commands (comments) */
348    if(*cp == '#'){
349       gecp->gec_hist_flags = a_GO_HIST_NONE;
350       goto jret0;
351    }
352 
353    /* Handle ! differently to get the correct lexical conventions */
354    if(*cp == '!')
355       ++cp;
356    /* Isolate the actual command; since it may not necessarily be
357     * separated from the arguments (as in `p1') we need to duplicate it to
358     * be able to create a NUL terminated version.
359     * We must be aware of several special one letter commands here */
360    else if((cp = n_UNCONST(mx_cmd_isolate_name(cp))) == line.s &&
361          (*cp == '|' || *cp == '?'))
362       ++cp;
363    c = (int)P2UZ(cp - line.s);
364    word = UCMP(z, c, <, sizeof _wordbuf) ? _wordbuf : n_autorec_alloc(c +1);
365    su_mem_copy(word, line.s, c);
366    word[c] = '\0';
367    line.l -= c;
368    line.s = cp;
369 
370    /* It may be a modifier.
371     * NOTE: changing modifiers must be reflected in cmd_is_valid_name() */
372    switch(c){
373    default:
374       break;
375    case sizeof("ignerr") -1:
376       if(!su_cs_cmp_case(word, "ignerr")){
377          flags |= a_NOPREFIX | a_IGNERR;
378          goto jrestart;
379       }
380       break;
381    /*case sizeof("scope") -1:*/
382    case sizeof("local") -1:
383       if(!su_cs_cmp_case(word, "local")){
384          flags |= a_NOPREFIX | a_LOCAL;
385          goto jrestart;
386       }else if(!su_cs_cmp_case(word, "scope")){
387          /* This will be an extended per-command `localopts' */
388          n_err(_("Ignoring yet unused `scope' command modifier!"));
389          flags |= a_NOPREFIX | a_SCOPE;
390          goto jrestart;
391       }
392       break;
393    case sizeof("u") -1:
394       if(!su_cs_cmp_case(word, "u")){
395          n_err(_("Ignoring yet unused `u' command modifier!"));
396          flags |= a_NOPREFIX | a_U;
397          goto jrestart;
398       }
399       break;
400    /*case sizeof("vput") -1:*/
401    case sizeof("wysh") -1:
402       if(!su_cs_cmp_case(word, "wysh")){
403          flags |= a_NOPREFIX | a_WYSH;
404          goto jrestart;
405       }else if(!su_cs_cmp_case(word, "vput")){
406          flags |= a_NOPREFIX | a_VPUT;
407          goto jrestart;
408       }
409       break;
410    }
411 
412    /* We need to trim for a possible history entry, but do it anyway and insert
413     * a space for argument separation in case of alias expansion.  Also, do
414     * terminate again because nothing prevents aliases from introducing WS */
415    n_str_trim_ifs(&line, TRU1);
416    line.s[line.l] = '\0';
417 
418    /* Lengthy history entry setup, possibly even redundant.  But having
419     * normalized history entries is a good thing, and this is maybe still
420     * cheaper than parsing a StrList of words per se
421     * TODO In v15 the history entry will be deduced from the argument vector,
422     * TODO possibly modified by the command itself, i.e., from the cmd_ctx
423     * TODO structure which is passed along.  And only if we have to do it */
424    if((gecp->gec_hist_flags & (a_GO_HIST_ADD | a_GO_HIST_INIT)
425          ) == a_GO_HIST_ADD){
426       if(line.l > 0){
427          s = n_string_creat_auto(&s_b);
428          s = n_string_assign_buf(s, line.s, line.l);
429          gecp->gec_hist_args = n_string_cp(s);
430          /* n_string_gut(n_string_drop_ownership(s)); */
431       }
432 
433       s = n_string_creat_auto(&s_b);
434       s = n_string_reserve(s, 32);
435 
436       if(flags & a_NOALIAS)
437          s = n_string_push_c(s, '\\');
438       if(flags & a_IGNERR)
439          s = n_string_push_buf(s, "ignerr ", sizeof("ignerr ") -1);
440       if(flags & a_WYSH)
441          s = n_string_push_buf(s, "wysh ", sizeof("wysh ") -1);
442       if(flags & a_VPUT)
443          s = n_string_push_buf(s, "vput ", sizeof("vput ") -1);
444       gecp->gec_hist_flags = a_GO_HIST_ADD | a_GO_HIST_INIT;
445    }
446 
447    /* Look up the command; if not found, bitch.  An empty cmd maps to the first
448     * command table entry.. */
449    if(*word == '\0'){
450       flags |= a_IS_EMPTY;
451       cdp = mx_cmd_default();
452       goto jexec;
453    }
454 
455    /* Can we expand an alias from what we have? */
456    if(!(flags & a_NOALIAS) && (flags & a_ALIAS_MASK) != a_ALIAS_MASK){
457       char const *alias_exp;
458       u8 expcnt;
459 
460       expcnt = (flags & a_ALIAS_MASK);
461       ++expcnt;
462       flags = (flags & ~(a_ALIAS_MASK | a_NOPREFIX)) | expcnt;
463 
464       /* Avoid self-recursion; since a commandalias can shadow a command of
465        * equal name allow one level of expansion to return an equal result:
466        * "commandalias q q;commandalias x q;x" should be "x->q->q->quit".
467        * P.S.: should also work for "help x" ... */
468       if(alias_name != NULL && !su_cs_cmp(word, alias_name))
469          flags |= a_NOALIAS;
470 
471       if((alias_name = mx_commandalias_exists(word, &alias_exp)) != NULL){
472          uz i;
473 
474          if(s != NULL){
475             s = n_string_push_cp(s, word);
476             gecp->gec_hist_cmd = n_string_cp(s);
477             s = NULL;
478          }
479 
480          /* And join arguments onto alias expansion */
481          alias_name = word;
482          i = strlen(alias_exp);
483          cp = line.s;
484          line.s = n_autorec_alloc(i + 1 + line.l +1);
485          su_mem_copy(line.s, alias_exp, i);
486          if(line.l > 0){
487             line.s[i++] = ' ';
488             su_mem_copy(&line.s[i], cp, line.l);
489          }
490          line.s[i += line.l] = '\0';
491          line.l = i;
492          goto jrestart;
493       }
494    }
495 
496    if((cdp = mx_cmd_firstfit(word)) == NIL){
497       if(!(flags & a_IS_SKIP) || (n_poption & n_PO_D_V))
498          n_err(_("%s: unknown command%s\n"),
499             prstr(word), ((flags & a_IS_SKIP)
500                ? _(" (ignored due to `if' condition)") : su_empty));
501       gecp->gec_hist_flags = a_GO_HIST_NONE;
502       if(flags & a_IS_SKIP)
503          goto jret0;
504       nerrn = su_ERR_NOSYS;
505       goto jleave;
506    }
507 
508 jexec:
509    /* The default command is not executed in a macro or when sourcing, when
510     * having expanded an alias etc.  To be able to deal with ";reply;~." we
511     * need to perform the shell expansion anyway, however */
512    if(UNLIKELY(flags & a_IS_EMPTY) &&
513          ((n_pstate & n_PS_ROBOT) || !(n_psonce & n_PSO_INTERACTIVE) ||
514           alias_name != NIL))
515       goto jwhite;
516 
517    /* See if we should execute the command -- if a conditional we always
518     * execute it, otherwise, check the state of cond.
519     * To allow "if 0; echo no; else; echo yes;end" we need to be able to
520     * perform input line sequentiation / rest injection even in whiteout
521     * situations.  See if we can do that. */
522    if(UNLIKELY(flags & a_IS_SKIP) && !(cdp->cd_caflags & mx_CMD_ARG_F)){
523 jwhite:
524       gecp->gec_hist_flags = a_GO_HIST_NONE;
525 
526       switch(cdp->cd_caflags & mx_CMD_ARG_TYPE_MASK){
527       case mx_CMD_ARG_TYPE_WYRA:{
528             char const *v15compat;
529 
530             if((v15compat = ok_vlook(v15_compat)) == su_NIL ||
531                   *v15compat == '\0')
532                break;
533          }
534          /* FALLTHRU */
535       case mx_CMD_ARG_TYPE_MSGLIST:
536       case mx_CMD_ARG_TYPE_NDMLIST:
537       case mx_CMD_ARG_TYPE_WYSH:
538       case mx_CMD_ARG_TYPE_ARG:{
539          boole once;
540 
541          emsg = line.s;
542          for(once = FAL0, s = n_string_creat_auto(&s_b);; once = TRU1){
543             su_u32 shs;
544 
545             shs = n_shexp_parse_token((n_SHEXP_PARSE_META_SEMICOLON |
546                      n_SHEXP_PARSE_DRYRUN | n_SHEXP_PARSE_TRIM_SPACE |
547                      n_SHEXP_PARSE_TRIM_IFSSPACE), s, &line,
548                   NULL);
549             if(!once && (flags & a_IS_EMPTY) && s->s_len != 0)
550                n_err(_("The empty (default) command is ignored here, "
551                      "but has arguments: %s\n"), emsg);
552             if(line.l == 0)
553                break;
554             if(shs & n_SHEXP_STATE_META_SEMICOLON){
555                ASSERT(shs & n_SHEXP_STATE_STOP);
556                n_go_input_inject(n_GO_INPUT_INJECT_COMMIT, line.s, line.l);
557                break;
558             }
559          }
560          }break;
561       case mx_CMD_ARG_TYPE_RAWDAT:
562       case mx_CMD_ARG_TYPE_STRING:
563       case mx_CMD_ARG_TYPE_RAWLIST:
564          break;
565       }
566       goto jret0;
567    }
568 
569    if(s != NIL && gecp->gec_hist_flags != a_GO_HIST_NONE){
570       s = n_string_push_cp(s, cdp->cd_name);
571       gecp->gec_hist_cmd = n_string_cp(s);
572       /* n_string_gut(n_string_drop_ownership(s)); */
573       s = NIL;
574    }
575 
576    nerrn = su_ERR_INVAL;
577 
578    /* Process the arguments to the command, depending on the type it expects */
579    UNINIT(emsg, NIL);
580    if((cdp->cd_caflags & mx_CMD_ARG_I) && !(n_psonce & n_PSO_INTERACTIVE) &&
581          !(n_poption & n_PO_BATCH_FLAG)){
582       emsg = N_("%s: can only be used batch or interactive mode\n");
583       goto jeflags;
584    }
585    if(!(cdp->cd_caflags & mx_CMD_ARG_M) && (n_psonce & n_PSO_SENDMODE)){
586       emsg = N_("%s: cannot be used while sending\n");
587       goto jeflags;
588    }
589    if(cdp->cd_caflags & mx_CMD_ARG_R){
590       if(n_pstate & n_PS_COMPOSE_MODE){
591          /* TODO n_PS_COMPOSE_MODE: should allow `reply': ~:reply! */
592          emsg = N_("%s: cannot be used in compose mode\n");
593          goto jeflags;
594       }
595       /* TODO Nothing should prevent mx_CMD_ARG_R in conjunction with
596        * TODO n_PS_ROBOT|_SOURCING; see a.._may_yield_control()! */
597       if(n_pstate & (n_PS_ROBOT | n_PS_SOURCING) && !n_go_may_yield_control()){
598          emsg = N_("%s: cannot be used in this program state\n");
599          goto jeflags;
600       }
601    }
602    if((cdp->cd_caflags & mx_CMD_ARG_S) && !(n_psonce & n_PSO_STARTED_CONFIG)){
603       emsg = N_("%s: cannot be used during startup\n");
604       goto jeflags;
605    }
606    if(!(cdp->cd_caflags & mx_CMD_ARG_X) && (n_pstate & n_PS_COMPOSE_FORKHOOK)){
607       emsg = N_("%s: cannot be used in a hook running in a child process\n");
608       goto jeflags;
609    }
610 
611    if((cdp->cd_caflags & mx_CMD_ARG_A) && mb.mb_type == MB_VOID){
612       emsg = N_("%s: needs an active mailbox\n");
613       goto jeflags;
614    }
615    if((cdp->cd_caflags & mx_CMD_ARG_W) && !(mb.mb_perm & MB_DELE)){
616       emsg = N_("%s: cannot be used in read-only mailbox\n");
617 jeflags:
618       n_err(V_(emsg), cdp->cd_name);
619       goto jleave;
620    }
621 
622    if((cdp->cd_caflags & mx_CMD_ARG_O) && /* XXX Remove! -> within command! */
623          !su_state_has(su_STATE_REPRODUCIBLE)){
624       static struct su_cs_dict a_go__obsol, *a_go_obsol;
625 
626       if(UNLIKELY(a_go_obsol == NIL)) /* XXX atexit cleanup */
627          a_go_obsol = su_cs_dict_set_treshold_shift(
628                su_cs_dict_create(&a_go__obsol, (su_CS_DICT_POW2_SPACED |
629                   su_CS_DICT_HEAD_RESORT | su_CS_DICT_ERR_PASS), NIL), 2);
630 
631       if(UNLIKELY(!su_cs_dict_has_key(a_go_obsol, cdp->cd_name))){
632          su_cs_dict_insert(a_go_obsol, cdp->cd_name, NIL);
633          n_err(_("Obsoletion warning: command will be removed: %s\n"),
634             cdp->cd_name);
635       }
636    }
637 
638    /* TODO v15: strip n_PS_ARGLIST_MASK off, just in case the actual command
639     * TODO doesn't use any of those list commands which strip this mask,
640     * TODO and for now we misuse bits for checking relation to history;
641     * TODO argument state should be property of a per-cmd carrier instead */
642    n_pstate &= ~n_PS_ARGLIST_MASK;
643 
644    if(flags & a_WYSH){
645       switch(cdp->cd_caflags & mx_CMD_ARG_TYPE_MASK){
646       case mx_CMD_ARG_TYPE_MSGLIST:
647       case mx_CMD_ARG_TYPE_NDMLIST:
648       case mx_CMD_ARG_TYPE_WYSH:
649       case mx_CMD_ARG_TYPE_ARG:
650          n_OBSOLETE2(cdp->cd_name, _("`wysh' modifier redundant/needless"));
651          flags ^= a_WYSH;
652          /* FALLTHRU */
653       case mx_CMD_ARG_TYPE_WYRA:
654          break;
655       case mx_CMD_ARG_TYPE_RAWDAT:
656       case mx_CMD_ARG_TYPE_STRING:
657       case mx_CMD_ARG_TYPE_RAWLIST:
658          n_err(_("%s: wysh: command modifier not supported\n"), cdp->cd_name);
659          goto jleave;
660       }
661    }
662 
663    if(flags & a_LOCAL){
664       /* TODO a_LOCAL should affect !CMD_ARG_L commands if `vput' is used!! */
665       if(!(cdp->cd_caflags & mx_CMD_ARG_L)){
666          emsg = N_("%s: local: command modifier not supported\n");
667          goto jeflags; /* above */
668       }
669       flags |= a_WYSH;
670       n_pstate |= n_PS_ARGMOD_LOCAL; /* TODO YET useless since stripped later
671          * TODO on in getrawlist() etc., i.e., the argument vector producers,
672          * TODO therefore yet needs to be set again based on flags&a_LOCAL! */
673    }
674 
675    if(flags & a_VPUT){
676       if(cdp->cd_caflags & mx_CMD_ARG_V){
677          emsg = line.s; /* xxx Cannot pass &char* as char const**, so no cp */
678          vput = n_shexp_parse_token_cp((n_SHEXP_PARSE_TRIM_SPACE |
679                n_SHEXP_PARSE_TRIM_IFSSPACE | n_SHEXP_PARSE_LOG |
680                n_SHEXP_PARSE_META_SEMICOLON | n_SHEXP_PARSE_META_KEEP), &emsg);
681          line.l -= P2UZ(emsg - line.s);
682          line.s = n_UNCONST(emsg);
683          if(emsg == NULL)
684             emsg = N_("could not parse input token");
685          else if(!n_shexp_is_valid_varname(vput, FAL0))
686             emsg = N_("not a valid variable name");
687          else if(!n_var_is_user_writable(vput))
688             emsg = N_("either not a user writable, or a boolean variable");
689          else
690             emsg = NULL;
691          if(emsg != NULL){
692             n_err("%s: vput: %s: %s\n",
693                   cdp->cd_name, V_(emsg), n_shexp_quote_cp(vput, FAL0));
694             nerrn = su_ERR_NOTSUP;
695             rv = -1;
696             goto jleave;
697          }
698          n_pstate |= n_PS_ARGMOD_VPUT; /* TODO YET useless since stripped later
699          * TODO on in getrawlist() etc., i.e., the argument vector producers,
700          * TODO therefore yet needs to be set again based on flags&a_VPUT! */
701       }else{
702          n_err(_("%s: %s: wysh: command modifier not supported\n"),
703             n_ERROR, cdp->cd_name);
704          mx_cmd_print_synopsis(cdp, NIL);
705          flags &= ~a_VPUT;
706       }
707    }
708 
709    if(n_poption & n_PO_D_VV)
710       n_err(_("COMMAND <%s> %s\n"), cdp->cd_name, line.s);
711 
712    switch(cdp->cd_caflags & mx_CMD_ARG_TYPE_MASK){
713    case mx_CMD_ARG_TYPE_MSGLIST:
714       /* Message list defaulting to nearest forward legal message */
715       if(n_msgvec == NULL)
716          goto jmsglist_err;
717       if((c = n_getmsglist(line.s, n_msgvec, cdp->cd_mflags_o_minargs, NULL)
718             ) < 0){
719          nerrn = su_ERR_NOMSG;
720          flags |= a_NO_ERRNO;
721          break;
722       }
723       if(c == 0){
724          if((n_msgvec[0] = first(cdp->cd_mflags_o_minargs,
725                cdp->cd_mmask_o_maxargs)) != 0){
726             c = 1;
727             n_msgmark1 = &message[n_msgvec[0] - 1];
728          }else{
729 jmsglist_err:
730             if(!(n_pstate & (n_PS_HOOK_MASK | n_PS_ROBOT)) ||
731                   (n_poption & n_PO_D_V))
732                n_err(_("No applicable messages\n"));
733             nerrn = su_ERR_NOMSG;
734             /* flags |= a_NO_ERRNO;*/
735             break;
736          }
737       }
738 jmsglist_go:
739       /* C99 */{
740          int *mvp;
741 
742          mvp = n_autorec_calloc(c +1, sizeof *mvp);
743          while(c-- > 0)
744             mvp[c] = n_msgvec[c];
745          if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & mx_CMD_ARG_EM))/*XXX*/
746             su_err_set_no(su_ERR_NONE);
747          rv = (*cdp->cd_func)(mvp);
748       }
749       break;
750 
751    case mx_CMD_ARG_TYPE_NDMLIST:
752       /* Message list with no defaults, but no error if none exist */
753       if(n_msgvec == NULL)
754          goto jmsglist_err;
755       if((c = n_getmsglist(line.s, n_msgvec, cdp->cd_mflags_o_minargs, NIL)
756             ) < 0){
757          nerrn = su_ERR_NOMSG;
758          flags |= a_NO_ERRNO;
759          break;
760       }
761       goto jmsglist_go;
762 
763    case mx_CMD_ARG_TYPE_STRING:
764       /* Just the straight string, old style, with leading blanks removed */
765       for(cp = line.s; su_cs_is_space(*cp);)
766          ++cp;
767       if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & mx_CMD_ARG_EM)) /* XXX */
768          su_err_set_no(su_ERR_NONE);
769       rv = (*cdp->cd_func)(cp);
770       break;
771 
772    case mx_CMD_ARG_TYPE_RAWDAT:
773       /* Just the straight string, placed in argv[] */
774       argvp = argv_stack;
775       if(flags & a_VPUT)
776          *argvp++ = vput;
777       *argvp++ = line.s;
778       *argvp = NULL;
779       if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & mx_CMD_ARG_EM)) /* XXX */
780          su_err_set_no(su_ERR_NONE);
781       rv = (*cdp->cd_func)(argv_stack);
782       break;
783 
784    case mx_CMD_ARG_TYPE_WYSH:
785       c = 1;
786       if(0){
787          /* FALLTHRU */
788    case mx_CMD_ARG_TYPE_WYRA:
789          /* C99 */{
790             char const *v15compat;
791 
792             if((v15compat = ok_vlook(v15_compat)) != su_NIL &&
793                   *v15compat != '\0')
794                flags |= a_WYSH;
795          }
796          c = (flags & a_WYSH) ? 1 : 0;
797          if(0){
798    case mx_CMD_ARG_TYPE_RAWLIST:
799             c = 0;
800          }
801       }
802       argvp = argv_base = n_autorec_alloc(sizeof(*argv_base) * n_MAXARGC);
803       if(flags & a_VPUT)
804          *argvp++ = vput;
805       if((c = getrawlist((c != 0), argvp,
806             (n_MAXARGC - ((flags & a_VPUT) != 0)), line.s, line.l)) < 0){
807          n_err(_("%s: invalid argument list\n"), cdp->cd_name);
808          flags |= a_NO_ERRNO;
809          break;
810       }
811 
812       if(UCMP(32, c, <, cdp->cd_mflags_o_minargs) ||
813             UCMP(32, c, >, cdp->cd_mmask_o_maxargs)){
814          n_err(_("%s: %s: takes at least %u, and no more than %u arg(s)\n"),
815             n_ERROR, cdp->cd_name, S(u32,cdp->cd_mflags_o_minargs),
816             S(u32,cdp->cd_mmask_o_maxargs));
817          mx_cmd_print_synopsis(cdp, NIL);
818          flags |= a_NO_ERRNO;
819          break;
820       }
821 
822       if(flags & a_LOCAL)
823          n_pstate |= n_PS_ARGMOD_LOCAL;
824       if(flags & a_VPUT)
825          n_pstate |= n_PS_ARGMOD_VPUT; /* TODO due to getrawlist(), as above */
826       if(flags & a_WYSH)
827          n_pstate |= n_PS_ARGMOD_WYSH;
828 
829       if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & mx_CMD_ARG_EM)) /* XXX */
830          su_err_set_no(su_ERR_NONE);
831       rv = (*cdp->cd_func)(argv_base);
832       if(a_go_xcall != NULL)
833          goto jret0;
834       break;
835 
836    case mx_CMD_ARG_TYPE_ARG:{
837       /* TODO The _ARG_TYPE_ARG is preliminary, in the end we should have a
838        * TODO per command-ctx carrier that also has slots for it arguments,
839        * TODO and that should be passed along all the way.  No more arglists
840        * TODO here, etc. */
841       struct mx_cmd_arg_ctx cac;
842 
843       cac.cac_desc = cdp->cd_cadp;
844       cac.cac_indat = line.s;
845       cac.cac_inlen = line.l;
846       cac.cac_msgflag = cdp->cd_mflags_o_minargs;
847       cac.cac_msgmask = cdp->cd_mmask_o_maxargs;
848       if(!mx_cmd_arg_parse(&cac)){
849          flags |= a_NO_ERRNO;
850          break;
851       }
852 
853       if(flags & a_VPUT){
854          cac.cac_vput = vput;
855          /* Global "hack" not used: n_pstate |= n_PS_ARGMOD_VPUT; */
856       }else
857          cac.cac_vput = NULL;
858 
859       if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & mx_CMD_ARG_EM)) /* XXX */
860          su_err_set_no(su_ERR_NONE);
861       rv = (*cdp->cd_func)(&cac);
862       if(a_go_xcall != NULL)
863          goto jret0;
864       }break;
865 
866    default:
867       su_DBG( n_panic(_("Implementation error: unknown argument type: %d"),
868          cdp->cd_caflags & mx_CMD_ARG_TYPE_MASK); )
869       nerrn = su_ERR_NOTOBACCO;
870       nexn = 1;
871       goto jret0;
872    }
873 
874    if(gecp->gec_hist_flags & a_GO_HIST_ADD){
875       if(cdp->cd_caflags & mx_CMD_ARG_H)
876          gecp->gec_hist_flags = a_GO_HIST_NONE;
877       else if((cdp->cd_caflags & mx_CMD_ARG_G) ||
878             (n_pstate & n_PS_MSGLIST_GABBY))
879          gecp->gec_hist_flags |= a_GO_HIST_GABBY;
880    }
881 
882    if(rv != 0){
883       if(!(flags & a_NO_ERRNO)){
884          if(cdp->cd_caflags & mx_CMD_ARG_EM)
885             flags |= a_NO_ERRNO;
886          else if((nerrn = su_err_no()) == 0)
887             nerrn = su_ERR_INVAL;
888       }/*else
889          flags ^= a_NO_ERRNO;*/
890    }else if(cdp->cd_caflags & mx_CMD_ARG_EM)
891       flags |= a_NO_ERRNO;
892    else
893       nerrn = su_ERR_NONE;
894 
895 jleave:
896    if((nexn = rv) != 0 &&
897          (gecp->gec_hist_flags & (a_GO_HIST_ADD | a_GO_HIST_INIT)
898             ) == (a_GO_HIST_ADD | a_GO_HIST_INIT))
899       gecp->gec_hist_flags |= a_GO_HIST_GABBY_ERROR;
900 
901    if(flags & a_IGNERR){
902       if(!(n_psonce & n_PSO_EXIT_MASK) && !(n_pstate & n_PS_ERR_EXIT_MASK))
903          n_exit_status = n_EXIT_OK;
904       n_pstate &= ~n_PS_ERR_EXIT_MASK;
905    }else if(rv != 0){
906       boole bo;
907 
908       if((bo = ok_blook(batch_exit_on_error))){
909          n_OBSOLETE(_("please use *errexit*, not *batch-exit-on-error*"));
910          if(!(n_poption & n_PO_BATCH_FLAG))
911             bo = FAL0;
912       }
913       if(ok_blook(errexit) || bo) /* TODO v15: drop bo */
914          n_pstate |= n_PS_ERR_QUIT;
915       else if(ok_blook(posix)){
916          if(n_psonce & n_PSO_STARTED)
917             rv = 0;
918          else if(!(n_psonce & n_PSO_INTERACTIVE))
919             n_pstate |= n_PS_ERR_XIT;
920       }else
921          rv = 0;
922 
923       if(rv != 0){
924          if(n_exit_status == n_EXIT_OK)
925             n_exit_status = n_EXIT_ERR;
926          if((n_poption & n_PO_D_V) &&
927                !(n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)))
928             n_alert(_("Non-interactive, bailing out due to errors "
929                "in startup load phase"));
930          goto jret;
931       }
932    }
933 
934    if(cdp == NULL)
935       goto jret0;
936    if((cdp->cd_caflags & mx_CMD_ARG_P) && ok_blook(autoprint) && visible(dot))
937       n_go_input_inject(n_GO_INPUT_INJECT_COMMIT, "\\type",
938          sizeof("\\type") -1);
939 
940    if(!(n_pstate & (n_PS_SOURCING | n_PS_HOOK_MASK)) &&
941          !(cdp->cd_caflags & mx_CMD_ARG_T))
942       n_pstate |= n_PS_SAW_COMMAND;
943 jret0:
944    rv = 0;
945 jret:
946    if(!(flags & a_NO_ERRNO))
947       n_pstate_err_no = nerrn;
948    n_pstate_ex_no = nexn;
949    NYD_OU;
950    return (rv == 0);
951 }
952 
953 static void
a_go_hangup(int s)954 a_go_hangup(int s){
955    NYD; /* Signal handler */
956    UNUSED(s);
957    /* nothing to do? */
958    exit(n_EXIT_ERR);
959 }
960 
961 #ifdef mx_HAVE_IMAP
n_go_onintr_for_imap(void)962 FL void n_go_onintr_for_imap(void){a_go_onintr(0);}
963 #endif
964 static void
a_go_onintr(int s)965 a_go_onintr(int s){ /* TODO block signals while acting */
966    NYD; /* Signal handler */
967    UNUSED(s);
968 
969    safe_signal(SIGINT, a_go_onintr);
970 
971    mx_termios_cmdx(mx_TERMIOS_CMD_RESET);
972 
973    a_go_cleanup(a_GO_CLEANUP_UNWIND | /* XXX FAKE */a_GO_CLEANUP_HOLDALLSIGS);
974 
975    if(interrupts != 1)
976       n_err_sighdl(_("Interrupt\n"));
977    safe_signal(SIGPIPE, a_go_oldpipe);
978    siglongjmp(a_go_srbuf, 0); /* FIXME get rid */
979 }
980 
981 static void
a_go_cleanup(enum a_go_cleanup_mode gcm)982 a_go_cleanup(enum a_go_cleanup_mode gcm){
983    /* Signals blocked */
984    struct a_go_ctx *gcp;
985    NYD_IN;
986 
987    if(!(gcm & a_GO_CLEANUP_HOLDALLSIGS))
988       mx_sigs_all_holdx();
989 jrestart:
990    gcp = a_go_ctx;
991 
992    /* Free input injections of this level first */
993    if(!(gcm & a_GO_CLEANUP_LOOPTICK)){
994       struct a_go_input_inject **giipp, *giip;
995 
996       for(giipp = &gcp->gc_inject; (giip = *giipp) != NULL;){
997          *giipp = giip->gii_next;
998          n_free(giip);
999       }
1000    }
1001 
1002    /* Cleanup non-crucial external stuff */
1003    mx_COLOUR(
1004       if(gcp->gc_data.gdc_colour != NIL)
1005          mx_colour_stack_del(&gcp->gc_data);
1006    )
1007 
1008    /* Cleanup crucial external stuff as necessary */
1009    if(gcp->gc_data.gdc_ifcond != NIL &&
1010          ((gcp->gc_outer == NIL && (gcm & a_GO_CLEANUP_UNWIND)) ||
1011             !(gcm & a_GO_CLEANUP_LOOPTICK))){
1012       n_cnd_if_stack_del(&gcp->gc_data);
1013       if(!(gcm & (a_GO_CLEANUP_ERROR | a_GO_CLEANUP_SIGINT)) &&
1014             !(gcp->gc_flags & a_GO_FORCE_EOF) && a_go_xcall == NULL &&
1015             !(n_psonce & n_PSO_EXIT_MASK)){
1016          n_err(_("Unmatched `if' at end of %s%s\n"),
1017             (gcp->gc_outer == NIL ? su_empty
1018              : ((gcp->gc_flags & a_GO_MACRO
1019               ? (gcp->gc_flags & a_GO_MACRO_CMD ? _(" command") : _(" macro"))
1020               : _(" `source'd file")))),
1021             gcp->gc_name);
1022          gcm |= a_GO_CLEANUP_ERROR;
1023       }
1024    }
1025 
1026    /* Work the actual context (according to cleanup mode) */
1027    if(gcp->gc_outer == NULL){
1028       if(gcm & (a_GO_CLEANUP_UNWIND | a_GO_CLEANUP_SIGINT)){
1029          if(a_go_xcall != NULL){
1030             n_free(a_go_xcall);
1031             a_go_xcall = NULL;
1032          }
1033          gcp->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
1034          n_pstate &= ~n_PS_ERR_EXIT_MASK;
1035          mx_fs_close_all();
1036       }else{
1037          if(!(n_pstate & n_PS_SOURCING))
1038             mx_fs_close_all();
1039       }
1040 
1041       su_mem_bag_reset(gcp->gc_data.gdc_membag);
1042       su_DBG( su_mem_set_conf(su_MEM_CONF_LINGER_FREE_RELEASE, 0); )
1043 
1044       n_pstate &= ~(n_PS_SOURCING | n_PS_ROBOT);
1045       ASSERT(a_go_xcall == NULL);
1046       ASSERT(!(gcp->gc_flags & a_GO_XCALL_LOOP_MASK));
1047       ASSERT(gcp->gc_on_finalize == NULL);
1048       mx_COLOUR( ASSERT(gcp->gc_data.gdc_colour == NIL); )
1049 
1050       if(gcm & a_GO_CLEANUP_ERROR)
1051          goto jerr;
1052       goto jxleave;
1053    }else if(gcm & a_GO_CLEANUP_LOOPTICK){
1054       su_mem_bag_reset(gcp->gc_data.gdc_membag);
1055       su_DBG( su_mem_set_conf(su_MEM_CONF_LINGER_FREE_RELEASE, 0); )
1056       goto jxleave;
1057    }else if(gcp->gc_flags & a_GO_SPLICE){ /* TODO Temporary hack */
1058       n_stdin = gcp->gc_splice_stdin;
1059       n_stdout = gcp->gc_splice_stdout;
1060       n_psonce = gcp->gc_splice_psonce;
1061       goto jstackpop;
1062    }
1063 
1064    /* Teardown context */
1065    if(gcp->gc_flags & a_GO_MACRO){
1066       if(gcp->gc_flags & a_GO_MACRO_FREE_DATA){
1067          char **lp;
1068 
1069          while(*(lp = &gcp->gc_lines[gcp->gc_loff]) != NULL){
1070             n_free(*lp);
1071             ++gcp->gc_loff;
1072          }
1073          /* Part of gcp's memory chunk, then */
1074          if(!(gcp->gc_flags & a_GO_MACRO_CMD))
1075             n_free(gcp->gc_lines);
1076       }
1077    }else if(gcp->gc_flags & a_GO_PIPE)
1078       /* XXX command manager should -TERM then -KILL instead of hoping
1079        * XXX for exit of provider due to su_ERR_PIPE / SIGPIPE */
1080       mx_fs_pipe_close(gcp->gc_file, TRU1);
1081    else if(gcp->gc_flags & a_GO_FILE)
1082       mx_fs_close(gcp->gc_file);
1083 
1084    if(!(gcp->gc_flags & a_GO_MEMBAG_INHERITED))
1085       su_mem_bag_gut(gcp->gc_data.gdc_membag);
1086    else
1087       su_mem_bag_reset(gcp->gc_data.gdc_membag);
1088 
1089 jstackpop:
1090    /* Update a_go_ctx and n_go_data, n_pstate ... */
1091    a_go_ctx = gcp->gc_outer;
1092    ASSERT(a_go_ctx != NULL);
1093    /* C99 */{
1094       struct a_go_ctx *x;
1095 
1096       for(x = a_go_ctx; x->gc_flags & a_GO_DATACTX_INHERITED;){
1097          x = x->gc_outer;
1098          ASSERT(x != NULL);
1099       }
1100       n_go_data = &x->gc_data;
1101    }
1102 
1103    if((a_go_ctx->gc_flags & (a_GO_MACRO | a_GO_SUPER_MACRO)) ==
1104          (a_GO_MACRO | a_GO_SUPER_MACRO)){
1105       n_pstate &= ~n_PS_SOURCING;
1106       ASSERT(n_pstate & n_PS_ROBOT);
1107    }else if(!(a_go_ctx->gc_flags & a_GO_TYPE_MASK))
1108       n_pstate &= ~(n_PS_SOURCING | n_PS_ROBOT);
1109    else
1110       ASSERT(n_pstate & n_PS_ROBOT);
1111 
1112    if(gcp->gc_on_finalize != NULL)
1113       (*gcp->gc_on_finalize)(gcp->gc_finalize_arg);
1114 
1115    if(gcm & a_GO_CLEANUP_ERROR){
1116       if(a_go_ctx->gc_flags & a_GO_XCALL_LOOP)
1117          a_go_ctx->gc_flags |= a_GO_XCALL_LOOP_ERROR;
1118       goto jerr;
1119    }
1120 jleave:
1121    if(gcp->gc_flags & a_GO_FREE)
1122       n_free(gcp);
1123 
1124    if(UNLIKELY((gcm & a_GO_CLEANUP_UNWIND) && gcp != a_go_ctx))
1125       goto jrestart;
1126 
1127 jxleave:
1128    NYD_OU;
1129    if(!(gcm & a_GO_CLEANUP_HOLDALLSIGS))
1130       mx_sigs_all_rele();
1131    return;
1132 
1133 jerr:
1134    /* With *posix* we follow what POSIX says:
1135     *    Any errors in the start-up file shall either cause mailx to
1136     *    terminate with a diagnostic message and a non-zero status or to
1137     *    continue after writing a diagnostic message, ignoring the
1138     *    remainder of the lines in the start-up file
1139     * Print the diagnostic only for the outermost resource unless the user
1140     * is debugging or in verbose mode */
1141    if((n_poption & n_PO_D_V) ||
1142          (!(n_psonce & n_PSO_STARTED) &&
1143           !(gcp->gc_flags & (a_GO_SPLICE | a_GO_MACRO)) &&
1144           (gcp->gc_outer == NIL ||
1145             !(gcp->gc_outer->gc_flags & a_GO_TYPE_MASK))))
1146       /* I18N: file inclusion, macro etc. evaluation has been stopped */
1147       n_alert(_("Stopped %s %s due to errors%s"),
1148          (n_psonce & n_PSO_STARTED
1149           ? (gcp->gc_flags & a_GO_SPLICE ? _("spliced in program")
1150           : (gcp->gc_flags & a_GO_MACRO
1151              ? (gcp->gc_flags & a_GO_MACRO_CMD
1152                 ? _("evaluating command") : _("evaluating macro"))
1153              : (gcp->gc_flags & a_GO_PIPE
1154                 ? _("executing `source'd pipe")
1155                 : (gcp->gc_flags & a_GO_FILE
1156                   ? _("loading `source'd file") : _(a_GO_MAINCTX_NAME))))
1157           )
1158           : (((gcp->gc_flags & (a_GO_MACRO | a_GO_MACRO_BLTIN_RC)
1159                ) == a_GO_MACRO)
1160              ? ((gcp->gc_flags & a_GO_MACRO_X_OPTION)
1161                 ? _("evaluating command line")
1162                 : _("evaluating macro"))
1163              : _("loading initialization resource"))),
1164          n_shexp_quote_cp(gcp->gc_name, FAL0),
1165          (n_poption & n_PO_D ? n_empty : _(" (enable *debug* for trace)")));
1166    goto jleave;
1167 }
1168 
1169 static boole
a_go_file(char const * file,boole silent_open_error)1170 a_go_file(char const *file, boole silent_open_error){
1171    struct a_go_ctx *gcp;
1172    sigset_t osigmask;
1173    uz nlen;
1174    char *nbuf;
1175    boole ispipe;
1176    FILE *fip;
1177    NYD_IN;
1178 
1179    fip = NIL;
1180    UNINIT(nbuf, NIL);
1181 
1182    /* Being a command argument file is space-trimmed *//* TODO v15 with
1183     * TODO WYRALIST this is no longer necessary true, and for that we
1184     * TODO don't set _PARSE_TRIM_SPACE because we cannot! -> cmd.h!! */
1185 #if 0
1186    ((ispipe = (!silent_open_error && (nlen = su_cs_len(file)) > 0 &&
1187          file[--nlen] == '|')))
1188 #else
1189    ispipe = FAL0;
1190    if(!silent_open_error){
1191       for(nlen = su_cs_len(file); nlen > 0;){
1192          char c;
1193 
1194          c = file[--nlen];
1195          if(!su_cs_is_space(c)){
1196             if(c == '|'){
1197                nbuf = savestrbuf(file, nlen);
1198                ispipe = TRU1;
1199             }
1200             break;
1201          }
1202       }
1203    }
1204 #endif
1205 
1206    if(ispipe){
1207       if((fip = mx_fs_pipe_open(nbuf /* #if 0 above = savestrbuf(file, nlen)*/,
1208             "r", ok_vlook(SHELL), NIL, -1)) == NIL)
1209          goto jeopencheck;
1210    }else if((nbuf = fexpand(file, FEXP_LOCAL_FILE | FEXP_NVAR)) == NIL)
1211       goto jeopencheck;
1212    else if((fip = mx_fs_open(nbuf, "r")) == NIL){
1213 jeopencheck:
1214       if(!silent_open_error || (n_poption & n_PO_D_V))
1215          n_perr(nbuf, 0);
1216       if(silent_open_error)
1217          fip = (FILE*)-1;
1218       goto jleave;
1219    }
1220 
1221    sigprocmask(SIG_BLOCK, NULL, &osigmask);
1222 
1223    gcp = n_alloc(VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
1224          (nlen = su_cs_len(nbuf) +1));
1225    su_mem_set(gcp, 0, VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1226    gcp->gc_data.gdc_membag =
1227          su_mem_bag_create(&gcp->gc_data.gdc__membag_buf[0], 0);
1228 
1229    mx_sigs_all_holdx();
1230 
1231    gcp->gc_outer = a_go_ctx;
1232    gcp->gc_osigmask = osigmask;
1233    gcp->gc_file = fip;
1234    gcp->gc_flags = (ispipe ? a_GO_FREE | a_GO_PIPE : a_GO_FREE | a_GO_FILE) |
1235          (a_go_ctx->gc_flags & a_GO_SUPER_MACRO ? a_GO_SUPER_MACRO : 0);
1236    su_mem_copy(gcp->gc_name, nbuf, nlen);
1237 
1238    a_go_ctx = gcp;
1239    n_go_data = &gcp->gc_data;
1240    n_pstate |= n_PS_SOURCING | n_PS_ROBOT;
1241    if(!a_go_event_loop(gcp, n_GO_INPUT_NONE | n_GO_INPUT_NL_ESC))
1242       fip = NULL;
1243 jleave:
1244    NYD_OU;
1245    return (fip != NULL);
1246 }
1247 
1248 static boole
a_go_load(struct a_go_ctx * gcp)1249 a_go_load(struct a_go_ctx *gcp){
1250    NYD2_IN;
1251 
1252    ASSERT(!(n_psonce & n_PSO_STARTED));
1253    ASSERT(!(a_go_ctx->gc_flags & a_GO_TYPE_MASK));
1254 
1255    gcp->gc_flags |= a_GO_MEMBAG_INHERITED;
1256    gcp->gc_data.gdc_membag = n_go_data->gdc_membag;
1257 
1258    mx_sigs_all_holdx();
1259 
1260    /* POSIX:
1261     *    Any errors in the start-up file shall either cause mailx to terminate
1262     *    with a diagnostic message and a non-zero status or to continue after
1263     *    writing a diagnostic message, ignoring the remainder of the lines in
1264     *    the start-up file. */
1265    gcp->gc_outer = a_go_ctx;
1266    a_go_ctx = gcp;
1267    n_go_data = &gcp->gc_data;
1268 /* FIXME won't work for now (n_PS_ROBOT needs n_PS_SOURCING sofar)
1269    n_pstate |= n_PS_ROBOT |
1270          (gcp->gc_flags & a_GO_MACRO_X_OPTION ? 0 : n_PS_SOURCING);
1271 */
1272    n_pstate |= n_PS_ROBOT | n_PS_SOURCING;
1273 
1274    mx_sigs_all_rele();
1275 
1276    n_go_main_loop();
1277    NYD2_OU;
1278    return (((n_psonce & n_PSO_EXIT_MASK) |
1279       (n_pstate & n_PS_ERR_EXIT_MASK)) == 0);
1280 }
1281 
1282 static void
a_go__eloopint(int sig)1283 a_go__eloopint(int sig){ /* TODO one day, we don't need it no more */
1284    NYD; /* Signal handler */
1285    UNUSED(sig);
1286    siglongjmp(a_go_ctx->gc_eloop_jmp, 1);
1287 }
1288 
1289 static boole
a_go_event_loop(struct a_go_ctx * gcp,enum n_go_input_flags gif)1290 a_go_event_loop(struct a_go_ctx *gcp, enum n_go_input_flags gif){
1291    n_sighdl_t soldhdl;
1292    struct a_go_eval_ctx gec;
1293    enum {a_RETOK = TRU1, a_TICKED = 1<<1} volatile f;
1294    volatile int hadint;/* TODO get rid of shitty signal stuff (see signal.c) */
1295    sigset_t osigmask;
1296    NYD2_IN;
1297 
1298    su_mem_set(&gec, 0, sizeof gec);
1299    if(gif & n_GO_INPUT_IGNERR)
1300       gec.gec_ignerr = TRU1;
1301    mx_fs_linepool_aquire(&gec.gec_line.s, &gec.gec_line.l);
1302 
1303    osigmask = gcp->gc_osigmask;
1304    hadint = FAL0;
1305    f = a_RETOK;
1306 
1307    if((soldhdl = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN){
1308       safe_signal(SIGINT, &a_go__eloopint);
1309       if(sigsetjmp(gcp->gc_eloop_jmp, 1)){
1310          mx_sigs_all_holdx();
1311          hadint = TRU1;
1312          f &= ~a_RETOK;
1313          gcp->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
1314          goto jjump;
1315       }
1316    }
1317 
1318    for(;; f |= a_TICKED){
1319       int n;
1320 
1321       if(f & a_TICKED){
1322          su_mem_bag_reset(gcp->gc_data.gdc_membag);
1323          su_DBG( su_mem_set_conf(su_MEM_CONF_LINGER_FREE_RELEASE, 0); )
1324       }
1325 
1326       /* Read a line of commands and handle end of file specially */
1327       gec.gec_line.l = gec.gec_line_size;
1328       mx_sigs_all_rele();
1329       n = n_go_input(gif, NULL, &gec.gec_line.s, &gec.gec_line.l, NULL, NULL);
1330       mx_sigs_all_holdx();
1331       gec.gec_line_size = S(u32,gec.gec_line.l);
1332       gec.gec_line.l = S(u32,n);
1333 
1334       if(n < 0)
1335          break;
1336 
1337       mx_sigs_all_rele();
1338       ASSERT(gec.gec_hist_flags == a_GO_HIST_NONE);
1339       if(!a_go_evaluate(&gec))
1340          f &= ~a_RETOK;
1341       mx_sigs_all_holdx();
1342 
1343       if(!(f & a_RETOK) || a_go_xcall != NULL ||
1344             (n_psonce & n_PSO_EXIT_MASK) || (n_pstate & n_PS_ERR_EXIT_MASK))
1345          break;
1346    }
1347 
1348 jjump: /* TODO Should be _CLEANUP_UNWIND not _TEARDOWN on signal if DOABLE! */
1349    a_go_cleanup(a_GO_CLEANUP_TEARDOWN |
1350       (f & a_RETOK ? 0 : a_GO_CLEANUP_ERROR) |
1351       (hadint ? a_GO_CLEANUP_SIGINT : 0) | a_GO_CLEANUP_HOLDALLSIGS);
1352 
1353    mx_fs_linepool_release(gec.gec_line.s, gec.gec_line_size);
1354 
1355    if(soldhdl != SIG_IGN)
1356       safe_signal(SIGINT, soldhdl);
1357 
1358    NYD2_OU;
1359    mx_sigs_all_rele();
1360    if(hadint){
1361       sigprocmask(SIG_SETMASK, &osigmask, NULL);
1362       n_raise(SIGINT);
1363    }
1364    return (f & a_RETOK);
1365 }
1366 
1367 FL void
n_go_init(void)1368 n_go_init(void){
1369    struct a_go_ctx *gcp;
1370    NYD2_IN;
1371 
1372    ASSERT(n_stdin != NULL);
1373 
1374    gcp = (void*)a_go__mainctx_b.uf;
1375    su_DBGOR( su_mem_set(gcp, 0, VSTRUCT_SIZEOF(struct a_go_ctx, gc_name)),
1376       su_mem_set(&gcp->gc_data, 0, sizeof gcp->gc_data) );
1377    gcp->gc_data.gdc_membag =
1378          su_mem_bag_create(&gcp->gc_data.gdc__membag_buf[0], 0);
1379    gcp->gc_file = n_stdin;
1380    su_mem_copy(gcp->gc_name, a_GO_MAINCTX_NAME, sizeof(a_GO_MAINCTX_NAME));
1381 
1382    a_go_ctx = gcp;
1383    n_go_data = &gcp->gc_data;
1384 
1385    mx_termios_controller_setup(mx_TERMIOS_SETUP_STARTUP);
1386    mx_child_controller_setup();
1387    NYD2_OU;
1388 }
1389 
1390 FL boole
n_go_main_loop(void)1391 n_go_main_loop(void){ /* FIXME */
1392    struct a_go_eval_ctx gec;
1393    int n, eofcnt;
1394    boole volatile rv;
1395    NYD_IN;
1396 
1397    rv = TRU1;
1398 
1399    if (!(n_pstate & n_PS_SOURCING)) {
1400       if (safe_signal(SIGINT, SIG_IGN) != SIG_IGN)
1401          safe_signal(SIGINT, &a_go_onintr);
1402       if (safe_signal(SIGHUP, SIG_IGN) != SIG_IGN)
1403          safe_signal(SIGHUP, &a_go_hangup);
1404    }
1405    a_go_oldpipe = safe_signal(SIGPIPE, SIG_IGN);
1406    safe_signal(SIGPIPE, a_go_oldpipe);
1407 
1408    su_mem_set(&gec, 0, sizeof gec);
1409 
1410    (void)sigsetjmp(a_go_srbuf, 1); /* FIXME get rid */
1411    mx_sigs_all_holdx();
1412 
1413    for (eofcnt = 0;; gec.gec_ever_seen = TRU1) {
1414       interrupts = 0;
1415       DVL(su_nyd_reset_level(1);)
1416 
1417       if(gec.gec_ever_seen)
1418          /* TODO too expensive, just do the membag (++?) here.
1419           * TODO in fact all other conditions would be an error, no? */
1420          a_go_cleanup(a_GO_CLEANUP_LOOPTICK | a_GO_CLEANUP_HOLDALLSIGS);
1421 
1422       /* TODO This condition test may not be here: if the condition is not true
1423        * TODO a recursive mainloop object without that cruft should be used! */
1424       if(!(n_pstate & (n_PS_ROBOT | n_PS_SOURCING))){
1425          if(a_go_ctx->gc_inject == su_NIL)
1426             mx_fs_linepool_cleanup(FAL0);
1427 
1428          /* TODO We need a regular on_tick_event, to which this one, the
1429           * TODO *newmail* thing below, and possibly other caches
1430           * TODO (mime.types, mta-aliases, mailcap, netrc; if not yet:
1431           * TODO convert!!) can attach: they should trigger a switch and
1432           * TODO update cache state only once per mainloop tick!! */
1433          /* C99 */{
1434             char const *ccp;
1435 
1436             if((ccp = ok_vlook(on_main_loop_tick)) != NIL)
1437                temporary_on_xy_hook_caller("on-main-loop-tick", ccp, TRU1);
1438          }
1439 
1440          /* Do not check newmail with active injections, wait for prompt */
1441          if(a_go_ctx->gc_inject == su_NIL && (n_psonce & n_PSO_INTERACTIVE)){
1442             char *cp;
1443 
1444             if ((cp = ok_vlook(newmail)) != NULL) { /* TODO on_tick_event! */
1445                struct stat st;
1446 
1447                if(mb.mb_type == MB_FILE){
1448                   if(!stat(mailname, &st) && st.st_size > mailsize)
1449 #if defined mx_HAVE_MAILDIR || defined mx_HAVE_IMAP
1450                   Jnewmail:
1451 #endif
1452                   {
1453                      u32 odid;
1454                      uz odot;
1455 
1456                      odot = P2UZ(dot - message);
1457                      odid = (n_pstate & n_PS_DID_PRINT_DOT);
1458 
1459                      mx_sigs_all_rele();
1460                      n = setfile(mailname,
1461                            (FEDIT_NEWMAIL |
1462                               ((mb.mb_perm & MB_DELE) ? 0 : FEDIT_RDONLY)));
1463                      mx_sigs_all_holdx();
1464 
1465                      if(n < 0) {
1466                         n_exit_status |= n_EXIT_ERR;
1467                         rv = FAL0;
1468                         break;
1469                      }
1470 #ifdef mx_HAVE_IMAP
1471                      if(mb.mb_type != MB_IMAP){
1472 #endif
1473                         dot = &message[odot];
1474                         n_pstate |= odid;
1475 #ifdef mx_HAVE_IMAP
1476                      }
1477 #endif
1478                   }
1479                }else{
1480 #if defined mx_HAVE_MAILDIR || defined mx_HAVE_IMAP
1481                   n = (cp != NULL && su_cs_cmp(cp, "nopoll"));
1482 #endif
1483 
1484 #ifdef mx_HAVE_MAILDIR
1485                   if(mb.mb_type == MB_MAILDIR){
1486                      if(n != 0)
1487                         goto Jnewmail;
1488                   }
1489 #endif
1490 #ifdef mx_HAVE_IMAP
1491                   if(mb.mb_type == MB_IMAP){
1492                      if(!n)
1493                         n = (cp != NULL && su_cs_cmp(cp, "noimap"));
1494 
1495                      if(imap_newmail(n) > (cp == NULL))
1496                         goto Jnewmail;
1497                   }
1498 #endif
1499                }
1500             }
1501          }
1502       }
1503 
1504       /* Read a line of commands and handle end of file specially */
1505       n_pstate |= n_PS_ERRORS_NEED_PRINT_ONCE;
1506 
1507       mx_fs_linepool_aquire(&gec.gec_line.s, &gec.gec_line.l);
1508       gec.gec_line_size = S(u32,gec.gec_line.l);
1509       /* C99 */{
1510          boole histadd;
1511 
1512          histadd = ((n_psonce & n_PSO_INTERACTIVE) &&
1513                !(n_pstate & (n_PS_ROBOT | n_PS_SOURCING)) &&
1514                 a_go_ctx->gc_inject == su_NIL); /* xxx really injection? */
1515          mx_sigs_all_rele();
1516          ASSERT(!gec.gec_ignerr);
1517          n = n_go_input(n_GO_INPUT_CTX_DEFAULT | n_GO_INPUT_NL_ESC, NULL,
1518                &gec.gec_line.s, &gec.gec_line.l, NULL, &histadd);
1519          mx_sigs_all_holdx();
1520 
1521          gec.gec_hist_flags = histadd ? a_GO_HIST_ADD : a_GO_HIST_NONE;
1522       }
1523       gec.gec_line_size = S(u32,gec.gec_line.l);
1524       gec.gec_line.l = S(u32,n);
1525 
1526       if(n < 0){
1527          mx_fs_linepool_release(gec.gec_line.s, gec.gec_line_size);
1528          if(!(n_pstate & n_PS_ROBOT) &&
1529                (n_psonce & n_PSO_INTERACTIVE) && ok_blook(ignoreeof) &&
1530                ++eofcnt < 4){
1531             fprintf(n_stdout, _("*ignoreeof* set, use `quit' to quit.\n"));
1532             n_go_input_clearerr();
1533             continue;
1534          }
1535          break;
1536       }
1537 
1538       n_pstate &= ~n_PS_HOOK_MASK;
1539       mx_sigs_all_rele();
1540       rv = a_go_evaluate(&gec);
1541       mx_sigs_all_holdx();
1542 
1543       n_pstate &= ~n_PS_ERRORS_NEED_PRINT_ONCE;
1544       switch(n_pstate & n_PS_ERR_EXIT_MASK){
1545       case n_PS_ERR_XIT: n_psonce |= n_PSO_XIT; break;
1546       case n_PS_ERR_QUIT: n_psonce |= n_PSO_QUIT; break;
1547       default: break;
1548       }
1549 
1550       if(gec.gec_hist_flags & a_GO_HIST_ADD){
1551          char const *cc, *ca;
1552 
1553          /* TODO history handling is terrible; this should pass the command
1554           * TODO evaluation context carrier all along the way, so that commands
1555           * TODO can alter the "add history" behaviour at will; also the
1556           * TODO arguments as passed into ARGV should be passed along to
1557           * TODO addhist, see *on-history-addition* for the why of this */
1558          cc = gec.gec_hist_cmd;
1559          ca = gec.gec_hist_args;
1560          if(cc != NULL && ca != NULL)
1561             cc = savecatsep(cc, ' ', ca);
1562          else if(ca != NULL)
1563             cc = ca;
1564          ASSERT(cc != NULL);
1565          mx_tty_addhist(cc, (n_GO_INPUT_CTX_DEFAULT |
1566             (gec.gec_hist_flags & a_GO_HIST_GABBY
1567                ? n_GO_INPUT_HIST_GABBY : n_GO_INPUT_NONE) |
1568             (gec.gec_hist_flags & a_GO_HIST_GABBY_ERROR
1569                ?  n_GO_INPUT_HIST_GABBY | n_GO_INPUT_HIST_ERROR
1570                : n_GO_INPUT_NONE)));
1571       }
1572 
1573       mx_fs_linepool_release(gec.gec_line.s, gec.gec_line_size);
1574 
1575       if((n_psonce & n_PSO_EXIT_MASK) || !rv)
1576          break;
1577    }
1578 
1579    a_go_cleanup(a_GO_CLEANUP_TEARDOWN | a_GO_CLEANUP_HOLDALLSIGS |
1580       (rv ? 0 : a_GO_CLEANUP_ERROR));
1581    mx_fs_linepool_cleanup(TRU1);
1582 
1583    mx_sigs_all_rele();
1584 
1585    NYD_OU;
1586    return rv;
1587 }
1588 
1589 FL void
n_go_input_clearerr(void)1590 n_go_input_clearerr(void){
1591    FILE *fp;
1592    NYD2_IN;
1593 
1594    fp = NULL;
1595 
1596    if(!(a_go_ctx->gc_flags & (a_GO_FORCE_EOF |
1597          a_GO_PIPE | a_GO_MACRO | a_GO_SPLICE)))
1598       fp = a_go_ctx->gc_file;
1599 
1600    if(fp != NULL){
1601       a_go_ctx->gc_flags &= ~a_GO_IS_EOF;
1602       clearerr(fp);
1603    }
1604    NYD2_OU;
1605 }
1606 
1607 FL void
n_go_input_force_eof(void)1608 n_go_input_force_eof(void){
1609    NYD2_IN;
1610    a_go_ctx->gc_flags |= a_GO_FORCE_EOF;
1611    NYD2_OU;
1612 }
1613 
1614 FL boole
n_go_input_is_eof(void)1615 n_go_input_is_eof(void){
1616    boole rv;
1617    NYD2_IN;
1618 
1619    rv = ((a_go_ctx->gc_flags & a_GO_IS_EOF) != 0);
1620    NYD2_OU;
1621    return rv;
1622 }
1623 
1624 FL boole
n_go_input_have_injections(void)1625 n_go_input_have_injections(void){
1626    boole rv;
1627    NYD2_IN;
1628 
1629    rv = (a_go_ctx->gc_inject != NULL);
1630    NYD2_OU;
1631    return rv;
1632 }
1633 
1634 FL void
n_go_input_inject(enum n_go_input_inject_flags giif,char const * buf,uz len)1635 n_go_input_inject(enum n_go_input_inject_flags giif, char const *buf,
1636       uz len){
1637    NYD_IN;
1638 
1639    if(len == UZ_MAX)
1640       len = su_cs_len(buf);
1641 
1642    if(UZ_MAX - VSTRUCT_SIZEOF(struct a_go_input_inject, gii_dat) -1 > len &&
1643          len > 0){
1644       struct a_go_input_inject *giip,  **giipp;
1645 
1646       mx_sigs_all_holdx();
1647 
1648       giip = n_alloc(VSTRUCT_SIZEOF(struct a_go_input_inject, gii_dat
1649             ) + 1 + len +1);
1650       giipp = &a_go_ctx->gc_inject;
1651       giip->gii_next = *giipp;
1652       giip->gii_commit = ((giif & n_GO_INPUT_INJECT_COMMIT) != 0);
1653       giip->gii_no_history = ((giif & n_GO_INPUT_INJECT_HISTORY) == 0);
1654       su_mem_copy(&giip->gii_dat[0], buf, len);
1655       giip->gii_dat[giip->gii_len = len] = '\0';
1656       *giipp = giip;
1657 
1658       mx_sigs_all_rele();
1659    }
1660    NYD_OU;
1661 }
1662 
1663 FL int
1664 (n_go_input)(enum n_go_input_flags gif, char const *prompt, char **linebuf,
1665       uz *linesize, char const *string, boole *histok_or_nil
1666       su_DBG_LOC_ARGS_DECL){
1667    /* TODO readline: linebuf pool!; n_go_input should return s64.
1668     * TODO This thing should be replaced by a(n) (stack of) event generator(s)
1669     * TODO and consumed by OnLineCompletedEvent listeners */
1670    struct n_string xprompt;
1671    FILE *ifile;
1672    char const *iftype;
1673    struct a_go_input_inject *giip;
1674    int nold, n;
1675    enum{
1676       a_NONE,
1677       a_HISTOK = 1u<<0,
1678       a_USE_PROMPT = 1u<<1,
1679       a_USE_MLE = 1u<<2,
1680       a_DIG_MSG_OVERLAY = 1u<<16
1681    } f;
1682    NYD2_IN;
1683 
1684    if(!(gif & n_GO_INPUT_HOLDALLSIGS))
1685       mx_sigs_all_holdx();
1686 
1687    f = a_NONE;
1688 
1689    if(a_go_ctx->gc_flags & a_GO_FORCE_EOF){
1690       a_go_ctx->gc_flags |= a_GO_IS_EOF;
1691       n = -1;
1692       goto jleave;
1693    }
1694 
1695    if(gif & n_GO_INPUT_FORCE_STDIN)
1696       goto jforce_stdin;
1697 
1698    /* Special case macro mode: never need to prompt, lines have always been
1699     * unfolded already; TODO we need on_line_completed event and producers! */
1700    if(a_go_ctx->gc_flags & a_GO_MACRO){
1701       if(*linebuf != NULL)
1702          n_free(*linebuf);
1703 
1704       /* Injection in progress?  Don't care about the autocommit state here */
1705       if(!(gif & n_GO_INPUT_DELAY_INJECTIONS) &&
1706             (giip = a_go_ctx->gc_inject) != NULL){
1707          a_go_ctx->gc_inject = giip->gii_next;
1708 
1709          /* Simply "reuse" allocation, copy string to front of it */
1710 jinject:
1711          *linesize = giip->gii_len;
1712          *linebuf = (char*)giip;
1713          su_mem_move(*linebuf, giip->gii_dat, giip->gii_len +1);
1714          iftype = "INJECTION";
1715       }else{
1716          if((*linebuf = a_go_ctx->gc_lines[a_go_ctx->gc_loff]) == NULL){
1717             *linesize = 0;
1718             a_go_ctx->gc_flags |= a_GO_IS_EOF;
1719             n = -1;
1720             goto jleave;
1721          }
1722 
1723          ++a_go_ctx->gc_loff;
1724          *linesize = su_cs_len(*linebuf);
1725          if(!(a_go_ctx->gc_flags & a_GO_MACRO_FREE_DATA))
1726             *linebuf = su_cs_dup_cbuf(*linebuf, *linesize, 0);
1727 
1728          iftype = ((a_go_ctx->gc_flags &
1729                   (a_GO_MACRO_X_OPTION | a_GO_MACRO_BLTIN_RC))
1730                ? "COMMAND-LINE"
1731                : (a_go_ctx->gc_flags & a_GO_MACRO_CMD) ? "CMD" : "MACRO");
1732       }
1733       n = (int)*linesize;
1734       n_pstate |= n_PS_READLINE_NL;
1735       goto jhave_dat;
1736    }
1737 
1738    if(!(gif & n_GO_INPUT_DELAY_INJECTIONS)){
1739       /* Injection in progress? */
1740       struct a_go_input_inject **giipp;
1741 
1742       giipp = &a_go_ctx->gc_inject;
1743 
1744       if((giip = *giipp) != NULL){
1745          *giipp = giip->gii_next;
1746 
1747          if(giip->gii_commit){
1748             if(*linebuf != NULL)
1749                n_free(*linebuf);
1750             if(!giip->gii_no_history)
1751                f |= a_HISTOK;
1752             goto jinject; /* (above) */
1753          }else{
1754             string = savestrbuf(giip->gii_dat, giip->gii_len);
1755             n_free(giip);
1756          }
1757       }
1758    }
1759 
1760 jforce_stdin:
1761    n_pstate &= ~n_PS_READLINE_NL;
1762    iftype = (!(n_psonce & n_PSO_STARTED) ? "LOAD"
1763           : (n_pstate & n_PS_SOURCING) ? "SOURCE" : "READ");
1764    if(!(n_pstate & n_PS_ROBOT) &&
1765          (n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)) ==
1766             (n_PSO_INTERACTIVE | n_PSO_STARTED))
1767       f |= a_HISTOK;
1768    if(!(f & a_HISTOK) || (gif & n_GO_INPUT_FORCE_STDIN))
1769       gif |= n_GO_INPUT_PROMPT_NONE;
1770    else{
1771       f |= a_USE_PROMPT;
1772       if(!ok_blook(line_editor_disable))
1773          f |= a_USE_MLE;
1774       else
1775          (void)n_string_creat_auto(&xprompt);
1776       if(prompt == NULL)
1777          gif |= n_GO_INPUT_PROMPT_EVAL;
1778    }
1779 
1780    /* Ensure stdout is flushed first anyway (partial lines, maybe?) */
1781    if((gif & n_GO_INPUT_PROMPT_NONE) && !(f & a_USE_MLE))
1782       fflush(n_stdout);
1783 
1784    if(gif & n_GO_INPUT_FORCE_STDIN){
1785       struct a_go_readctl_ctx *grcp;
1786       struct mx_dig_msg_ctx *dmcp;
1787 
1788       if((dmcp = mx_dig_msg_read_overlay) != NIL){
1789          ifile = dmcp->dmc_fp;
1790          f |= a_DIG_MSG_OVERLAY;
1791       }else if((grcp = n_readctl_read_overlay) == NULL ||
1792             (ifile = grcp->grc_fp) == NULL)
1793          ifile = n_stdin;
1794    }else
1795       ifile = a_go_ctx->gc_file;
1796    if(ifile == NULL){
1797       ASSERT((n_pstate & n_PS_COMPOSE_FORKHOOK) &&
1798          (a_go_ctx->gc_flags & a_GO_MACRO));
1799       ifile = n_stdin;
1800    }
1801 
1802    for(nold = n = 0;;){
1803       if(f & a_USE_MLE){
1804          ASSERT(ifile == n_stdin);
1805          if(string != NULL && (n = (int)su_cs_len(string)) > 0){
1806             if(*linesize > 0)
1807                *linesize += n +1;
1808             else
1809                *linesize = (uz)n + LINESIZE +1;
1810             *linebuf = su_MEM_REALLOC_LOCOR(*linebuf, *linesize,
1811                   su_DBG_LOC_ARGS_ORUSE);
1812            su_mem_copy(*linebuf, string, (uz)n +1);
1813          }
1814          string = NULL;
1815 
1816          mx_sigs_all_rele();
1817 
1818          n = (mx_tty_readline)(gif, prompt, linebuf, linesize, n, histok_or_nil
1819                su_DBG_LOC_ARGS_USE);
1820 
1821          mx_sigs_all_holdx();
1822 
1823          if(n < 0 && !ferror(ifile)) /* EOF never i guess */
1824             a_go_ctx->gc_flags |= a_GO_IS_EOF;
1825       }else{
1826          mx_sigs_all_rele();
1827 
1828          if(!(gif & n_GO_INPUT_PROMPT_NONE)){
1829             mx_tty_create_prompt(&xprompt, prompt, gif);
1830 
1831             if(xprompt.s_len > 0){
1832                fwrite(xprompt.s_dat, 1, xprompt.s_len, n_stdout);
1833                fflush(n_stdout);
1834             }
1835          }
1836 
1837          n = (readline_restart)(ifile, linebuf, linesize, n
1838                su_DBG_LOC_ARGS_USE);
1839 
1840          mx_sigs_all_holdx();
1841 
1842          if(n < 0 && !ferror(ifile))
1843             a_go_ctx->gc_flags |= a_GO_IS_EOF;
1844 
1845          if(n > 0 && nold > 0){
1846             char const *cp;
1847             int i;
1848 
1849             i = 0;
1850             cp = &(*linebuf)[nold];
1851             while(su_cs_is_space(*cp) && n - i >= nold)
1852                ++cp, ++i;
1853             if(i > 0){
1854                su_mem_move(&(*linebuf)[nold], cp, n - nold - i);
1855                n -= i;
1856                (*linebuf)[n] = '\0';
1857             }
1858          }
1859       }
1860       if(n <= 0)
1861          break;
1862 
1863       /* POSIX says:
1864        * TODO This does not take care for current shell quote mode!
1865        * TODO Thus "echo '\<NEWLINE HERE> bla' will never work
1866        *    An unquoted <backslash> at the end of a command line shall
1867        *    be discarded and the next line shall continue the command */
1868       if(!(gif & n_GO_INPUT_NL_ESC) || (*linebuf)[n - 1] != '\\')
1869          break;
1870 
1871       /* Definitely outside of quotes, thus quoting rules are so that an uneven
1872        * number of successive reverse solidus at EOL is a continuation */
1873       if(n > 1){
1874          uz i, j;
1875 
1876          for(j = 1, i = (uz)n - 1; i-- > 0; ++j)
1877             if((*linebuf)[i] != '\\')
1878                break;
1879          if(!(j & 1))
1880             break;
1881       }
1882       (*linebuf)[nold = --n] = '\0';
1883       gif |= n_GO_INPUT_NL_FOLLOW;
1884    }
1885    if(n < 0)
1886       goto jleave;
1887 
1888    (*linebuf)[*linesize = n] = '\0';
1889 
1890    if(f & a_USE_MLE)
1891       n_pstate |= n_PS_READLINE_NL;
1892    else if(n == 0 || su_cs_is_space(**linebuf))
1893       f &= ~a_HISTOK;
1894 
1895 jhave_dat:
1896    if(n_poption & n_PO_D_VVV)
1897       n_err(_("%s%s %d bytes <%s>\n"),
1898          iftype, (n_cnd_if_exists() == TRUM1 ? "?whiteout" : su_empty),
1899          n, *linebuf);
1900 jleave:
1901    if (n_pstate & n_PS_PSTATE_PENDMASK)
1902       a_go_update_pstate();
1903 
1904    /* TODO We need to special case a_GO_SPLICE, since that is not managed by us
1905     * TODO but only established from the outside and we need to drop this
1906     * TODO overlay context somehow; ditto DIG_MSG_OVERLAY */
1907    if(n < 0){
1908       if(f & a_DIG_MSG_OVERLAY)
1909          mx_dig_msg_read_overlay = NIL;
1910       if(a_go_ctx->gc_flags & a_GO_SPLICE)
1911          a_go_cleanup(a_GO_CLEANUP_TEARDOWN | a_GO_CLEANUP_HOLDALLSIGS);
1912    }
1913 
1914    if(histok_or_nil != NIL && !(f & a_HISTOK))
1915       *histok_or_nil = FAL0;
1916 
1917    if(!(gif & n_GO_INPUT_HOLDALLSIGS))
1918       mx_sigs_all_rele();
1919    NYD2_OU;
1920    return n;
1921 }
1922 
1923 FL char *
n_go_input_cp(enum n_go_input_flags gif,char const * prompt,char const * string)1924 n_go_input_cp(enum n_go_input_flags gif, char const *prompt,
1925       char const *string){
1926    struct n_sigman sm;
1927    boole histadd;
1928    uz linesize;
1929    char *linebuf, * volatile rv;
1930    int n;
1931    NYD2_IN;
1932 
1933    mx_fs_linepool_aquire(&linebuf, &linesize);
1934    rv = NIL;
1935 
1936    n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
1937    case 0:
1938       break;
1939    default:
1940       goto jleave;
1941    }
1942 
1943    histadd = TRU1;
1944    n = n_go_input(gif, prompt, &linebuf, &linesize, string, &histadd);
1945    if(n > 0 && *(rv = savestrbuf(linebuf, (uz)n)) != '\0' &&
1946          (gif & n_GO_INPUT_HIST_ADD) && (n_psonce & n_PSO_INTERACTIVE) &&
1947          histadd){
1948       ASSERT(!(gif & n_GO_INPUT_HIST_ERROR) || (gif & n_GO_INPUT_HIST_GABBY));
1949       mx_tty_addhist(rv, gif);
1950    }
1951 
1952    n_sigman_cleanup_ping(&sm);
1953 
1954 jleave:
1955    mx_fs_linepool_release(linebuf, linesize);
1956    NYD2_OU;
1957    n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
1958    return rv;
1959 }
1960 
1961 FL boole
n_go_load_rc(char const * name)1962 n_go_load_rc(char const *name){
1963    struct a_go_ctx *gcp;
1964    uz i;
1965    FILE *fip;
1966    boole rv;
1967    NYD_IN;
1968    ASSERT_NYD_EXEC(name != NIL, rv = FAL0);
1969 
1970    rv = TRU1;
1971 
1972    if((fip = mx_fs_open(name, "r")) == NIL){
1973       if(n_poption & n_PO_D_V)
1974          n_err(_("No such file to load: %s\n"), n_shexp_quote_cp(name, FAL0));
1975       goto jleave;
1976    }
1977 
1978    i = su_cs_len(name) +1;
1979    gcp = n_alloc(VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) + i);
1980    su_mem_set(gcp, 0, VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1981 
1982    gcp->gc_file = fip;
1983    gcp->gc_flags = a_GO_FREE | a_GO_FILE;
1984    su_mem_copy(gcp->gc_name, name, i);
1985 
1986    if(n_poption & n_PO_D_VV)
1987       n_err(_("Loading %s\n"), n_shexp_quote_cp(gcp->gc_name, FAL0));
1988    rv = a_go_load(gcp);
1989 jleave:
1990    NYD_OU;
1991    return rv;
1992 }
1993 
1994 FL boole
n_go_load_lines(boole injectit,char const ** lines,uz cnt)1995 n_go_load_lines(boole injectit, char const **lines, uz cnt){
1996    static char const a_name_x[] = "-X", a_name_bltin[] = "builtin RC file";
1997 
1998    union{
1999       boole rv;
2000       u64 align;
2001       char uf[VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
2002             MAX(sizeof(a_name_x), sizeof(a_name_bltin))];
2003    } b;
2004    char const *srcp, *xsrcp;
2005    char *cp;
2006    uz imax, i, len;
2007    boole nofail;
2008    struct a_go_ctx *gcp;
2009    NYD_IN;
2010 
2011    gcp = (void*)b.uf;
2012    su_mem_set(gcp, 0, VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2013 
2014    if(lines == NIL){
2015       su_mem_copy(gcp->gc_name, a_name_bltin, sizeof a_name_bltin);
2016       lines = C(char const**,a_go_bltin_rc_lines);
2017       cnt = a_GO_BLTIN_RC_LINES_CNT;
2018       gcp->gc_flags = a_GO_MACRO | a_GO_MACRO_BLTIN_RC |
2019             a_GO_SUPER_MACRO | a_GO_MACRO_FREE_DATA;
2020       nofail = TRUM1;
2021    }else if(!injectit){
2022       su_mem_copy(gcp->gc_name, a_name_x, sizeof a_name_x);
2023       gcp->gc_flags = a_GO_MACRO | a_GO_MACRO_X_OPTION |
2024             a_GO_SUPER_MACRO | a_GO_MACRO_FREE_DATA;
2025       nofail = FAL0;
2026    }else
2027       nofail = TRU1;
2028 
2029    /* The problem being that we want to support reverse solidus newline
2030     * escaping also within multiline -X, i.e., POSIX says:
2031     *    An unquoted <backslash> at the end of a command line shall
2032     *    be discarded and the next line shall continue the command
2033     * Therefore instead of "gcp->gc_lines = UNCONST(lines)", duplicate
2034     * the entire lines array and set _MACRO_FREE_DATA.
2035     * Likewise, for injections, we need to reverse the order. */
2036    imax = cnt + 1;
2037    gcp->gc_lines = n_alloc(sizeof(*gcp->gc_lines) * imax);
2038 
2039    /* For each of the input lines.. */
2040    for(i = len = 0, cp = NULL; cnt > 0;){
2041       boole keep;
2042       uz j;
2043 
2044       if((j = su_cs_len(srcp = *lines)) == 0){
2045          ++lines, --cnt;
2046          continue;
2047       }
2048 
2049       /* Separate one line from a possible multiline input string */
2050       if(nofail != TRUM1 && (xsrcp = su_mem_find(srcp, '\n', j)) != NIL){
2051          *lines = &xsrcp[1];
2052          j = P2UZ(xsrcp - srcp);
2053       }else
2054          ++lines, --cnt;
2055 
2056       /* The (separated) string may itself indicate soft newline escaping */
2057       if((keep = (srcp[j - 1] == '\\'))){
2058          uz xj, xk;
2059 
2060          /* Need an uneven number of reverse solidus */
2061          for(xk = 1, xj = j - 1; xj-- > 0; ++xk)
2062             if(srcp[xj] != '\\')
2063                break;
2064          if(xk & 1)
2065             --j;
2066          else
2067             keep = FAL0;
2068       }
2069 
2070       /* Strip any leading WS from follow lines, then */
2071       if(cp != NULL)
2072          while(j > 0 && su_cs_is_space(*srcp))
2073             ++srcp, --j;
2074 
2075       if(j > 0){
2076          if(i + 2 >= imax){ /* TODO need a vector (main.c, here, ++) */
2077             imax += 4;
2078             gcp->gc_lines = n_realloc(gcp->gc_lines, sizeof(*gcp->gc_lines) *
2079                   imax);
2080          }
2081          gcp->gc_lines[i] = cp = n_realloc(cp, len + j +1);
2082          su_mem_copy(&cp[len], srcp, j);
2083          cp[len += j] = '\0';
2084 
2085          if(!keep)
2086             ++i;
2087       }
2088       if(!keep)
2089          cp = NULL, len = 0;
2090    }
2091    if(cp != NULL){
2092       ASSERT(i + 1 < imax);
2093       gcp->gc_lines[i++] = cp;
2094    }
2095    gcp->gc_lines[i] = NULL;
2096 
2097    if(!injectit)
2098       b.rv = a_go_load(gcp);
2099    else{
2100       while(i > 0){
2101          n_go_input_inject(n_GO_INPUT_INJECT_COMMIT, cp = gcp->gc_lines[--i],
2102             UZ_MAX);
2103          n_free(cp);
2104       }
2105       n_free(gcp->gc_lines);
2106       ASSERT(nofail);
2107    }
2108 
2109    if(nofail)
2110       /* Program exit handling is a total mess! */
2111       b.rv = ((n_psonce & n_PSO_EXIT_MASK) == 0);
2112    NYD_OU;
2113    return b.rv;
2114 }
2115 
2116 FL int
c_source(void * v)2117 c_source(void *v){
2118    int rv;
2119    NYD_IN;
2120 
2121    rv = (a_go_file(*(char**)v, FAL0) == TRU1) ? 0 : 1;
2122    NYD_OU;
2123    return rv;
2124 }
2125 
2126 FL int
c_source_if(void * v)2127 c_source_if(void *v){ /* XXX obsolete?, support file tests in `if' etc.! */
2128    int rv;
2129    NYD_IN;
2130 
2131    rv = (a_go_file(*(char**)v, TRU1) == TRU1) ? 0 : 1;
2132    NYD_OU;
2133    return rv;
2134 }
2135 
2136 FL boole
n_go_macro(enum n_go_input_flags gif,char const * name,char ** lines,void (* on_finalize)(void *),void * finalize_arg)2137 n_go_macro(enum n_go_input_flags gif, char const *name, char **lines,
2138       void (*on_finalize)(void*), void *finalize_arg){
2139    struct a_go_ctx *gcp;
2140    uz i;
2141    int rv;
2142    sigset_t osigmask;
2143    NYD_IN;
2144 
2145    sigprocmask(SIG_BLOCK, NULL, &osigmask);
2146 
2147    gcp = n_alloc(VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
2148          (i = su_cs_len(name) +1));
2149    su_mem_set(gcp, 0, VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2150    gcp->gc_data.gdc_membag =
2151          su_mem_bag_create(&gcp->gc_data.gdc__membag_buf[0], 0);
2152 
2153    mx_sigs_all_holdx();
2154 
2155    gcp->gc_outer = a_go_ctx;
2156    gcp->gc_osigmask = osigmask;
2157    gcp->gc_flags = a_GO_FREE | a_GO_MACRO | a_GO_MACRO_FREE_DATA |
2158          ((!(a_go_ctx->gc_flags & a_GO_TYPE_MASK) ||
2159             (a_go_ctx->gc_flags & a_GO_SUPER_MACRO)) ? a_GO_SUPER_MACRO : 0) |
2160          ((gif & n_GO_INPUT_NO_XCALL) ? a_GO_XCALL_IS_CALL : 0);
2161    gcp->gc_lines = lines;
2162    gcp->gc_on_finalize = on_finalize;
2163    gcp->gc_finalize_arg = finalize_arg;
2164    su_mem_copy(gcp->gc_name, name, i);
2165 
2166    a_go_ctx = gcp;
2167    n_go_data = &gcp->gc_data;
2168    n_pstate |= n_PS_ROBOT;
2169    rv = a_go_event_loop(gcp, gif);
2170 
2171    /* Shall this enter a `xcall' stack avoidance optimization (loop)? */
2172    if(a_go_xcall != NULL){
2173       void *vp;
2174       struct mx_cmd_arg_ctx *cacp;
2175 
2176       if(a_go_xcall == (void*)-1)
2177          a_go_xcall = NULL;
2178       else if(((void const*)(cacp = a_go_xcall)->cac_indat) == gcp){
2179          /* Indicate that "our" (ex-) parent now hosts xcall optimization */
2180          a_go_ctx->gc_flags |= a_GO_XCALL_LOOP;
2181          while(a_go_xcall != NIL){
2182             mx_sigs_all_holdx();
2183 
2184             a_go_ctx->gc_flags &= ~a_GO_XCALL_LOOP_ERROR;
2185 
2186             vp = a_go_xcall;
2187             a_go_xcall = NIL;
2188             cacp = mx_cmd_arg_restore_from_heap(vp);
2189             n_free(vp);
2190 
2191             mx_sigs_all_rele();
2192 
2193             (void)c_call(cacp);
2194          }
2195          rv = ((a_go_ctx->gc_flags & a_GO_XCALL_LOOP_ERROR) == 0);
2196          a_go_ctx->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
2197       }
2198    }
2199    NYD_OU;
2200    return rv;
2201 }
2202 
2203 FL boole
n_go_command(enum n_go_input_flags gif,char const * cmd)2204 n_go_command(enum n_go_input_flags gif, char const *cmd){
2205    struct a_go_ctx *gcp;
2206    boole rv;
2207    uz i, ial;
2208    sigset_t osigmask;
2209    NYD_IN;
2210 
2211    sigprocmask(SIG_BLOCK, NULL, &osigmask);
2212 
2213    i = su_cs_len(cmd) +1;
2214    ial = Z_ALIGN(i);
2215    gcp = n_alloc(VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
2216          ial + 2*sizeof(char*));
2217    su_mem_set(gcp, 0, VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2218    gcp->gc_data.gdc_membag =
2219          su_mem_bag_create(&gcp->gc_data.gdc__membag_buf[0], 0);
2220 
2221    mx_sigs_all_holdx();
2222 
2223    gcp->gc_outer = a_go_ctx;
2224    gcp->gc_osigmask = osigmask;
2225    gcp->gc_flags = a_GO_FREE | a_GO_MACRO | a_GO_MACRO_CMD |
2226          ((!(a_go_ctx->gc_flags & a_GO_TYPE_MASK) ||
2227             (a_go_ctx->gc_flags & a_GO_SUPER_MACRO)) ? a_GO_SUPER_MACRO : 0);
2228    gcp->gc_lines = (void*)&gcp->gc_name[ial];
2229    su_mem_copy(gcp->gc_lines[0] = &gcp->gc_name[0], cmd, i);
2230    gcp->gc_lines[1] = NULL;
2231 
2232    a_go_ctx = gcp;
2233    n_go_data = &gcp->gc_data;
2234    n_pstate |= n_PS_ROBOT;
2235    rv = a_go_event_loop(gcp, gif);
2236    NYD_OU;
2237    return rv;
2238 }
2239 
2240 FL void
n_go_splice_hack(char const * cmd,FILE * new_stdin,FILE * new_stdout,u32 new_psonce,void (* on_finalize)(void *),void * finalize_arg)2241 n_go_splice_hack(char const *cmd, FILE *new_stdin, FILE *new_stdout,
2242       u32 new_psonce, void (*on_finalize)(void*), void *finalize_arg){
2243    struct a_go_ctx *gcp;
2244    uz i;
2245    sigset_t osigmask;
2246    NYD_IN;
2247 
2248    sigprocmask(SIG_BLOCK, NULL, &osigmask);
2249 
2250    gcp = n_alloc(VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
2251          (i = su_cs_len(cmd) +1));
2252    su_mem_set(gcp, 0, VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2253 
2254    mx_sigs_all_holdx();
2255 
2256    gcp->gc_outer = a_go_ctx;
2257    gcp->gc_osigmask = osigmask;
2258    gcp->gc_file = new_stdin;
2259    gcp->gc_flags = a_GO_FREE | a_GO_SPLICE | a_GO_DATACTX_INHERITED;
2260    gcp->gc_on_finalize = on_finalize;
2261    gcp->gc_finalize_arg = finalize_arg;
2262    gcp->gc_splice_stdin = n_stdin;
2263    gcp->gc_splice_stdout = n_stdout;
2264    gcp->gc_splice_psonce = n_psonce;
2265    su_mem_copy(gcp->gc_name, cmd, i);
2266 
2267    n_stdin = new_stdin;
2268    n_stdout = new_stdout;
2269    n_psonce = new_psonce;
2270    a_go_ctx = gcp;
2271    /* Do NOT touch n_go_data! */
2272    n_pstate |= n_PS_ROBOT;
2273 
2274    mx_sigs_all_rele();
2275    NYD_OU;
2276 }
2277 
2278 FL void
n_go_splice_hack_remove_after_jump(void)2279 n_go_splice_hack_remove_after_jump(void){
2280    a_go_cleanup(a_GO_CLEANUP_TEARDOWN);
2281 }
2282 
2283 FL boole
n_go_may_yield_control(void)2284 n_go_may_yield_control(void){ /* TODO this is a terrible hack */
2285    struct a_go_ctx *gcp;
2286    boole rv;
2287    NYD2_IN;
2288 
2289    rv = FAL0;
2290 
2291    /* Only when startup completed */
2292    if(!(n_psonce & n_PSO_STARTED))
2293       goto jleave;
2294    /* Only interactive or batch mode (assuming that is ok) */
2295    if(!(n_psonce & n_PSO_INTERACTIVE) && !(n_poption & n_PO_BATCH_FLAG))
2296       goto jleave;
2297 
2298    /* Not when running any hook */
2299    if(n_pstate & n_PS_HOOK_MASK)
2300       goto jleave;
2301 
2302    /* Traverse up the stack:
2303     * . not when controlled by a child process
2304     * TODO . not when there are pipes involved, we neither handle job control,
2305     * TODO   nor process groups, that is, controlling terminal acceptably
2306     * . not when sourcing a file */
2307    for(gcp = a_go_ctx; gcp != NULL; gcp = gcp->gc_outer){
2308       if(gcp->gc_flags & (a_GO_PIPE | a_GO_FILE | a_GO_SPLICE))
2309          goto jleave;
2310    }
2311 
2312    rv = TRU1;
2313 jleave:
2314    NYD2_OU;
2315    return rv;
2316 }
2317 
2318 FL int
c_eval(void * vp)2319 c_eval(void *vp){
2320    /* TODO HACK! `eval' should be nothing else but a command prefix, evaluate
2321     * TODO ARGV with shell rules, but if that is not possible then simply
2322     * TODO adjust argv/argc of "the CmdCtx" that we will have exec real cmd */
2323    struct a_go_eval_ctx gec;
2324    struct n_string s_b, *s;
2325    uz i, j;
2326    char const **argv, *cp;
2327    NYD_IN;
2328 
2329    argv = vp;
2330 
2331    for(j = i = 0; (cp = argv[i]) != NULL; ++i)
2332       j += su_cs_len(cp);
2333 
2334    s = n_string_creat_auto(&s_b);
2335    s = n_string_reserve(s, j);
2336 
2337    for(i = 0; (cp = argv[i]) != NULL; ++i){
2338       if(i > 0)
2339          s = n_string_push_c(s, ' ');
2340       s = n_string_push_cp(s, cp);
2341    }
2342 
2343    su_mem_set(&gec, 0, sizeof gec);
2344    gec.gec_line.s = n_string_cp(s);
2345    gec.gec_line.l = s->s_len;
2346    if(n_poption & n_PO_D_VV)
2347       n_err(_("EVAL %" PRIuZ " bytes <%s>\n"), gec.gec_line.l, gec.gec_line.s);
2348    (void)/* XXX */a_go_evaluate(&gec);
2349 
2350    NYD_OU;
2351    return (a_go_xcall != NULL ? 0 : n_pstate_ex_no);
2352 }
2353 
2354 FL int
c_xcall(void * vp)2355 c_xcall(void *vp){
2356    int rv;
2357    struct a_go_ctx *gcp;
2358    NYD2_IN;
2359 
2360    /* The context can only be a macro context, except that possibly a single
2361     * level of `eval' (TODO: yet) was used to double-expand our arguments */
2362    if((gcp = a_go_ctx)->gc_flags & a_GO_MACRO_CMD)
2363       gcp = gcp->gc_outer;
2364    if((gcp->gc_flags & (a_GO_MACRO | a_GO_MACRO_X_OPTION |
2365          a_GO_MACRO_BLTIN_RC | a_GO_MACRO_CMD)) != a_GO_MACRO){
2366       if(n_poption & n_PO_D_V)
2367          n_err(_("xcall: can only be used inside a macro, using `call'\n"));
2368       rv = c_call(vp);
2369       goto jleave;
2370    }
2371 
2372    /* Try to roll up the stack as much as possible.
2373     * See a_GO_XCALL_LOOP flag description for more */
2374    if(!(gcp->gc_flags & a_GO_XCALL_IS_CALL) && gcp->gc_outer != NULL){
2375       if(gcp->gc_outer->gc_flags & a_GO_XCALL_LOOP)
2376          gcp = gcp->gc_outer;
2377    }else{
2378       /* Otherwise this macro is "invoked from the top level", in which case we
2379        * silently act as if we were `call'... */
2380       rv = c_call(vp);
2381       /* ...which means we must ensure the rest of the macro that was us
2382        * doesn't become evaluated! */
2383       a_go_xcall = (void*)-1;
2384       goto jleave;
2385    }
2386 
2387    /* C99 */{
2388       struct mx_cmd_arg_ctx *cacp;
2389 
2390       cacp = mx_cmd_arg_save_to_heap(vp);
2391       cacp->cac_indat = (char*)gcp;
2392       a_go_xcall = cacp;
2393    }
2394    rv = 0;
2395 jleave:
2396    NYD2_OU;
2397    return rv;
2398 }
2399 
2400 FL int
c_exit(void * vp)2401 c_exit(void *vp){
2402    char const **argv;
2403    NYD_IN;
2404 
2405    if(*(argv = vp) != NULL && (su_idec_s32_cp(&n_exit_status, *argv, 0, NULL) &
2406             (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
2407          ) != su_IDEC_STATE_CONSUMED)
2408       n_exit_status |= n_EXIT_ERR;
2409 
2410    if(n_pstate & n_PS_COMPOSE_FORKHOOK){ /* TODO sic */
2411       fflush(NULL);
2412       _exit(n_exit_status);
2413    }else if(n_pstate & n_PS_COMPOSE_MODE) /* XXX really.. */
2414       n_err(_("exit: delayed until compose mode is left\n")); /* XXX ..log? */
2415    n_psonce |= n_PSO_XIT;
2416    NYD_OU;
2417    return 0;
2418 }
2419 
2420 FL int
c_quit(void * vp)2421 c_quit(void *vp){
2422    char const **argv;
2423    NYD_IN;
2424 
2425    if(*(argv = vp) != NULL && (su_idec_s32_cp(&n_exit_status, *argv, 0, NULL) &
2426             (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
2427          ) != su_IDEC_STATE_CONSUMED)
2428       n_exit_status |= n_EXIT_ERR;
2429 
2430    if(n_pstate & n_PS_COMPOSE_FORKHOOK){ /* TODO sic */
2431       fflush(NULL);
2432       _exit(n_exit_status);
2433    }else if(n_pstate & n_PS_COMPOSE_MODE) /* XXX really.. */
2434       n_err(_("quit: delayed until compose mode is left\n")); /* XXX ..log? */
2435    n_psonce |= n_PSO_QUIT;
2436    NYD_OU;
2437    return 0;
2438 }
2439 
2440 FL int
c_readctl(void * vp)2441 c_readctl(void *vp){
2442    /* TODO We would need OnForkEvent and then simply remove some internal
2443     * TODO management; we don't have this, therefore we need global
2444     * TODO n_readctl_read_overlay to be accessible via =NULL, and to make that
2445     * TODO work in turn we need an instance for default STDIN!  Sigh. */
2446    static union{
2447       u64 alignme;
2448       u8 buf[VSTRUCT_SIZEOF(struct a_go_readctl_ctx, grc_name)+1 +1];
2449    } a;
2450    static struct a_go_readctl_ctx *a_stdin;
2451 
2452    struct a_go_readctl_ctx *grcp;
2453    char const *emsg;
2454    enum{
2455       a_NONE = 0,
2456       a_ERR = 1u<<0,
2457       a_SET = 1u<<1,
2458       a_CREATE = 1u<<2,
2459       a_REMOVE = 1u<<3
2460    } f;
2461    struct mx_cmd_arg *cap;
2462    struct mx_cmd_arg_ctx *cacp;
2463    NYD_IN;
2464 
2465    if(a_stdin == NULL){
2466       a_stdin = (struct a_go_readctl_ctx*)(void*)a.buf;
2467       a_stdin->grc_name[0] = '-';
2468       n_readctl_read_overlay = a_stdin;
2469    }
2470 
2471    n_pstate_err_no = su_ERR_NONE;
2472    cacp = vp;
2473    cap = cacp->cac_arg;
2474 
2475    if(cacp->cac_no == 0 ||
2476          su_cs_starts_with_case("show", cap->ca_arg.ca_str.s))
2477       goto jshow;
2478    else if(su_cs_starts_with_case("set", cap->ca_arg.ca_str.s))
2479       f = a_SET;
2480    else if(su_cs_starts_with_case("create", cap->ca_arg.ca_str.s))
2481       f = a_CREATE;
2482    else if(su_cs_starts_with_case("remove", cap->ca_arg.ca_str.s))
2483       f = a_REMOVE;
2484    else{
2485       emsg = N_("readctl: invalid subcommand: %s\n");
2486       goto jeinval_quote;
2487    }
2488 
2489    if(cacp->cac_no == 1){ /* TODO better option parser <> subcommand */
2490       n_err(_("readctl: %s: requires argument\n"), cap->ca_arg.ca_str.s);
2491       goto jeinval;
2492    }
2493    cap = cap->ca_next;
2494 
2495    /* - is special TODO unfortunately also regarding storage */
2496    if(cap->ca_arg.ca_str.l == 1 && *cap->ca_arg.ca_str.s == '-'){
2497       if(f & (a_CREATE | a_REMOVE)){
2498          n_err(_("readctl: cannot create nor remove -\n"));
2499          goto jeinval;
2500       }
2501       n_readctl_read_overlay = a_stdin;
2502       goto jleave;
2503    }
2504 
2505    /* Try to find a yet existing instance */
2506    if((grcp = n_readctl_read_overlay) != NULL){
2507       for(; grcp != NULL; grcp = grcp->grc_next)
2508          if(!su_cs_cmp(grcp->grc_name, cap->ca_arg.ca_str.s))
2509             goto jfound;
2510       for(grcp = n_readctl_read_overlay; (grcp = grcp->grc_last) != NULL;)
2511          if(!su_cs_cmp(grcp->grc_name, cap->ca_arg.ca_str.s))
2512             goto jfound;
2513    }
2514 
2515    if(f & (a_SET | a_REMOVE)){
2516       emsg = N_("readctl: no such channel: %s\n");
2517       goto jeinval_quote;
2518    }
2519 
2520 jfound:
2521    if(f & a_SET)
2522       n_readctl_read_overlay = grcp;
2523    else if(f & a_REMOVE){
2524       if(n_readctl_read_overlay == grcp)
2525          n_readctl_read_overlay = a_stdin;
2526 
2527       if(grcp->grc_last != NULL)
2528          grcp->grc_last->grc_next = grcp->grc_next;
2529       if(grcp->grc_next != NULL)
2530          grcp->grc_next->grc_last = grcp->grc_last;
2531       fclose(grcp->grc_fp);
2532       n_free(grcp);
2533    }else{
2534       FILE *fp;
2535       uz elen;
2536       s32 fd;
2537 
2538       if(grcp != NULL){
2539          n_err(_("readctl: channel already exists: %s\n"), /* TODO reopen */
2540             n_shexp_quote_cp(cap->ca_arg.ca_str.s, FAL0));
2541          n_pstate_err_no = su_ERR_EXIST;
2542          f = a_ERR;
2543          goto jleave;
2544       }
2545 
2546       if((su_idec_s32_cp(&fd, cap->ca_arg.ca_str.s, 0, NULL
2547                ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
2548             ) != su_IDEC_STATE_CONSUMED){
2549          if((emsg = fexpand(cap->ca_arg.ca_str.s, (FEXP_LOCAL_FILE |
2550                FEXP_NVAR))) == NIL){
2551             emsg = N_("readctl: cannot expand filename %s\n");
2552             goto jeinval_quote;
2553          }
2554          fd = -1;
2555          elen = su_cs_len(emsg);
2556          fp = mx_fs_open(emsg, "&r");
2557       }else if(fd == STDIN_FILENO || fd == STDOUT_FILENO ||
2558             fd == STDERR_FILENO){
2559          emsg = N_("readctl: create: standard descriptors not allowed: %s\n");
2560          goto jeinval_quote;
2561       }else{
2562          /* xxx Avoid */
2563          if(!mx_FS_FD_CLOEXEC_SET(fd)){
2564             emsg = N_("readctl: create: "
2565                   "cannot set close-on-exec flag for: %s\n");
2566             goto jeinval_quote;
2567          }
2568          emsg = NIL;
2569          elen = 0;
2570          fp = fdopen(fd, "r");
2571       }
2572 
2573       if(fp != NULL){
2574          uz i;
2575 
2576          if((i = UZ_MAX - elen) <= cap->ca_arg.ca_str.l ||
2577                (i -= cap->ca_arg.ca_str.l) <=
2578                   VSTRUCT_SIZEOF(struct a_go_readctl_ctx, grc_name) +2){
2579             fclose(fp);
2580             n_err(_("readctl: failed to create storage for %s\n"),
2581                cap->ca_arg.ca_str.s);
2582             n_pstate_err_no = su_ERR_OVERFLOW;
2583             f = a_ERR;
2584             goto jleave;
2585          }
2586 
2587          grcp = n_alloc(VSTRUCT_SIZEOF(struct a_go_readctl_ctx, grc_name) +
2588                cap->ca_arg.ca_str.l +1 + elen +1);
2589          grcp->grc_last = NULL;
2590          if((grcp->grc_next = n_readctl_read_overlay) != NULL)
2591             grcp->grc_next->grc_last = grcp;
2592          n_readctl_read_overlay = grcp;
2593          grcp->grc_fp = fp;
2594          grcp->grc_fd = fd;
2595          su_mem_copy(grcp->grc_name, cap->ca_arg.ca_str.s,
2596             cap->ca_arg.ca_str.l +1);
2597          if(elen == 0)
2598             grcp->grc_expand = NULL;
2599          else{
2600             char *cp;
2601 
2602             grcp->grc_expand = cp = &grcp->grc_name[cap->ca_arg.ca_str.l +1];
2603             su_mem_copy(cp, emsg, ++elen);
2604          }
2605       }else{
2606          emsg = N_("readctl: failed to create file for %s\n");
2607          goto jeinval_quote;
2608       }
2609    }
2610 
2611 jleave:
2612    NYD_OU;
2613    return (f & a_ERR) ? 1 : 0;
2614 jeinval_quote:
2615    n_err(V_(emsg), n_shexp_quote_cp(cap->ca_arg.ca_str.s, FAL0));
2616 jeinval:
2617    n_pstate_err_no = su_ERR_INVAL;
2618    f = a_ERR;
2619    goto jleave;
2620 
2621 jshow:
2622    if((grcp = n_readctl_read_overlay) == NULL)
2623       fprintf(n_stdout, _("readctl: no channels registered\n"));
2624    else{
2625       while(grcp->grc_last != NULL)
2626          grcp = grcp->grc_last;
2627 
2628       fprintf(n_stdout, _("readctl: registered channels:\n"));
2629       for(; grcp != NULL; grcp = grcp->grc_next)
2630          fprintf(n_stdout, _("%c%s %s%s%s%s\n"),
2631             (grcp == n_readctl_read_overlay ? '*' : ' '),
2632             (grcp->grc_fd != -1 ? _("descriptor") : _("name")),
2633             n_shexp_quote_cp(grcp->grc_name, FAL0),
2634             (grcp->grc_expand != NULL ? " (" : n_empty),
2635             (grcp->grc_expand != NULL ? grcp->grc_expand : n_empty),
2636             (grcp->grc_expand != NULL ? ")" : n_empty));
2637    }
2638    f = a_NONE;
2639    goto jleave;
2640 }
2641 
2642 #include "su/code-ou.h"
2643 /* s-it-mode */
2644