"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.11/go.c" (8 Aug 2018, 75593 Bytes) of package /linux/misc/s-nail-14.9.11.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "go.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 14.9.10_vs_14.9.11.

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