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