"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.7/go.c" (16 Feb 2018, 73968 Bytes) of package /linux/misc/s-nail-14.9.7.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.6_vs_14.9.7.

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