"Fossies" - the Fresh Open Source Software Archive

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


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

    1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
    2  *@ Account, macro and variable handling; `vexpr' and `vpospar'.
    3  *@ HOWTO add a new non-dynamic boolean or value option:
    4  *@ - add an entry to nail.h:enum okeys
    5  *@ - run make-okey-map.pl (which is highly related..)
    6  *@ - update the manual!
    7  *@ TODO . `localopts' should act like an automatic permanent `scope' command
    8  *@ TODO    modifier!  We need an OnScopeLeaveEvent, then.
    9  *@ TODO   Also see the a_GO_SPLICE comment in go.c.
   10  *@ TODO . Optimize: with the dynamic hashmaps, and the object based approach
   11  *@ TODO   it should become possible to strip down the implementation again.
   12  *@ TODO   E.g., FREEZE is much too complicated: use an overlay object ptr,
   13  *@ TODO   UNLIKELY() it, and add a OnProgramStartupCompletedEvent to
   14  *@ TODO   incorporate what it tracks, then drop it.  Etc.
   15  *@ TODO   Global -> Scope -> Local, all "overlay" objects.
   16  *
   17  * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
   18  * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
   19  * SPDX-License-Identifier: BSD-3-Clause
   20  */
   21 /*
   22  * Copyright (c) 1980, 1993
   23  *      The Regents of the University of California.  All rights reserved.
   24  *
   25  * Redistribution and use in source and binary forms, with or without
   26  * modification, are permitted provided that the following conditions
   27  * are met:
   28  * 1. Redistributions of source code must retain the above copyright
   29  *    notice, this list of conditions and the following disclaimer.
   30  * 2. Redistributions in binary form must reproduce the above copyright
   31  *    notice, this list of conditions and the following disclaimer in the
   32  *    documentation and/or other materials provided with the distribution.
   33  * 3. Neither the name of the University nor the names of its contributors
   34  *    may be used to endorse or promote products derived from this software
   35  *    without specific prior written permission.
   36  *
   37  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
   38  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   39  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   40  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
   41  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   42  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   43  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   44  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   45  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   46  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   47  * SUCH DAMAGE.
   48  */
   49 #undef n_FILE
   50 #define n_FILE accmacvar
   51 
   52 #ifndef HAVE_AMALGAMATION
   53 # include "nail.h"
   54 #endif
   55 
   56 #if !defined HAVE_SETENV && !defined HAVE_PUTENV
   57 # error Exactly one of HAVE_SETENV and HAVE_PUTENV
   58 #endif
   59 
   60 /* Positional parameter maximum (macro arguments, `vexpr' "splitifs") */
   61 #define a_AMV_POSPAR_MAX SI16_MAX
   62 
   63 /* Special "pseudo macro" that stabs you from the back */
   64 #define a_AMV_MACKY_MACK ((struct a_amv_mac*)-1)
   65 
   66 /* Note: changing the hash function must be reflected in `vexpr' "hash",
   67  * because that is used by the hashtable creator scripts! */
   68 #define a_AMV_PRIME HSHSIZE
   69 #define a_AMV_NAME2HASH(N) n_torek_hash(N)
   70 #define a_AMV_HASH2PRIME(H) ((H) % a_AMV_PRIME)
   71 
   72 enum a_amv_mac_flags{
   73    a_AMV_MF_NONE = 0,
   74    a_AMV_MF_ACCOUNT = 1u<<0,  /* This macro is an `account' */
   75    a_AMV_MF_TYPE_MASK = a_AMV_MF_ACCOUNT,
   76    a_AMV_MF_UNDEF = 1u<<1,    /* Unlink after lookup */
   77    a_AMV_MF_DELETE = 1u<<7,   /* Delete in progress, free once refcnt==0 */
   78    a_AMV_MF__MAX = 0xFFu
   79 };
   80 
   81 enum a_amv_loflags{
   82    a_AMV_LF_NONE = 0,
   83    a_AMV_LF_SCOPE = 1u<<0,          /* Current scope `localopts' on */
   84    a_AMV_LF_SCOPE_FIXATE = 1u<<1,   /* Ditto, but fixated */
   85    a_AMV_LF_SCOPE_MASK = a_AMV_LF_SCOPE | a_AMV_LF_SCOPE_FIXATE,
   86    a_AMV_LF_CALL = 1u<<2,           /* `localopts' on for `call'ed scopes */
   87    a_AMV_LF_CALL_FIXATE = 1u<<3,    /* Ditto, but fixated */
   88    a_AMV_LF_CALL_MASK = a_AMV_LF_CALL | a_AMV_LF_CALL_FIXATE,
   89    a_AMV_LF_CALL_TO_SCOPE_SHIFT = 2
   90 };
   91 
   92 /* make-okey-map.pl ensures that _VIRT implies _RDONLY and _NODEL, and that
   93  * _IMPORT implies _ENV; it doesn't verify anything...
   94  * More description at nail.h:enum okeys */
   95 enum a_amv_var_flags{
   96    a_AMV_VF_NONE = 0,
   97 
   98    /* The basic set of flags, also present in struct a_amv_var_map.avm_flags */
   99    a_AMV_VF_BOOL = 1u<<0,     /* ok_b_* */
  100    a_AMV_VF_CHAIN = 1u<<1,    /* Has -HOST and/or -USER@HOST variants */
  101    a_AMV_VF_VIRT = 1u<<2,     /* "Stateless" automatic variable */
  102    a_AMV_VF_VIP = 1u<<3,      /* Wants _var_check_vips() evaluation */
  103    a_AMV_VF_RDONLY = 1u<<4,   /* May not be set by user */
  104    a_AMV_VF_NODEL = 1u<<5,    /* May not be deleted */
  105    a_AMV_VF_I3VAL = 1u<<6,    /* Has an initial value */
  106    a_AMV_VF_DEFVAL = 1u<<7,   /* Has a default value */
  107    a_AMV_VF_IMPORT = 1u<<8,   /* Import ONLY from env (pre n_PSO_STARTED) */
  108    a_AMV_VF_ENV = 1u<<9,      /* Update environment on change */
  109    a_AMV_VF_NOLOPTS = 1u<<10, /* May not be tracked by `localopts' */
  110    a_AMV_VF_NOTEMPTY = 1u<<11, /* May not be assigned an empty value */
  111    /* TODO _VF_NUM, _VF_POSNUM: we also need 64-bit limit numbers! */
  112    a_AMV_VF_NUM = 1u<<12,     /* Value must be a 32-bit number */
  113    a_AMV_VF_POSNUM = 1u<<13,  /* Value must be positive 32-bit number */
  114    a_AMV_VF_LOWER = 1u<<14,   /* Values will be stored in lowercase version */
  115    a_AMV_VF_OBSOLETE = 1u<<15, /* Is obsolete? */
  116    a_AMV_VF__MASK = (1u<<(15+1)) - 1,
  117 
  118    /* Extended flags, not part of struct a_amv_var_map.avm_flags */
  119    /* This flag indicates the instance is actually a variant of a _VF_CHAIN,
  120     * it thus uses the a_amv_var_map of the base variable, but it is not the
  121     * base itself and therefore care must be taken */
  122    a_AMV_VF_EXT_CHAIN = 1u<<22,
  123    a_AMV_VF_EXT_LOCAL = 1u<<23,        /* `local' */
  124    a_AMV_VF_EXT_LINKED = 1u<<24,       /* `environ' link'ed */
  125    a_AMV_VF_EXT_FROZEN = 1u<<25,       /* Has been set by -S,.. */
  126    a_AMV_VF_EXT_FROZEN_UNSET = 1u<<26, /* ..and was used to unset a variable */
  127    a_AMV_VF_EXT__FROZEN_MASK = a_AMV_VF_EXT_FROZEN | a_AMV_VF_EXT_FROZEN_UNSET,
  128    a_AMV_VF_EXT__MASK = (1u<<(26+1)) - 1
  129 };
  130 
  131 enum a_amv_var_lookup_flags{
  132    a_AMV_VLOOK_NONE = 0,
  133    a_AMV_VLOOK_LOCAL = 1u<<0,       /* Query `local' layer first */
  134    a_AMV_VLOOK_LOCAL_ONLY = 1u<<1,  /* MUST be a `local' variable */
  135    /* Do not allocate new var for _I3VAL, see _var_lookup() for more */
  136    a_AMV_VLOOK_I3VAL_NONEW = 1u<<2,
  137    a_AMV_VLOOK_I3VAL_NONEW_REPORT = 1u<<3
  138 };
  139 
  140 enum a_amv_var_setclr_flags{
  141    a_AMV_VSETCLR_NONE = 0,
  142    a_AMV_VSETCLR_LOCAL = 1u<<0,     /* Use `local' variables only */
  143    /* XXX Maybe something for only non-local? */
  144    a_AMV_VSETCLR_ENV = 1u<<1        /* `environ' or otherwise environ */
  145 };
  146 
  147 /* We support some special parameter names for one(+)-letter variable names;
  148  * note these have counterparts in the code that manages shell expansion!
  149  * All these special variables are solely backed by n_var_vlook(), and besides
  150  * there is only a_amv_var_revlookup() which knows about them */
  151 enum a_amv_var_special_category{
  152    a_AMV_VSC_NONE,      /* Normal variable, no special treatment */
  153    a_AMV_VSC_GLOBAL,    /* ${[?!]} are specially mapped, but global */
  154    a_AMV_VSC_MULTIPLEX, /* ${^.*} circumflex accent multiplexer */
  155    a_AMV_VSC_POSPAR,    /* ${[1-9][0-9]*} positional parameters */
  156    a_AMV_VSC_POSPAR_ENV /* ${[*@#]} positional parameter support variables */
  157 };
  158 
  159 enum a_amv_var_special_type{
  160    /* _VSC_GLOBAL */
  161    a_AMV_VST_QM,     /* ? */
  162    a_AMV_VST_EM,     /* ! */
  163    /* _VSC_MULTIPLEX */
  164    /* This is special in that it is a multiplex indicator, the ^ is followed by
  165     * a normal variable */
  166    a_AMV_VST_CACC,   /* ^ (circumflex accent) */
  167    /* _VSC_POSPAR_ENV */
  168    a_AMV_VST_STAR,   /* * */
  169    a_AMV_VST_AT,     /* @ */
  170    a_AMV_VST_NOSIGN  /* # */
  171 };
  172 
  173 enum a_amv_var_vip_mode{
  174    a_AMV_VIP_SET_PRE,
  175    a_AMV_VIP_SET_POST,
  176    a_AMV_VIP_CLEAR
  177 };
  178 
  179 struct a_amv_pospar{
  180    ui16_t app_maxcount;    /* == slots in .app_dat */
  181    ui16_t app_count;       /* Maximum a_AMV_POSPAR_MAX */
  182    ui16_t app_idx;         /* `shift' moves this one, decs .app_count */
  183    bool_t app_not_heap;    /* .app_dat stuff not dynamically allocated */
  184    ui8_t app__dummy[1];
  185    char const **app_dat;   /* NULL terminated (for "$@" explosion support) */
  186 };
  187 n_CTA(a_AMV_POSPAR_MAX <= SI16_MAX, "Limit exceeds datatype capabilities");
  188 
  189 struct a_amv_mac{
  190    struct a_amv_mac *am_next;
  191    ui32_t am_maxlen;             /* of any line in .am_line_dat */
  192    ui32_t am_line_cnt;           /* of *.am_line_dat (but NULL terminated) */
  193    struct a_amv_mac_line **am_line_dat; /* TODO use deque? */
  194    struct a_amv_var *am_lopts;   /* `localopts' unroll list */
  195    ui32_t am_refcnt;             /* 0-based for `un{account,define}' purposes */
  196    ui8_t am_flags;               /* enum a_amv_mac_flags */
  197    char am_name[n_VFIELD_SIZE(3)]; /* of this macro */
  198 };
  199 n_CTA(a_AMV_MF__MAX <= UI8_MAX, "Enumeration excesses storage datatype");
  200 
  201 struct a_amv_mac_line{
  202    ui32_t aml_len;
  203    ui32_t aml_prespc;   /* Number of leading SPACEs, for display purposes */
  204    char aml_dat[n_VFIELD_SIZE(0)];
  205 };
  206 
  207 struct a_amv_mac_call_args{
  208    char const *amca_name;        /* For MACKY_MACK, this is *0*! */
  209    struct a_amv_mac *amca_amp;   /* "const", but for am_refcnt */
  210    struct a_amv_var **amca_unroller;
  211    void (*amca_hook_pre)(void *);
  212    void *amca_hook_arg;
  213    ui8_t amca_loflags;
  214    bool_t amca_ps_hook_mask;
  215    bool_t amca_no_xcall;         /* We want n_GO_INPUT_NO_XCALL for this */
  216    ui8_t amca__pad[5];
  217    struct a_amv_var *(*amca_local_vars)[a_AMV_PRIME]; /* `local's, or NULL */
  218    struct a_amv_pospar amca_pospar;
  219 };
  220 
  221 struct a_amv_lostack{
  222    struct a_amv_lostack *as_global_saved; /* Saved global XXX due to jump */
  223    struct a_amv_mac_call_args *as_amcap;
  224    struct a_amv_lostack *as_up;  /* Outer context */
  225    struct a_amv_var *as_lopts;
  226    ui8_t as_loflags;             /* enum a_amv_mac_loflags */
  227    ui8_t avs__pad[7];
  228 };
  229 
  230 struct a_amv_var{
  231    struct a_amv_var *av_link;
  232    char *av_value;
  233 #ifdef HAVE_PUTENV
  234    char *av_env;              /* Actively managed putenv(3) memory, or NULL */
  235 #endif
  236    ui32_t av_flags;           /* enum a_amv_var_flags inclusive extended bits */
  237    char av_name[n_VFIELD_SIZE(4)];
  238 };
  239 n_CTA(a_AMV_VF_EXT__MASK <= UI32_MAX, "Enumeration excesses storage datatype");
  240 
  241 /* After inclusion of gen-okeys.h we assert keyoff fits in 16-bit */
  242 struct a_amv_var_map{
  243    ui32_t avm_hash;
  244    ui16_t avm_keyoff;
  245    ui16_t avm_flags;    /* enum a_amv_var_flags without extended bits */
  246 };
  247 n_CTA(a_AMV_VF__MASK <= UI16_MAX, "Enumeration excesses storage datatype");
  248 
  249 /* XXX Since there is no indicator character used for variable chains, we just
  250  * XXX cannot do better than using some s....y detection.
  251  * The length of avcmb_prefix is highly hardwired with make-okey-map.pl etc. */
  252 struct a_amv_var_chain_map_bsrch{
  253    char avcmb_prefix[4];
  254    ui16_t avcmb_chain_map_off;
  255    ui16_t avcmb_chain_map_eokey; /* This is an enum okey */
  256 };
  257 
  258 /* Use 16-bit for enum okeys, which should always be sufficient; all around
  259  * here we use 32-bit for it instead, but that owed to faster access (?) */
  260 struct a_amv_var_chain_map{
  261    ui16_t avcm_keyoff;
  262    ui16_t avcm_okey;
  263 };
  264 n_CTA(n_OKEYS_MAX <= UI16_MAX, "Enumeration excesses storage datatype");
  265 
  266 struct a_amv_var_virt{
  267    ui32_t avv_okey;
  268    ui8_t avv__dummy[4];
  269    struct a_amv_var const *avv_var;
  270 };
  271 
  272 struct a_amv_var_defval{
  273    ui32_t avdv_okey;
  274    ui8_t avdv__pad[4];
  275    char const *avdv_value; /* Only for !BOOL (otherwise plain existence) */
  276 };
  277 
  278 struct a_amv_var_carrier{
  279    char const *avc_name;
  280    ui32_t avc_hash;
  281    ui32_t avc_prime;
  282    struct a_amv_var *avc_var;
  283    struct a_amv_var_map const *avc_map;
  284    enum okeys avc_okey;
  285    bool_t avc_is_chain_variant;  /* Base is a chain, this a variant thereof */
  286    ui8_t avc_special_cat;
  287    /* Numerical parameter name if .avc_special_cat=a_AMV_VSC_POSPAR,
  288     * otherwise a enum a_amv_var_special_type */
  289    ui16_t avc_special_prop;
  290 };
  291 
  292 /* Include constant make-okey-map.pl output, and the generated version data */
  293 #include <gen-version.h>
  294 #include <gen-okeys.h>
  295 
  296 /* As promised above, CTAs to protect our structures */
  297 n_CTA(a_AMV_VAR_NAME_KEY_MAXOFF <= UI16_MAX,
  298    "Enumeration excesses storage datatype");
  299 
  300 /* The currently active account */
  301 static struct a_amv_mac *a_amv_acc_curr;
  302 
  303 static struct a_amv_mac *a_amv_macs[a_AMV_PRIME]; /* TODO dynamically spaced */
  304 
  305 /* Unroll list of currently running macro stack */
  306 static struct a_amv_lostack *a_amv_lopts;
  307 
  308 static struct a_amv_var *a_amv_vars[a_AMV_PRIME]; /* TODO dynamically spaced */
  309 
  310 /* Global (i.e., non-local) a_AMV_VSC_POSPAR stack */
  311 static struct a_amv_pospar a_amv_pospar;
  312 
  313 /* TODO We really deserve localopts support for *folder-hook*s, so hack it in
  314  * TODO today via a static lostack, it should be a field in mailbox, once that
  315  * TODO is a real multi-instance object */
  316 static struct a_amv_var *a_amv_folder_hook_lopts;
  317 
  318 /* TODO Rather ditto (except for storage -> cmd_ctx), compose hooks */
  319 static struct a_amv_var *a_amv_compose_lopts;
  320 
  321 /* Lookup for macros/accounts: if newamp is not NULL it will be linked in the
  322  * map, if _MF_UNDEF is set a possibly existing entry will be removed (first).
  323  * Returns NULL if a lookup failed, or if newamp was set, the found entry in
  324  * plain lookup cases or when _UNDEF was performed on a currently active entry
  325  * (the entry will have been unlinked, and the _MF_DELETE will be honoured once
  326  * the reference count reaches 0), and (*)-1 if an _UNDEF was performed */
  327 static struct a_amv_mac *a_amv_mac_lookup(char const *name,
  328                            struct a_amv_mac *newamp, enum a_amv_mac_flags amf);
  329 
  330 /* `call', `call_if' (and `xcall' via go.c -> c_call()) */
  331 static int a_amv_mac_call(void *v, bool_t silent_nexist);
  332 
  333 /* Execute a macro; amcap must reside in LOFI memory */
  334 static bool_t a_amv_mac_exec(struct a_amv_mac_call_args *amcap);
  335 
  336 static void a_amv_mac__finalize(void *vp);
  337 
  338 /* User display helpers */
  339 static bool_t a_amv_mac_show(enum a_amv_mac_flags amf);
  340 
  341 /* _def() returns error for faulty definitions and already existing * names,
  342  * _undef() returns error if a named thing doesn't exist */
  343 static bool_t a_amv_mac_def(char const *name, enum a_amv_mac_flags amf);
  344 static bool_t a_amv_mac_undef(char const *name, enum a_amv_mac_flags amf);
  345 
  346 /* */
  347 static void a_amv_mac_free(struct a_amv_mac *amp);
  348 
  349 /* Update replay-log */
  350 static void a_amv_lopts_add(struct a_amv_lostack *alp, char const *name,
  351                struct a_amv_var *oavp);
  352 static void a_amv_lopts_unroll(struct a_amv_var **avpp);
  353 
  354 /* Special cased value string allocation */
  355 static char *a_amv_var_copy(char const *str);
  356 static void a_amv_var_free(char *cp);
  357 
  358 /* Check for special housekeeping.  _VIP_SET_POST and _VIP_CLEAR do not fail
  359  * (or propagate errors), _VIP_SET_PRE may and should case abortion */
  360 static bool_t a_amv_var_check_vips(enum a_amv_var_vip_mode avvm,
  361                enum okeys okey, char const **val);
  362 
  363 /* _VF_NUM / _VF_POSNUM */
  364 static bool_t a_amv_var_check_num(char const *val, bool_t posnum);
  365 
  366 /* Try to reverse lookup a name to an enum okeys mapping, zeroing avcp.
  367  * Updates .avc_name and .avc_hash; .avc_map is NULL if none found.
  368  * We may try_harder to identify name: it may be an extended chain.
  369  * That test only is actually performed by the latter(, then) */
  370 static bool_t a_amv_var_revlookup(struct a_amv_var_carrier *avcp,
  371                char const *name, bool_t try_harder);
  372 static bool_t a_amv_var_revlookup_chain(struct a_amv_var_carrier *avcp,
  373                char const *name);
  374 
  375 /* Lookup a variable from .avc_(map|name|hash), return whether it was found.
  376  * Sets .avc_prime; .avc_var is NULL if not found.
  377  * Here it is where we care for _I3VAL and _DEFVAL.
  378  * An _I3VAL will be "consumed" as necessary anyway, but it won't be used to
  379  * create a new variable if _VLOOK_I3VAL_NONEW is set; if
  380  * _VLOOK_I3VAL_NONEW_REPORT is set then we set .avc_var to -1 and return true
  381  * if that was the case, otherwise we'll return FAL0, then! */
  382 static bool_t a_amv_var_lookup(struct a_amv_var_carrier *avcp,
  383                enum a_amv_var_lookup_flags avlf);
  384 
  385 /* Lookup functions for special category variables, _pospar drives all
  386  * positional parameter etc. special categories */
  387 static char const *a_amv_var_vsc_global(struct a_amv_var_carrier *avcp);
  388 static char const *a_amv_var_vsc_multiplex(struct a_amv_var_carrier *avcp);
  389 static char const *a_amv_var_vsc_pospar(struct a_amv_var_carrier *avcp);
  390 
  391 /* Set var from .avc_(map|name|hash), return success */
  392 static bool_t a_amv_var_set(struct a_amv_var_carrier *avcp, char const *value,
  393                enum a_amv_var_setclr_flags avscf);
  394 
  395 static bool_t a_amv_var__putenv(struct a_amv_var_carrier *avcp,
  396                struct a_amv_var *avp);
  397 
  398 /* Clear var from .avc_(map|name|hash); sets .avc_var=NULL, return success */
  399 static bool_t a_amv_var_clear(struct a_amv_var_carrier *avcp,
  400                enum a_amv_var_setclr_flags avscf);
  401 
  402 static bool_t a_amv_var__clearenv(char const *name, struct a_amv_var *avp);
  403 
  404 /* List all variables */
  405 static void a_amv_var_show_all(void);
  406 
  407 static int a_amv_var__show_cmp(void const *s1, void const *s2);
  408 
  409 /* Actually do print one, return number of lines written */
  410 static size_t a_amv_var_show(char const *name, FILE *fp, struct n_string *msgp);
  411 
  412 /* Shared c_set() and c_environ():set impl, return success */
  413 static bool_t a_amv_var_c_set(char **ap, enum a_amv_var_setclr_flags avscf);
  414 
  415 static struct a_amv_mac *
  416 a_amv_mac_lookup(char const *name, struct a_amv_mac *newamp,
  417       enum a_amv_mac_flags amf){
  418    struct a_amv_mac *amp, **ampp;
  419    ui32_t h;
  420    enum a_amv_mac_flags save_amf;
  421    NYD2_ENTER;
  422 
  423    save_amf = amf;
  424    amf &= a_AMV_MF_TYPE_MASK;
  425    h = a_AMV_NAME2HASH(name);
  426    h = a_AMV_HASH2PRIME(h);
  427    ampp = &a_amv_macs[h];
  428 
  429    for(amp = *ampp; amp != NULL; ampp = &(*ampp)->am_next, amp = amp->am_next){
  430       if((amp->am_flags & a_AMV_MF_TYPE_MASK) == amf &&
  431             !strcmp(amp->am_name, name)){
  432          if(n_LIKELY((save_amf & a_AMV_MF_UNDEF) == 0))
  433             goto jleave;
  434 
  435          *ampp = amp->am_next;
  436 
  437          if(amp->am_refcnt > 0){
  438             amp->am_flags |= a_AMV_MF_DELETE;
  439             if(n_poption & n_PO_D_V)
  440                n_err(_("Delayed deletion of currently active %s: %s\n"),
  441                   (amp->am_flags & a_AMV_MF_ACCOUNT ? "account" : "define"),
  442                   name);
  443          }else{
  444             a_amv_mac_free(amp);
  445             amp = (struct a_amv_mac*)-1;
  446          }
  447          break;
  448       }
  449    }
  450 
  451    if(newamp != NULL){
  452       ampp = &a_amv_macs[h];
  453       newamp->am_next = *ampp;
  454       *ampp = newamp;
  455       amp = NULL;
  456    }
  457 jleave:
  458    NYD2_LEAVE;
  459    return amp;
  460 }
  461 
  462 static int
  463 a_amv_mac_call(void *v, bool_t silent_nexist){
  464    struct a_amv_mac *amp;
  465    int rv;
  466    char const *name;
  467    struct n_cmd_arg_ctx *cacp;
  468    NYD_ENTER;
  469 
  470    cacp = v;
  471 
  472    if(cacp->cac_no == 0){
  473       n_err(_("Synopsis: call(_if)?: name [:<arguments>:]\n"));
  474       n_pstate_err_no = n_ERR_INVAL;
  475       rv = 1;
  476       goto jleave;
  477    }
  478 
  479    name = cacp->cac_arg->ca_arg.ca_str.s;
  480 
  481    if(n_UNLIKELY(cacp->cac_no > a_AMV_POSPAR_MAX)){
  482       n_err(_("Too many arguments to macro `call': %s\n"), name);
  483       n_pstate_err_no = n_ERR_OVERFLOW;
  484       rv = 1;
  485    }else if(n_UNLIKELY((amp = a_amv_mac_lookup(name, NULL, a_AMV_MF_NONE)
  486          ) == NULL)){
  487       if(!silent_nexist)
  488          n_err(_("Undefined macro called: %s\n"), n_shexp_quote_cp(name, FAL0));
  489       n_pstate_err_no = n_ERR_NOENT;
  490       rv = 1;
  491    }else{
  492       char const **argv;
  493       struct a_amv_mac_call_args *amcap;
  494       size_t argc;
  495 
  496       argc = cacp->cac_no + 1;
  497       amcap = n_lofi_alloc(sizeof *amcap + (argc * sizeof *argv));
  498       argv = (void*)&amcap[1];
  499 
  500       for(argc = 0; (cacp->cac_arg = cacp->cac_arg->ca_next) != NULL; ++argc)
  501          argv[argc] = cacp->cac_arg->ca_arg.ca_str.s;
  502       argv[argc] = NULL;
  503 
  504       memset(amcap, 0, sizeof *amcap);
  505       amcap->amca_name = name;
  506       amcap->amca_amp = amp;
  507       if(a_amv_lopts != NULL)
  508          amcap->amca_loflags = (a_amv_lopts->as_loflags & a_AMV_LF_CALL_MASK
  509                ) >> a_AMV_LF_CALL_TO_SCOPE_SHIFT;
  510       if(argc > 0){
  511          amcap->amca_pospar.app_count = (ui16_t)argc;
  512          amcap->amca_pospar.app_not_heap = TRU1;
  513          amcap->amca_pospar.app_dat = argv;
  514       }
  515 
  516       (void)a_amv_mac_exec(amcap);
  517       rv = n_pstate_ex_no;
  518    }
  519 jleave:
  520    NYD_LEAVE;
  521    return rv;
  522 }
  523 
  524 static bool_t
  525 a_amv_mac_exec(struct a_amv_mac_call_args *amcap){
  526    struct a_amv_lostack *losp;
  527    struct a_amv_mac_line **amlp;
  528    char **args_base, **args;
  529    struct a_amv_mac *amp;
  530    bool_t rv;
  531    NYD2_ENTER;
  532 
  533    amp = amcap->amca_amp;
  534    assert(amp != NULL && amp != a_AMV_MACKY_MACK);
  535    ++amp->am_refcnt;
  536    /* XXX Unfortunately we yet need to dup the macro lines! :( */
  537    args_base = args = n_alloc(sizeof(*args) * (amp->am_line_cnt +1));
  538    for(amlp = amp->am_line_dat; *amlp != NULL; ++amlp)
  539       *(args++) = sbufdup((*amlp)->aml_dat, (*amlp)->aml_len);
  540    *args = NULL;
  541 
  542    losp = n_lofi_alloc(sizeof *losp);
  543    losp->as_global_saved = a_amv_lopts;
  544    if((losp->as_amcap = amcap)->amca_unroller == NULL){
  545       losp->as_up = losp->as_global_saved;
  546       losp->as_lopts = NULL;
  547    }else{
  548       losp->as_up = NULL;
  549       losp->as_lopts = *amcap->amca_unroller;
  550    }
  551    losp->as_loflags = amcap->amca_loflags;
  552 
  553    a_amv_lopts = losp;
  554    if(amcap->amca_hook_pre != NULL)
  555       n_PS_ROOT_BLOCK((*amcap->amca_hook_pre)(amcap->amca_hook_arg));
  556    rv = n_go_macro((n_GO_INPUT_NONE |
  557             (amcap->amca_no_xcall ? n_GO_INPUT_NO_XCALL : 0)),
  558          amp->am_name, args_base, &a_amv_mac__finalize, losp);
  559    NYD2_LEAVE;
  560    return rv;
  561 }
  562 
  563 static void
  564 a_amv_mac__finalize(void *vp){
  565    struct a_amv_mac *amp;
  566    struct a_amv_mac_call_args *amcap;
  567    struct a_amv_lostack *losp;
  568    NYD2_ENTER;
  569 
  570    losp = vp;
  571    a_amv_lopts = losp->as_global_saved;
  572 
  573    amcap = losp->as_amcap;
  574 
  575    /* Delete positional parameter stack */
  576    if(!amcap->amca_pospar.app_not_heap && amcap->amca_pospar.app_maxcount > 0){
  577       ui16_t i;
  578 
  579       for(i = amcap->amca_pospar.app_maxcount; i-- != 0;)
  580          n_free(n_UNCONST(amcap->amca_pospar.app_dat[i]));
  581       n_free(amcap->amca_pospar.app_dat);
  582    }
  583 
  584    /* `local' variable hashmap.  These have no environment map, never */
  585    if(amcap->amca_local_vars != NULL){
  586       struct a_amv_var **avpp_base, **avpp, *avp;
  587 
  588       for(avpp_base = *amcap->amca_local_vars, avpp = &avpp_base[a_AMV_PRIME];
  589             avpp-- != avpp_base;)
  590          while((avp = *avpp)){
  591             assert((avp->av_flags & (a_AMV_VF_NOLOPTS | a_AMV_VF_EXT_LOCAL)) ==
  592                (a_AMV_VF_NOLOPTS | a_AMV_VF_EXT_LOCAL));
  593             assert(!(avp->av_flags &
  594                   ((a_AMV_VF__MASK | a_AMV_VF_EXT__MASK) &
  595                      ~(a_AMV_VF_NOLOPTS | a_AMV_VF_EXT_LOCAL))));
  596             *avpp = avp->av_link;
  597             a_amv_var_free(avp->av_value);
  598             n_free(avp);
  599          }
  600       n_free(avpp_base);
  601    }
  602 
  603    /* Unroll `localopts', if applicable */
  604    if(amcap->amca_unroller == NULL){
  605       if(losp->as_lopts != NULL)
  606          a_amv_lopts_unroll(&losp->as_lopts);
  607    }else
  608       *amcap->amca_unroller = losp->as_lopts;
  609 
  610    if(amcap->amca_ps_hook_mask)
  611       n_pstate &= ~n_PS_HOOK_MASK;
  612 
  613    if((amp = amcap->amca_amp) != a_AMV_MACKY_MACK && amp != NULL &&
  614          --amp->am_refcnt == 0 && (amp->am_flags & a_AMV_MF_DELETE))
  615       a_amv_mac_free(amp);
  616 
  617    n_lofi_free(losp);
  618    n_lofi_free(amcap);
  619    NYD2_LEAVE;
  620 }
  621 
  622 static bool_t
  623 a_amv_mac_show(enum a_amv_mac_flags amf){
  624    size_t lc, mc, ti, i;
  625    char const *typestr;
  626    FILE *fp;
  627    bool_t rv;
  628    NYD2_ENTER;
  629 
  630    rv = FAL0;
  631 
  632    if((fp = Ftmp(NULL, "deflist", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
  633          NULL){
  634       n_perr(_("`define' or `account' list: cannot create temporary file"), 0);
  635       goto jleave;
  636    }
  637 
  638    amf &= a_AMV_MF_TYPE_MASK;
  639    typestr = (amf & a_AMV_MF_ACCOUNT) ? "account" : "define";
  640 
  641    for(lc = mc = ti = 0; ti < a_AMV_PRIME; ++ti){
  642       struct a_amv_mac *amp;
  643 
  644       for(amp = a_amv_macs[ti]; amp != NULL; amp = amp->am_next){
  645          if((amp->am_flags & a_AMV_MF_TYPE_MASK) == amf){
  646             struct a_amv_mac_line **amlpp;
  647 
  648             if(++mc > 1){
  649                putc('\n', fp);
  650                ++lc;
  651             }
  652             ++lc;
  653             fprintf(fp, "%s %s {\n", typestr, amp->am_name);
  654             for(amlpp = amp->am_line_dat; *amlpp != NULL; ++lc, ++amlpp){
  655                for(i = (*amlpp)->aml_prespc; i > 0; --i)
  656                   putc(' ', fp);
  657                fputs((*amlpp)->aml_dat, fp);
  658                putc('\n', fp);
  659             }
  660             fputs("}\n", fp);
  661             ++lc;
  662          }
  663       }
  664    }
  665    if(mc > 0)
  666       page_or_print(fp, lc);
  667 
  668    rv = (ferror(fp) == 0);
  669    Fclose(fp);
  670 jleave:
  671    NYD2_LEAVE;
  672    return rv;
  673 }
  674 
  675 static bool_t
  676 a_amv_mac_def(char const *name, enum a_amv_mac_flags amf){
  677    struct str line;
  678    ui32_t line_cnt, maxlen;
  679    struct linelist{
  680       struct linelist *ll_next;
  681       struct a_amv_mac_line *ll_amlp;
  682    } *llp, *ll_head, *ll_tail;
  683    union {size_t s; int i; ui32_t ui; size_t l;} n;
  684    struct a_amv_mac *amp;
  685    bool_t rv;
  686    NYD2_ENTER;
  687 
  688    memset(&line, 0, sizeof line);
  689    rv = FAL0;
  690    amp = NULL;
  691 
  692    /* TODO We should have our input state machine which emits Line events,
  693     * TODO and hook different consumers dependent on our content, as stated
  694     * TODO in i think lex_input: like this local macros etc. would become
  695     * TODO possible from the input side */
  696    /* Read in the lines which form the macro content */
  697    for(ll_tail = ll_head = NULL, line_cnt = maxlen = 0;;){
  698       ui32_t leaspc;
  699       char *cp;
  700 
  701       n.i = n_go_input(n_GO_INPUT_CTX_DEFAULT | n_GO_INPUT_NL_ESC, n_empty,
  702             &line.s, &line.l, NULL, NULL);
  703       if(n.ui == 0)
  704          continue;
  705       if(n.i < 0){
  706          n_err(_("Unterminated %s definition: %s\n"),
  707             (amf & a_AMV_MF_ACCOUNT ? "account" : "macro"), name);
  708          goto jerr;
  709       }
  710 
  711       /* Trim WS, remember amount of leading spaces for display purposes */
  712       for(cp = line.s, leaspc = 0; n.ui > 0; ++cp, --n.ui)
  713          if(*cp == '\t')
  714             leaspc = (leaspc + 8u) & ~7u;
  715          else if(*cp == ' ')
  716             ++leaspc;
  717          else
  718             break;
  719       for(; n.ui > 0 && spacechar(cp[n.ui - 1]); --n.ui)
  720          ;
  721       if(n.ui == 0)
  722          continue;
  723 
  724       maxlen = n_MAX(maxlen, n.ui);
  725       cp[n.ui++] = '\0';
  726 
  727       /* Is is the closing brace? */
  728       if(*cp == '}')
  729          break;
  730 
  731       if(n_LIKELY(++line_cnt < UI32_MAX)){
  732          struct a_amv_mac_line *amlp;
  733 
  734          llp = n_autorec_alloc(sizeof *llp);
  735          if(ll_head == NULL)
  736             ll_head = llp;
  737          else
  738             ll_tail->ll_next = llp;
  739          ll_tail = llp;
  740          llp->ll_next = NULL;
  741          llp->ll_amlp = amlp = n_alloc(n_VSTRUCT_SIZEOF(struct a_amv_mac_line,
  742                aml_dat) + n.ui);
  743          amlp->aml_len = n.ui -1;
  744          amlp->aml_prespc = leaspc;
  745          memcpy(amlp->aml_dat, cp, n.ui);
  746       }else{
  747          n_err(_("Too much content in %s definition: %s\n"),
  748             (amf & a_AMV_MF_ACCOUNT ? "account" : "macro"), name);
  749          goto jerr;
  750       }
  751    }
  752 
  753    /* Create the new macro */
  754    n.s = strlen(name) +1;
  755    amp = n_alloc(n_VSTRUCT_SIZEOF(struct a_amv_mac, am_name) + n.s);
  756    memset(amp, 0, n_VSTRUCT_SIZEOF(struct a_amv_mac, am_name));
  757    amp->am_maxlen = maxlen;
  758    amp->am_line_cnt = line_cnt;
  759    amp->am_flags = amf;
  760    memcpy(amp->am_name, name, n.s);
  761    /* C99 */{
  762       struct a_amv_mac_line **amlpp;
  763 
  764       amp->am_line_dat = amlpp = n_alloc(sizeof(*amlpp) * ++line_cnt);
  765       for(llp = ll_head; llp != NULL; llp = llp->ll_next)
  766          *amlpp++ = llp->ll_amlp;
  767       *amlpp = NULL;
  768    }
  769 
  770    /* Create entry, replace a yet existing one as necessary */
  771    a_amv_mac_lookup(name, amp, amf | a_AMV_MF_UNDEF);
  772    rv = TRU1;
  773 jleave:
  774    if(line.s != NULL)
  775       n_free(line.s);
  776    NYD2_LEAVE;
  777    return rv;
  778 
  779 jerr:
  780    for(llp = ll_head; llp != NULL; llp = llp->ll_next)
  781       n_free(llp->ll_amlp);
  782    /*
  783     * if(amp != NULL){
  784     *   n_free(amp->am_line_dat);
  785     *   n_free(amp);
  786     *}*/
  787    goto jleave;
  788 }
  789 
  790 static bool_t
  791 a_amv_mac_undef(char const *name, enum a_amv_mac_flags amf){
  792    struct a_amv_mac *amp;
  793    bool_t rv;
  794    NYD2_ENTER;
  795 
  796    rv = TRU1;
  797 
  798    if(n_LIKELY(name[0] != '*' || name[1] != '\0')){
  799       if((amp = a_amv_mac_lookup(name, NULL, amf | a_AMV_MF_UNDEF)) == NULL){
  800          n_err(_("%s not defined: %s\n"),
  801             (amf & a_AMV_MF_ACCOUNT ? "Account" : "Macro"), name);
  802          rv = FAL0;
  803       }
  804    }else{
  805       struct a_amv_mac **ampp, *lamp;
  806 
  807       for(ampp = a_amv_macs; PTRCMP(ampp, <, &a_amv_macs[n_NELEM(a_amv_macs)]);
  808             ++ampp)
  809          for(lamp = NULL, amp = *ampp; amp != NULL;){
  810             if((amp->am_flags & a_AMV_MF_TYPE_MASK) == amf){
  811                /* xxx Expensive but rare: be simple */
  812                a_amv_mac_lookup(amp->am_name, NULL, amf | a_AMV_MF_UNDEF);
  813                amp = (lamp == NULL) ? *ampp : lamp->am_next;
  814             }else{
  815                lamp = amp;
  816                amp = amp->am_next;
  817             }
  818          }
  819    }
  820    NYD2_LEAVE;
  821    return rv;
  822 }
  823 
  824 static void
  825 a_amv_mac_free(struct a_amv_mac *amp){
  826    struct a_amv_mac_line **amlpp;
  827    NYD2_ENTER;
  828 
  829    for(amlpp = amp->am_line_dat; *amlpp != NULL; ++amlpp)
  830       n_free(*amlpp);
  831    n_free(amp->am_line_dat);
  832    n_free(amp);
  833    NYD2_LEAVE;
  834 }
  835 
  836 static void
  837 a_amv_lopts_add(struct a_amv_lostack *alp, char const *name,
  838       struct a_amv_var *oavp){
  839    struct a_amv_var *avp;
  840    size_t nl, vl;
  841    NYD2_ENTER;
  842 
  843    /* Propagate unrolling up the stack, as necessary */
  844    assert(alp != NULL);
  845    for(;;){
  846       if(alp->as_loflags & a_AMV_LF_SCOPE_MASK)
  847          break;
  848       if((alp = alp->as_up) == NULL)
  849          goto jleave;
  850    }
  851 
  852    /* Check whether this variable is handled yet XXX Boost: ID check etc.!! */
  853    for(avp = alp->as_lopts; avp != NULL; avp = avp->av_link)
  854       if(!strcmp(avp->av_name, name))
  855          goto jleave;
  856 
  857    nl = strlen(name) +1;
  858    vl = (oavp != NULL) ? strlen(oavp->av_value) +1 : 0;
  859    avp = n_calloc(1, n_VSTRUCT_SIZEOF(struct a_amv_var, av_name) + nl + vl);
  860    avp->av_link = alp->as_lopts;
  861    alp->as_lopts = avp;
  862    if(vl != 0){
  863       avp->av_value = &avp->av_name[nl];
  864       avp->av_flags = oavp->av_flags;
  865       memcpy(avp->av_value, oavp->av_value, vl);
  866    }
  867    memcpy(avp->av_name, name, nl);
  868 jleave:
  869    NYD2_LEAVE;
  870 }
  871 
  872 static void
  873 a_amv_lopts_unroll(struct a_amv_var **avpp){
  874    struct a_amv_lostack *save_alp;
  875    struct a_amv_var *x, *avp;
  876    NYD2_ENTER;
  877 
  878    avp = *avpp;
  879    *avpp = NULL;
  880 
  881    save_alp = a_amv_lopts;
  882    a_amv_lopts = NULL;
  883    while(avp != NULL){
  884       x = avp;
  885       avp = avp->av_link;
  886       n_PS_ROOT_BLOCK(n_var_vset(x->av_name, (uintptr_t)x->av_value));
  887       n_free(x);
  888    }
  889    a_amv_lopts = save_alp;
  890    NYD2_LEAVE;
  891 }
  892 
  893 static char *
  894 a_amv_var_copy(char const *str){
  895    char *news;
  896    size_t len;
  897    NYD2_ENTER;
  898 
  899    if(*str == '\0')
  900       news = n_UNCONST(n_empty);
  901    else if(str[1] == '\0'){
  902       if(str[0] == '1')
  903          news = n_UNCONST(n_1);
  904       else if(str[0] == '0')
  905          news = n_UNCONST(n_0);
  906       else
  907          goto jheap;
  908    }else if(str[2] == '\0' && str[0] == '-' && str[1] == '1')
  909       news = n_UNCONST(n_m1);
  910    else{
  911 jheap:
  912       len = strlen(str) +1;
  913       news = n_alloc(len);
  914       memcpy(news, str, len);
  915    }
  916    NYD2_LEAVE;
  917    return news;
  918 }
  919 
  920 static void
  921 a_amv_var_free(char *cp){
  922    NYD2_ENTER;
  923    if(cp[0] != '\0' && cp != n_0 && cp != n_1 && cp != n_m1)
  924       n_free(cp);
  925    NYD2_LEAVE;
  926 }
  927 
  928 static bool_t
  929 a_amv_var_check_vips(enum a_amv_var_vip_mode avvm, enum okeys okey,
  930       char const **val){
  931    char const *emsg;
  932    bool_t ok;
  933    NYD2_ENTER;
  934 
  935    ok = TRU1;
  936 
  937    if(avvm == a_AMV_VIP_SET_PRE){
  938       switch(okey){
  939       default:
  940          break;
  941       case ok_v_charset_7bit:
  942       case ok_v_charset_8bit:
  943       case ok_v_charset_unknown_8bit:
  944       case ok_v_ttycharset:
  945          if((*val = n_iconv_normalize_name(*val)) == NULL)
  946             ok = FAL0;
  947          break;
  948       case ok_v_customhdr:{
  949          char const *vp;
  950          char *buf;
  951          struct n_header_field *hflp, **hflpp, *hfp;
  952          NYD_ENTER;
  953 
  954          buf = savestr(*val);
  955          hflp = NULL;
  956          hflpp = &hflp;
  957 
  958          while((vp = n_strsep_esc(&buf, ',', TRU1)) != NULL){
  959             if(!n_header_add_custom(hflpp, vp, TRU1)){
  960                emsg = N_("Invalid *customhdr* entry: %s\n");
  961                ok = FAL0;
  962                break;
  963             }
  964             hflpp = &(*hflpp)->hf_next;
  965          }
  966 
  967          hflpp = ok ? &n_customhdr_list : &hflp;
  968          while((hfp = *hflpp) != NULL){
  969             *hflpp = hfp->hf_next;
  970             n_free(hfp);
  971          }
  972          if(!ok)
  973             goto jerr;
  974          n_customhdr_list = hflp;
  975          }break;
  976       case ok_v_from:
  977       case ok_v_sender:{
  978          struct name *np;
  979 
  980          if((np = lextract(*val, GEXTRA | GFULL)) == NULL){
  981 jefrom:
  982             emsg = N_("*from* / *sender*: invalid  address(es): %s\n");
  983             goto jerr;
  984          }else if(okey == ok_v_sender && np->n_flink != NULL){
  985             emsg = N_("*sender*: may not contain multiple addresses: %s\n");
  986             goto jerr;
  987          }else for(; np != NULL; np = np->n_flink)
  988             if(is_addr_invalid(np, EACM_STRICT | EACM_NOLOG | EACM_NONAME))
  989                goto jefrom;
  990          }break;
  991       case ok_v_HOME:
  992          /* Note this gets called from main.c during initialization, and they
  993           * simply set this to pw_dir as a fallback: don't verify _that_ call.
  994           * See main.c! */
  995          if(!(n_pstate & n_PS_ROOT) && !n_is_dir(*val, TRUM1)){
  996             emsg = N_("$HOME is not a directory or not accessible: %s\n");
  997             goto jerr;
  998          }
  999          break;
 1000       case ok_v_hostname:
 1001       case ok_v_smtp_hostname:
 1002 #ifdef HAVE_IDNA
 1003          if(**val != '\0'){
 1004             struct n_string cnv;
 1005 
 1006             n_string_creat_auto(&cnv);
 1007             if(!n_idna_to_ascii(&cnv, *val, UIZ_MAX)){
 1008                /*n_string_gut(&res);*/
 1009                emsg = N_("*hostname*/*smtp_hostname*: "
 1010                      "IDNA encoding failed: %s\n");
 1011                goto jerr;
 1012             }
 1013             *val = n_string_cp(&cnv);
 1014             /*n_string_drop_ownership(&cnv);*/
 1015          }
 1016 #endif
 1017          break;
 1018       case ok_v_quote_chars:{
 1019          char c;
 1020          char const *cp;
 1021 
 1022          for(cp = *val; (c = *cp++) != '\0';)
 1023             if(!asciichar(c) || blankspacechar(c)){
 1024                ok = FAL0;
 1025                break;
 1026             }
 1027          }break;
 1028       case ok_v_sendcharsets:{
 1029          struct n_string s, *sp = &s;
 1030          char *csv, *cp;
 1031 
 1032          sp = n_string_creat_auto(sp);
 1033          csv = savestr(*val);
 1034 
 1035          while((cp = n_strsep(&csv, ',', TRU1)) != NULL){
 1036             if((cp = n_iconv_normalize_name(cp)) == NULL){
 1037                ok = FAL0;
 1038                break;
 1039             }
 1040             if(sp->s_len > 0)
 1041                sp = n_string_push_c(sp, ',');
 1042             sp = n_string_push_cp(sp, cp);
 1043          }
 1044 
 1045          *val = n_string_cp(sp);
 1046          /* n_string_drop_ownership(sp); */
 1047          }break;
 1048       case ok_v_TMPDIR:
 1049          if(!n_is_dir(*val, TRU1)){
 1050             emsg = N_("$TMPDIR is not a directory or not accessible: %s\n");
 1051             goto jerr;
 1052          }
 1053          break;
 1054       case ok_v_umask:
 1055          if(**val != '\0'){
 1056             ui64_t uib;
 1057 
 1058             n_idec_ui64_cp(&uib, *val, 0, NULL);
 1059             if(uib & ~0777u){ /* (is valid _VF_POSNUM) */
 1060                emsg = N_("Invalid *umask* setting: %s\n");
 1061                goto jerr;
 1062             }
 1063          }
 1064          break;
 1065       }
 1066    }else if(avvm == a_AMV_VIP_SET_POST){
 1067       switch(okey){
 1068       default:
 1069          break;
 1070       case ok_b_ask:
 1071          ok_bset(asksub);
 1072          break;
 1073       case ok_b_debug:
 1074          n_poption |= n_PO_DEBUG;
 1075          break;
 1076       case ok_v_HOME:
 1077          /* Invalidate any resolved folder then, too
 1078           * FALLTHRU */
 1079       case ok_v_folder:
 1080          n_PS_ROOT_BLOCK(ok_vclear(folder_resolved));
 1081          break;
 1082       case ok_v_ifs:{
 1083          char *x_b, *x, c;
 1084          char const *cp;
 1085 
 1086          cp = *val;
 1087          x_b = x = n_autorec_alloc(strlen(cp) +1);
 1088          while((c = *cp++) != '\0')
 1089             if(spacechar(c))
 1090                *x++ = c;
 1091          *x = '\0';
 1092          n_PS_ROOT_BLOCK(ok_vset(ifs_ws, x_b));
 1093          }break;
 1094 #ifdef HAVE_SETLOCALE
 1095       case ok_v_LANG:
 1096       case ok_v_LC_ALL:
 1097       case ok_v_LC_CTYPE:
 1098          n_locale_init();
 1099          break;
 1100 #endif
 1101       case ok_b_memdebug:
 1102          n_poption |= n_PO_MEMDEBUG;
 1103          break;
 1104       case ok_b_POSIXLY_CORRECT: /* <-> *posix* */
 1105          if(!(n_pstate & n_PS_ROOT))
 1106             n_PS_ROOT_BLOCK(ok_bset(posix));
 1107          break;
 1108       case ok_b_posix: /* <-> $POSIXLY_CORRECT */
 1109          if(!(n_pstate & n_PS_ROOT))
 1110             n_PS_ROOT_BLOCK(ok_bset(POSIXLY_CORRECT));
 1111          break;
 1112       case ok_b_skipemptybody:
 1113          n_poption |= n_PO_E_FLAG;
 1114          break;
 1115       case ok_b_typescript_mode:
 1116          ok_bset(colour_disable);
 1117          ok_bset(line_editor_disable);
 1118          if(!(n_psonce & n_PSO_STARTED))
 1119             ok_bset(termcap_disable);
 1120          break;
 1121       case ok_v_umask:
 1122          if(**val != '\0'){
 1123             ui64_t uib;
 1124 
 1125             n_idec_ui64_cp(&uib, *val, 0, NULL);
 1126             umask((mode_t)uib);
 1127          }
 1128          break;
 1129       case ok_b_verbose:
 1130          n_poption |= (n_poption & n_PO_VERB) ? n_PO_VERBVERB : n_PO_VERB;
 1131          break;
 1132       }
 1133    }else{
 1134       switch(okey){
 1135       default:
 1136          break;
 1137       case ok_b_ask:
 1138          ok_bclear(asksub);
 1139          break;
 1140       case ok_b_debug:
 1141          n_poption &= ~n_PO_DEBUG;
 1142          break;
 1143       case ok_v_customhdr:{
 1144          struct n_header_field *hfp;
 1145 
 1146          while((hfp = n_customhdr_list) != NULL){
 1147             n_customhdr_list = hfp->hf_next;
 1148             n_free(hfp);
 1149          }
 1150          }break;
 1151       case ok_v_HOME:
 1152          /* Invalidate any resolved folder then, too
 1153           * FALLTHRU */
 1154       case ok_v_folder:
 1155          n_PS_ROOT_BLOCK(ok_vclear(folder_resolved));
 1156          break;
 1157       case ok_b_memdebug:
 1158          n_poption &= ~n_PO_MEMDEBUG;
 1159          break;
 1160       case ok_b_POSIXLY_CORRECT: /* <-> *posix* */
 1161          if(!(n_pstate & n_PS_ROOT))
 1162             n_PS_ROOT_BLOCK(ok_bclear(posix));
 1163          break;
 1164       case ok_b_posix: /* <-> $POSIXLY_CORRECT */
 1165          if(!(n_pstate & n_PS_ROOT))
 1166             n_PS_ROOT_BLOCK(ok_bclear(POSIXLY_CORRECT));
 1167          break;
 1168       case ok_b_skipemptybody:
 1169          n_poption &= ~n_PO_E_FLAG;
 1170          break;
 1171       case ok_b_verbose:
 1172          n_poption &= ~(n_PO_VERB | n_PO_VERBVERB);
 1173          break;
 1174       }
 1175    }
 1176 
 1177 jleave:
 1178    NYD2_LEAVE;
 1179    return ok;
 1180 jerr:
 1181    n_err(V_(emsg), n_shexp_quote_cp(*val, FAL0));
 1182    ok = FAL0;
 1183    goto jleave;
 1184 }
 1185 
 1186 static bool_t
 1187 a_amv_var_check_num(char const *val, bool_t posnum){
 1188    /* TODO The internal/environment  variables which are num= or posnum= should
 1189     * TODO gain special lookup functions, or the return should be void* and
 1190     * TODO castable to integer; i.e. no more strtoX() should be needed.
 1191     * TODO I.e., the result of this function should instead be stored */
 1192    bool_t rv;
 1193    NYD2_ENTER;
 1194 
 1195    rv = TRU1;
 1196 
 1197    if(*val != '\0'){ /* Would be _VF_NOTEMPTY if not allowed */
 1198       ui64_t uib;
 1199       enum n_idec_state ids;
 1200 
 1201       ids = n_idec_cp(&uib, val, 0,
 1202             (n_IDEC_MODE_LIMIT_32BIT |
 1203              (posnum ?  n_IDEC_MODE_SIGNED_TYPE : n_IDEC_MODE_NONE)), NULL);
 1204       if((ids & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
 1205             ) != n_IDEC_STATE_CONSUMED)
 1206          rv = FAL0;
 1207       /* TODO Unless we store integers we need to look and forbid, because
 1208        * TODO callee may not be able to swallow, e.g., "-1" */
 1209       if(posnum && (ids & n_IDEC_STATE_SEEN_MINUS))
 1210          rv = FAL0;
 1211    }
 1212    NYD2_LEAVE;
 1213    return rv;
 1214 }
 1215 
 1216 static bool_t
 1217 a_amv_var_revlookup(struct a_amv_var_carrier *avcp, char const *name,
 1218       bool_t try_harder){
 1219    ui32_t hash, i, j;
 1220    struct a_amv_var_map const *avmp;
 1221    char c;
 1222    NYD2_ENTER;
 1223 
 1224    memset(avcp, 0, sizeof *avcp); /* XXX overkill, just set chain */
 1225 
 1226    /* It may be a special a.k.a. macro-local or one-letter parameter */
 1227    c = name[0];
 1228    if(n_UNLIKELY(digitchar(c))){
 1229       /* (Inline dec. atoi, ugh) */
 1230       for(j = (ui8_t)c - '0', i = 1;; ++i){
 1231          c = name[i];
 1232          if(c == '\0')
 1233             break;
 1234          if(!digitchar(c))
 1235             goto jno_special_param;
 1236          j = j * 10 + (ui8_t)c - '0';
 1237       }
 1238       if(j <= a_AMV_POSPAR_MAX){
 1239          avcp->avc_special_cat = a_AMV_VSC_POSPAR;
 1240          goto jspecial_param;
 1241       }
 1242    }else if(n_UNLIKELY(name[1] == '\0')){
 1243       switch(c){
 1244       case '?':
 1245       case '!':
 1246          avcp->avc_special_cat = a_AMV_VSC_GLOBAL;
 1247          j = (c == '?') ? a_AMV_VST_QM : a_AMV_VST_EM;
 1248          goto jspecial_param;
 1249       case '^':
 1250          goto jmultiplex;
 1251       case '*':
 1252          avcp->avc_special_cat = a_AMV_VSC_POSPAR_ENV;
 1253          j = a_AMV_VST_STAR;
 1254          goto jspecial_param;
 1255       case '@':
 1256          avcp->avc_special_cat = a_AMV_VSC_POSPAR_ENV;
 1257          j = a_AMV_VST_AT;
 1258          goto jspecial_param;
 1259       case '#':
 1260          avcp->avc_special_cat = a_AMV_VSC_POSPAR_ENV;
 1261          j = a_AMV_VST_NOSIGN;
 1262          goto jspecial_param;
 1263       default:
 1264          break;
 1265       }
 1266    }else if(c == '^'){
 1267 jmultiplex:
 1268       avcp->avc_special_cat = a_AMV_VSC_MULTIPLEX;
 1269       j = a_AMV_VST_CACC;
 1270       goto jspecial_param;
 1271    }
 1272 
 1273    /* This is nothing special, but a plain variable */
 1274 jno_special_param:
 1275    assert(a_AMV_VSC_NONE == 0);/*avcp->avc_special_cat = a_AMV_VSC_NONE;*/
 1276    avcp->avc_name = name;
 1277    avcp->avc_hash = hash = a_AMV_NAME2HASH(name);
 1278 
 1279    /* Is it a known okey?  Walk over the hashtable */
 1280    for(i = hash % a_AMV_VAR_REV_PRIME, j = 0; j <= a_AMV_VAR_REV_LONGEST; ++j){
 1281       ui32_t x;
 1282 
 1283       if((x = a_amv_var_revmap[i]) == a_AMV_VAR_REV_ILL)
 1284          break;
 1285 
 1286       avmp = &a_amv_var_map[x];
 1287       if(avmp->avm_hash == hash &&
 1288             !strcmp(&a_amv_var_names[avmp->avm_keyoff], name)){
 1289          avcp->avc_map = avmp;
 1290          avcp->avc_okey = (enum okeys)x;
 1291          goto jleave;
 1292       }
 1293 
 1294       if(++i == a_AMV_VAR_REV_PRIME){
 1295 #ifdef a_AMV_VAR_REV_WRAPAROUND
 1296          i = 0;
 1297 #else
 1298          break;
 1299 #endif
 1300       }
 1301    }
 1302 
 1303    /* Not a known key, but it may be a chain extension of one.
 1304     * We possibly wanna know for a variety of reasons */
 1305    if(try_harder && a_amv_var_revlookup_chain(avcp, name))
 1306       goto jleave;
 1307 
 1308    assert(avcp->avc_map == NULL);/*avcp->avc_map = NULL;*/
 1309    avcp = NULL;
 1310 jleave:
 1311    assert(avcp == NULL || avcp->avc_map != NULL ||
 1312       avcp->avc_special_cat == a_AMV_VSC_NONE);
 1313    NYD2_LEAVE;
 1314    return (avcp != NULL);
 1315 
 1316    /* All these are mapped to *--special-param* */
 1317 jspecial_param:
 1318    avcp->avc_name = name;
 1319    avcp->avc_special_prop = (ui16_t)j;
 1320    avmp = &a_amv_var_map[a_AMV_VAR__SPECIAL_PARAM_MAP_IDX];
 1321    avcp->avc_hash = avmp->avm_hash;
 1322    avcp->avc_map = avmp;
 1323    avcp->avc_okey = ok_v___special_param;
 1324    goto jleave;
 1325 }
 1326 
 1327 static bool_t
 1328 a_amv_var_revlookup_chain(struct a_amv_var_carrier *avcp, char const *name){
 1329    uiz_t i;
 1330    struct a_amv_var_chain_map_bsrch const *avcmbp, *avcmbp_x;
 1331    NYD_ENTER;
 1332 
 1333    if(strlen(name) <
 1334          n_SIZEOF_FIELD(struct a_amv_var_chain_map_bsrch, avcmb_prefix)){
 1335       avcp = NULL;
 1336       goto jleave;
 1337    }
 1338 
 1339    avcmbp = &a_amv_var_chain_map_bsrch[0];
 1340    i = a_AMV_VAR_CHAIN_MAP_BSRCH_CNT - 0;
 1341    do{
 1342       int cres;
 1343       avcmbp_x = &avcmbp[i >> 1];
 1344       cres = memcmp(name, avcmbp_x->avcmb_prefix,
 1345             n_SIZEOF_FIELD(struct a_amv_var_chain_map_bsrch, avcmb_prefix));
 1346       if(cres != 0){
 1347          /* Go right instead? */
 1348          if(cres > 0){
 1349             avcmbp = ++avcmbp_x;
 1350             --i;
 1351          }
 1352       }else{
 1353          /* Once the binary search found the right prefix we have to use
 1354           * a linear walk from then on, because there is no "trigger"
 1355           * character: anything could be something free-form or
 1356           * a chain-extension, we just do not know.  Unfortunately.
 1357           * Luckily cramping the walk to a small window is possible */
 1358          struct a_amv_var_chain_map const *avcmp, *avcmp_hit;
 1359 
 1360          avcmp = &a_amv_var_chain_map[avcmbp_x->avcmb_chain_map_off];
 1361          avcmp_hit = NULL;
 1362          do{
 1363             char c;
 1364             char const *cp, *ncp;
 1365 
 1366             cp = &a_amv_var_names[avcmp->avcm_keyoff +
 1367                   n_SIZEOF_FIELD(struct a_amv_var_chain_map_bsrch,
 1368                      avcmb_prefix)];
 1369             ncp = &name[n_SIZEOF_FIELD(struct a_amv_var_chain_map_bsrch,
 1370                   avcmb_prefix)];
 1371             for(;; ++ncp, ++cp)
 1372                if(*ncp != (c = *cp) || c == '\0')
 1373                   break;
 1374             /* Is it a chain extension of this key? */
 1375             if(c == '\0' && *ncp == '-')
 1376                avcmp_hit = avcmp;
 1377             else if(avcmp_hit != NULL)
 1378                break;
 1379          }while((avcmp++)->avcm_okey < avcmbp_x->avcmb_chain_map_eokey);
 1380 
 1381          if(avcmp_hit != NULL){
 1382             avcp->avc_map = &a_amv_var_map[avcp->avc_okey =
 1383                   (enum okeys)avcmp_hit->avcm_okey];
 1384             avcp->avc_is_chain_variant = TRU1;
 1385             goto jleave;
 1386          }
 1387          break;
 1388       }
 1389    }while((i >>= 1) > 0);
 1390 
 1391    avcp = NULL;
 1392 jleave:
 1393    NYD_LEAVE;
 1394    return (avcp != NULL);
 1395 }
 1396 
 1397 static bool_t
 1398 a_amv_var_lookup(struct a_amv_var_carrier *avcp,
 1399       enum a_amv_var_lookup_flags avlf){
 1400    size_t i;
 1401    char const *cp;
 1402    ui32_t f;
 1403    struct a_amv_var_map const *avmp;
 1404    struct a_amv_var *avp;
 1405    NYD2_ENTER;
 1406 
 1407    assert(!(avlf & a_AMV_VLOOK_LOCAL_ONLY) || (avlf & a_AMV_VLOOK_LOCAL));
 1408    assert(!(avlf & a_AMV_VLOOK_I3VAL_NONEW_REPORT) ||
 1409       (avlf & a_AMV_VLOOK_I3VAL_NONEW));
 1410 
 1411    /* C99 */{
 1412       struct a_amv_var **avpp, *lavp;
 1413 
 1414       avcp->avc_prime = a_AMV_HASH2PRIME(avcp->avc_hash);
 1415 
 1416       /* Optionally macro-`local' variables first */
 1417       if(avlf & a_AMV_VLOOK_LOCAL){
 1418          if(a_amv_lopts != NULL &&
 1419                (avpp = *a_amv_lopts->as_amcap->amca_local_vars) != NULL){
 1420             avpp += avcp->avc_prime;
 1421 
 1422             for(lavp = NULL, avp = *avpp; avp != NULL;
 1423                   lavp = avp, avp = avp->av_link)
 1424                if(!strcmp(avp->av_name, avcp->avc_name)){
 1425                   /* Relink as head, hope it "sorts on usage" over time.
 1426                    * The code relies on this behaviour! */
 1427                   if(lavp != NULL){
 1428                      lavp->av_link = avp->av_link;
 1429                      avp->av_link = *avpp;
 1430                      *avpp = avp;
 1431                   }
 1432                   goto jleave;
 1433                }
 1434          }
 1435 
 1436          if(avlf & a_AMV_VLOOK_LOCAL_ONLY)
 1437             goto jerr;
 1438       }
 1439 
 1440       /* Global variable map */
 1441       avpp = &a_amv_vars[avcp->avc_prime];
 1442 
 1443       for(lavp = NULL, avp = *avpp; avp != NULL; lavp = avp, avp = avp->av_link)
 1444          if(!strcmp(avp->av_name, avcp->avc_name)){
 1445             /* Relink as head, hope it "sorts on usage" over time.
 1446              * The code relies on this behaviour! */
 1447             if(lavp != NULL){
 1448                lavp->av_link = avp->av_link;
 1449                avp->av_link = *avpp;
 1450                *avpp = avp;
 1451             }
 1452 
 1453             /* If this setting has been established via -S and we still have
 1454              * not reached the _STARTED_CONFIG program state, it may have been
 1455              * an explicit "clearance" that is to be treated as unset.
 1456              * Because that is a special condition that (has been hacked in
 1457              * later and) needs to be encapsulated in lower levels, but not
 1458              * of interest if _set() or _clear() called us */
 1459             switch(avp->av_flags & a_AMV_VF_EXT__FROZEN_MASK){
 1460             case a_AMV_VF_EXT_FROZEN | a_AMV_VF_EXT_FROZEN_UNSET:
 1461                if(!(avlf & a_AMV_VLOOK_I3VAL_NONEW)){
 1462                   avcp->avc_var = avp;
 1463                   avp = NULL;
 1464                   goto j_leave;
 1465                }
 1466                /* FALLTHRU */
 1467             default:
 1468                break;
 1469             }
 1470             goto jleave;
 1471          }
 1472    }
 1473 
 1474    /* If this is not an assembled variable we need to consider some special
 1475     * initialization cases and eventually create the variable anew */
 1476    if(n_LIKELY((avmp = avcp->avc_map) != NULL)){
 1477       f = avmp->avm_flags;
 1478 
 1479       /* Does it have an import-from-environment flag? */
 1480       if(n_UNLIKELY((f & (a_AMV_VF_IMPORT | a_AMV_VF_ENV)) != 0)){
 1481          if(n_LIKELY((cp = getenv(avcp->avc_name)) != NULL)){
 1482             /* May be better not to use that one, though? */
 1483             /* TODO Outsource the tests into a _shared_ test function! */
 1484             bool_t isempty, isbltin;
 1485 
 1486             isempty = (*cp == '\0' && (f & a_AMV_VF_NOTEMPTY) != 0);
 1487             isbltin = ((f & (a_AMV_VF_I3VAL | a_AMV_VF_DEFVAL)) != 0);
 1488 
 1489             if(n_UNLIKELY(isempty)){
 1490                n_err(_("Environment variable must not be empty: %s\n"),
 1491                   avcp->avc_name);
 1492                if(!isbltin)
 1493                   goto jerr;
 1494             }else if(n_LIKELY(*cp != '\0')){
 1495                if(n_UNLIKELY((f & a_AMV_VF_NUM) &&
 1496                      !a_amv_var_check_num(cp, FAL0))){
 1497                   n_err(_("Environment variable value not a number "
 1498                      "or out of range: %s\n"), avcp->avc_name);
 1499                   goto jerr;
 1500                }
 1501                if(n_UNLIKELY((f & a_AMV_VF_POSNUM) &&
 1502                      !a_amv_var_check_num(cp, TRU1))){
 1503                   n_err(_("Environment variable value not a number, "
 1504                      "negative or out of range: %s\n"), avcp->avc_name);
 1505                   goto jerr;
 1506                }
 1507                goto jnewval;
 1508             }else
 1509                goto jnewval;
 1510          }
 1511       }
 1512 
 1513       /* A first-time init switch is to be handled now and here */
 1514       if(n_UNLIKELY((f & a_AMV_VF_I3VAL) != 0)){
 1515          static struct a_amv_var_defval const **arr,
 1516             *arr_base[a_AMV_VAR_I3VALS_CNT +1];
 1517 
 1518          if(arr == NULL){
 1519             arr = &arr_base[0];
 1520             arr[i = a_AMV_VAR_I3VALS_CNT] = NULL;
 1521             while(i-- > 0)
 1522                arr[i] = &a_amv_var_i3vals[i];
 1523          }
 1524 
 1525          for(i = 0; arr[i] != NULL; ++i)
 1526             if(arr[i]->avdv_okey == avcp->avc_okey){
 1527                cp = (f & a_AMV_VF_BOOL) ? n_1 : arr[i]->avdv_value;
 1528                /* Remove this entry, hope entire block becomes no-op asap */
 1529                do
 1530                   arr[i] = arr[i + 1];
 1531                while(arr[i++] != NULL);
 1532 
 1533                if(!(avlf & a_AMV_VLOOK_I3VAL_NONEW))
 1534                   goto jnewval;
 1535                if(avlf & a_AMV_VLOOK_I3VAL_NONEW_REPORT)
 1536                   avp = (struct a_amv_var*)-1;
 1537                goto jleave;
 1538             }
 1539       }
 1540 
 1541       /* */
 1542 jdefval:
 1543       if(n_UNLIKELY(f & a_AMV_VF_DEFVAL) != 0){
 1544          for(i = 0; i < a_AMV_VAR_DEFVALS_CNT; ++i)
 1545             if(a_amv_var_defvals[i].avdv_okey == avcp->avc_okey){
 1546                cp = (f & a_AMV_VF_BOOL) ? n_1
 1547                      : a_amv_var_defvals[i].avdv_value;
 1548                goto jnewval;
 1549             }
 1550       }
 1551 
 1552       /* The virtual variables */
 1553       if(n_UNLIKELY((f & a_AMV_VF_VIRT) != 0)){
 1554          for(i = 0; i < a_AMV_VAR_VIRTS_CNT; ++i)
 1555             if(a_amv_var_virts[i].avv_okey == avcp->avc_okey){
 1556                avp = n_UNCONST(a_amv_var_virts[i].avv_var);
 1557                goto jleave;
 1558             }
 1559          /* Not reached */
 1560       }
 1561    }
 1562 
 1563 jerr:
 1564    avp = NULL;
 1565 jleave:
 1566    avcp->avc_var = avp;
 1567 j_leave:
 1568    if(!(avlf & a_AMV_VLOOK_I3VAL_NONEW) && (n_poption & n_PO_D_VV) &&
 1569          avp != (struct a_amv_var*)-1 && avcp->avc_okey != ok_v_log_prefix){
 1570       /* I18N: Variable "name" is set to "value" */
 1571       n_err(_("*%s* is %s\n"),
 1572          n_shexp_quote_cp(avcp->avc_name, FAL0),
 1573          (avp == NULL ? _("not set")
 1574             : ((avp->av_flags & a_AMV_VF_BOOL) ? _("boolean set")
 1575                : n_shexp_quote_cp(avp->av_value, FAL0))));
 1576    }
 1577    NYD2_LEAVE;
 1578    return (avp != NULL);
 1579 
 1580 jnewval:
 1581    assert(avmp != NULL);
 1582    assert(f == avmp->avm_flags);
 1583    /* E.g., $TMPDIR may be set to non-existent, so we need to be able to catch
 1584     * that and redirect to a possible default value */
 1585    if((f & a_AMV_VF_VIP) &&
 1586          !a_amv_var_check_vips(a_AMV_VIP_SET_PRE, avcp->avc_okey, &cp)){
 1587 #ifdef HAVE_SETENV
 1588       if(f & (a_AMV_VF_IMPORT | a_AMV_VF_ENV))
 1589          unsetenv(avcp->avc_name);
 1590 #endif
 1591       if(n_UNLIKELY(f & a_AMV_VF_DEFVAL) != 0)
 1592          goto jdefval;
 1593       goto jerr;
 1594    }else{
 1595       struct a_amv_var **avpp;
 1596       size_t l;
 1597 
 1598       l = strlen(avcp->avc_name) +1;
 1599       avcp->avc_var =
 1600       avp = n_calloc(1, n_VSTRUCT_SIZEOF(struct a_amv_var, av_name) + l);
 1601       avp->av_link = *(avpp = &a_amv_vars[avcp->avc_prime]);
 1602       *avpp = avp;
 1603       assert(!avcp->avc_is_chain_variant);
 1604       avp->av_flags = f;
 1605       avp->av_value = a_amv_var_copy(cp);
 1606       memcpy(avp->av_name, avcp->avc_name, l);
 1607 
 1608       if(f & a_AMV_VF_ENV)
 1609          a_amv_var__putenv(avcp, avp);
 1610       if(f & a_AMV_VF_VIP)
 1611          a_amv_var_check_vips(a_AMV_VIP_SET_POST, avcp->avc_okey, &cp);
 1612       goto jleave;
 1613    }
 1614 }
 1615 
 1616 static char const *
 1617 a_amv_var_vsc_global(struct a_amv_var_carrier *avcp){
 1618    char iencbuf[n_IENC_BUFFER_SIZE];
 1619    char const *rv;
 1620    si32_t *ep;
 1621    struct a_amv_var_map const *avmp;
 1622    NYD2_ENTER;
 1623 
 1624    /* Not function local, TODO but lazy evaluted for now */
 1625    if(avcp->avc_special_prop == a_AMV_VST_QM){
 1626       avmp = &a_amv_var_map[a_AMV_VAR__QM_MAP_IDX];
 1627       avcp->avc_okey = ok_v___qm;
 1628       ep = &n_pstate_ex_no;
 1629    }else{
 1630       avmp = &a_amv_var_map[a_AMV_VAR__EM_MAP_IDX];
 1631       avcp->avc_okey = ok_v___em;
 1632       ep = &n_pstate_err_no;
 1633    }
 1634 
 1635    /* XXX Oh heaven, we are responsible to ensure that $?/! is up-to-date
 1636     * TODO we could num=1 ok_v___[qe]m, but the thing is still a string
 1637     * TODO and thus conversion takes places over and over again; also
 1638     * TODO for now that would have to occur before we set _that_ value
 1639     * TODO so let's special treat it until we store ints as such */
 1640    switch(*ep){
 1641    case 0: rv = n_0; break;
 1642    case 1: rv = n_1; break;
 1643    default:
 1644       rv = n_ienc_buf(iencbuf, *ep, 10, n_IENC_MODE_SIGNED_TYPE);
 1645       break;
 1646    }
 1647    n_PS_ROOT_BLOCK(n_var_okset(avcp->avc_okey, (uintptr_t)rv));
 1648 
 1649    avcp->avc_hash = avmp->avm_hash;
 1650    avcp->avc_map = avmp;
 1651    rv = a_amv_var_lookup(avcp, a_AMV_VLOOK_NONE)
 1652          ? avcp->avc_var->av_value : NULL;
 1653    NYD2_LEAVE;
 1654    return rv;
 1655 }
 1656 
 1657 static char const *
 1658 a_amv_var_vsc_multiplex(struct a_amv_var_carrier *avcp){
 1659    char iencbuf[n_IENC_BUFFER_SIZE];
 1660    si32_t e;
 1661    size_t i;
 1662    char const *rv;
 1663    NYD2_ENTER;
 1664 
 1665    i = strlen(rv = &avcp->avc_name[1]);
 1666 
 1667    /* ERR, ERRDOC, ERRNAME, plus *-NAME variants */
 1668    if(rv[0] == 'E' && i >= 3 && rv[1] == 'R' && rv[2] == 'R'){
 1669       if(i == 3){
 1670          e = n_pstate_err_no;
 1671          goto jeno;
 1672       }else if(rv[3] == '-'){
 1673          e = n_err_from_name(&rv[4]);
 1674 jeno:
 1675          switch(e){
 1676          case 0: rv = n_0; break;
 1677          case 1: rv = n_1; break;
 1678          default:
 1679             /* XXX Need to convert number to string yet */
 1680             rv = savestr(n_ienc_buf(iencbuf, e, 10, n_IENC_MODE_SIGNED_TYPE));
 1681             break;
 1682          }
 1683          goto jleave;
 1684       }else if(i >= 6){
 1685          if(!memcmp(&rv[3], "DOC", 3)){
 1686             rv += 6;
 1687             switch(*rv){
 1688             case '\0': e = n_pstate_err_no; break;
 1689             case '-': e = n_err_from_name(&rv[1]); break;
 1690             default: goto jerr;
 1691             }
 1692             rv = n_err_to_doc(e);
 1693             goto jleave;
 1694          }else if(i >= 7 && !memcmp(&rv[3], "NAME", 4)){
 1695             rv += 7;
 1696             switch(*rv){
 1697             case '\0': e = n_pstate_err_no; break;
 1698             case '-': e = n_err_from_name(&rv[1]); break;
 1699             default: goto jerr;
 1700             }
 1701             rv = n_err_to_name(e);
 1702             goto jleave;
 1703          }
 1704       }
 1705    }
 1706 
 1707 jerr:
 1708    rv = NULL;
 1709 jleave:
 1710    NYD2_LEAVE;
 1711    return rv;
 1712 }
 1713 
 1714 static char const *
 1715 a_amv_var_vsc_pospar(struct a_amv_var_carrier *avcp){
 1716    size_t i, j;
 1717    ui16_t argc;
 1718    char const *rv, **argv;
 1719    NYD2_ENTER;
 1720 
 1721    rv = NULL;
 1722 
 1723    /* If in a macro/xy.. */
 1724    if(a_amv_lopts != NULL){
 1725       bool_t ismacky;
 1726       struct a_amv_mac_call_args *amcap;
 1727 
 1728       amcap = a_amv_lopts->as_amcap;
 1729       argc = amcap->amca_pospar.app_count;
 1730       argv = amcap->amca_pospar.app_dat;
 1731       argv += amcap->amca_pospar.app_idx;
 1732 
 1733       /* ..in a `call'ed macro only, to be exact.  Or in a_AMV_MACKY_MACK */
 1734       if(!(ismacky = (amcap->amca_amp == a_AMV_MACKY_MACK)) &&
 1735             (amcap->amca_ps_hook_mask ||
 1736              (assert(amcap->amca_amp != NULL),
 1737               (amcap->amca_amp->am_flags & a_AMV_MF_TYPE_MASK
 1738                ) == a_AMV_MF_ACCOUNT)))
 1739          goto jleave;
 1740 
 1741       if(avcp->avc_special_cat == a_AMV_VSC_POSPAR){
 1742          if(avcp->avc_special_prop > 0){
 1743             if(argc >= avcp->avc_special_prop)
 1744                rv = argv[avcp->avc_special_prop - 1];
 1745          }else if(ismacky)
 1746             rv = amcap->amca_name;
 1747          else
 1748             rv = (a_amv_lopts->as_up != NULL
 1749                   ? a_amv_lopts->as_up->as_amcap->amca_name : n_empty);
 1750          goto jleave;
 1751       }
 1752       /* MACKY_MACK doesn't know about [*@#] */
 1753       /*else*/ if(ismacky){
 1754          if(n_poption & n_PO_D_V)
 1755             n_err(_("Cannot use $*/$@/$# in this context: %s\n"),
 1756                n_shexp_quote_cp(avcp->avc_name, FAL0));
 1757          goto jleave;
 1758       }
 1759    }else{
 1760       argc = a_amv_pospar.app_count;
 1761       argv = a_amv_pospar.app_dat;
 1762       argv += a_amv_pospar.app_idx;
 1763 
 1764       if(avcp->avc_special_cat == a_AMV_VSC_POSPAR){
 1765          if(avcp->avc_special_prop > 0){
 1766             if(argc >= avcp->avc_special_prop)
 1767                rv = argv[avcp->avc_special_prop - 1];
 1768          }else
 1769             rv = n_progname;
 1770          goto jleave;
 1771       }
 1772    }
 1773 
 1774    switch(avcp->avc_special_prop){ /* XXX OPTIMIZE */
 1775    case a_AMV_VST_STAR:{
 1776       char sep;
 1777 
 1778       sep = *ok_vlook(ifs);
 1779       if(0){
 1780    case a_AMV_VST_AT:
 1781          sep = ' ';
 1782       }
 1783       for(i = j = 0; i < argc; ++i)
 1784          j += strlen(argv[i]) + 1;
 1785       if(j == 0)
 1786          rv = n_empty;
 1787       else{
 1788          char *cp;
 1789 
 1790          rv = cp = n_autorec_alloc(j);
 1791          for(i = j = 0; i < argc; ++i){
 1792             j = strlen(argv[i]);
 1793             memcpy(cp, argv[i], j);
 1794             cp += j;
 1795             if(sep != '\0')
 1796                *cp++ = sep;
 1797          }
 1798          if(sep != '\0')
 1799             --cp;
 1800          *cp = '\0';
 1801       }
 1802       }break;
 1803    case a_AMV_VST_NOSIGN:{
 1804       char iencbuf[n_IENC_BUFFER_SIZE];
 1805 
 1806       rv = savestr(n_ienc_buf(iencbuf, argc, 10, n_IENC_MODE_NONE));
 1807       }break;
 1808    default:
 1809       rv = n_empty;
 1810       break;
 1811    }
 1812 jleave:
 1813    NYD2_LEAVE;
 1814    return rv;
 1815 }
 1816 
 1817 static bool_t
 1818 a_amv_var_set(struct a_amv_var_carrier *avcp, char const *value,
 1819       enum a_amv_var_setclr_flags avscf){
 1820    struct a_amv_var *avp;
 1821    char *oval;
 1822    struct a_amv_var_map const *avmp;
 1823    bool_t rv;
 1824    NYD2_ENTER;
 1825 
 1826    if(value == NULL){
 1827       rv = a_amv_var_clear(avcp, avscf);
 1828       goto jleave;
 1829    }
 1830 
 1831    if((avmp = avcp->avc_map) != NULL){
 1832       rv = FAL0;
 1833 
 1834       /* Validity checks */
 1835       if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_RDONLY) != 0 &&
 1836             !(n_pstate & n_PS_ROOT))){
 1837          value = N_("Variable is read-only: %s\n");
 1838          goto jeavmp;
 1839       }
 1840       if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_NOTEMPTY) && *value == '\0')){
 1841          value = N_("Variable must not be empty: %s\n");
 1842          goto jeavmp;
 1843       }
 1844       if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_NUM) &&
 1845             !a_amv_var_check_num(value, FAL0))){
 1846          value = N_("Variable value not a number or out of range: %s\n");
 1847          goto jeavmp;
 1848       }
 1849       if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_POSNUM) &&
 1850             !a_amv_var_check_num(value, TRU1))){
 1851          value = _("Variable value not a number, negative, "
 1852                "or out of range: %s\n");
 1853          goto jeavmp;
 1854       }
 1855 
 1856       if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_IMPORT) != 0 &&
 1857             !(n_psonce & n_PSO_STARTED) && !(n_pstate & n_PS_ROOT))){
 1858          value = N_("Variable cannot be set in a resource file: %s\n");
 1859          goto jeavmp;
 1860       }
 1861 
 1862       /* Any more complicated inter-dependency? */
 1863       if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_VIP) != 0 &&
 1864             !a_amv_var_check_vips(a_AMV_VIP_SET_PRE, avcp->avc_okey, &value))){
 1865          value = N_("Assignment of variable aborted: %s\n");
 1866 jeavmp:
 1867          n_err(V_(value), avcp->avc_name);
 1868          goto jleave;
 1869       }
 1870 
 1871       /* Transformations */
 1872       if(n_UNLIKELY(avmp->avm_flags & a_AMV_VF_LOWER)){
 1873          char c;
 1874 
 1875          oval = savestr(value);
 1876          value = oval;
 1877          for(; (c = *oval) != '\0'; ++oval)
 1878             *oval = lowerconv(c);
 1879       }
 1880 
 1881       /* Obsoletion warning */
 1882       if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_OBSOLETE) != 0))
 1883          n_OBSOLETE2(_("variable superseded or obsoleted"), avcp->avc_name);
 1884    }
 1885 
 1886    /* Lookup possibly existing var.  For */
 1887 
 1888    rv = TRU1;
 1889    a_amv_var_lookup(avcp, (a_AMV_VLOOK_I3VAL_NONEW |
 1890          ((avscf & a_AMV_VSETCLR_LOCAL)
 1891           ? (a_AMV_VLOOK_LOCAL | a_AMV_VLOOK_LOCAL_ONLY)
 1892           : a_AMV_VLOOK_LOCAL)));
 1893    avp = avcp->avc_var;
 1894 
 1895    /* A `local' setting is never covered by `localopts' nor frozen */
 1896    if(avscf & a_AMV_VSETCLR_LOCAL)
 1897       goto jislocal;
 1898 
 1899    /* If this setting had been established via -S and we still have not reached
 1900     * the _STARTED_CONFIG program state, silently ignore request! */
 1901    if(n_UNLIKELY(avp != NULL) &&
 1902          n_UNLIKELY((avp->av_flags & a_AMV_VF_EXT__FROZEN_MASK) != 0)){
 1903       if(!(n_psonce & n_PSO_STARTED_CONFIG)){
 1904          if((n_pstate & n_PS_ROOT) ||
 1905                (!(n_psonce & n_PSO_STARTED_GETOPT) &&
 1906                 (n_poption & n_PO_S_FLAG_TEMPORARY)))
 1907             goto joval_and_go;
 1908          if(n_poption & n_PO_D_VV)
 1909             n_err(_("Temporarily frozen by -S, not `set'ing: %s\n"),
 1910                avcp->avc_name);
 1911          goto jleave;
 1912       }
 1913 
 1914       /* Otherwise, if -S freezing was an `unset' request, be very simple and
 1915        * avoid tampering with that very special case we are not really prepared
 1916        * for just one more line of code: throw the old thing away! */
 1917       if(!(avp->av_flags & a_AMV_VF_EXT_FROZEN_UNSET))
 1918          avp->av_flags &= ~a_AMV_VF_EXT__FROZEN_MASK;
 1919       else{
 1920          assert(avp->av_value == n_empty);
 1921          assert(a_amv_vars[avcp->avc_prime] == avp);
 1922          a_amv_vars[avcp->avc_prime] = avp->av_link;
 1923          n_free(avp);
 1924          avcp->avc_var = avp = NULL;
 1925       }
 1926    }
 1927 
 1928    /* Optionally cover by `localopts' */
 1929    if(n_UNLIKELY(a_amv_lopts != NULL) &&
 1930          (avmp == NULL || !(avmp->avm_flags & a_AMV_VF_NOLOPTS)))
 1931       a_amv_lopts_add(a_amv_lopts, avcp->avc_name, avcp->avc_var);
 1932 
 1933 jislocal:
 1934    if(avp != NULL)
 1935 joval_and_go:
 1936       oval = avp->av_value;
 1937    else{
 1938       size_t l;
 1939       struct a_amv_var **avpp;
 1940 
 1941       if(avscf & a_AMV_VSETCLR_LOCAL){
 1942          if((avpp = *a_amv_lopts->as_amcap->amca_local_vars) == NULL)
 1943             avpp = *(a_amv_lopts->as_amcap->amca_local_vars =
 1944                   n_calloc(1, sizeof(*a_amv_lopts->as_amcap->amca_local_vars)));
 1945          avpp += avcp->avc_prime;
 1946       }else
 1947          avpp = &a_amv_vars[avcp->avc_prime];
 1948 
 1949       l = strlen(avcp->avc_name) +1;
 1950       avcp->avc_var = avp = n_calloc(1,
 1951             n_VSTRUCT_SIZEOF(struct a_amv_var, av_name) + l);
 1952       avp->av_link = *avpp;
 1953       *avpp = avp;
 1954       avp->av_flags = (((avscf & a_AMV_VSETCLR_LOCAL)
 1955                ? a_AMV_VF_NOLOPTS | a_AMV_VF_EXT_LOCAL
 1956                : ((avmp != NULL) ? avmp->avm_flags : 0)) |
 1957             (avcp->avc_is_chain_variant ? a_AMV_VF_EXT_CHAIN : a_AMV_VF_NONE));
 1958       memcpy(avp->av_name, avcp->avc_name, l);
 1959       oval = n_UNCONST(n_empty);
 1960    }
 1961 
 1962    if(avmp == NULL)
 1963       avp->av_value = a_amv_var_copy(value);
 1964    else{
 1965       assert(!(avscf & a_AMV_VSETCLR_LOCAL));
 1966       /* Via `set' etc. the user may give even boolean options non-boolean
 1967        * values, ignore that and force boolean */
 1968       if(!(avp->av_flags & a_AMV_VF_BOOL))
 1969          avp->av_value = a_amv_var_copy(value);
 1970       else{
 1971          if(!(n_pstate & n_PS_ROOT) && (n_poption & n_PO_D_V) &&
 1972                *value != '\0')
 1973             n_err(_("Ignoring value of boolean variable: %s: %s\n"),
 1974                avcp->avc_name, value);
 1975          avp->av_value = n_UNCONST(n_1);
 1976       }
 1977    }
 1978 
 1979    /* A `local' setting can skip all the crude special things */
 1980    if(!(avscf & a_AMV_VSETCLR_LOCAL)){
 1981       ui32_t f;
 1982 
 1983       f = avp->av_flags;
 1984 
 1985       if((avscf & a_AMV_VSETCLR_ENV) && !(f & a_AMV_VF_ENV))
 1986          f |= a_AMV_VF_EXT_LINKED;
 1987       if(f & (a_AMV_VF_ENV | a_AMV_VF_EXT_LINKED))
 1988          rv = a_amv_var__putenv(avcp, avp);
 1989       if(f & a_AMV_VF_VIP)
 1990          a_amv_var_check_vips(a_AMV_VIP_SET_POST, avcp->avc_okey, &value);
 1991 
 1992       f &= ~a_AMV_VF_EXT__FROZEN_MASK;
 1993       if(!(n_psonce & n_PSO_STARTED_GETOPT) &&
 1994             (n_poption & n_PO_S_FLAG_TEMPORARY) != 0)
 1995          f |= a_AMV_VF_EXT_FROZEN;
 1996 
 1997       avp->av_flags = f;
 1998    }
 1999 
 2000    a_amv_var_free(oval);
 2001 jleave:
 2002    NYD2_LEAVE;
 2003    return rv;
 2004 }
 2005 
 2006 static bool_t
 2007 a_amv_var__putenv(struct a_amv_var_carrier *avcp, struct a_amv_var *avp){
 2008 #ifndef HAVE_SETENV
 2009    char *cp;
 2010 #endif
 2011    bool_t rv;
 2012    NYD2_ENTER;
 2013 
 2014 #ifdef HAVE_SETENV
 2015    rv = (setenv(avcp->avc_name, avp->av_value, 1) == 0);
 2016 #else
 2017    cp = sstrdup(savecatsep(avcp->avc_name, '=', avp->av_value));
 2018 
 2019    if((rv = (putenv(cp) == 0))){
 2020       char *ocp;
 2021 
 2022       ocp = avp->av_env;
 2023       avp->av_env = cp;
 2024       cp = ocp;
 2025    }
 2026 
 2027    if(cp != NULL)
 2028       n_free(cp);
 2029 #endif
 2030    NYD2_LEAVE;
 2031    return rv;
 2032 }
 2033 
 2034 static bool_t
 2035 a_amv_var_clear(struct a_amv_var_carrier *avcp,
 2036       enum a_amv_var_setclr_flags avscf){
 2037    struct a_amv_var **avpp, *avp;
 2038    ui32_t f;
 2039    struct a_amv_var_map const *avmp;
 2040    bool_t rv;
 2041    NYD2_ENTER;
 2042 
 2043    rv = FAL0;
 2044    f = 0;
 2045 
 2046    if(n_LIKELY((avmp = avcp->avc_map) != NULL)){
 2047       if(n_UNLIKELY(((f = avmp->avm_flags) & a_AMV_VF_NODEL) != 0 &&
 2048             !(n_pstate & n_PS_ROOT))){
 2049          n_err(_("Variable may not be unset: %s\n"), avcp->avc_name);
 2050          goto jleave;
 2051       }
 2052       if(n_UNLIKELY((f & a_AMV_VF_VIP) != 0 &&
 2053             !a_amv_var_check_vips(a_AMV_VIP_CLEAR, avcp->avc_okey, NULL))){
 2054          n_err(_("Clearance of variable aborted: %s\n"), avcp->avc_name);
 2055          goto jleave;
 2056       }
 2057    }
 2058 
 2059    rv = TRU1;
 2060 
 2061    if(n_UNLIKELY(!a_amv_var_lookup(avcp,
 2062          (((avscf & a_AMV_VSETCLR_LOCAL)
 2063             ? (a_AMV_VLOOK_LOCAL | a_AMV_VLOOK_LOCAL_ONLY)
 2064             : a_AMV_VLOOK_LOCAL) |
 2065           a_AMV_VLOOK_I3VAL_NONEW | a_AMV_VLOOK_I3VAL_NONEW_REPORT)))){
 2066       assert(avcp->avc_var == NULL);
 2067       /* This may be a clearance request from the command line, via -S, and we
 2068        * need to keep track of that!  Unfortunately we are not prepared for
 2069        * this, really, so we need to create a fake entry that is known and
 2070        * handled correctly by the lowermost variable layer!
 2071        * However, all this cannot happen for plain unset of `local' variables */
 2072       if(avscf & a_AMV_VSETCLR_LOCAL)
 2073          goto jleave;
 2074       if(n_UNLIKELY(!(n_psonce & n_PSO_STARTED_GETOPT)) &&
 2075             (n_poption & n_PO_S_FLAG_TEMPORARY)) Jfreeze:{
 2076          size_t l;
 2077 
 2078          l = strlen(avcp->avc_name) +1;
 2079          avp = n_calloc(1, n_VSTRUCT_SIZEOF(struct a_amv_var, av_name) + l);
 2080          avp->av_link = *(avpp = &a_amv_vars[avcp->avc_prime]);
 2081          *avpp = avp;
 2082          avp->av_value = n_UNCONST(n_empty); /* Sth. covered by _var_free()! */
 2083          assert(f == (avmp != NULL ? avmp->avm_flags : 0));
 2084          avp->av_flags = f | a_AMV_VF_EXT_FROZEN | a_AMV_VF_EXT_FROZEN_UNSET;
 2085          memcpy(avp->av_name, avcp->avc_name, l);
 2086 
 2087          if((avscf & a_AMV_VSETCLR_ENV) || (f & a_AMV_VF_ENV))
 2088             a_amv_var__clearenv(avcp->avc_name, NULL);
 2089       }else if(avscf & a_AMV_VSETCLR_ENV){
 2090 jforce_env:
 2091          if(!(rv = a_amv_var__clearenv(avcp->avc_name, NULL)))
 2092             goto jerr_env_unset;
 2093       }else{
 2094          /* TODO "cannot unset undefined variable" not echoed in "ROBOT" state,
 2095           * TODO should only be like that with "ignerr"! */
 2096 jerr_env_unset:
 2097          if(!(n_pstate & (n_PS_ROOT | n_PS_ROBOT)) && (n_poption & n_PO_D_V))
 2098             n_err(_("Cannot unset undefined variable: %s\n"), avcp->avc_name);
 2099       }
 2100       goto jleave;
 2101    }else if((avp = avcp->avc_var) == (struct a_amv_var*)-1){
 2102       /* Clearance request from command line, via -S?  As above.. */
 2103       if(n_UNLIKELY(!(n_psonce & n_PSO_STARTED_GETOPT) &&
 2104             (n_poption & n_PO_S_FLAG_TEMPORARY) != 0))
 2105          goto Jfreeze;
 2106       avcp->avc_var = NULL;
 2107       if(avscf & a_AMV_VSETCLR_ENV)
 2108          goto jforce_env;
 2109       goto jleave;
 2110    }
 2111    assert(avcp->avc_var != NULL);
 2112 
 2113    /* `local' variables bypass "frozen" checks and `localopts' coverage etc. */
 2114    if((f = avp->av_flags) & a_AMV_VF_EXT_LOCAL)
 2115       goto jdefault_path;
 2116 
 2117    /* If this setting has been established via -S and we still have not reached
 2118     * the _STARTED_CONFIG program state, silently ignore request!
 2119     * XXX All this is very complicated for the tenth of a second */
 2120    /*else*/ if(n_UNLIKELY((f & a_AMV_VF_EXT__FROZEN_MASK) != 0)){
 2121       if(!(n_psonce & n_PSO_STARTED_CONFIG)){
 2122          if((n_pstate & n_PS_ROOT) ||
 2123                (!(n_psonce & n_PSO_STARTED_GETOPT) &&
 2124                 (n_poption & n_PO_S_FLAG_TEMPORARY))){
 2125             /* Be aware this may turn a set into an unset! */
 2126             if(!(f & a_AMV_VF_EXT_FROZEN_UNSET)){
 2127                if(f & a_AMV_VF_DEFVAL)
 2128                   goto jdefault_path;
 2129                a_amv_var_free(avp->av_value);
 2130                f |= a_AMV_VF_EXT_FROZEN_UNSET;
 2131                avp->av_flags = f;
 2132                avp->av_value = n_UNCONST(n_empty); /* _var_free() covered */
 2133                if(f & (a_AMV_VF_ENV | a_AMV_VF_EXT_LINKED))
 2134                   goto jforce_env;
 2135             }
 2136             goto jleave;
 2137          }
 2138          if(n_poption & n_PO_D_VV)
 2139             n_err(_("Temporarily frozen by -S, not `unset'ting: %s\n"),
 2140                avcp->avc_name);
 2141          goto jleave;
 2142       }
 2143       f &= ~a_AMV_VF_EXT__FROZEN_MASK;
 2144       avp->av_flags = f;
 2145    }
 2146 
 2147    if(n_UNLIKELY(a_amv_lopts != NULL) &&
 2148          (avmp == NULL || !(avmp->avm_flags & a_AMV_VF_NOLOPTS)))
 2149       a_amv_lopts_add(a_amv_lopts, avcp->avc_name, avcp->avc_var);
 2150 
 2151 jdefault_path:
 2152    assert(avp == avcp->avc_var);
 2153    avcp->avc_var = NULL;
 2154    avpp = &(((f = avp->av_flags) & a_AMV_VF_EXT_LOCAL)
 2155          ? *a_amv_lopts->as_amcap->amca_local_vars : a_amv_vars
 2156          )[avcp->avc_prime];
 2157    assert(*avpp == avp); /* (always listhead after lookup()) */
 2158    *avpp = (*avpp)->av_link;
 2159 
 2160    if(f & (a_AMV_VF_ENV | a_AMV_VF_EXT_LINKED))
 2161       rv = a_amv_var__clearenv(avp->av_name, avp);
 2162    a_amv_var_free(avp->av_value);
 2163    n_free(avp);
 2164 
 2165    /* XXX Fun part, extremely simple-minded for now: if this variable has
 2166     * XXX a default value, immediately reinstantiate it!  TODO Heh? */
 2167    /* xxx Simply assuming we will never have default values for actual
 2168     * xxx -HOST or -USER@HOST chain extensions */
 2169    if(n_UNLIKELY(avmp != NULL && (avmp->avm_flags & a_AMV_VF_DEFVAL) != 0)){
 2170       a_amv_var_lookup(avcp, a_AMV_VLOOK_I3VAL_NONEW);
 2171       if(n_UNLIKELY(!(n_psonce & n_PSO_STARTED_GETOPT)) &&
 2172             (n_poption & n_PO_S_FLAG_TEMPORARY))
 2173          avcp->avc_var->av_flags |= a_AMV_VF_EXT_FROZEN;
 2174    }
 2175 jleave:
 2176    NYD2_LEAVE;
 2177    return rv;
 2178 }
 2179 
 2180 static bool_t
 2181 a_amv_var__clearenv(char const *name, struct a_amv_var *avp){
 2182    extern char **environ;
 2183    char **ecpp;
 2184    bool_t rv;
 2185    NYD2_ENTER;
 2186    n_UNUSED(avp);
 2187 
 2188    rv = FAL0;
 2189    ecpp = environ;
 2190 
 2191 #ifndef HAVE_SETENV
 2192    if(avp != NULL && avp->av_env != NULL){
 2193       for(; *ecpp != NULL; ++ecpp)
 2194          if(*ecpp == avp->av_env){
 2195             do
 2196                ecpp[0] = ecpp[1];
 2197             while(*ecpp++ != NULL);
 2198             n_free(avp->av_env);
 2199             avp->av_env = NULL;
 2200             rv = TRU1;
 2201             break;
 2202          }
 2203    }else
 2204 #endif
 2205    {
 2206       size_t l;
 2207 
 2208       if((l = strlen(name)) > 0){
 2209          for(; *ecpp != NULL; ++ecpp)
 2210             if(!strncmp(*ecpp, name, l) && (*ecpp)[l] == '='){
 2211 #ifdef HAVE_SETENV
 2212                unsetenv(name);
 2213 #else
 2214                do
 2215                   ecpp[0] = ecpp[1];
 2216                while(*ecpp++ != NULL);
 2217 #endif
 2218                rv = TRU1;
 2219                break;
 2220             }
 2221       }
 2222    }
 2223    NYD2_LEAVE;
 2224    return rv;
 2225 }
 2226 
 2227 static void
 2228 a_amv_var_show_all(void){
 2229    struct n_string msg, *msgp;
 2230    FILE *fp;
 2231    size_t no, i;
 2232    struct a_amv_var *avp;
 2233    char const **vacp, **cap;
 2234    NYD2_ENTER;
 2235 
 2236    if((fp = Ftmp(NULL, "setlist", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL){
 2237       n_perr(_("`set' list: cannot create temporary file"), 0);
 2238       goto jleave;
 2239    }
 2240 
 2241    /* We need to instantiate first-time-inits and default values here, so that
 2242     * they will be regular members of our _vars[] table */
 2243    for(i = a_AMV_VAR_I3VALS_CNT; i-- > 0;)
 2244       n_var_oklook(a_amv_var_i3vals[i].avdv_okey);
 2245    for(i = a_AMV_VAR_DEFVALS_CNT; i-- > 0;)
 2246       n_var_oklook(a_amv_var_defvals[i].avdv_okey);
 2247 
 2248    for(no = i = 0; i < a_AMV_PRIME; ++i)
 2249       for(avp = a_amv_vars[i]; avp != NULL; avp = avp->av_link)
 2250          ++no;
 2251    no += a_AMV_VAR_VIRTS_CNT;
 2252 
 2253    vacp = n_autorec_alloc(no * sizeof(*vacp));
 2254 
 2255    for(cap = vacp, i = 0; i < a_AMV_PRIME; ++i)
 2256       for(avp = a_amv_vars[i]; avp != NULL; avp = avp->av_link)
 2257          *cap++ = avp->av_name;
 2258    for(i = a_AMV_VAR_VIRTS_CNT; i-- > 0;)
 2259       *cap++ = a_amv_var_virts[i].avv_var->av_name;
 2260 
 2261    if(no > 1)
 2262       qsort(vacp, no, sizeof *vacp, &a_amv_var__show_cmp);
 2263 
 2264    msgp = &msg;
 2265    msgp = n_string_reserve(n_string_creat(msgp), 80);
 2266    for(i = 0, cap = vacp; no != 0; ++cap, --no)
 2267       i += a_amv_var_show(*cap, fp, msgp);
 2268    n_string_gut(&msg);
 2269 
 2270    page_or_print(fp, i);
 2271    Fclose(fp);
 2272 jleave:
 2273    NYD2_LEAVE;
 2274 }
 2275 
 2276 static int
 2277 a_amv_var__show_cmp(void const *s1, void const *s2){
 2278    int rv;
 2279    NYD2_ENTER;
 2280 
 2281    rv = strcmp(*(char**)n_UNCONST(s1), *(char**)n_UNCONST(s2));
 2282    NYD2_LEAVE;
 2283    return rv;
 2284 }
 2285 
 2286 static size_t
 2287 a_amv_var_show(char const *name, FILE *fp, struct n_string *msgp){
 2288    /* XXX a_amv_var_show(): if we iterate over all the actually set variables
 2289     * XXX via a_amv_var_show_all() there is no need to call
 2290     * XXX a_amv_var_revlookup() at all!  Revisit this call chain */
 2291    struct a_amv_var_carrier avc;
 2292    char const *quote;
 2293    struct a_amv_var *avp;
 2294    bool_t isset;
 2295    size_t i;
 2296    NYD2_ENTER;
 2297 
 2298    msgp = n_string_trunc(msgp, 0);
 2299    i = 0;
 2300 
 2301    a_amv_var_revlookup(&avc, name, TRU1);
 2302    isset = a_amv_var_lookup(&avc, a_AMV_VLOOK_NONE);
 2303    avp = avc.avc_var;
 2304 
 2305    if(n_poption & n_PO_D_V){
 2306       if(avc.avc_map == NULL){
 2307          msgp = n_string_push_cp(msgp, "#assembled variable with value");
 2308          i = 1;
 2309       }else{
 2310          struct{
 2311             ui16_t flag;
 2312             char msg[22];
 2313          } const tbase[] = {
 2314             {a_AMV_VF_CHAIN, "variable chain"},
 2315             {a_AMV_VF_VIRT, "virtual"},
 2316             {a_AMV_VF_RDONLY, "read-only"},
 2317             {a_AMV_VF_NODEL, "nodelete"},
 2318             {a_AMV_VF_I3VAL, "initial-value"},
 2319             {a_AMV_VF_DEFVAL, "default-value"},
 2320             {a_AMV_VF_IMPORT, "import-environ-first\0"}, /* assert NUL in max */
 2321             {a_AMV_VF_ENV, "sync-environ"},
 2322             {a_AMV_VF_NOLOPTS, "no-localopts"},
 2323             {a_AMV_VF_NOTEMPTY, "notempty"},
 2324             {a_AMV_VF_NUM, "number"},
 2325             {a_AMV_VF_POSNUM, "positive-number"},
 2326             {a_AMV_VF_OBSOLETE, "obsoleted"},
 2327          }, *tp;
 2328          assert(!isset || ((avp->av_flags & a_AMV_VF__MASK) ==
 2329             (avc.avc_map->avm_flags & a_AMV_VF__MASK)));
 2330 
 2331          for(tp = tbase; PTRCMP(tp, <, &tbase[n_NELEM(tbase)]); ++tp)
 2332             if(isset ? (avp->av_flags & tp->flag)
 2333                   : (avc.avc_map->avm_flags & tp->flag)){
 2334                msgp = n_string_push_c(msgp, (i++ == 0 ? '#' : ','));
 2335                msgp = n_string_push_cp(msgp, tp->msg);
 2336                if(isset){
 2337                   if((tp->flag == a_AMV_VF_CHAIN) &&
 2338                         (avp->av_flags & a_AMV_VF_EXT_CHAIN))
 2339                      msgp = n_string_push_cp(msgp, " (extension)");
 2340                }
 2341             }
 2342       }
 2343 
 2344       if(isset){
 2345          if(avp->av_flags & a_AMV_VF_EXT_FROZEN){
 2346             msgp = n_string_push_c(msgp, (i++ == 0 ? '#' : ','));
 2347             msgp = n_string_push_cp(msgp, "(un)?set via -S");
 2348          }
 2349       }
 2350 
 2351       if(i > 0)
 2352          msgp = n_string_push_cp(msgp, "\n  ");
 2353    }
 2354 
 2355    /* (Read-only variables are generally shown via comments..) */
 2356    if(!isset || (avp->av_flags & a_AMV_VF_RDONLY)){
 2357       msgp = n_string_push_c(msgp, n_ns[0]);
 2358       if(!isset){
 2359          if(avc.avc_map != NULL && (avc.avc_map->avm_flags & a_AMV_VF_BOOL))
 2360             msgp = n_string_push_cp(msgp, "boolean; ");
 2361          msgp = n_string_push_cp(msgp, "variable not set: ");
 2362          msgp = n_string_push_cp(msgp, n_shexp_quote_cp(name, FAL0));
 2363          goto jleave;
 2364       }
 2365    }
 2366 
 2367    n_UNINIT(quote, NULL);
 2368    if(!(avp->av_flags & a_AMV_VF_BOOL)){
 2369       quote = n_shexp_quote_cp(avp->av_value, TRU1);
 2370       if(strcmp(quote, avp->av_value))
 2371          msgp = n_string_push_cp(msgp, "wysh ");
 2372    }else if(n_poption & n_PO_D_V)
 2373       msgp = n_string_push_cp(msgp, "wysh "); /* (for shell-style comment) */
 2374 
 2375    if(avp->av_flags & a_AMV_VF_EXT_LINKED)
 2376       msgp = n_string_push_cp(msgp, "environ ");
 2377    msgp = n_string_push_cp(msgp, "set ");
 2378    msgp = n_string_push_cp(msgp, name);
 2379 
 2380    if(!(avp->av_flags & a_AMV_VF_BOOL)){
 2381       msgp = n_string_push_c(msgp, '=');
 2382       msgp = n_string_push_cp(msgp, quote);
 2383    }else if(n_poption & n_PO_D_V)
 2384       msgp = n_string_push_cp(msgp, " #boolean");
 2385 
 2386 jleave:
 2387    msgp = n_string_push_c(msgp, '\n');
 2388    fputs(n_string_cp(msgp), fp);
 2389    NYD2_ENTER;
 2390    return (i > 0 ? 2 : 1);
 2391 }
 2392 
 2393 static bool_t
 2394 a_amv_var_c_set(char **ap, enum a_amv_var_setclr_flags avscf){
 2395    char *cp, *cp2, *varbuf, c;
 2396    size_t errs;
 2397    NYD2_ENTER;
 2398 
 2399    errs = 0;
 2400 jouter:
 2401    while((cp = *ap++) != NULL){
 2402       /* Isolate key */
 2403       cp2 = varbuf = n_autorec_alloc(strlen(cp) +1);
 2404 
 2405       for(; (c = *cp) != '=' && c != '\0'; ++cp){
 2406          if(cntrlchar(c) || spacechar(c)){
 2407             n_err(_("Variable name with control or space character ignored: "
 2408                "%s\n"), ap[-1]);
 2409             ++errs;
 2410             goto jouter;
 2411          }
 2412          *cp2++ = c;
 2413       }
 2414       *cp2 = '\0';
 2415       if(c == '\0')
 2416          cp = n_UNCONST(n_empty);
 2417       else
 2418          ++cp;
 2419 
 2420       if(varbuf == cp2){
 2421          n_err(_("Empty variable name ignored\n"));
 2422          ++errs;
 2423       }else{
 2424          struct a_amv_var_carrier avc;
 2425          bool_t isunset;
 2426 
 2427          if((isunset = (varbuf[0] == 'n' && varbuf[1] == 'o')))
 2428             varbuf = &varbuf[2];
 2429 
 2430          a_amv_var_revlookup(&avc, varbuf, TRU1);
 2431 
 2432          if((avscf & a_AMV_VSETCLR_LOCAL) && avc.avc_map != NULL){
 2433             if(n_poption & n_PO_D_V)
 2434                n_err(_("Builtin variable not overwritten by `local': %s\n"),
 2435                   varbuf);
 2436             ++errs;
 2437          }else if(isunset)
 2438             errs += !a_amv_var_clear(&avc, avscf);
 2439          else
 2440             errs += !a_amv_var_set(&avc, cp, avscf);
 2441       }
 2442    }
 2443    NYD2_LEAVE;
 2444    return (errs == 0);
 2445 }
 2446 
 2447 FL int
 2448 c_define(void *v){
 2449    int rv;
 2450    char **args;
 2451    NYD_ENTER;
 2452 
 2453    rv = 1;
 2454 
 2455    if((args = v)[0] == NULL){
 2456       rv = (a_amv_mac_show(a_AMV_MF_NONE) == FAL0);
 2457       goto jleave;
 2458    }
 2459 
 2460    if(args[1] == NULL || args[1][0] != '{' || args[1][1] != '\0' ||
 2461          args[2] != NULL){
 2462       n_err(_("Synopsis: define: <name> {\n"));
 2463       goto jleave;
 2464    }
 2465 
 2466    rv = (a_amv_mac_def(args[0], a_AMV_MF_NONE) == FAL0);
 2467 jleave:
 2468    NYD_LEAVE;
 2469    return rv;
 2470 }
 2471 
 2472 FL int
 2473 c_undefine(void *v){
 2474    int rv;
 2475    char **args;
 2476    NYD_ENTER;
 2477 
 2478    rv = 0;
 2479    args = v;
 2480    do
 2481       rv |= !a_amv_mac_undef(*args, a_AMV_MF_NONE);
 2482    while(*++args != NULL);
 2483    NYD_LEAVE;
 2484    return rv;
 2485 }
 2486 
 2487 FL int
 2488 c_call(void *vp){
 2489    int rv;
 2490    NYD_ENTER;
 2491 
 2492    rv = a_amv_mac_call(vp, FAL0);
 2493    NYD_LEAVE;
 2494    return rv;
 2495 }
 2496 
 2497 FL int
 2498 c_call_if(void *vp){
 2499    int rv;
 2500    NYD_ENTER;
 2501 
 2502    rv = a_amv_mac_call(vp, TRU1);
 2503    NYD_LEAVE;
 2504    return rv;
 2505 }
 2506 
 2507 FL int
 2508 c_account(void *v){
 2509    struct a_amv_mac_call_args *amcap;
 2510    struct a_amv_mac *amp;
 2511    char **args;
 2512    int rv, i, oqf, nqf;
 2513    NYD_ENTER;
 2514 
 2515    rv = 1;
 2516 
 2517    if((args = v)[0] == NULL){
 2518       rv = (a_amv_mac_show(a_AMV_MF_ACCOUNT) == FAL0);
 2519       goto jleave;
 2520    }
 2521 
 2522    if(args[1] && args[1][0] == '{' && args[1][1] == '\0'){
 2523       if(args[2] != NULL){
 2524          n_err(_("Synopsis: account: <name> {\n"));
 2525          goto jleave;
 2526       }
 2527       if(!asccasecmp(args[0], ACCOUNT_NULL)){
 2528          n_err(_("`account': cannot use reserved name: %s\n"),
 2529             ACCOUNT_NULL);
 2530          goto jleave;
 2531       }
 2532       rv = (a_amv_mac_def(args[0], a_AMV_MF_ACCOUNT) == FAL0);
 2533       goto jleave;
 2534    }
 2535 
 2536    if(n_pstate & n_PS_HOOK_MASK){
 2537       n_err(_("`account': cannot change account from within a hook\n"));
 2538       goto jleave;
 2539    }
 2540 
 2541    save_mbox_for_possible_quitstuff();
 2542 
 2543    amp = NULL;
 2544    if(asccasecmp(args[0], ACCOUNT_NULL) != 0 &&
 2545          (amp = a_amv_mac_lookup(args[0], NULL, a_AMV_MF_ACCOUNT)) == NULL){
 2546       n_err(_("`account': account does not exist: %s\n"), args[0]);
 2547       goto jleave;
 2548    }
 2549 
 2550    oqf = savequitflags();
 2551 
 2552    /* Shutdown the active account */
 2553    if(a_amv_acc_curr != NULL){
 2554       char const *cp;
 2555       char *var;
 2556 
 2557       /* Is there a cleanup hook? */
 2558       var = savecat("on-account-cleanup-", a_amv_acc_curr->am_name);
 2559       if((cp = n_var_vlook(var, FAL0)) != NULL ||
 2560             (cp = ok_vlook(on_account_cleanup)) != NULL){
 2561          struct a_amv_mac *amphook;
 2562 
 2563          if((amphook = a_amv_mac_lookup(cp, NULL, a_AMV_MF_NONE)) != NULL){
 2564             amcap = n_lofi_alloc(sizeof *amcap);
 2565             memset(amcap, 0, sizeof *amcap);
 2566             amcap->amca_name = cp;
 2567             amcap->amca_amp = amphook;
 2568             amcap->amca_unroller = &a_amv_acc_curr->am_lopts;
 2569             amcap->amca_loflags = a_AMV_LF_SCOPE_FIXATE;
 2570             amcap->amca_no_xcall = TRU1;
 2571             n_pstate |= n_PS_HOOK;
 2572             rv = a_amv_mac_exec(amcap);
 2573             n_pstate &= ~n_PS_HOOK_MASK;
 2574          }else
 2575             n_err(_("*on-account-leave* hook %s does not exist\n"),
 2576                n_shexp_quote_cp(cp, FAL0));
 2577       }
 2578 
 2579       /* `localopts'? */
 2580       if(a_amv_acc_curr->am_lopts != NULL)
 2581          a_amv_lopts_unroll(&a_amv_acc_curr->am_lopts);
 2582 
 2583       /* For accounts this lingers */
 2584       --a_amv_acc_curr->am_refcnt;
 2585       if(a_amv_acc_curr->am_flags & a_AMV_MF_DELETE)
 2586          a_amv_mac_free(a_amv_acc_curr);
 2587    }
 2588 
 2589    a_amv_acc_curr = amp;
 2590 
 2591    /* And switch to any non-"null" account */
 2592    if(amp != NULL){
 2593       assert(amp->am_lopts == NULL);
 2594       amcap = n_lofi_alloc(sizeof *amcap);
 2595       memset(amcap, 0, sizeof *amcap);
 2596       amcap->amca_name = amp->am_name;
 2597       amcap->amca_amp = amp;
 2598       amcap->amca_unroller = &amp->am_lopts;
 2599       amcap->amca_loflags = a_AMV_LF_SCOPE_FIXATE;
 2600       amcap->amca_no_xcall = TRU1;
 2601       ++amp->am_refcnt; /* We may not run 0 to avoid being deleted! */
 2602       if(!a_amv_mac_exec(amcap)){
 2603          /* XXX account switch incomplete, unroll? */
 2604          n_err(_("`account': failed to switch to account: %s\n"), amp->am_name);
 2605          goto jleave;
 2606       }
 2607    }
 2608 
 2609    n_PS_ROOT_BLOCK((amp != NULL ? ok_vset(account, amp->am_name)
 2610       : ok_vclear(account)));
 2611 
 2612    /* Otherwise likely initial setfile() in a_main_rcv_mode() will pick up */
 2613    if(n_psonce & n_PSO_STARTED){
 2614       assert(!(n_pstate & n_PS_HOOK_MASK));
 2615       nqf = savequitflags(); /* TODO obsolete (leave -> void -> new box!) */
 2616       restorequitflags(oqf);
 2617       i = setfile("%", FEDIT_SYSBOX | FEDIT_ACCOUNT);
 2618       restorequitflags(nqf);
 2619       if(i < 0)
 2620          goto jleave;
 2621       temporary_folder_hook_check(FAL0);
 2622       if(i != 0 && !ok_blook(emptystart)) /* Avoid annoying "double message" */
 2623          goto jleave;
 2624       n_folder_announce(n_ANNOUNCE_CHANGE);
 2625    }
 2626    rv = 0;
 2627 jleave:
 2628    NYD_LEAVE;
 2629    return rv;
 2630 }
 2631 
 2632 FL int
 2633 c_unaccount(void *v){
 2634    int rv;
 2635    char **args;
 2636    NYD_ENTER;
 2637 
 2638    rv = 0;
 2639    args = v;
 2640    do
 2641       rv |= !a_amv_mac_undef(*args, a_AMV_MF_ACCOUNT);
 2642    while(*++args != NULL);
 2643    NYD_LEAVE;
 2644    return rv;
 2645 }
 2646 
 2647 FL int
 2648 c_localopts(void *vp){
 2649    enum a_amv_loflags alf, alm;
 2650    char const **argv;
 2651    int rv;
 2652    NYD_ENTER;
 2653 
 2654    rv = 1;
 2655 
 2656    if(a_amv_lopts == NULL){
 2657       n_err(_("Cannot use `localopts' in this context\n"));
 2658       goto jleave;
 2659    }
 2660 
 2661    if((argv = vp)[1] == NULL || is_asccaseprefix((++argv)[-1], "scope"))
 2662       alf = alm = a_AMV_LF_SCOPE;
 2663    else if(is_asccaseprefix(argv[-1], "call"))
 2664       alf = a_AMV_LF_CALL, alm = a_AMV_LF_CALL_MASK;
 2665    else if(is_asccaseprefix(argv[-1], "call-fixate"))
 2666       alf = a_AMV_LF_CALL_FIXATE, alm = a_AMV_LF_CALL_MASK;
 2667    else{
 2668 jesynopsis:
 2669       n_err(_("Synopsis: localopts: [<scope|call|call-fixate>] <boolean>\n"));
 2670       goto jleave;
 2671    }
 2672 
 2673    if(alf == a_AMV_LF_SCOPE &&
 2674          (a_amv_lopts->as_loflags & a_AMV_LF_SCOPE_FIXATE)){
 2675       if(n_poption & n_PO_D_V)
 2676          n_err(_("Cannot turn off `localopts', setting is fixated\n"));
 2677       goto jleave;
 2678    }
 2679 
 2680    if((rv = n_boolify(*argv, UIZ_MAX, FAL0)) < FAL0)
 2681       goto jesynopsis;
 2682    a_amv_lopts->as_loflags &= ~alm;
 2683    if(rv > FAL0)
 2684       a_amv_lopts->as_loflags |= alf;
 2685    rv = 0;
 2686 jleave:
 2687    NYD_LEAVE;
 2688    return rv;
 2689 }
 2690 
 2691 FL int
 2692 c_shift(void *vp){ /* xxx move to bottom, not in macro part! */
 2693    struct a_amv_pospar *appp;
 2694    ui16_t i;
 2695    int rv;
 2696    NYD_ENTER;
 2697 
 2698    rv = 1;
 2699 
 2700    if((vp = *(char**)vp) == NULL)
 2701       i = 1;
 2702    else{
 2703       si16_t sib;
 2704 
 2705       if((n_idec_si16_cp(&sib, vp, 10, NULL
 2706                ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
 2707             ) != n_IDEC_STATE_CONSUMED || sib < 0){
 2708          n_err(_("`shift': invalid argument: %s\n"), vp);
 2709          goto jleave;
 2710       }
 2711       i = (ui16_t)sib;
 2712    }
 2713 
 2714    /* If in in a macro/xy */
 2715    if(a_amv_lopts != NULL){
 2716       struct a_amv_mac const *amp;
 2717       struct a_amv_mac_call_args *amcap;
 2718 
 2719       /* Explicitly do allow `vpospar' created things! */
 2720       amp = (amcap = a_amv_lopts->as_amcap)->amca_amp;
 2721       if((amp == NULL || amcap->amca_ps_hook_mask ||
 2722                (amp->am_flags & a_AMV_MF_TYPE_MASK) == a_AMV_MF_ACCOUNT) &&
 2723             amcap->amca_pospar.app_not_heap){
 2724          n_err(_("Cannot use `shift' in `account's or hook macros etc.\n"));
 2725          goto jleave;
 2726       }
 2727       appp = &amcap->amca_pospar;
 2728    }else
 2729       appp = &a_amv_pospar;
 2730 
 2731    if(i > appp->app_count){
 2732       n_err(_("`shift': cannot shift %hu of %hu parameters\n"),
 2733          i, appp->app_count);
 2734       goto jleave;
 2735    }else{
 2736       appp->app_idx += i;
 2737       appp->app_count -= i;
 2738       rv = 0;
 2739    }
 2740 jleave:
 2741    NYD_LEAVE;
 2742    return rv;
 2743 }
 2744 
 2745 FL int
 2746 c_return(void *vp){ /* TODO the exit status should be m_si64! */
 2747    int rv;
 2748    NYD_ENTER;
 2749 
 2750    if(a_amv_lopts != NULL){
 2751       char const **argv;
 2752 
 2753       n_go_input_force_eof();
 2754       n_pstate_err_no = n_ERR_NONE;
 2755       rv = 0;
 2756 
 2757       if((argv = vp)[0] != NULL){
 2758          si32_t i;
 2759 
 2760          if((n_idec_si32_cp(&i, argv[0], 10, NULL
 2761                   ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
 2762                ) == n_IDEC_STATE_CONSUMED && i >= 0)
 2763             rv = (int)i;
 2764          else{
 2765             n_err(_("`return': return value argument is invalid: %s\n"),
 2766                argv[0]);
 2767             n_pstate_err_no = n_ERR_INVAL;
 2768             rv = 1;
 2769          }
 2770 
 2771          if(argv[1] != NULL){
 2772             if((n_idec_si32_cp(&i, argv[1], 10, NULL
 2773                      ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
 2774                   ) == n_IDEC_STATE_CONSUMED && i >= 0)
 2775                n_pstate_err_no = i;
 2776             else{
 2777                n_err(_("`return': error number argument is invalid: %s\n"),
 2778                   argv[1]);
 2779                n_pstate_err_no = n_ERR_INVAL;
 2780                rv = 1;
 2781             }
 2782          }
 2783       }
 2784    }else{
 2785       n_err(_("Can only use `return' in a macro\n"));
 2786       n_pstate_err_no = n_ERR_OPNOTSUPP;
 2787       rv = 1;
 2788    }
 2789    NYD_LEAVE;
 2790    return rv;
 2791 }
 2792 
 2793 FL bool_t
 2794 temporary_folder_hook_check(bool_t nmail){ /* TODO temporary, v15: drop */
 2795    struct a_amv_mac_call_args *amcap;
 2796    struct a_amv_mac *amp;
 2797    size_t len;
 2798    char const *cp;
 2799    char *var;
 2800    bool_t rv;
 2801    NYD_ENTER;
 2802 
 2803    rv = TRU1;
 2804    var = n_autorec_alloc(len = strlen(mailname) + sizeof("folder-hook-") -1 +1);
 2805 
 2806    /* First try the fully resolved path */
 2807    snprintf(var, len, "folder-hook-%s", mailname);
 2808    if((cp = n_var_vlook(var, FAL0)) != NULL)
 2809       goto jmac;
 2810 
 2811    /* If we are under *folder*, try the usual +NAME syntax, too */
 2812    if(displayname[0] == '+'){
 2813       char *x;
 2814 
 2815       for(x = &mailname[len]; x != mailname; --x)
 2816          if(x[-1] == '/'){
 2817             snprintf(var, len, "folder-hook-+%s", x);
 2818             if((cp = n_var_vlook(var, FAL0)) != NULL)
 2819                goto jmac;
 2820             break;
 2821          }
 2822    }
 2823 
 2824    /* Plain *folder-hook* is our last try */
 2825    if((cp = ok_vlook(folder_hook)) == NULL)
 2826       goto jleave;
 2827 
 2828 jmac:
 2829    if((amp = a_amv_mac_lookup(cp, NULL, a_AMV_MF_NONE)) == NULL){
 2830       n_err(_("Cannot call *folder-hook* for %s: macro does not exist: %s\n"),
 2831          n_shexp_quote_cp(displayname, FAL0), cp);
 2832       rv = FAL0;
 2833       goto jleave;
 2834    }
 2835 
 2836    amcap = n_lofi_alloc(sizeof *amcap);
 2837    memset(amcap, 0, sizeof *amcap);
 2838    amcap->amca_name = cp;
 2839    amcap->amca_amp = amp;
 2840    n_pstate &= ~n_PS_HOOK_MASK;
 2841    if(nmail){
 2842       amcap->amca_unroller = NULL;
 2843       n_pstate |= n_PS_HOOK_NEWMAIL;
 2844    }else{
 2845       amcap->amca_unroller = &a_amv_folder_hook_lopts;
 2846       n_pstate |= n_PS_HOOK;
 2847    }
 2848    amcap->amca_loflags = a_AMV_LF_SCOPE_FIXATE;
 2849    amcap->amca_ps_hook_mask = TRU1;
 2850    amcap->amca_no_xcall = TRU1;
 2851    rv = a_amv_mac_exec(amcap);
 2852    n_pstate &= ~n_PS_HOOK_MASK;
 2853 
 2854 jleave:
 2855    NYD_LEAVE;
 2856    return rv;
 2857 }
 2858 
 2859 FL void
 2860 temporary_folder_hook_unroll(void){ /* XXX intermediate hack */
 2861    NYD_ENTER;
 2862    if(a_amv_folder_hook_lopts != NULL){
 2863       void *save = a_amv_lopts;
 2864 
 2865       a_amv_lopts = NULL;
 2866       a_amv_lopts_unroll(&a_amv_folder_hook_lopts);
 2867       assert(a_amv_folder_hook_lopts == NULL);
 2868       a_amv_lopts = save;
 2869    }
 2870    NYD_LEAVE;
 2871 }
 2872 
 2873 FL void
 2874 temporary_compose_mode_hook_call(char const *macname,
 2875       void (*hook_pre)(void *), void *hook_arg){
 2876    /* TODO compose_mode_hook_call() temporary, v15: generalize; see a_GO_SPLICE
 2877     * TODO comment in go.c for the right way of doing things! */
 2878    static struct a_amv_lostack *cmh_losp;
 2879    struct a_amv_mac_call_args *amcap;
 2880    struct a_amv_mac *amp;
 2881    NYD_ENTER;
 2882 
 2883    amp = NULL;
 2884 
 2885    if(macname == (char*)-1){
 2886       a_amv_mac__finalize(cmh_losp);
 2887       cmh_losp = NULL;
 2888    }else if(macname != NULL &&
 2889          (amp = a_amv_mac_lookup(macname, NULL, a_AMV_MF_NONE)) == NULL)
 2890       n_err(_("Cannot call *on-compose-**: macro does not exist: %s\n"),
 2891          macname);
 2892    else{
 2893       amcap = n_lofi_alloc(sizeof *amcap);
 2894       memset(amcap, 0, sizeof *amcap);
 2895       amcap->amca_name = (macname != NULL) ? macname
 2896             : "*on-compose-splice(-shell)?*";
 2897       amcap->amca_amp = amp;
 2898       amcap->amca_unroller = &a_amv_compose_lopts;
 2899       amcap->amca_hook_pre = hook_pre;
 2900       amcap->amca_hook_arg = hook_arg;
 2901       amcap->amca_loflags = a_AMV_LF_SCOPE_FIXATE;
 2902       amcap->amca_ps_hook_mask = TRU1;
 2903       amcap->amca_no_xcall = TRU1;
 2904       n_pstate &= ~n_PS_HOOK_MASK;
 2905       n_pstate |= n_PS_HOOK;
 2906       if(macname != NULL)
 2907          a_amv_mac_exec(amcap);
 2908       else{
 2909          cmh_losp = n_lofi_alloc(sizeof *cmh_losp);
 2910          memset(cmh_losp, 0, sizeof *cmh_losp);
 2911          cmh_losp->as_global_saved = a_amv_lopts;
 2912          cmh_losp->as_lopts = *(cmh_losp->as_amcap = amcap)->amca_unroller;
 2913          cmh_losp->as_loflags = a_AMV_LF_SCOPE_FIXATE;
 2914          a_amv_lopts = cmh_losp;
 2915       }
 2916    }
 2917    NYD_LEAVE;
 2918 }
 2919 
 2920 FL void
 2921 temporary_compose_mode_hook_unroll(void){ /* XXX intermediate hack */
 2922    NYD_ENTER;
 2923    if(a_amv_compose_lopts != NULL){
 2924       void *save = a_amv_lopts;
 2925 
 2926       a_amv_lopts = NULL;
 2927       a_amv_lopts_unroll(&a_amv_compose_lopts);
 2928       assert(a_amv_compose_lopts == NULL);
 2929       a_amv_lopts = save;
 2930    }
 2931    NYD_LEAVE;
 2932 }
 2933 
 2934 FL bool_t
 2935 n_var_is_user_writable(char const *name){
 2936    struct a_amv_var_carrier avc;
 2937    struct a_amv_var_map const *avmp;
 2938    bool_t rv;
 2939    NYD_ENTER;
 2940 
 2941    a_amv_var_revlookup(&avc, name, TRU1);
 2942    if((avmp = avc.avc_map) == NULL)
 2943       rv = TRU1;
 2944    else
 2945       rv = ((avmp->avm_flags & (a_AMV_VF_BOOL | a_AMV_VF_RDONLY)) == 0);
 2946    NYD_LEAVE;
 2947    return rv;
 2948 }
 2949 
 2950 FL char *
 2951 n_var_oklook(enum okeys okey){
 2952    struct a_amv_var_carrier avc;
 2953    char *rv;
 2954    struct a_amv_var_map const *avmp;
 2955    NYD_ENTER;
 2956 
 2957    memset(&avc, 0, sizeof avc);
 2958    avc.avc_map = avmp = &a_amv_var_map[okey];
 2959    avc.avc_name = &a_amv_var_names[avmp->avm_keyoff];
 2960    avc.avc_hash = avmp->avm_hash;
 2961    avc.avc_okey = okey;
 2962 
 2963    if(a_amv_var_lookup(&avc, a_AMV_VLOOK_NONE))
 2964       rv = avc.avc_var->av_value;
 2965    else
 2966       rv = NULL;
 2967    NYD_LEAVE;
 2968    return rv;
 2969 }
 2970 
 2971 FL bool_t
 2972 n_var_okset(enum okeys okey, uintptr_t val){
 2973    struct a_amv_var_carrier avc;
 2974    bool_t ok;
 2975    struct a_amv_var_map const *avmp;
 2976    NYD_ENTER;
 2977 
 2978    memset(&avc, 0, sizeof avc);
 2979    avc.avc_map = avmp = &a_amv_var_map[okey];
 2980    avc.avc_name = &a_amv_var_names[avmp->avm_keyoff];
 2981    avc.avc_hash = avmp->avm_hash;
 2982    avc.avc_okey = okey;
 2983 
 2984    ok = a_amv_var_set(&avc, (val == 0x1 ? n_empty : (char const*)val),
 2985          a_AMV_VSETCLR_NONE);
 2986    NYD_LEAVE;
 2987    return ok;
 2988 }
 2989 
 2990 FL bool_t
 2991 n_var_okclear(enum okeys okey){
 2992    struct a_amv_var_carrier avc;
 2993    bool_t rv;
 2994    struct a_amv_var_map const *avmp;
 2995    NYD_ENTER;
 2996 
 2997    memset(&avc, 0, sizeof avc);
 2998    avc.avc_map = avmp = &a_amv_var_map[okey];
 2999    avc.avc_name = &a_amv_var_names[avmp->avm_keyoff];
 3000    avc.avc_hash = avmp->avm_hash;
 3001    avc.avc_okey = okey;
 3002 
 3003    rv = a_amv_var_clear(&avc, a_AMV_VSETCLR_NONE);
 3004    NYD_LEAVE;
 3005    return rv;
 3006 }
 3007 
 3008 FL char const *
 3009 n_var_vlook(char const *vokey, bool_t try_getenv){
 3010    struct a_amv_var_carrier avc;
 3011    char const *rv;
 3012    NYD_ENTER;
 3013 
 3014    a_amv_var_revlookup(&avc, vokey, FAL0);
 3015 
 3016    switch((enum a_amv_var_special_category)avc.avc_special_cat){
 3017    default: /* silence CC */
 3018    case a_AMV_VSC_NONE:
 3019       rv = NULL;
 3020       if(a_amv_var_lookup(&avc, a_AMV_VLOOK_LOCAL))
 3021          rv = avc.avc_var->av_value;
 3022       /* Only check the environment for something that is otherwise unknown */
 3023       else if(try_getenv && avc.avc_map == NULL &&
 3024             !a_amv_var_revlookup_chain(&avc, vokey))
 3025          rv = getenv(vokey);
 3026       break;
 3027    case a_AMV_VSC_GLOBAL:
 3028       rv = a_amv_var_vsc_global(&avc);
 3029       break;
 3030    case a_AMV_VSC_MULTIPLEX:
 3031       rv = a_amv_var_vsc_multiplex(&avc);
 3032       break;
 3033    case a_AMV_VSC_POSPAR:
 3034    case a_AMV_VSC_POSPAR_ENV:
 3035       rv = a_amv_var_vsc_pospar(&avc);
 3036       break;
 3037    }
 3038    NYD_LEAVE;
 3039    return rv;
 3040 }
 3041 
 3042 FL bool_t
 3043 n_var_vexplode(void const **cookie){
 3044    struct a_amv_pospar *appp;
 3045    NYD_ENTER;
 3046 
 3047    appp = (a_amv_lopts != NULL) ? &a_amv_lopts->as_amcap->amca_pospar
 3048          : &a_amv_pospar;
 3049    *cookie = (appp->app_count > 0) ? &appp->app_dat[appp->app_idx] : NULL;
 3050    NYD_LEAVE;
 3051    return (*cookie != NULL);
 3052 }
 3053 
 3054 FL bool_t
 3055 n_var_vset(char const *vokey, uintptr_t val){
 3056    struct a_amv_var_carrier avc;
 3057    bool_t ok;
 3058    NYD_ENTER;
 3059 
 3060    a_amv_var_revlookup(&avc, vokey, TRU1);
 3061 
 3062    ok = a_amv_var_set(&avc, (val == 0x1 ? n_empty : (char const*)val),
 3063          a_AMV_VSETCLR_NONE);
 3064    NYD_LEAVE;
 3065    return ok;
 3066 }
 3067 
 3068 FL bool_t
 3069 n_var_vclear(char const *vokey){
 3070    struct a_amv_var_carrier avc;
 3071    bool_t ok;
 3072    NYD_ENTER;
 3073 
 3074    a_amv_var_revlookup(&avc, vokey, FAL0);
 3075 
 3076    ok = a_amv_var_clear(&avc, a_AMV_VSETCLR_NONE);
 3077    NYD_LEAVE;
 3078    return ok;
 3079 }
 3080 
 3081 #ifdef HAVE_SOCKETS
 3082 FL char *
 3083 n_var_xoklook(enum okeys okey, struct url const *urlp,
 3084       enum okey_xlook_mode oxm){
 3085    struct a_amv_var_carrier avc;
 3086    struct str const *us;
 3087    size_t nlen;
 3088    char *nbuf, *rv;
 3089    NYD_ENTER;
 3090 
 3091    assert(oxm & (OXM_PLAIN | OXM_H_P | OXM_U_H_P));
 3092 
 3093    /* For simplicity: allow this case too */
 3094    if(!(oxm & (OXM_H_P | OXM_U_H_P))){
 3095       nbuf = NULL;
 3096       goto jplain;
 3097    }
 3098 
 3099    memset(&avc, 0, sizeof avc);
 3100    avc.avc_name = &a_amv_var_names[(avc.avc_map = &a_amv_var_map[okey]
 3101          )->avm_keyoff];
 3102    avc.avc_okey = okey;
 3103    avc.avc_is_chain_variant = TRU1;
 3104 
 3105    us = (oxm & OXM_U_H_P) ? &urlp->url_u_h_p : &urlp->url_h_p;
 3106    nlen = strlen(avc.avc_name);
 3107    nbuf = n_lofi_alloc(nlen + 1 + us->l +1);
 3108    memcpy(nbuf, avc.avc_name, nlen);
 3109 
 3110    /* One of .url_u_h_p and .url_h_p we test in here */
 3111    nbuf[nlen++] = '-';
 3112    memcpy(&nbuf[nlen], us->s, us->l +1);
 3113    avc.avc_name = nbuf;
 3114    avc.avc_hash = a_AMV_NAME2HASH(avc.avc_name);
 3115    if(a_amv_var_lookup(&avc, a_AMV_VLOOK_NONE))
 3116       goto jvar;
 3117 
 3118    /* The second */
 3119    if((oxm & (OXM_U_H_P | OXM_H_P)) == (OXM_U_H_P | OXM_H_P)){
 3120       us = &urlp->url_h_p;
 3121       memcpy(&nbuf[nlen], us->s, us->l +1);
 3122       avc.avc_name = nbuf;
 3123       avc.avc_hash = a_AMV_NAME2HASH(avc.avc_name);
 3124       if(a_amv_var_lookup(&avc, a_AMV_VLOOK_NONE)){
 3125 jvar:
 3126          rv = avc.avc_var->av_value;
 3127          goto jleave;
 3128       }
 3129    }
 3130 
 3131 jplain:
 3132    /*avc.avc_is_chain_variant = FAL0;*/
 3133    rv = (oxm & OXM_PLAIN) ? n_var_oklook(okey) : NULL;
 3134 jleave:
 3135    if(nbuf != NULL)
 3136       n_lofi_free(nbuf);
 3137    NYD_LEAVE;
 3138    return rv;
 3139 }
 3140 #endif /* HAVE_SOCKETS */
 3141 
 3142 FL int
 3143 c_set(void *vp){
 3144    int err;
 3145    char **ap;
 3146    NYD_ENTER;
 3147 
 3148    if(*(ap = vp) == NULL){
 3149       a_amv_var_show_all();
 3150       err = 0;
 3151    }else{
 3152       enum a_amv_var_setclr_flags avscf;
 3153 
 3154       if(!(n_pstate & n_PS_ARGMOD_LOCAL))
 3155          avscf = a_AMV_VSETCLR_NONE;
 3156       else{
 3157          if(a_amv_lopts == NULL){
 3158             n_err(_("`set': cannot use `local' in this context\n"));
 3159             err = 1;
 3160             goto jleave;
 3161          }
 3162          avscf = a_AMV_VSETCLR_LOCAL;
 3163       }
 3164       err = !a_amv_var_c_set(ap, avscf);
 3165    }
 3166 jleave:
 3167    NYD_LEAVE;
 3168    return err;
 3169 }
 3170 
 3171 FL int
 3172 c_unset(void *vp){
 3173    struct a_amv_var_carrier avc;
 3174    char **ap;
 3175    int err;
 3176    enum a_amv_var_setclr_flags avscf;
 3177    NYD_ENTER;
 3178 
 3179    if(!(n_pstate & n_PS_ARGMOD_LOCAL))
 3180       avscf = a_AMV_VSETCLR_NONE;
 3181    else{
 3182       if(a_amv_lopts == NULL){
 3183          n_err(_("`unset': cannot use `local' in this context\n"));
 3184          err = 1;
 3185          goto jleave;
 3186       }
 3187       avscf = a_AMV_VSETCLR_LOCAL;
 3188    }
 3189 
 3190    for(err = 0, ap = vp; *ap != NULL; ++ap){
 3191       a_amv_var_revlookup(&avc, *ap, FAL0);
 3192 
 3193       err |= !a_amv_var_clear(&avc, avscf);
 3194    }
 3195 jleave:
 3196    NYD_LEAVE;
 3197    return err;
 3198 }
 3199 
 3200 FL int
 3201 c_varshow(void *v){
 3202    char **ap;
 3203    NYD_ENTER;
 3204 
 3205    if(*(ap = v) == NULL)
 3206       v = NULL;
 3207    else{
 3208       struct n_string msg, *msgp = &msg;
 3209 
 3210       msgp = n_string_creat(msgp);
 3211       for(; *ap != NULL; ++ap)
 3212          a_amv_var_show(*ap, n_stdout, msgp);
 3213       n_string_gut(msgp);
 3214    }
 3215    NYD_LEAVE;
 3216    return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
 3217 }
 3218 
 3219 FL int
 3220 c_varedit(void *v){ /* TODO v15 drop */
 3221    struct a_amv_var_carrier avc;
 3222    FILE *of, *nf;
 3223    char *val, **argv;
 3224    int err;
 3225    sighandler_type sigint;
 3226    NYD_ENTER;
 3227 
 3228    sigint = safe_signal(SIGINT, SIG_IGN);
 3229 
 3230    for(err = 0, argv = v; *argv != NULL; ++argv){
 3231       a_amv_var_revlookup(&avc, *argv, TRU1);
 3232 
 3233       if(avc.avc_map != NULL){
 3234          if(avc.avc_map->avm_flags & a_AMV_VF_BOOL){
 3235             n_err(_("`varedit': cannot edit boolean variable: %s\n"),
 3236                avc.avc_name);
 3237             continue;
 3238          }
 3239          if(avc.avc_map->avm_flags & a_AMV_VF_RDONLY){
 3240             n_err(_("`varedit': cannot edit read-only variable: %s\n"),
 3241                avc.avc_name);
 3242             continue;
 3243          }
 3244       }
 3245 
 3246       a_amv_var_lookup(&avc, a_AMV_VLOOK_NONE);
 3247 
 3248       if((of = Ftmp(NULL, "varedit", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
 3249             NULL){
 3250          n_perr(_("`varedit': cannot create temporary file"), 0);
 3251          err = 1;
 3252          break;
 3253       }else if(avc.avc_var != NULL && *(val = avc.avc_var->av_value) != '\0' &&
 3254             sizeof *val != fwrite(val, strlen(val), sizeof *val, of)){
 3255          n_perr(_("`varedit' failed to write old value to temporary file"), 0);
 3256          Fclose(of);
 3257          err = 1;
 3258          continue;
 3259       }
 3260 
 3261       fflush_rewind(of);
 3262       nf = n_run_editor(of, (off_t)-1, 'e', FAL0, NULL,NULL, SEND_MBOX, sigint,
 3263             NULL);
 3264       Fclose(of);
 3265 
 3266       if(nf != NULL){
 3267          int c;
 3268          char const *varres;
 3269          off_t l;
 3270 
 3271          l = fsize(nf);
 3272          if(UICMP(64, l, >=, UIZ_MAX -42)){
 3273             n_err(_("`varedit': not enough memory to store variable: %s\n"),
 3274                avc.avc_name);
 3275             varres = n_empty;
 3276             err = 1;
 3277          }else{
 3278             varres = val = n_autorec_alloc(l +1);
 3279             for(; l > 0 && (c = getc(nf)) != EOF; --l)
 3280                *val++ = c;
 3281             *val++ = '\0';
 3282             if(l != 0){
 3283                n_err(_("`varedit': I/O while reading new value of: %s\n"),
 3284                   avc.avc_name);
 3285                err = 1;
 3286             }
 3287          }
 3288 
 3289          if(!a_amv_var_set(&avc, varres, a_AMV_VSETCLR_NONE))
 3290             err = 1;
 3291 
 3292          Fclose(nf);
 3293       }else{
 3294          n_err(_("`varedit': cannot start $EDITOR\n"));
 3295          err = 1;
 3296          break;
 3297       }
 3298    }
 3299 
 3300    safe_signal(SIGINT, sigint);
 3301    NYD_LEAVE;
 3302    return err;
 3303 }
 3304 
 3305 FL int
 3306 c_environ(void *v){
 3307    struct a_amv_var_carrier avc;
 3308    int err;
 3309    char **ap;
 3310    bool_t islnk;
 3311    NYD_ENTER;
 3312 
 3313    if((islnk = is_asccaseprefix(*(ap = v), "link")) ||
 3314          is_asccaseprefix(*ap, "unlink")){
 3315       for(err = 0; *++ap != NULL;){
 3316          a_amv_var_revlookup(&avc, *ap, TRU1);
 3317 
 3318          if(a_amv_var_lookup(&avc, a_AMV_VLOOK_NONE) && (islnk ||
 3319                (avc.avc_var->av_flags & a_AMV_VF_EXT_LINKED))){
 3320             if(!islnk){
 3321                avc.avc_var->av_flags &= ~a_AMV_VF_EXT_LINKED;
 3322                continue;
 3323             }else if(avc.avc_var->av_flags &
 3324                   (a_AMV_VF_ENV | a_AMV_VF_EXT_LINKED)){
 3325                if(n_poption & n_PO_D_V)
 3326                   n_err(_("`environ': link: already established: %s\n"), *ap);
 3327                continue;
 3328             }
 3329             avc.avc_var->av_flags |= a_AMV_VF_EXT_LINKED;
 3330             if(!(avc.avc_var->av_flags & a_AMV_VF_ENV))
 3331                a_amv_var__putenv(&avc, avc.avc_var);
 3332          }else if(!islnk){
 3333             n_err(_("`environ': unlink: no link established: %s\n"), *ap);
 3334             err = 1;
 3335          }else{
 3336             char const *evp = getenv(*ap);
 3337 
 3338             if(evp != NULL)
 3339                err |= !a_amv_var_set(&avc, evp, a_AMV_VSETCLR_ENV);
 3340             else{
 3341                n_err(_("`environ': link: cannot link to non-existent: %s\n"),
 3342                   *ap);
 3343                err = 1;
 3344             }
 3345          }
 3346       }
 3347    }else if(is_asccaseprefix(*ap, "set"))
 3348       err = !a_amv_var_c_set(++ap, a_AMV_VSETCLR_ENV);
 3349    else if(is_asccaseprefix(*ap, "unset")){
 3350       for(err = 0; *++ap != NULL;){
 3351          a_amv_var_revlookup(&avc, *ap, FAL0);
 3352 
 3353          if(!a_amv_var_clear(&avc, a_AMV_VSETCLR_ENV))
 3354             err = 1;
 3355       }
 3356    }else{
 3357       n_err(_("Synopsis: environ: <link|set|unset> <variable>...\n"));
 3358       err = 1;
 3359    }
 3360    NYD_LEAVE;
 3361    return err;
 3362 }
 3363 
 3364 FL int
 3365 c_vexpr(void *v){ /* TODO POSIX expr(1) comp. exit status; overly complicat. */
 3366    char pbase, op, iencbuf[2+1/* BASE# prefix*/ + n_IENC_BUFFER_SIZE + 1];
 3367    size_t i;
 3368    enum n_idec_state ids;
 3369    enum n_idec_mode idm;
 3370    si64_t lhv, rhv;
 3371    char const **argv, *varname, *varres, *cp;
 3372    enum{
 3373       a_ERR = 1u<<0,
 3374       a_SOFTOVERFLOW = 1u<<1,
 3375       a_ISNUM = 1u<<2,
 3376       a_ISDECIMAL = 1u<<3,    /* Print only decimal result */
 3377       a_SATURATED = 1u<<4,
 3378       a_ICASE = 1u<<5,
 3379       a_UNSIGNED_OP = 1u<<6,  /* Unsigned right shift (share bit ok) */
 3380       a_PBASE = 1u<<7,        /* Print additional number base */
 3381       a_TMP = 1u<<30
 3382    } f;
 3383    NYD_ENTER;
 3384 
 3385    f = a_ERR;
 3386    argv = v;
 3387    varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
 3388    n_UNINIT(varres, n_empty);
 3389    n_UNINIT(pbase, '\0');
 3390 
 3391    if((cp = argv[0])[0] == '\0')
 3392       goto jesubcmd;
 3393 
 3394    if(cp[1] == '\0'){
 3395       op = cp[0];
 3396 jnumop:
 3397       f |= a_ISNUM;
 3398       switch(op){
 3399       case '=':
 3400       case '~':
 3401          if(argv[1] == NULL || argv[2] != NULL)
 3402             goto jesynopsis;
 3403 
 3404          if(*(cp = *++argv) == '\0')
 3405             lhv = 0;
 3406          else{
 3407             idm = ((*cp == 'u' || *cp == 'U')
 3408                   ? (++cp, n_IDEC_MODE_NONE)
 3409                   : ((*cp == 's' || *cp == 'S')
 3410                      ? (++cp, n_IDEC_MODE_SIGNED_TYPE)
 3411                      : n_IDEC_MODE_SIGNED_TYPE |
 3412                         n_IDEC_MODE_POW2BASE_UNSIGNED));
 3413             if(((ids = n_idec_cp(&lhv, cp, 0, idm, NULL)
 3414                      ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
 3415                   ) != n_IDEC_STATE_CONSUMED){
 3416                if(!(ids & n_IDEC_STATE_EOVERFLOW) || !(f & a_SATURATED))
 3417                   goto jenum_range;
 3418                f |= a_SOFTOVERFLOW;
 3419                break;
 3420             }
 3421          }
 3422          if(op == '~')
 3423             lhv = ~lhv;
 3424          break;
 3425 
 3426       case '+':
 3427       case '-':
 3428       case '*':
 3429       case '/':
 3430       case '%':
 3431       case '|':
 3432       case '&':
 3433       case '^':
 3434       case '<':
 3435       case '>':
 3436          if(argv[1] == NULL || argv[2] == NULL || argv[3] != NULL)
 3437             goto jesynopsis;
 3438          else{
 3439             char xop;
 3440 
 3441             if(*(cp = *++argv) == '\0')
 3442                lhv = 0;
 3443             else{
 3444                idm = ((*cp == 'u' || *cp == 'U')
 3445                      ? (++cp, n_IDEC_MODE_NONE)
 3446                      : ((*cp == 's' || *cp == 'S')
 3447                         ? (++cp, n_IDEC_MODE_SIGNED_TYPE)
 3448                         : n_IDEC_MODE_SIGNED_TYPE |
 3449                            n_IDEC_MODE_POW2BASE_UNSIGNED));
 3450                if(((ids = n_idec_cp(&lhv, cp, 0, idm, NULL)
 3451                         ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
 3452                      ) != n_IDEC_STATE_CONSUMED){
 3453                   if(!(ids & n_IDEC_STATE_EOVERFLOW) || !(f & a_SATURATED))
 3454                      goto jenum_range;
 3455                   f |= a_SOFTOVERFLOW;
 3456                   break;
 3457                }
 3458             }
 3459 
 3460             if(*(cp = *++argv) == '\0')
 3461                rhv = 0;
 3462             else{
 3463                idm = ((*cp == 'u' || *cp == 'U')
 3464                      ? (++cp, n_IDEC_MODE_NONE)
 3465                      : ((*cp == 's' || *cp == 'S')
 3466                         ? (++cp, n_IDEC_MODE_SIGNED_TYPE)
 3467                         : n_IDEC_MODE_SIGNED_TYPE |
 3468                            n_IDEC_MODE_POW2BASE_UNSIGNED));
 3469                if(((ids = n_idec_cp(&rhv, cp, 0, idm, NULL)
 3470                         ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
 3471                      ) != n_IDEC_STATE_CONSUMED){
 3472                   if(!(ids & n_IDEC_STATE_EOVERFLOW) || !(f & a_SATURATED))
 3473                      goto jenum_range;
 3474                   f |= a_SOFTOVERFLOW;
 3475                   lhv = rhv;
 3476                   break;
 3477                }
 3478             }
 3479 
 3480             xop = op;
 3481 jnumop_again:
 3482             switch(xop){
 3483             case '+':
 3484                if(rhv < 0){
 3485                   if(rhv != SI64_MIN){
 3486                      rhv = -rhv;
 3487                      xop = '-';
 3488                      goto jnumop_again;
 3489                   }else if(lhv < 0)
 3490                      goto jenum_plusminus;
 3491                   else if(lhv == 0){
 3492                      lhv = rhv;
 3493                      break;
 3494                   }
 3495                }else if(SI64_MAX - rhv < lhv)
 3496                   goto jenum_plusminus;
 3497                lhv += rhv;
 3498                break;
 3499             case '-':
 3500                if(rhv < 0){
 3501                   if(rhv != SI64_MIN){
 3502                      rhv = -rhv;
 3503                      xop = '+';
 3504                      goto jnumop_again;
 3505                   }else if(lhv > 0)
 3506                      goto jenum_plusminus;
 3507                   else if(lhv == 0){
 3508                      lhv = rhv;
 3509                      break;
 3510                   }
 3511                }else if(SI64_MIN + rhv > lhv){
 3512 jenum_plusminus:
 3513                   if(!(f & a_SATURATED))
 3514                      goto jenum_overflow;
 3515                   f |= a_SOFTOVERFLOW;
 3516                   lhv = (lhv < 0 || xop == '-') ? SI64_MIN : SI64_MAX;
 3517                   break;
 3518                }
 3519                lhv -= rhv;
 3520                break;
 3521             case '*':
 3522                /* Will the result be positive? */
 3523                if((lhv < 0) == (rhv < 0)){
 3524                   if(lhv > 0){
 3525                      lhv = -lhv;
 3526                      rhv = -rhv;
 3527                   }
 3528                   if(rhv != 0 && lhv != 0 && SI64_MAX / rhv > lhv){
 3529                      if(!(f & a_SATURATED))
 3530                         goto jenum_overflow;
 3531                      f |= a_SOFTOVERFLOW;
 3532                      lhv = SI64_MAX;
 3533                   }else
 3534                      lhv *= rhv;
 3535                }else{
 3536                   if(rhv > 0){
 3537                      if(lhv != 0 && SI64_MIN / lhv < rhv){
 3538                         if(!(f & a_SATURATED))
 3539                            goto jenum_overflow;
 3540                         f |= a_SOFTOVERFLOW;
 3541                         lhv = SI64_MIN;
 3542                      }else
 3543                         lhv *= rhv;
 3544                   }else{
 3545                      if(rhv != 0 && lhv != 0 && SI64_MIN / rhv < lhv){
 3546                         if(!(f & a_SATURATED))
 3547                            goto jenum_overflow;
 3548                         f |= a_SOFTOVERFLOW;
 3549                         lhv = SI64_MIN;
 3550                      }else
 3551                         lhv *= rhv;
 3552                   }
 3553                }
 3554                break;
 3555             case '/':
 3556                if(rhv == 0){
 3557                   if(!(f & a_SATURATED))
 3558                      goto jenum_range;
 3559                   f |= a_SOFTOVERFLOW;
 3560                   lhv = SI64_MAX;
 3561                }else
 3562                   lhv /= rhv;
 3563                break;
 3564             case '%':
 3565                if(rhv == 0){
 3566                   if(!(f & a_SATURATED))
 3567                      goto jenum_range;
 3568                   f |= a_SOFTOVERFLOW;
 3569                   lhv = SI64_MAX;
 3570                }else
 3571                   lhv %= rhv;
 3572                break;
 3573             case '|':
 3574                lhv |= rhv;
 3575                break;
 3576             case '&':
 3577                lhv &= rhv;
 3578                break;
 3579             case '^':
 3580                lhv ^= rhv;
 3581                break;
 3582             case '<':
 3583             case '>':
 3584                if(!(f & a_TMP)){
 3585                   argv -= 2;
 3586                   goto jesubcmd;
 3587                }
 3588                if(rhv > 63){ /* xxx 63? */
 3589                   if(!(f & a_SATURATED))
 3590                      goto jenum_overflow;
 3591                   rhv = 63;
 3592                }
 3593                if(op == '<')
 3594                   lhv <<= (ui8_t)rhv;
 3595                else if(f & a_UNSIGNED_OP)
 3596                   lhv = (ui64_t)lhv >> (ui8_t)rhv;
 3597                else
 3598                   lhv >>= (ui8_t)rhv;
 3599                break;
 3600             }
 3601          }
 3602          break;
 3603       default:
 3604          goto jesubcmd;
 3605       }
 3606    }else if(cp[0] == '<'){
 3607       if(*++cp != '<')
 3608          goto jesubcmd;
 3609       if(*++cp == '@'){
 3610          n_OBSOLETE(_("`vexpr': please use @ modifier as prefix not suffix"));
 3611          f |= a_SATURATED;
 3612          ++cp;
 3613       }
 3614       if(*cp != '\0')
 3615          goto jesubcmd;
 3616       f |= a_TMP;
 3617       op = '<';
 3618       goto jnumop;
 3619    }else if(cp[0] == '>'){
 3620       if(*++cp != '>')
 3621          goto jesubcmd;
 3622       if(*++cp == '>'){
 3623          f |= a_UNSIGNED_OP;
 3624          ++cp;
 3625       }
 3626       if(*cp == '@'){
 3627          n_OBSOLETE(_("`vexpr': please use @ modifier as prefix not suffix"));
 3628          f |= a_SATURATED;
 3629          ++cp;
 3630       }
 3631       if(*cp != '\0')
 3632          goto jesubcmd;
 3633       f |= a_TMP;
 3634       op = '>';
 3635       goto jnumop;
 3636    }else if(cp[2] == '\0' && cp[1] == '@'){
 3637       n_OBSOLETE(_("`vexpr': please use @ modifier as prefix, not suffix"));
 3638       f |= a_SATURATED;
 3639       op = cp[0];
 3640       goto jnumop;
 3641    }else if(cp[0] == '@'){
 3642       f |= a_SATURATED;
 3643       op = *++cp;
 3644       if(*++cp != '\0'){
 3645          if(op != *cp)
 3646             goto jesubcmd;
 3647          switch(op){
 3648          case '<':
 3649             if(*++cp != '\0')
 3650                goto jesubcmd;
 3651             f |= a_TMP;
 3652             break;
 3653          case '>':
 3654             if(*++cp != '\0'){
 3655                if(*cp != '>' || *++cp != '\0')
 3656                   goto jesubcmd;
 3657                f |= a_UNSIGNED_OP;
 3658             }
 3659             f |= a_TMP;
 3660             break;
 3661          default:
 3662             goto jesubcmd;
 3663          }
 3664       }
 3665       goto jnumop;
 3666    }else if(is_asccaseprefix(cp, "pbase")){
 3667       if(argv[1] == NULL || argv[2] == NULL || argv[3] != NULL)
 3668          goto jesynopsis;
 3669 
 3670       if(((ids = n_idec_si8_cp(&pbase, argv[1], 0, NULL)
 3671                ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
 3672             ) != n_IDEC_STATE_CONSUMED || pbase < 2 || pbase > 36)
 3673          goto jenum_range;
 3674 
 3675       f |= a_PBASE;
 3676       op = *(argv[0] = cp = "=");
 3677       argv[1] = argv[2];
 3678       argv[2] = NULL;
 3679       goto jnumop;
 3680    }else if(is_asccaseprefix(cp, "length")){
 3681       f |= a_ISNUM | a_ISDECIMAL;
 3682       if(argv[1] == NULL || argv[2] != NULL)
 3683          goto jesynopsis;
 3684 
 3685       i = strlen(*++argv);
 3686       if(UICMP(64, i, >, SI64_MAX))
 3687          goto jestr_overflow;
 3688       lhv = (si64_t)i;
 3689    }else if(is_asccaseprefix(cp, "hash")){
 3690       f |= a_ISNUM | a_ISDECIMAL;
 3691       if(argv[1] == NULL || argv[2] != NULL)
 3692          goto jesynopsis;
 3693 
 3694       i = n_torek_hash(*++argv);
 3695       lhv = (si64_t)i;
 3696    }else if(is_asccaseprefix(cp, "find")){
 3697       f |= a_ISNUM | a_ISDECIMAL;
 3698       if(argv[1] == NULL || argv[2] == NULL || argv[3] != NULL)
 3699          goto jesynopsis;
 3700 
 3701       if((cp = strstr(argv[1], argv[2])) == NULL)
 3702          goto jestr_nodata;
 3703       i = PTR2SIZE(cp - argv[1]);
 3704       if(UICMP(64, i, >, SI64_MAX))
 3705          goto jestr_overflow;
 3706       lhv = (si64_t)i;
 3707    }else if(is_asccaseprefix(cp, "ifind")){
 3708       f |= a_ISNUM | a_ISDECIMAL;
 3709       if(argv[1] == NULL || argv[2] == NULL || argv[3] != NULL)
 3710          goto jesynopsis;
 3711 
 3712       if((cp = asccasestr(argv[1], argv[2])) == NULL)
 3713          goto jestr_nodata;
 3714       i = PTR2SIZE(cp - argv[1]);
 3715       if(UICMP(64, i, >, SI64_MAX))
 3716          goto jestr_overflow;
 3717       lhv = (si64_t)i;
 3718    }else if(is_asccaseprefix(cp, "substring")){
 3719       if(argv[1] == NULL || argv[2] == NULL)
 3720          goto jesynopsis;
 3721       if(argv[3] != NULL && argv[4] != NULL)
 3722          goto jesynopsis;
 3723 
 3724       i = strlen(varres = *++argv);
 3725 
 3726       if(*(cp = *++argv) == '\0')
 3727          lhv = 0;
 3728       else if((n_idec_si64_cp(&lhv, cp, 0, NULL
 3729                ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
 3730             ) != n_IDEC_STATE_CONSUMED)
 3731          goto jestr_numrange;
 3732       if(lhv < 0){
 3733          if(UICMP(64, i, <, -lhv))
 3734             goto jesubstring_off;
 3735          lhv = i + lhv;
 3736       }
 3737       if(UICMP(64, i, >=, lhv)){
 3738          i -= lhv;
 3739          varres += lhv;
 3740       }else{
 3741 jesubstring_off:
 3742          if(n_poption & n_PO_D_V)
 3743             n_err(_("`vexpr': substring: offset argument too large: %s\n"),
 3744                n_shexp_quote_cp(argv[-1], FAL0));
 3745          f |= a_SOFTOVERFLOW;
 3746       }
 3747 
 3748       if(argv[1] != NULL){
 3749          if(*(cp = *++argv) == '\0')
 3750             lhv = 0;
 3751          else if((n_idec_si64_cp(&lhv, cp, 0, NULL
 3752                   ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
 3753                ) != n_IDEC_STATE_CONSUMED)
 3754             goto jestr_numrange;
 3755          if(lhv < 0){
 3756             if(UICMP(64, i, <, -lhv))
 3757                goto jesubstring_len;
 3758             lhv = i + lhv;
 3759          }
 3760          if(UICMP(64, i, >=, lhv)){
 3761             if(UICMP(64, i, !=, lhv))
 3762                varres = savestrbuf(varres, (size_t)lhv);
 3763          }else{
 3764 jesubstring_len:
 3765             if(n_poption & n_PO_D_V)
 3766                n_err(_("`vexpr': substring: length argument too large: %s\n"),
 3767                   n_shexp_quote_cp(argv[-2], FAL0));
 3768             f |= a_SOFTOVERFLOW;
 3769          }
 3770       }
 3771    }else if(is_asccaseprefix(cp, "trim")) Jtrim: {
 3772       struct str trim;
 3773       enum n_str_trim_flags stf;
 3774 
 3775       if(argv[1] == NULL || argv[2] != NULL)
 3776          goto jesynopsis;
 3777 
 3778       if(is_asccaseprefix(cp, "trim-front"))
 3779          stf = n_STR_TRIM_FRONT;
 3780       else if(is_asccaseprefix(cp, "trim-end"))
 3781          stf = n_STR_TRIM_END;
 3782       else
 3783          stf = n_STR_TRIM_BOTH;
 3784 
 3785       trim.l = strlen(trim.s = n_UNCONST(argv[1]));
 3786       (void)n_str_trim(&trim, stf);
 3787       varres = savestrbuf(trim.s, trim.l);
 3788    }else if(is_asccaseprefix(cp, "trim-front"))
 3789       goto Jtrim;
 3790    else if(is_asccaseprefix(cp, "trim-end"))
 3791       goto Jtrim;
 3792    else if(is_asccaseprefix(cp, "random")){
 3793       if(argv[1] == NULL || argv[2] != NULL)
 3794          goto jesynopsis;
 3795 
 3796       if((n_idec_si64_cp(&lhv, argv[1], 0, NULL
 3797                ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
 3798             ) != n_IDEC_STATE_CONSUMED || lhv < 0 || lhv > PATH_MAX)
 3799          goto jestr_numrange;
 3800       if(lhv == 0)
 3801          lhv = NAME_MAX;
 3802       varres = n_random_create_cp((size_t)lhv, NULL);
 3803    }else if(is_asccaseprefix(cp, "file-expand")){
 3804       if(argv[1] == NULL || argv[2] != NULL)
 3805          goto jesynopsis;
 3806 
 3807       if((varres = fexpand(argv[1], FEXP_NVAR | FEXP_NOPROTO)) == NULL)
 3808          goto jestr_nodata;
 3809    }else if(is_asccaseprefix(cp, "makeprint")){
 3810       struct str sin, sout;
 3811 
 3812       if(argv[1] == NULL || argv[2] != NULL)
 3813          goto jesynopsis;
 3814 
 3815       /* XXX using strlen for `vexpr makeprint' is wrong for UTF-16 */
 3816       sin.l = strlen(sin.s = n_UNCONST(argv[1]));
 3817       makeprint(&sin, &sout);
 3818       varres = savestrbuf(sout.s, sout.l);
 3819       n_free(sout.s);
 3820    /* TODO `vexpr': (wide) string length, find, etc!! */
 3821 #ifdef HAVE_REGEX
 3822    }else if(is_asccaseprefix(cp, "regex")) Jregex:{
 3823       regmatch_t rema[1 + n_VEXPR_REGEX_MAX];
 3824       regex_t re;
 3825       int reflrv;
 3826 
 3827       f |= a_ISNUM | a_ISDECIMAL;
 3828       if(argv[1] == NULL || argv[2] == NULL ||
 3829             (argv[3] != NULL && argv[4] != NULL))
 3830          goto jesynopsis;
 3831 
 3832       reflrv = REG_EXTENDED;
 3833       if(f & a_ICASE)
 3834          reflrv |= REG_ICASE;
 3835       if((reflrv = regcomp(&re, argv[2], reflrv))){
 3836          n_err(_("`vexpr': invalid regular expression: %s: %s\n"),
 3837             n_shexp_quote_cp(argv[2], FAL0), n_regex_err_to_doc(NULL, reflrv));
 3838          assert(f & a_ERR);
 3839          n_pstate_err_no = n_ERR_INVAL;
 3840          goto jestr;
 3841       }
 3842       reflrv = regexec(&re, argv[1], n_NELEM(rema), rema, 0);
 3843       regfree(&re);
 3844       if(reflrv == REG_NOMATCH)
 3845          goto jestr_nodata;
 3846 
 3847       /* Search only?  Else replace, which is a bit */
 3848       if(argv[3] == NULL){
 3849          if(UICMP(64, rema[0].rm_so, >, SI64_MAX))
 3850             goto jestr_overflow;
 3851          lhv = (si64_t)rema[0].rm_so;
 3852       }else{
 3853          /* We need to setup some kind of pseudo macro environment for this */
 3854          struct a_amv_lostack los;
 3855          struct a_amv_mac_call_args amca;
 3856          char const **reargv;
 3857 
 3858          memset(&amca, 0, sizeof amca);
 3859          amca.amca_name = savestrbuf(&argv[1][rema[0].rm_so],
 3860                rema[0].rm_eo - rema[0].rm_so);
 3861          amca.amca_amp = a_AMV_MACKY_MACK;
 3862          for(i = 1; i < n_NELEM(rema) && rema[i].rm_so != -1; ++i)
 3863             ;
 3864          amca.amca_pospar.app_count = (ui32_t)i - 1;
 3865          amca.amca_pospar.app_not_heap = TRU1;
 3866          amca.amca_pospar.app_dat =
 3867          reargv = n_autorec_alloc(sizeof(char*) * i);
 3868          for(i = 1; i < n_NELEM(rema) && rema[i].rm_so != -1; ++i)
 3869             *reargv++ = savestrbuf(&argv[1][rema[i].rm_so],
 3870                   rema[i].rm_eo - rema[i].rm_so);
 3871          *reargv = NULL;
 3872 
 3873          memset(&los, 0, sizeof los);
 3874          hold_all_sigs(); /* TODO DISLIKE! */
 3875          los.as_global_saved = a_amv_lopts;
 3876          los.as_amcap = &amca;
 3877          los.as_up = los.as_global_saved;
 3878          a_amv_lopts = &los;
 3879 
 3880          /* C99 */{
 3881             struct str templ;
 3882             struct n_string s_b;
 3883             enum n_shexp_state shs;
 3884 
 3885             templ.s = n_UNCONST(argv[3]);
 3886             templ.l = UIZ_MAX;
 3887             shs = n_shexp_parse_token((n_SHEXP_PARSE_LOG |
 3888                   n_SHEXP_PARSE_IGNORE_EMPTY | n_SHEXP_PARSE_QUOTE_AUTO_FIXED |
 3889                   n_SHEXP_PARSE_QUOTE_AUTO_DSQ),
 3890                   n_string_creat_auto(&s_b), &templ, NULL);
 3891             if((shs & (n_SHEXP_STATE_ERR_MASK | n_SHEXP_STATE_STOP)
 3892                   ) == n_SHEXP_STATE_STOP){
 3893                varres = n_string_cp(&s_b);
 3894                n_string_drop_ownership(&s_b);
 3895             }else
 3896                varres = NULL;
 3897          }
 3898 
 3899          a_amv_lopts = los.as_global_saved;
 3900          rele_all_sigs(); /* TODO DISLIKE! */
 3901 
 3902          if(varres == NULL)
 3903             goto jestr_nodata;
 3904          f &= ~(a_ISNUM | a_ISDECIMAL);
 3905       }
 3906    }else if(is_asccaseprefix(argv[0], "iregex")){
 3907       f |= a_ICASE;
 3908       goto Jregex;
 3909 #endif /* HAVE_REGEX */
 3910    }else
 3911       goto jesubcmd;
 3912 
 3913    n_pstate_err_no = (f & a_SOFTOVERFLOW) ? n_ERR_OVERFLOW : n_ERR_NONE;
 3914    f &= ~a_ERR;
 3915 
 3916    /* Generate the variable value content for numerics.
 3917     * Anticipate in our handling below!  (Don't do needless work) */
 3918 jleave:
 3919    if((f & a_ISNUM) && ((f & (a_ISDECIMAL | a_PBASE)) || varname != NULL)){
 3920       cp = n_ienc_buf(iencbuf, lhv, (f & a_PBASE ? pbase : 10),
 3921             n_IENC_MODE_SIGNED_TYPE);
 3922       if(cp != NULL)
 3923          varres = cp;
 3924       else{
 3925          f |= a_ERR;
 3926          varres = n_empty;
 3927       }
 3928    }
 3929 
 3930    if(varname == NULL){
 3931       /* If there was no error and we are printing a numeric result, print some
 3932        * more bases for the fun of it */
 3933       if((f & (a_ERR | a_ISNUM | a_ISDECIMAL)) == a_ISNUM){
 3934          char binabuf[64 + 64 / 8 +1];
 3935          size_t j;
 3936 
 3937          for(j = 1, i = 0; i < 64; ++i){
 3938             binabuf[63 + 64 / 8 -j - i] = (lhv & ((ui64_t)1 << i)) ? '1' : '0';
 3939             if((i & 7) == 7 && i != 63){
 3940                ++j;
 3941                binabuf[63 + 64 / 8 -j - i] = ' ';
 3942             }
 3943          }
 3944          binabuf[64 + 64 / 8 -1] = '\0';
 3945 
 3946          if(fprintf(n_stdout,
 3947                   "0b %s\n0%" PRIo64 " | 0x%" PRIX64 " | %" PRId64 "\n",
 3948                   binabuf, lhv, lhv, lhv) < 0 ||
 3949                ((f & a_PBASE) && (assert(varres != NULL),
 3950                 fprintf(n_stdout, "%s\n", varres) < 0))){
 3951             n_pstate_err_no = n_err_no;
 3952             f |= a_ERR;
 3953          }
 3954       }else if(varres != NULL && fprintf(n_stdout, "%s\n", varres) < 0){
 3955          n_pstate_err_no = n_err_no;
 3956          f |= a_ERR;
 3957       }
 3958    }else if(!n_var_vset(varname, (uintptr_t)varres)){
 3959       n_pstate_err_no = n_ERR_NOTSUP;
 3960       f |= a_ERR;
 3961    }
 3962    NYD_LEAVE;
 3963    return (f & a_ERR) ? 1 : 0;
 3964 
 3965 jerr:
 3966    f = a_ERR | a_ISNUM;
 3967    lhv = -1;
 3968    goto jleave;
 3969 jesubcmd:
 3970    n_err(_("`vexpr': invalid subcommand: %s\n"),
 3971       n_shexp_quote_cp(*argv, FAL0));
 3972    n_pstate_err_no = n_ERR_INVAL;
 3973    goto jerr;
 3974 jesynopsis:
 3975    n_err(_("Synopsis: vexpr: <operator> <:argument:>\n"));
 3976    n_pstate_err_no = n_ERR_INVAL;
 3977    goto jerr;
 3978 
 3979 jenum_range:
 3980    n_err(_("`vexpr': numeric argument invalid or out of range: %s\n"),
 3981       n_shexp_quote_cp(*argv, FAL0));
 3982    n_pstate_err_no = n_ERR_RANGE;
 3983    goto jerr;
 3984 jenum_overflow:
 3985    n_err(_("`vexpr': expression overflows datatype: %" PRId64 " %c %" PRId64
 3986       "\n"), lhv, op, rhv);
 3987    n_pstate_err_no = n_ERR_OVERFLOW;
 3988    goto jerr;
 3989 
 3990 jestr_numrange:
 3991    n_err(_("`vexpr': numeric argument invalid or out of range: %s\n"),
 3992       n_shexp_quote_cp(*argv, FAL0));
 3993    n_pstate_err_no = n_ERR_RANGE;
 3994    goto jestr;
 3995 jestr_overflow:
 3996    n_err(_("`vexpr': string length or offset overflows datatype\n"));
 3997    n_pstate_err_no = n_ERR_OVERFLOW;
 3998    goto jestr;
 3999 jestr_nodata:
 4000    n_pstate_err_no = n_ERR_NODATA;
 4001    /* FALLTHRU */
 4002 jestr:
 4003    varres = n_empty;
 4004    f &= ~a_ISNUM;
 4005    f |= a_ERR;
 4006    goto jleave;
 4007 }
 4008 
 4009 FL int
 4010 c_vpospar(void *v){
 4011    struct n_cmd_arg *cap;
 4012    size_t i;
 4013    struct a_amv_pospar *appp;
 4014    enum{
 4015       a_NONE = 0,
 4016       a_ERR = 1u<<0,
 4017       a_SET = 1u<<1,
 4018       a_CLEAR = 1u<<2,
 4019       a_QUOTE = 1u<<3
 4020    } f;
 4021    char const *varres;
 4022    struct n_cmd_arg_ctx *cacp;
 4023    NYD_ENTER;
 4024 
 4025    n_pstate_err_no = n_ERR_NONE;
 4026    n_UNINIT(varres, n_empty);
 4027    cacp = v;
 4028    cap = cacp->cac_arg;
 4029 
 4030    if(is_asccaseprefix(cap->ca_arg.ca_str.s, "set"))
 4031       f = a_SET;
 4032    else if(is_asccaseprefix(cap->ca_arg.ca_str.s, "clear"))
 4033       f = a_CLEAR;
 4034    else if(is_asccaseprefix(cap->ca_arg.ca_str.s, "quote"))
 4035       f = a_QUOTE;
 4036    else{
 4037       n_err(_("`vpospar': invalid subcommand: %s\n"),
 4038          n_shexp_quote_cp(cap->ca_arg.ca_str.s, FAL0));
 4039       n_pstate_err_no = n_ERR_INVAL;
 4040       f = a_ERR;
 4041       goto jleave;
 4042    }
 4043    --cacp->cac_no;
 4044 
 4045    if((f & (a_CLEAR | a_QUOTE)) && cap->ca_next != NULL){
 4046       n_err(_("`vpospar': `%s': takes no argument\n"), cap->ca_arg.ca_str.s);
 4047       n_pstate_err_no = n_ERR_INVAL;
 4048       f = a_ERR;
 4049       goto jleave;
 4050    }
 4051 
 4052    cap = cap->ca_next;
 4053 
 4054    /* If in a macro, we need to overwrite the local instead of global argv */
 4055    appp = (a_amv_lopts != NULL) ? &a_amv_lopts->as_amcap->amca_pospar
 4056          : &a_amv_pospar;
 4057 
 4058    if(f & (a_SET | a_CLEAR)){
 4059       if(cacp->cac_vput != NULL)
 4060          n_err(_("`vpospar': `vput' only supported for `quote' subcommand\n"));
 4061       if(!appp->app_not_heap && appp->app_maxcount > 0){
 4062          for(i = appp->app_maxcount; i-- != 0;)
 4063             n_free(n_UNCONST(appp->app_dat[i]));
 4064          n_free(appp->app_dat);
 4065       }
 4066       memset(appp, 0, sizeof *appp);
 4067 
 4068       if(f & a_SET){
 4069          if((i = cacp->cac_no) > a_AMV_POSPAR_MAX){
 4070             n_err(_("`vpospar': overflow: %" PRIuZ " arguments!\n"), i);
 4071             n_pstate_err_no = n_ERR_OVERFLOW;
 4072             f = a_ERR;
 4073             goto jleave;
 4074          }
 4075 
 4076          memset(appp, 0, sizeof *appp);
 4077          if(i > 0){
 4078             appp->app_maxcount = appp->app_count = (ui16_t)i;
 4079             /* XXX Optimize: store it all in one chunk! */
 4080             ++i;
 4081             i *= sizeof *appp->app_dat;
 4082             appp->app_dat = n_alloc(i);
 4083 
 4084             for(i = 0; cap != NULL; ++i, cap = cap->ca_next){
 4085                appp->app_dat[i] = n_alloc(cap->ca_arg.ca_str.l +1);
 4086                memcpy(n_UNCONST(appp->app_dat[i]), cap->ca_arg.ca_str.s,
 4087                   cap->ca_arg.ca_str.l +1);
 4088             }
 4089 
 4090             appp->app_dat[i] = NULL;
 4091          }
 4092       }
 4093    }else{
 4094       if(appp->app_count == 0)
 4095          varres = n_empty;
 4096       else{
 4097          struct str in;
 4098          struct n_string s, *sp;
 4099          char sep1, sep2;
 4100 
 4101          sp = n_string_creat_auto(&s);
 4102 
 4103          sep1 = *ok_vlook(ifs);
 4104          sep2 = *ok_vlook(ifs_ws);
 4105          if(sep1 == sep2)
 4106             sep2 = '\0';
 4107          if(sep1 == '\0')
 4108             sep1 = ' ';
 4109 
 4110          for(i = 0; i < appp->app_count; ++i){
 4111             if(sp->s_len){
 4112                if(!n_string_can_book(sp, 2))
 4113                   goto jeover;
 4114                sp = n_string_push_c(sp, sep1);
 4115                if(sep2 != '\0')
 4116                   sp = n_string_push_c(sp, sep2);
 4117             }
 4118             in.l = strlen(in.s = n_UNCONST(appp->app_dat[i + appp->app_idx]));
 4119 
 4120             if(!n_string_can_book(sp, in.l)){
 4121 jeover:
 4122                n_err(_("`vpospar': overflow: string too long!\n"));
 4123                n_pstate_err_no = n_ERR_OVERFLOW;
 4124                f = a_ERR;
 4125                goto jleave;
 4126             }
 4127             sp = n_shexp_quote(sp, &in, TRU1);
 4128          }
 4129 
 4130          varres = n_string_cp(sp);
 4131       }
 4132 
 4133       if(cacp->cac_vput == NULL){
 4134          if(fprintf(n_stdout, "%s\n", varres) < 0){
 4135             n_pstate_err_no = n_err_no;
 4136             f |= a_ERR;
 4137          }
 4138       }else if(!n_var_vset(cacp->cac_vput, (uintptr_t)varres)){
 4139          n_pstate_err_no = n_ERR_NOTSUP;
 4140          f |= a_ERR;
 4141       }
 4142    }
 4143 jleave:
 4144    NYD_LEAVE;
 4145    return (f & a_ERR) ? 1 : 0;
 4146 }
 4147 
 4148 /* s-it-mode */