"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.19/src/mx/go.c" (26 Apr 2020, 83897 Bytes) of package /linux/misc/s-nail-14.9.19.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.18_vs_14.9.19.

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