gen-okeys.h: regenerate
[s-mailx.git] / accmacvar.c
blobd028e5b1089a86f6f7d4ce3effd38442a9fe0615
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Account, macro and variable handling.
3 *@ HOWTO add a new non-dynamic boolean or value option:
4 *@ - add an entry to nail.h:enum okeys
5 *@ - run mk-okey-map.pl
6 *@ - update the manual!
7 *@ TODO . should be recursive environment based.
8 *@ TODO Otherwise, the `localopts' should be an attribute of the go.c
9 *@ TODO command context, so that it belongs to the execution context
10 *@ TODO we are running in, instead of being global data. See, e.g.,
11 *@ TODO the a_GO_SPLICE comment in go.c.
12 *@ TODO . once we can have non-fatal !0 returns for commands, we should
13 *@ TODO return error if "(environ)? unset" goes for non-existent.
15 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
16 * Copyright (c) 2012 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
19 * Copyright (c) 1980, 1993
20 * The Regents of the University of California. All rights reserved.
22 * Redistribution and use in source and binary forms, with or without
23 * modification, are permitted provided that the following conditions
24 * are met:
25 * 1. Redistributions of source code must retain the above copyright
26 * notice, this list of conditions and the following disclaimer.
27 * 2. Redistributions in binary form must reproduce the above copyright
28 * notice, this list of conditions and the following disclaimer in the
29 * documentation and/or other materials provided with the distribution.
30 * 3. Neither the name of the University nor the names of its contributors
31 * may be used to endorse or promote products derived from this software
32 * without specific prior written permission.
34 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
35 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
36 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
37 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
38 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
39 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
40 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
41 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
42 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
43 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
44 * SUCH DAMAGE.
46 #undef n_FILE
47 #define n_FILE accmacvar
49 #ifndef HAVE_AMALGAMATION
50 # include "nail.h"
51 #endif
53 #if !defined HAVE_SETENV && !defined HAVE_PUTENV
54 # error Exactly one of HAVE_SETENV and HAVE_PUTENV
55 #endif
57 /* Special "pseudo macro" that stabs you from the back */
58 #define a_AMV_MACKY_MACK ((struct a_amv_mac*)-1)
60 /* Note: changing the hash function must be reflected in `vexpr' "hash",
61 * because that is used by the hashtable creator scripts! */
62 #define a_AMV_PRIME HSHSIZE
63 #define a_AMV_NAME2HASH(N) n_torek_hash(N)
64 #define a_AMV_HASH2PRIME(H) ((H) % a_AMV_PRIME)
66 enum a_amv_mac_flags{
67 a_AMV_MF_NONE = 0,
68 a_AMV_MF_ACCOUNT = 1<<0, /* This macro is an `account' */
69 a_AMV_MF_TYPE_MASK = a_AMV_MF_ACCOUNT,
70 a_AMV_MF_UNDEF = 1<<1, /* Unlink after lookup */
71 a_AMV_MF_DELETE = 1<<7, /* Delete in progress, free once refcnt==0 */
72 a_AMV_MF__MAX = 0xFF
75 /* mk-okey-map.pl ensures that _VIRT implies _RDONLY and _NODEL, and that
76 * _IMPORT implies _ENV; it doesn't verify anything... */
77 enum a_amv_var_flags{
78 a_AMV_VF_NONE = 0,
79 a_AMV_VF_BOOL = 1<<0, /* ok_b_* */
80 a_AMV_VF_VIRT = 1<<1, /* "Stateless" automatic variable */
81 a_AMV_VF_NOLOPTS = 1<<2, /* May not be tracked by `localopts' */
82 a_AMV_VF_RDONLY = 1<<3, /* May not be set by user */
83 a_AMV_VF_NODEL = 1<<4, /* May not be deleted */
84 a_AMV_VF_NOTEMPTY = 1<<5, /* May not be assigned an empty value */
85 a_AMV_VF_NOCNTRLS = 1<<6, /* Value may not contain control characters */
86 a_AMV_VF_NUM = 1<<7, /* Value must be a 32-bit number */
87 a_AMV_VF_POSNUM = 1<<8, /* Value must be positive 32-bit number */
88 a_AMV_VF_LOWER = 1<<9, /* Values will be stored in a lowercase version */
89 a_AMV_VF_VIP = 1<<10, /* Wants _var_check_vips() evaluation */
90 a_AMV_VF_IMPORT = 1<<11, /* Import ONLY from environ (pre n_PSO_STARTED) */
91 a_AMV_VF_ENV = 1<<12, /* Update environment on change */
92 a_AMV_VF_I3VAL = 1<<13, /* Has an initial value */
93 a_AMV_VF_DEFVAL = 1<<14, /* Has a default value */
94 a_AMV_VF_LINKED = 1<<15, /* `environ' linked */
95 a_AMV_VF__MASK = (1<<(15+1)) - 1
98 /* We support some special parameter names for one-letter variable names;
99 * note these have counterparts in the code that manages shell expansion!
100 * Macro-local variables are solely backed by n_var_vlook(), and besides there
101 * is only a_amv_var_revlookup() which knows about them.
102 * The ARGV $[1-9][0-9]* variables are also special, but they are special ;] */
103 enum a_amv_var_special_category{
104 a_AMV_VSC_NONE, /* Normal variable, no special treatment */
105 a_AMV_VSC_GLOBAL, /* ${[?!]} are specially mapped, but global */
106 a_AMV_VSC_MULTIPLEX, /* ${^.*} circumflex accent multiplexer */
107 a_AMV_VSC_MAC, /* ${[*@#]} macro argument access */
108 a_AMV_VSC_MAC_ARGV /* ${[1-9][0-9]*} macro ARGV access */
111 enum a_amv_var_special_type{
112 /* _VSC_GLOBAL */
113 a_AMV_VST_QM, /* ? */
114 a_AMV_VST_EM, /* ! */
115 /* _VSC_MULTIPLEX */
116 /* This is special in that it is a multiplex indicator, the ^ is followed by
117 * a normal variable */
118 a_AMV_VST_CACC, /* ^ (circumflex accent) */
119 /* _VSC_MAC */
120 a_AMV_VST_STAR, /* * */
121 a_AMV_VST_AT, /* @ */
122 a_AMV_VST_NOSIGN /* # */
125 enum a_amv_var_vip_mode{
126 a_AMV_VIP_SET_PRE,
127 a_AMV_VIP_SET_POST,
128 a_AMV_VIP_CLEAR
131 struct a_amv_mac{
132 struct a_amv_mac *am_next;
133 ui32_t am_maxlen; /* of any line in .am_line_dat */
134 ui32_t am_line_cnt; /* of *.am_line_dat (but NULL terminated) */
135 struct a_amv_mac_line **am_line_dat; /* TODO use deque? */
136 struct a_amv_var *am_lopts; /* `localopts' unroll list */
137 ui32_t am_refcnt; /* 0-based for `un{account,define}' purposes */
138 ui8_t am_flags; /* enum a_amv_mac_flags */
139 char am_name[n_VFIELD_SIZE(3)]; /* of this macro */
141 n_CTA(a_AMV_MF__MAX <= UI8_MAX, "Enumeration excesses storage datatype");
143 struct a_amv_mac_line{
144 ui32_t aml_len;
145 ui32_t aml_prespc; /* Number of leading SPC, for display purposes */
146 char aml_dat[n_VFIELD_SIZE(0)];
149 struct a_amv_mac_call_args{
150 char const *amca_name; /* For MACKY_MACK, this is *0*! */
151 struct a_amv_mac *amca_amp; /* "const", but for am_refcnt */
152 struct a_amv_var **amca_unroller;
153 void (*amca_hook_pre)(void *);
154 void *amca_hook_arg;
155 bool_t amca_lopts_on;
156 bool_t amca_ps_hook_mask;
157 ui8_t amca__pad[4];
158 ui16_t amca_argc; /* Max is SI16_MAX */
159 char const **amca_argv;
162 struct a_amv_lostack{
163 struct a_amv_lostack *as_global_saved; /* Saved global XXX due to jump */
164 struct a_amv_mac_call_args *as_amcap;
165 struct a_amv_lostack *as_up; /* Outer context */
166 struct a_amv_var *as_lopts;
167 bool_t as_unroll; /* Unrolling enabled? */
168 ui8_t avs__pad[7];
171 struct a_amv_var{
172 struct a_amv_var *av_link;
173 char *av_value;
174 #ifdef HAVE_PUTENV
175 char *av_env; /* Actively managed putenv(3) memory */
176 #endif
177 ui16_t av_flags; /* enum a_amv_var_flags */
178 char av_name[n_VFIELD_SIZE(6)];
180 n_CTA(a_AMV_VF__MASK <= UI16_MAX, "Enumeration excesses storage datatype");
182 struct a_amv_var_map{
183 ui32_t avm_hash;
184 ui16_t avm_keyoff;
185 ui16_t avm_flags; /* enum a_amv_var_flags */
187 n_CTA(a_AMV_VF__MASK <= UI16_MAX, "Enumeration excesses storage datatype");
189 struct a_amv_var_virt{
190 ui32_t avv_okey;
191 ui8_t avv__dummy[4];
192 struct a_amv_var const *avv_var;
195 struct a_amv_var_defval{
196 ui32_t avdv_okey;
197 ui8_t avdv__pad[4];
198 char const *avdv_value; /* Only for !BOOL (otherwise plain existence) */
201 struct a_amv_var_carrier{
202 char const *avc_name;
203 ui32_t avc_hash;
204 ui32_t avc_prime;
205 struct a_amv_var *avc_var;
206 struct a_amv_var_map const *avc_map;
207 enum okeys avc_okey;
208 ui8_t avc__pad[1];
209 ui8_t avc_special_cat;
210 /* Numerical parameter name if .avc_special_cat=a_AMV_VSC_MAC_ARGV,
211 * otherwise a enum a_amv_var_special_type */
212 ui16_t avc_special_prop;
215 /* Include the constant mk-okey-map.pl output, and the generated version data */
216 #include "gen-version.h"
217 #include "gen-okeys.h"
219 /* The currently active account */
220 static struct a_amv_mac *a_amv_acc_curr;
222 static struct a_amv_mac *a_amv_macs[a_AMV_PRIME]; /* TODO dynamically spaced */
224 /* Unroll list of currently running macro stack */
225 static struct a_amv_lostack *a_amv_lopts;
227 static struct a_amv_var *a_amv_vars[a_AMV_PRIME]; /* TODO dynamically spaced */
229 /* TODO We really deserve localopts support for *folder-hook*s, so hack it in
230 * TODO today via a static lostack, it should be a field in mailbox, once that
231 * TODO is a real multi-instance object */
232 static struct a_amv_var *a_amv_folder_hook_lopts;
234 /* TODO Rather ditto (except for storage -> cmd_ctx), compose hooks */
235 static struct a_amv_var *a_amv_compose_lopts;
237 /* Lookup for macros/accounts: if newamp is not NULL it will be linked in the
238 * map, if _MF_UNDEF is set a possibly existing entry will be removed (first).
239 * Returns NULL if a lookup failed, or if newamp was set, the found entry in
240 * plain lookup cases or when _UNDEF was performed on a currently active entry
241 * (the entry will have been unlinked, and the _MF_DELETE will be honoured once
242 * the reference count reaches 0), and (*)-1 if an _UNDEF was performed */
243 static struct a_amv_mac *a_amv_mac_lookup(char const *name,
244 struct a_amv_mac *newamp, enum a_amv_mac_flags amf);
246 /* `call', `call_if' */
247 static int a_amv_mac_call(void *v, bool_t silent_nexist);
249 /* Execute a macro; amcap must reside in LOFI memory */
250 static bool_t a_amv_mac_exec(struct a_amv_mac_call_args *amcap);
252 static void a_amv_mac__finalize(void *vp);
254 /* User display helpers */
255 static bool_t a_amv_mac_show(enum a_amv_mac_flags amf);
257 /* _def() returns error for faulty definitions and already existing * names,
258 * _undef() returns error if a named thing doesn't exist */
259 static bool_t a_amv_mac_def(char const *name, enum a_amv_mac_flags amf);
260 static bool_t a_amv_mac_undef(char const *name, enum a_amv_mac_flags amf);
262 /* */
263 static void a_amv_mac_free(struct a_amv_mac *amp);
265 /* Update replay-log */
266 static void a_amv_lopts_add(struct a_amv_lostack *alp, char const *name,
267 struct a_amv_var *oavp);
268 static void a_amv_lopts_unroll(struct a_amv_var **avpp);
270 /* Special cased value string allocation */
271 static char *a_amv_var_copy(char const *str);
272 static void a_amv_var_free(char *cp);
274 /* Check for special housekeeping. _VIP_SET_POST and _VIP_CLEAR do not fail
275 * (or propagate errors), _VIP_SET_PRE may and should case abortion */
276 static bool_t a_amv_var_check_vips(enum a_amv_var_vip_mode avvm,
277 enum okeys okey, char const *val);
279 /* _VF_NOCNTRLS, _VF_NUM / _VF_POSNUM */
280 static bool_t a_amv_var_check_nocntrls(char const *val);
281 static bool_t a_amv_var_check_num(char const *val, bool_t posnum);
283 /* If a variable name begins with a lowercase-character and contains at
284 * least one '@', it is converted to all-lowercase. This is necessary
285 * for lookups of names based on email addresses.
286 * Following the standard, only the part following the last '@' should
287 * be lower-cased, but practice has established otherwise here */
288 static char const *a_amv_var_canonify(char const *vn);
290 /* Try to reverse lookup an option name to an enum okeys mapping.
291 * Updates .avc_name and .avc_hash; .avc_map is NULL if none found */
292 static bool_t a_amv_var_revlookup(struct a_amv_var_carrier *avcp,
293 char const *name);
295 /* Lookup a variable from .avc_(map|name|hash), return whether it was found.
296 * Sets .avc_prime; .avc_var is NULL if not found.
297 * Here it is where we care for _I3VAL and _DEFVAL, too.
298 * An _I3VAL will be "consumed" as necessary anyway, but it won't be used to
299 * create a new variable if i3val_nonew is true; if i3val_nonew is TRUM1 then
300 * we set .avc_var to -1 and return true if that was the case, otherwise we'll
301 * return FAL0, then! */
302 static bool_t a_amv_var_lookup(struct a_amv_var_carrier *avcp,
303 bool_t i3val_nonew);
305 /* Lookup functions for special category variables, _mac drives all macro etc.
306 * local special categories */
307 static char const *a_amv_var_vsc_global(struct a_amv_var_carrier *avcp);
308 static char const *a_amv_var_vsc_multiplex(struct a_amv_var_carrier *avcp);
309 static char const *a_amv_var_vsc_mac(struct a_amv_var_carrier *avcp);
311 /* Set var from .avc_(map|name|hash), return success */
312 static bool_t a_amv_var_set(struct a_amv_var_carrier *avcp, char const *value,
313 bool_t force_env);
315 static bool_t a_amv_var__putenv(struct a_amv_var_carrier *avcp,
316 struct a_amv_var *avp);
318 /* Clear var from .avc_(map|name|hash); sets .avc_var=NULL, return success */
319 static bool_t a_amv_var_clear(struct a_amv_var_carrier *avcp, bool_t force_env);
321 static bool_t a_amv_var__clearenv(char const *name, char *value);
323 /* List all variables */
324 static void a_amv_var_show_all(void);
326 static int a_amv_var__show_cmp(void const *s1, void const *s2);
328 /* Actually do print one, return number of lines written */
329 static size_t a_amv_var_show(char const *name, FILE *fp, struct n_string *msgp);
331 /* Shared c_set() and c_environ():set impl, return success */
332 static bool_t a_amv_var_c_set(char **ap, bool_t issetenv);
334 static struct a_amv_mac *
335 a_amv_mac_lookup(char const *name, struct a_amv_mac *newamp,
336 enum a_amv_mac_flags amf){
337 struct a_amv_mac *amp, **ampp;
338 ui32_t h;
339 enum a_amv_mac_flags save_amf;
340 NYD2_ENTER;
342 save_amf = amf;
343 amf &= a_AMV_MF_TYPE_MASK;
344 h = a_AMV_NAME2HASH(name);
345 h = a_AMV_HASH2PRIME(h);
346 ampp = &a_amv_macs[h];
348 for(amp = *ampp; amp != NULL; ampp = &(*ampp)->am_next, amp = amp->am_next){
349 if((amp->am_flags & a_AMV_MF_TYPE_MASK) == amf &&
350 !strcmp(amp->am_name, name)){
351 if(n_LIKELY((save_amf & a_AMV_MF_UNDEF) == 0))
352 goto jleave;
354 *ampp = amp->am_next;
356 if(amp->am_refcnt > 0){
357 amp->am_flags |= a_AMV_MF_DELETE;
358 if(n_poption & n_PO_D_V)
359 n_err(_("Delayed deletion of currently active %s: %s\n"),
360 (amp->am_flags & a_AMV_MF_ACCOUNT ? "account" : "define"),
361 name);
362 }else{
363 a_amv_mac_free(amp);
364 amp = (struct a_amv_mac*)-1;
366 break;
370 if(newamp != NULL){
371 ampp = &a_amv_macs[h];
372 newamp->am_next = *ampp;
373 *ampp = newamp;
374 amp = NULL;
376 jleave:
377 NYD2_LEAVE;
378 return amp;
381 static int
382 a_amv_mac_call(void *v, bool_t silent_nexist){
383 int rv;
384 struct a_amv_mac *amp;
385 char const *name;
386 NYD_ENTER;
388 name = *(char const**)v;
390 if((amp = a_amv_mac_lookup(name, NULL, a_AMV_MF_NONE)) != NULL){
391 struct a_amv_mac_call_args *amcap;
393 amcap = n_lofi_alloc(sizeof *amcap);
394 memset(amcap, 0, sizeof *amcap);
395 amcap->amca_name = name;
396 amcap->amca_amp = amp;
397 /* C99 */{
398 char const **argv;
399 ui32_t argc;
401 for(argc = 0, argv = v; *++argv != NULL; ++argc)
403 if(argc > 0){
404 amcap->amca_argc = argc;
405 amcap->amca_argv = &(argv = v)[1];
409 rv = a_amv_mac_exec(amcap);
410 rv = n_pstate_ex_no;
411 }else{
412 if((rv = (silent_nexist == FAL0)))
413 n_err(_("Undefined macro `call'ed: %s\n"),
414 n_shexp_quote_cp(name, FAL0));
415 n_pstate_err_no = n_ERR_NOENT;
416 rv = 1;
418 NYD_LEAVE;
419 return rv;
422 static bool_t
423 a_amv_mac_exec(struct a_amv_mac_call_args *amcap){
424 struct a_amv_lostack *losp;
425 struct a_amv_mac_line **amlp;
426 char **args_base, **args;
427 struct a_amv_mac *amp;
428 bool_t rv;
429 NYD2_ENTER;
431 amp = amcap->amca_amp;
432 assert(amp != NULL && amp != a_AMV_MACKY_MACK);
433 ++amp->am_refcnt;
434 /* XXX Unfortunately we yet need to dup the macro lines! :( */
435 args_base = args = smalloc(sizeof(*args) * (amp->am_line_cnt +1));
436 for(amlp = amp->am_line_dat; *amlp != NULL; ++amlp)
437 *(args++) = sbufdup((*amlp)->aml_dat, (*amlp)->aml_len);
438 *args = NULL;
440 losp = n_lofi_alloc(sizeof *losp);
441 losp->as_global_saved = a_amv_lopts;
442 if((losp->as_amcap = amcap)->amca_unroller == NULL){
443 losp->as_up = losp->as_global_saved;
444 losp->as_lopts = NULL;
445 }else{
446 losp->as_up = NULL;
447 losp->as_lopts = *amcap->amca_unroller;
449 losp->as_unroll = amcap->amca_lopts_on;
451 a_amv_lopts = losp;
452 if(amcap->amca_hook_pre != NULL)
453 n_PS_ROOT_BLOCK((*amcap->amca_hook_pre)(amcap->amca_hook_arg));
454 rv = n_go_macro(n_GO_INPUT_NONE, amp->am_name, args_base,
455 &a_amv_mac__finalize, losp);
456 NYD2_LEAVE;
457 return rv;
460 static void
461 a_amv_mac__finalize(void *vp){
462 struct a_amv_mac *amp;
463 struct a_amv_mac_call_args *amcap;
464 struct a_amv_lostack *losp;
465 NYD2_ENTER;
467 losp = vp;
468 a_amv_lopts = losp->as_global_saved;
470 if((amcap = losp->as_amcap)->amca_unroller == NULL){
471 if(losp->as_lopts != NULL)
472 a_amv_lopts_unroll(&losp->as_lopts);
473 }else
474 *amcap->amca_unroller = losp->as_lopts;
476 if(amcap->amca_ps_hook_mask)
477 n_pstate &= ~n_PS_HOOK_MASK;
479 if((amp = amcap->amca_amp) != a_AMV_MACKY_MACK && amp != NULL &&
480 --amp->am_refcnt == 0 && (amp->am_flags & a_AMV_MF_DELETE))
481 a_amv_mac_free(amp);
483 n_lofi_free(losp);
484 n_lofi_free(amcap);
485 NYD2_LEAVE;
488 static bool_t
489 a_amv_mac_show(enum a_amv_mac_flags amf){
490 size_t lc, mc, ti, i;
491 char const *typestr;
492 FILE *fp;
493 bool_t rv;
494 NYD2_ENTER;
496 rv = FAL0;
498 if((fp = Ftmp(NULL, "deflist", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
499 NULL){
500 n_perr(_("Can't create temporary file for `define' or `account' listing"),
502 goto jleave;
505 amf &= a_AMV_MF_TYPE_MASK;
506 typestr = (amf & a_AMV_MF_ACCOUNT) ? "account" : "define";
508 for(lc = mc = ti = 0; ti < a_AMV_PRIME; ++ti){
509 struct a_amv_mac *amp;
511 for(amp = a_amv_macs[ti]; amp != NULL; amp = amp->am_next){
512 if((amp->am_flags & a_AMV_MF_TYPE_MASK) == amf){
513 struct a_amv_mac_line **amlpp;
515 if(++mc > 1){
516 putc('\n', fp);
517 ++lc;
519 ++lc;
520 fprintf(fp, "%s %s {\n", typestr, amp->am_name);
521 for(amlpp = amp->am_line_dat; *amlpp != NULL; ++lc, ++amlpp){
522 for(i = (*amlpp)->aml_prespc; i > 0; --i)
523 putc(' ', fp);
524 fputs((*amlpp)->aml_dat, fp);
525 putc('\n', fp);
527 fputs("}\n", fp);
528 ++lc;
532 if(mc > 0)
533 page_or_print(fp, lc);
535 rv = (ferror(fp) == 0);
536 Fclose(fp);
537 jleave:
538 NYD2_LEAVE;
539 return rv;
542 static bool_t
543 a_amv_mac_def(char const *name, enum a_amv_mac_flags amf){
544 struct str line;
545 ui32_t line_cnt, maxlen;
546 struct linelist{
547 struct linelist *ll_next;
548 struct a_amv_mac_line *ll_amlp;
549 } *llp, *ll_head, *ll_tail;
550 union {size_t s; int i; ui32_t ui; size_t l;} n;
551 struct a_amv_mac *amp;
552 bool_t rv;
553 NYD2_ENTER;
555 memset(&line, 0, sizeof line);
556 rv = FAL0;
557 amp = NULL;
559 /* TODO We should have our input state machine which emits Line events,
560 * TODO and hook different consumers dependent on our content, as stated
561 * TODO in i think lex_input; */
562 /* Read in the lines which form the macro content */
563 for(ll_tail = ll_head = NULL, line_cnt = maxlen = 0;;){
564 ui32_t leaspc;
565 char *cp;
567 n.i = n_go_input(n_GO_INPUT_CTX_DEFAULT | n_GO_INPUT_NL_ESC, n_empty,
568 &line.s, &line.l, NULL);
569 if(n.ui == 0)
570 continue;
571 if(n.i < 0){
572 n_err(_("Unterminated %s definition: %s\n"),
573 (amf & a_AMV_MF_ACCOUNT ? "account" : "macro"), name);
574 goto jerr;
577 /* Trim WS, remember amount of leading spaces for display purposes */
578 for(cp = line.s, leaspc = 0; n.ui > 0; ++cp, --n.ui)
579 if(*cp == '\t')
580 leaspc = (leaspc + 8u) & ~7u;
581 else if(*cp == ' ')
582 ++leaspc;
583 else
584 break;
585 for(; n.ui > 0 && spacechar(cp[n.ui - 1]); --n.ui)
587 if(n.ui == 0)
588 continue;
590 maxlen = n_MAX(maxlen, n.ui);
591 cp[n.ui++] = '\0';
593 /* Is is the closing brace? */
594 if(*cp == '}')
595 break;
597 if(n_LIKELY(++line_cnt < UI32_MAX)){
598 struct a_amv_mac_line *amlp;
600 llp = salloc(sizeof *llp);
601 if(ll_head == NULL)
602 ll_head = llp;
603 else
604 ll_tail->ll_next = llp;
605 ll_tail = llp;
606 llp->ll_next = NULL;
607 llp->ll_amlp = amlp = smalloc(n_VSTRUCT_SIZEOF(struct a_amv_mac_line,
608 aml_dat) + n.ui);
609 amlp->aml_len = n.ui -1;
610 amlp->aml_prespc = leaspc;
611 memcpy(amlp->aml_dat, cp, n.ui);
612 }else{
613 n_err(_("Too much content in %s definition: %s\n"),
614 (amf & a_AMV_MF_ACCOUNT ? "account" : "macro"), name);
615 goto jerr;
619 /* Create the new macro */
620 n.s = strlen(name) +1;
621 amp = smalloc(n_VSTRUCT_SIZEOF(struct a_amv_mac, am_name) + n.s);
622 amp->am_next = NULL;
623 amp->am_maxlen = maxlen;
624 amp->am_line_cnt = line_cnt;
625 amp->am_refcnt = 0;
626 amp->am_flags = amf;
627 amp->am_lopts = NULL;
628 memcpy(amp->am_name, name, n.s);
629 /* C99 */{
630 struct a_amv_mac_line **amlpp;
632 amp->am_line_dat = amlpp = smalloc(sizeof(*amlpp) * ++line_cnt);
633 for(llp = ll_head; llp != NULL; llp = llp->ll_next)
634 *amlpp++ = llp->ll_amlp;
635 *amlpp = NULL;
638 /* Create entry, replace a yet existing one as necessary */
639 a_amv_mac_lookup(name, amp, amf | a_AMV_MF_UNDEF);
640 rv = TRU1;
641 jleave:
642 if(line.s != NULL)
643 free(line.s);
644 NYD2_LEAVE;
645 return rv;
647 jerr:
648 for(llp = ll_head; llp != NULL; llp = llp->ll_next)
649 free(llp->ll_amlp);
650 if(amp != NULL){
651 free(amp->am_line_dat);
652 free(amp);
654 goto jleave;
657 static bool_t
658 a_amv_mac_undef(char const *name, enum a_amv_mac_flags amf){
659 struct a_amv_mac *amp;
660 bool_t rv;
661 NYD2_ENTER;
663 rv = TRU1;
665 if(n_LIKELY(name[0] != '*' || name[1] != '\0')){
666 if((amp = a_amv_mac_lookup(name, NULL, amf | a_AMV_MF_UNDEF)) == NULL){
667 n_err(_("%s not defined: %s\n"),
668 (amf & a_AMV_MF_ACCOUNT ? "Account" : "Macro"), name);
669 rv = FAL0;
671 }else{
672 struct a_amv_mac **ampp, *lamp;
674 for(ampp = a_amv_macs; PTRCMP(ampp, <, &a_amv_macs[n_NELEM(a_amv_macs)]);
675 ++ampp)
676 for(lamp = NULL, amp = *ampp; amp != NULL;){
677 if((amp->am_flags & a_AMV_MF_TYPE_MASK) == amf){
678 /* xxx Expensive but rare: be simple */
679 a_amv_mac_lookup(amp->am_name, NULL, amf | a_AMV_MF_UNDEF);
680 amp = (lamp == NULL) ? *ampp : lamp->am_next;
681 }else{
682 lamp = amp;
683 amp = amp->am_next;
687 NYD2_LEAVE;
688 return rv;
691 static void
692 a_amv_mac_free(struct a_amv_mac *amp){
693 struct a_amv_mac_line **amlpp;
694 NYD2_ENTER;
696 for(amlpp = amp->am_line_dat; *amlpp != NULL; ++amlpp)
697 free(*amlpp);
698 free(amp->am_line_dat);
699 free(amp);
700 NYD2_LEAVE;
703 static void
704 a_amv_lopts_add(struct a_amv_lostack *alp, char const *name,
705 struct a_amv_var *oavp){
706 struct a_amv_var *avp;
707 size_t nl, vl;
708 NYD2_ENTER;
710 /* Propagate unrolling up the stack, as necessary */
711 assert(alp != NULL);
712 for(;;){
713 if(alp->as_unroll)
714 break;
715 if((alp = alp->as_up) == NULL)
716 goto jleave;
719 /* Check whether this variable is handled yet */
720 for(avp = alp->as_lopts; avp != NULL; avp = avp->av_link)
721 if(!strcmp(avp->av_name, name))
722 goto jleave;
724 nl = strlen(name) +1;
725 vl = (oavp != NULL) ? strlen(oavp->av_value) +1 : 0;
726 avp = smalloc(n_VSTRUCT_SIZEOF(struct a_amv_var, av_name) + nl + vl);
727 avp->av_link = alp->as_lopts;
728 alp->as_lopts = avp;
729 memcpy(avp->av_name, name, nl);
730 if(vl == 0){
731 avp->av_value = NULL;
732 avp->av_flags = 0;
733 #ifdef HAVE_PUTENV
734 avp->av_env = NULL;
735 #endif
736 }else{
737 avp->av_value = &avp->av_name[nl];
738 avp->av_flags = oavp->av_flags;
739 memcpy(avp->av_value, oavp->av_value, vl);
740 #ifdef HAVE_PUTENV
741 avp->av_env = (oavp->av_env == NULL) ? NULL : sstrdup(oavp->av_env);
742 #endif
744 jleave:
745 NYD2_LEAVE;
748 static void
749 a_amv_lopts_unroll(struct a_amv_var **avpp){
750 struct a_amv_lostack *save_alp;
751 struct a_amv_var *x, *avp;
752 NYD2_ENTER;
754 avp = *avpp;
755 *avpp = NULL;
757 save_alp = a_amv_lopts;
758 a_amv_lopts = NULL;
759 while(avp != NULL){
760 x = avp;
761 avp = avp->av_link;
762 n_PS_ROOT_BLOCK(n_var_vset(x->av_name, (uintptr_t)x->av_value));
763 free(x);
765 a_amv_lopts = save_alp;
766 NYD2_LEAVE;
769 static char *
770 a_amv_var_copy(char const *str){
771 char *news;
772 size_t len;
773 NYD2_ENTER;
775 if(*str == '\0')
776 news = n_UNCONST(n_empty);
777 else if(str[1] == '\0'){
778 if(str[0] == '1')
779 news = n_UNCONST(n_1);
780 else if(str[0] == '0')
781 news = n_UNCONST(n_0);
782 else
783 goto jheap;
784 }else if(str[2] == '\0' && str[0] == '-' && str[1] == '1')
785 news = n_UNCONST(n_m1);
786 else{
787 jheap:
788 len = strlen(str) +1;
789 news = smalloc(len);
790 memcpy(news, str, len);
792 NYD2_LEAVE;
793 return news;
796 static void
797 a_amv_var_free(char *cp){
798 NYD2_ENTER;
799 if(cp[0] != '\0' && cp != n_0 && cp != n_1 && cp != n_m1)
800 free(cp);
801 NYD2_LEAVE;
804 static bool_t
805 a_amv_var_check_vips(enum a_amv_var_vip_mode avvm, enum okeys okey,
806 char const *val){
807 bool_t ok;
808 NYD2_ENTER;
810 ok = TRU1;
812 if(avvm == a_AMV_VIP_SET_PRE){
813 switch(okey){
814 default:
815 break;
816 case ok_v_HOME:
817 /* Note this gets called from main.c during initialization, and they
818 * simply set this to pw_dir as a fallback: don't verify _that_ call.
819 * See main.c! */
820 if(!(n_pstate & n_PS_ROOT) && !n_is_dir(val, TRU1)){
821 n_err(_("$HOME is not a directory or not accessible: %s\n"),
822 n_shexp_quote_cp(val, FAL0));
823 ok = FAL0;
824 break;
826 case ok_v_TMPDIR:
827 if(!n_is_dir(val, TRU1)){
828 n_err(_("$TMPDIR is not a directory or not accessible: %s\n"),
829 n_shexp_quote_cp(val, FAL0));
830 ok = FAL0;
832 break;
833 case ok_v_umask:
834 if(*val != '\0'){
835 ui64_t uib;
837 n_idec_ui64_cp(&uib, val, 0, NULL);
838 if(uib & ~0777u){ /* (is valid _VF_POSNUM) */
839 n_err(_("Invalid *umask* setting: %s\n"), val);
840 ok = FAL0;
843 break;
845 }else if(avvm == a_AMV_VIP_SET_POST){
846 switch(okey){
847 default:
848 break;
849 case ok_b_debug:
850 n_poption |= n_PO_DEBUG;
851 break;
852 case ok_v_HOME:
853 /* Invalidate any resolved folder then, too
854 * FALLTHRU */
855 case ok_v_folder:
856 n_PS_ROOT_BLOCK(ok_vclear(folder_resolved));
857 break;
858 case ok_b_memdebug:
859 n_poption |= n_PO_MEMDEBUG;
860 break;
861 case ok_b_POSIXLY_CORRECT: /* <-> *posix* */
862 if(!(n_pstate & n_PS_ROOT))
863 n_PS_ROOT_BLOCK(ok_bset(posix));
864 break;
865 case ok_b_posix: /* <-> $POSIXLY_CORRECT */
866 if(!(n_pstate & n_PS_ROOT))
867 n_PS_ROOT_BLOCK(ok_bset(POSIXLY_CORRECT));
868 break;
869 case ok_b_skipemptybody:
870 n_poption |= n_PO_E_FLAG;
871 break;
872 case ok_b_typescript_mode:
873 ok_bset(colour_disable);
874 ok_bset(line_editor_disable);
875 if(!(n_psonce & n_PSO_STARTED))
876 ok_bset(termcap_disable);
877 case ok_v_umask:
878 if(*val != '\0'){
879 ui64_t uib;
881 n_idec_ui64_cp(&uib, val, 0, NULL);
882 umask((mode_t)uib);
884 break;
885 case ok_b_verbose:
886 n_poption |= (n_poption & n_PO_VERB) ? n_PO_VERBVERB : n_PO_VERB;
887 break;
889 }else{
890 switch(okey){
891 default:
892 break;
893 case ok_b_debug:
894 n_poption &= ~n_PO_DEBUG;
895 break;
896 case ok_v_HOME:
897 /* Invalidate any resolved folder then, too
898 * FALLTHRU */
899 case ok_v_folder:
900 n_PS_ROOT_BLOCK(ok_vclear(folder_resolved));
901 break;
902 case ok_b_memdebug:
903 n_poption &= ~n_PO_MEMDEBUG;
904 break;
905 case ok_b_POSIXLY_CORRECT: /* <-> *posix* */
906 if(!(n_pstate & n_PS_ROOT))
907 n_PS_ROOT_BLOCK(ok_bclear(posix));
908 break;
909 case ok_b_posix: /* <-> $POSIXLY_CORRECT */
910 if(!(n_pstate & n_PS_ROOT))
911 n_PS_ROOT_BLOCK(ok_bclear(POSIXLY_CORRECT));
912 break;
913 case ok_b_skipemptybody:
914 n_poption &= ~n_PO_E_FLAG;
915 break;
916 case ok_b_verbose:
917 n_poption &= ~(n_PO_VERB | n_PO_VERBVERB);
918 break;
921 NYD2_LEAVE;
922 return ok;
925 static bool_t
926 a_amv_var_check_nocntrls(char const *val){
927 char c;
928 NYD2_ENTER;
930 while((c = *val++) != '\0')
931 if(cntrlchar(c))
932 break;
933 NYD2_LEAVE;
934 return (c == '\0');
937 static bool_t
938 a_amv_var_check_num(char const *val, bool_t posnum){
939 /* TODO The internal/environment variables which are num= or posnum= should
940 * TODO gain special lookup functions, or the return should be void* and
941 * TODO castable to integer; i.e. no more strtoX() should be needed.
942 * TODO I.e., the result of this function should instead be stored */
943 bool_t rv;
944 NYD2_ENTER;
946 rv = TRU1;
948 if(*val != '\0'){ /* Would be _VF_NOTEMPTY if not allowed */
949 ui64_t uib;
950 enum n_idec_state ids;
952 ids = n_idec_cp(&uib, val, 0,
953 (posnum ? n_IDEC_MODE_SIGNED_TYPE : n_IDEC_MODE_NONE), NULL);
954 if((ids & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
955 ) != n_IDEC_STATE_CONSUMED)
956 rv = FAL0;
957 /* TODO Unless we store integers we need to look and forbid, because
958 * TODO callee may not be able to swallow, e.g., "-1" */
959 if(posnum && (ids & n_IDEC_STATE_SEEN_MINUS))
960 rv = FAL0;
962 NYD2_LEAVE;
963 return rv;
966 static char const *
967 a_amv_var_canonify(char const *vn){
968 NYD2_ENTER;
969 if(!upperchar(*vn)){
970 char const *vp;
972 for(vp = vn; *vp != '\0' && *vp != '@'; ++vp)
974 vn = (*vp == '@') ? i_strdup(vn) : vn;
976 NYD2_LEAVE;
977 return vn;
980 static bool_t
981 a_amv_var_revlookup(struct a_amv_var_carrier *avcp, char const *name){
982 ui32_t hash, i, j;
983 struct a_amv_var_map const *avmp;
984 char c;
985 NYD2_ENTER;
987 /* It may be a special a.k.a. macro-local or one-letter parameter */
988 c = name[0];
989 if(n_UNLIKELY(digitchar(c))){
990 /* (Inline dec. atoi, ugh) */
991 for(j = (ui8_t)c - '0', i = 1;; ++i){
992 c = name[i];
993 if(c == '\0')
994 break;
995 if(!digitchar(c))
996 goto jno_special_param;
997 j = j * 10 + (ui8_t)c - '0';
999 if(j <= SI16_MAX){
1000 avcp->avc_special_cat = a_AMV_VSC_MAC_ARGV;
1001 goto jspecial_param;
1003 }else if(n_UNLIKELY(name[1] == '\0')){
1004 switch(c){
1005 case '?':
1006 case '!':
1007 avcp->avc_special_cat = a_AMV_VSC_GLOBAL;
1008 j = (c == '?') ? a_AMV_VST_QM : a_AMV_VST_EM;
1009 goto jspecial_param;
1010 case '^':
1011 goto jmultiplex;
1012 case '*':
1013 avcp->avc_special_cat = a_AMV_VSC_MAC;
1014 j = a_AMV_VST_STAR;
1015 goto jspecial_param;
1016 case '@':
1017 avcp->avc_special_cat = a_AMV_VSC_MAC;
1018 j = a_AMV_VST_AT;
1019 goto jspecial_param;
1020 case '#':
1021 avcp->avc_special_cat = a_AMV_VSC_MAC;
1022 j = a_AMV_VST_NOSIGN;
1023 goto jspecial_param;
1024 default:
1025 break;
1027 }else if(c == '^'){
1028 jmultiplex:
1029 avcp->avc_special_cat = a_AMV_VSC_MULTIPLEX;
1030 j = a_AMV_VST_CACC;
1031 goto jspecial_param;
1034 /* Normal reverse lookup, walk over the hashtable */
1035 jno_special_param:
1036 avcp->avc_special_cat = a_AMV_VSC_NONE;
1037 avcp->avc_name = name = a_amv_var_canonify(name);
1038 avcp->avc_hash = hash = a_AMV_NAME2HASH(name);
1040 for(i = hash % a_AMV_VAR_REV_PRIME, j = 0; j <= a_AMV_VAR_REV_LONGEST; ++j){
1041 ui32_t x;
1043 if((x = a_amv_var_revmap[i]) == a_AMV_VAR_REV_ILL)
1044 break;
1046 avmp = &a_amv_var_map[x];
1047 if(avmp->avm_hash == hash &&
1048 !strcmp(&a_amv_var_names[avmp->avm_keyoff], name)){
1049 avcp->avc_map = avmp;
1050 avcp->avc_okey = (enum okeys)x;
1051 goto jleave;
1054 if(++i == a_AMV_VAR_REV_PRIME){
1055 #ifdef a_AMV_VAR_REV_WRAPAROUND
1056 i = 0;
1057 #else
1058 break;
1059 #endif
1062 avcp->avc_map = NULL;
1063 avcp = NULL;
1064 jleave:
1065 NYD2_LEAVE;
1066 return (avcp != NULL);
1068 /* All these are mapped to *--special-param* */
1069 jspecial_param:
1070 avcp->avc_name = name;
1071 avcp->avc_special_prop = (ui16_t)j;
1072 avmp = &a_amv_var_map[a_AMV_VAR__SPECIAL_PARAM_MAP_IDX];
1073 avcp->avc_hash = avmp->avm_hash;
1074 avcp->avc_map = avmp;
1075 avcp->avc_okey = ok_v___special_param;
1076 goto jleave;
1079 static bool_t
1080 a_amv_var_lookup(struct a_amv_var_carrier *avcp, bool_t i3val_nonew){
1081 size_t i;
1082 char const *cp;
1083 struct a_amv_var_map const *avmp;
1084 struct a_amv_var *avp;
1085 NYD2_ENTER;
1087 /* C99 */{
1088 struct a_amv_var **avpp, *lavp;
1090 avpp = &a_amv_vars[avcp->avc_prime = a_AMV_HASH2PRIME(avcp->avc_hash)];
1092 for(lavp = NULL, avp = *avpp; avp != NULL; lavp = avp, avp = avp->av_link)
1093 if(!strcmp(avp->av_name, avcp->avc_name)){
1094 /* Relink as head, hope it "sorts on usage" over time.
1095 * _clear() relies on this behaviour */
1096 if(lavp != NULL){
1097 lavp->av_link = avp->av_link;
1098 avp->av_link = *avpp;
1099 *avpp = avp;
1101 goto jleave;
1105 /* If this is not an assembled variable we need to consider some special
1106 * initialization cases and eventually create the variable anew */
1107 if(n_LIKELY((avmp = avcp->avc_map) != NULL)){
1108 /* Does it have an import-from-environment flag? */
1109 if(n_UNLIKELY((avmp->avm_flags & (a_AMV_VF_IMPORT | a_AMV_VF_ENV)) != 0)){
1110 if(n_LIKELY((cp = getenv(avcp->avc_name)) != NULL)){
1111 /* May be better not to use that one, though? */
1112 bool_t isempty, isbltin;
1114 isempty = (*cp == '\0' &&
1115 (avmp->avm_flags & a_AMV_VF_NOTEMPTY) != 0);
1116 isbltin = ((avmp->avm_flags & (a_AMV_VF_I3VAL | a_AMV_VF_DEFVAL)
1117 ) != 0);
1119 if(n_UNLIKELY(isempty)){
1120 if(!isbltin)
1121 goto jerr;
1122 }else if(n_LIKELY(*cp != '\0')){
1123 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_NOCNTRLS) &&
1124 !a_amv_var_check_nocntrls(cp))){
1125 n_err(_("Ignoring environment, control characters "
1126 "invalid in variable: %s\n"), avcp->avc_name);
1127 goto jerr;
1129 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_NUM) &&
1130 !a_amv_var_check_num(cp, FAL0))){
1131 n_err(_("Environment variable value not a number "
1132 "or out of range: %s\n"), avcp->avc_name);
1133 goto jerr;
1135 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_POSNUM) &&
1136 !a_amv_var_check_num(cp, TRU1))){
1137 n_err(_("Environment variable value not a number, "
1138 "negative or out of range: %s\n"), avcp->avc_name);
1139 goto jerr;
1141 goto jnewval;
1142 }else
1143 goto jnewval;
1147 /* A first-time init switch is to be handled now and here */
1148 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_I3VAL) != 0)){
1149 static struct a_amv_var_defval const **arr,
1150 *arr_base[a_AMV_VAR_I3VALS_CNT +1];
1152 if(arr == NULL){
1153 arr = &arr_base[0];
1154 arr[i = a_AMV_VAR_I3VALS_CNT] = NULL;
1155 while(i-- > 0)
1156 arr[i] = &a_amv_var_i3vals[i];
1159 for(i = 0; arr[i] != NULL; ++i)
1160 if(arr[i]->avdv_okey == avcp->avc_okey){
1161 cp = (avmp->avm_flags & a_AMV_VF_BOOL) ? n_empty
1162 : arr[i]->avdv_value;
1163 /* Remove this entry, hope entire block becomes no-op asap */
1165 arr[i] = arr[i + 1];
1166 while(arr[i++] != NULL);
1168 if(!i3val_nonew)
1169 goto jnewval;
1170 if(i3val_nonew == TRUM1)
1171 avp = (struct a_amv_var*)-1;
1172 goto jleave;
1176 /* The virtual variables */
1177 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_VIRT) != 0)){
1178 for(i = 0; i < a_AMV_VAR_VIRTS_CNT; ++i)
1179 if(a_amv_var_virts[i].avv_okey == avcp->avc_okey){
1180 avp = n_UNCONST(a_amv_var_virts[i].avv_var);
1181 goto jleave;
1183 /* Not reached */
1186 /* Place this last because once it is set first the variable will never
1187 * be removed again and thus match in the first block above */
1188 jdefval:
1189 if(n_UNLIKELY(avmp->avm_flags & a_AMV_VF_DEFVAL) != 0){
1190 for(i = 0; i < a_AMV_VAR_DEFVALS_CNT; ++i)
1191 if(a_amv_var_defvals[i].avdv_okey == avcp->avc_okey){
1192 cp = (avmp->avm_flags & a_AMV_VF_BOOL) ? n_empty
1193 : a_amv_var_defvals[i].avdv_value;
1194 goto jnewval;
1199 jerr:
1200 avp = NULL;
1201 jleave:
1202 avcp->avc_var = avp;
1203 NYD2_LEAVE;
1204 return (avp != NULL);
1206 jnewval:
1207 /* E.g., $TMPDIR may be set to non-existent, so we need to be able to catch
1208 * that and redirect to a possible default value */
1209 if((avmp->avm_flags & a_AMV_VF_VIP) &&
1210 !a_amv_var_check_vips(a_AMV_VIP_SET_PRE, avcp->avc_okey, cp)){
1211 #ifdef HAVE_SETENV
1212 if(avmp->avm_flags & (a_AMV_VF_IMPORT | a_AMV_VF_ENV))
1213 unsetenv(avcp->avc_name);
1214 #endif
1215 if(n_UNLIKELY(avmp->avm_flags & a_AMV_VF_DEFVAL) != 0)
1216 goto jdefval;
1217 goto jerr;
1218 }else{
1219 struct a_amv_var **avpp;
1220 size_t l;
1222 l = strlen(avcp->avc_name) +1;
1223 avcp->avc_var =
1224 avp = smalloc(n_VSTRUCT_SIZEOF(struct a_amv_var, av_name) + l);
1225 avp->av_link = *(avpp = &a_amv_vars[avcp->avc_prime]);
1226 *avpp = avp;
1227 memcpy(avp->av_name, avcp->avc_name, l);
1228 #ifdef HAVE_PUTENV
1229 avp->av_env = NULL;
1230 #endif
1231 avp->av_flags = avmp->avm_flags;
1232 avp->av_value = a_amv_var_copy(cp);
1234 if(avp->av_flags & a_AMV_VF_ENV)
1235 a_amv_var__putenv(avcp, avp);
1236 if(avmp->avm_flags & a_AMV_VF_VIP)
1237 a_amv_var_check_vips(a_AMV_VIP_SET_POST, avcp->avc_okey, cp);
1238 goto jleave;
1242 static char const *
1243 a_amv_var_vsc_global(struct a_amv_var_carrier *avcp){
1244 char itoabuf[32];
1245 char const *rv;
1246 si32_t *ep;
1247 struct a_amv_var_map const *avmp;
1248 NYD2_ENTER;
1250 /* Not function local, TODO but lazy evaluted for now */
1251 if(avcp->avc_special_prop == a_AMV_VST_QM){
1252 avmp = &a_amv_var_map[a_AMV_VAR__QM_MAP_IDX];
1253 avcp->avc_okey = ok_v___qm;
1254 ep = &n_pstate_ex_no;
1255 }else{
1256 avmp = &a_amv_var_map[a_AMV_VAR__EM_MAP_IDX];
1257 avcp->avc_okey = ok_v___em;
1258 ep = &n_pstate_err_no;
1261 /* XXX Oh heaven, we are responsible to ensure that $?/! is up-to-date
1262 * XXX and represents n_pstate_err_no; TODO unfortunately
1263 * TODO we could num=1 ok_v___[qe]m, but the thing is still a string
1264 * TODO and thus conversion takes places over and over again; also
1265 * TODO for now that would have to occur before we set _that_ value
1266 * TODO so let's special treat it until we store ints as such */
1267 if(*ep >= 0){
1268 switch(*ep){
1269 case 0: rv = n_0; break;
1270 case 1: rv = n_1; break;
1271 default:
1272 snprintf(itoabuf, sizeof itoabuf, "%d", *ep);
1273 rv = itoabuf;
1274 break;
1276 n_PS_ROOT_BLOCK(n_var_okset(avcp->avc_okey, (uintptr_t)rv));
1278 avcp->avc_hash = avmp->avm_hash;
1279 avcp->avc_map = avmp;
1281 rv = a_amv_var_lookup(avcp, TRU1) ? avcp->avc_var->av_value : NULL;
1282 NYD2_LEAVE;
1283 return rv;
1286 static char const *
1287 a_amv_var_vsc_multiplex(struct a_amv_var_carrier *avcp){
1288 char itoabuf[32];
1289 si32_t e;
1290 size_t i;
1291 char const *rv;
1292 NYD2_ENTER;
1294 i = strlen(rv = &avcp->avc_name[1]);
1296 /* ERR, ERRDOC, ERRNAME, plus *-NAME variants */
1297 if(rv[0] == 'E' && i >= 3 && rv[1] == 'R' && rv[2] == 'R'){
1298 if(i == 3){
1299 e = n_pstate_err_no;
1300 goto jeno;
1301 }else if(rv[3] == '-'){
1302 e = n_err_from_name(&rv[4]);
1303 jeno:
1304 switch(e){
1305 case 0: rv = n_0; break;
1306 case 1: rv = n_1; break;
1307 default:
1308 snprintf(itoabuf, sizeof itoabuf, "%d", e);
1309 rv = savestr(itoabuf); /* XXX yet, cannot do numbers */
1310 break;
1312 goto jleave;
1313 }else if(i >= 6){
1314 if(!memcmp(&rv[3], "DOC", 3)){
1315 rv += 6;
1316 switch(*rv){
1317 case '\0': e = n_pstate_err_no; break;
1318 case '-': e = n_err_from_name(&rv[1]); break;
1319 default: goto jerr;
1321 rv = n_err_to_doc(e);
1322 goto jleave;
1323 }else if(i >= 7 && !memcmp(&rv[3], "NAME", 4)){
1324 rv += 7;
1325 switch(*rv){
1326 case '\0': e = n_pstate_err_no; break;
1327 case '-': e = n_err_from_name(&rv[1]); break;
1328 default: goto jerr;
1330 rv = n_err_to_name(e);
1331 goto jleave;
1336 jerr:
1337 rv = NULL;
1338 jleave:
1339 NYD2_LEAVE;
1340 return rv;
1343 static char const *
1344 a_amv_var_vsc_mac(struct a_amv_var_carrier *avcp){
1345 bool_t ismacky;
1346 struct a_amv_mac_call_args *amcap;
1347 char const *rv;
1348 NYD2_ENTER;
1350 rv = NULL;
1352 /* These may occur only in a macro.. */
1353 if(n_UNLIKELY(a_amv_lopts == NULL))
1354 goto jmaylog;
1356 amcap = a_amv_lopts->as_amcap;
1358 /* ..in a `call'ed macro only, to be exact. Or in a_AMV_MACKY_MACK */
1359 if(!(ismacky = (amcap->amca_amp == a_AMV_MACKY_MACK)) &&
1360 (amcap->amca_ps_hook_mask ||
1361 (assert(amcap->amca_amp != NULL),
1362 (amcap->amca_amp->am_flags & a_AMV_MF_TYPE_MASK
1363 ) == a_AMV_MF_ACCOUNT)))
1364 goto jleave;
1366 if(avcp->avc_special_cat == a_AMV_VSC_MAC_ARGV){
1367 if(avcp->avc_special_prop > 0){
1368 if(amcap->amca_argc >= avcp->avc_special_prop)
1369 rv = amcap->amca_argv[avcp->avc_special_prop - 1];
1370 }else if(ismacky)
1371 rv = amcap->amca_name;
1372 else
1373 rv = (a_amv_lopts->as_up != NULL
1374 ? a_amv_lopts->as_up->as_amcap->amca_name : n_empty);
1375 goto jleave;
1378 /* MACKY_MACK doesn't know about [*@#] */
1379 if(ismacky)
1380 goto jmaylog;
1382 switch(avcp->avc_special_prop){
1383 case a_AMV_VST_STAR:
1384 case a_AMV_VST_AT:{
1385 ui32_t i, l;
1387 for(i = l = 0; i < amcap->amca_argc; ++i)
1388 l += strlen(amcap->amca_argv[i]) + 1;
1389 if(l == 0)
1390 rv = n_empty;
1391 else{
1392 char *cp;
1394 rv = cp = salloc(l);
1395 for(i = l = 0; i < amcap->amca_argc; ++i){
1396 l = strlen(amcap->amca_argv[i]);
1397 memcpy(cp, amcap->amca_argv[i], l);
1398 cp += l;
1399 *cp++ = ' ';
1401 *--cp = '\0';
1403 } break;
1404 case a_AMV_VST_NOSIGN:{
1405 char *cp;
1407 rv = cp = salloc(sizeof("65535"));
1408 snprintf(cp, sizeof("65535"), "%hu", amcap->amca_argc);
1409 } break;
1410 default:
1411 rv = n_empty;
1412 break;
1415 jleave:
1416 NYD2_LEAVE;
1417 return rv;
1418 jmaylog:
1419 if(n_poption & n_PO_D_V)
1420 n_err(_("Cannot use macro local variable in this context: %s\n"),
1421 n_shexp_quote_cp(avcp->avc_name, FAL0));
1422 goto jleave;
1425 static bool_t
1426 a_amv_var_set(struct a_amv_var_carrier *avcp, char const *value,
1427 bool_t force_env){
1428 struct a_amv_var *avp;
1429 char *oval;
1430 struct a_amv_var_map const *avmp;
1431 bool_t rv;
1432 NYD2_ENTER;
1434 if(value == NULL){
1435 rv = a_amv_var_clear(avcp, force_env);
1436 goto jleave;
1439 if((avmp = avcp->avc_map) != NULL){
1440 rv = FAL0;
1442 /* Validity checks */
1443 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_RDONLY) != 0 &&
1444 !(n_pstate & n_PS_ROOT))){
1445 value = N_("Variable is read-only: %s\n");
1446 goto jeavmp;
1448 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_NOTEMPTY) && *value == '\0')){
1449 value = N_("Variable must not be empty: %s\n");
1450 goto jeavmp;
1452 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_NOCNTRLS) != 0 &&
1453 !a_amv_var_check_nocntrls(value))){
1454 value = N_("Variable forbids control characters: %s\n");
1455 goto jeavmp;
1457 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_NUM) &&
1458 !a_amv_var_check_num(value, FAL0))){
1459 value = N_("Variable value not a number or out of range: %s\n");
1460 goto jeavmp;
1462 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_POSNUM) &&
1463 !a_amv_var_check_num(value, TRU1))){
1464 value = _("Variable value not a number, negative, "
1465 "or out of range: %s\n");
1466 goto jeavmp;
1468 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_IMPORT) != 0 &&
1469 !(n_psonce & n_PSO_STARTED) && !(n_pstate & n_PS_ROOT))){
1470 value = N_("Variable cannot be set in a resource file: %s\n");
1471 goto jeavmp;
1474 /* Any more complicated inter-dependency? */
1475 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_VIP) != 0 &&
1476 !a_amv_var_check_vips(a_AMV_VIP_SET_PRE, avcp->avc_okey, value))){
1477 value = N_("Assignment of variable aborted: %s\n");
1478 jeavmp:
1479 n_err(V_(value), avcp->avc_name);
1480 goto jleave;
1483 /* Transformations */
1484 if(n_UNLIKELY(avmp->avm_flags & a_AMV_VF_LOWER)){
1485 char c;
1487 oval = savestr(value);
1488 value = oval;
1489 for(; (c = *oval) != '\0'; ++oval)
1490 *oval = lowerconv(c);
1494 rv = TRU1;
1495 a_amv_var_lookup(avcp, TRU1);
1497 /* Don't care what happens later on, store this in the unroll list */
1498 if(a_amv_lopts != NULL &&
1499 (avmp == NULL || !(avmp->avm_flags & a_AMV_VF_NOLOPTS)))
1500 a_amv_lopts_add(a_amv_lopts, avcp->avc_name, avcp->avc_var);
1502 if((avp = avcp->avc_var) == NULL){
1503 struct a_amv_var **avpp;
1504 size_t l;
1506 l = strlen(avcp->avc_name) +1;
1507 avcp->avc_var = avp = smalloc(n_VSTRUCT_SIZEOF(struct a_amv_var, av_name
1508 ) + l);
1509 avp->av_link = *(avpp = &a_amv_vars[avcp->avc_prime]);
1510 *avpp = avp;
1511 #ifdef HAVE_PUTENV
1512 avp->av_env = NULL;
1513 #endif
1514 memcpy(avp->av_name, avcp->avc_name, l);
1515 avp->av_flags = (avmp != NULL) ? avmp->avm_flags : 0;
1516 oval = n_UNCONST(n_empty);
1517 }else
1518 oval = avp->av_value;
1520 if(avmp == NULL)
1521 avp->av_value = a_amv_var_copy(value);
1522 else{
1523 /* Via `set' etc. the user may give even boolean options non-boolean
1524 * values, ignore that and force boolean */
1525 if(avp->av_flags & a_AMV_VF_BOOL){
1526 if(!(n_pstate & n_PS_ROOT) && (n_poption & n_PO_D_VV) &&
1527 *value != '\0')
1528 n_err(_("Ignoring value of boolean variable: %s: %s\n"),
1529 avcp->avc_name, value);
1530 avp->av_value = n_UNCONST(n_1);
1531 }else
1532 avp->av_value = a_amv_var_copy(value);
1535 if(force_env && !(avp->av_flags & a_AMV_VF_ENV))
1536 avp->av_flags |= a_AMV_VF_LINKED;
1537 if(avp->av_flags & (a_AMV_VF_ENV | a_AMV_VF_LINKED))
1538 rv = a_amv_var__putenv(avcp, avp);
1539 if(avp->av_flags & a_AMV_VF_VIP)
1540 a_amv_var_check_vips(a_AMV_VIP_SET_POST, avcp->avc_okey, value);
1541 a_amv_var_free(oval);
1542 jleave:
1543 NYD2_LEAVE;
1544 return rv;
1547 static bool_t
1548 a_amv_var__putenv(struct a_amv_var_carrier *avcp, struct a_amv_var *avp){
1549 #ifndef HAVE_SETENV
1550 char *cp;
1551 #endif
1552 bool_t rv;
1553 NYD2_ENTER;
1555 #ifdef HAVE_SETENV
1556 rv = (setenv(avcp->avc_name, avp->av_value, 1) == 0);
1557 #else
1558 cp = sstrdup(savecatsep(avcp->avc_name, '=', avp->av_value));
1560 if((rv = (putenv(cp) == 0))){
1561 char *ocp;
1563 if((ocp = avp->av_env) != NULL)
1564 free(ocp);
1565 avp->av_env = cp;
1566 }else
1567 free(cp);
1568 #endif
1569 NYD2_LEAVE;
1570 return rv;
1573 static bool_t
1574 a_amv_var_clear(struct a_amv_var_carrier *avcp, bool_t force_env){
1575 struct a_amv_var **avpp, *avp;
1576 struct a_amv_var_map const *avmp;
1577 bool_t rv;
1578 NYD2_ENTER;
1580 rv = FAL0;
1582 if(n_LIKELY((avmp = avcp->avc_map) != NULL)){
1583 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_NODEL) != 0 &&
1584 !(n_pstate & n_PS_ROOT))){
1585 n_err(_("Variable may not be unset: %s\n"), avcp->avc_name);
1586 goto jleave;
1588 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_VIP) != 0 &&
1589 !a_amv_var_check_vips(a_AMV_VIP_CLEAR, avcp->avc_okey, NULL))){
1590 n_err(_("Clearance of variable aborted: %s\n"), avcp->avc_name);
1591 goto jleave;
1595 rv = TRU1;
1597 if(n_UNLIKELY(!a_amv_var_lookup(avcp, TRUM1))){
1598 if(force_env){
1599 jforce_env:
1600 rv = a_amv_var__clearenv(avcp->avc_name, NULL);
1601 }else if(!(n_pstate & (n_PS_ROOT | n_PS_ROBOT)) && (n_poption & n_PO_D_V))
1602 n_err(_("Can't unset undefined variable: %s\n"), avcp->avc_name);
1603 goto jleave;
1604 }else if(avcp->avc_var == (struct a_amv_var*)-1){
1605 avcp->avc_var = NULL;
1606 if(force_env)
1607 goto jforce_env;
1608 goto jleave;
1611 if(a_amv_lopts != NULL &&
1612 (avmp == NULL || !(avmp->avm_flags & a_AMV_VF_NOLOPTS)))
1613 a_amv_lopts_add(a_amv_lopts, avcp->avc_name, avcp->avc_var);
1615 avp = avcp->avc_var;
1616 avcp->avc_var = NULL;
1617 avpp = &a_amv_vars[avcp->avc_prime];
1618 assert(*avpp == avp); /* (always listhead after lookup()) */
1619 *avpp = (*avpp)->av_link;
1621 /* C99 */{
1622 char *envval;
1624 #ifdef HAVE_SETENV
1625 envval = NULL;
1626 #else
1627 envval = avp->av_env;
1628 #endif
1629 if((avp->av_flags & (a_AMV_VF_ENV | a_AMV_VF_LINKED)) || envval != NULL)
1630 rv = a_amv_var__clearenv(avp->av_name, envval);
1632 a_amv_var_free(avp->av_value);
1633 free(avp);
1635 /* XXX Fun part, extremely simple-minded for now: if this variable has
1636 * XXX a default value, immediately reinstantiate it! TODO Heh? */
1637 if(n_UNLIKELY(avmp != NULL && (avmp->avm_flags & a_AMV_VF_DEFVAL) != 0))
1638 a_amv_var_lookup(avcp, TRU1);
1639 jleave:
1640 NYD2_LEAVE;
1641 return rv;
1644 static bool_t
1645 a_amv_var__clearenv(char const *name, char *value){
1646 #ifndef HAVE_SETENV
1647 extern char **environ;
1648 char **ecpp;
1649 #endif
1650 bool_t rv;
1651 NYD2_ENTER;
1652 n_UNUSED(value);
1654 #ifdef HAVE_SETENV
1655 unsetenv(name);
1656 rv = TRU1;
1657 #else
1658 if(value != NULL)
1659 for(ecpp = environ; *ecpp != NULL; ++ecpp)
1660 if(*ecpp == value){
1661 free(value);
1663 ecpp[0] = ecpp[1];
1664 while(*ecpp++ != NULL);
1665 break;
1667 rv = TRU1;
1668 #endif
1669 NYD2_LEAVE;
1670 return rv;
1673 static void
1674 a_amv_var_show_all(void){
1675 struct n_string msg, *msgp;
1676 FILE *fp;
1677 size_t no, i;
1678 struct a_amv_var *avp;
1679 char const **vacp, **cap;
1680 NYD2_ENTER;
1682 if((fp = Ftmp(NULL, "setlist", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL){
1683 n_perr(_("Can't create temporary file for `set' listing"), 0);
1684 goto jleave;
1687 /* We need to instantiate first-time-inits and default values here, so that
1688 * they will be regular members of our _vars[] table */
1689 for(i = a_AMV_VAR_I3VALS_CNT; i-- > 0;)
1690 n_var_oklook(a_amv_var_i3vals[i].avdv_okey);
1691 for(i = a_AMV_VAR_DEFVALS_CNT; i-- > 0;)
1692 n_var_oklook(a_amv_var_defvals[i].avdv_okey);
1694 for(no = i = 0; i < a_AMV_PRIME; ++i)
1695 for(avp = a_amv_vars[i]; avp != NULL; avp = avp->av_link)
1696 ++no;
1697 no += a_AMV_VAR_VIRTS_CNT;
1699 vacp = salloc(no * sizeof(*vacp));
1701 for(cap = vacp, i = 0; i < a_AMV_PRIME; ++i)
1702 for(avp = a_amv_vars[i]; avp != NULL; avp = avp->av_link)
1703 *cap++ = avp->av_name;
1704 for(i = a_AMV_VAR_VIRTS_CNT; i-- > 0;)
1705 *cap++ = a_amv_var_virts[i].avv_var->av_name;
1707 if(no > 1)
1708 qsort(vacp, no, sizeof *vacp, &a_amv_var__show_cmp);
1710 msgp = &msg;
1711 msgp = n_string_reserve(n_string_creat(msgp), 80);
1712 for(i = 0, cap = vacp; no != 0; ++cap, --no)
1713 i += a_amv_var_show(*cap, fp, msgp);
1714 n_string_gut(&msg);
1716 page_or_print(fp, i);
1717 Fclose(fp);
1718 jleave:
1719 NYD2_LEAVE;
1722 static int
1723 a_amv_var__show_cmp(void const *s1, void const *s2){
1724 int rv;
1725 NYD2_ENTER;
1727 rv = strcmp(*(char**)n_UNCONST(s1), *(char**)n_UNCONST(s2));
1728 NYD2_LEAVE;
1729 return rv;
1732 static size_t
1733 a_amv_var_show(char const *name, FILE *fp, struct n_string *msgp){
1734 struct a_amv_var_carrier avc;
1735 char const *quote;
1736 bool_t isset;
1737 size_t i;
1738 NYD2_ENTER;
1740 msgp = n_string_trunc(msgp, 0);
1741 i = 0;
1743 a_amv_var_revlookup(&avc, name);
1744 isset = a_amv_var_lookup(&avc, FAL0);
1746 if(n_poption & n_PO_D_V){
1747 if(avc.avc_map == NULL){
1748 msgp = n_string_push_cp(msgp, "#assembled variable with value");
1749 i = 1;
1750 }else{
1751 struct{
1752 ui16_t flag;
1753 char msg[22];
1754 } const tbase[] = {
1755 {a_AMV_VF_VIRT, "virtual"},
1756 {a_AMV_VF_RDONLY, "read-only"},
1757 {a_AMV_VF_NODEL, "nodelete"},
1758 {a_AMV_VF_NOTEMPTY, "notempty"},
1759 {a_AMV_VF_NOCNTRLS, "no-control-chars"},
1760 {a_AMV_VF_NUM, "number"},
1761 {a_AMV_VF_POSNUM, "positive-number"},
1762 {a_AMV_VF_IMPORT, "import-environ-first\0"},
1763 {a_AMV_VF_ENV, "sync-environ"},
1764 {a_AMV_VF_I3VAL, "initial-value"},
1765 {a_AMV_VF_DEFVAL, "default-value"},
1766 {a_AMV_VF_LINKED, "`environ' link"}
1767 }, *tp;
1769 for(tp = tbase; PTRCMP(tp, <, &tbase[n_NELEM(tbase)]); ++tp)
1770 if(isset ? (avc.avc_var->av_flags & tp->flag)
1771 : (avc.avc_map->avm_flags & tp->flag)){
1772 msgp = n_string_push_c(msgp, (i++ == 0 ? '#' : ','));
1773 msgp = n_string_push_cp(msgp, tp->msg);
1776 if(i > 0)
1777 msgp = n_string_push_cp(msgp, "\n ");
1780 if(!isset || (avc.avc_var->av_flags & a_AMV_VF_RDONLY)){
1781 msgp = n_string_push_c(msgp, *n_ns);
1782 if(!isset){
1783 if(avc.avc_map != NULL && (avc.avc_map->avm_flags & a_AMV_VF_BOOL))
1784 msgp = n_string_push_cp(msgp, "boolean; ");
1785 msgp = n_string_push_cp(msgp, "variable not set: ");
1786 msgp = n_string_push_cp(msgp, n_shexp_quote_cp(name, FAL0));
1787 goto jleave;
1791 n_UNINIT(quote, NULL);
1792 if(!(avc.avc_var->av_flags & a_AMV_VF_BOOL)){
1793 quote = n_shexp_quote_cp(avc.avc_var->av_value, TRU1);
1794 if(strcmp(quote, avc.avc_var->av_value))
1795 msgp = n_string_push_cp(msgp, "wysh ");
1796 }else if(n_poption & n_PO_D_V)
1797 msgp = n_string_push_cp(msgp, "wysh ");
1798 if(avc.avc_var->av_flags & a_AMV_VF_LINKED)
1799 msgp = n_string_push_cp(msgp, "environ ");
1800 msgp = n_string_push_cp(msgp, "set ");
1801 msgp = n_string_push_cp(msgp, name);
1802 if(!(avc.avc_var->av_flags & a_AMV_VF_BOOL)){
1803 msgp = n_string_push_c(msgp, '=');
1804 msgp = n_string_push_cp(msgp, quote);
1805 }else if(n_poption & n_PO_D_V)
1806 msgp = n_string_push_cp(msgp, " #boolean");
1808 jleave:
1809 msgp = n_string_push_c(msgp, '\n');
1810 fputs(n_string_cp(msgp), fp);
1811 NYD2_ENTER;
1812 return (i > 0 ? 2 : 1);
1815 static bool_t
1816 a_amv_var_c_set(char **ap, bool_t issetenv){
1817 char *cp, *cp2, *varbuf, c;
1818 size_t errs;
1819 NYD2_ENTER;
1821 errs = 0;
1822 jouter:
1823 while((cp = *ap++) != NULL){
1824 /* Isolate key */
1825 cp2 = varbuf = salloc(strlen(cp) +1);
1827 for(; (c = *cp) != '=' && c != '\0'; ++cp){
1828 if(cntrlchar(c) || spacechar(c)){
1829 n_err(_("Variable name with control character ignored: %s\n"),
1830 ap[-1]);
1831 ++errs;
1832 goto jouter;
1834 *cp2++ = c;
1836 *cp2 = '\0';
1837 if(c == '\0')
1838 cp = n_UNCONST(n_empty);
1839 else
1840 ++cp;
1842 if(varbuf == cp2){
1843 n_err(_("Empty variable name ignored\n"));
1844 ++errs;
1845 }else{
1846 struct a_amv_var_carrier avc;
1847 bool_t isunset;
1849 if((isunset = (varbuf[0] == 'n' && varbuf[1] == 'o')))
1850 varbuf = &varbuf[2];
1852 a_amv_var_revlookup(&avc, varbuf);
1854 if(isunset)
1855 errs += !a_amv_var_clear(&avc, issetenv);
1856 else
1857 errs += !a_amv_var_set(&avc, cp, issetenv);
1860 NYD2_LEAVE;
1861 return (errs == 0);
1864 FL int
1865 c_define(void *v){
1866 int rv;
1867 char **args;
1868 NYD_ENTER;
1870 rv = 1;
1872 if((args = v)[0] == NULL){
1873 rv = (a_amv_mac_show(a_AMV_MF_NONE) == FAL0);
1874 goto jleave;
1877 if(args[1] == NULL || args[1][0] != '{' || args[1][1] != '\0' ||
1878 args[2] != NULL){
1879 n_err(_("Synopsis: define: <name> {\n"));
1880 goto jleave;
1883 rv = (a_amv_mac_def(args[0], a_AMV_MF_NONE) == FAL0);
1884 jleave:
1885 NYD_LEAVE;
1886 return rv;
1889 FL int
1890 c_undefine(void *v){
1891 int rv;
1892 char **args;
1893 NYD_ENTER;
1895 rv = 0;
1896 args = v;
1898 rv |= !a_amv_mac_undef(*args, a_AMV_MF_NONE);
1899 while(*++args != NULL);
1900 NYD_LEAVE;
1901 return rv;
1904 FL int
1905 c_call(void *v){
1906 int rv;
1907 NYD_ENTER;
1909 rv = a_amv_mac_call(v, FAL0);
1910 NYD_LEAVE;
1911 return rv;
1914 FL int
1915 c_call_if(void *v){
1916 int rv;
1917 NYD_ENTER;
1919 rv = a_amv_mac_call(v, TRU1);
1920 NYD_LEAVE;
1921 return rv;
1924 FL int
1925 c_account(void *v){
1926 struct a_amv_mac_call_args *amcap;
1927 struct a_amv_mac *amp;
1928 char **args;
1929 int rv, i, oqf, nqf;
1930 NYD_ENTER;
1932 rv = 1;
1934 if((args = v)[0] == NULL){
1935 rv = (a_amv_mac_show(a_AMV_MF_ACCOUNT) == FAL0);
1936 goto jleave;
1939 if(args[1] && args[1][0] == '{' && args[1][1] == '\0'){
1940 if(args[2] != NULL){
1941 n_err(_("Synopsis: account: <name> {\n"));
1942 goto jleave;
1944 if(!asccasecmp(args[0], ACCOUNT_NULL)){
1945 n_err(_("`account': cannot use reserved name: %s\n"),
1946 ACCOUNT_NULL);
1947 goto jleave;
1949 rv = (a_amv_mac_def(args[0], a_AMV_MF_ACCOUNT) == FAL0);
1950 goto jleave;
1953 if(n_pstate & n_PS_HOOK_MASK){
1954 n_err(_("`account': can't change account from within a hook\n"));
1955 goto jleave;
1958 save_mbox_for_possible_quitstuff();
1960 amp = NULL;
1961 if(asccasecmp(args[0], ACCOUNT_NULL) != 0 &&
1962 (amp = a_amv_mac_lookup(args[0], NULL, a_AMV_MF_ACCOUNT)) == NULL){
1963 n_err(_("`account': account does not exist: %s\n"), args[0]);
1964 goto jleave;
1967 oqf = savequitflags();
1969 if(a_amv_acc_curr != NULL){
1970 if(a_amv_acc_curr->am_lopts != NULL)
1971 a_amv_lopts_unroll(&a_amv_acc_curr->am_lopts);
1972 /* For accounts this lingers */
1973 --a_amv_acc_curr->am_refcnt;
1974 if(a_amv_acc_curr->am_flags & a_AMV_MF_DELETE)
1975 a_amv_mac_free(a_amv_acc_curr);
1978 a_amv_acc_curr = amp;
1980 if(amp != NULL){
1981 bool_t ok;
1982 assert(amp->am_lopts == NULL);
1983 amcap = n_lofi_alloc(sizeof *amcap);
1984 memset(amcap, 0, sizeof *amcap);
1985 amcap->amca_name = amp->am_name;
1986 amcap->amca_amp = amp;
1987 amcap->amca_unroller = &amp->am_lopts;
1988 amcap->amca_lopts_on = TRU1;
1989 ++amp->am_refcnt; /* We may not run 0 to avoid being deleted! */
1990 ok = a_amv_mac_exec(amcap);
1991 if(!ok){
1992 /* XXX account switch incomplete, unroll? */
1993 n_err(_("`account': failed to switch to account: %s\n"), amp->am_name);
1994 goto jleave;
1998 n_PS_ROOT_BLOCK((amp != NULL ? ok_vset(account, amp->am_name)
1999 : ok_vclear(account)));
2001 if((n_psonce & n_PSO_STARTED) && !(n_pstate & n_PS_HOOK_MASK)){
2002 nqf = savequitflags(); /* TODO obsolete (leave -> void -> new box!) */
2003 restorequitflags(oqf);
2004 if((i = setfile("%", 0)) < 0)
2005 goto jleave;
2006 temporary_folder_hook_check(FAL0);
2007 if(i > 0 && !ok_blook(emptystart))
2008 goto jleave;
2009 announce(ok_blook(bsdcompat) || ok_blook(bsdannounce));
2010 restorequitflags(nqf);
2012 rv = 0;
2013 jleave:
2014 NYD_LEAVE;
2015 return rv;
2018 FL int
2019 c_unaccount(void *v){
2020 int rv;
2021 char **args;
2022 NYD_ENTER;
2024 rv = 0;
2025 args = v;
2027 rv |= !a_amv_mac_undef(*args, a_AMV_MF_ACCOUNT);
2028 while(*++args != NULL);
2029 NYD_LEAVE;
2030 return rv;
2033 FL int
2034 c_localopts(void *v){
2035 char **argv;
2036 int rv;
2037 NYD_ENTER;
2039 rv = 1;
2041 if(a_amv_lopts == NULL){
2042 n_err(_("Cannot use `localopts' in this context "
2043 "(not in `define' or `account', nor special hook)\n"));
2044 goto jleave;
2047 rv = 0;
2049 if(n_pstate & (n_PS_HOOK | n_PS_COMPOSE_MODE)){
2050 if(n_poption & n_PO_D_V)
2051 n_err(_("Cannot turn off `localopts' for compose-mode hooks\n"));
2052 goto jleave;
2055 a_amv_lopts->as_unroll = (boolify(*(argv = v), UIZ_MAX, FAL0) > 0);
2056 jleave:
2057 NYD_LEAVE;
2058 return rv;
2061 FL int
2062 c_shift(void *v){
2063 int rv;
2064 NYD_ENTER;
2066 rv = 1;
2068 if(a_amv_lopts != NULL){
2069 ui16_t i, j;
2070 struct a_amv_mac const *amp;
2071 struct a_amv_mac_call_args *amcap;
2073 amp = (amcap = a_amv_lopts->as_amcap)->amca_amp;
2074 if(amp == NULL || amcap->amca_ps_hook_mask ||
2075 (amp->am_flags & a_AMV_MF_TYPE_MASK) == a_AMV_MF_ACCOUNT)
2076 goto jerr;
2078 v = *(char**)v;
2079 if(v == NULL)
2080 i = 1;
2081 else{
2082 si16_t sib;
2084 if((n_idec_si16_cp(&sib, v, 10, NULL
2085 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
2086 ) != n_IDEC_STATE_CONSUMED || sib < 0){
2087 n_err(_("`shift': invalid argument: %s\n"), v);
2088 goto jleave;
2090 i = (ui16_t)sib;
2093 if(i > (j = amcap->amca_argc)){
2094 n_err(_("`shift': cannot shift %hu of %hu parameters\n"), i, j);
2095 goto jleave;
2096 }else{
2097 rv = 0;
2098 if(j > 0){
2099 j -= i;
2100 amcap->amca_argc = j;
2101 amcap->amca_argv += i;
2104 }else
2105 jerr:
2106 n_err(_("Can only use `shift' in a `call'ed macro\n"));
2107 jleave:
2108 NYD_LEAVE;
2109 return rv;
2112 FL int
2113 c_return(void *v){ /* TODO the exit status should be m_si64! */
2114 int rv;
2115 NYD_ENTER;
2117 if(a_amv_lopts != NULL){
2118 char const **argv;
2120 n_go_input_force_eof();
2121 n_pstate_err_no = n_ERR_NONE;
2122 rv = 0;
2124 if((argv = v)[0] != NULL){
2125 si32_t i;
2127 if((n_idec_si32_cp(&i, argv[0], 10, NULL
2128 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
2129 ) == n_IDEC_STATE_CONSUMED && i >= 0)
2130 rv = (int)i;
2131 else{
2132 n_err(_("`return': return value argument is invalid: %s\n"),
2133 argv[0]);
2134 n_pstate_err_no = n_ERR_INVAL;
2135 rv = 1;
2138 if(argv[1] != NULL){
2139 if((n_idec_si32_cp(&i, argv[1], 10, NULL
2140 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
2141 ) == n_IDEC_STATE_CONSUMED && i >= 0)
2142 n_pstate_err_no = i;
2143 else{
2144 n_err(_("`return': error number argument is invalid: %s\n"),
2145 argv[1]);
2146 n_pstate_err_no = n_ERR_INVAL;
2147 rv = 1;
2151 }else{
2152 n_err(_("Can only use `return' in a macro\n"));
2153 n_pstate_err_no = n_ERR_OPNOTSUPP;
2154 rv = 1;
2156 NYD_LEAVE;
2157 return rv;
2160 FL bool_t
2161 temporary_folder_hook_check(bool_t nmail){ /* TODO temporary, v15: drop */
2162 struct a_amv_mac_call_args *amcap;
2163 struct a_amv_mac *amp;
2164 size_t len;
2165 char const *cp;
2166 char *var;
2167 bool_t rv;
2168 NYD_ENTER;
2170 rv = TRU1;
2171 var = salloc(len = strlen(mailname) + sizeof("folder-hook-") -1 +1);
2173 /* First try the fully resolved path */
2174 snprintf(var, len, "folder-hook-%s", mailname);
2175 if((cp = n_var_vlook(var, FAL0)) != NULL)
2176 goto jmac;
2178 /* If we are under *folder*, try the usual +NAME syntax, too */
2179 if(displayname[0] == '+'){
2180 char *x;
2182 for(x = &mailname[len]; x != mailname; --x)
2183 if(x[-1] == '/'){
2184 snprintf(var, len, "folder-hook-+%s", x);
2185 if((cp = n_var_vlook(var, FAL0)) != NULL)
2186 goto jmac;
2187 break;
2191 /* Plain *folder-hook* is our last try */
2192 if((cp = ok_vlook(folder_hook)) == NULL)
2193 goto jleave;
2195 jmac:
2196 if((amp = a_amv_mac_lookup(cp, NULL, a_AMV_MF_NONE)) == NULL){
2197 n_err(_("Cannot call *folder-hook* for %s: macro does not exist: %s\n"),
2198 n_shexp_quote_cp(displayname, FAL0), cp);
2199 rv = FAL0;
2200 goto jleave;
2203 amcap = n_lofi_alloc(sizeof *amcap);
2204 memset(amcap, 0, sizeof *amcap);
2205 amcap->amca_name = cp;
2206 amcap->amca_amp = amp;
2207 n_pstate &= ~n_PS_HOOK_MASK;
2208 if(nmail){
2209 amcap->amca_unroller = NULL;
2210 n_pstate |= n_PS_HOOK_NEWMAIL;
2211 }else{
2212 amcap->amca_unroller = &a_amv_folder_hook_lopts;
2213 n_pstate |= n_PS_HOOK;
2215 amcap->amca_lopts_on = TRU1;
2216 amcap->amca_ps_hook_mask = TRU1;
2217 rv = a_amv_mac_exec(amcap);
2218 n_pstate &= ~n_PS_HOOK_MASK;
2220 jleave:
2221 NYD_LEAVE;
2222 return rv;
2225 FL void
2226 temporary_folder_hook_unroll(void){ /* XXX intermediate hack */
2227 NYD_ENTER;
2228 if(a_amv_folder_hook_lopts != NULL){
2229 void *save = a_amv_lopts;
2231 a_amv_lopts = NULL;
2232 a_amv_lopts_unroll(&a_amv_folder_hook_lopts);
2233 a_amv_folder_hook_lopts = NULL;
2234 a_amv_lopts = save;
2236 NYD_LEAVE;
2239 FL void
2240 temporary_compose_mode_hook_call(char const *macname,
2241 void (*hook_pre)(void *), void *hook_arg){
2242 /* TODO compose_mode_hook_call() temporary, v15: generalize; see a_GO_SPLICE
2243 * TODO comment in go.c for the right way of doing things! */
2244 static struct a_amv_lostack *cmh_losp;
2245 struct a_amv_mac_call_args *amcap;
2246 struct a_amv_mac *amp;
2247 NYD_ENTER;
2249 amp = NULL;
2251 if(macname == (char*)-1){
2252 a_amv_mac__finalize(cmh_losp);
2253 cmh_losp = NULL;
2254 }else if(macname != NULL &&
2255 (amp = a_amv_mac_lookup(macname, NULL, a_AMV_MF_NONE)) == NULL)
2256 n_err(_("Cannot call *on-compose-**: macro does not exist: %s\n"),
2257 macname);
2258 else{
2259 amcap = n_lofi_alloc(sizeof *amcap);
2260 memset(amcap, 0, sizeof *amcap);
2261 amcap->amca_name = (macname != NULL) ? macname
2262 : "on-compose-splice-shell";
2263 amcap->amca_amp = amp;
2264 amcap->amca_unroller = &a_amv_compose_lopts;
2265 amcap->amca_hook_pre = hook_pre;
2266 amcap->amca_hook_arg = hook_arg;
2267 amcap->amca_lopts_on = TRU1;
2268 amcap->amca_ps_hook_mask = TRU1;
2269 n_pstate &= ~n_PS_HOOK_MASK;
2270 n_pstate |= n_PS_HOOK;
2271 if(macname != NULL)
2272 a_amv_mac_exec(amcap);
2273 else{
2274 cmh_losp = n_lofi_alloc(sizeof *cmh_losp);
2275 cmh_losp->as_global_saved = a_amv_lopts;
2276 cmh_losp->as_up = NULL;
2277 cmh_losp->as_lopts = *(cmh_losp->as_amcap = amcap)->amca_unroller;
2278 cmh_losp->as_unroll = TRU1;
2279 a_amv_lopts = cmh_losp;
2282 NYD_LEAVE;
2285 FL void
2286 temporary_compose_mode_hook_unroll(void){ /* XXX intermediate hack */
2287 NYD_ENTER;
2288 if(a_amv_compose_lopts != NULL){
2289 void *save = a_amv_lopts;
2291 a_amv_lopts = NULL;
2292 a_amv_lopts_unroll(&a_amv_compose_lopts);
2293 a_amv_compose_lopts = NULL;
2294 a_amv_lopts = save;
2296 NYD_LEAVE;
2299 FL bool_t
2300 n_var_is_user_writable(char const *name){
2301 struct a_amv_var_carrier avc;
2302 struct a_amv_var_map const *avmp;
2303 bool_t rv;
2304 NYD_ENTER;
2306 a_amv_var_revlookup(&avc, name);
2307 if((avmp = avc.avc_map) == NULL)
2308 rv = TRU1;
2309 else
2310 rv = ((avmp->avm_flags & (a_AMV_VF_BOOL | a_AMV_VF_RDONLY)) == 0);
2311 NYD_LEAVE;
2312 return rv;
2315 FL char *
2316 n_var_oklook(enum okeys okey){
2317 struct a_amv_var_carrier avc;
2318 char *rv;
2319 struct a_amv_var_map const *avmp;
2320 NYD_ENTER;
2322 avc.avc_map = avmp = &a_amv_var_map[okey];
2323 avc.avc_name = a_amv_var_names + avmp->avm_keyoff;
2324 avc.avc_hash = avmp->avm_hash;
2325 avc.avc_okey = okey;
2327 if(a_amv_var_lookup(&avc, FAL0))
2328 rv = avc.avc_var->av_value;
2329 else
2330 rv = NULL;
2331 NYD_LEAVE;
2332 return rv;
2335 FL bool_t
2336 n_var_okset(enum okeys okey, uintptr_t val){
2337 struct a_amv_var_carrier avc;
2338 bool_t ok;
2339 struct a_amv_var_map const *avmp;
2340 NYD_ENTER;
2342 avc.avc_map = avmp = &a_amv_var_map[okey];
2343 avc.avc_name = a_amv_var_names + avmp->avm_keyoff;
2344 avc.avc_hash = avmp->avm_hash;
2345 avc.avc_okey = okey;
2347 ok = a_amv_var_set(&avc, (val == 0x1 ? n_empty : (char const*)val), FAL0);
2348 NYD_LEAVE;
2349 return ok;
2352 FL bool_t
2353 n_var_okclear(enum okeys okey){
2354 struct a_amv_var_carrier avc;
2355 bool_t rv;
2356 struct a_amv_var_map const *avmp;
2357 NYD_ENTER;
2359 avc.avc_map = avmp = &a_amv_var_map[okey];
2360 avc.avc_name = a_amv_var_names + avmp->avm_keyoff;
2361 avc.avc_hash = avmp->avm_hash;
2362 avc.avc_okey = okey;
2364 rv = a_amv_var_clear(&avc, FAL0);
2365 NYD_LEAVE;
2366 return rv;
2369 FL char const *
2370 n_var_vlook(char const *vokey, bool_t try_getenv){
2371 struct a_amv_var_carrier avc;
2372 char const *rv;
2373 NYD_ENTER;
2375 a_amv_var_revlookup(&avc, vokey);
2377 switch((enum a_amv_var_special_category)avc.avc_special_cat){
2378 default: /* silence CC */
2379 case a_AMV_VSC_NONE:
2380 if(a_amv_var_lookup(&avc, FAL0))
2381 rv = avc.avc_var->av_value;
2382 /* Only check the environment for something that is otherwise unknown */
2383 else if(try_getenv && avc.avc_map == NULL)
2384 rv = getenv(vokey);
2385 else
2386 rv = NULL;
2387 break;
2388 case a_AMV_VSC_GLOBAL:
2389 rv = a_amv_var_vsc_global(&avc);
2390 break;
2391 case a_AMV_VSC_MULTIPLEX:
2392 rv = a_amv_var_vsc_multiplex(&avc);
2393 break;
2394 case a_AMV_VSC_MAC:
2395 case a_AMV_VSC_MAC_ARGV:
2396 rv = a_amv_var_vsc_mac(&avc);
2397 break;
2399 NYD_LEAVE;
2400 return rv;
2403 FL bool_t
2404 n_var_vexplode(void const **cookie){
2405 NYD_ENTER;
2406 /* These may occur only in a macro.. */
2407 *cookie = (a_amv_lopts != NULL) ? a_amv_lopts->as_amcap->amca_argv : NULL;
2408 NYD_LEAVE;
2409 return (*cookie != NULL);
2412 FL bool_t
2413 n_var_vset(char const *vokey, uintptr_t val){
2414 struct a_amv_var_carrier avc;
2415 bool_t ok;
2416 NYD_ENTER;
2418 a_amv_var_revlookup(&avc, vokey);
2420 ok = a_amv_var_set(&avc, (val == 0x1 ? n_empty : (char const*)val), FAL0);
2421 NYD_LEAVE;
2422 return ok;
2425 FL bool_t
2426 n_var_vclear(char const *vokey){
2427 struct a_amv_var_carrier avc;
2428 bool_t ok;
2429 NYD_ENTER;
2431 a_amv_var_revlookup(&avc, vokey);
2433 ok = a_amv_var_clear(&avc, FAL0);
2434 NYD_LEAVE;
2435 return ok;
2438 #ifdef HAVE_SOCKETS
2439 FL char *
2440 n_var_xoklook(enum okeys okey, struct url const *urlp,
2441 enum okey_xlook_mode oxm){
2442 struct a_amv_var_carrier avc;
2443 struct str const *us;
2444 size_t nlen;
2445 char *nbuf, *rv;
2446 struct a_amv_var_map const *avmp;
2447 NYD_ENTER;
2449 assert(oxm & (OXM_PLAIN | OXM_H_P | OXM_U_H_P));
2451 /* For simplicity: allow this case too */
2452 if(!(oxm & (OXM_H_P | OXM_U_H_P))){
2453 nbuf = NULL;
2454 goto jplain;
2457 avc.avc_map = avmp = &a_amv_var_map[okey];
2458 avc.avc_name = a_amv_var_names + avmp->avm_keyoff;
2459 avc.avc_okey = okey;
2461 us = (oxm & OXM_U_H_P) ? &urlp->url_u_h_p : &urlp->url_h_p;
2462 nlen = strlen(avc.avc_name);
2463 nbuf = n_lofi_alloc(nlen + 1 + us->l +1);
2464 memcpy(nbuf, avc.avc_name, nlen);
2465 nbuf[nlen++] = '-';
2467 /* One of .url_u_h_p and .url_h_p we test in here */
2468 memcpy(nbuf + nlen, us->s, us->l +1);
2469 avc.avc_name = a_amv_var_canonify(nbuf);
2470 avc.avc_hash = a_AMV_NAME2HASH(avc.avc_name);
2471 if(a_amv_var_lookup(&avc, FAL0))
2472 goto jvar;
2474 /* The second */
2475 if(oxm & OXM_H_P){
2476 us = &urlp->url_h_p;
2477 memcpy(nbuf + nlen, us->s, us->l +1);
2478 avc.avc_name = a_amv_var_canonify(nbuf);
2479 avc.avc_hash = a_AMV_NAME2HASH(avc.avc_name);
2480 if(a_amv_var_lookup(&avc, FAL0)){
2481 jvar:
2482 rv = avc.avc_var->av_value;
2483 goto jleave;
2487 jplain:
2488 rv = (oxm & OXM_PLAIN) ? n_var_oklook(okey) : NULL;
2489 jleave:
2490 if(nbuf != NULL)
2491 n_lofi_free(nbuf);
2492 NYD_LEAVE;
2493 return rv;
2495 #endif /* HAVE_SOCKETS */
2497 FL int
2498 c_set(void *v){
2499 char **ap;
2500 int err;
2501 NYD_ENTER;
2503 if(*(ap = v) == NULL){
2504 a_amv_var_show_all();
2505 err = 0;
2506 }else
2507 err = !a_amv_var_c_set(ap, FAL0);
2508 NYD_LEAVE;
2509 return err;
2512 FL int
2513 c_unset(void *v){
2514 char **ap;
2515 int err;
2516 NYD_ENTER;
2518 for(err = 0, ap = v; *ap != NULL; ++ap)
2519 err |= !n_var_vclear(*ap);
2520 NYD_LEAVE;
2521 return err;
2524 FL int
2525 c_varshow(void *v){
2526 char **ap;
2527 NYD_ENTER;
2529 if(*(ap = v) == NULL)
2530 v = NULL;
2531 else{
2532 struct n_string msg, *msgp = &msg;
2534 msgp = n_string_creat(msgp);
2535 for(; *ap != NULL; ++ap)
2536 a_amv_var_show(*ap, n_stdout, msgp);
2537 n_string_gut(msgp);
2539 NYD_LEAVE;
2540 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
2543 FL int
2544 c_varedit(void *v){
2545 struct a_amv_var_carrier avc;
2546 FILE *of, *nf;
2547 char *val, **argv;
2548 int err;
2549 sighandler_type sigint;
2550 NYD_ENTER;
2552 sigint = safe_signal(SIGINT, SIG_IGN);
2554 for(err = 0, argv = v; *argv != NULL; ++argv){
2555 memset(&avc, 0, sizeof avc);
2557 a_amv_var_revlookup(&avc, *argv);
2559 if(avc.avc_map != NULL){
2560 if(avc.avc_map->avm_flags & a_AMV_VF_BOOL){
2561 n_err(_("`varedit': cannot edit boolean variable: %s\n"),
2562 avc.avc_name);
2563 continue;
2565 if(avc.avc_map->avm_flags & a_AMV_VF_RDONLY){
2566 n_err(_("`varedit': cannot edit read-only variable: %s\n"),
2567 avc.avc_name);
2568 continue;
2572 a_amv_var_lookup(&avc, FAL0);
2574 if((of = Ftmp(NULL, "varedit", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
2575 NULL){
2576 n_perr(_("`varedit': can't create temporary file, bailing out"), 0);
2577 err = 1;
2578 break;
2579 }else if(avc.avc_var != NULL && *(val = avc.avc_var->av_value) != '\0' &&
2580 sizeof *val != fwrite(val, strlen(val), sizeof *val, of)){
2581 n_perr(_("`varedit' failed to write old value to temporary file"), 0);
2582 Fclose(of);
2583 err = 1;
2584 continue;
2587 fflush_rewind(of);
2588 nf = run_editor(of, (off_t)-1, 'e', FAL0, NULL, NULL, SEND_MBOX, sigint);
2589 Fclose(of);
2591 if(nf != NULL){
2592 int c;
2593 char *base;
2594 off_t l;
2596 l = fsize(nf);
2597 assert(l >= 0);
2598 base = salloc((size_t)l +1);
2600 for(l = 0, val = base; (c = getc(nf)) != EOF; ++val)
2601 if(c == '\n' || c == '\r'){
2602 *val = ' ';
2603 ++l;
2604 }else{
2605 *val = (char)(uc_i)c;
2606 l = 0;
2608 val -= l;
2609 *val = '\0';
2611 if(!a_amv_var_set(&avc, base, FAL0))
2612 err = 1;
2614 Fclose(nf);
2615 }else{
2616 n_err(_("`varedit': can't start $EDITOR, bailing out\n"));
2617 err = 1;
2618 break;
2622 safe_signal(SIGINT, sigint);
2623 NYD_LEAVE;
2624 return err;
2627 FL int
2628 c_environ(void *v){
2629 struct a_amv_var_carrier avc;
2630 int err;
2631 char **ap;
2632 bool_t islnk;
2633 NYD_ENTER;
2635 if((islnk = is_asccaseprefix(*(ap = v), "link")) ||
2636 is_asccaseprefix(*ap, "unlink")){
2637 for(err = 0; *++ap != NULL;){
2638 a_amv_var_revlookup(&avc, *ap);
2640 if(a_amv_var_lookup(&avc, FAL0) && (islnk ||
2641 (avc.avc_var->av_flags & a_AMV_VF_LINKED))){
2642 if(!islnk){
2643 avc.avc_var->av_flags &= ~a_AMV_VF_LINKED;
2644 continue;
2645 }else if(avc.avc_var->av_flags & (a_AMV_VF_ENV | a_AMV_VF_LINKED)){
2646 if(n_poption & n_PO_D_V)
2647 n_err(_("`environ': link: already established: %s\n"), *ap);
2648 continue;
2650 avc.avc_var->av_flags |= a_AMV_VF_LINKED;
2651 if(!(avc.avc_var->av_flags & a_AMV_VF_ENV))
2652 a_amv_var__putenv(&avc, avc.avc_var);
2653 }else if(!islnk){
2654 n_err(_("`environ': unlink: no link established: %s\n"), *ap);
2655 err = 1;
2656 }else{
2657 char const *evp = getenv(*ap);
2659 if(evp != NULL)
2660 err |= !a_amv_var_set(&avc, evp, TRU1);
2661 else{
2662 n_err(_("`environ': link: cannot link to non-existent: %s\n"),
2663 *ap);
2664 err = 1;
2668 }else if(is_asccaseprefix(*ap, "set"))
2669 err = !a_amv_var_c_set(++ap, TRU1);
2670 else if(is_asccaseprefix(*ap, "unset")){
2671 for(err = 0; *++ap != NULL;){
2672 a_amv_var_revlookup(&avc, *ap);
2674 if(!a_amv_var_clear(&avc, TRU1))
2675 err = 1;
2677 }else{
2678 n_err(_("Synopsis: environ: <link|set|unset> <variable>...\n"));
2679 err = 1;
2681 NYD_LEAVE;
2682 return err;
2685 FL int
2686 c_vexpr(void *v){ /* TODO POSIX expr(1) comp. exit status; overly complicat. */
2687 /* This needs to be here because we need to apply MACKY_MACK for
2688 * search+replace regular expression support :( */
2689 size_t i;
2690 enum n_idec_state ids;
2691 si64_t lhv, rhv;
2692 char op, varbuf[64 + 64 / 8 +1];
2693 char const **argv, *varname, *varres, *cp;
2694 enum{
2695 a_ERR = 1<<0,
2696 a_SOFTOVERFLOW = 1<<1,
2697 a_ISNUM = 1<<2,
2698 a_ISDECIMAL = 1<<3, /* Print only decimal result */
2699 a_SATURATED = 1<<4,
2700 a_ICASE = 1<<5,
2701 a_UNSIGNED = 1<<6, /* Unsigned right shift (share bit ok) */
2702 a_TMP = 1<<30
2703 } f;
2704 NYD_ENTER;
2706 f = a_ERR;
2707 argv = v;
2708 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
2709 n_UNINIT(varres, n_empty);
2711 if((cp = argv[0])[0] == '\0')
2712 goto jesubcmd;
2714 if(cp[1] == '\0'){
2715 op = cp[0];
2716 jnumop:
2717 f |= a_ISNUM;
2718 switch(op){
2719 case '=':
2720 case '~':
2721 if(argv[1] == NULL || argv[2] != NULL)
2722 goto jesynopsis;
2724 if(*(cp = *++argv) == '\0')
2725 lhv = 0;
2726 else if(((ids = n_idec_si64_cp(&lhv, cp, 0, NULL)
2727 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
2728 ) != n_IDEC_STATE_CONSUMED){
2729 if(!(ids & n_IDEC_STATE_EOVERFLOW) || !(f & a_SATURATED))
2730 goto jenum_range;
2731 f |= a_SOFTOVERFLOW;
2732 break;
2734 if(op == '~')
2735 lhv = ~lhv;
2736 break;
2738 case '+':
2739 case '-':
2740 case '*':
2741 case '/':
2742 case '%':
2743 case '|':
2744 case '&':
2745 case '^':
2746 case '<':
2747 case '>':
2748 if(argv[1] == NULL || argv[2] == NULL || argv[3] != NULL)
2749 goto jesynopsis;
2750 else{
2751 char xop;
2753 if(*(cp = *++argv) == '\0')
2754 lhv = 0;
2755 else if(((ids = n_idec_si64_cp(&lhv, cp, 0, NULL)
2756 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
2757 ) != n_IDEC_STATE_CONSUMED){
2758 if(!(ids & n_IDEC_STATE_EOVERFLOW) || !(f & a_SATURATED))
2759 goto jenum_range;
2760 f |= a_SOFTOVERFLOW;
2761 break;
2764 if(*(cp = *++argv) == '\0')
2765 rhv = 0;
2766 else if(((ids = n_idec_si64_cp(&rhv, cp, 0, NULL)
2767 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
2768 ) != n_IDEC_STATE_CONSUMED){
2769 if(!(ids & n_IDEC_STATE_EOVERFLOW) || !(f & a_SATURATED))
2770 goto jenum_range;
2771 f |= a_SOFTOVERFLOW;
2772 lhv = rhv;
2773 break;
2776 xop = op;
2777 jnumop_again:
2778 switch(xop){
2779 case '+':
2780 if(rhv < 0){
2781 if(rhv != SI64_MIN){
2782 rhv = -rhv;
2783 xop = '-';
2784 goto jnumop_again;
2785 }else if(lhv < 0)
2786 goto jenum_plusminus;
2787 else if(lhv == 0){
2788 lhv = rhv;
2789 break;
2792 if(SI64_MAX - rhv < lhv)
2793 goto jenum_plusminus;
2794 lhv += rhv;
2795 break;
2796 case '-':
2797 if(rhv < 0){
2798 if(rhv != SI64_MIN){
2799 rhv = -rhv;
2800 xop = '+';
2801 goto jnumop_again;
2802 }else if(lhv > 0)
2803 goto jenum_plusminus;
2804 else if(lhv == 0){
2805 lhv = rhv;
2806 break;
2809 if(SI64_MIN + rhv > lhv){
2810 jenum_plusminus:
2811 if(!(f & a_SATURATED))
2812 goto jenum_overflow;
2813 f |= a_SOFTOVERFLOW;
2814 lhv = (lhv < 0 || xop == '-') ? SI64_MIN : SI64_MAX;
2815 }else
2816 lhv -= rhv;
2817 break;
2818 case '*':
2819 /* Will the result be positive? */
2820 if((lhv < 0) == (rhv < 0)){
2821 if(lhv > 0){
2822 lhv = -lhv;
2823 rhv = -rhv;
2825 if(rhv != 0 && lhv != 0 && SI64_MAX / rhv > lhv){
2826 if(!(f & a_SATURATED))
2827 goto jenum_overflow;
2828 f |= a_SOFTOVERFLOW;
2829 lhv = SI64_MAX;
2830 }else
2831 lhv *= rhv;
2832 }else{
2833 if(rhv > 0){
2834 if(lhv != 0 && SI64_MIN / lhv < rhv){
2835 if(!(f & a_SATURATED))
2836 goto jenum_overflow;
2837 f |= a_SOFTOVERFLOW;
2838 lhv = SI64_MIN;
2839 }else
2840 lhv *= rhv;
2841 }else{
2842 if(rhv != 0 && lhv != 0 && SI64_MIN / rhv < lhv){
2843 if(!(f & a_SATURATED))
2844 goto jenum_overflow;
2845 f |= a_SOFTOVERFLOW;
2846 lhv = SI64_MIN;
2847 }else
2848 lhv *= rhv;
2851 break;
2852 case '/':
2853 if(rhv == 0){
2854 if(!(f & a_SATURATED))
2855 goto jenum_range;
2856 f |= a_SOFTOVERFLOW;
2857 lhv = SI64_MAX;
2858 }else
2859 lhv /= rhv;
2860 break;
2861 case '%':
2862 if(rhv == 0){
2863 if(!(f & a_SATURATED))
2864 goto jenum_range;
2865 f |= a_SOFTOVERFLOW;
2866 lhv = SI64_MAX;
2867 }else
2868 lhv %= rhv;
2869 break;
2870 case '|':
2871 lhv |= rhv;
2872 break;
2873 case '&':
2874 lhv &= rhv;
2875 break;
2876 case '^':
2877 lhv ^= rhv;
2878 break;
2879 case '<':
2880 case '>':
2881 if(!(f & a_TMP))
2882 goto jesubcmd;
2883 if(rhv > 63){ /* xxx 63? */
2884 if(!(f & a_SATURATED))
2885 goto jenum_overflow;
2886 rhv = 63;
2888 if(op == '<')
2889 lhv <<= (ui8_t)rhv;
2890 else if(f & a_UNSIGNED)
2891 lhv = (ui64_t)lhv >> (ui8_t)rhv;
2892 else
2893 lhv >>= (ui8_t)rhv;
2894 break;
2897 break;
2898 default:
2899 goto jesubcmd;
2901 }else if(cp[2] == '\0' && cp[1] == '@'){
2902 f |= a_SATURATED;
2903 op = cp[0];
2904 goto jnumop;
2905 }else if(cp[0] == '<'){
2906 if(*++cp != '<')
2907 goto jesubcmd;
2908 if(*++cp == '@'){
2909 f |= a_SATURATED;
2910 ++cp;
2912 if(*cp != '\0')
2913 goto jesubcmd;
2914 f |= a_TMP;
2915 op = '<';
2916 goto jnumop;
2917 }else if(cp[0] == '>'){
2918 if(*++cp != '>')
2919 goto jesubcmd;
2920 if(*++cp == '>'){
2921 f |= a_UNSIGNED;
2922 ++cp;
2924 if(*cp == '@'){
2925 f |= a_SATURATED;
2926 ++cp;
2928 if(*cp != '\0')
2929 goto jesubcmd;
2930 f |= a_TMP;
2931 op = '>';
2932 goto jnumop;
2933 }else if(is_asccaseprefix(cp, "length")){
2934 f |= a_ISNUM | a_ISDECIMAL;
2935 if(argv[1] == NULL || argv[2] != NULL)
2936 goto jesynopsis;
2938 i = strlen(*++argv);
2939 if(UICMP(64, i, >, SI64_MAX))
2940 goto jestr_overflow;
2941 lhv = (si64_t)i;
2942 }else if(is_asccaseprefix(cp, "hash")){
2943 f |= a_ISNUM | a_ISDECIMAL;
2944 if(argv[1] == NULL || argv[2] != NULL)
2945 goto jesynopsis;
2947 i = n_torek_hash(*++argv);
2948 lhv = (si64_t)i;
2949 }else if(is_asccaseprefix(cp, "file-expand")){
2950 if(argv[1] == NULL || argv[2] != NULL)
2951 goto jesynopsis;
2953 if((varres = fexpand(argv[1], FEXP_NVAR | FEXP_NOPROTO)) == NULL)
2954 goto jestr_nodata;
2955 }else if(is_asccaseprefix(cp, "random")){
2956 if(argv[1] == NULL || argv[2] != NULL)
2957 goto jesynopsis;
2959 if((n_idec_si64_cp(&lhv, argv[1], 0, NULL
2960 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
2961 ) != n_IDEC_STATE_CONSUMED || lhv < 0 || lhv > PATH_MAX)
2962 goto jestr_numrange;
2963 if(lhv == 0)
2964 lhv = NAME_MAX;
2965 varres = n_random_create_cp((size_t)lhv);
2966 }else if(is_asccaseprefix(cp, "find")){
2967 f |= a_ISNUM | a_ISDECIMAL;
2968 if(argv[1] == NULL || argv[2] == NULL || argv[3] != NULL)
2969 goto jesynopsis;
2971 if((cp = strstr(argv[1], argv[2])) == NULL)
2972 goto jestr_nodata;
2973 i = PTR2SIZE(cp - argv[1]);
2974 if(UICMP(64, i, >, SI64_MAX))
2975 goto jestr_overflow;
2976 lhv = (si64_t)i;
2977 }else if(is_asccaseprefix(cp, "ifind")){
2978 f |= a_ISNUM | a_ISDECIMAL;
2979 if(argv[1] == NULL || argv[2] == NULL || argv[3] != NULL)
2980 goto jesynopsis;
2982 if((cp = asccasestr(argv[1], argv[2])) == NULL)
2983 goto jestr_nodata;
2984 i = PTR2SIZE(cp - argv[1]);
2985 if(UICMP(64, i, >, SI64_MAX))
2986 goto jestr_overflow;
2987 lhv = (si64_t)i;
2988 }else if(is_asccaseprefix(cp, "substring")){
2989 if(argv[1] == NULL || argv[2] == NULL)
2990 goto jesynopsis;
2991 if(argv[3] != NULL && argv[4] != NULL)
2992 goto jesynopsis;
2994 i = strlen(varres = *++argv);
2996 if(*(cp = *++argv) == '\0')
2997 lhv = 0;
2998 else if((n_idec_si64_cp(&lhv, cp, 0, NULL
2999 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
3000 ) != n_IDEC_STATE_CONSUMED)
3001 goto jestr_numrange;
3002 if(UICMP(64, i, >=, lhv)){
3003 i -= lhv;
3004 varres += lhv;
3005 }else{
3006 if(n_poption & n_PO_D_V)
3007 n_err(_("`vexpr': substring: offset argument too large: %s\n"),
3008 n_shexp_quote_cp(argv[-1], FAL0));
3009 f |= a_SOFTOVERFLOW;
3012 if(argv[1] != NULL){
3013 if(*(cp = *++argv) == '\0')
3014 lhv = 0;
3015 else if((n_idec_si64_cp(&lhv, cp, 0, NULL
3016 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
3017 ) != n_IDEC_STATE_CONSUMED)
3018 goto jestr_numrange;
3019 if(UICMP(64, i, >=, lhv)){
3020 if(UICMP(64, i, !=, lhv))
3021 varres = savestrbuf(varres, (size_t)lhv);
3022 }else{
3023 if(n_poption & n_PO_D_V)
3024 n_err(_("`vexpr': substring: length argument too large: %s\n"),
3025 n_shexp_quote_cp(argv[-2], FAL0));
3026 f |= a_SOFTOVERFLOW;
3029 #ifdef HAVE_REGEX
3030 }else if(is_asccaseprefix(cp, "regex")) Jregex:{
3031 regmatch_t rema[1 + n_VEXPR_REGEX_MAX];
3032 regex_t re;
3033 int reflrv;
3035 f |= a_ISNUM | a_ISDECIMAL;
3036 if(argv[1] == NULL || argv[2] == NULL ||
3037 (argv[3] != NULL && argv[4] != NULL))
3038 goto jesynopsis;
3040 reflrv = REG_EXTENDED;
3041 if(f & a_ICASE)
3042 reflrv |= REG_ICASE;
3043 if((reflrv = regcomp(&re, argv[2], reflrv))){
3044 n_err(_("`vexpr': invalid regular expression: %s: %s\n"),
3045 n_shexp_quote_cp(argv[2], FAL0), n_regex_err_to_doc(&re, reflrv));
3046 assert(f & a_ERR);
3047 n_pstate_err_no = n_ERR_INVAL;
3048 goto jestr;
3050 reflrv = regexec(&re, argv[1], n_NELEM(rema), rema, 0);
3051 regfree(&re);
3052 if(reflrv == REG_NOMATCH)
3053 goto jestr_nodata;
3055 /* Search only? Else replace, which is a bit */
3056 if(argv[3] == NULL){
3057 if(UICMP(64, rema[0].rm_so, >, SI64_MAX))
3058 goto jestr_overflow;
3059 lhv = (si64_t)rema[0].rm_so;
3060 }else{
3061 /* We need to setup some kind of pseudo macro environment for this */
3062 struct a_amv_lostack los;
3063 struct a_amv_mac_call_args amca;
3064 char const **reargv;
3066 memset(&amca, 0, sizeof amca);
3067 amca.amca_name = savestrbuf(&argv[1][rema[0].rm_so],
3068 rema[0].rm_eo - rema[0].rm_so);
3069 amca.amca_amp = a_AMV_MACKY_MACK;
3070 for(i = 1; rema[i].rm_so != -1 && i < n_NELEM(rema); ++i)
3072 amca.amca_argc = (ui32_t)i - 1;
3073 amca.amca_argv =
3074 reargv = salloc(sizeof(char*) * i);
3075 for(i = 1; rema[i].rm_so != -1 && i < n_NELEM(rema); ++i)
3076 *reargv++ = savestrbuf(&argv[1][rema[i].rm_so],
3077 rema[i].rm_eo - rema[i].rm_so);
3078 *reargv = NULL;
3080 hold_all_sigs(); /* TODO DISLIKE! */
3081 los.as_global_saved = a_amv_lopts;
3082 los.as_amcap = &amca;
3083 los.as_up = los.as_global_saved;
3084 los.as_lopts = NULL;
3085 los.as_unroll = FAL0;
3086 a_amv_lopts = &los;
3088 /* C99 */{
3089 struct str templ;
3090 struct n_string s_b;
3091 enum n_shexp_state shs;
3093 templ.s = n_UNCONST(argv[3]);
3094 templ.l = UIZ_MAX;
3095 shs = n_shexp_parse_token((n_SHEXP_PARSE_LOG |
3096 n_SHEXP_PARSE_IGNORE_EMPTY | n_SHEXP_PARSE_QUOTE_AUTO_FIXED |
3097 n_SHEXP_PARSE_QUOTE_AUTO_DSQ),
3098 n_string_creat_auto(&s_b), &templ, NULL);
3099 if((shs & (n_SHEXP_STATE_ERR_MASK | n_SHEXP_STATE_STOP)
3100 ) == n_SHEXP_STATE_STOP){
3101 varres = n_string_cp(&s_b);
3102 n_string_drop_ownership(&s_b);
3103 }else
3104 varres = NULL;
3107 a_amv_lopts = los.as_global_saved;
3108 rele_all_sigs(); /* TODO DISLIKE! */
3110 if(varres == NULL)
3111 goto jestr_nodata;
3112 f &= ~(a_ISNUM | a_ISDECIMAL);
3114 }else if(is_asccaseprefix(argv[0], "iregex")){
3115 f |= a_ICASE;
3116 goto Jregex;
3117 #endif /* HAVE_REGEX */
3118 }else
3119 goto jesubcmd;
3121 n_pstate_err_no = (f & a_SOFTOVERFLOW) ? n_ERR_OVERFLOW : n_ERR_NONE;
3122 f &= ~a_ERR;
3124 /* Generate the variable value content for numerics.
3125 * Anticipate in our handling below! (Don't do needless work) */
3126 jleave:
3127 if((f & a_ISNUM) && ((f & a_ISDECIMAL) || varname != NULL)){
3128 snprintf(varbuf, sizeof varbuf, "%" PRId64 , lhv);
3129 varres = varbuf;
3132 if(varname == NULL){
3133 /* If there was no error and we are printing a numeric result, print some
3134 * more bases for the fun of it */
3135 if((f & (a_ERR | a_ISNUM | a_ISDECIMAL)) == a_ISNUM){
3136 size_t j;
3138 for(j = 1, i = 0; i < 64; ++i){
3139 varbuf[63 + 64 / 8 -j - i] = (lhv & ((ui64_t)1 << i)) ? '1' : '0';
3140 if((i & 7) == 7 && i != 63){
3141 ++j;
3142 varbuf[63 + 64 / 8 -j - i] = ' ';
3145 varbuf[64 + 64 / 8 -1] = '\0';
3146 if(fprintf(n_stdout,
3147 "%s\n0%" PRIo64 " | 0x%" PRIX64 " | %" PRId64 "\n",
3148 varbuf, lhv, lhv, lhv) < 0){
3149 n_pstate_err_no = n_err_no;
3150 f |= a_ERR;
3152 }else if(fprintf(n_stdout, "%s\n", varres) < 0){
3153 n_pstate_err_no = n_err_no;
3154 f |= a_ERR;
3156 }else if(!n_var_vset(varname, (uintptr_t)varres)){
3157 n_pstate_err_no = n_ERR_NOTSUP;
3158 f |= a_ERR;
3160 NYD_LEAVE;
3161 return (f & a_ERR) ? 1 : 0;
3163 jerr:
3164 f = a_ERR | a_ISNUM;
3165 lhv = -1;
3166 goto jleave;
3167 jesubcmd:
3168 n_err(_("`vexpr': invalid subcommand: %s\n"),
3169 n_shexp_quote_cp(*argv, FAL0));
3170 n_pstate_err_no = n_ERR_INVAL;
3171 goto jerr;
3172 jesynopsis:
3173 n_err(_("Synopsis: vexpr: <operator> <:argument:>\n"));
3174 n_pstate_err_no = n_ERR_INVAL;
3175 goto jerr;
3177 jenum_range:
3178 n_err(_("`vexpr': numeric argument invalid or out of range: %s\n"),
3179 n_shexp_quote_cp(*argv, FAL0));
3180 n_pstate_err_no = n_ERR_RANGE;
3181 goto jerr;
3182 jenum_overflow:
3183 n_err(_("`vexpr': expression overflows datatype: %" PRId64 " %c %" PRId64
3184 "\n"), lhv, op, rhv);
3185 n_pstate_err_no = n_ERR_OVERFLOW;
3186 goto jerr;
3188 jestr_numrange:
3189 n_err(_("`vexpr': numeric argument invalid or out of range: %s\n"),
3190 n_shexp_quote_cp(*argv, FAL0));
3191 n_pstate_err_no = n_ERR_RANGE;
3192 goto jestr;
3193 jestr_overflow:
3194 n_err(_("`vexpr': string length or offset overflows datatype\n"));
3195 n_pstate_err_no = n_ERR_OVERFLOW;
3196 goto jestr;
3197 jestr_nodata:
3198 n_pstate_err_no = n_ERR_NODATA;
3199 /* FALLTHRU */
3200 jestr:
3201 varres = n_empty;
3202 f &= ~a_ISNUM;
3203 f |= a_ERR;
3204 goto jleave;
3207 /* s-it-mode */