-
Notifications
You must be signed in to change notification settings - Fork 594
/
hush.c
11685 lines (10851 loc) · 319 KB
/
hush.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
/* vi: set sw=4 ts=4: */
/*
* A prototype Bourne shell grammar parser.
* Intended to follow the original Thompson and Ritchie
* "small and simple is beautiful" philosophy, which
* incidentally is a good match to today's BusyBox.
*
* Copyright (C) 2000,2001 Larry Doolittle <larry@doolittle.boa.org>
* Copyright (C) 2008,2009 Denys Vlasenko <vda.linux@googlemail.com>
*
* Licensed under GPLv2 or later, see file LICENSE in this source tree.
*
* Credits:
* The parser routines proper are all original material, first
* written Dec 2000 and Jan 2001 by Larry Doolittle. The
* execution engine, the builtins, and much of the underlying
* support has been adapted from busybox-0.49pre's lash, which is
* Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
* written by Erik Andersen <andersen@codepoet.org>. That, in turn,
* is based in part on ladsh.c, by Michael K. Johnson and Erik W.
* Troan, which they placed in the public domain. I don't know
* how much of the Johnson/Troan code has survived the repeated
* rewrites.
*
* Other credits:
* o_addchr derived from similar w_addchar function in glibc-2.2.
* parse_redirect, redirect_opt_num, and big chunks of main
* and many builtins derived from contributions by Erik Andersen.
* Miscellaneous bugfixes from Matt Kraai.
*
* There are two big (and related) architecture differences between
* this parser and the lash parser. One is that this version is
* actually designed from the ground up to understand nearly all
* of the Bourne grammar. The second, consequential change is that
* the parser and input reader have been turned inside out. Now,
* the parser is in control, and asks for input as needed. The old
* way had the input reader in control, and it asked for parsing to
* take place as needed. The new way makes it much easier to properly
* handle the recursion implicit in the various substitutions, especially
* across continuation lines.
*
* TODOs:
* grep for "TODO" and fix (some of them are easy)
* make complex ${var%...} constructs support optional
* make here documents optional
* special variables (done: PWD, PPID, RANDOM)
* follow IFS rules more precisely, including update semantics
* tilde expansion
* aliases
* "command" missing features:
* command -p CMD: run CMD using default $PATH
* (can use this to override standalone shell as well?)
* command BLTIN: disables special-ness (e.g. errors do not abort)
* command -V CMD1 CMD2 CMD3 (multiple args) (not in standard)
* builtins mandated by standards we don't support:
* [un]alias, fc:
* fc -l[nr] [BEG] [END]: list range of commands in history
* fc [-e EDITOR] [BEG] [END]: edit/rerun range of commands
* fc -s [PAT=REP] [CMD]: rerun CMD, replacing PAT with REP
*
* Bash compat TODO:
* redirection of stdout+stderr: &> and >&
* reserved words: function select
* advanced test: [[ ]]
* process substitution: <(list) and >(list)
* =~: regex operator
* let EXPR [EXPR...]
* Each EXPR is an arithmetic expression (ARITHMETIC EVALUATION)
* If the last arg evaluates to 0, let returns 1; 0 otherwise.
* NB: let `echo 'a=a + 1'` - error (IOW: multi-word expansion is used)
* ((EXPR))
* The EXPR is evaluated according to ARITHMETIC EVALUATION.
* This is exactly equivalent to let "EXPR".
* $[EXPR]: synonym for $((EXPR))
* indirect expansion: ${!VAR}
* substring op on @: ${@:n:m}
*
* Won't do:
* Some builtins mandated by standards:
* newgrp [GRP]: not a builtin in bash but a suid binary
* which spawns a new shell with new group ID
*
* Status of [[ support:
* [[ args ]] are CMD_SINGLEWORD_NOGLOB:
* v='a b'; [[ $v = 'a b' ]]; echo 0:$?
* [[ /bin/n* ]]; echo 0:$?
* TODO:
* &&/|| are AND/OR ops, -a/-o are not
* quoting needs to be considered (-f is an operator, "-f" and ""-f are not; etc)
* = is glob match operator, not equality operator: STR = GLOB
* (in GLOB, quoting is significant on char-by-char basis: a*cd"*")
* == same as =
* add =~ regex match operator: STR =~ REGEX
*/
//config:config HUSH
//config: bool "hush (64 kb)"
//config: default y
//config: help
//config: hush is a small shell. It handles the normal flow control
//config: constructs such as if/then/elif/else/fi, for/in/do/done, while loops,
//config: case/esac. Redirections, here documents, $((arithmetic))
//config: and functions are supported.
//config:
//config: It will compile and work on no-mmu systems.
//config:
//config: It does not handle select, aliases, tilde expansion,
//config: &>file and >&file redirection of stdout+stderr.
//config:
//config:config HUSH_BASH_COMPAT
//config: bool "bash-compatible extensions"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_BRACE_EXPANSION
//config: bool "Brace expansion"
//config: default y
//config: depends on HUSH_BASH_COMPAT
//config: help
//config: Enable {abc,def} extension.
//config:
//config:config HUSH_LINENO_VAR
//config: bool "$LINENO variable"
//config: default y
//config: depends on HUSH_BASH_COMPAT
//config:
//config:config HUSH_BASH_SOURCE_CURDIR
//config: bool "'source' and '.' builtins search current directory after $PATH"
//config: default n # do not encourage non-standard behavior
//config: depends on HUSH_BASH_COMPAT
//config: help
//config: This is not compliant with standards. Avoid if possible.
//config:
//config:config HUSH_INTERACTIVE
//config: bool "Interactive mode"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config: help
//config: Enable interactive mode (prompt and command editing).
//config: Without this, hush simply reads and executes commands
//config: from stdin just like a shell script from a file.
//config: No prompt, no PS1/PS2 magic shell variables.
//config:
//config:config HUSH_SAVEHISTORY
//config: bool "Save command history to .hush_history"
//config: default y
//config: depends on HUSH_INTERACTIVE && FEATURE_EDITING_SAVEHISTORY
//config:
//config:config HUSH_JOB
//config: bool "Job control"
//config: default y
//config: depends on HUSH_INTERACTIVE
//config: help
//config: Enable job control: Ctrl-Z backgrounds, Ctrl-C interrupts current
//config: command (not entire shell), fg/bg builtins work. Without this option,
//config: "cmd &" still works by simply spawning a process and immediately
//config: prompting for next command (or executing next command in a script),
//config: but no separate process group is formed.
//config:
//config:config HUSH_TICK
//config: bool "Support process substitution"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config: help
//config: Enable `command` and $(command).
//config:
//config:config HUSH_IF
//config: bool "Support if/then/elif/else/fi"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_LOOPS
//config: bool "Support for, while and until loops"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_CASE
//config: bool "Support case ... esac statement"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config: help
//config: Enable case ... esac statement. +400 bytes.
//config:
//config:config HUSH_FUNCTIONS
//config: bool "Support funcname() { commands; } syntax"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config: help
//config: Enable support for shell functions. +800 bytes.
//config:
//config:config HUSH_LOCAL
//config: bool "local builtin"
//config: default y
//config: depends on HUSH_FUNCTIONS
//config: help
//config: Enable support for local variables in functions.
//config:
//config:config HUSH_RANDOM_SUPPORT
//config: bool "Pseudorandom generator and $RANDOM variable"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config: help
//config: Enable pseudorandom generator and dynamic variable "$RANDOM".
//config: Each read of "$RANDOM" will generate a new pseudorandom value.
//config:
//config:config HUSH_MODE_X
//config: bool "Support 'hush -x' option and 'set -x' command"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config: help
//config: This instructs hush to print commands before execution.
//config: Adds ~300 bytes.
//config:
//config:config HUSH_ECHO
//config: bool "echo builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_PRINTF
//config: bool "printf builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_TEST
//config: bool "test builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_HELP
//config: bool "help builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_EXPORT
//config: bool "export builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_EXPORT_N
//config: bool "Support 'export -n' option"
//config: default y
//config: depends on HUSH_EXPORT
//config: help
//config: export -n unexports variables. It is a bash extension.
//config:
//config:config HUSH_READONLY
//config: bool "readonly builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config: help
//config: Enable support for read-only variables.
//config:
//config:config HUSH_KILL
//config: bool "kill builtin (supports kill %jobspec)"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_WAIT
//config: bool "wait builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_COMMAND
//config: bool "command builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_TRAP
//config: bool "trap builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_TYPE
//config: bool "type builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_TIMES
//config: bool "times builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_READ
//config: bool "read builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_SET
//config: bool "set builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_UNSET
//config: bool "unset builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_ULIMIT
//config: bool "ulimit builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_UMASK
//config: bool "umask builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_GETOPTS
//config: bool "getopts builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_MEMLEAK
//config: bool "memleak builtin (debugging)"
//config: default n
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//applet:IF_HUSH(APPLET(hush, BB_DIR_BIN, BB_SUID_DROP))
// APPLET_ODDNAME:name main location suid_type help
//applet:IF_SH_IS_HUSH( APPLET_ODDNAME(sh, hush, BB_DIR_BIN, BB_SUID_DROP, hush))
//applet:IF_BASH_IS_HUSH(APPLET_ODDNAME(bash, hush, BB_DIR_BIN, BB_SUID_DROP, hush))
//kbuild:lib-$(CONFIG_HUSH) += hush.o match.o shell_common.o
//kbuild:lib-$(CONFIG_SH_IS_HUSH) += hush.o match.o shell_common.o
//kbuild:lib-$(CONFIG_BASH_IS_HUSH) += hush.o match.o shell_common.o
//kbuild:lib-$(CONFIG_HUSH_RANDOM_SUPPORT) += random.o
/* -i (interactive) is also accepted,
* but does nothing, therefore not shown in help.
* NOMMU-specific options are not meant to be used by users,
* therefore we don't show them either.
*/
//usage:#define hush_trivial_usage
//usage: "[-enxl] [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS] / -s [ARGS]]"
//usage:#define hush_full_usage "\n\n"
//usage: "Unix shell interpreter"
#if !(defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) \
|| defined(__APPLE__) \
)
# include <malloc.h> /* for malloc_trim */
#endif
#include <glob.h>
/* #include <dmalloc.h> */
#if ENABLE_HUSH_CASE
# include <fnmatch.h>
#endif
#include <sys/times.h>
#include <sys/utsname.h> /* for setting $HOSTNAME */
#include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */
#include "unicode.h"
#include "shell_common.h"
#include "math.h"
#include "match.h"
#if ENABLE_HUSH_RANDOM_SUPPORT
# include "random.h"
#else
# define CLEAR_RANDOM_T(rnd) ((void)0)
#endif
#ifndef O_CLOEXEC
# define O_CLOEXEC 0
#endif
#ifndef F_DUPFD_CLOEXEC
# define F_DUPFD_CLOEXEC F_DUPFD
#endif
#ifndef PIPE_BUF
# define PIPE_BUF 4096 /* amount of buffering in a pipe */
#endif
/* So far, all bash compat is controlled by one config option */
/* Separate defines document which part of code implements what */
#define BASH_PATTERN_SUBST ENABLE_HUSH_BASH_COMPAT
#define BASH_SUBSTR ENABLE_HUSH_BASH_COMPAT
#define BASH_SOURCE ENABLE_HUSH_BASH_COMPAT
#define BASH_HOSTNAME_VAR ENABLE_HUSH_BASH_COMPAT
#define BASH_TEST2 (ENABLE_HUSH_BASH_COMPAT && ENABLE_HUSH_TEST)
#define BASH_READ_D ENABLE_HUSH_BASH_COMPAT
/* Build knobs */
#define LEAK_HUNTING 0
#define BUILD_AS_NOMMU 0
/* Enable/disable sanity checks. Ok to enable in production,
* only adds a bit of bloat. Set to >1 to get non-production level verbosity.
* Keeping 1 for now even in released versions.
*/
#define HUSH_DEBUG 1
/* Slightly bigger (+200 bytes), but faster hush.
* So far it only enables a trick with counting SIGCHLDs and forks,
* which allows us to do fewer waitpid's.
* (we can detect a case where neither forks were done nor SIGCHLDs happened
* and therefore waitpid will return the same result as last time)
*/
#define ENABLE_HUSH_FAST 0
/* TODO: implement simplified code for users which do not need ${var%...} ops
* So far ${var%...} ops are always enabled:
*/
#define ENABLE_HUSH_DOLLAR_OPS 1
#if BUILD_AS_NOMMU
# undef BB_MMU
# undef USE_FOR_NOMMU
# undef USE_FOR_MMU
# define BB_MMU 0
# define USE_FOR_NOMMU(...) __VA_ARGS__
# define USE_FOR_MMU(...)
#endif
#include "NUM_APPLETS.h"
#if NUM_APPLETS == 1
/* STANDALONE does not make sense, and won't compile */
# undef CONFIG_FEATURE_SH_STANDALONE
# undef ENABLE_FEATURE_SH_STANDALONE
# undef IF_FEATURE_SH_STANDALONE
# undef IF_NOT_FEATURE_SH_STANDALONE
# define ENABLE_FEATURE_SH_STANDALONE 0
# define IF_FEATURE_SH_STANDALONE(...)
# define IF_NOT_FEATURE_SH_STANDALONE(...) __VA_ARGS__
#endif
#if !ENABLE_HUSH_INTERACTIVE
# undef ENABLE_FEATURE_EDITING
# define ENABLE_FEATURE_EDITING 0
# undef ENABLE_FEATURE_EDITING_FANCY_PROMPT
# define ENABLE_FEATURE_EDITING_FANCY_PROMPT 0
# undef ENABLE_FEATURE_EDITING_SAVE_ON_EXIT
# define ENABLE_FEATURE_EDITING_SAVE_ON_EXIT 0
#endif
/* Do we support ANY keywords? */
#if ENABLE_HUSH_IF || ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE
# define HAS_KEYWORDS 1
# define IF_HAS_KEYWORDS(...) __VA_ARGS__
# define IF_HAS_NO_KEYWORDS(...)
#else
# define HAS_KEYWORDS 0
# define IF_HAS_KEYWORDS(...)
# define IF_HAS_NO_KEYWORDS(...) __VA_ARGS__
#endif
/* If you comment out one of these below, it will be #defined later
* to perform debug printfs to stderr: */
#define debug_printf(...) do {} while (0)
/* Finer-grained debug switches */
#define debug_printf_parse(...) do {} while (0)
#define debug_printf_heredoc(...) do {} while (0)
#define debug_print_tree(a, b) do {} while (0)
#define debug_printf_exec(...) do {} while (0)
#define debug_printf_env(...) do {} while (0)
#define debug_printf_jobs(...) do {} while (0)
#define debug_printf_expand(...) do {} while (0)
#define debug_printf_varexp(...) do {} while (0)
#define debug_printf_glob(...) do {} while (0)
#define debug_printf_redir(...) do {} while (0)
#define debug_printf_list(...) do {} while (0)
#define debug_printf_subst(...) do {} while (0)
#define debug_printf_prompt(...) do {} while (0)
#define debug_printf_clean(...) do {} while (0)
#define ERR_PTR ((void*)(long)1)
#define JOB_STATUS_FORMAT "[%u] %-22s %.40s\n"
#define _SPECIAL_VARS_STR "_*@$!?#"
#define SPECIAL_VARS_STR ("_*@$!?#" + 1)
#define NUMERIC_SPECVARS_STR ("_*@$!?#" + 3)
#if BASH_PATTERN_SUBST
/* Support / and // replace ops */
/* Note that // is stored as \ in "encoded" string representation */
# define VAR_ENCODED_SUBST_OPS "\\/%#:-=+?"
# define VAR_SUBST_OPS ("\\/%#:-=+?" + 1)
# define MINUS_PLUS_EQUAL_QUESTION ("\\/%#:-=+?" + 5)
#else
# define VAR_ENCODED_SUBST_OPS "%#:-=+?"
# define VAR_SUBST_OPS "%#:-=+?"
# define MINUS_PLUS_EQUAL_QUESTION ("%#:-=+?" + 3)
#endif
#define SPECIAL_VAR_SYMBOL_STR "\3"
#define SPECIAL_VAR_SYMBOL 3
/* The "variable" with name "\1" emits string "\3". Testcase: "echo ^C" */
#define SPECIAL_VAR_QUOTED_SVS 1
struct variable;
static const char hush_version_str[] ALIGN1 = "HUSH_VERSION="BB_VER;
/* This supports saving pointers malloced in vfork child,
* to be freed in the parent.
*/
#if !BB_MMU
typedef struct nommu_save_t {
struct variable *old_vars;
char **argv;
char **argv_from_re_execing;
} nommu_save_t;
#endif
enum {
RES_NONE = 0,
#if ENABLE_HUSH_IF
RES_IF ,
RES_THEN ,
RES_ELIF ,
RES_ELSE ,
RES_FI ,
#endif
#if ENABLE_HUSH_LOOPS
RES_FOR ,
RES_WHILE ,
RES_UNTIL ,
RES_DO ,
RES_DONE ,
#endif
#if ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE
RES_IN ,
#endif
#if ENABLE_HUSH_CASE
RES_CASE ,
/* three pseudo-keywords support contrived "case" syntax: */
RES_CASE_IN, /* "case ... IN", turns into RES_MATCH when IN is observed */
RES_MATCH , /* "word)" */
RES_CASE_BODY, /* "this command is inside CASE" */
RES_ESAC ,
#endif
RES_XXXX ,
RES_SNTX
};
typedef struct o_string {
char *data;
int length; /* position where data is appended */
int maxlen;
int o_expflags;
/* At least some part of the string was inside '' or "",
* possibly empty one: word"", wo''rd etc. */
smallint has_quoted_part;
smallint has_empty_slot;
smallint ended_in_ifs;
} o_string;
enum {
EXP_FLAG_SINGLEWORD = 0x80, /* must be 0x80 */
EXP_FLAG_GLOB = 0x2,
/* Protect newly added chars against globbing
* by prepending \ to *, ?, [, \ */
EXP_FLAG_ESC_GLOB_CHARS = 0x1,
};
/* Used for initialization: o_string foo = NULL_O_STRING; */
#define NULL_O_STRING { NULL }
#ifndef debug_printf_parse
static const char *const assignment_flag[] = {
"MAYBE_ASSIGNMENT",
"DEFINITELY_ASSIGNMENT",
"NOT_ASSIGNMENT",
"WORD_IS_KEYWORD",
};
#endif
/* We almost can use standard FILE api, but we need an ability to move
* its fd when redirects coincide with it. No api exists for that
* (RFE for it at https://sourceware.org/bugzilla/show_bug.cgi?id=21902).
* HFILE is our internal alternative. Only supports reading.
* Since we now can, we incorporate linked list of all opened HFILEs
* into the struct (used to be a separate mini-list).
*/
typedef struct HFILE {
char *cur;
char *end;
struct HFILE *next_hfile;
int is_stdin;
int fd;
char buf[1024];
} HFILE;
typedef struct in_str {
const char *p;
int peek_buf[2];
int last_char;
HFILE *file;
} in_str;
/* The descrip member of this structure is only used to make
* debugging output pretty */
static const struct {
int mode;
signed char default_fd;
char descrip[3];
} redir_table[] = {
{ O_RDONLY, 0, "<" },
{ O_CREAT|O_TRUNC|O_WRONLY, 1, ">" },
{ O_CREAT|O_APPEND|O_WRONLY, 1, ">>" },
{ O_CREAT|O_RDWR, 1, "<>" },
{ O_RDONLY, 0, "<<" },
/* Should not be needed. Bogus default_fd helps in debugging */
/* { O_RDONLY, 77, "<<" }, */
};
struct redir_struct {
struct redir_struct *next;
char *rd_filename; /* filename */
int rd_fd; /* fd to redirect */
/* fd to redirect to, or -3 if rd_fd is to be closed (n>&-) */
int rd_dup;
smallint rd_type; /* (enum redir_type) */
/* note: for heredocs, rd_filename contains heredoc delimiter,
* and subsequently heredoc itself; and rd_dup is a bitmask:
* bit 0: do we need to trim leading tabs?
* bit 1: is heredoc quoted (<<'delim' syntax) ?
*/
};
typedef enum redir_type {
REDIRECT_INPUT = 0,
REDIRECT_OVERWRITE = 1,
REDIRECT_APPEND = 2,
REDIRECT_IO = 3,
REDIRECT_HEREDOC = 4,
REDIRECT_HEREDOC2 = 5, /* REDIRECT_HEREDOC after heredoc is loaded */
REDIRFD_CLOSE = -3,
REDIRFD_SYNTAX_ERR = -2,
REDIRFD_TO_FILE = -1,
/* otherwise, rd_fd is redirected to rd_dup */
HEREDOC_SKIPTABS = 1,
HEREDOC_QUOTED = 2,
} redir_type;
struct command {
pid_t pid; /* 0 if exited */
unsigned assignment_cnt; /* how many argv[i] are assignments? */
#if ENABLE_HUSH_LINENO_VAR
unsigned lineno;
#endif
smallint cmd_type; /* CMD_xxx */
#define CMD_NORMAL 0
#define CMD_SUBSHELL 1
#if BASH_TEST2 || ENABLE_HUSH_LOCAL || ENABLE_HUSH_EXPORT || ENABLE_HUSH_READONLY
/* used for "[[ EXPR ]]", and to prevent word splitting and globbing in
* "export v=t*"
*/
# define CMD_SINGLEWORD_NOGLOB 2
#endif
#if ENABLE_HUSH_FUNCTIONS
# define CMD_FUNCDEF 3
#endif
smalluint cmd_exitcode;
/* if non-NULL, this "command" is { list }, ( list ), or a compound statement */
struct pipe *group;
#if !BB_MMU
char *group_as_string;
#endif
#if ENABLE_HUSH_FUNCTIONS
struct function *child_func;
/* This field is used to prevent a bug here:
* while...do f1() {a;}; f1; f1() {b;}; f1; done
* When we execute "f1() {a;}" cmd, we create new function and clear
* cmd->group, cmd->group_as_string, cmd->argv[0].
* When we execute "f1() {b;}", we notice that f1 exists,
* and that its "parent cmd" struct is still "alive",
* we put those fields back into cmd->xxx
* (struct function has ->parent_cmd ptr to facilitate that).
* When we loop back, we can execute "f1() {a;}" again and set f1 correctly.
* Without this trick, loop would execute a;b;b;b;...
* instead of correct sequence a;b;a;b;...
* When command is freed, it severs the link
* (sets ->child_func->parent_cmd to NULL).
*/
#endif
char **argv; /* command name and arguments */
/* argv vector may contain variable references (^Cvar^C, ^C0^C etc)
* and on execution these are substituted with their values.
* Substitution can make _several_ words out of one argv[n]!
* Example: argv[0]=='.^C*^C.' here: echo .$*.
* References of the form ^C`cmd arg^C are `cmd arg` substitutions.
*/
struct redir_struct *redirects; /* I/O redirections */
};
/* Is there anything in this command at all? */
#define IS_NULL_CMD(cmd) \
(!(cmd)->group && !(cmd)->argv && !(cmd)->redirects)
struct pipe {
struct pipe *next;
int num_cmds; /* total number of commands in pipe */
int alive_cmds; /* number of commands running (not exited) */
int stopped_cmds; /* number of commands alive, but stopped */
#if ENABLE_HUSH_JOB
unsigned jobid; /* job number */
pid_t pgrp; /* process group ID for the job */
char *cmdtext; /* name of job */
#endif
struct command *cmds; /* array of commands in pipe */
smallint followup; /* PIPE_BG, PIPE_SEQ, PIPE_OR, PIPE_AND */
IF_HAS_KEYWORDS(smallint pi_inverted;) /* "! cmd | cmd" */
IF_HAS_KEYWORDS(smallint res_word;) /* needed for if, for, while, until... */
};
typedef enum pipe_style {
PIPE_SEQ = 0,
PIPE_AND = 1,
PIPE_OR = 2,
PIPE_BG = 3,
} pipe_style;
/* Is there anything in this pipe at all? */
#define IS_NULL_PIPE(pi) \
((pi)->num_cmds == 0 IF_HAS_KEYWORDS( && (pi)->res_word == RES_NONE))
/* This holds pointers to the various results of parsing */
struct parse_context {
/* linked list of pipes */
struct pipe *list_head;
/* last pipe (being constructed right now) */
struct pipe *pipe;
/* last command in pipe (being constructed right now) */
struct command *command;
/* last redirect in command->redirects list */
struct redir_struct *pending_redirect;
o_string word;
#if !BB_MMU
o_string as_string;
#endif
smallint is_assignment; /* 0:maybe, 1:yes, 2:no, 3:keyword */
#if HAS_KEYWORDS
smallint ctx_res_w;
smallint ctx_inverted; /* "! cmd | cmd" */
#if ENABLE_HUSH_CASE
smallint ctx_dsemicolon; /* ";;" seen */
#endif
/* bitmask of FLAG_xxx, for figuring out valid reserved words */
int old_flag;
/* group we are enclosed in:
* example: "if pipe1; pipe2; then pipe3; fi"
* when we see "if" or "then", we malloc and copy current context,
* and make ->stack point to it. then we parse pipeN.
* when closing "then" / fi" / whatever is found,
* we move list_head into ->stack->command->group,
* copy ->stack into current context, and delete ->stack.
* (parsing of { list } and ( list ) doesn't use this method)
*/
struct parse_context *stack;
#endif
};
enum {
MAYBE_ASSIGNMENT = 0,
DEFINITELY_ASSIGNMENT = 1,
NOT_ASSIGNMENT = 2,
/* Not an assignment, but next word may be: "if v=xyz cmd;" */
WORD_IS_KEYWORD = 3,
};
/* On program start, environ points to initial environment.
* putenv adds new pointers into it, unsetenv removes them.
* Neither of these (de)allocates the strings.
* setenv allocates new strings in malloc space and does putenv,
* and thus setenv is unusable (leaky) for shell's purposes */
#define setenv(...) setenv_is_leaky_dont_use()
struct variable {
struct variable *next;
char *varstr; /* points to "name=" portion */
int max_len; /* if > 0, name is part of initial env; else name is malloced */
uint16_t var_nest_level;
smallint flg_export; /* putenv should be done on this var */
smallint flg_read_only;
};
enum {
BC_BREAK = 1,
BC_CONTINUE = 2,
};
#if ENABLE_HUSH_FUNCTIONS
struct function {
struct function *next;
char *name;
struct command *parent_cmd;
struct pipe *body;
# if !BB_MMU
char *body_as_string;
# endif
};
#endif
/* set -/+o OPT support. (TODO: make it optional)
* bash supports the following opts:
* allexport off
* braceexpand on
* emacs on
* errexit off
* errtrace off
* functrace off
* hashall on
* histexpand off
* history on
* ignoreeof off
* interactive-comments on
* keyword off
* monitor on
* noclobber off
* noexec off
* noglob off
* nolog off
* notify off
* nounset off
* onecmd off
* physical off
* pipefail off
* posix off
* privileged off
* verbose off
* vi off
* xtrace off
*/
static const char o_opt_strings[] ALIGN1 =
"pipefail\0"
"noexec\0"
"errexit\0"
#if ENABLE_HUSH_MODE_X
"xtrace\0"
#endif
;
enum {
OPT_O_PIPEFAIL,
OPT_O_NOEXEC,
OPT_O_ERREXIT,
#if ENABLE_HUSH_MODE_X
OPT_O_XTRACE,
#endif
NUM_OPT_O
};
/* "Globals" within this file */
/* Sorted roughly by size (smaller offsets == smaller code) */
struct globals {
/* interactive_fd != 0 means we are an interactive shell.
* If we are, then saved_tty_pgrp can also be != 0, meaning
* that controlling tty is available. With saved_tty_pgrp == 0,
* job control still works, but terminal signals
* (^C, ^Z, ^Y, ^\) won't work at all, and background
* process groups can only be created with "cmd &".
* With saved_tty_pgrp != 0, hush will use tcsetpgrp()
* to give tty to the foreground process group,
* and will take it back when the group is stopped (^Z)
* or killed (^C).
*/
#if ENABLE_HUSH_INTERACTIVE
/* 'interactive_fd' is a fd# open to ctty, if we have one
* _AND_ if we decided to act interactively */
int interactive_fd;
const char *PS1;
IF_FEATURE_EDITING_FANCY_PROMPT(const char *PS2;)
# define G_interactive_fd (G.interactive_fd)
#else
# define G_interactive_fd 0
#endif
#if ENABLE_FEATURE_EDITING
line_input_t *line_input_state;
#endif
pid_t root_pid;
pid_t root_ppid;
pid_t last_bg_pid;
#if ENABLE_HUSH_RANDOM_SUPPORT
random_t random_gen;
#endif
#if ENABLE_HUSH_JOB
int run_list_level;
unsigned last_jobid;
pid_t saved_tty_pgrp;
struct pipe *job_list;
# define G_saved_tty_pgrp (G.saved_tty_pgrp)
#else
# define G_saved_tty_pgrp 0
#endif
/* How deeply are we in context where "set -e" is ignored */
int errexit_depth;
/* "set -e" rules (do we follow them correctly?):
* Exit if pipe, list, or compound command exits with a non-zero status.
* Shell does not exit if failed command is part of condition in
* if/while, part of && or || list except the last command, any command
* in a pipe but the last, or if the command's return value is being
* inverted with !. If a compound command other than a subshell returns a
* non-zero status because a command failed while -e was being ignored, the
* shell does not exit. A trap on ERR, if set, is executed before the shell
* exits [ERR is a bashism].
*
* If a compound command or function executes in a context where -e is
* ignored, none of the commands executed within are affected by the -e
* setting. If a compound command or function sets -e while executing in a
* context where -e is ignored, that setting does not have any effect until
* the compound command or the command containing the function call completes.
*/
char o_opt[NUM_OPT_O];
#if ENABLE_HUSH_MODE_X
# define G_x_mode (G.o_opt[OPT_O_XTRACE])
#else
# define G_x_mode 0
#endif
#if ENABLE_HUSH_INTERACTIVE
smallint promptmode; /* 0: PS1, 1: PS2 */
#endif
smallint flag_SIGINT;
#if ENABLE_HUSH_LOOPS
smallint flag_break_continue;
#endif
#if ENABLE_HUSH_FUNCTIONS
/* 0: outside of a function (or sourced file)
* -1: inside of a function, ok to use return builtin
* 1: return is invoked, skip all till end of func
*/
smallint flag_return_in_progress;
# define G_flag_return_in_progress (G.flag_return_in_progress)
#else
# define G_flag_return_in_progress 0
#endif
smallint exiting; /* used to prevent EXIT trap recursion */
/* These support $?, $#, and $1 */
smalluint last_exitcode;
smalluint expand_exitcode;
smalluint last_bg_pid_exitcode;
#if ENABLE_HUSH_SET
/* are global_argv and global_argv[1..n] malloced? (note: not [0]) */
smalluint global_args_malloced;
# define G_global_args_malloced (G.global_args_malloced)
#else
# define G_global_args_malloced 0
#endif
/* how many non-NULL argv's we have. NB: $# + 1 */
int global_argc;
char **global_argv;
#if !BB_MMU
char *argv0_for_re_execing;
#endif
#if ENABLE_HUSH_LOOPS
unsigned depth_break_continue;
unsigned depth_of_loop;
#endif
#if ENABLE_HUSH_GETOPTS
unsigned getopt_count;
#endif
const char *ifs;
char *ifs_whitespace; /* = G.ifs or malloced */
const char *cwd;
struct variable *top_var;
char **expanded_assignments;
struct variable **shadowed_vars_pp;
unsigned var_nest_level;
#if ENABLE_HUSH_FUNCTIONS
# if ENABLE_HUSH_LOCAL
unsigned func_nest_level; /* solely to prevent "local v" in non-functions */
# endif
struct function *top_func;
#endif
/* Signal and trap handling */
#if ENABLE_HUSH_FAST
unsigned count_SIGCHLD;
unsigned handled_SIGCHLD;
smallint we_have_children;
#endif
#if ENABLE_HUSH_LINENO_VAR
unsigned lineno;
char *lineno_var;
#endif
HFILE *HFILE_list;
/* Which signals have non-DFL handler (even with no traps set)?
* Set at the start to:
* (SIGQUIT + maybe SPECIAL_INTERACTIVE_SIGS + maybe SPECIAL_JOBSTOP_SIGS)
* SPECIAL_INTERACTIVE_SIGS are cleared after fork.
* The rest is cleared right before execv syscalls.
* Other than these two times, never modified.
*/
unsigned special_sig_mask;
#if ENABLE_HUSH_JOB
unsigned fatal_sig_mask;
# define G_fatal_sig_mask (G.fatal_sig_mask)
#else
# define G_fatal_sig_mask 0
#endif
#if ENABLE_HUSH_TRAP
char **traps; /* char *traps[NSIG] */
# define G_traps G.traps
#else
# define G_traps ((char**)NULL)
#endif
sigset_t pending_set;
#if ENABLE_HUSH_MEMLEAK
unsigned long memleak_value;
#endif
#if ENABLE_HUSH_MODE_X
unsigned x_mode_depth;
/* "set -x" output should not be redirectable with subsequent 2>FILE.
* We dup fd#2 to x_mode_fd when "set -x" is executed, and use it
* for all subsequent output.
*/
int x_mode_fd;
o_string x_mode_buf;