-
Notifications
You must be signed in to change notification settings - Fork 30
/
variable.c
3358 lines (3034 loc) · 105 KB
/
variable.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* Yash: yet another shell */
/* variable.c: deals with shell variables and parameters */
/* (C) 2007-2022 magicant */
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
#include "common.h"
#include "variable.h"
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#if HAVE_GETTEXT
# include <libintl.h>
#endif
#include <limits.h>
#include <locale.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <wchar.h>
#include <wctype.h>
#include "builtin.h"
#include "configm.h"
#include "exec.h"
#include "expand.h"
#include "hashtable.h"
#include "input.h"
#include "option.h"
#include "parser.h"
#include "path.h"
#include "plist.h"
#include "sig.h"
#include "strbuf.h"
#include "util.h"
#include "xfnmatch.h"
#include "yash.h"
#if YASH_ENABLE_LINEEDIT
# include "lineedit/complete.h"
# include "lineedit/lineedit.h"
# include "lineedit/terminfo.h"
#endif
static const wchar_t *const path_variables[PA_count] = {
[PA_PATH] = L VAR_PATH,
[PA_CDPATH] = L VAR_CDPATH,
[PA_LOADPATH] = L VAR_YASH_LOADPATH,
};
/* variable environment (= set of variables) */
/* not to be confused with environment variables */
typedef struct environ_T {
struct environ_T *parent; /* parent environment */
struct hashtable_T contents; /* hashtable containing variables */
bool is_temporary; /* for temporary assignment? */
char **paths[PA_count];
} environ_T;
/* `contents' is a hashtable from (wchar_t *) to (variable_T *).
* A variable name may contain any characters except L'\0' and L'=', though
* assignment syntax disallows other characters.
* Variable names starting with L'=' are used for special purposes.
* The positional parameter is treated as an array whose name is L"=".
* Note that the number of positional parameters is offset by 1 against the
* array index.
* An environment whose `is_temporary' is true is used for temporary variables.
* The elements of `paths' are arrays of the pathnames contained in the
* $PATH, $CDPATH and $YASH_LOADPATH variables. They are NULL if the
* corresponding variables are not set. */
#define VAR_positional "="
/* flags for variable attributes */
typedef enum vartype_T {
VF_SCALAR,
VF_ARRAY,
VF_EXPORT = 1 << 2,
VF_READONLY = 1 << 3,
VF_NODELETE = 1 << 4,
} vartype_T;
#define VF_MASK ((1 << 2) - 1)
/* For any variable, the variable type is either VF_SCALAR or VF_ARRAY,
* possibly OR'ed with other flags. */
/* type of variables */
typedef struct variable_T {
vartype_T v_type;
union {
wchar_t *value;
struct {
void **vals;
size_t valc;
} array;
} v_contents;
void (*v_getter)(struct variable_T *var);
} variable_T;
#define v_value v_contents.value
#define v_vals v_contents.array.vals
#define v_valc v_contents.array.valc
/* `v_vals' is a NULL-terminated array of pointers to wide strings.
* `v_valc' is, of course, the number of elements in `v_vals'.
* `v_value', `v_vals' and the elements of `v_vals' are `free'able.
* `v_value' is NULL if the variable is declared but not yet assigned.
* `v_vals' is always non-NULL, but it may contain no elements.
* `v_getter' is the setter function, which is reset to NULL on reassignment.*/
/* type of shell functions (defined later) */
typedef struct function_T function_T;
static void varvaluefree(variable_T *v)
__attribute__((nonnull));
static void varfree(variable_T *v);
static void varkvfree(kvpair_T kv);
static void varkvfree_reexport(kvpair_T kv);
static void init_pwd(void);
static variable_T *search_variable(const wchar_t *name)
__attribute__((pure,nonnull));
static variable_T *search_array_and_check_if_changeable(const wchar_t *name)
__attribute__((pure,nonnull));
static void update_environment(const wchar_t *name)
__attribute__((nonnull));
static void reset_locale(const wchar_t *name)
__attribute__((nonnull));
static void reset_locale_category(const wchar_t *name, int category)
__attribute__((nonnull));
static variable_T *new_global(const wchar_t *name)
__attribute__((nonnull));
static variable_T *new_local(const wchar_t *name)
__attribute__((nonnull));
static variable_T *new_temporary(const wchar_t *name)
__attribute__((nonnull));
static variable_T *new_variable(const wchar_t *name, scope_T scope)
__attribute__((nonnull));
static void xtrace_variable(const wchar_t *name, const wchar_t *value)
__attribute__((nonnull));
static void xtrace_array(const wchar_t *name, void *const *values)
__attribute__((nonnull));
static size_t make_array_of_all_variables(bool global, kvpair_T **resultp)
__attribute__((nonnull));
static void get_all_variables_rec(
hashtable_T *table, environ_T *env, bool global)
__attribute__((nonnull));
static void lineno_getter(variable_T *var)
__attribute__((nonnull));
static void random_getter(variable_T *var)
__attribute__((nonnull));
static unsigned next_random(void);
static void variable_set(const wchar_t *name, variable_T *var)
__attribute__((nonnull(1)));
static char **convert_path_array(void **ary)
__attribute__((malloc,warn_unused_result));
static void add_to_list_no_dup(plist_T *list, char *s)
__attribute__((nonnull(1)));
static void reset_path(path_T name, variable_T *var);
static void funcfree(function_T *f);
static void funckvfree(kvpair_T kv);
static void hash_all_commands_recursively(const command_T *c);
static void hash_all_commands_in_and_or(const and_or_T *ao);
static void hash_all_commands_in_if(const ifcommand_T *ic);
static void hash_all_commands_in_case(const caseitem_T *ci);
static void tryhash_word_as_command(const wordunit_T *w);
/* the current environment */
static environ_T *current_env;
/* the top-level environment (the farthest from the current) */
static environ_T *first_env;
/* whether $RANDOM is functioning as a random number */
static bool random_active;
/* hashtable from function names (wchar_t *) to functions (function_T *). */
static hashtable_T functions;
/* Frees the value of the specified variable (but not the variable itself). */
/* This function does not change the value of `*v'. */
void varvaluefree(variable_T *v)
{
switch (v->v_type & VF_MASK) {
case VF_SCALAR:
free(v->v_value);
break;
case VF_ARRAY:
plfree(v->v_vals, free);
break;
}
}
/* Frees the specified variable. */
void varfree(variable_T *v)
{
if (v != NULL) {
varvaluefree(v);
free(v);
}
}
/* Frees the specified key-value pair of a variable name and a variable. */
void varkvfree(kvpair_T kv)
{
free(kv.key);
varfree(kv.value);
}
/* Calls `variable_set' and `update_environment' for `kv.key' and
* calls `varkvfree'. */
void varkvfree_reexport(kvpair_T kv)
{
if (kv.key == NULL) {
assert(kv.value == NULL);
return;
}
variable_set(kv.key, NULL);
if (((variable_T *) kv.value)->v_type & VF_EXPORT)
update_environment(kv.key);
varkvfree(kv);
}
/* Initializes the top-level environment. */
void init_environment(void)
{
assert(first_env == NULL && current_env == NULL);
first_env = current_env = xmalloc(sizeof *current_env);
current_env->parent = NULL;
current_env->is_temporary = false;
ht_init(¤t_env->contents, hashwcs, htwcscmp);
// for (size_t i = 0; i < PA_count; i++)
// current_env->paths[i] = NULL;
ht_init(&functions, hashwcs, htwcscmp);
/* add all the existing environment variables to the variable environment */
for (char **e = environ; *e != NULL; e++) {
wchar_t *we = malloc_mbstowcs(*e);
if (we == NULL)
continue;
wchar_t *eqp = wcschr(we, L'=');
variable_T *v = xmalloc(sizeof *v);
v->v_type = VF_SCALAR | VF_EXPORT;
v->v_value = (eqp != NULL) ? xwcsdup(&eqp[1]) : NULL;
v->v_getter = NULL;
if (eqp != NULL) {
*eqp = L'\0';
we = xreallocn(we, eqp - we + 1, sizeof *we);
}
varkvfree(ht_set(¤t_env->contents, we, v));
}
/* initialize path according to $PATH etc. */
for (size_t i = 0; i < PA_count; i++)
current_env->paths[i] = decompose_paths(getvar(path_variables[i]));
}
/* Initializes the default variables.
* This function must be called after the shell options have been set. */
void init_variables(void)
{
/* set $IFS */
set_variable(L VAR_IFS, xwcsdup(DEFAULT_IFS), SCOPE_GLOBAL, false);
/* set $LINENO */
{
variable_T *v = new_variable(L VAR_LINENO, SCOPE_GLOBAL);
assert(v != NULL);
v->v_type = VF_SCALAR | (v->v_type & VF_EXPORT);
v->v_value = NULL;
v->v_getter = lineno_getter;
// variable_set(VAR_LINENO, v);
// if (v->v_type & VF_EXPORT)
// update_environment(L VAR_LINENO);
}
/* set $MAILCHECK */
if (getvar(L VAR_MAILCHECK) == NULL)
set_variable(L VAR_MAILCHECK, xwcsdup(L"600"), SCOPE_GLOBAL, false);
/* set $PS1~4 */
{
const wchar_t *ps1 = (geteuid() != 0) ? L"$ " : L"# ";
set_variable(L VAR_PS1, xwcsdup(ps1), SCOPE_GLOBAL, false);
set_variable(L VAR_PS2, xwcsdup(L"> "), SCOPE_GLOBAL, false);
set_variable(L VAR_PS4, xwcsdup(L"+ "), SCOPE_GLOBAL, false);
}
/* set $PWD */
init_pwd();
/* export $OLDPWD */
{
variable_T *v = new_global(L VAR_OLDPWD);
assert(v != NULL);
v->v_type |= VF_EXPORT;
variable_set(L VAR_OLDPWD, v);
}
/* set $PPID */
set_variable(L VAR_PPID, malloc_wprintf(L"%jd", (intmax_t) getppid()),
SCOPE_GLOBAL, false);
/* set $OPTIND */
set_variable(L VAR_OPTIND, xwcsdup(L"1"), SCOPE_GLOBAL, false);
/* set $RANDOM */
if (!posixly_correct) {
variable_T *v = new_variable(L VAR_RANDOM, SCOPE_GLOBAL);
assert(v != NULL);
v->v_type = VF_SCALAR;
v->v_value = NULL;
v->v_getter = random_getter;
random_active = true;
srand((unsigned) time(NULL) ^ (unsigned) shell_pid << 17);
} else {
random_active = false;
}
/* set $YASH_LOADPATH */
if (getvar(L VAR_YASH_LOADPATH) == NULL)
set_variable(L VAR_YASH_LOADPATH, xwcsdup(L DEFAULT_LOADPATH),
SCOPE_GLOBAL, false);
/* set $YASH_VERSION */
set_variable(L VAR_YASH_VERSION, xwcsdup(L PACKAGE_VERSION),
SCOPE_GLOBAL, false);
}
/* Reset the value of $PWD if
* - $PWD is not set, or
* - the value of $PWD isn't an absolute path, or
* - the value of $PWD isn't the actual current directory, or
* - the value of $PWD isn't canonicalized. */
void init_pwd(void)
{
const char *pwd = getenv(VAR_PWD);
if (pwd == NULL || pwd[0] != '/' || !is_same_file(pwd, "."))
goto set;
const wchar_t *wpwd = getvar(L VAR_PWD);
if (wpwd == NULL || !is_normalized_path(wpwd))
goto set;
return;
char *newpwd;
wchar_t *wnewpwd;
set:
newpwd = xgetcwd();
if (newpwd == NULL) {
xerror(errno, Ngt("failed to set $PWD"));
return;
}
wnewpwd = realloc_mbstowcs(newpwd);
if (wnewpwd == NULL) {
xerror(0, Ngt("failed to set $PWD"));
return;
}
set_variable(L VAR_PWD, wnewpwd, SCOPE_GLOBAL, true);
}
/* Searches for a variable with the specified name.
* Returns NULL if none was found. */
variable_T *search_variable(const wchar_t *name)
{
for (environ_T *env = current_env; env != NULL; env = env->parent) {
variable_T *var = ht_get(&env->contents, name).value;
if (var != NULL)
return var;
}
return NULL;
}
/* Searches for an array with the specified name and checks if it is not read-
* only. If unsuccessful, prints an error message and returns NULL. */
variable_T *search_array_and_check_if_changeable(const wchar_t *name)
{
variable_T *array = search_variable(name);
if (array == NULL || (array->v_type & VF_MASK) != VF_ARRAY) {
xerror(0, Ngt("no such array $%ls"), name);
return NULL;
} else if (array->v_type & VF_READONLY) {
xerror(0, Ngt("$%ls is read-only"), name);
return NULL;
}
return array;
}
/* Update the value in `environ' for the variable with the specified name.
* `name' must not contain '='. */
void update_environment(const wchar_t *name)
{
char *mname = malloc_wcstombs(name);
if (mname == NULL)
return;
char *value = get_exported_value(name);
if (value == NULL) {
if (xunsetenv(mname) < 0)
xerror(errno, Ngt("failed to unset environment variable $%s"),
mname);
} else {
if (setenv(mname, value, true) < 0)
xerror(errno, Ngt("failed to set environment variable $%s"), mname);
}
free(mname);
free(value);
}
/* Returns the value of variable `name' that should be exported.
* If the variable is not exported or the variable value cannot be converted to
* a multibyte string, NULL is returned. */
char *get_exported_value(const wchar_t *name)
{
for (environ_T *env = current_env; env != NULL; env = env->parent) {
const variable_T *var = ht_get(&env->contents, name).value;
if (var != NULL && (var->v_type & VF_EXPORT)) {
switch (var->v_type & VF_MASK) {
case VF_SCALAR:
if (var->v_value == NULL)
continue;
return malloc_wcstombs(var->v_value);
case VF_ARRAY:
return realloc_wcstombs(joinwcsarray(var->v_vals, L":"));
default:
assert(false);
}
}
}
return NULL;
}
/* Resets the locate settings for the specified variable.
* If `name' is not any of "LANG", "LC_ALL", etc., does nothing. */
void reset_locale(const wchar_t *name)
{
if (wcscmp(name, L VAR_LANG) == 0) {
goto reset_locale_all;
} else if (wcsncmp(name, L"LC_", 3) == 0) {
/* POSIX forbids resetting LC_CTYPE even if the value of the variable
* is changed, but we do reset LC_CTYPE if the shell is interactive and
* not in the POSIXly-correct mode. */
if (wcscmp(&name[3], &(L VAR_LC_ALL)[3]) == 0) {
reset_locale_all:
reset_locale_category(L VAR_LC_COLLATE, LC_COLLATE);
if (!posixly_correct && is_interactive_now)
reset_locale_category(L VAR_LC_CTYPE, LC_CTYPE);
reset_locale_category(L VAR_LC_MESSAGES, LC_MESSAGES);
reset_locale_category(L VAR_LC_MONETARY, LC_MONETARY);
reset_locale_category(L VAR_LC_NUMERIC, LC_NUMERIC);
reset_locale_category(L VAR_LC_TIME, LC_TIME);
} else if (wcscmp(&name[3], &(L VAR_LC_COLLATE)[3]) == 0) {
reset_locale_category(L VAR_LC_COLLATE, LC_COLLATE);
} else if (wcscmp(&name[3], &(L VAR_LC_CTYPE)[3]) == 0) {
if (!posixly_correct && is_interactive_now)
reset_locale_category(L VAR_LC_CTYPE, LC_CTYPE);
} else if (wcscmp(&name[3], &(L VAR_LC_MESSAGES)[3]) == 0) {
reset_locale_category(L VAR_LC_MESSAGES, LC_MESSAGES);
} else if (wcscmp(&name[3], &(L VAR_LC_MONETARY)[3]) == 0) {
reset_locale_category(L VAR_LC_MONETARY, LC_MONETARY);
} else if (wcscmp(&name[3], &(L VAR_LC_NUMERIC)[3]) == 0) {
reset_locale_category(L VAR_LC_NUMERIC, LC_NUMERIC);
} else if (wcscmp(&name[3], &(L VAR_LC_TIME)[3]) == 0) {
reset_locale_category(L VAR_LC_TIME, LC_TIME);
}
}
}
/* Resets the locale of the specified category.
* `name' must be one of the `LC_*' constants except LC_ALL. */
void reset_locale_category(const wchar_t *name, int category)
{
const wchar_t *locale = getvar(L VAR_LC_ALL);
if (locale == NULL) {
locale = getvar(name);
if (locale == NULL) {
locale = getvar(L VAR_LANG);
if (locale == NULL)
locale = L"";
}
}
char *wlocale = malloc_wcstombs(locale);
if (wlocale != NULL) {
setlocale(category, wlocale);
free(wlocale);
}
}
/* Creates a new scalar variable that has no value.
* If the variable already exists, it is returned without change. So the return
* value may be an array variable or it may be a scalar variable with a value.
* Temporary variables with the `name' are cleared if any. */
variable_T *new_global(const wchar_t *name)
{
variable_T *var;
for (environ_T *env = current_env; env != NULL; env = env->parent) {
var = ht_get(&env->contents, name).value;
if (var != NULL) {
if (env->is_temporary) {
assert(!(var->v_type & VF_NODELETE));
varkvfree_reexport(ht_remove(&env->contents, name));
continue;
}
return var;
}
}
var = xmalloc(sizeof *var);
var->v_type = VF_SCALAR;
var->v_value = NULL;
var->v_getter = NULL;
ht_set(&first_env->contents, xwcsdup(name), var);
return var;
}
/* Creates a new scalar variable that has no value.
* If the variable already exists, it is returned without change. So the return
* value may be an array variable or it may be a scalar variable with a value.
* Temporary variables with the `name' are cleared if any. */
variable_T *new_local(const wchar_t *name)
{
environ_T *env = current_env;
while (env->is_temporary) {
varkvfree_reexport(ht_remove(&env->contents, name));
env = env->parent;
}
variable_T *var = ht_get(&env->contents, name).value;
if (var != NULL)
return var;
var = xmalloc(sizeof *var);
var->v_type = VF_SCALAR;
var->v_value = NULL;
var->v_getter = NULL;
ht_set(&env->contents, xwcsdup(name), var);
return var;
}
/* Creates a new scalar variable that has no value.
* If the variable already exists, it is returned without change. So the return
* value may be an array variable or it may be a scalar variable with a value.
* The current environment must be a temporary environment.
* If there is a read-only non-temporary variable with the specified name, it is
* returned (no new temporary variable is created). */
variable_T *new_temporary(const wchar_t *name)
{
environ_T *env = current_env;
assert(env->is_temporary);
/* check if read-only */
variable_T *var = search_variable(name);
if (var != NULL && (var->v_type & VF_READONLY))
return var;
var = ht_get(&env->contents, name).value;
if (var != NULL)
return var;
var = xmalloc(sizeof *var);
var->v_type = VF_SCALAR;
var->v_value = NULL;
var->v_getter = NULL;
ht_set(&env->contents, xwcsdup(name), var);
return var;
}
/* Creates a new variable with the specified name if there is none.
* If the variable already exists, it is cleared and returned.
*
* On error, an error message is printed to the standard error and NULL is
* returned. Otherwise, the (new) variable is returned.
* `v_type' is the only valid member of the returned variable and all the
* members of the variable (including `v_type') must be initialized by the
* caller. If `v_type' of the return value includes the VF_EXPORT flag, the
* caller must call `update_environment'. */
variable_T *new_variable(const wchar_t *name, scope_T scope)
{
variable_T *var;
switch (scope) {
case SCOPE_GLOBAL: var = new_global(name); break;
case SCOPE_LOCAL: var = new_local(name); break;
case SCOPE_TEMP: var = new_temporary(name); break;
default: assert(false);
}
if (var->v_type & VF_READONLY) {
xerror(0, Ngt("$%ls is read-only"), name);
return NULL;
} else {
varvaluefree(var);
return var;
}
}
/* Creates a scalar variable with the specified name and value.
* `value' must be a `free'able string or NULL. The caller must not modify or
* free `value' hereafter, whether or not this function is successful.
* If `export' is true, the variable is exported (i.e., the VF_EXPORT flag is
* set to the variable), but this function does not reset an existing VF_EXPORT
* flag if `export' is false. The `shopt_allexport' option, if true, supersedes
* `export' unless `name' begins with an '='.
* Returns true iff successful. On error, an error message is printed to the
* standard error. */
bool set_variable(
const wchar_t *name, wchar_t *value, scope_T scope, bool export)
{
if (shopt_allexport && name[0] != '=')
export = true;
variable_T *var = new_variable(name, scope);
if (var == NULL) {
free(value);
return false;
}
var->v_type = VF_SCALAR
| (var->v_type & (VF_EXPORT | VF_NODELETE))
| (export ? VF_EXPORT : 0);
var->v_value = value;
var->v_getter = NULL;
variable_set(name, var);
if (var->v_type & VF_EXPORT)
update_environment(name);
return true;
}
/* Creates an array variable with the specified name and values.
* `values' is a NULL-terminated array of pointers to wide strings. It is used
* as the contents of the array variable hereafter, so you must not modify or
* free the array or its elements whether or not this function succeeds.
* `values' and its elements must be `free'able.
* `count' is the number of elements in `values'. If `count' is zero, the
* number is counted in this function.
* If `export' is true, the variable is exported (i.e., the VF_EXPORT flag is
* set to the variable), but this function does not reset an existing VF_EXPORT
* flag if `export' is false. The `shopt_allexport' option, if true, supersedes
* `export' unless `name' begins with an '='.
* Returns the set array iff successful. On error, an error message is printed
* to the standard error and NULL is returned. */
variable_T *set_array(const wchar_t *name, size_t count, void **values,
scope_T scope, bool export)
{
if (shopt_allexport && name[0] != '=')
export = true;
variable_T *var = new_variable(name, scope);
if (var == NULL) {
plfree(values, free);
return NULL;
}
var->v_type = VF_ARRAY
| (var->v_type & (VF_EXPORT | VF_NODELETE))
| (export ? VF_EXPORT : 0);
var->v_vals = values;
var->v_valc = (count != 0) ? count : plcount(var->v_vals);
var->v_getter = NULL;
variable_set(name, var);
if (var->v_type & VF_EXPORT)
update_environment(name);
return var;
}
/* Changes the value of the specified array element.
* `name' must be the name of an existing array.
* `index' is the index of the element (counted from zero).
* `value' is the new value, which must be a `free'able string. Since `value' is
* used as the contents of the array element, you must not modify or free
* `value' after this function returned (whether successful or not).
* Returns true iff successful. An error message is printed on failure. */
bool set_array_element(const wchar_t *name, size_t index, wchar_t *value)
{
variable_T *array = search_array_and_check_if_changeable(name);
if (array == NULL)
goto fail;
if (array->v_valc <= index)
goto invalid_index;
free(array->v_vals[index]);
array->v_vals[index] = value;
if (array->v_type & VF_EXPORT)
update_environment(name);
return true;
invalid_index:
xerror(0,
Ngt("index %zu is out of range "
"(the actual size of array $%ls is %zu)"),
index + 1, name, array->v_valc);
fail:
free(value);
return false;
}
/* Sets the positional parameters of the current environment.
* The existent parameters are cleared.
* `values' is an NULL-terminated array of pointers to wide strings.
* `values[0]' will be the new $1, `values[1]' $2, and so on.
* When a new non-temporary environment is created, this function must be called
* at least once before the environment is used by the user. */
void set_positional_parameters(void *const *values)
{
set_array(L VAR_positional, 0, pldup(values, copyaswcs),
SCOPE_LOCAL, false);
}
/* Performs the specified assignments.
* If `shopt_xtrace' is true, traces are printed to the standard error.
* If `temp' is true, the variables are assigned in the current environment,
* which must be a temporary environment. Otherwise, they are assigned globally.
* If `export' is true, the variables are exported (i.e., the VF_EXPORT flag is
* set to the variables), but this function does not reset any existing
* VF_EXPORT flag if `export' is false. The `shopt_allexport' option, if true,
* supersedes `export'.
* Returns true iff successful. On error, already-assigned variables are not
* restored to the previous values. */
bool do_assignments(const assign_T *assign, bool temp, bool export)
{
if (temp)
assert(current_env->is_temporary);
scope_T scope = temp ? SCOPE_TEMP : SCOPE_GLOBAL;
while (assign != NULL) {
wchar_t *value;
int count;
void **values;
switch (assign->a_type) {
case A_SCALAR:
value =
expand_single(assign->a_scalar, TT_MULTI, Q_WORD, ES_NONE);
if (value == NULL)
return false;
if (shopt_xtrace)
xtrace_variable(assign->a_name, value);
if (!set_variable(assign->a_name, value, scope, export))
return false;
break;
case A_ARRAY:
if (!expand_line(assign->a_array, &count, &values))
return false;
assert(values != NULL);
if (shopt_xtrace)
xtrace_array(assign->a_name, values);
if (!set_array(assign->a_name, count, values, scope, export))
return false;
break;
}
assign = assign->next;
}
return true;
}
/* Pushes a trace of the specified variable assignment to the xtrace buffer. */
void xtrace_variable(const wchar_t *name, const wchar_t *value)
{
xwcsbuf_T *buf = get_xtrace_buffer();
wb_wccat(buf, L' ');
wb_cat(buf, name);
wb_wccat(buf, L'=');
wb_quote_as_word(buf, value);
}
/* Pushes a trace of the specified array assignment to the xtrace buffer. */
void xtrace_array(const wchar_t *name, void *const *values)
{
xwcsbuf_T *buf = get_xtrace_buffer();
wb_wprintf(buf, L" %ls=(", name);
if (*values != NULL) {
for (;;) {
wb_quote_as_word(buf, *values);
values++;
if (*values == NULL)
break;
wb_wccat(buf, L' ');
}
}
wb_wccat(buf, L')');
}
/* Gets the value of the specified scalar variable.
* Cannot be used for special parameters such as $$ and $@.
* Returns the value of the variable, or NULL if not found.
* The return value must not be modified or `free'ed by the caller and
* is valid until the variable is re-assigned or unset. */
const wchar_t *getvar(const wchar_t *name)
{
variable_T *var = search_variable(name);
if (var != NULL && (var->v_type & VF_MASK) == VF_SCALAR) {
if (var->v_getter) {
var->v_getter(var);
if ((var->v_type & VF_MASK) != VF_SCALAR)
return NULL;
}
return var->v_value;
}
return NULL;
}
/* Returns the value(s) of the specified variable/array as an array.
* The return value's type is `struct get_variable_T'. It has three members:
* `type', `count' and `values'.
* `type' is the type of the result:
* GV_NOTFOUND: no such variable/array
* GV_SCALAR: a normal scalar variable
* GV_ARRAY: an array of zero or more values
* GV_ARRAY_CONCAT: an array whose values should be concatenated by caller
* `values' is an array containing the value(s) of the variable/array.
* A scalar value (GV_SCALAR) is returned as a NULL-terminated array containing
* exactly one wide string. An array (GV_ARRAY*) is returned as a NULL-
* terminated array of pointers to wide strings. If no such variable is found
* (GV_NOTFOUND), `values' is NULL. The caller must free the `values' array and
* its element strings iff `freevalues' is true. If `freevalues' is false, the
* caller must not modify the array or its elements.
* `count' is the number of elements in `values'. */
struct get_variable_T get_variable(const wchar_t *name)
{
struct get_variable_T result;
wchar_t *value;
variable_T *var;
if (name[0] == L'\0') {
goto not_found;
} else if (name[1] == L'\0') {
/* `name' is one-character long: check if it's a special parameter */
switch (name[0]) {
case L'*':
result.type = GV_ARRAY_CONCAT;
goto positional_parameters;
case L'@':
result.type = GV_ARRAY;
positional_parameters:
var = search_variable(L VAR_positional);
assert(var != NULL && (var->v_type & VF_MASK) == VF_ARRAY);
result.count = var->v_valc;
result.values = var->v_vals;
result.freevalues = false;
return result;
case L'#':
var = search_variable(L VAR_positional);
assert(var != NULL && (var->v_type & VF_MASK) == VF_ARRAY);
value = malloc_wprintf(L"%zu", var->v_valc);
goto return_single;
case L'?':
value = malloc_wprintf(L"%d", laststatus);
goto return_single;
case L'-':
value = get_hyphen_parameter();
goto return_single;
case L'$':
value = malloc_wprintf(L"%jd", (intmax_t) shell_pid);
goto return_single;
case L'!':
value = malloc_wprintf(L"%jd", (intmax_t) lastasyncpid);
goto return_single;
case L'0':
value = xwcsdup(command_name);
goto return_single;
}
}
if (iswdigit(name[0])) {
/* `name' starts with a digit: a positional parameter */
wchar_t *nameend;
errno = 0;
uintmax_t v = wcstoumax(name, &nameend, 10);
if (errno != 0 || *nameend != L'\0')
goto not_found; /* not a number or overflow */
var = search_variable(L VAR_positional);
assert(var != NULL && (var->v_type & VF_MASK) == VF_ARRAY);
if (v == 0 || var->v_valc < v)
goto not_found; /* index out of bounds */
value = xwcsdup(var->v_vals[v - 1]);
goto return_single;
}
/* now it should be a normal variable */
var = search_variable(name);
if (var != NULL) {
if (var->v_getter)
var->v_getter(var);
switch (var->v_type & VF_MASK) {
case VF_SCALAR:
value = var->v_value ? xwcsdup(var->v_value) : NULL;
goto return_single;
case VF_ARRAY:
result.type = GV_ARRAY;
result.count = var->v_valc;
result.values = var->v_vals;
result.freevalues = false;
return result;
}
}
goto not_found;
return_single: /* return a scalar as a one-element array */
if (value != NULL) {
result.type = GV_SCALAR;
result.count = 1;
result.values = xmallocn(2, sizeof *result.values);
result.values[0] = value;
result.values[1] = NULL;
result.freevalues = true;
return result;
}
not_found:
return (struct get_variable_T) { .type = GV_NOTFOUND };
}
/* If `gv->freevalues' is false, substitutes `gv->values' with a newly-malloced
* copy of it and turns `gv->freevalues' to true. */
void save_get_variable_values(struct get_variable_T *gv)
{
if (!gv->freevalues) {
gv->values = plndup(gv->values, gv->count, copyaswcs);
gv->freevalues = true;
}
}
/* Makes a new array that contains all the variables in the current environment.
* The elements of the array are key-value pairs of names (const wchar_t *) and
* values (const variable_T *).
* If `global' is true, variables in all the ancestor environments are also
* included (except the ones hidden by local variables).
* The resultant array is assigned to `*resultp' and the number of the key-value
* pairs is returned. The array contents must not be modified or freed. */
size_t make_array_of_all_variables(bool global, kvpair_T **resultp)
{
if (current_env->parent == NULL || (!global && current_env->is_temporary)) {
*resultp = ht_tokvarray(¤t_env->contents);
return current_env->contents.count;
} else {
hashtable_T variables;
size_t count;
ht_init(&variables, hashwcs, htwcscmp);
get_all_variables_rec(&variables, current_env, global);
*resultp = ht_tokvarray(&variables);
count = variables.count;
ht_destroy(&variables);
return count;
}
}
/* Gathers all variables in the specified environment and adds them to the
* specified hashtable.
* If `global' is true, variables in all the ancestor environments are also
* included (except the ones hidden by local variables).
* Keys and values added to the hashtable must not be modified or freed by the
* caller. */
void get_all_variables_rec(hashtable_T *table, environ_T *env, bool global)
{
if (env->parent != NULL && (global || env->is_temporary))
get_all_variables_rec(table, env->parent, global);
size_t i = 0;
kvpair_T kv;
while ((kv = ht_next(&env->contents, &i)).key != NULL)
ht_set(table, kv.key, kv.value);
}
/* Creates a new variable environment.
* `temp' specifies whether the new environment is for temporary assignments.
* The current environment will be the parent of the new environment. */
/* Don't forget to call `set_positional_parameters'! */
void open_new_environment(bool temp)
{
environ_T *newenv = xmalloc(sizeof *newenv);
newenv->parent = current_env;
newenv->is_temporary = temp;
ht_init(&newenv->contents, hashwcs, htwcscmp);
for (size_t i = 0; i < PA_count; i++)
newenv->paths[i] = NULL;
current_env = newenv;