cmd_tab.h: allow `source', `source_if' and `shell' during composing?!
[s-mailx.git] / accmacvar.c
bloba539e281a4fbb52541e97054a603c5c97f32f7f4
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 . undefining and overwriting a macro should always be possible:
9 *@ TODO simply place the thing in a delete-later list and replace the
10 *@ TODO accessible entry! (instant delete if on top recursion level.)
11 *@ TODO . Likewise, overwriting an existing should be like delete+create
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 - 2016 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 /* Note: changing the hash function must be reflected in mk-okey-map.pl */
58 #define a_AMV_PRIME HSHSIZE
59 #define a_AMV_NAME2HASH(N) torek_hash(N)
60 #define a_AMV_HASH2PRIME(H) ((H) % a_AMV_PRIME)
62 enum a_amv_mac_flags{
63 a_AMV_MF_NONE = 0,
64 a_AMV_MF_ACC = 1<<0, /* This macro is an `account' */
65 a_AMV_MF_TYPE_MASK = a_AMV_MF_ACC,
66 a_AMV_MF_UNDEF = 1<<1, /* Unlink after lookup */
67 a_AMV_MF_DEL = 1<<7, /* Current `account': deleted while active */
68 a_AMV_MF__MAX = 0xFF
71 /* mk-okey-map.pl ensures that _VIRT implies _RDONLY and _NODEL, and that
72 * _IMPORT implies _ENV; it doesn't verify anything... */
73 enum a_amv_var_flags{
74 a_AMV_VF_NONE = 0,
75 a_AMV_VF_BOOL = 1<<0, /* ok_b_* */
76 a_AMV_VF_VIRT = 1<<1, /* "Stateless" automatic variable */
77 a_AMV_VF_NOLOPTS = 1<<2, /* May not be tracked by `localopts' */
78 a_AMV_VF_RDONLY = 1<<3, /* May not be set by user */
79 a_AMV_VF_NODEL = 1<<4, /* May not be deleted */
80 a_AMV_VF_NOTEMPTY = 1<<5, /* May not be assigned an empty value */
81 a_AMV_VF_NOCNTRLS = 1<<6, /* Value may not contain control characters */
82 a_AMV_VF_NUM = 1<<7, /* Value must be a 32-bit number */
83 a_AMV_VF_POSNUM = 1<<8, /* Value must be positive 32-bit number */
84 a_AMV_VF_VIP = 1<<9, /* Wants _var_check_vips() evaluation */
85 a_AMV_VF_IMPORT = 1<<10, /* Import ONLY from environ (before PS_STARTED) */
86 a_AMV_VF_ENV = 1<<11, /* Update environment on change */
87 a_AMV_VF_I3VAL = 1<<12, /* Has an initial value */
88 a_AMV_VF_DEFVAL = 1<<13, /* Has a default value */
89 a_AMV_VF_LINKED = 1<<14, /* `environ' linked */
90 a_AMV_VF__MASK = (1<<(14+1)) - 1
93 struct a_amv_mac{
94 struct a_amv_mac *am_next;
95 ui32_t am_maxlen; /* of any line in .am_line_dat */
96 ui32_t am_line_cnt; /* of *.am_line_dat (but NULL terminated) */
97 struct a_amv_mac_line **am_line_dat; /* TODO use deque? */
98 struct a_amv_var *am_lopts; /* `localopts' unroll list */
99 ui8_t am_flags; /* enum a_amv_mac_flags */
100 char am_name[n_VFIELD_SIZE(7)]; /* of this macro */
102 n_CTA(a_AMV_MF__MAX <= UI8_MAX, "Enumeration excesses storage datatype");
104 struct a_amv_mac_line{
105 ui32_t aml_len;
106 ui32_t aml_prespc; /* Number of leading SPC, for display purposes */
107 char aml_dat[n_VFIELD_SIZE(0)];
110 struct a_amv_mac_call_args{
111 char const *amca_name;
112 struct a_amv_mac const *amca_amp;
113 struct a_amv_var **amca_unroller;
114 void (*amca_hook_pre)(void *);
115 void *amca_hook_arg;
116 bool_t amca_lopts_on;
117 bool_t amca_ps_hook_mask;
118 ui8_t amca__pad[6];
121 struct a_amv_lostack{
122 struct a_amv_lostack *as_global_saved; /* Saved global XXX due to jump */
123 struct a_amv_mac *as_mac; /* Context (`account' or `define') */
124 struct a_amv_mac_call_args *as_amcap;
125 struct a_amv_lostack *as_up; /* Outer context */
126 struct a_amv_var *as_lopts;
127 bool_t as_unroll; /* Unrolling enabled? */
128 ui8_t avs__pad[7];
131 struct a_amv_var{
132 struct a_amv_var *av_link;
133 char *av_value;
134 #ifdef HAVE_PUTENV
135 char *av_env; /* Actively managed putenv(3) memory */
136 #endif
137 ui16_t av_flags; /* enum a_amv_var_flags */
138 char av_name[n_VFIELD_SIZE(6)];
140 n_CTA(a_AMV_VF__MASK <= UI16_MAX, "Enumeration excesses storage datatype");
142 struct a_amv_var_map{
143 ui32_t avm_hash;
144 ui16_t avm_keyoff;
145 ui16_t avm_flags; /* enum a_amv_var_flags */
147 n_CTA(a_AMV_VF__MASK <= UI16_MAX, "Enumeration excesses storage datatype");
149 struct a_amv_var_virt{
150 ui32_t avv_okey;
151 ui8_t avv__dummy[4];
152 struct a_amv_var const *avv_var;
155 struct a_amv_var_defval{
156 ui32_t avdv_okey;
157 ui8_t avdv__pad[4];
158 char const *avdv_value; /* Only for !BOOL (otherwise plain existence) */
161 struct a_amv_var_carrier{
162 char const *avc_name;
163 ui32_t avc_hash;
164 ui32_t avc_prime;
165 struct a_amv_var *avc_var;
166 struct a_amv_var_map const *avc_map;
167 enum okeys avc_okey;
168 ui8_t avc__pad[4];
171 /* Include the constant mk-okey-map.pl output */
172 #include "version.h"
173 #include "okeys.h"
175 /* True boolean visualization: this string will not be copied to heap memory
176 * in a_amv_var_copy(), but we must avoid confusion with identical user data.
177 * While here, add a special "0" one and speed up *.exit-status* assignments! */
178 static char const a_amv_var_1[] = "1";
179 static char const a_amv_var_0[] = "0";
181 /* The currently active account */
182 static struct a_amv_mac *a_amv_acc_curr;
184 static struct a_amv_mac *a_amv_macs[a_AMV_PRIME]; /* TODO dynamically spaced */
186 /* Unroll list of currently running macro stack */
187 static struct a_amv_lostack *a_amv_lopts;
189 static struct a_amv_var *a_amv_vars[a_AMV_PRIME]; /* TODO dynamically spaced */
191 /* TODO We really deserve localopts support for *folder-hook*s, so hack it in
192 * TODO today via a static lostack, it should be a field in mailbox, once that
193 * TODO is a real multi-instance object */
194 static struct a_amv_var *a_amv_folder_hook_lopts;
196 /* TODO Rather ditto (except for storage -> cmd_ctx), compose hooks */
197 static struct a_amv_var *a_amv_compose_lopts;
199 /* Does cp consist solely of WS and a } */
200 static bool_t a_amv_mac_is_closing_angle(char const *cp);
202 /* Lookup for macros/accounts */
203 static struct a_amv_mac *a_amv_mac_lookup(char const *name,
204 struct a_amv_mac *newamp, enum a_amv_mac_flags amf);
206 /* Execute a macro; amcap must reside in LOFI memory */
207 static bool_t a_amv_mac_exec(struct a_amv_mac_call_args *amcap);
209 static void a_amv_mac__finalize(void *vp);
211 /* User display helpers */
212 static bool_t a_amv_mac_show(enum a_amv_mac_flags amf);
214 /* _def() returns error for faulty definitions and already existing * names,
215 * _undef() returns error if a named thing doesn't exist */
216 static bool_t a_amv_mac_def(char const *name, enum a_amv_mac_flags amf);
217 static bool_t a_amv_mac_undef(char const *name, enum a_amv_mac_flags amf);
219 /* */
220 static void a_amv_mac_free(struct a_amv_mac *amp);
222 /* Update replay-log */
223 static void a_amv_lopts_add(struct a_amv_lostack *alp, char const *name,
224 struct a_amv_var *oavp);
225 static void a_amv_lopts_unroll(struct a_amv_var **avpp);
227 /* Special cased value string allocation */
228 static char *a_amv_var_copy(char const *str);
229 static void a_amv_var_free(char *cp);
231 /* Check for special housekeeping */
232 static bool_t a_amv_var_check_vips(enum okeys okey, bool_t enable, char **val);
234 /* _VF_NOCNTRLS, _VF_NUM / _VF_POSNUM */
235 static bool_t a_amv_var_check_nocntrls(char const *val);
236 static bool_t a_amv_var_check_num(char const *val, bool_t pos);
238 /* If a variable name begins with a lowercase-character and contains at
239 * least one '@', it is converted to all-lowercase. This is necessary
240 * for lookups of names based on email addresses.
241 * Following the standard, only the part following the last '@' should
242 * be lower-cased, but practice has established otherwise here */
243 static char const *a_amv_var_canonify(char const *vn);
245 /* Try to reverse lookup an option name to an enum okeys mapping.
246 * Updates .avc_name and .avc_hash; .avc_map is NULL if none found */
247 static bool_t a_amv_var_revlookup(struct a_amv_var_carrier *avcp,
248 char const *name);
250 /* Lookup a variable from .avc_(map|name|hash), return whether it was found.
251 * Sets .avc_prime; .avc_var is NULL if not found.
252 * Here it is where we care for _I3VAL and _DEFVAL, too.
253 * An _I3VAL will be "consumed" as necessary anyway, but it won't be used to
254 * create a new variable if i3val_nonew is true; if i3val_nonew is TRUM1 then
255 * we set .avc_var to -1 and return true if that was the case, otherwise we'll
256 * return FAL0, then! */
257 static bool_t a_amv_var_lookup(struct a_amv_var_carrier *avcp,
258 bool_t i3val_nonew);
260 /* Set var from .avc_(map|name|hash), return success */
261 static bool_t a_amv_var_set(struct a_amv_var_carrier *avcp, char const *value,
262 bool_t force_env);
264 static bool_t a_amv_var__putenv(struct a_amv_var_carrier *avcp,
265 struct a_amv_var *avp);
267 /* Clear var from .avc_(map|name|hash); sets .avc_var=NULL, return success */
268 static bool_t a_amv_var_clear(struct a_amv_var_carrier *avcp, bool_t force_env);
270 static bool_t a_amv_var__clearenv(char const *name, char *value);
272 /* List all variables */
273 static void a_amv_var_show_all(void);
275 static int a_amv_var__show_cmp(void const *s1, void const *s2);
277 /* Actually do print one, return number of lines written */
278 static size_t a_amv_var_show(char const *name, FILE *fp, struct n_string *msgp);
280 /* Shared c_set() and c_environ():set impl, return success */
281 static bool_t a_amv_var_c_set(char **ap, bool_t issetenv);
283 static bool_t
284 a_amv_mac_is_closing_angle(char const *cp){
285 bool_t rv;
286 NYD2_ENTER;
288 while(spacechar(*cp))
289 ++cp;
291 if((rv = (*cp++ == '}'))){
292 while(spacechar(*cp))
293 ++cp;
294 rv = (*cp == '\0');
296 NYD2_LEAVE;
297 return rv;
300 static struct a_amv_mac *
301 a_amv_mac_lookup(char const *name, struct a_amv_mac *newamp,
302 enum a_amv_mac_flags amf){
303 struct a_amv_mac *amp, **ampp;
304 ui32_t h;
305 enum a_amv_mac_flags save_amf;
306 NYD2_ENTER;
308 save_amf = amf;
309 amf &= a_AMV_MF_TYPE_MASK;
310 h = a_AMV_NAME2HASH(name);
311 h = a_AMV_HASH2PRIME(h);
312 ampp = &a_amv_macs[h];
314 for(amp = *ampp; amp != NULL; ampp = &(*ampp)->am_next, amp = amp->am_next){
315 if((amp->am_flags & a_AMV_MF_TYPE_MASK) == amf &&
316 !strcmp(amp->am_name, name)){
317 if(n_LIKELY((save_amf & a_AMV_MF_UNDEF) == 0))
318 goto jleave;
320 *ampp = amp->am_next;
322 if((amf & a_AMV_MF_ACC) &&
323 account_name != NULL && !strcmp(account_name, name)){
324 amp->am_flags |= a_AMV_MF_DEL;
325 n_err(_("Delayed deletion of active account: %s\n"), name);
326 }else{
327 a_amv_mac_free(amp);
328 amp = (struct a_amv_mac*)-1;
330 goto jleave;
334 if(newamp != NULL){
335 ampp = &a_amv_macs[h];
336 newamp->am_next = *ampp;
337 *ampp = newamp;
338 amp = NULL;
340 jleave:
341 NYD2_LEAVE;
342 return amp;
345 static bool_t
346 a_amv_mac_exec(struct a_amv_mac_call_args *amcap){
347 struct a_amv_lostack *losp;
348 struct a_amv_mac_line **amlp;
349 char **args_base, **args;
350 struct a_amv_mac const *amp;
351 bool_t rv;
352 NYD2_ENTER;
354 amp = amcap->amca_amp;
355 /* XXX Unfortunately we yet need to dup the macro lines! :( */
356 args_base = args = smalloc(sizeof(*args) * (amp->am_line_cnt +1));
357 for(amlp = amp->am_line_dat; *amlp != NULL; ++amlp)
358 *(args++) = sbufdup((*amlp)->aml_dat, (*amlp)->aml_len);
359 *args = NULL;
361 losp = n_lofi_alloc(sizeof *losp);
362 losp->as_global_saved = a_amv_lopts;
363 losp->as_mac = n_UNCONST(amp); /* But not used.. */
364 if((losp->as_amcap = amcap)->amca_unroller == NULL){
365 losp->as_up = losp->as_global_saved;
366 losp->as_lopts = NULL;
367 }else{
368 losp->as_up = NULL;
369 losp->as_lopts = *amcap->amca_unroller;
371 losp->as_unroll = amcap->amca_lopts_on;
373 a_amv_lopts = losp;
374 if(amcap->amca_hook_pre != NULL)
375 (*amcap->amca_hook_pre)(amcap->amca_hook_arg);
376 rv = n_source_macro(n_LEXINPUT_NONE, amp->am_name, args_base,
377 &a_amv_mac__finalize, losp);
378 NYD2_LEAVE;
379 return rv;
382 static void
383 a_amv_mac__finalize(void *vp){
384 struct a_amv_mac_call_args *amcap;
385 struct a_amv_lostack *losp;
386 NYD2_ENTER;
388 losp = vp;
389 a_amv_lopts = losp->as_global_saved;
391 if((amcap = losp->as_amcap)->amca_unroller == NULL){
392 if(losp->as_lopts != NULL)
393 a_amv_lopts_unroll(&losp->as_lopts);
394 }else
395 *amcap->amca_unroller = losp->as_lopts;
397 if(amcap->amca_ps_hook_mask)
398 pstate &= ~PS_HOOK_MASK;
400 n_lofi_free(losp);
401 n_lofi_free(amcap);
402 NYD2_LEAVE;
405 static bool_t
406 a_amv_mac_show(enum a_amv_mac_flags amf){
407 size_t lc, mc, ti, i;
408 char const *typestr;
409 FILE *fp;
410 bool_t rv;
411 NYD2_ENTER;
413 rv = FAL0;
415 if((fp = Ftmp(NULL, "deflist", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
416 NULL){
417 n_perr(_("Can't create temporary file for `define' or `account' listing"),
419 goto jleave;
422 amf &= a_AMV_MF_TYPE_MASK;
423 typestr = (amf & a_AMV_MF_ACC) ? "account" : "define";
425 for(lc = mc = ti = 0; ti < a_AMV_PRIME; ++ti){
426 struct a_amv_mac *amp;
428 for(amp = a_amv_macs[ti]; amp != NULL; amp = amp->am_next){
429 if((amp->am_flags & a_AMV_MF_TYPE_MASK) == amf){
430 struct a_amv_mac_line **amlpp;
432 if(++mc > 1){
433 putc('\n', fp);
434 ++lc;
436 ++lc;
437 fprintf(fp, "%s %s {\n", typestr, amp->am_name);
438 for(amlpp = amp->am_line_dat; *amlpp != NULL; ++lc, ++amlpp){
439 for(i = (*amlpp)->aml_prespc; i > 0; --i)
440 putc(' ', fp);
441 fputs((*amlpp)->aml_dat, fp);
442 putc('\n', fp);
444 fputs("}\n", fp);
445 ++lc;
449 if(mc > 0)
450 page_or_print(fp, lc);
452 rv = (ferror(fp) == 0);
453 Fclose(fp);
454 jleave:
455 NYD2_LEAVE;
456 return rv;
459 static bool_t
460 a_amv_mac_def(char const *name, enum a_amv_mac_flags amf){
461 struct str line;
462 ui32_t line_cnt, maxlen;
463 struct linelist{
464 struct linelist *ll_next;
465 struct a_amv_mac_line *ll_amlp;
466 } *llp, *ll_head, *ll_tail;
467 union {size_t s; int i; ui32_t ui; size_t l;} n;
468 struct a_amv_mac *amp;
469 bool_t rv;
470 NYD2_ENTER;
472 memset(&line, 0, sizeof line);
473 rv = FAL0;
474 amp = NULL;
476 /* Read in the lines which form the macro content */
477 for(ll_tail = ll_head = NULL, line_cnt = maxlen = 0;;){
478 ui32_t leaspc;
479 char *cp;
481 n.i = n_lex_input(n_LEXINPUT_CTX_DEFAULT | n_LEXINPUT_NL_ESC, n_empty,
482 &line.s, &line.l, NULL);
483 if(n.ui == 0)
484 continue;
485 if(n.i < 0){
486 n_err(_("Unterminated %s definition: %s\n"),
487 (amf & a_AMV_MF_ACC ? "account" : "macro"), name);
488 goto jerr;
490 if(a_amv_mac_is_closing_angle(line.s))
491 break;
493 /* Trim WS, remember amount of leading spaces for display purposes */
494 for(cp = line.s, leaspc = 0; n.ui > 0; ++cp, --n.ui)
495 if(*cp == '\t')
496 leaspc = (leaspc + 8u) & ~7u;
497 else if(*cp == ' ')
498 ++leaspc;
499 else
500 break;
501 for(; n.ui > 0 && whitechar(cp[n.ui - 1]); --n.ui)
503 if(n.ui == 0)
504 continue;
506 maxlen = n_MAX(maxlen, n.ui);
507 cp[n.ui++] = '\0';
509 if(n_LIKELY(++line_cnt < UI32_MAX)){
510 struct a_amv_mac_line *amlp;
512 llp = salloc(sizeof *llp);
513 if(ll_head == NULL)
514 ll_head = llp;
515 else
516 ll_tail->ll_next = llp;
517 ll_tail = llp;
518 llp->ll_next = NULL;
519 llp->ll_amlp = amlp = smalloc(sizeof(*amlp) -
520 n_VFIELD_SIZEOF(struct a_amv_mac_line, aml_dat) + n.ui);
521 amlp->aml_len = n.ui -1;
522 amlp->aml_prespc = leaspc;
523 memcpy(amlp->aml_dat, cp, n.ui);
524 }else{
525 n_err(_("Too much content in %s definition: %s\n"),
526 (amf & a_AMV_MF_ACC ? "account" : "macro"), name);
527 goto jerr;
531 /* Create the new macro */
532 n.s = strlen(name) +1;
533 amp = smalloc(sizeof(*amp) - n_VFIELD_SIZEOF(struct a_amv_mac, am_name) +
534 n.s);
535 amp->am_next = NULL;
536 amp->am_maxlen = maxlen;
537 amp->am_line_cnt = line_cnt;
538 amp->am_flags = amf;
539 amp->am_lopts = NULL;
540 memcpy(amp->am_name, name, n.s);
541 /* C99 */{
542 struct a_amv_mac_line **amlpp;
544 amp->am_line_dat = amlpp = smalloc(sizeof(*amlpp) * ++line_cnt);
545 for(llp = ll_head; llp != NULL; llp = llp->ll_next)
546 *amlpp++ = llp->ll_amlp;
547 *amlpp = NULL;
550 /* Finally check whether such a macro already exists, in which case we throw
551 * it all away again. At least we know it would have worked */
552 if(a_amv_mac_lookup(name, amp, amf) != NULL){
553 n_err(_("There is already a %s of name: %s\n"),
554 (amf & a_AMV_MF_ACC ? "account" : "macro"), name);
555 goto jerr;
558 rv = TRU1;
559 jleave:
560 if(line.s != NULL)
561 free(line.s);
562 NYD2_LEAVE;
563 return rv;
565 jerr:
566 for(llp = ll_head; llp != NULL; llp = llp->ll_next)
567 free(llp->ll_amlp);
568 if(amp != NULL){
569 free(amp->am_line_dat);
570 free(amp);
572 goto jleave;
575 static bool_t
576 a_amv_mac_undef(char const *name, enum a_amv_mac_flags amf){
577 struct a_amv_mac *amp;
578 bool_t rv;
579 NYD2_ENTER;
581 rv = TRU1;
583 if(n_LIKELY(name[0] != '*' || name[1] != '\0')){
584 if((amp = a_amv_mac_lookup(name, NULL, amf | a_AMV_MF_UNDEF)) == NULL){
585 n_err(_("%s not defined: %s\n"),
586 (amf & a_AMV_MF_ACC ? "Account" : "Macro"), name);
587 rv = FAL0;
589 }else{
590 struct a_amv_mac **ampp, *lamp;
592 for(ampp = a_amv_macs; PTRCMP(ampp, <, &a_amv_macs[n_NELEM(a_amv_macs)]);
593 ++ampp)
594 for(lamp = NULL, amp = *ampp; amp != NULL;){
595 if((amp->am_flags & a_AMV_MF_TYPE_MASK) == amf){
596 /* xxx Expensive but rare: be simple */
597 a_amv_mac_lookup(amp->am_name, NULL, amf | a_AMV_MF_UNDEF);
598 amp = (lamp == NULL) ? *ampp : lamp->am_next;
599 }else{
600 lamp = amp;
601 amp = amp->am_next;
605 NYD2_LEAVE;
606 return rv;
609 static void
610 a_amv_mac_free(struct a_amv_mac *amp){
611 struct a_amv_mac_line **amlpp;
612 NYD2_ENTER;
614 for(amlpp = amp->am_line_dat; *amlpp != NULL; ++amlpp)
615 free(*amlpp);
616 free(amp->am_line_dat);
617 free(amp);
618 NYD2_LEAVE;
621 static void
622 a_amv_lopts_add(struct a_amv_lostack *alp, char const *name,
623 struct a_amv_var *oavp){
624 struct a_amv_var *avp;
625 size_t nl, vl;
626 NYD2_ENTER;
628 /* Propagate unrolling up the stack, as necessary */
629 assert(alp != NULL);
630 for(;;){
631 if(alp->as_unroll)
632 break;
633 if((alp = alp->as_up) == NULL)
634 goto jleave;
637 /* Check whether this variable is handled yet */
638 for(avp = alp->as_lopts; avp != NULL; avp = avp->av_link)
639 if(!strcmp(avp->av_name, name))
640 goto jleave;
642 nl = strlen(name) +1;
643 vl = (oavp != NULL) ? strlen(oavp->av_value) +1 : 0;
644 avp = smalloc(sizeof(*avp) - n_VFIELD_SIZEOF(struct a_amv_var, av_name) +
645 nl + vl);
646 avp->av_link = alp->as_lopts;
647 alp->as_lopts = avp;
648 memcpy(avp->av_name, name, nl);
649 if(vl == 0){
650 avp->av_value = NULL;
651 avp->av_flags = 0;
652 #ifdef HAVE_PUTENV
653 avp->av_env = NULL;
654 #endif
655 }else{
656 avp->av_value = &avp->av_name[nl];
657 avp->av_flags = oavp->av_flags;
658 memcpy(avp->av_value, oavp->av_value, vl);
659 #ifdef HAVE_PUTENV
660 avp->av_env = (oavp->av_env == NULL) ? NULL : sstrdup(oavp->av_env);
661 #endif
663 jleave:
664 NYD2_LEAVE;
667 static void
668 a_amv_lopts_unroll(struct a_amv_var **avpp){
669 struct a_amv_lostack *save_alp;
670 bool_t reset;
671 struct a_amv_var *x, *avp;
672 NYD2_ENTER;
674 avp = *avpp;
675 *avpp = NULL;
676 reset = !(pstate & PS_ROOT);
678 save_alp = a_amv_lopts;
679 a_amv_lopts = NULL;
680 while(avp != NULL){
681 x = avp;
682 avp = avp->av_link;
683 pstate |= PS_ROOT;
684 vok_vset(x->av_name, x->av_value);
685 if(reset)
686 pstate &= ~PS_ROOT;
687 free(x);
689 a_amv_lopts = save_alp;
690 NYD2_LEAVE;
693 static char *
694 a_amv_var_copy(char const *str){
695 char *news;
696 size_t len;
697 NYD2_ENTER;
699 if(*str == '\0')
700 news = n_UNCONST(n_empty);
701 else if(str[1] == '\0'){
702 if(str[0] == '1')
703 news = n_UNCONST(a_amv_var_1);
704 else if(str[0] == '0')
705 news = n_UNCONST(a_amv_var_0);
706 else
707 goto jheap;
708 }else{
709 jheap:
710 len = strlen(str) +1;
711 news = smalloc(len);
712 memcpy(news, str, len);
714 NYD2_LEAVE;
715 return news;
718 static void
719 a_amv_var_free(char *cp){
720 NYD2_ENTER;
721 if(cp[0] != '\0' && cp != a_amv_var_1 && cp != a_amv_var_0)
722 free(cp);
723 NYD2_LEAVE;
726 static bool_t
727 a_amv_var_check_vips(enum okeys okey, bool_t enable, char **val){
728 int flag;
729 bool_t ok;
730 NYD2_ENTER;
732 ok = TRU1;
733 flag = 0;
735 switch(okey){
736 case ok_b_debug:
737 flag = OPT_DEBUG;
738 break;
739 case ok_v_HOME:
740 /* Invalidate any resolved folder then, too
741 * FALLTHRU */
742 case ok_v_folder:
743 ok = !(pstate & PS_ROOT);
744 pstate |= PS_ROOT;
745 ok_vclear(_folder_resolved);
746 if(ok)
747 pstate &= ~PS_ROOT;
748 ok = TRU1;
749 break;
750 case ok_b_header:
751 flag = OPT_N_FLAG;
752 enable = !enable;
753 break;
754 case ok_b_memdebug:
755 flag = OPT_MEMDEBUG;
756 break;
757 case ok_b_POSIXLY_CORRECT:
758 if(!(pstate & PS_ROOT)){
759 bool_t reset = !(pstate & PS_ROOT);
761 pstate |= PS_ROOT;
762 if(enable)
763 ok_bset(posix);
764 else
765 ok_bclear(posix);
766 if(reset)
767 pstate &= ~PS_ROOT;
769 break;
770 case ok_b_posix:
771 if(!(pstate & PS_ROOT)){
772 bool_t reset = !(pstate & PS_ROOT);
774 pstate |= PS_ROOT;
775 if(enable)
776 ok_bset(POSIXLY_CORRECT);
777 else
778 ok_bclear(POSIXLY_CORRECT);
779 if(reset)
780 pstate &= ~PS_ROOT;
782 break;
783 case ok_b_skipemptybody:
784 flag = OPT_E_FLAG;
785 break;
786 case ok_b_typescript_mode:
787 if(enable){
788 ok_bset(colour_disable);
789 ok_bset(line_editor_disable);
790 if(!(pstate & PS_STARTED))
791 ok_bset(termcap_disable);
793 case ok_v_umask:
794 assert(enable);
795 if(**val != '\0'){
796 ul_i ul;
798 if((ul = strtoul(*val, NULL, 0)) & ~0777u){ /* (is valid _VF_POSNUM) */
799 n_err(_("Invalid *umask* setting: %s\n"), *val);
800 ok = FAL0;
801 }else
802 umask((mode_t)ul);
804 break;
805 case ok_b_verbose:
806 flag = (enable && !(options & OPT_VERB))
807 ? OPT_VERB : OPT_VERB | OPT_VERBVERB;
808 break;
809 default:
810 DBG( n_err("Implementation error: never heard of %u\n", ok); )
811 break;
814 if(flag){
815 if(enable)
816 options |= flag;
817 else
818 options &= ~flag;
820 NYD2_LEAVE;
821 return ok;
824 static bool_t
825 a_amv_var_check_nocntrls(char const *val){
826 char c;
827 NYD2_ENTER;
829 while((c = *val++) != '\0')
830 if(cntrlchar(c))
831 break;
832 NYD2_LEAVE;
833 return (c == '\0');
836 static bool_t
837 a_amv_var_check_num(char const *val, bool_t pos){ /* TODO intmax_t anywhere! */
838 /* TODO The internal/environment variables which are num= or posnum= should
839 * TODO gain special lookup functions, or the return should be void* and
840 * TODO castable to integer; i.e. no more strtoX() should be needed.
841 * TODO I.e., the result of this function should instead be stored.
842 * TODO Use intmax_t IF that is sizeof(void*) only? */
843 bool_t rv;
844 NYD2_ENTER;
846 rv = TRU1;
848 if(*val != '\0'){ /* Would be _VF_NOTEMPTY if not allowed */
849 char *eptr;
850 union {long s; unsigned long u;} i;
852 if(!pos){
853 i.s = strtol(val, &eptr, 0); /* TODO strtoimax() */
855 if(*eptr != '\0' ||
856 ((i.s == LONG_MIN || i.s == LONG_MAX) && errno == ERANGE))
857 rv = FAL0;
858 #if INT_MIN != LONG_MIN
859 else if(i.s < INT_MIN)
860 rv = FAL0;
861 #endif
862 #if INT_MAX != LONG_MAX
863 else if(i.s > INT_MAX)
864 rv = FAL0;
865 #endif
866 }else{
867 i.u = strtoul(val, &eptr, 0); /* TODO strtoumax() */
869 if(*eptr != '\0' || (i.u == ULONG_MAX && errno == ERANGE))
870 rv = FAL0;
871 #if UINT_MAX != ULONG_MAX
872 else if(i.u > UINT_MAX)
873 rv = FAL0;
874 #endif
877 NYD2_LEAVE;
878 return rv;
881 static char const *
882 a_amv_var_canonify(char const *vn){
883 NYD2_ENTER;
884 if(!upperchar(*vn)){
885 char const *vp;
887 for(vp = vn; *vp != '\0' && *vp != '@'; ++vp)
889 vn = (*vp == '@') ? i_strdup(vn) : vn;
891 NYD2_LEAVE;
892 return vn;
895 static bool_t
896 a_amv_var_revlookup(struct a_amv_var_carrier *avcp, char const *name){
897 ui32_t hash, i, j;
898 struct a_amv_var_map const *avmp;
899 NYD2_ENTER;
901 avcp->avc_name = name = a_amv_var_canonify(name);
902 avcp->avc_hash = hash = a_AMV_NAME2HASH(name);
904 for(i = hash % a_AMV_VAR_REV_PRIME, j = 0; j <= a_AMV_VAR_REV_LONGEST; ++j){
905 ui32_t x;
907 if((x = a_amv_var_revmap[i]) == a_AMV_VAR_REV_ILL)
908 break;
910 avmp = &a_amv_var_map[x];
911 if(avmp->avm_hash == hash &&
912 !strcmp(&a_amv_var_names[avmp->avm_keyoff], name)){
913 avcp->avc_map = avmp;
914 avcp->avc_okey = (enum okeys)x;
915 goto jleave;
918 if(++i == a_AMV_VAR_REV_PRIME){
919 #ifdef a_AMV_VAR_REV_WRAPAROUND
920 i = 0;
921 #else
922 break;
923 #endif
926 avcp->avc_map = NULL;
927 avcp = NULL;
928 jleave:
929 NYD2_LEAVE;
930 return (avcp != NULL);
933 static bool_t
934 a_amv_var_lookup(struct a_amv_var_carrier *avcp, bool_t i3val_nonew){
935 size_t i;
936 char const *cp;
937 struct a_amv_var_map const *avmp;
938 struct a_amv_var *avp;
939 NYD2_ENTER;
941 /* C99 */{
942 struct a_amv_var **avpp, *lavp;
944 avpp = &a_amv_vars[avcp->avc_prime = a_AMV_HASH2PRIME(avcp->avc_hash)];
946 for(lavp = NULL, avp = *avpp; avp != NULL; lavp = avp, avp = avp->av_link)
947 if(!strcmp(avp->av_name, avcp->avc_name)){
948 /* Relink as head, hope it "sorts on usage" over time.
949 * _clear() relies on this behaviour */
950 if(lavp != NULL){
951 lavp->av_link = avp->av_link;
952 avp->av_link = *avpp;
953 *avpp = avp;
955 goto jleave;
959 /* If this is not an assembled variable we need to consider some special
960 * initialization cases and eventually create the variable anew */
961 if(n_LIKELY((avmp = avcp->avc_map) != NULL)){
962 /* Does it have an import-from-environment flag? */
963 if(n_UNLIKELY((avmp->avm_flags & (a_AMV_VF_IMPORT | a_AMV_VF_ENV)) != 0)){
964 if(n_LIKELY((cp = getenv(avcp->avc_name)) != NULL)){
965 /* May be better not to use that one, though? */
966 bool_t isempty, isbltin;
968 isempty = (*cp == '\0' &&
969 (avmp->avm_flags & a_AMV_VF_NOTEMPTY) != 0);
970 isbltin = ((avmp->avm_flags & (a_AMV_VF_I3VAL | a_AMV_VF_DEFVAL)
971 ) != 0);
973 if(n_UNLIKELY(isempty)){
974 if(!isbltin)
975 goto jerr;
976 }else if(n_LIKELY(*cp != '\0')){
977 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_NOCNTRLS) &&
978 !a_amv_var_check_nocntrls(cp))){
979 n_err(_("Ignoring environment, control characters "
980 "invalid in variable: %s\n"), avcp->avc_name);
981 goto jerr;
983 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_NUM) &&
984 !a_amv_var_check_num(cp, FAL0))){
985 n_err(_("Environment variable value not a number "
986 "or out of range: %s\n"), avcp->avc_name);
987 goto jerr;
989 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_POSNUM) &&
990 !a_amv_var_check_num(cp, TRU1))){
991 n_err(_("Environment variable value not a number, "
992 "negative or out of range: %s\n"), avcp->avc_name);
993 goto jerr;
996 goto jnewval;
1000 /* A first-time init switch is to be handled now and here */
1001 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_I3VAL) != 0)){
1002 static struct a_amv_var_defval const **arr,
1003 *arr_base[a_AMV_VAR_I3VALS_CNT +1];
1005 if(arr == NULL){
1006 arr = &arr_base[0];
1007 arr[i = a_AMV_VAR_I3VALS_CNT] = NULL;
1008 while(i-- > 0)
1009 arr[i] = &a_amv_var_i3vals[i];
1012 for(i = 0; arr[i] != NULL; ++i)
1013 if(arr[i]->avdv_okey == avcp->avc_okey){
1014 cp = (avmp->avm_flags & a_AMV_VF_BOOL) ? n_empty
1015 : arr[i]->avdv_value;
1016 /* Remove this entry, hope entire block becomes no-op asap */
1018 arr[i] = arr[i + 1];
1019 while(arr[i++] != NULL);
1021 if(!i3val_nonew)
1022 goto jnewval;
1023 if(i3val_nonew == TRUM1)
1024 avp = (struct a_amv_var*)-1;
1025 goto jleave;
1029 /* The virtual variables */
1030 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_VIRT) != 0)){
1031 for(i = 0; i < a_AMV_VAR_VIRTS_CNT; ++i)
1032 if(a_amv_var_virts[i].avv_okey == avcp->avc_okey){
1033 avp = n_UNCONST(a_amv_var_virts[i].avv_var);
1034 goto jleave;
1036 /* Not reached */
1039 /* Place this last because once it is set first the variable will never
1040 * be removed again and thus match in the first block above */
1041 if(n_UNLIKELY(avmp->avm_flags & a_AMV_VF_DEFVAL) != 0){
1042 for(i = 0; i < a_AMV_VAR_DEFVALS_CNT; ++i)
1043 if(a_amv_var_defvals[i].avdv_okey == avcp->avc_okey){
1044 cp = (avmp->avm_flags & a_AMV_VF_BOOL) ? n_empty
1045 : a_amv_var_defvals[i].avdv_value;
1046 goto jnewval;
1051 jerr:
1052 avp = NULL;
1053 jleave:
1054 avcp->avc_var = avp;
1055 NYD2_LEAVE;
1056 return (avp != NULL);
1058 jnewval: /* C99 */{
1059 struct a_amv_var **avpp;
1060 size_t l;
1062 l = strlen(avcp->avc_name) +1;
1063 avcp->avc_var = avp = smalloc(sizeof(*avp) -
1064 n_VFIELD_SIZEOF(struct a_amv_var, av_name) + l);
1065 avp->av_link = *(avpp = &a_amv_vars[avcp->avc_prime]);
1066 *avpp = avp;
1067 memcpy(avp->av_name, avcp->avc_name, l);
1068 avp->av_value = a_amv_var_copy(cp);
1069 #ifdef HAVE_PUTENV
1070 avp->av_env = NULL;
1071 #endif
1072 avp->av_flags = avmp->avm_flags;
1074 if(avp->av_flags & a_AMV_VF_VIP)
1075 a_amv_var_check_vips(avcp->avc_okey, TRU1, &avp->av_value);
1076 if(avp->av_flags & a_AMV_VF_ENV)
1077 a_amv_var__putenv(avcp, avp);
1078 goto jleave;
1082 static bool_t
1083 a_amv_var_set(struct a_amv_var_carrier *avcp, char const *value,
1084 bool_t force_env){
1085 struct a_amv_var *avp;
1086 char *oval;
1087 struct a_amv_var_map const *avmp;
1088 bool_t rv;
1089 NYD2_ENTER;
1091 if(value == NULL){
1092 rv = a_amv_var_clear(avcp, force_env);
1093 goto jleave;
1096 if((avmp = avcp->avc_map) != NULL){
1097 rv = FAL0;
1099 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_RDONLY) != 0 &&
1100 !(pstate & PS_ROOT))){
1101 value = N_("Variable is readonly: %s\n");
1102 goto jeavmp;
1104 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_NOTEMPTY) && *value == '\0')){
1105 value = N_("Variable must not be empty: %s\n");
1106 goto jeavmp;
1108 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_NOCNTRLS) != 0 &&
1109 !a_amv_var_check_nocntrls(value))){
1110 value = N_("Variable forbids control characters: %s\n");
1111 goto jeavmp;
1113 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_NUM) &&
1114 !a_amv_var_check_num(value, FAL0))){
1115 value = N_("Variable value not a number or out of range: %s\n");
1116 goto jeavmp;
1118 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_POSNUM) &&
1119 !a_amv_var_check_num(value, TRU1))){
1120 value = _("Variable value not a number, negative, "
1121 "or out of range: %s\n");
1122 goto jeavmp;
1124 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_IMPORT) != 0 &&
1125 !(pstate & (PS_ROOT | PS_STARTED)))){
1126 value = N_("Variable cannot be set in a resource file: %s\n");
1127 jeavmp:
1128 n_err(V_(value), avcp->avc_name);
1129 goto jleave;
1133 rv = TRU1;
1134 a_amv_var_lookup(avcp, TRU1);
1136 /* Don't care what happens later on, store this in the unroll list */
1137 if(a_amv_lopts != NULL &&
1138 (avmp == NULL || !(avmp->avm_flags & a_AMV_VF_NOLOPTS)))
1139 a_amv_lopts_add(a_amv_lopts, avcp->avc_name, avcp->avc_var);
1141 if((avp = avcp->avc_var) == NULL){
1142 struct a_amv_var **avpp;
1143 size_t l;
1145 l = strlen(avcp->avc_name) +1;
1146 avcp->avc_var = avp = smalloc(sizeof(*avp) -
1147 n_VFIELD_SIZEOF(struct a_amv_var, av_name) + l);
1148 avp->av_link = *(avpp = &a_amv_vars[avcp->avc_prime]);
1149 *avpp = avp;
1150 #ifdef HAVE_PUTENV
1151 avp->av_env = NULL;
1152 #endif
1153 memcpy(avp->av_name, avcp->avc_name, l);
1154 avp->av_flags = (avmp != NULL) ? avmp->avm_flags : 0;
1155 oval = n_UNCONST(n_empty);
1156 }else
1157 oval = avp->av_value;
1159 if(avmp == NULL)
1160 avp->av_value = a_amv_var_copy(value);
1161 else{
1162 /* Via `set' etc. the user may give even boolean options non-boolean
1163 * values, ignore that and force boolean */
1164 if(avp->av_flags & a_AMV_VF_BOOL){
1165 if((options & OPT_D_VV) && *value != '\0')
1166 n_err(_("Ignoring value of boolean variable: %s: %s\n"),
1167 avcp->avc_name, value);
1168 avp->av_value = n_UNCONST(a_amv_var_1);
1169 }else
1170 avp->av_value = a_amv_var_copy(value);
1172 /* Check if update allowed XXX wasteful on error! */
1173 if((avp->av_flags & a_AMV_VF_VIP) &&
1174 !(rv = a_amv_var_check_vips(avcp->avc_okey, TRU1, &avp->av_value))){
1175 char *cp = avp->av_value;
1177 avp->av_value = oval;
1178 oval = cp;
1182 if(force_env && !(avp->av_flags & a_AMV_VF_ENV))
1183 avp->av_flags |= a_AMV_VF_LINKED;
1184 if(avp->av_flags & (a_AMV_VF_ENV | a_AMV_VF_LINKED))
1185 rv = a_amv_var__putenv(avcp, avp);
1187 a_amv_var_free(oval);
1188 jleave:
1189 NYD2_LEAVE;
1190 return rv;
1193 static bool_t
1194 a_amv_var__putenv(struct a_amv_var_carrier *avcp, struct a_amv_var *avp){
1195 #ifndef HAVE_SETENV
1196 char *cp;
1197 #endif
1198 bool_t rv;
1199 NYD2_ENTER;
1201 #ifdef HAVE_SETENV
1202 rv = (setenv(avcp->avc_name, avp->av_value, 1) == 0);
1203 #else
1204 cp = sstrdup(savecatsep(avcp->avc_name, '=', avp->av_value));
1206 if((rv = (putenv(cp) == 0))){
1207 char *ocp;
1209 if((ocp = avp->av_env) != NULL)
1210 free(ocp);
1211 avp->av_env = cp;
1212 }else
1213 free(cp);
1214 #endif
1215 NYD2_LEAVE;
1216 return rv;
1219 static bool_t
1220 a_amv_var_clear(struct a_amv_var_carrier *avcp, bool_t force_env){
1221 struct a_amv_var **avpp, *avp;
1222 struct a_amv_var_map const *avmp;
1223 bool_t rv;
1224 NYD2_ENTER;
1226 rv = FAL0;
1228 if(n_LIKELY((avmp = avcp->avc_map) != NULL)){
1229 if(n_UNLIKELY((avmp->avm_flags & a_AMV_VF_NODEL) != 0 &&
1230 !(pstate & PS_ROOT))){
1231 n_err(_("Variable may not be unset: %s\n"), avcp->avc_name);
1232 goto jleave;
1234 if((avmp->avm_flags & a_AMV_VF_VIP) &&
1235 !a_amv_var_check_vips(avcp->avc_okey, FAL0, NULL))
1236 goto jleave;
1239 rv = TRU1;
1241 if(n_UNLIKELY(!a_amv_var_lookup(avcp, TRUM1))){
1242 if(force_env){
1243 jforce_env:
1244 rv = a_amv_var__clearenv(avcp->avc_name, NULL);
1245 }else if(!(pstate & (PS_ROOT | PS_ROBOT)) && (options & OPT_D_V))
1246 n_err(_("Can't unset undefined variable: %s\n"), avcp->avc_name);
1247 goto jleave;
1248 }else if(avcp->avc_var == (struct a_amv_var*)-1){
1249 avcp->avc_var = NULL;
1250 if(force_env)
1251 goto jforce_env;
1252 goto jleave;
1255 if(a_amv_lopts != NULL &&
1256 (avmp == NULL || !(avmp->avm_flags & a_AMV_VF_NOLOPTS)))
1257 a_amv_lopts_add(a_amv_lopts, avcp->avc_name, avcp->avc_var);
1259 avp = avcp->avc_var;
1260 avcp->avc_var = NULL;
1261 avpp = &a_amv_vars[avcp->avc_prime];
1262 assert(*avpp == avp); /* (always listhead after lookup()) */
1263 *avpp = (*avpp)->av_link;
1265 /* C99 */{
1266 #ifdef HAVE_SETENV
1267 char *envval = NULL;
1268 #else
1269 char *envval = avp->av_env;
1270 #endif
1271 if((avp->av_flags & (a_AMV_VF_ENV | a_AMV_VF_LINKED)) || envval != NULL)
1272 rv = a_amv_var__clearenv(avp->av_name, envval);
1274 a_amv_var_free(avp->av_value);
1275 free(avp);
1277 /* XXX Fun part, extremely simple-minded for now: if this variable has
1278 * XXX a default value, immediately reinstantiate it! */
1279 if(n_UNLIKELY(avmp != NULL && (avmp->avm_flags & a_AMV_VF_DEFVAL) != 0))
1280 a_amv_var_lookup(avcp, TRU1);
1281 jleave:
1282 NYD2_LEAVE;
1283 return rv;
1286 static bool_t
1287 a_amv_var__clearenv(char const *name, char *value){
1288 #ifndef HAVE_SETENV
1289 extern char **environ;
1290 char **ecpp;
1291 #endif
1292 bool_t rv;
1293 NYD2_ENTER;
1294 n_UNUSED(value);
1296 #ifdef HAVE_SETENV
1297 unsetenv(name);
1298 rv = TRU1;
1299 #else
1300 if(value != NULL)
1301 for(ecpp = environ; *ecpp != NULL; ++ecpp)
1302 if(*ecpp == value){
1303 free(value);
1305 ecpp[0] = ecpp[1];
1306 while(*ecpp++ != NULL);
1307 break;
1309 rv = TRU1;
1310 #endif
1311 NYD2_LEAVE;
1312 return rv;
1315 static void
1316 a_amv_var_show_all(void){
1317 struct n_string msg, *msgp;
1318 FILE *fp;
1319 size_t no, i;
1320 struct a_amv_var *avp;
1321 char const **vacp, **cap;
1322 NYD2_ENTER;
1324 if((fp = Ftmp(NULL, "setlist", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL){
1325 n_perr(_("Can't create temporary file for `set' listing"), 0);
1326 goto jleave;
1329 /* We need to instantiate first-time-inits and default values here, so that
1330 * they will be regular members of our _vars[] table */
1331 for(i = a_AMV_VAR_I3VALS_CNT; i-- > 0;)
1332 n_var_oklook(a_amv_var_i3vals[i].avdv_okey);
1333 for(i = a_AMV_VAR_DEFVALS_CNT; i-- > 0;)
1334 n_var_oklook(a_amv_var_defvals[i].avdv_okey);
1336 for(no = i = 0; i < a_AMV_PRIME; ++i)
1337 for(avp = a_amv_vars[i]; avp != NULL; avp = avp->av_link)
1338 ++no;
1339 no += a_AMV_VAR_VIRTS_CNT;
1341 vacp = salloc(no * sizeof(*vacp));
1343 for(cap = vacp, i = 0; i < a_AMV_PRIME; ++i)
1344 for(avp = a_amv_vars[i]; avp != NULL; avp = avp->av_link)
1345 *cap++ = avp->av_name;
1346 for(i = a_AMV_VAR_VIRTS_CNT; i-- > 0;)
1347 *cap++ = a_amv_var_virts[i].avv_var->av_name;
1349 if(no > 1)
1350 qsort(vacp, no, sizeof *vacp, &a_amv_var__show_cmp);
1352 msgp = &msg;
1353 msgp = n_string_reserve(n_string_creat(msgp), 80);
1354 for(i = 0, cap = vacp; no != 0; ++cap, --no)
1355 i += a_amv_var_show(*cap, fp, msgp);
1356 n_string_gut(&msg);
1358 page_or_print(fp, i);
1359 Fclose(fp);
1360 jleave:
1361 NYD2_LEAVE;
1364 static int
1365 a_amv_var__show_cmp(void const *s1, void const *s2){
1366 int rv;
1367 NYD2_ENTER;
1369 rv = strcmp(*(char**)n_UNCONST(s1), *(char**)n_UNCONST(s2));
1370 NYD2_LEAVE;
1371 return rv;
1374 static size_t
1375 a_amv_var_show(char const *name, FILE *fp, struct n_string *msgp){
1376 struct a_amv_var_carrier avc;
1377 char const *quote;
1378 size_t i;
1379 NYD2_ENTER;
1381 msgp = n_string_trunc(msgp, 0);
1382 i = 0;
1384 a_amv_var_revlookup(&avc, name);
1385 if(!a_amv_var_lookup(&avc, FAL0)){
1386 struct str s;
1388 msgp = n_string_assign_cp(msgp, _("No such variable: "));
1389 s.s = n_UNCONST(name);
1390 s.l = UIZ_MAX;
1391 msgp = n_shexp_quote(msgp, &s, FAL0);
1392 goto jleave;
1395 if(options & OPT_D_V){
1396 if(avc.avc_map == NULL){
1397 msgp = n_string_push_c(msgp, '#');
1398 msgp = n_string_push_cp(msgp, "assembled");
1399 i = 1;
1401 /* C99 */{
1402 struct{
1403 ui16_t flags;
1404 char msg[22];
1405 } const tbase[] = {
1406 {a_AMV_VF_VIRT, "virtual"},
1407 {a_AMV_VF_RDONLY, "readonly"},
1408 {a_AMV_VF_NODEL, "nodelete"},
1409 {a_AMV_VF_NOTEMPTY, "notempty"},
1410 {a_AMV_VF_NOCNTRLS, "no-control-chars"},
1411 {a_AMV_VF_NUM, "number"},
1412 {a_AMV_VF_POSNUM, "positive-number"},
1413 {a_AMV_VF_IMPORT, "import-environ-first\0"},
1414 {a_AMV_VF_ENV, "sync-environ"},
1415 {a_AMV_VF_I3VAL, "initial-value"},
1416 {a_AMV_VF_DEFVAL, "default-value"},
1417 {a_AMV_VF_LINKED, "`environ' link"}
1418 }, *tp;
1420 for(tp = tbase; PTRCMP(tp, <, &tbase[n_NELEM(tbase)]); ++tp)
1421 if(avc.avc_var->av_flags & tp->flags){
1422 msgp = n_string_push_c(msgp, (i++ == 0 ? '#' : ','));
1423 msgp = n_string_push_cp(msgp, tp->msg);
1427 if(i > 0)
1428 msgp = n_string_push_cp(msgp, "\n ");
1431 if(avc.avc_var->av_flags & a_AMV_VF_RDONLY)
1432 msgp = n_string_push_cp(msgp, "# ");
1433 n_UNINIT(quote, NULL);
1434 if(!(avc.avc_var->av_flags & a_AMV_VF_BOOL)){
1435 quote = n_shexp_quote_cp(avc.avc_var->av_value, TRU1);
1436 if(strcmp(quote, avc.avc_var->av_value))
1437 msgp = n_string_push_cp(msgp, "wysh ");
1439 if(avc.avc_var->av_flags & a_AMV_VF_LINKED)
1440 msgp = n_string_push_cp(msgp, "environ ");
1441 msgp = n_string_push_cp(msgp, "set ");
1442 msgp = n_string_push_cp(msgp, name);
1443 if(!(avc.avc_var->av_flags & a_AMV_VF_BOOL)){
1444 msgp = n_string_push_c(msgp, '=');
1445 msgp = n_string_push_cp(msgp, quote);
1448 jleave:
1449 msgp = n_string_push_c(msgp, '\n');
1450 fputs(n_string_cp(msgp), fp);
1451 NYD2_ENTER;
1452 return (i > 0 ? 2 : 1);
1455 static bool_t
1456 a_amv_var_c_set(char **ap, bool_t issetenv){
1457 char *cp, *cp2, *varbuf, c;
1458 size_t errs;
1459 NYD2_ENTER;
1461 errs = 0;
1462 jouter:
1463 while((cp = *ap++) != NULL){
1464 /* Isolate key */
1465 cp2 = varbuf = salloc(strlen(cp) +1);
1467 for(; (c = *cp) != '=' && c != '\0'; ++cp){
1468 if(cntrlchar(c) || whitechar(c)){
1469 n_err(_("Variable name with control character ignored: %s\n"),
1470 ap[-1]);
1471 ++errs;
1472 goto jouter;
1474 *cp2++ = c;
1476 *cp2 = '\0';
1477 if(c == '\0')
1478 cp = n_UNCONST(n_empty);
1479 else
1480 ++cp;
1482 if(varbuf == cp2){
1483 n_err(_("Empty variable name ignored\n"));
1484 ++errs;
1485 }else{
1486 struct a_amv_var_carrier avc;
1487 bool_t isunset;
1489 if((isunset = (varbuf[0] == 'n' && varbuf[1] == 'o')))
1490 varbuf = &varbuf[2];
1492 a_amv_var_revlookup(&avc, varbuf);
1494 if(isunset)
1495 errs += !a_amv_var_clear(&avc, issetenv);
1496 else
1497 errs += !a_amv_var_set(&avc, cp, issetenv);
1500 NYD2_LEAVE;
1501 return (errs == 0);
1504 FL int
1505 c_define(void *v){
1506 int rv;
1507 char **args;
1508 NYD_ENTER;
1510 rv = 1;
1512 if((args = v)[0] == NULL){
1513 rv = (a_amv_mac_show(a_AMV_MF_NONE) == FAL0);
1514 goto jleave;
1517 if(args[1] == NULL || args[1][0] != '{' || args[1][1] != '\0' ||
1518 args[2] != NULL){
1519 n_err(_("Synopsis: define: <name> {\n"));
1520 goto jleave;
1523 rv = (a_amv_mac_def(args[0], a_AMV_MF_NONE) == FAL0);
1524 jleave:
1525 NYD_LEAVE;
1526 return rv;
1529 FL int
1530 c_undefine(void *v){
1531 int rv;
1532 char **args;
1533 NYD_ENTER;
1535 rv = 0;
1536 args = v;
1538 rv |= !a_amv_mac_undef(*args, a_AMV_MF_NONE);
1539 while(*++args != NULL);
1540 NYD_LEAVE;
1541 return rv;
1544 FL int
1545 c_call(void *v){
1546 struct a_amv_mac_call_args *amcap;
1547 char const *errs, *name;
1548 struct a_amv_mac *amp;
1549 char **args;
1550 int rv;
1551 NYD_ENTER;
1553 rv = 1;
1555 if((args = v)[0] == NULL || (args[1] != NULL && args[2] != NULL)){
1556 errs = _("Synopsis: call: <%s>\n");
1557 name = "name";
1558 goto jerr;
1561 if((amp = a_amv_mac_lookup(name = *args, NULL, a_AMV_MF_NONE)) == NULL){
1562 errs = _("Undefined macro `call'ed: %s\n");
1563 goto jerr;
1566 amcap = n_lofi_alloc(sizeof *amcap);
1567 memset(amcap, 0, sizeof *amcap);
1568 amcap->amca_name = name;
1569 amcap->amca_amp = amp;
1570 rv = (a_amv_mac_exec(amcap) == FAL0);
1571 jleave:
1572 NYD_LEAVE;
1573 return rv;
1574 jerr:
1575 n_err(errs, name);
1576 goto jleave;
1579 FL bool_t
1580 check_folder_hook(bool_t nmail){ /* TODO temporary, v15: drop */
1581 struct a_amv_mac_call_args *amcap;
1582 struct a_amv_mac *amp;
1583 size_t len;
1584 char *var, *cp;
1585 bool_t rv;
1586 NYD_ENTER;
1588 rv = TRU1;
1589 var = salloc(len = strlen(mailname) + sizeof("folder-hook-") -1 +1);
1591 /* First try the fully resolved path */
1592 snprintf(var, len, "folder-hook-%s", mailname);
1593 if((cp = vok_vlook(var)) != NULL)
1594 goto jmac;
1596 /* If we are under *folder*, try the usual +NAME syntax, too */
1597 if(displayname[0] == '+'){
1598 char *x;
1600 for(x = &mailname[len]; x != mailname; --x)
1601 if(x[-1] == '/'){
1602 snprintf(var, len, "folder-hook-+%s", x);
1603 if((cp = vok_vlook(var)) != NULL)
1604 goto jmac;
1605 break;
1609 /* Plain *folder-hook* is our last try */
1610 if((cp = ok_vlook(folder_hook)) == NULL)
1611 goto jleave;
1613 jmac:
1614 if((amp = a_amv_mac_lookup(cp, NULL, a_AMV_MF_NONE)) == NULL){
1615 n_err(_("Cannot call *folder-hook* for %s: macro does not exist: %s\n"),
1616 n_shexp_quote_cp(displayname, FAL0), cp);
1617 rv = FAL0;
1618 goto jleave;
1621 amcap = n_lofi_alloc(sizeof *amcap);
1622 memset(amcap, 0, sizeof *amcap);
1623 amcap->amca_name = cp;
1624 amcap->amca_amp = amp;
1625 pstate &= ~PS_HOOK_MASK;
1626 if(nmail){
1627 amcap->amca_unroller = NULL;
1628 pstate |= PS_HOOK_NEWMAIL;
1629 }else{
1630 amcap->amca_unroller = &a_amv_folder_hook_lopts;
1631 pstate |= PS_HOOK;
1633 amcap->amca_lopts_on = TRU1;
1634 amcap->amca_ps_hook_mask = TRU1;
1635 rv = a_amv_mac_exec(amcap);
1636 pstate &= ~PS_HOOK_MASK;
1638 jleave:
1639 NYD_LEAVE;
1640 return rv;
1643 FL void
1644 call_compose_mode_hook(char const *macname, /* TODO temporary, v15: drop */
1645 void (*hook_pre)(void *), void *hook_arg){
1646 struct a_amv_mac_call_args *amcap;
1647 struct a_amv_mac *amp;
1648 NYD_ENTER;
1650 if((amp = a_amv_mac_lookup(macname, NULL, a_AMV_MF_NONE)) == NULL)
1651 n_err(_("Cannot call *on-compose-**: macro does not exist: %s\n"),
1652 macname);
1653 else{
1654 amcap = n_lofi_alloc(sizeof *amcap);
1655 memset(amcap, 0, sizeof *amcap);
1656 amcap->amca_name = macname;
1657 amcap->amca_amp = amp;
1658 amcap->amca_unroller = &a_amv_compose_lopts;
1659 amcap->amca_hook_pre = hook_pre;
1660 amcap->amca_hook_arg = hook_arg;
1661 amcap->amca_lopts_on = TRU1;
1662 amcap->amca_ps_hook_mask = TRU1;
1663 pstate &= ~PS_HOOK_MASK;
1664 pstate |= PS_HOOK;
1665 a_amv_mac_exec(amcap);
1667 NYD_LEAVE;
1670 FL int
1671 c_account(void *v){
1672 struct a_amv_mac_call_args *amcap;
1673 struct a_amv_mac *amp;
1674 char **args;
1675 int rv, i, oqf, nqf;
1676 NYD_ENTER;
1678 rv = 1;
1680 if((args = v)[0] == NULL){
1681 rv = (a_amv_mac_show(a_AMV_MF_ACC) == FAL0);
1682 goto jleave;
1685 if(args[1] && args[1][0] == '{' && args[1][1] == '\0'){
1686 if(args[2] != NULL){
1687 n_err(_("Synopsis: account: <name> {\n"));
1688 goto jleave;
1690 if(!asccasecmp(args[0], ACCOUNT_NULL)){
1691 n_err(_("`account': cannot use reserved name: %s\n"),
1692 ACCOUNT_NULL);
1693 goto jleave;
1695 rv = (a_amv_mac_def(args[0], a_AMV_MF_ACC) == FAL0);
1696 goto jleave;
1699 if(pstate & PS_HOOK_MASK){
1700 n_err(_("`account': can't change account from within a hook\n"));
1701 goto jleave;
1704 save_mbox_for_possible_quitstuff();
1706 amp = NULL;
1707 if(asccasecmp(args[0], ACCOUNT_NULL) != 0 &&
1708 (amp = a_amv_mac_lookup(args[0], NULL, a_AMV_MF_ACC)) == NULL) {
1709 n_err(_("`account': account does not exist: %s\n"), args[0]);
1710 goto jleave;
1713 oqf = savequitflags();
1715 if(a_amv_acc_curr != NULL){
1716 if(a_amv_acc_curr->am_lopts != NULL)
1717 a_amv_lopts_unroll(&a_amv_acc_curr->am_lopts);
1718 if(a_amv_acc_curr->am_flags & a_AMV_MF_DEL)
1719 a_amv_mac_free(a_amv_acc_curr);
1722 account_name = (amp != NULL) ? amp->am_name : NULL;
1723 a_amv_acc_curr = amp;
1725 if(amp != NULL){
1726 bool_t ok;
1727 assert(amp->am_lopts == NULL);
1728 amcap = n_lofi_alloc(sizeof *amcap);
1729 memset(amcap, 0, sizeof *amcap);
1730 amcap->amca_name = amp->am_name;
1731 amcap->amca_amp = amp;
1732 amcap->amca_unroller = &amp->am_lopts;
1733 amcap->amca_lopts_on = TRU1;
1734 ok = a_amv_mac_exec(amcap);
1735 if(!ok){
1736 /* XXX account switch incomplete, unroll? */
1737 n_err(_("`account': failed to switch to account: %s\n"), amp->am_name);
1738 goto jleave;
1742 /* C99 */{
1743 bool_t reset = !(pstate & PS_ROOT);
1745 pstate |= PS_ROOT;
1746 if(amp != NULL)
1747 ok_vset(_account_name, amp->am_name);
1748 else
1749 ok_vclear(_account_name);
1750 if(reset)
1751 pstate &= ~PS_ROOT;
1754 if((pstate & (PS_STARTED | PS_HOOK_MASK)) == PS_STARTED){
1755 nqf = savequitflags(); /* TODO obsolete (leave -> void -> new box!) */
1756 restorequitflags(oqf);
1757 if((i = setfile("%", 0)) < 0)
1758 goto jleave;
1759 check_folder_hook(FAL0);
1760 if(i > 0 && !ok_blook(emptystart))
1761 goto jleave;
1762 announce(ok_blook(bsdcompat) || ok_blook(bsdannounce));
1763 restorequitflags(nqf);
1765 rv = 0;
1766 jleave:
1767 NYD_LEAVE;
1768 return rv;
1771 FL int
1772 c_unaccount(void *v){
1773 int rv;
1774 char **args;
1775 NYD_ENTER;
1777 rv = 0;
1778 args = v;
1780 rv |= !a_amv_mac_undef(*args, a_AMV_MF_ACC);
1781 while(*++args != NULL);
1782 NYD_LEAVE;
1783 return rv;
1786 FL int
1787 c_localopts(void *v){
1788 char **argv;
1789 int rv;
1790 NYD_ENTER;
1792 rv = 1;
1794 if(a_amv_lopts == NULL){
1795 n_err(_("Cannot use `localopts' but from within a "
1796 "`define' or `account'\n"));
1797 goto jleave;
1800 a_amv_lopts->as_unroll = (boolify(*(argv = v), UIZ_MAX, FAL0) > 0);
1801 rv = 0;
1802 jleave:
1803 NYD_LEAVE;
1804 return rv;
1807 FL void
1808 temporary_localopts_free(void){ /* XXX intermediate hack */
1809 NYD_ENTER;
1810 if(a_amv_compose_lopts != NULL){
1811 void *save = a_amv_lopts;
1813 a_amv_lopts = NULL;
1814 a_amv_lopts_unroll(&a_amv_compose_lopts);
1815 a_amv_compose_lopts = NULL;
1816 a_amv_lopts = save;
1818 NYD_LEAVE;
1821 FL void
1822 temporary_localopts_folder_hook_unroll(void){ /* XXX intermediate hack */
1823 NYD_ENTER;
1824 if(a_amv_folder_hook_lopts != NULL){
1825 void *save = a_amv_lopts;
1827 a_amv_lopts = NULL;
1828 a_amv_lopts_unroll(&a_amv_folder_hook_lopts);
1829 a_amv_folder_hook_lopts = NULL;
1830 a_amv_lopts = save;
1832 NYD_LEAVE;
1835 FL char *
1836 n_var_oklook(enum okeys okey){
1837 struct a_amv_var_carrier avc;
1838 char *rv;
1839 struct a_amv_var_map const *avmp;
1840 NYD_ENTER;
1842 avc.avc_map = avmp = &a_amv_var_map[okey];
1843 avc.avc_name = a_amv_var_names + avmp->avm_keyoff;
1844 avc.avc_hash = avmp->avm_hash;
1845 avc.avc_okey = okey;
1847 if(a_amv_var_lookup(&avc, FAL0))
1848 rv = avc.avc_var->av_value;
1849 else
1850 rv = NULL;
1851 NYD_LEAVE;
1852 return rv;
1855 FL bool_t
1856 n_var_okset(enum okeys okey, uintptr_t val){
1857 struct a_amv_var_carrier avc;
1858 bool_t ok;
1859 struct a_amv_var_map const *avmp;
1860 NYD_ENTER;
1862 avc.avc_map = avmp = &a_amv_var_map[okey];
1863 avc.avc_name = a_amv_var_names + avmp->avm_keyoff;
1864 avc.avc_hash = avmp->avm_hash;
1865 avc.avc_okey = okey;
1867 ok = a_amv_var_set(&avc, (val == 0x1 ? n_empty : (char const*)val), FAL0);
1868 NYD_LEAVE;
1869 return ok;
1872 FL bool_t
1873 n_var_okclear(enum okeys okey){
1874 struct a_amv_var_carrier avc;
1875 bool_t rv;
1876 struct a_amv_var_map const *avmp;
1877 NYD_ENTER;
1879 avc.avc_map = avmp = &a_amv_var_map[okey];
1880 avc.avc_name = a_amv_var_names + avmp->avm_keyoff;
1881 avc.avc_hash = avmp->avm_hash;
1882 avc.avc_okey = okey;
1884 rv = a_amv_var_clear(&avc, FAL0);
1885 NYD_LEAVE;
1886 return rv;
1889 FL char *
1890 n_var_voklook(char const *vokey){
1891 struct a_amv_var_carrier avc;
1892 char *rv;
1893 NYD_ENTER;
1895 a_amv_var_revlookup(&avc, vokey);
1897 rv = a_amv_var_lookup(&avc, FAL0) ? avc.avc_var->av_value : NULL;
1898 NYD_LEAVE;
1899 return rv;
1902 FL bool_t
1903 n_var_vokset(char const *vokey, uintptr_t val){
1904 struct a_amv_var_carrier avc;
1905 bool_t ok;
1906 NYD_ENTER;
1908 a_amv_var_revlookup(&avc, vokey);
1910 ok = a_amv_var_set(&avc, (val == 0x1 ? n_empty : (char const*)val), FAL0);
1911 NYD_LEAVE;
1912 return ok;
1915 FL bool_t
1916 n_var_vokclear(char const *vokey){
1917 struct a_amv_var_carrier avc;
1918 bool_t ok;
1919 NYD_ENTER;
1921 a_amv_var_revlookup(&avc, vokey);
1923 ok = a_amv_var_clear(&avc, FAL0);
1924 NYD_LEAVE;
1925 return ok;
1928 #ifdef HAVE_SOCKETS
1929 FL char *
1930 n_var_xoklook(enum okeys okey, struct url const *urlp,
1931 enum okey_xlook_mode oxm){
1932 struct a_amv_var_carrier avc;
1933 struct str const *us;
1934 size_t nlen;
1935 char *nbuf, *rv;
1936 struct a_amv_var_map const *avmp;
1937 NYD_ENTER;
1939 assert(oxm & (OXM_PLAIN | OXM_H_P | OXM_U_H_P));
1941 /* For simplicity: allow this case too */
1942 if(!(oxm & (OXM_H_P | OXM_U_H_P))){
1943 nbuf = NULL;
1944 goto jplain;
1947 avc.avc_map = avmp = &a_amv_var_map[okey];
1948 avc.avc_name = a_amv_var_names + avmp->avm_keyoff;
1949 avc.avc_okey = okey;
1951 us = (oxm & OXM_U_H_P) ? &urlp->url_u_h_p : &urlp->url_h_p;
1952 nlen = strlen(avc.avc_name);
1953 nbuf = n_lofi_alloc(nlen + 1 + us->l +1);
1954 memcpy(nbuf, avc.avc_name, nlen);
1955 nbuf[nlen++] = '-';
1957 /* One of .url_u_h_p and .url_h_p we test in here */
1958 memcpy(nbuf + nlen, us->s, us->l +1);
1959 avc.avc_name = a_amv_var_canonify(nbuf);
1960 avc.avc_hash = a_AMV_NAME2HASH(avc.avc_name);
1961 if(a_amv_var_lookup(&avc, FAL0))
1962 goto jvar;
1964 /* The second */
1965 if(oxm & OXM_H_P){
1966 us = &urlp->url_h_p;
1967 memcpy(nbuf + nlen, us->s, us->l +1);
1968 avc.avc_name = a_amv_var_canonify(nbuf);
1969 avc.avc_hash = a_AMV_NAME2HASH(avc.avc_name);
1970 if(a_amv_var_lookup(&avc, FAL0)){
1971 jvar:
1972 rv = avc.avc_var->av_value;
1973 goto jleave;
1977 jplain:
1978 rv = (oxm & OXM_PLAIN) ? n_var_oklook(okey) : NULL;
1979 jleave:
1980 if(nbuf != NULL)
1981 n_lofi_free(nbuf);
1982 NYD_LEAVE;
1983 return rv;
1985 #endif /* HAVE_SOCKETS */
1987 FL int
1988 c_set(void *v){
1989 char **ap;
1990 int err;
1991 NYD_ENTER;
1993 if(*(ap = v) == NULL){
1994 a_amv_var_show_all();
1995 err = 0;
1996 }else
1997 err = !a_amv_var_c_set(ap, FAL0);
1998 NYD_LEAVE;
1999 return err;
2002 FL int
2003 c_unset(void *v){
2004 char **ap;
2005 int err;
2006 NYD_ENTER;
2008 for(err = 0, ap = v; *ap != NULL; ++ap)
2009 err |= !n_var_vokclear(*ap);
2010 NYD_LEAVE;
2011 return err;
2014 FL int
2015 c_varshow(void *v){
2016 char **ap;
2017 NYD_ENTER;
2019 if(*(ap = v) == NULL)
2020 v = NULL;
2021 else{
2022 struct n_string msg, *msgp = &msg;
2024 msgp = n_string_creat(msgp);
2025 for(; *ap != NULL; ++ap)
2026 a_amv_var_show(*ap, stdout, msgp);
2027 n_string_gut(msgp);
2029 NYD_LEAVE;
2030 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
2033 FL int
2034 c_varedit(void *v){
2035 struct a_amv_var_carrier avc;
2036 FILE *of, *nf;
2037 char *val, **argv;
2038 int err;
2039 sighandler_type sigint;
2040 NYD_ENTER;
2042 sigint = safe_signal(SIGINT, SIG_IGN);
2044 for(err = 0, argv = v; *argv != NULL; ++argv){
2045 memset(&avc, 0, sizeof avc);
2047 a_amv_var_revlookup(&avc, *argv);
2049 if(avc.avc_map != NULL){
2050 if(avc.avc_map->avm_flags & a_AMV_VF_BOOL){
2051 n_err(_("`varedit': cannot edit boolean variable: %s\n"),
2052 avc.avc_name);
2053 continue;
2055 if(avc.avc_map->avm_flags & a_AMV_VF_RDONLY){
2056 n_err(_("`varedit': cannot edit readonly variable: %s\n"),
2057 avc.avc_name);
2058 continue;
2062 a_amv_var_lookup(&avc, FAL0);
2064 if((of = Ftmp(NULL, "varedit", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
2065 NULL){
2066 n_perr(_("`varedit': can't create temporary file, bailing out"), 0);
2067 err = 1;
2068 break;
2069 }else if(avc.avc_var != NULL && *(val = avc.avc_var->av_value) != '\0' &&
2070 sizeof *val != fwrite(val, strlen(val), sizeof *val, of)){
2071 n_perr(_("`varedit' failed to write old value to temporary file"), 0);
2072 Fclose(of);
2073 err = 1;
2074 continue;
2077 fflush_rewind(of);
2078 nf = run_editor(of, (off_t)-1, 'e', FAL0, NULL, NULL, SEND_MBOX, sigint);
2079 Fclose(of);
2081 if(nf != NULL){
2082 int c;
2083 char *base;
2084 off_t l;
2086 l = fsize(nf);
2087 assert(l >= 0);
2088 base = salloc((size_t)l +1);
2090 for(l = 0, val = base; (c = getc(nf)) != EOF; ++val)
2091 if(c == '\n' || c == '\r'){
2092 *val = ' ';
2093 ++l;
2094 }else{
2095 *val = (char)(uc_i)c;
2096 l = 0;
2098 val -= l;
2099 *val = '\0';
2101 if(!a_amv_var_set(&avc, base, FAL0))
2102 err = 1;
2104 Fclose(nf);
2105 }else{
2106 n_err(_("`varedit': can't start $EDITOR, bailing out\n"));
2107 err = 1;
2108 break;
2112 safe_signal(SIGINT, sigint);
2113 NYD_LEAVE;
2114 return err;
2117 FL int
2118 c_environ(void *v){
2119 struct a_amv_var_carrier avc;
2120 int err;
2121 char **ap;
2122 bool_t islnk;
2123 NYD_ENTER;
2125 if((islnk = !strcmp(*(ap = v), "link")) || !strcmp(*ap, "unlink")){
2126 for(err = 0; *++ap != NULL;){
2127 a_amv_var_revlookup(&avc, *ap);
2129 if(a_amv_var_lookup(&avc, FAL0) && (islnk ||
2130 (avc.avc_var->av_flags & a_AMV_VF_LINKED))){
2131 if(!islnk){
2132 avc.avc_var->av_flags &= ~a_AMV_VF_LINKED;
2133 continue;
2134 }else if(avc.avc_var->av_flags & (a_AMV_VF_ENV | a_AMV_VF_LINKED)){
2135 if(options & OPT_D_V)
2136 n_err(_("`environ': link: already established: %s\n"), *ap);
2137 continue;
2139 avc.avc_var->av_flags |= a_AMV_VF_LINKED;
2140 if(!(avc.avc_var->av_flags & a_AMV_VF_ENV))
2141 a_amv_var__putenv(&avc, avc.avc_var);
2142 }else if(!islnk){
2143 n_err(_("`environ': unlink: no link established: %s\n"), *ap);
2144 err = 1;
2145 }else{
2146 char const *evp = getenv(*ap);
2148 if(evp != NULL)
2149 err |= !a_amv_var_set(&avc, evp, TRU1);
2150 else{
2151 n_err(_("`environ': link: cannot link to non-existent: %s\n"),
2152 *ap);
2153 err = 1;
2157 }else if(!strcmp(*ap, "set"))
2158 err = !a_amv_var_c_set(++ap, TRU1);
2159 else if(!strcmp(*ap, "unset")){
2160 for(err = 0; *++ap != NULL;){
2161 a_amv_var_revlookup(&avc, *ap);
2163 if(!a_amv_var_clear(&avc, TRU1))
2164 err = 1;
2166 }else{
2167 n_err(_("Synopsis: environ: <link|set|unset> <variable>...\n"));
2168 err = 1;
2170 NYD_LEAVE;
2171 return err;
2174 /* s-it-mode */