"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.19/src/mx/accmacvar.c" (26 Apr 2020, 114098 Bytes) of package /linux/misc/s-nail-14.9.19.tar.xz:


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

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