"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.10/go.c" (25 Mar 2018, 74112 Bytes) of package /linux/misc/s-nail-14.9.10.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.9_vs_14.9.10.

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