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 = &->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