diff options
author | Ron Yorston <rmy@pobox.com> | 2018-05-13 08:15:58 +0100 |
---|---|---|
committer | Ron Yorston <rmy@pobox.com> | 2018-05-13 08:15:58 +0100 |
commit | 3ce461fdf3b7adfd44ea058fa0c5ca6d91a5bc5d (patch) | |
tree | a527d0db15f34a137fc11df5538c7f2f7c6d72de | |
parent | 6f7d1af269eed4b42daeb9c6dfd2ba62f9cd47e4 (diff) | |
parent | d80eecb86812c1fbda652f9b995060c26ba0b155 (diff) | |
download | busybox-w32-3ce461fdf3b7adfd44ea058fa0c5ca6d91a5bc5d.tar.gz busybox-w32-3ce461fdf3b7adfd44ea058fa0c5ca6d91a5bc5d.tar.bz2 busybox-w32-3ce461fdf3b7adfd44ea058fa0c5ca6d91a5bc5d.zip |
Merge branch 'busybox' into merge
78 files changed, 2613 insertions, 469 deletions
@@ -370,7 +370,7 @@ gen_build_files: $(wildcard $(srctree)/*/*.c) $(wildcard $(srctree)/*/*/*.c) | |||
370 | # we depend on scripts_basic, since scripts/basic/fixdep | 370 | # we depend on scripts_basic, since scripts/basic/fixdep |
371 | # must be built before any other host prog | 371 | # must be built before any other host prog |
372 | PHONY += applets_dir | 372 | PHONY += applets_dir |
373 | applets_dir: scripts_basic gen_build_files | 373 | applets_dir: scripts_basic gen_build_files include/config/MARKER |
374 | $(Q)$(MAKE) $(build)=applets | 374 | $(Q)$(MAKE) $(build)=applets |
375 | 375 | ||
376 | applets/%: applets_dir ; | 376 | applets/%: applets_dir ; |
diff --git a/Makefile.custom b/Makefile.custom index 28d0ef7bc..6f679c4e1 100644 --- a/Makefile.custom +++ b/Makefile.custom | |||
@@ -11,6 +11,9 @@ busybox.cfg.nosuid: $(srctree)/applets/busybox.mksuid $(objtree)/include/autocon | |||
11 | $(Q)-SUID="DROP" $(SHELL) $^ > $@ | 11 | $(Q)-SUID="DROP" $(SHELL) $^ > $@ |
12 | 12 | ||
13 | .PHONY: install | 13 | .PHONY: install |
14 | ifeq ($(CONFIG_INSTALL_APPLET_DONT),y) | ||
15 | INSTALL_OPTS:= --none | ||
16 | endif | ||
14 | ifeq ($(CONFIG_INSTALL_APPLET_SYMLINKS),y) | 17 | ifeq ($(CONFIG_INSTALL_APPLET_SYMLINKS),y) |
15 | INSTALL_OPTS:= --symlinks | 18 | INSTALL_OPTS:= --symlinks |
16 | endif | 19 | endif |
diff --git a/Makefile.flags b/Makefile.flags index 86ab0a0ec..6d4b2c3aa 100644 --- a/Makefile.flags +++ b/Makefile.flags | |||
@@ -162,6 +162,10 @@ CPPFLAGS += $(SELINUX_CFLAGS) | |||
162 | LDLIBS += $(if $(SELINUX_LIBS),$(SELINUX_LIBS:-l%=%),$(SELINUX_PC_MODULES:lib%=%)) | 162 | LDLIBS += $(if $(SELINUX_LIBS),$(SELINUX_LIBS:-l%=%),$(SELINUX_PC_MODULES:lib%=%)) |
163 | endif | 163 | endif |
164 | 164 | ||
165 | ifeq ($(CONFIG_FEATURE_NSLOOKUP_BIG),y) | ||
166 | LDLIBS += resolv | ||
167 | endif | ||
168 | |||
165 | ifeq ($(CONFIG_EFENCE),y) | 169 | ifeq ($(CONFIG_EFENCE),y) |
166 | LDLIBS += efence | 170 | LDLIBS += efence |
167 | endif | 171 | endif |
diff --git a/applets/applet_tables.c b/applets/applet_tables.c index 858429ac2..b997194ad 100644 --- a/applets/applet_tables.c +++ b/applets/applet_tables.c | |||
@@ -10,6 +10,7 @@ | |||
10 | #include <sys/types.h> | 10 | #include <sys/types.h> |
11 | #include <sys/stat.h> | 11 | #include <sys/stat.h> |
12 | #include <fcntl.h> | 12 | #include <fcntl.h> |
13 | #include <limits.h> | ||
13 | #include <stdlib.h> | 14 | #include <stdlib.h> |
14 | #include <string.h> | 15 | #include <string.h> |
15 | #include <stdio.h> | 16 | #include <stdio.h> |
@@ -62,6 +63,7 @@ int main(int argc, char **argv) | |||
62 | { | 63 | { |
63 | int i, j; | 64 | int i, j; |
64 | unsigned MAX_APPLET_NAME_LEN = 1; | 65 | unsigned MAX_APPLET_NAME_LEN = 1; |
66 | char tmp1[PATH_MAX], tmp2[PATH_MAX]; | ||
65 | 67 | ||
66 | // In find_applet_by_name(), before linear search, narrow it down | 68 | // In find_applet_by_name(), before linear search, narrow it down |
67 | // by looking at N "equidistant" names. With ~350 applets: | 69 | // by looking at N "equidistant" names. With ~350 applets: |
@@ -85,7 +87,8 @@ int main(int argc, char **argv) | |||
85 | 87 | ||
86 | if (!argv[1]) | 88 | if (!argv[1]) |
87 | return 1; | 89 | return 1; |
88 | i = open(argv[1], O_WRONLY | O_TRUNC | O_CREAT, 0666); | 90 | snprintf(tmp1, PATH_MAX, "%s.%u.new", argv[1], (int) getpid()); |
91 | i = open(tmp1, O_WRONLY | O_TRUNC | O_CREAT, 0666); | ||
89 | if (i < 0) | 92 | if (i < 0) |
90 | return 1; | 93 | return 1; |
91 | dup2(i, 1); | 94 | dup2(i, 1); |
@@ -210,12 +213,21 @@ int main(int argc, char **argv) | |||
210 | // fclose(fp); | 213 | // fclose(fp); |
211 | // } | 214 | // } |
212 | // if (strcmp(line_old, line_new) != 0) { | 215 | // if (strcmp(line_old, line_new) != 0) { |
213 | fp = fopen(argv[2], "w"); | 216 | snprintf(tmp2, PATH_MAX, "%s.%u.new", argv[2], (int) getpid()); |
217 | fp = fopen(tmp2, "w"); | ||
214 | if (!fp) | 218 | if (!fp) |
215 | return 1; | 219 | return 1; |
216 | fputs(line_new, fp); | 220 | fputs(line_new, fp); |
221 | if (fclose(fp)) | ||
222 | return 1; | ||
217 | // } | 223 | // } |
218 | } | 224 | } |
219 | 225 | ||
226 | if (fclose(stdout)) | ||
227 | return 1; | ||
228 | if (rename(tmp1, argv[1])) | ||
229 | return 1; | ||
230 | if (rename(tmp2, argv[2])) | ||
231 | return 1; | ||
220 | return 0; | 232 | return 0; |
221 | } | 233 | } |
diff --git a/applets/install.sh b/applets/install.sh index c75a78e9d..9aede0f53 100755 --- a/applets/install.sh +++ b/applets/install.sh | |||
@@ -5,7 +5,9 @@ export LC_CTYPE=POSIX | |||
5 | 5 | ||
6 | prefix=$1 | 6 | prefix=$1 |
7 | if [ -z "$prefix" ]; then | 7 | if [ -z "$prefix" ]; then |
8 | echo "usage: applets/install.sh DESTINATION [--symlinks/--hardlinks/--binaries/--scriptwrapper]" | 8 | echo "usage: applets/install.sh DESTINATION TYPE [OPTS ...]" |
9 | echo " TYPE is one of: --symlinks --hardlinks --binaries --scriptwrapper --none" | ||
10 | echo " OPTS is one or more of: --cleanup --noclobber" | ||
9 | exit 1 | 11 | exit 1 |
10 | fi | 12 | fi |
11 | shift # Keep only remaining options | 13 | shift # Keep only remaining options |
@@ -32,7 +34,7 @@ while [ ${#} -gt 0 ]; do | |||
32 | --sw-sh-sym) scriptwrapper="y"; linkopts="-fs";; | 34 | --sw-sh-sym) scriptwrapper="y"; linkopts="-fs";; |
33 | --cleanup) cleanup="1";; | 35 | --cleanup) cleanup="1";; |
34 | --noclobber) noclobber="1";; | 36 | --noclobber) noclobber="1";; |
35 | "") h="";; | 37 | --none) h="";; |
36 | *) echo "Unknown install option: $1"; exit 1;; | 38 | *) echo "Unknown install option: $1"; exit 1;; |
37 | esac | 39 | esac |
38 | shift | 40 | shift |
diff --git a/archival/libarchive/decompress_unlzma.c b/archival/libarchive/decompress_unlzma.c index 80a453806..446319e7b 100644 --- a/archival/libarchive/decompress_unlzma.c +++ b/archival/libarchive/decompress_unlzma.c | |||
@@ -224,6 +224,7 @@ unpack_lzma_stream(transformer_state_t *xstate) | |||
224 | rc_t *rc; | 224 | rc_t *rc; |
225 | int i; | 225 | int i; |
226 | uint8_t *buffer; | 226 | uint8_t *buffer; |
227 | uint32_t buffer_size; | ||
227 | uint8_t previous_byte = 0; | 228 | uint8_t previous_byte = 0; |
228 | size_t buffer_pos = 0, global_pos = 0; | 229 | size_t buffer_pos = 0, global_pos = 0; |
229 | int len = 0; | 230 | int len = 0; |
@@ -253,7 +254,8 @@ unpack_lzma_stream(transformer_state_t *xstate) | |||
253 | if (header.dict_size == 0) | 254 | if (header.dict_size == 0) |
254 | header.dict_size++; | 255 | header.dict_size++; |
255 | 256 | ||
256 | buffer = xmalloc(MIN(header.dst_size, header.dict_size)); | 257 | buffer_size = MIN(header.dst_size, header.dict_size); |
258 | buffer = xmalloc(buffer_size); | ||
257 | 259 | ||
258 | { | 260 | { |
259 | int num_probs; | 261 | int num_probs; |
@@ -464,7 +466,10 @@ unpack_lzma_stream(transformer_state_t *xstate) | |||
464 | if ((int32_t)pos < 0) { | 466 | if ((int32_t)pos < 0) { |
465 | pos += header.dict_size; | 467 | pos += header.dict_size; |
466 | /* bug 10436 has an example file where this triggers: */ | 468 | /* bug 10436 has an example file where this triggers: */ |
467 | if ((int32_t)pos < 0) | 469 | //if ((int32_t)pos < 0) |
470 | // goto bad; | ||
471 | /* more stringent test (see unzip_bad_lzma_1.zip): */ | ||
472 | if (pos >= buffer_size) | ||
468 | goto bad; | 473 | goto bad; |
469 | } | 474 | } |
470 | previous_byte = buffer[pos]; | 475 | previous_byte = buffer[pos]; |
@@ -493,6 +498,12 @@ unpack_lzma_stream(transformer_state_t *xstate) | |||
493 | IF_DESKTOP(total_written += buffer_pos;) | 498 | IF_DESKTOP(total_written += buffer_pos;) |
494 | if (transformer_write(xstate, buffer, buffer_pos) != (ssize_t)buffer_pos) { | 499 | if (transformer_write(xstate, buffer, buffer_pos) != (ssize_t)buffer_pos) { |
495 | bad: | 500 | bad: |
501 | /* One of our users, bbunpack(), expects _us_ to emit | ||
502 | * the error message (since it's the best place to give | ||
503 | * potentially more detailed information). | ||
504 | * Do not fail silently. | ||
505 | */ | ||
506 | bb_error_msg("corrupted data"); | ||
496 | total_written = -1; /* failure */ | 507 | total_written = -1; /* failure */ |
497 | } | 508 | } |
498 | rc_free(rc); | 509 | rc_free(rc); |
diff --git a/archival/libarchive/get_header_ar.c b/archival/libarchive/get_header_ar.c index 93e071c9f..adcde46d5 100644 --- a/archival/libarchive/get_header_ar.c +++ b/archival/libarchive/get_header_ar.c | |||
@@ -34,10 +34,6 @@ char FAST_FUNC get_header_ar(archive_handle_t *archive_handle) | |||
34 | char raw[60]; | 34 | char raw[60]; |
35 | struct ar_header formatted; | 35 | struct ar_header formatted; |
36 | } ar; | 36 | } ar; |
37 | #if ENABLE_FEATURE_AR_LONG_FILENAMES | ||
38 | static char *ar_long_names; | ||
39 | static unsigned ar_long_name_size; | ||
40 | #endif | ||
41 | 37 | ||
42 | /* dont use xread as we want to handle the error ourself */ | 38 | /* dont use xread as we want to handle the error ourself */ |
43 | if (read(archive_handle->src_fd, ar.raw, 60) != 60) { | 39 | if (read(archive_handle->src_fd, ar.raw, 60) != 60) { |
@@ -81,10 +77,10 @@ char FAST_FUNC get_header_ar(archive_handle_t *archive_handle) | |||
81 | * stores long filename for multiple entries, they are stored | 77 | * stores long filename for multiple entries, they are stored |
82 | * in static variable long_names for use in future entries | 78 | * in static variable long_names for use in future entries |
83 | */ | 79 | */ |
84 | ar_long_name_size = size; | 80 | archive_handle->ar__long_name_size = size; |
85 | free(ar_long_names); | 81 | free(archive_handle->ar__long_names); |
86 | ar_long_names = xzalloc(size + 1); | 82 | archive_handle->ar__long_names = xzalloc(size + 1); |
87 | xread(archive_handle->src_fd, ar_long_names, size); | 83 | xread(archive_handle->src_fd, archive_handle->ar__long_names, size); |
88 | archive_handle->offset += size; | 84 | archive_handle->offset += size; |
89 | /* Return next header */ | 85 | /* Return next header */ |
90 | return get_header_ar(archive_handle); | 86 | return get_header_ar(archive_handle); |
@@ -107,13 +103,13 @@ char FAST_FUNC get_header_ar(archive_handle_t *archive_handle) | |||
107 | unsigned long_offset; | 103 | unsigned long_offset; |
108 | 104 | ||
109 | /* The number after the '/' indicates the offset in the ar data section | 105 | /* The number after the '/' indicates the offset in the ar data section |
110 | * (saved in ar_long_names) that contains the real filename */ | 106 | * (saved in ar__long_names) that contains the real filename */ |
111 | long_offset = read_num(&ar.formatted.name[1], 10, | 107 | long_offset = read_num(&ar.formatted.name[1], 10, |
112 | sizeof(ar.formatted.name) - 1); | 108 | sizeof(ar.formatted.name) - 1); |
113 | if (long_offset >= ar_long_name_size) { | 109 | if (long_offset >= archive_handle->ar__long_name_size) { |
114 | bb_error_msg_and_die("can't resolve long filename"); | 110 | bb_error_msg_and_die("can't resolve long filename"); |
115 | } | 111 | } |
116 | typed->name = xstrdup(ar_long_names + long_offset); | 112 | typed->name = xstrdup(archive_handle->ar__long_names + long_offset); |
117 | } else | 113 | } else |
118 | #endif | 114 | #endif |
119 | { | 115 | { |
@@ -127,8 +123,10 @@ char FAST_FUNC get_header_ar(archive_handle_t *archive_handle) | |||
127 | archive_handle->action_header(typed); | 123 | archive_handle->action_header(typed); |
128 | #if ENABLE_DPKG || ENABLE_DPKG_DEB | 124 | #if ENABLE_DPKG || ENABLE_DPKG_DEB |
129 | if (archive_handle->dpkg__sub_archive) { | 125 | if (archive_handle->dpkg__sub_archive) { |
130 | while (archive_handle->dpkg__action_data_subarchive(archive_handle->dpkg__sub_archive) == EXIT_SUCCESS) | 126 | struct archive_handle_t *sa = archive_handle->dpkg__sub_archive; |
127 | while (archive_handle->dpkg__action_data_subarchive(sa) == EXIT_SUCCESS) | ||
131 | continue; | 128 | continue; |
129 | create_symlinks_from_list(sa->symlink_placeholders); | ||
132 | } else | 130 | } else |
133 | #endif | 131 | #endif |
134 | archive_handle->action_data(archive_handle); | 132 | archive_handle->action_data(archive_handle); |
diff --git a/console-tools/setlogcons.c b/console-tools/setlogcons.c index 6778a4d2b..0fad6600a 100644 --- a/console-tools/setlogcons.c +++ b/console-tools/setlogcons.c | |||
@@ -41,6 +41,7 @@ | |||
41 | int setlogcons_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | 41 | int setlogcons_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
42 | int setlogcons_main(int argc UNUSED_PARAM, char **argv) | 42 | int setlogcons_main(int argc UNUSED_PARAM, char **argv) |
43 | { | 43 | { |
44 | char *devname; | ||
44 | struct { | 45 | struct { |
45 | char fn; | 46 | char fn; |
46 | char subarg; | 47 | char subarg; |
@@ -52,7 +53,14 @@ int setlogcons_main(int argc UNUSED_PARAM, char **argv) | |||
52 | if (argv[1]) | 53 | if (argv[1]) |
53 | arg.subarg = xatou_range(argv[1], 0, 63); | 54 | arg.subarg = xatou_range(argv[1], 0, 63); |
54 | 55 | ||
55 | xioctl(xopen(VC_1, O_RDONLY), TIOCLINUX, &arg); | 56 | /* Can just call it on "/dev/tty1" always, but... |
57 | * in my testing, inactive (never opened) VTs are not | ||
58 | * redirected to, despite ioctl not failing. | ||
59 | * | ||
60 | * By using "/dev/ttyN", ensure it is activated. | ||
61 | */ | ||
62 | devname = xasprintf("/dev/tty%u", arg.subarg); | ||
63 | xioctl(xopen(devname, O_RDONLY), TIOCLINUX, &arg); | ||
56 | 64 | ||
57 | return EXIT_SUCCESS; | 65 | return EXIT_SUCCESS; |
58 | } | 66 | } |
diff --git a/coreutils/cat.c b/coreutils/cat.c index 5f02233ca..fb735f994 100644 --- a/coreutils/cat.c +++ b/coreutils/cat.c | |||
@@ -112,10 +112,10 @@ static int catv(unsigned opts, char **argv) | |||
112 | int retval = EXIT_SUCCESS; | 112 | int retval = EXIT_SUCCESS; |
113 | int fd; | 113 | int fd; |
114 | #if ENABLE_FEATURE_CATN | 114 | #if ENABLE_FEATURE_CATN |
115 | unsigned lineno = 0; | 115 | bool eol_seen = (opts & (CAT_OPT_n|CAT_OPT_b)); |
116 | unsigned eol_char = (opts & (CAT_OPT_n|CAT_OPT_b)) ? '\n' : 0x100; | 116 | unsigned eol_char = (eol_seen ? '\n' : 0x100); |
117 | unsigned skip_num_on = (opts & CAT_OPT_b) ? '\n' : 0x100; | 117 | unsigned skip_num_on = (opts & CAT_OPT_b) ? '\n' : 0x100; |
118 | bool eol_seen = 1; | 118 | unsigned lineno = 0; |
119 | #endif | 119 | #endif |
120 | 120 | ||
121 | BUILD_BUG_ON(CAT_OPT_e != VISIBLE_ENDLINE); | 121 | BUILD_BUG_ON(CAT_OPT_e != VISIBLE_ENDLINE); |
diff --git a/coreutils/test.c b/coreutils/test.c index 824ce3b5a..ddb66ddce 100644 --- a/coreutils/test.c +++ b/coreutils/test.c | |||
@@ -313,6 +313,9 @@ static const struct operator_t ops_table[] = { | |||
313 | { /* "-L" */ FILSYM , UNOP }, | 313 | { /* "-L" */ FILSYM , UNOP }, |
314 | { /* "-S" */ FILSOCK , UNOP }, | 314 | { /* "-S" */ FILSOCK , UNOP }, |
315 | { /* "=" */ STREQ , BINOP }, | 315 | { /* "=" */ STREQ , BINOP }, |
316 | /* "==" is bashism, http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html | ||
317 | * lists only "=" as comparison operator. | ||
318 | */ | ||
316 | { /* "==" */ STREQ , BINOP }, | 319 | { /* "==" */ STREQ , BINOP }, |
317 | { /* "!=" */ STRNE , BINOP }, | 320 | { /* "!=" */ STRNE , BINOP }, |
318 | { /* "<" */ STRLT , BINOP }, | 321 | { /* "<" */ STRLT , BINOP }, |
@@ -357,6 +360,7 @@ static const char ops_texts[] ALIGN1 = | |||
357 | "-L" "\0" | 360 | "-L" "\0" |
358 | "-S" "\0" | 361 | "-S" "\0" |
359 | "=" "\0" | 362 | "=" "\0" |
363 | /* "==" is bashism */ | ||
360 | "==" "\0" | 364 | "==" "\0" |
361 | "!=" "\0" | 365 | "!=" "\0" |
362 | "<" "\0" | 366 | "<" "\0" |
diff --git a/debianutils/start_stop_daemon.c b/debianutils/start_stop_daemon.c index fa77a7e00..43b6fca26 100644 --- a/debianutils/start_stop_daemon.c +++ b/debianutils/start_stop_daemon.c | |||
@@ -157,6 +157,9 @@ struct globals { | |||
157 | unsigned execname_sizeof; | 157 | unsigned execname_sizeof; |
158 | int user_id; | 158 | int user_id; |
159 | smallint signal_nr; | 159 | smallint signal_nr; |
160 | #ifdef OLDER_VERSION_OF_X | ||
161 | struct stat execstat; | ||
162 | #endif | ||
160 | } FIX_ALIASING; | 163 | } FIX_ALIASING; |
161 | #define G (*(struct globals*)bb_common_bufsiz1) | 164 | #define G (*(struct globals*)bb_common_bufsiz1) |
162 | #define userspec (G.userspec ) | 165 | #define userspec (G.userspec ) |
@@ -184,13 +187,12 @@ static int pid_is_exec(pid_t pid) | |||
184 | sprintf(buf, "/proc/%u/exe", (unsigned)pid); | 187 | sprintf(buf, "/proc/%u/exe", (unsigned)pid); |
185 | if (stat(buf, &st) < 0) | 188 | if (stat(buf, &st) < 0) |
186 | return 0; | 189 | return 0; |
187 | if (st.st_dev == execstat.st_dev | 190 | if (st.st_dev == G.execstat.st_dev |
188 | && st.st_ino == execstat.st_ino) | 191 | && st.st_ino == G.execstat.st_ino) |
189 | return 1; | 192 | return 1; |
190 | return 0; | 193 | return 0; |
191 | } | 194 | } |
192 | #endif | 195 | #else |
193 | |||
194 | static int pid_is_exec(pid_t pid) | 196 | static int pid_is_exec(pid_t pid) |
195 | { | 197 | { |
196 | ssize_t bytes; | 198 | ssize_t bytes; |
@@ -214,6 +216,7 @@ static int pid_is_exec(pid_t pid) | |||
214 | } | 216 | } |
215 | return 0; | 217 | return 0; |
216 | } | 218 | } |
219 | #endif | ||
217 | 220 | ||
218 | static int pid_is_name(pid_t pid) | 221 | static int pid_is_name(pid_t pid) |
219 | { | 222 | { |
@@ -408,9 +411,6 @@ int start_stop_daemon_main(int argc UNUSED_PARAM, char **argv) | |||
408 | char *signame; | 411 | char *signame; |
409 | char *startas; | 412 | char *startas; |
410 | char *chuid; | 413 | char *chuid; |
411 | #ifdef OLDER_VERSION_OF_X | ||
412 | struct stat execstat; | ||
413 | #endif | ||
414 | #if ENABLE_FEATURE_START_STOP_DAEMON_FANCY | 414 | #if ENABLE_FEATURE_START_STOP_DAEMON_FANCY |
415 | // char *retry_arg = NULL; | 415 | // char *retry_arg = NULL; |
416 | // int retries = -1; | 416 | // int retries = -1; |
@@ -479,7 +479,7 @@ int start_stop_daemon_main(int argc UNUSED_PARAM, char **argv) | |||
479 | 479 | ||
480 | #ifdef OLDER_VERSION_OF_X | 480 | #ifdef OLDER_VERSION_OF_X |
481 | if (execname) | 481 | if (execname) |
482 | xstat(execname, &execstat); | 482 | xstat(execname, &G.execstat); |
483 | #endif | 483 | #endif |
484 | 484 | ||
485 | *--argv = startas; | 485 | *--argv = startas; |
diff --git a/editors/awk.c b/editors/awk.c index 9d74d931d..b9c8d2130 100644 --- a/editors/awk.c +++ b/editors/awk.c | |||
@@ -598,6 +598,7 @@ static const char EMSG_NOT_ARRAY[] ALIGN1 = "Not an array"; | |||
598 | static const char EMSG_POSSIBLE_ERROR[] ALIGN1 = "Possible syntax error"; | 598 | static const char EMSG_POSSIBLE_ERROR[] ALIGN1 = "Possible syntax error"; |
599 | static const char EMSG_UNDEF_FUNC[] ALIGN1 = "Call to undefined function"; | 599 | static const char EMSG_UNDEF_FUNC[] ALIGN1 = "Call to undefined function"; |
600 | static const char EMSG_NO_MATH[] ALIGN1 = "Math support is not compiled in"; | 600 | static const char EMSG_NO_MATH[] ALIGN1 = "Math support is not compiled in"; |
601 | static const char EMSG_NEGATIVE_FIELD[] ALIGN1 = "Access to negative field"; | ||
601 | 602 | ||
602 | static void zero_out_var(var *vp) | 603 | static void zero_out_var(var *vp) |
603 | { | 604 | { |
@@ -2953,6 +2954,8 @@ static var *evaluate(node *op, var *res) | |||
2953 | 2954 | ||
2954 | case XC( OC_FIELD ): { | 2955 | case XC( OC_FIELD ): { |
2955 | int i = (int)getvar_i(R.v); | 2956 | int i = (int)getvar_i(R.v); |
2957 | if (i < 0) | ||
2958 | syntax_error(EMSG_NEGATIVE_FIELD); | ||
2956 | if (i == 0) { | 2959 | if (i == 0) { |
2957 | res = intvar[F0]; | 2960 | res = intvar[F0]; |
2958 | } else { | 2961 | } else { |
diff --git a/editors/patch.c b/editors/patch.c index a51b7a502..eca6bc5f6 100644 --- a/editors/patch.c +++ b/editors/patch.c | |||
@@ -15,7 +15,6 @@ | |||
15 | * -D define wrap #ifdef and #ifndef around changes | 15 | * -D define wrap #ifdef and #ifndef around changes |
16 | * -o outfile output here instead of in place | 16 | * -o outfile output here instead of in place |
17 | * -r rejectfile write rejected hunks to this file | 17 | * -r rejectfile write rejected hunks to this file |
18 | * --dry-run (regression!) | ||
19 | * | 18 | * |
20 | * -f force (no questions asked) | 19 | * -f force (no questions asked) |
21 | * -F fuzz (number, default 2) | 20 | * -F fuzz (number, default 2) |
@@ -34,23 +33,15 @@ | |||
34 | //usage:#define patch_trivial_usage | 33 | //usage:#define patch_trivial_usage |
35 | //usage: "[OPTIONS] [ORIGFILE [PATCHFILE]]" | 34 | //usage: "[OPTIONS] [ORIGFILE [PATCHFILE]]" |
36 | //usage:#define patch_full_usage "\n\n" | 35 | //usage:#define patch_full_usage "\n\n" |
37 | //usage: IF_LONG_OPTS( | ||
38 | //usage: " -p,--strip N Strip N leading components from file names" | ||
39 | //usage: "\n -i,--input DIFF Read DIFF instead of stdin" | ||
40 | //usage: "\n -R,--reverse Reverse patch" | ||
41 | //usage: "\n -N,--forward Ignore already applied patches" | ||
42 | /*usage: "\n --dry-run Don't actually change files" - TODO */ | ||
43 | //usage: "\n -E,--remove-empty-files Remove output files if they become empty" | ||
44 | //usage: ) | ||
45 | //usage: IF_NOT_LONG_OPTS( | ||
46 | //usage: " -p N Strip N leading components from file names" | 36 | //usage: " -p N Strip N leading components from file names" |
47 | //usage: "\n -i DIFF Read DIFF instead of stdin" | 37 | //usage: "\n -i DIFF Read DIFF instead of stdin" |
48 | //usage: "\n -R Reverse patch" | 38 | //usage: "\n -R Reverse patch" |
49 | //usage: "\n -N Ignore already applied patches" | 39 | //usage: "\n -N Ignore already applied patches" |
50 | //usage: "\n -E Remove output files if they become empty" | 40 | //usage: "\n -E Remove output files if they become empty" |
41 | //usage: IF_LONG_OPTS( | ||
42 | //usage: "\n --dry-run Don't actually change files" | ||
51 | //usage: ) | 43 | //usage: ) |
52 | /* -u "interpret as unified diff" is supported but not documented: this info is not useful for --help */ | 44 | /* -u "interpret as unified diff" is supported but not documented: this info is not useful for --help */ |
53 | /* -x "debug" is supported but does nothing */ | ||
54 | //usage: | 45 | //usage: |
55 | //usage:#define patch_example_usage | 46 | //usage:#define patch_example_usage |
56 | //usage: "$ patch -p1 < example.diff\n" | 47 | //usage: "$ patch -p1 < example.diff\n" |
@@ -58,6 +49,7 @@ | |||
58 | 49 | ||
59 | #include "libbb.h" | 50 | #include "libbb.h" |
60 | 51 | ||
52 | #define PATCH_DEBUG 0 | ||
61 | 53 | ||
62 | // libbb candidate? | 54 | // libbb candidate? |
63 | 55 | ||
@@ -122,16 +114,18 @@ struct globals { | |||
122 | } while (0) | 114 | } while (0) |
123 | 115 | ||
124 | 116 | ||
125 | #define FLAG_STR "Rup:i:NEx" | 117 | #define FLAG_STR "Rup:i:NEfg" |
126 | /* FLAG_REVERSE must be == 1! Code uses this fact. */ | 118 | /* FLAG_REVERSE must be == 1! Code uses this fact. */ |
127 | #define FLAG_REVERSE (1 << 0) | 119 | #define FLAG_REVERSE (1 << 0) |
128 | #define FLAG_u (1 << 1) | 120 | #define FLAG_u (1 << 1) |
129 | #define FLAG_PATHLEN (1 << 2) | 121 | #define FLAG_PATHLEN (1 << 2) |
130 | #define FLAG_INPUT (1 << 3) | 122 | #define FLAG_INPUT (1 << 3) |
131 | #define FLAG_IGNORE (1 << 4) | 123 | #define FLAG_IGNORE (1 << 4) |
132 | #define FLAG_RMEMPTY (1 << 5) | 124 | #define FLAG_RMEMPTY (1 << 5) |
133 | /* Enable this bit and use -x for debug output: */ | 125 | #define FLAG_f_unused (1 << 6) |
134 | #define FLAG_DEBUG (0 << 6) | 126 | #define FLAG_g_unused (1 << 7) |
127 | #define FLAG_dry_run ((1 << 8) * ENABLE_LONG_OPTS) | ||
128 | |||
135 | 129 | ||
136 | // Dispose of a line of input, either by writing it out or discarding it. | 130 | // Dispose of a line of input, either by writing it out or discarding it. |
137 | 131 | ||
@@ -140,8 +134,6 @@ struct globals { | |||
140 | // state = 3: write whole line to fileout | 134 | // state = 3: write whole line to fileout |
141 | // state > 3: write line+1 to fileout when *line != state | 135 | // state > 3: write line+1 to fileout when *line != state |
142 | 136 | ||
143 | #define PATCH_DEBUG (option_mask32 & FLAG_DEBUG) | ||
144 | |||
145 | static void do_line(void *data) | 137 | static void do_line(void *data) |
146 | { | 138 | { |
147 | struct double_list *dlist = data; | 139 | struct double_list *dlist = data; |
@@ -168,12 +160,14 @@ static void finish_oldfile(void) | |||
168 | } | 160 | } |
169 | xclose(TT.fileout); | 161 | xclose(TT.fileout); |
170 | 162 | ||
171 | temp = xstrdup(TT.tempname); | 163 | if (!ENABLE_LONG_OPTS || TT.tempname[0]) { /* not --dry-run? */ |
172 | temp[strlen(temp) - 6] = '\0'; | 164 | temp = xstrdup(TT.tempname); |
173 | rename(TT.tempname, temp); | 165 | temp[strlen(temp) - 6] = '\0'; |
174 | free(temp); | 166 | rename(TT.tempname, temp); |
167 | free(temp); | ||
168 | free(TT.tempname); | ||
169 | } | ||
175 | 170 | ||
176 | free(TT.tempname); | ||
177 | TT.tempname = NULL; | 171 | TT.tempname = NULL; |
178 | } | 172 | } |
179 | TT.fileout = TT.filein = -1; | 173 | TT.fileout = TT.filein = -1; |
@@ -197,8 +191,10 @@ static void fail_hunk(void) | |||
197 | // Abort the copy and delete the temporary file. | 191 | // Abort the copy and delete the temporary file. |
198 | close(TT.filein); | 192 | close(TT.filein); |
199 | close(TT.fileout); | 193 | close(TT.fileout); |
200 | unlink(TT.tempname); | 194 | if (!ENABLE_LONG_OPTS || TT.tempname[0]) { /* not --dry-run? */ |
201 | free(TT.tempname); | 195 | unlink(TT.tempname); |
196 | free(TT.tempname); | ||
197 | } | ||
202 | TT.tempname = NULL; | 198 | TT.tempname = NULL; |
203 | 199 | ||
204 | TT.state = 0; | 200 | TT.state = 0; |
@@ -239,6 +235,7 @@ static int apply_one_hunk(void) | |||
239 | plist = TT.current_hunk; | 235 | plist = TT.current_hunk; |
240 | buf = NULL; | 236 | buf = NULL; |
241 | if (reverse ? TT.oldlen : TT.newlen) for (;;) { | 237 | if (reverse ? TT.oldlen : TT.newlen) for (;;) { |
238 | //FIXME: this performs 1-byte reads: | ||
242 | char *data = xmalloc_reads(TT.filein, NULL); | 239 | char *data = xmalloc_reads(TT.filein, NULL); |
243 | 240 | ||
244 | TT.linenum++; | 241 | TT.linenum++; |
@@ -369,9 +366,45 @@ int patch_main(int argc UNUSED_PARAM, char **argv) | |||
369 | long oldlen = oldlen; /* for compiler */ | 366 | long oldlen = oldlen; /* for compiler */ |
370 | long newlen = newlen; /* for compiler */ | 367 | long newlen = newlen; /* for compiler */ |
371 | 368 | ||
369 | #if ENABLE_LONG_OPTS | ||
370 | static const char patch_longopts[] ALIGN1 = | ||
371 | "reverse\0" No_argument "R" | ||
372 | "unified\0" No_argument "u" | ||
373 | "strip\0" Required_argument "p" | ||
374 | "input\0" Required_argument "i" | ||
375 | "forward\0" No_argument "N" | ||
376 | # if ENABLE_DESKTOP | ||
377 | "remove-empty-files\0" No_argument "E" /*ignored*/ | ||
378 | /* "debug" Required_argument "x" */ | ||
379 | # endif | ||
380 | /* "Assume user knows what [s]he is doing, do not ask any questions": */ | ||
381 | "force\0" No_argument "f" /*ignored*/ | ||
382 | # if ENABLE_DESKTOP | ||
383 | /* "Controls actions when a file is under RCS or SCCS control, | ||
384 | * and does not exist or is read-only and matches the default version, | ||
385 | * or when a file is under ClearCase control and does not exist..." | ||
386 | * IOW: rather obscure option. | ||
387 | * But Gentoo's portage does use -g0 | ||
388 | */ | ||
389 | "get\0" Required_argument "g" /*ignored*/ | ||
390 | # endif | ||
391 | "dry-run\0" No_argument "\xfd" | ||
392 | # if ENABLE_DESKTOP | ||
393 | "backup-if-mismatch\0" No_argument "\xfe" /*ignored*/ | ||
394 | "no-backup-if-mismatch\0" No_argument "\xff" /*ignored*/ | ||
395 | # endif | ||
396 | ; | ||
397 | #endif | ||
398 | |||
372 | INIT_TT(); | 399 | INIT_TT(); |
373 | 400 | ||
401 | #if ENABLE_LONG_OPTS | ||
402 | opts = getopt32long(argv, FLAG_STR, patch_longopts, &opt_p, &opt_i); | ||
403 | #else | ||
374 | opts = getopt32(argv, FLAG_STR, &opt_p, &opt_i); | 404 | opts = getopt32(argv, FLAG_STR, &opt_p, &opt_i); |
405 | #endif | ||
406 | //bb_error_msg_and_die("opts:%x", opts); | ||
407 | |||
375 | argv += optind; | 408 | argv += optind; |
376 | reverse = opts & FLAG_REVERSE; | 409 | reverse = opts & FLAG_REVERSE; |
377 | TT.prefix = (opts & FLAG_PATHLEN) ? xatoi(opt_p) : 0; // can be negative! | 410 | TT.prefix = (opts & FLAG_PATHLEN) ? xatoi(opt_p) : 0; // can be negative! |
@@ -517,10 +550,12 @@ int patch_main(int argc UNUSED_PARAM, char **argv) | |||
517 | if (option_mask32 & FLAG_RMEMPTY) { | 550 | if (option_mask32 & FLAG_RMEMPTY) { |
518 | // If flag -E or --remove-empty-files is set | 551 | // If flag -E or --remove-empty-files is set |
519 | printf("removing %s\n", name); | 552 | printf("removing %s\n", name); |
520 | xunlink(name); | 553 | if (!(opts & FLAG_dry_run)) |
554 | xunlink(name); | ||
521 | } else { | 555 | } else { |
522 | printf("patching file %s\n", name); | 556 | printf("patching file %s\n", name); |
523 | xclose(xopen(name, O_WRONLY | O_TRUNC)); | 557 | if (!(opts & FLAG_dry_run)) |
558 | xclose(xopen(name, O_WRONLY | O_TRUNC)); | ||
524 | } | 559 | } |
525 | // If we've got a file to open, do so. | 560 | // If we've got a file to open, do so. |
526 | } else if (!(option_mask32 & FLAG_PATHLEN) || i <= TT.prefix) { | 561 | } else if (!(option_mask32 & FLAG_PATHLEN) || i <= TT.prefix) { |
@@ -529,24 +564,32 @@ int patch_main(int argc UNUSED_PARAM, char **argv) | |||
529 | // If the old file was null, we're creating a new one. | 564 | // If the old file was null, we're creating a new one. |
530 | if (strcmp(oldname, "/dev/null") == 0 || !oldsum) { | 565 | if (strcmp(oldname, "/dev/null") == 0 || !oldsum) { |
531 | printf("creating %s\n", name); | 566 | printf("creating %s\n", name); |
532 | s = strrchr(name, '/'); | 567 | if (!(opts & FLAG_dry_run)) { |
533 | if (s) { | 568 | s = strrchr(name, '/'); |
534 | *s = 0; | 569 | if (s) { |
535 | bb_make_directory(name, -1, FILEUTILS_RECUR); | 570 | *s = '\0'; |
536 | *s = '/'; | 571 | bb_make_directory(name, -1, FILEUTILS_RECUR); |
572 | *s = '/'; | ||
573 | } | ||
574 | TT.filein = xopen(name, O_CREAT|O_EXCL|O_RDWR); | ||
575 | } else { | ||
576 | TT.filein = xopen("/dev/null", O_RDONLY); | ||
537 | } | 577 | } |
538 | TT.filein = xopen(name, O_CREAT|O_EXCL|O_RDWR); | ||
539 | } else { | 578 | } else { |
540 | printf("patching file %s\n", name); | 579 | printf("patching file %s\n", name); |
541 | TT.filein = xopen(name, O_RDONLY); | 580 | TT.filein = xopen(name, O_RDONLY); |
542 | } | 581 | } |
543 | 582 | ||
544 | TT.tempname = xasprintf("%sXXXXXX", name); | 583 | if (!(opts & FLAG_dry_run)) { |
545 | TT.fileout = xmkstemp(TT.tempname); | 584 | TT.tempname = xasprintf("%sXXXXXX", name); |
546 | // Set permissions of output file | 585 | TT.fileout = xmkstemp(TT.tempname); |
547 | fstat(TT.filein, &statbuf); | 586 | // Set permissions of output file |
548 | fchmod(TT.fileout, statbuf.st_mode); | 587 | fstat(TT.filein, &statbuf); |
549 | 588 | fchmod(TT.fileout, statbuf.st_mode); | |
589 | } else { | ||
590 | TT.tempname = (char*)""; | ||
591 | TT.fileout = xopen("/dev/null", O_WRONLY); | ||
592 | } | ||
550 | TT.linenum = 0; | 593 | TT.linenum = 0; |
551 | TT.hunknum = 0; | 594 | TT.hunknum = 0; |
552 | } | 595 | } |
diff --git a/examples/shutdown-1.0/README b/examples/shutdown-1.0/README new file mode 100644 index 000000000..40fe0ebed --- /dev/null +++ b/examples/shutdown-1.0/README | |||
@@ -0,0 +1,30 @@ | |||
1 | # Replaces traditional overdesigned shutdown mechanism. | ||
2 | # | ||
3 | # No communication with init is necessary: | ||
4 | # just ask all processes to exit. | ||
5 | # Then unmount everything. Then reboot or power off. | ||
6 | |||
7 | # Install /sbin/ symlinks named halt, reboot, poweroff | ||
8 | # (and also possibly shutdown) to e.g. | ||
9 | # /app/shutdown-1.0/script/shutdown: | ||
10 | # | ||
11 | ln -s /app/shutdown-1.0/script/shutdown /sbin/halt | ||
12 | ln -s /app/shutdown-1.0/script/shutdown /sbin/reboot | ||
13 | ln -s /app/shutdown-1.0/script/shutdown /sbin/poweroff | ||
14 | # | ||
15 | # shutdown spawns do_shutdown in new session, redirected to /dev/null, | ||
16 | # tells user that shutdown is in progress, and sleeps | ||
17 | # (for cosmetic reasons: do not confuse user by letting him | ||
18 | # type more commands in this terminal). | ||
19 | # | ||
20 | # do_shutdown tries to switch to a VT console. | ||
21 | # Then, (only if -r) it spawns a hardshutdown child, to reboot | ||
22 | # unconditionally in 30 seconds if something later goes seriously bad. | ||
23 | # Then it runs stop_tasks, writing to /var/log/reboot/YYYYMMDDhhmmss.log, | ||
24 | # then it runs stop_storage. | ||
25 | # Then it commands kernel to halt/reboot/poweroff, if requested. | ||
26 | # Then it sleeps forever. | ||
27 | # | ||
28 | # Build the hardshutdown binary: | ||
29 | # | ||
30 | cd script && ./hardshutdown.make.sh | ||
diff --git a/examples/shutdown-1.0/script/do_shutdown b/examples/shutdown-1.0/script/do_shutdown new file mode 100755 index 000000000..0c1e0dce8 --- /dev/null +++ b/examples/shutdown-1.0/script/do_shutdown | |||
@@ -0,0 +1,54 @@ | |||
1 | #!/bin/sh | ||
2 | # We are called with stdin/out/err = /dev/null | ||
3 | |||
4 | resetgracetime=60 | ||
5 | |||
6 | logfile="/var/log/reboot/`date '+%Y%m%d%H%M%S'`.log" | ||
7 | mkdir -p /var/log/reboot | ||
8 | |||
9 | PATH=/sbin:/bin | ||
10 | |||
11 | say() { | ||
12 | printf "\r%s\n\r" "$*" | ||
13 | } | ||
14 | |||
15 | # Since there is a potential for various fuckups during umount, | ||
16 | # we start delayed hard reboot here which will forcibly | ||
17 | # reboot hung box in a remote datacenter a thousand miles away ;) | ||
18 | if test "$1" = "-r"; then | ||
19 | ./hardshutdown -r "$resetgracetime" & | ||
20 | fi | ||
21 | |||
22 | # Now, (try to) switch away from X and open a console. I've seen reboots | ||
23 | # hung on open("/dev/console"), therefore we do it _after_ hardshutdown | ||
24 | exec >/dev/console 2>&1 | ||
25 | |||
26 | if test "$1" = "-r"; then | ||
27 | say "* `date '+%H:%M:%S'` Scheduled hard reboot in $resetgracetime seconds" | ||
28 | fi | ||
29 | |||
30 | say "* `date '+%H:%M:%S'` Stopping tasks (see /var/log/reboot/* files)" | ||
31 | # log reboot event to file. %Y%m%d%H%M%S: YYYYMMDDHHMMSS | ||
32 | ./stop_tasks >"$logfile" 2>&1 | ||
33 | |||
34 | # Dying X tends to leave us at semi-random vt. Try to fix that, | ||
35 | # but if it doesn't work, proceed anyway. | ||
36 | exec >/dev/null 2>&1 | ||
37 | chvt 1 & sleep 1 | ||
38 | exec >/dev/console 2>&1 | ||
39 | |||
40 | command -v ctrlaltdel >/dev/null && { | ||
41 | say "* `date '+%H:%M:%S'` Setting Ctrl-Alt-Del to 'hard'" | ||
42 | ctrlaltdel hard | ||
43 | } | ||
44 | |||
45 | say "* `date '+%H:%M:%S'` Stopping storage devices" | ||
46 | # we can't log this: we are about to unmount everything! | ||
47 | ./stop_storage "$@" | ||
48 | |||
49 | # If we have cmdline params, start hardshutdown with them | ||
50 | test "$*" && ./hardshutdown "$@" | ||
51 | |||
52 | # Just sleep endlessly... | ||
53 | say "* `date '+%H:%M:%S'` You may now power off or press Ctrl-Alt-Del to reboot" | ||
54 | while true; do sleep 32000; done | ||
diff --git a/examples/shutdown-1.0/script/hardshutdown.c b/examples/shutdown-1.0/script/hardshutdown.c new file mode 100644 index 000000000..c21ddad58 --- /dev/null +++ b/examples/shutdown-1.0/script/hardshutdown.c | |||
@@ -0,0 +1,168 @@ | |||
1 | /* Including <unistd.h> makes sure that on a glibc system | ||
2 | * <features.h> is included, which again defines __GLIBC__ | ||
3 | */ | ||
4 | |||
5 | #include <unistd.h> | ||
6 | #include <stdio.h> /* puts */ | ||
7 | #include <time.h> /* nanosleep */ | ||
8 | #include <errno.h> | ||
9 | #include <stdlib.h> | ||
10 | #include <string.h> | ||
11 | |||
12 | |||
13 | /* | ||
14 | * Magic values required to use _reboot() system call. | ||
15 | */ | ||
16 | #define LINUX_REBOOT_MAGIC1 0xfee1dead | ||
17 | #define LINUX_REBOOT_MAGIC2 672274793 | ||
18 | #define LINUX_REBOOT_MAGIC2A 85072278 | ||
19 | #define LINUX_REBOOT_MAGIC2B 369367448 | ||
20 | /* | ||
21 | * Commands accepted by the _reboot() system call. | ||
22 | * | ||
23 | * RESTART Restart system using default command and mode. | ||
24 | * HALT Stop OS and give system control to ROM monitor, if any. | ||
25 | * CAD_ON Ctrl-Alt-Del sequence causes RESTART command. | ||
26 | * CAD_OFF Ctrl-Alt-Del sequence sends SIGINT to init task. | ||
27 | * POWER_OFF Stop OS and remove all power from system, if possible. | ||
28 | * RESTART2 Restart system using given command string. | ||
29 | */ | ||
30 | #define LINUX_REBOOT_CMD_RESTART 0x01234567 | ||
31 | #define LINUX_REBOOT_CMD_HALT 0xCDEF0123 | ||
32 | #define LINUX_REBOOT_CMD_CAD_ON 0x89ABCDEF | ||
33 | #define LINUX_REBOOT_CMD_CAD_OFF 0x00000000 | ||
34 | #define LINUX_REBOOT_CMD_POWER_OFF 0x4321FEDC | ||
35 | #define LINUX_REBOOT_CMD_RESTART2 0xA1B2C3D4 | ||
36 | |||
37 | |||
38 | #define USE_LIBC | ||
39 | |||
40 | #ifdef USE_LIBC | ||
41 | |||
42 | /* libc version */ | ||
43 | #if defined __GLIBC__ && __GLIBC__ >= 2 | ||
44 | # include <sys/reboot.h> | ||
45 | # define REBOOT(cmd) reboot(cmd) | ||
46 | #else | ||
47 | extern int reboot(int, int, int); | ||
48 | # define REBOOT(cmd) reboot(LINUX_REBOOT_MAGIC1,LINUX_REBOOT_MAGIC2,(cmd)) | ||
49 | #endif | ||
50 | |||
51 | static int my_reboot(int cmd) | ||
52 | { | ||
53 | return REBOOT(cmd); | ||
54 | } | ||
55 | |||
56 | #else /* no USE_LIBC */ | ||
57 | |||
58 | /* direct syscall version */ | ||
59 | #include <linux/unistd.h> | ||
60 | |||
61 | #ifdef _syscall3 | ||
62 | _syscall3(int, reboot, int, magic, int, magic_too, int, cmd); | ||
63 | #else | ||
64 | /* Let us hope we have a 3-argument reboot here */ | ||
65 | extern int reboot(int, int, int); | ||
66 | #endif | ||
67 | |||
68 | static int my_reboot(int cmd) | ||
69 | { | ||
70 | return reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, cmd); | ||
71 | } | ||
72 | |||
73 | #endif | ||
74 | |||
75 | |||
76 | static void do_reboot(void) | ||
77 | { | ||
78 | my_reboot(LINUX_REBOOT_CMD_RESTART); | ||
79 | } | ||
80 | static void do_poweroff(void) | ||
81 | { | ||
82 | my_reboot(LINUX_REBOOT_CMD_POWER_OFF); | ||
83 | } | ||
84 | static void do_halt(void) | ||
85 | { | ||
86 | my_reboot(LINUX_REBOOT_CMD_HALT); | ||
87 | } | ||
88 | |||
89 | static void usage(void) | ||
90 | { | ||
91 | puts( | ||
92 | "Usage: hardshutdown -h|-r|-p [NN]\n" | ||
93 | " NN - seconds to sleep before requested action" | ||
94 | ); | ||
95 | exit(1); | ||
96 | } | ||
97 | |||
98 | enum action_t { | ||
99 | SHUTDOWN, // do nothing | ||
100 | HALT, | ||
101 | POWEROFF, | ||
102 | REBOOT | ||
103 | }; | ||
104 | |||
105 | int main(int argc, char *argv[]) | ||
106 | { | ||
107 | struct timespec t = {0,0}; | ||
108 | enum action_t action = SHUTDOWN; | ||
109 | int c, i; | ||
110 | char *prog, *ptr; | ||
111 | |||
112 | //if (*argv[0] == '-') argv[0]++; /* allow shutdown as login shell */ | ||
113 | prog = argv[0]; | ||
114 | ptr = strrchr(prog,'/'); | ||
115 | if (ptr) | ||
116 | prog = ptr+1; | ||
117 | |||
118 | for (c=1; c < argc; c++) { | ||
119 | if (argv[c][0] >= '0' && argv[c][0] <= '9') { | ||
120 | t.tv_sec = strtol(argv[c], NULL, 10); | ||
121 | continue; | ||
122 | } | ||
123 | if (argv[c][0] != '-') { | ||
124 | usage(); | ||
125 | return 1; | ||
126 | } | ||
127 | for (i=1; argv[c][i]; i++) { | ||
128 | switch (argv[c][i]) { | ||
129 | case 'h': | ||
130 | action = HALT; | ||
131 | break; | ||
132 | case 'p': | ||
133 | action = POWEROFF; | ||
134 | break; | ||
135 | case 'r': | ||
136 | action = REBOOT; | ||
137 | break; | ||
138 | default: | ||
139 | usage(); | ||
140 | return 1; | ||
141 | } | ||
142 | } | ||
143 | } | ||
144 | |||
145 | if (action==SHUTDOWN) { | ||
146 | usage(); | ||
147 | return 1; | ||
148 | } | ||
149 | |||
150 | chdir("/"); | ||
151 | while (nanosleep(&t,&t)<0) | ||
152 | if (errno!=EINTR) break; | ||
153 | |||
154 | switch (action) { | ||
155 | case HALT: | ||
156 | do_halt(); | ||
157 | break; | ||
158 | case POWEROFF: | ||
159 | do_poweroff(); | ||
160 | break; | ||
161 | case REBOOT: | ||
162 | do_reboot(); | ||
163 | break; | ||
164 | default: /* SHUTDOWN */ | ||
165 | break; | ||
166 | } | ||
167 | return 1; | ||
168 | } | ||
diff --git a/examples/shutdown-1.0/script/hardshutdown.make.sh b/examples/shutdown-1.0/script/hardshutdown.make.sh new file mode 100755 index 000000000..90f8c5456 --- /dev/null +++ b/examples/shutdown-1.0/script/hardshutdown.make.sh | |||
@@ -0,0 +1,8 @@ | |||
1 | #!/bin/sh | ||
2 | |||
3 | gcc -Wall -Os -o hardshutdown hardshutdown.c | ||
4 | strip hardshutdown | ||
5 | |||
6 | #or: | ||
7 | #diet gcc -Wall -o hardshutdown hardshutdown.c | ||
8 | #elftrunc hardshutdown | ||
diff --git a/examples/shutdown-1.0/script/shutdown b/examples/shutdown-1.0/script/shutdown new file mode 100755 index 000000000..dbab9d81e --- /dev/null +++ b/examples/shutdown-1.0/script/shutdown | |||
@@ -0,0 +1,64 @@ | |||
1 | #!/bin/sh | ||
2 | |||
3 | PATH=/sbin:/usr/sbin:/bin:/usr/bin | ||
4 | |||
5 | # Usually, /sbin/ has symlinks named halt, reboot, poweroff | ||
6 | # (and also possibly shutdown) to e.g. | ||
7 | # /app/shutdown-1.0/script/shutdown (this file). | ||
8 | cd /app/shutdown-1.0/script || exit 1 | ||
9 | test -x ./do_shutdown || exit 1 | ||
10 | test -x ./hardshutdown || exit 1 | ||
11 | |||
12 | # "reboot -f" -> "shutdown -f -r" -> "hardshutdown -r" -> immediate reboot | ||
13 | # "reboot" -> "shutdown -r" -> "do_shutdown -r" | ||
14 | # ^^^^^^^^^^^^^^^^^^ similarly for halt, poweroff. | ||
15 | # "shutdown" -> "do_shutdown" (everything killed/unmounted, but kernel not asked to do any poweroff etc) | ||
16 | force="" | ||
17 | test x"$1" = x"-f" && { | ||
18 | force="-f" | ||
19 | shift | ||
20 | } | ||
21 | test ! "$*" && test x"${0##*/}" = x"halt" && exec "$0" $force -h | ||
22 | test ! "$*" && test x"${0##*/}" = x"reboot" && exec "$0" $force -r | ||
23 | test ! "$*" && test x"${0##*/}" = x"poweroff" && exec "$0" $force -p | ||
24 | # We have something else than allowed parameters? | ||
25 | test x"$*" = x"" || test x"$*" = x"-h" || test x"$*" = x"-r" || test x"$*" = x"-p" || { | ||
26 | echo "Syntax: $0 [-f] [-h/-r/-p]" | ||
27 | exit 1 | ||
28 | } | ||
29 | |||
30 | # Emergency shutdown? | ||
31 | test "$force" && { | ||
32 | exec ./hardshutdown "$@" | ||
33 | exit 1 | ||
34 | } | ||
35 | |||
36 | # Normal shutdown | ||
37 | |||
38 | # We must have these executables on root fs | ||
39 | # (mount/umount aren't checked, all systems are ok versus that): | ||
40 | test -x /bin/killall5 -o -x /sbin/killall5 || exit 1 | ||
41 | test -x /bin/ps -o -x /sbin/ps || exit 1 | ||
42 | test -x /bin/date -o -x /sbin/date || exit 1 | ||
43 | test -x /bin/xargs -o -x /sbin/xargs || exit 1 | ||
44 | test -x /bin/wc -o -x /sbin/wc || exit 1 | ||
45 | test -x /bin/cat -o -x /sbin/cat || exit 1 | ||
46 | test -x /bin/sort -o -x /sbin/sort || exit 1 | ||
47 | |||
48 | i="`ulimit -n`" | ||
49 | echo -n "Closing file descriptors $i-3... " | ||
50 | while test "$i" -ge 3; do | ||
51 | eval "exec $i>&-" | ||
52 | i=$((i-1)) | ||
53 | done | ||
54 | |||
55 | echo "Shutting down. Please stand by..." | ||
56 | |||
57 | # setsid & /dev/null: | ||
58 | # make it a process leader & detach it from current tty. | ||
59 | # Why /dev/null and not /dev/console? | ||
60 | # I have seen a system which locked up while opening /dev/console | ||
61 | # due to the bug (?) in keyboard driver. | ||
62 | setsid env - PATH="$PATH" ./do_shutdown "$@" </dev/null >/dev/null 2>&1 & | ||
63 | |||
64 | while true; do read junk; done | ||
diff --git a/examples/shutdown-1.0/script/stop_storage b/examples/shutdown-1.0/script/stop_storage new file mode 100755 index 000000000..1be5f735b --- /dev/null +++ b/examples/shutdown-1.0/script/stop_storage | |||
@@ -0,0 +1,81 @@ | |||
1 | #!/bin/sh | ||
2 | # Do unmount/remount-ro. Wait. | ||
3 | # KILL everybody. Wait. | ||
4 | # Repeat. | ||
5 | |||
6 | umountcnt=2 | ||
7 | writeout=0 # increase if your kernel doesn ot guarantee writes to complete | ||
8 | |||
9 | # No /usr - we are expecting all binaries to be accessible | ||
10 | # from root fs alone | ||
11 | PATH=/sbin:/bin | ||
12 | |||
13 | say() { | ||
14 | printf "\r%s\n\r" "$*" | ||
15 | } | ||
16 | |||
17 | showps() { | ||
18 | # sleep 1 ensures that xargs will have time to start up | ||
19 | # this makes pslist less prone to random jitter | ||
20 | pslist=`{ sleep 1; ps -A -o comm=; } | sort | xargs` | ||
21 | pscnt=$(( `say "$pslist" | wc -w` + 0 )) | ||
22 | if test x"$VERBOSE" = x; then | ||
23 | say "* `date '+%H:%M:%S'` $pscnt processes" | ||
24 | else | ||
25 | say "* `date '+%H:%M:%S'` Processes ($pscnt): $pslist" | ||
26 | fi | ||
27 | } | ||
28 | |||
29 | say "<*> `date '+%Y-%m-%d %H:%M:%S'` Executing '$0 $*'" | ||
30 | |||
31 | showps | ||
32 | |||
33 | i="$umountcnt" | ||
34 | while test "$i" -gt 0; do | ||
35 | say "* `date '+%H:%M:%S'` Unmounting filesystems" | ||
36 | umount -a -n -r -f | ||
37 | # In case we unmounted proc... | ||
38 | test -e /proc/version || mount -t proc none /proc | ||
39 | # Remounting / RO isn't necessary when /etc/mtab is linked to /proc/mounts: | ||
40 | # already done. But let's be more paranoid here... | ||
41 | say "* `date '+%H:%M:%S'` Remounting root filesystem read-only" | ||
42 | mount -n -o remount,ro / | ||
43 | say "* `date '+%H:%M:%S'` Freeing loop devices" | ||
44 | for a in /dev/loop*; do | ||
45 | test -b "$a" && losetup -d "$a" | ||
46 | done | ||
47 | say "* `date '+%H:%M:%S'` Syncing" | ||
48 | sync | ||
49 | say "* `date '+%H:%M:%S'` Executing: killall5 -KILL" | ||
50 | killall5 -9 | ||
51 | showps | ||
52 | i=$((i-1)) | ||
53 | done | ||
54 | |||
55 | say "* `date '+%H:%M:%S'` Filesystem status (/proc/mounts)" | ||
56 | cat /proc/mounts \ | ||
57 | | { | ||
58 | bad=false | ||
59 | while read dev mntpoint fstype opt n1 n2; do | ||
60 | case "$fstype" in | ||
61 | ( proc | sysfs | usbfs | devpts | rpc_pipefs | binfmt_misc | autofs | rootfs | tmpfs | ramfs ) | ||
62 | say "$dev $mntpoint $fstype $opt $n1 $n2" | ||
63 | continue | ||
64 | ;; | ||
65 | esac | ||
66 | if test "${opt:0:2}" = "rw"; then | ||
67 | say "$dev $mntpoint $fstype $opt $n1 $n2 - RW!" | ||
68 | bad=true | ||
69 | else | ||
70 | say "$dev $mntpoint $fstype $opt $n1 $n2" | ||
71 | fi | ||
72 | done | ||
73 | if $bad; then | ||
74 | say "ERROR: we have filesystems mounted RW! Press <Enter> (^J)..." | ||
75 | read junk </dev/console | ||
76 | #sh </dev/console >&0 2>&0 # debug | ||
77 | fi | ||
78 | } | ||
79 | |||
80 | # Disk cache writeout | ||
81 | sleep "$writeout" | ||
diff --git a/examples/shutdown-1.0/script/stop_tasks b/examples/shutdown-1.0/script/stop_tasks new file mode 100755 index 000000000..2d752a3da --- /dev/null +++ b/examples/shutdown-1.0/script/stop_tasks | |||
@@ -0,0 +1,70 @@ | |||
1 | #!/bin/sh | ||
2 | # We are trying to be nice. | ||
3 | # TERM everybody. Give them some time to die. | ||
4 | # KILL might make some filesystems non-unmountable, | ||
5 | # so we'll do it in stop_storage instead. | ||
6 | |||
7 | killcnt=30 | ||
8 | |||
9 | PATH=/sbin:/usr/sbin:/bin:/usr/bin | ||
10 | |||
11 | echo "<*> `date '+%Y-%m-%d %H:%M:%S'` Executing '$0 $*'" | ||
12 | |||
13 | showps() { | ||
14 | # sleep 1 ensures that xargs will have time to start up. | ||
15 | # This makes pslist less prone to random jitter. | ||
16 | pslist=`{ sleep 1; ps -A -o comm=; } | sort | xargs` | ||
17 | pscnt=$(( `echo "$pslist" | wc -w` + 0 )) | ||
18 | if test x"$VERBOSE" = x; then | ||
19 | echo "* `date '+%H:%M:%S'` $pscnt processes" | ||
20 | else | ||
21 | echo "* `date '+%H:%M:%S'` Processes ($pscnt): $pslist" | ||
22 | fi | ||
23 | } | ||
24 | |||
25 | # Sync. | ||
26 | # Rationale: sometimes buggy root processes can | ||
27 | # hang the system when killed (X for example may have problems | ||
28 | # with restoring text mode on a poorly supported hardware). | ||
29 | # These are bugs and must be fixed, but until then users will lose | ||
30 | # dirty data on shutdown! Let's make that less likely. | ||
31 | sync & | ||
32 | |||
33 | # Send SIGTERMs. If list of processes changes, proceed slower. | ||
34 | # If it has stabilised (all who wanted to, exited), proceed faster. | ||
35 | showps | ||
36 | i="$killcnt" | ||
37 | while test "$i" -gt 0; do | ||
38 | echo "* `date '+%H:%M:%S'` Sending CONT, TERM" #, HUP" | ||
39 | # I've seen "killall5 2.86" which doesn't grok signal names! | ||
40 | killall5 -18 | ||
41 | killall5 -15 | ||
42 | #killall5 -1 # HUP: because interactive bash does not die on TERM... | ||
43 | # but init will reread /etc/inittab on HUP and my /etc is on non root fs! | ||
44 | # -> umounts will complain. | ||
45 | oldpslist="$pslist" | ||
46 | showps | ||
47 | if test x"$pslist" = x"$oldpslist"; then | ||
48 | i=$((i-8)) | ||
49 | fi | ||
50 | i=$((i-2)) | ||
51 | done | ||
52 | |||
53 | echo "* `date '+%H:%M:%S'` Turning off swap" | ||
54 | swapoff -a | ||
55 | cat /proc/swaps | grep -v ^Filename | cut -d ' ' -f1 \ | ||
56 | | while read -r line; do | ||
57 | test "$line" && { | ||
58 | echo swapoff "$line" | ||
59 | swapoff "$line" | ||
60 | } | ||
61 | done | ||
62 | |||
63 | echo "* /proc/swaps:" | ||
64 | cat /proc/swaps | ||
65 | echo "* /proc/mounts:" | ||
66 | cat /proc/mounts | ||
67 | echo "* ps -A e:" | ||
68 | ps -A e | ||
69 | echo "* top -bn1:" | ||
70 | top -bn1 | ||
diff --git a/examples/udhcp/udhcpd.conf b/examples/udhcp/udhcpd.conf index eca44c0ab..90714bcdf 100644 --- a/examples/udhcp/udhcpd.conf +++ b/examples/udhcp/udhcpd.conf | |||
@@ -70,8 +70,9 @@ option domain local | |||
70 | option lease 864000 # default: 10 days | 70 | option lease 864000 # default: 10 days |
71 | option msstaticroutes 10.0.0.0/8 10.127.0.1 # single static route | 71 | option msstaticroutes 10.0.0.0/8 10.127.0.1 # single static route |
72 | option staticroutes 10.0.0.0/8 10.127.0.1, 10.11.12.0/24 10.11.12.1 | 72 | option staticroutes 10.0.0.0/8 10.127.0.1, 10.11.12.0/24 10.11.12.1 |
73 | # Arbitrary option in hex form: | 73 | # Arbitrary option in hex or string form: |
74 | option 0x08 01020304 # option 8: "cookie server IP addr: 1.2.3.4" | 74 | option 0x08 01020304 # option 8: "cookie server IP addr: 1.2.3.4" |
75 | option 14 "dumpfile" | ||
75 | 76 | ||
76 | # Currently supported options (for more info, see options.c): | 77 | # Currently supported options (for more info, see options.c): |
77 | #opt lease NUM | 78 | #opt lease NUM |
diff --git a/examples/var_service/tftpd/run b/examples/var_service/tftpd/run index e492d8453..ceb2be555 100755 --- a/examples/var_service/tftpd/run +++ b/examples/var_service/tftpd/run | |||
@@ -7,7 +7,7 @@ exec </dev/null | |||
7 | user=root # for bind to port 69 | 7 | user=root # for bind to port 69 |
8 | 8 | ||
9 | exec \ | 9 | exec \ |
10 | env - \ | 10 | env - PATH="$PATH" \ |
11 | softlimit \ | 11 | softlimit \ |
12 | setuidgid "$user" \ | 12 | setuidgid "$user" \ |
13 | udpsvd -v -c 10 -l localhost \ | 13 | udpsvd -v -c 10 -l localhost \ |
diff --git a/findutils/find.c b/findutils/find.c index d9a42b3c6..07321c81a 100644 --- a/findutils/find.c +++ b/findutils/find.c | |||
@@ -263,7 +263,7 @@ | |||
263 | //usage: "\n -regex PATTERN Match path to regex PATTERN" | 263 | //usage: "\n -regex PATTERN Match path to regex PATTERN" |
264 | //usage: ) | 264 | //usage: ) |
265 | //usage: IF_FEATURE_FIND_TYPE( | 265 | //usage: IF_FEATURE_FIND_TYPE( |
266 | //usage: "\n -type X File type is X (one of: f,d,l,b,c,...)" | 266 | //usage: "\n -type X File type is X (one of: f,d,l,b,c,s,p)" |
267 | //usage: ) | 267 | //usage: ) |
268 | //usage: IF_FEATURE_FIND_PERM( | 268 | //usage: IF_FEATURE_FIND_PERM( |
269 | //usage: "\n -perm MASK At least one mask bit (+MASK), all bits (-MASK)," | 269 | //usage: "\n -perm MASK At least one mask bit (+MASK), all bits (-MASK)," |
diff --git a/include/bb_archive.h b/include/bb_archive.h index 084ba9470..2182fcc3c 100644 --- a/include/bb_archive.h +++ b/include/bb_archive.h | |||
@@ -126,6 +126,10 @@ typedef struct archive_handle_t { | |||
126 | #if ENABLE_FEATURE_AR_CREATE | 126 | #if ENABLE_FEATURE_AR_CREATE |
127 | const char *ar__name; | 127 | const char *ar__name; |
128 | struct archive_handle_t *ar__out; | 128 | struct archive_handle_t *ar__out; |
129 | # if ENABLE_FEATURE_AR_LONG_FILENAMES | ||
130 | char *ar__long_names; | ||
131 | unsigned ar__long_name_size; | ||
132 | # endif | ||
129 | #endif | 133 | #endif |
130 | } archive_handle_t; | 134 | } archive_handle_t; |
131 | /* bits in ah_flags */ | 135 | /* bits in ah_flags */ |
diff --git a/include/libbb.h b/include/libbb.h index 876875045..fb9167ce3 100644 --- a/include/libbb.h +++ b/include/libbb.h | |||
@@ -681,6 +681,11 @@ int setsockopt_bindtodevice(int fd, const char *iface) FAST_FUNC; | |||
681 | int bb_getsockname(int sockfd, void *addr, socklen_t addrlen) FAST_FUNC; | 681 | int bb_getsockname(int sockfd, void *addr, socklen_t addrlen) FAST_FUNC; |
682 | /* NB: returns port in host byte order */ | 682 | /* NB: returns port in host byte order */ |
683 | unsigned bb_lookup_port(const char *port, const char *protocol, unsigned default_port) FAST_FUNC; | 683 | unsigned bb_lookup_port(const char *port, const char *protocol, unsigned default_port) FAST_FUNC; |
684 | #if ENABLE_FEATURE_ETC_SERVICES | ||
685 | # define bb_lookup_std_port(portstr, protocol, portnum) bb_lookup_port(portstr, protocol, portnum) | ||
686 | #else | ||
687 | # define bb_lookup_std_port(portstr, protocol, portnum) (portnum) | ||
688 | #endif | ||
684 | typedef struct len_and_sockaddr { | 689 | typedef struct len_and_sockaddr { |
685 | socklen_t len; | 690 | socklen_t len; |
686 | union { | 691 | union { |
@@ -1645,9 +1650,11 @@ int get_terminal_width_height(int fd, unsigned *width, unsigned *height) FAST_FU | |||
1645 | int get_terminal_width(int fd) FAST_FUNC; | 1650 | int get_terminal_width(int fd) FAST_FUNC; |
1646 | 1651 | ||
1647 | int tcsetattr_stdin_TCSANOW(const struct termios *tp) FAST_FUNC; | 1652 | int tcsetattr_stdin_TCSANOW(const struct termios *tp) FAST_FUNC; |
1648 | #define TERMIOS_CLEAR_ISIG (1 << 0) | 1653 | #define TERMIOS_CLEAR_ISIG (1 << 0) |
1649 | #define TERMIOS_RAW_CRNL (1 << 1) | 1654 | #define TERMIOS_RAW_CRNL_INPUT (1 << 1) |
1650 | #define TERMIOS_RAW_INPUT (1 << 2) | 1655 | #define TERMIOS_RAW_CRNL_OUTPUT (1 << 2) |
1656 | #define TERMIOS_RAW_CRNL (TERMIOS_RAW_CRNL_INPUT|TERMIOS_RAW_CRNL_OUTPUT) | ||
1657 | #define TERMIOS_RAW_INPUT (1 << 3) | ||
1651 | int get_termios_and_make_raw(int fd, struct termios *newterm, struct termios *oldterm, int flags) FAST_FUNC; | 1658 | int get_termios_and_make_raw(int fd, struct termios *newterm, struct termios *oldterm, int flags) FAST_FUNC; |
1652 | int set_termios_to_raw(int fd, struct termios *oldterm, int flags) FAST_FUNC; | 1659 | int set_termios_to_raw(int fd, struct termios *oldterm, int flags) FAST_FUNC; |
1653 | 1660 | ||
diff --git a/libbb/Config.src b/libbb/Config.src index fdf8bbb28..16e16480b 100644 --- a/libbb/Config.src +++ b/libbb/Config.src | |||
@@ -76,6 +76,18 @@ config FEATURE_ETC_NETWORKS | |||
76 | a rarely used feature which allows you to use names | 76 | a rarely used feature which allows you to use names |
77 | instead of IP/mask pairs in route command. | 77 | instead of IP/mask pairs in route command. |
78 | 78 | ||
79 | config FEATURE_ETC_SERVICES | ||
80 | bool "Consult /etc/services even for well-known ports" | ||
81 | default n | ||
82 | help | ||
83 | Look up e.g. "telnet" and "http" in /etc/services file | ||
84 | instead of assuming ports 23 and 80. | ||
85 | This is almost never necessary (everybody uses standard ports), | ||
86 | and it makes sense to avoid reading this file. | ||
87 | If you disable this option, in the cases where port is explicitly | ||
88 | specified as a service name (e.g. "telnet HOST PORTNAME"), | ||
89 | it will still be looked up in /etc/services. | ||
90 | |||
79 | config FEATURE_EDITING | 91 | config FEATURE_EDITING |
80 | bool "Command line editing" | 92 | bool "Command line editing" |
81 | default y | 93 | default y |
diff --git a/libbb/xfuncs.c b/libbb/xfuncs.c index 6fa21ad00..57bda6204 100644 --- a/libbb/xfuncs.c +++ b/libbb/xfuncs.c | |||
@@ -333,7 +333,6 @@ int FAST_FUNC get_termios_and_make_raw(int fd, struct termios *newterm, struct t | |||
333 | newterm->c_cc[VMIN] = 1; | 333 | newterm->c_cc[VMIN] = 1; |
334 | /* no timeout (reads block forever) */ | 334 | /* no timeout (reads block forever) */ |
335 | newterm->c_cc[VTIME] = 0; | 335 | newterm->c_cc[VTIME] = 0; |
336 | if (flags & TERMIOS_RAW_CRNL) { | ||
337 | /* IXON, IXOFF, and IXANY: | 336 | /* IXON, IXOFF, and IXANY: |
338 | * IXOFF=1: sw flow control is enabled on input queue: | 337 | * IXOFF=1: sw flow control is enabled on input queue: |
339 | * tty transmits a STOP char when input queue is close to full | 338 | * tty transmits a STOP char when input queue is close to full |
@@ -343,9 +342,12 @@ int FAST_FUNC get_termios_and_make_raw(int fd, struct termios *newterm, struct t | |||
343 | * and resume sending if START is received, or if any char | 342 | * and resume sending if START is received, or if any char |
344 | * is received and IXANY=1. | 343 | * is received and IXANY=1. |
345 | */ | 344 | */ |
345 | if (flags & TERMIOS_RAW_CRNL_INPUT) { | ||
346 | /* IXON=0: XON/XOFF chars are treated as normal chars (why we do this?) */ | 346 | /* IXON=0: XON/XOFF chars are treated as normal chars (why we do this?) */ |
347 | /* dont convert CR to NL on input */ | 347 | /* dont convert CR to NL on input */ |
348 | newterm->c_iflag &= ~(IXON | ICRNL); | 348 | newterm->c_iflag &= ~(IXON | ICRNL); |
349 | } | ||
350 | if (flags & TERMIOS_RAW_CRNL_OUTPUT) { | ||
349 | /* dont convert NL to CR+NL on output */ | 351 | /* dont convert NL to CR+NL on output */ |
350 | newterm->c_oflag &= ~(ONLCR); | 352 | newterm->c_oflag &= ~(ONLCR); |
351 | /* Maybe clear more c_oflag bits? Usually, only OPOST and ONLCR are set. | 353 | /* Maybe clear more c_oflag bits? Usually, only OPOST and ONLCR are set. |
@@ -366,9 +368,12 @@ int FAST_FUNC get_termios_and_make_raw(int fd, struct termios *newterm, struct t | |||
366 | #ifndef IXANY | 368 | #ifndef IXANY |
367 | # define IXANY 0 | 369 | # define IXANY 0 |
368 | #endif | 370 | #endif |
369 | /* IXOFF=0: disable sending XON/XOFF if input buf is full */ | 371 | /* IXOFF=0: disable sending XON/XOFF if input buf is full |
370 | /* IXON=0: input XON/XOFF chars are not special */ | 372 | * IXON=0: input XON/XOFF chars are not special |
371 | /* dont convert anything on input */ | 373 | * BRKINT=0: dont send SIGINT on break |
374 | * IMAXBEL=0: dont echo BEL on input line too long | ||
375 | * INLCR,ICRNL,IUCLC: dont convert anything on input | ||
376 | */ | ||
372 | newterm->c_iflag &= ~(IXOFF|IXON|IXANY|BRKINT|INLCR|ICRNL|IUCLC|IMAXBEL); | 377 | newterm->c_iflag &= ~(IXOFF|IXON|IXANY|BRKINT|INLCR|ICRNL|IUCLC|IMAXBEL); |
373 | } | 378 | } |
374 | return r; | 379 | return r; |
diff --git a/miscutils/less.c b/miscutils/less.c index 9c75ae7ac..6b5c8c2dd 100644 --- a/miscutils/less.c +++ b/miscutils/less.c | |||
@@ -121,11 +121,12 @@ | |||
121 | //kbuild:lib-$(CONFIG_LESS) += less.o | 121 | //kbuild:lib-$(CONFIG_LESS) += less.o |
122 | 122 | ||
123 | //usage:#define less_trivial_usage | 123 | //usage:#define less_trivial_usage |
124 | //usage: "[-E" IF_FEATURE_LESS_REGEXP("I")IF_FEATURE_LESS_FLAGS("Mm") | 124 | //usage: "[-EF" IF_FEATURE_LESS_REGEXP("I")IF_FEATURE_LESS_FLAGS("Mm") |
125 | //usage: "N" IF_FEATURE_LESS_TRUNCATE("S") IF_FEATURE_LESS_RAW("R") "h~] [FILE]..." | 125 | //usage: "N" IF_FEATURE_LESS_TRUNCATE("S") IF_FEATURE_LESS_RAW("R") "h~] [FILE]..." |
126 | //usage:#define less_full_usage "\n\n" | 126 | //usage:#define less_full_usage "\n\n" |
127 | //usage: "View FILE (or stdin) one screenful at a time\n" | 127 | //usage: "View FILE (or stdin) one screenful at a time\n" |
128 | //usage: "\n -E Quit once the end of a file is reached" | 128 | //usage: "\n -E Quit once the end of a file is reached" |
129 | //usage: "\n -F Quit if entire file fits on first screen" | ||
129 | //usage: IF_FEATURE_LESS_REGEXP( | 130 | //usage: IF_FEATURE_LESS_REGEXP( |
130 | //usage: "\n -I Ignore case in all searches" | 131 | //usage: "\n -I Ignore case in all searches" |
131 | //usage: ) | 132 | //usage: ) |
@@ -179,8 +180,9 @@ enum { | |||
179 | FLAG_N = 1 << 3, | 180 | FLAG_N = 1 << 3, |
180 | FLAG_TILDE = 1 << 4, | 181 | FLAG_TILDE = 1 << 4, |
181 | FLAG_I = 1 << 5, | 182 | FLAG_I = 1 << 5, |
182 | FLAG_S = (1 << 6) * ENABLE_FEATURE_LESS_TRUNCATE, | 183 | FLAG_F = 1 << 6, |
183 | FLAG_R = (1 << 7) * ENABLE_FEATURE_LESS_RAW, | 184 | FLAG_S = (1 << 7) * ENABLE_FEATURE_LESS_TRUNCATE, |
185 | FLAG_R = (1 << 8) * ENABLE_FEATURE_LESS_RAW, | ||
184 | /* hijack command line options variable for internal state vars */ | 186 | /* hijack command line options variable for internal state vars */ |
185 | LESS_STATE_MATCH_BACKWARDS = 1 << 15, | 187 | LESS_STATE_MATCH_BACKWARDS = 1 << 15, |
186 | }; | 188 | }; |
@@ -938,11 +940,12 @@ static void buffer_print(void) | |||
938 | else | 940 | else |
939 | print_ascii(buffer[i]); | 941 | print_ascii(buffer[i]); |
940 | } | 942 | } |
941 | if ((option_mask32 & FLAG_E) | 943 | if ((option_mask32 & (FLAG_E|FLAG_F)) |
942 | && eof_error <= 0 | 944 | && eof_error <= 0 |
943 | && (max_fline - cur_fline) <= max_displayed_line | ||
944 | ) { | 945 | ) { |
945 | less_exit(EXIT_SUCCESS); | 946 | i = option_mask32 & FLAG_F ? 0 : cur_fline; |
947 | if (max_fline - i <= max_displayed_line) | ||
948 | less_exit(EXIT_SUCCESS); | ||
946 | } | 949 | } |
947 | status_print(); | 950 | status_print(); |
948 | } | 951 | } |
@@ -1894,7 +1897,7 @@ int less_main(int argc, char **argv) | |||
1894 | * -s: condense many empty lines to one | 1897 | * -s: condense many empty lines to one |
1895 | * (used by some setups for manpage display) | 1898 | * (used by some setups for manpage display) |
1896 | */ | 1899 | */ |
1897 | getopt32(argv, "EMmN~I" | 1900 | getopt32(argv, "EMmN~IF" |
1898 | IF_FEATURE_LESS_TRUNCATE("S") | 1901 | IF_FEATURE_LESS_TRUNCATE("S") |
1899 | IF_FEATURE_LESS_RAW("R") | 1902 | IF_FEATURE_LESS_RAW("R") |
1900 | /*ignored:*/"s" | 1903 | /*ignored:*/"s" |
@@ -1908,6 +1911,9 @@ int less_main(int argc, char **argv) | |||
1908 | if (ENABLE_FEATURE_LESS_ENV) { | 1911 | if (ENABLE_FEATURE_LESS_ENV) { |
1909 | char *c = getenv("LESS"); | 1912 | char *c = getenv("LESS"); |
1910 | if (c) while (*c) switch (*c++) { | 1913 | if (c) while (*c) switch (*c++) { |
1914 | case 'F': | ||
1915 | option_mask32 |= FLAG_F; | ||
1916 | break; | ||
1911 | case 'M': | 1917 | case 'M': |
1912 | option_mask32 |= FLAG_M; | 1918 | option_mask32 |= FLAG_M; |
1913 | break; | 1919 | break; |
@@ -1930,7 +1936,6 @@ int less_main(int argc, char **argv) | |||
1930 | if (!num_files) { | 1936 | if (!num_files) { |
1931 | if (isatty(STDIN_FILENO)) { | 1937 | if (isatty(STDIN_FILENO)) { |
1932 | /* Just "less"? No args and no redirection? */ | 1938 | /* Just "less"? No args and no redirection? */ |
1933 | bb_error_msg("missing filename"); | ||
1934 | bb_show_usage(); | 1939 | bb_show_usage(); |
1935 | } | 1940 | } |
1936 | } else { | 1941 | } else { |
@@ -1971,7 +1976,7 @@ int less_main(int argc, char **argv) | |||
1971 | #endif | 1976 | #endif |
1972 | 1977 | ||
1973 | #if !ENABLE_PLATFORM_MINGW32 | 1978 | #if !ENABLE_PLATFORM_MINGW32 |
1974 | get_termios_and_make_raw(tty_fd, &term_less, &term_orig, TERMIOS_RAW_CRNL); | 1979 | get_termios_and_make_raw(tty_fd, &term_less, &term_orig, TERMIOS_RAW_CRNL_INPUT); |
1975 | #endif | 1980 | #endif |
1976 | 1981 | ||
1977 | IF_FEATURE_LESS_ASK_TERMINAL(G.winsize_err =) get_terminal_width_height(tty_fd, &width, &max_displayed_line); | 1982 | IF_FEATURE_LESS_ASK_TERMINAL(G.winsize_err =) get_terminal_width_height(tty_fd, &width, &max_displayed_line); |
diff --git a/networking/ifplugd.c b/networking/ifplugd.c index 5059eaf73..9a67d24d8 100644 --- a/networking/ifplugd.c +++ b/networking/ifplugd.c | |||
@@ -686,6 +686,8 @@ int ifplugd_main(int argc UNUSED_PARAM, char **argv) | |||
686 | goto exiting; | 686 | goto exiting; |
687 | default: | 687 | default: |
688 | bb_got_signal = 0; | 688 | bb_got_signal = 0; |
689 | /* do not clear bb_got_signal if already 0, this can lose signals */ | ||
690 | case 0: | ||
689 | break; | 691 | break; |
690 | } | 692 | } |
691 | 693 | ||
diff --git a/networking/isrv_identd.c b/networking/isrv_identd.c index 133d62a65..0c33dde4f 100644 --- a/networking/isrv_identd.c +++ b/networking/isrv_identd.c | |||
@@ -159,7 +159,7 @@ int fakeidentd_main(int argc UNUSED_PARAM, char **argv) | |||
159 | fd = 0; | 159 | fd = 0; |
160 | if (!(opt & OPT_inetdwait)) { | 160 | if (!(opt & OPT_inetdwait)) { |
161 | fd = create_and_bind_stream_or_die(bind_address, | 161 | fd = create_and_bind_stream_or_die(bind_address, |
162 | bb_lookup_port("identd", "tcp", 113)); | 162 | bb_lookup_std_port("identd", "tcp", 113)); |
163 | xlisten(fd, 5); | 163 | xlisten(fd, 5); |
164 | } | 164 | } |
165 | 165 | ||
diff --git a/networking/nslookup.c b/networking/nslookup.c index 45c218e6c..fd241a5ca 100644 --- a/networking/nslookup.c +++ b/networking/nslookup.c | |||
@@ -1,31 +1,31 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | 1 | /* vi: set sw=4 ts=4: */ |
2 | /* | 2 | |
3 | * Mini nslookup implementation for busybox | ||
4 | * | ||
5 | * Copyright (C) 1999,2000 by Lineo, inc. and John Beppu | ||
6 | * Copyright (C) 1999,2000,2001 by John Beppu <beppu@codepoet.org> | ||
7 | * | ||
8 | * Correct default name server display and explicit name server option | ||
9 | * added by Ben Zeckel <bzeckel@hmc.edu> June 2001 | ||
10 | * | ||
11 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. | ||
12 | */ | ||
13 | //config:config NSLOOKUP | 3 | //config:config NSLOOKUP |
14 | //config: bool "nslookup (4.5 kb)" | 4 | //config: bool "nslookup (4.5 kb)" |
15 | //config: default y | 5 | //config: default y |
16 | //config: help | 6 | //config: help |
17 | //config: nslookup is a tool to query Internet name servers. | 7 | //config: nslookup is a tool to query Internet name servers. |
8 | //config: | ||
9 | //config:config FEATURE_NSLOOKUP_BIG | ||
10 | //config: bool "Use internal resolver code instead of libc" | ||
11 | //config: depends on NSLOOKUP | ||
12 | //config: default y | ||
13 | //config: | ||
14 | //config:config FEATURE_NSLOOKUP_LONG_OPTIONS | ||
15 | //config: bool "Enable long options" | ||
16 | //config: default y | ||
17 | //config: depends on FEATURE_NSLOOKUP_BIG && LONG_OPTS | ||
18 | 18 | ||
19 | //applet:IF_NSLOOKUP(APPLET(nslookup, BB_DIR_USR_BIN, BB_SUID_DROP)) | 19 | //applet:IF_NSLOOKUP(APPLET(nslookup, BB_DIR_USR_BIN, BB_SUID_DROP)) |
20 | 20 | ||
21 | //kbuild:lib-$(CONFIG_NSLOOKUP) += nslookup.o | 21 | //kbuild:lib-$(CONFIG_NSLOOKUP) += nslookup.o |
22 | 22 | ||
23 | //usage:#define nslookup_trivial_usage | 23 | //usage:#define nslookup_trivial_usage |
24 | //usage: "[HOST] [SERVER]" | 24 | //usage: IF_FEATURE_NSLOOKUP_BIG("[-type=QUERY_TYPE] [-debug] ") "HOST [DNS_SERVER]" |
25 | //usage:#define nslookup_full_usage "\n\n" | 25 | //usage:#define nslookup_full_usage "\n\n" |
26 | //usage: "Query the nameserver for the IP address of the given HOST\n" | 26 | //usage: "Query DNS about HOST" |
27 | //usage: "optionally using a specified DNS server" | 27 | //usage: IF_FEATURE_NSLOOKUP_BIG("\n") |
28 | //usage: | 28 | //usage: IF_FEATURE_NSLOOKUP_BIG("\nQUERY_TYPE: soa,ns,a,"IF_FEATURE_IPV6("aaaa,")"cname,mx,txt,ptr,any") |
29 | //usage:#define nslookup_example_usage | 29 | //usage:#define nslookup_example_usage |
30 | //usage: "$ nslookup localhost\n" | 30 | //usage: "$ nslookup localhost\n" |
31 | //usage: "Server: default\n" | 31 | //usage: "Server: default\n" |
@@ -35,7 +35,26 @@ | |||
35 | //usage: "Address: 127.0.0.1\n" | 35 | //usage: "Address: 127.0.0.1\n" |
36 | 36 | ||
37 | #include <resolv.h> | 37 | #include <resolv.h> |
38 | #include <net/if.h> /* for IFNAMSIZ */ | ||
39 | //#include <arpa/inet.h> | ||
40 | //#include <netdb.h> | ||
38 | #include "libbb.h" | 41 | #include "libbb.h" |
42 | #include "common_bufsiz.h" | ||
43 | |||
44 | |||
45 | #if !ENABLE_FEATURE_NSLOOKUP_BIG | ||
46 | |||
47 | /* | ||
48 | * Mini nslookup implementation for busybox | ||
49 | * | ||
50 | * Copyright (C) 1999,2000 by Lineo, inc. and John Beppu | ||
51 | * Copyright (C) 1999,2000,2001 by John Beppu <beppu@codepoet.org> | ||
52 | * | ||
53 | * Correct default name server display and explicit name server option | ||
54 | * added by Ben Zeckel <bzeckel@hmc.edu> June 2001 | ||
55 | * | ||
56 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. | ||
57 | */ | ||
39 | 58 | ||
40 | /* | 59 | /* |
41 | * I'm only implementing non-interactive mode; | 60 | * I'm only implementing non-interactive mode; |
@@ -207,3 +226,709 @@ int nslookup_main(int argc, char **argv) | |||
207 | 226 | ||
208 | return print_host(argv[1], "Name:"); | 227 | return print_host(argv[1], "Name:"); |
209 | } | 228 | } |
229 | |||
230 | |||
231 | #else /****** A version from LEDE / OpenWRT ******/ | ||
232 | |||
233 | /* | ||
234 | * musl compatible nslookup | ||
235 | * | ||
236 | * Copyright (C) 2017 Jo-Philipp Wich <jo@mein.io> | ||
237 | * | ||
238 | * Permission to use, copy, modify, and/or distribute this software for any | ||
239 | * purpose with or without fee is hereby granted, provided that the above | ||
240 | * copyright notice and this permission notice appear in all copies. | ||
241 | * | ||
242 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
243 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
244 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
245 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
246 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
247 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
248 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
249 | */ | ||
250 | |||
251 | #if 0 | ||
252 | # define dbg(...) fprintf(stderr, __VA_ARGS__) | ||
253 | #else | ||
254 | # define dbg(...) ((void)0) | ||
255 | #endif | ||
256 | |||
257 | struct ns { | ||
258 | const char *name; | ||
259 | len_and_sockaddr *lsa; | ||
260 | int failures; | ||
261 | int replies; | ||
262 | }; | ||
263 | |||
264 | struct query { | ||
265 | const char *name; | ||
266 | unsigned qlen; | ||
267 | // unsigned latency; | ||
268 | // uint8_t rcode; | ||
269 | unsigned char query[512]; | ||
270 | // unsigned char reply[512]; | ||
271 | }; | ||
272 | |||
273 | static const struct { | ||
274 | unsigned char type; | ||
275 | char name[7]; | ||
276 | } qtypes[] = { | ||
277 | { ns_t_soa, "SOA" }, | ||
278 | { ns_t_ns, "NS" }, | ||
279 | { ns_t_a, "A" }, | ||
280 | #if ENABLE_FEATURE_IPV6 | ||
281 | { ns_t_aaaa, "AAAA" }, | ||
282 | #endif | ||
283 | { ns_t_cname, "CNAME" }, | ||
284 | { ns_t_mx, "MX" }, | ||
285 | { ns_t_txt, "TXT" }, | ||
286 | { ns_t_ptr, "PTR" }, | ||
287 | { ns_t_any, "ANY" }, | ||
288 | }; | ||
289 | |||
290 | static const char *const rcodes[] = { | ||
291 | "NOERROR", // 0 | ||
292 | "FORMERR", // 1 | ||
293 | "SERVFAIL", // 2 | ||
294 | "NXDOMAIN", // 3 | ||
295 | "NOTIMP", // 4 | ||
296 | "REFUSED", // 5 | ||
297 | "YXDOMAIN", // 6 | ||
298 | "YXRRSET", // 7 | ||
299 | "NXRRSET", // 8 | ||
300 | "NOTAUTH", // 9 | ||
301 | "NOTZONE", // 10 | ||
302 | "11", // 11 not assigned | ||
303 | "12", // 12 not assigned | ||
304 | "13", // 13 not assigned | ||
305 | "14", // 14 not assigned | ||
306 | "15", // 15 not assigned | ||
307 | }; | ||
308 | |||
309 | #if ENABLE_FEATURE_IPV6 | ||
310 | static const char v4_mapped[12] = { 0,0,0,0, 0,0,0,0, 0,0,0xff,0xff }; | ||
311 | #endif | ||
312 | |||
313 | struct globals { | ||
314 | unsigned default_port; | ||
315 | unsigned default_retry; | ||
316 | unsigned default_timeout; | ||
317 | unsigned query_count; | ||
318 | unsigned serv_count; | ||
319 | struct ns *server; | ||
320 | struct query *query; | ||
321 | } FIX_ALIASING; | ||
322 | #define G (*(struct globals*)bb_common_bufsiz1) | ||
323 | #define INIT_G() do { \ | ||
324 | setup_common_bufsiz(); \ | ||
325 | G.default_port = 53; \ | ||
326 | G.default_retry = 2; \ | ||
327 | G.default_timeout = 5; \ | ||
328 | } while (0) | ||
329 | |||
330 | enum { | ||
331 | OPT_debug = (1 << 0), | ||
332 | }; | ||
333 | |||
334 | static int parse_reply(const unsigned char *msg, size_t len) | ||
335 | { | ||
336 | HEADER *header; | ||
337 | |||
338 | ns_msg handle; | ||
339 | ns_rr rr; | ||
340 | int i, n, rdlen; | ||
341 | const char *format = NULL; | ||
342 | char astr[INET6_ADDRSTRLEN], dname[MAXDNAME]; | ||
343 | const unsigned char *cp; | ||
344 | |||
345 | header = (HEADER *)msg; | ||
346 | if (!header->aa) | ||
347 | printf("Non-authoritative answer:\n"); | ||
348 | |||
349 | if (ns_initparse(msg, len, &handle) != 0) { | ||
350 | //printf("Unable to parse reply: %s\n", strerror(errno)); | ||
351 | return -1; | ||
352 | } | ||
353 | |||
354 | for (i = 0; i < ns_msg_count(handle, ns_s_an); i++) { | ||
355 | if (ns_parserr(&handle, ns_s_an, i, &rr) != 0) { | ||
356 | //printf("Unable to parse resource record: %s\n", strerror(errno)); | ||
357 | return -1; | ||
358 | } | ||
359 | |||
360 | rdlen = ns_rr_rdlen(rr); | ||
361 | |||
362 | switch (ns_rr_type(rr)) | ||
363 | { | ||
364 | case ns_t_a: | ||
365 | if (rdlen != 4) { | ||
366 | dbg("unexpected A record length %d\n", rdlen); | ||
367 | return -1; | ||
368 | } | ||
369 | inet_ntop(AF_INET, ns_rr_rdata(rr), astr, sizeof(astr)); | ||
370 | printf("Name:\t%s\nAddress: %s\n", ns_rr_name(rr), astr); | ||
371 | break; | ||
372 | |||
373 | #if ENABLE_FEATURE_IPV6 | ||
374 | case ns_t_aaaa: | ||
375 | if (rdlen != 16) { | ||
376 | dbg("unexpected AAAA record length %d\n", rdlen); | ||
377 | return -1; | ||
378 | } | ||
379 | inet_ntop(AF_INET6, ns_rr_rdata(rr), astr, sizeof(astr)); | ||
380 | /* bind-utils-9.11.3 uses the same format for A and AAAA answers */ | ||
381 | printf("Name:\t%s\nAddress: %s\n", ns_rr_name(rr), astr); | ||
382 | break; | ||
383 | #endif | ||
384 | |||
385 | case ns_t_ns: | ||
386 | if (!format) | ||
387 | format = "%s\tnameserver = %s\n"; | ||
388 | /* fall through */ | ||
389 | |||
390 | case ns_t_cname: | ||
391 | if (!format) | ||
392 | format = "%s\tcanonical name = %s\n"; | ||
393 | /* fall through */ | ||
394 | |||
395 | case ns_t_ptr: | ||
396 | if (!format) | ||
397 | format = "%s\tname = %s\n"; | ||
398 | if (ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), | ||
399 | ns_rr_rdata(rr), dname, sizeof(dname)) < 0 | ||
400 | ) { | ||
401 | //printf("Unable to uncompress domain: %s\n", strerror(errno)); | ||
402 | return -1; | ||
403 | } | ||
404 | printf(format, ns_rr_name(rr), dname); | ||
405 | break; | ||
406 | |||
407 | case ns_t_mx: | ||
408 | if (rdlen < 2) { | ||
409 | printf("MX record too short\n"); | ||
410 | return -1; | ||
411 | } | ||
412 | n = ns_get16(ns_rr_rdata(rr)); | ||
413 | if (ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), | ||
414 | ns_rr_rdata(rr) + 2, dname, sizeof(dname)) < 0 | ||
415 | ) { | ||
416 | //printf("Cannot uncompress MX domain: %s\n", strerror(errno)); | ||
417 | return -1; | ||
418 | } | ||
419 | printf("%s\tmail exchanger = %d %s\n", ns_rr_name(rr), n, dname); | ||
420 | break; | ||
421 | |||
422 | case ns_t_txt: | ||
423 | if (rdlen < 1) { | ||
424 | //printf("TXT record too short\n"); | ||
425 | return -1; | ||
426 | } | ||
427 | n = *(unsigned char *)ns_rr_rdata(rr); | ||
428 | if (n > 0) { | ||
429 | memset(dname, 0, sizeof(dname)); | ||
430 | memcpy(dname, ns_rr_rdata(rr) + 1, n); | ||
431 | printf("%s\ttext = \"%s\"\n", ns_rr_name(rr), dname); | ||
432 | } | ||
433 | break; | ||
434 | |||
435 | case ns_t_soa: | ||
436 | if (rdlen < 20) { | ||
437 | dbg("SOA record too short:%d\n", rdlen); | ||
438 | return -1; | ||
439 | } | ||
440 | |||
441 | printf("%s\n", ns_rr_name(rr)); | ||
442 | |||
443 | cp = ns_rr_rdata(rr); | ||
444 | n = ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), | ||
445 | cp, dname, sizeof(dname)); | ||
446 | if (n < 0) { | ||
447 | //printf("Unable to uncompress domain: %s\n", strerror(errno)); | ||
448 | return -1; | ||
449 | } | ||
450 | |||
451 | printf("\torigin = %s\n", dname); | ||
452 | cp += n; | ||
453 | |||
454 | n = ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), | ||
455 | cp, dname, sizeof(dname)); | ||
456 | if (n < 0) { | ||
457 | //printf("Unable to uncompress domain: %s\n", strerror(errno)); | ||
458 | return -1; | ||
459 | } | ||
460 | |||
461 | printf("\tmail addr = %s\n", dname); | ||
462 | cp += n; | ||
463 | |||
464 | printf("\tserial = %lu\n", ns_get32(cp)); | ||
465 | cp += 4; | ||
466 | |||
467 | printf("\trefresh = %lu\n", ns_get32(cp)); | ||
468 | cp += 4; | ||
469 | |||
470 | printf("\tretry = %lu\n", ns_get32(cp)); | ||
471 | cp += 4; | ||
472 | |||
473 | printf("\texpire = %lu\n", ns_get32(cp)); | ||
474 | cp += 4; | ||
475 | |||
476 | printf("\tminimum = %lu\n", ns_get32(cp)); | ||
477 | break; | ||
478 | |||
479 | default: | ||
480 | break; | ||
481 | } | ||
482 | } | ||
483 | |||
484 | return i; | ||
485 | } | ||
486 | |||
487 | /* | ||
488 | * Function logic borrowed & modified from musl libc, res_msend.c | ||
489 | * G.query_count is always > 0. | ||
490 | */ | ||
491 | static int send_queries(struct ns *ns) | ||
492 | { | ||
493 | unsigned char reply[512]; | ||
494 | uint8_t rcode; | ||
495 | len_and_sockaddr *local_lsa; | ||
496 | struct pollfd pfd; | ||
497 | int servfail_retry = 0; | ||
498 | int n_replies = 0; | ||
499 | // int save_idx = 0; | ||
500 | unsigned retry_interval; | ||
501 | unsigned timeout = G.default_timeout * 1000; | ||
502 | unsigned tstart, tsent, tcur; | ||
503 | |||
504 | pfd.events = POLLIN; | ||
505 | pfd.fd = xsocket_type(&local_lsa, ns->lsa->u.sa.sa_family, SOCK_DGRAM); | ||
506 | /* | ||
507 | * local_lsa has "null" address and port 0 now. | ||
508 | * bind() ensures we have a *particular port* selected by kernel | ||
509 | * and remembered in fd, thus later recv(fd) | ||
510 | * receives only packets sent to this port. | ||
511 | */ | ||
512 | xbind(pfd.fd, &local_lsa->u.sa, local_lsa->len); | ||
513 | free(local_lsa); | ||
514 | /* Make read/writes know the destination */ | ||
515 | xconnect(pfd.fd, &ns->lsa->u.sa, ns->lsa->len); | ||
516 | ndelay_on(pfd.fd); | ||
517 | |||
518 | retry_interval = timeout / G.default_retry; | ||
519 | tstart = tcur = monotonic_ms(); | ||
520 | goto send; | ||
521 | |||
522 | while (tcur - tstart < timeout) { | ||
523 | int qn; | ||
524 | int recvlen; | ||
525 | |||
526 | if (tcur - tsent >= retry_interval) { | ||
527 | send: | ||
528 | for (qn = 0; qn < G.query_count; qn++) { | ||
529 | if (G.query[qn].qlen == 0) | ||
530 | continue; /* this one was replied already */ | ||
531 | |||
532 | if (write(pfd.fd, G.query[qn].query, G.query[qn].qlen) < 0) { | ||
533 | bb_perror_msg("write to '%s'", ns->name); | ||
534 | n_replies = -1; /* "no go, try next server" */ | ||
535 | goto ret; | ||
536 | } | ||
537 | dbg("query %u sent\n", qn); | ||
538 | } | ||
539 | tsent = tcur; | ||
540 | servfail_retry = 2 * G.query_count; | ||
541 | } | ||
542 | |||
543 | /* Wait for a response, or until time to retry */ | ||
544 | if (poll(&pfd, 1, retry_interval - (tcur - tsent)) <= 0) | ||
545 | goto next; | ||
546 | |||
547 | recvlen = read(pfd.fd, reply, sizeof(reply)); | ||
548 | if (recvlen < 0) { | ||
549 | bb_perror_msg("read"); | ||
550 | next: | ||
551 | tcur = monotonic_ms(); | ||
552 | continue; | ||
553 | } | ||
554 | |||
555 | if (ns->replies++ == 0) { | ||
556 | printf("Server:\t\t%s\n", ns->name); | ||
557 | printf("Address:\t%s\n\n", | ||
558 | auto_string(xmalloc_sockaddr2dotted(&ns->lsa->u.sa)) | ||
559 | ); | ||
560 | /* In "Address", bind-utils-9.11.3 show port after a hash: "1.2.3.4#53" */ | ||
561 | /* Should we do the same? */ | ||
562 | } | ||
563 | |||
564 | /* Non-identifiable packet */ | ||
565 | if (recvlen < 4) { | ||
566 | dbg("read is too short:%d\n", recvlen); | ||
567 | goto next; | ||
568 | } | ||
569 | |||
570 | /* Find which query this answer goes with, if any */ | ||
571 | // qn = save_idx; | ||
572 | qn = 0; | ||
573 | for (;;) { | ||
574 | if (memcmp(reply, G.query[qn].query, 2) == 0) { | ||
575 | dbg("response matches query %u\n", qn); | ||
576 | break; | ||
577 | } | ||
578 | if (++qn >= G.query_count) { | ||
579 | dbg("response does not match any query\n"); | ||
580 | goto next; | ||
581 | } | ||
582 | } | ||
583 | |||
584 | if (G.query[qn].qlen == 0) { | ||
585 | dbg("dropped duplicate response to query %u\n", qn); | ||
586 | goto next; | ||
587 | } | ||
588 | |||
589 | rcode = reply[3] & 0x0f; | ||
590 | dbg("query %u rcode:%s\n", qn, rcodes[rcode]); | ||
591 | |||
592 | /* Retry immediately on SERVFAIL */ | ||
593 | if (rcode == 2) { | ||
594 | ns->failures++; | ||
595 | if (servfail_retry) { | ||
596 | servfail_retry--; | ||
597 | write(pfd.fd, G.query[qn].query, G.query[qn].qlen); | ||
598 | dbg("query %u resent\n", qn); | ||
599 | goto next; | ||
600 | } | ||
601 | } | ||
602 | |||
603 | /* Process reply */ | ||
604 | G.query[qn].qlen = 0; /* flag: "reply received" */ | ||
605 | tcur = monotonic_ms(); | ||
606 | #if 1 | ||
607 | if (option_mask32 & OPT_debug) { | ||
608 | printf("Query #%d completed in %ums:\n", qn, tcur - tstart); | ||
609 | } | ||
610 | if (rcode != 0) { | ||
611 | printf("** server can't find %s: %s\n", | ||
612 | G.query[qn].name, rcodes[rcode]); | ||
613 | } else { | ||
614 | if (parse_reply(reply, recvlen) < 0) | ||
615 | printf("*** Can't find %s: Parse error\n", G.query[qn].name); | ||
616 | } | ||
617 | bb_putchar('\n'); | ||
618 | n_replies++; | ||
619 | if (n_replies >= G.query_count) | ||
620 | goto ret; | ||
621 | #else | ||
622 | //used to store replies and process them later | ||
623 | G.query[qn].latency = tcur - tstart; | ||
624 | n_replies++; | ||
625 | if (qn != save_idx) { | ||
626 | /* "wrong" receive buffer, move to correct one */ | ||
627 | memcpy(G.query[qn].reply, G.query[save_idx].reply, recvlen); | ||
628 | continue; | ||
629 | } | ||
630 | /* G.query[0..save_idx] have replies, move to next one, if exists */ | ||
631 | for (;;) { | ||
632 | save_idx++; | ||
633 | if (save_idx >= G.query_count) | ||
634 | goto ret; /* all are full: we have all results */ | ||
635 | if (!G.query[save_idx].rlen) | ||
636 | break; /* this one is empty */ | ||
637 | } | ||
638 | #endif | ||
639 | } /* while() */ | ||
640 | |||
641 | ret: | ||
642 | close(pfd.fd); | ||
643 | |||
644 | return n_replies; | ||
645 | } | ||
646 | |||
647 | static void add_ns(const char *addr) | ||
648 | { | ||
649 | struct ns *ns; | ||
650 | unsigned count; | ||
651 | |||
652 | dbg("%s: addr:'%s'\n", __func__, addr); | ||
653 | |||
654 | count = G.serv_count++; | ||
655 | |||
656 | G.server = xrealloc_vector(G.server, /*8=2^3:*/ 3, count); | ||
657 | ns = &G.server[count]; | ||
658 | ns->name = addr; | ||
659 | ns->lsa = xhost2sockaddr(addr, G.default_port); | ||
660 | /*ns->replies = 0; - already is */ | ||
661 | /*ns->failures = 0; - already is */ | ||
662 | } | ||
663 | |||
664 | static void parse_resolvconf(void) | ||
665 | { | ||
666 | FILE *resolv; | ||
667 | |||
668 | resolv = fopen("/etc/resolv.conf", "r"); | ||
669 | if (resolv) { | ||
670 | char line[128], *p; | ||
671 | |||
672 | while (fgets(line, sizeof(line), resolv)) { | ||
673 | p = strtok(line, " \t\n"); | ||
674 | |||
675 | if (!p || strcmp(p, "nameserver") != 0) | ||
676 | continue; | ||
677 | |||
678 | p = strtok(NULL, " \t\n"); | ||
679 | |||
680 | if (!p) | ||
681 | continue; | ||
682 | |||
683 | add_ns(xstrdup(p)); | ||
684 | } | ||
685 | |||
686 | fclose(resolv); | ||
687 | } | ||
688 | } | ||
689 | |||
690 | static void add_query(int type, const char *dname) | ||
691 | { | ||
692 | struct query *new_q; | ||
693 | unsigned count; | ||
694 | ssize_t qlen; | ||
695 | |||
696 | count = G.query_count++; | ||
697 | |||
698 | G.query = xrealloc_vector(G.query, /*2=2^1:*/ 1, count); | ||
699 | new_q = &G.query[count]; | ||
700 | |||
701 | dbg("new query#%u type %u for '%s'\n", count, type, dname); | ||
702 | new_q->name = dname; | ||
703 | |||
704 | qlen = res_mkquery(QUERY, dname, C_IN, type, | ||
705 | /*data:*/ NULL, /*datalen:*/ 0, | ||
706 | /*newrr:*/ NULL, | ||
707 | new_q->query, sizeof(new_q->query) | ||
708 | ); | ||
709 | new_q->qlen = qlen; | ||
710 | } | ||
711 | |||
712 | static char *make_ptr(const char *addrstr) | ||
713 | { | ||
714 | unsigned char addr[16]; | ||
715 | int i; | ||
716 | |||
717 | #if ENABLE_FEATURE_IPV6 | ||
718 | if (inet_pton(AF_INET6, addrstr, addr)) { | ||
719 | if (memcmp(addr, v4_mapped, 12) != 0) { | ||
720 | char resbuf[80]; | ||
721 | char *ptr = resbuf; | ||
722 | for (i = 0; i < 16; i++) { | ||
723 | *ptr++ = 0x20 | bb_hexdigits_upcase[(unsigned char)addr[15 - i] & 0xf]; | ||
724 | *ptr++ = '.'; | ||
725 | *ptr++ = 0x20 | bb_hexdigits_upcase[(unsigned char)addr[15 - i] >> 4]; | ||
726 | *ptr++ = '.'; | ||
727 | } | ||
728 | strcpy(ptr, "ip6.arpa"); | ||
729 | return xstrdup(resbuf); | ||
730 | } | ||
731 | return xasprintf("%u.%u.%u.%u.in-addr.arpa", | ||
732 | addr[15], addr[14], addr[13], addr[12]); | ||
733 | } | ||
734 | #endif | ||
735 | |||
736 | if (inet_pton(AF_INET, addrstr, addr)) { | ||
737 | return xasprintf("%u.%u.%u.%u.in-addr.arpa", | ||
738 | addr[3], addr[2], addr[1], addr[0]); | ||
739 | } | ||
740 | |||
741 | return NULL; | ||
742 | } | ||
743 | |||
744 | int nslookup_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | ||
745 | int nslookup_main(int argc UNUSED_PARAM, char **argv) | ||
746 | { | ||
747 | unsigned types; | ||
748 | int rc; | ||
749 | int err; | ||
750 | |||
751 | INIT_G(); | ||
752 | |||
753 | /* manpage: "Options can also be specified on the command line | ||
754 | * if they precede the arguments and are prefixed with a hyphen." | ||
755 | */ | ||
756 | types = 0; | ||
757 | argv++; | ||
758 | for (;;) { | ||
759 | const char *options = | ||
760 | // bind-utils-9.11.3 accept these: | ||
761 | // class= cl= | ||
762 | // type= ty= querytype= query= qu= q= | ||
763 | // domain= do= | ||
764 | // port= po= | ||
765 | // timeout= t= | ||
766 | // retry= ret= | ||
767 | // ndots= | ||
768 | // recurse | ||
769 | // norecurse | ||
770 | // defname | ||
771 | // nodefname | ||
772 | // vc | ||
773 | // novc | ||
774 | // debug | ||
775 | // nodebug | ||
776 | // d2 | ||
777 | // nod2 | ||
778 | // search | ||
779 | // nosearch | ||
780 | // sil | ||
781 | // fail | ||
782 | // nofail | ||
783 | // ver (prints version and exits) | ||
784 | "type\0" /* 0 */ | ||
785 | "querytype\0" /* 1 */ | ||
786 | "port\0" /* 2 */ | ||
787 | "retry\0" /* 3 */ | ||
788 | "debug\0" /* 4 */ | ||
789 | "t\0" /* disambiguate with "type": else -t=2 fails */ | ||
790 | "timeout\0" /* 6 */ | ||
791 | ""; | ||
792 | int i; | ||
793 | char *arg; | ||
794 | char *val; | ||
795 | |||
796 | if (!*argv) | ||
797 | bb_show_usage(); | ||
798 | if (argv[0][0] != '-') | ||
799 | break; | ||
800 | |||
801 | /* Separate out "=val" part */ | ||
802 | arg = (*argv++) + 1; | ||
803 | val = strchrnul(arg, '='); | ||
804 | if (*val) | ||
805 | *val++ = '\0'; | ||
806 | |||
807 | i = index_in_substrings(options, arg); | ||
808 | //bb_error_msg("i:%d arg:'%s' val:'%s'", i, arg, val); | ||
809 | if (i < 0) | ||
810 | bb_show_usage(); | ||
811 | |||
812 | if (i <= 1) { | ||
813 | for (i = 0;; i++) { | ||
814 | if (i == ARRAY_SIZE(qtypes)) | ||
815 | bb_error_msg_and_die("invalid query type \"%s\"", val); | ||
816 | if (strcasecmp(qtypes[i].name, val) == 0) | ||
817 | break; | ||
818 | } | ||
819 | types |= (1 << i); | ||
820 | continue; | ||
821 | } | ||
822 | if (i == 2) { | ||
823 | G.default_port = xatou_range(val, 1, 0xffff); | ||
824 | } | ||
825 | if (i == 3) { | ||
826 | G.default_retry = xatou_range(val, 1, INT_MAX); | ||
827 | } | ||
828 | if (i == 4) { | ||
829 | option_mask32 |= OPT_debug; | ||
830 | } | ||
831 | if (i > 4) { | ||
832 | G.default_timeout = xatou_range(val, 1, INT_MAX / 1000); | ||
833 | } | ||
834 | } | ||
835 | |||
836 | if (types == 0) { | ||
837 | /* No explicit type given, guess query type. | ||
838 | * If we can convert the domain argument into a ptr (means that | ||
839 | * inet_pton() could read it) we assume a PTR request, else | ||
840 | * we issue A+AAAA queries and switch to an output format | ||
841 | * mimicking the one of the traditional nslookup applet. | ||
842 | */ | ||
843 | char *ptr; | ||
844 | |||
845 | ptr = make_ptr(argv[0]); | ||
846 | if (ptr) { | ||
847 | add_query(T_PTR, ptr); | ||
848 | } else { | ||
849 | add_query(T_A, argv[0]); | ||
850 | #if ENABLE_FEATURE_IPV6 | ||
851 | add_query(T_AAAA, argv[0]); | ||
852 | #endif | ||
853 | } | ||
854 | } else { | ||
855 | int c; | ||
856 | for (c = 0; c < ARRAY_SIZE(qtypes); c++) { | ||
857 | if (types & (1 << c)) | ||
858 | add_query(qtypes[c].type, argv[0]); | ||
859 | } | ||
860 | } | ||
861 | |||
862 | /* Use given DNS server if present */ | ||
863 | if (argv[1]) { | ||
864 | if (argv[2]) | ||
865 | bb_show_usage(); | ||
866 | add_ns(argv[1]); | ||
867 | } else { | ||
868 | parse_resolvconf(); | ||
869 | /* Fall back to localhost if we could not find NS in resolv.conf */ | ||
870 | if (G.serv_count == 0) | ||
871 | add_ns("127.0.0.1"); | ||
872 | } | ||
873 | |||
874 | for (rc = 0; rc < G.serv_count;) { | ||
875 | int c; | ||
876 | |||
877 | c = send_queries(&G.server[rc]); | ||
878 | if (c > 0) { | ||
879 | /* more than zero replies received */ | ||
880 | #if 0 /* which version does this? */ | ||
881 | if (option_mask32 & OPT_debug) { | ||
882 | printf("Replies:\t%d\n", G.server[rc].replies); | ||
883 | printf("Failures:\t%d\n\n", G.server[rc].failures); | ||
884 | } | ||
885 | #endif | ||
886 | break; | ||
887 | //FIXME: we "break" even though some queries may still be not answered, and other servers may know them? | ||
888 | } | ||
889 | /* c = 0: timed out waiting for replies */ | ||
890 | /* c < 0: error (message already printed) */ | ||
891 | rc++; | ||
892 | if (rc >= G.serv_count) { | ||
893 | // | ||
894 | // NB: bind-utils-9.11.3 behavior (all to stdout, not stderr): | ||
895 | // | ||
896 | // $ nslookup gmail.com 8.8.8.8 | ||
897 | // ;; connection timed out; no servers could be reached | ||
898 | // | ||
899 | // Using TCP mode: | ||
900 | // $ nslookup -vc gmail.com 8.8.8.8; echo EXITCODE:$? | ||
901 | // <~10 sec> | ||
902 | // ;; Connection to 8.8.8.8#53(8.8.8.8) for gmail.com failed: timed out. | ||
903 | // <~10 sec> | ||
904 | // ;; Connection to 8.8.8.8#53(8.8.8.8) for gmail.com failed: timed out. | ||
905 | // <~10 sec> | ||
906 | // ;; connection timed out; no servers could be reached | ||
907 | // ;; Connection to 8.8.8.8#53(8.8.8.8) for gmail.com failed: timed out. | ||
908 | // <empty line> | ||
909 | // EXITCODE:1 | ||
910 | // $ _ | ||
911 | printf(";; connection timed out; no servers could be reached\n\n"); | ||
912 | return EXIT_FAILURE; | ||
913 | } | ||
914 | } | ||
915 | |||
916 | err = 0; | ||
917 | for (rc = 0; rc < G.query_count; rc++) { | ||
918 | if (G.query[rc].qlen) { | ||
919 | printf("*** Can't find %s: No answer\n", G.query[rc].name); | ||
920 | err = 1; | ||
921 | } | ||
922 | } | ||
923 | if (err) /* should this affect exicode too? */ | ||
924 | bb_putchar('\n'); | ||
925 | |||
926 | if (ENABLE_FEATURE_CLEAN_UP) { | ||
927 | free(G.server); | ||
928 | free(G.query); | ||
929 | } | ||
930 | |||
931 | return EXIT_SUCCESS; | ||
932 | } | ||
933 | |||
934 | #endif | ||
diff --git a/networking/telnet.c b/networking/telnet.c index 15d6a08d8..1e6be85bd 100644 --- a/networking/telnet.c +++ b/networking/telnet.c | |||
@@ -643,7 +643,8 @@ int telnet_main(int argc UNUSED_PARAM, char **argv) | |||
643 | if (!*argv) | 643 | if (!*argv) |
644 | bb_show_usage(); | 644 | bb_show_usage(); |
645 | host = *argv++; | 645 | host = *argv++; |
646 | port = bb_lookup_port(*argv ? *argv++ : "telnet", "tcp", 23); | 646 | port = *argv ? bb_lookup_port(*argv++, "tcp", 23) |
647 | : bb_lookup_std_port("telnet", "tcp", 23); | ||
647 | if (*argv) /* extra params?? */ | 648 | if (*argv) /* extra params?? */ |
648 | bb_show_usage(); | 649 | bb_show_usage(); |
649 | 650 | ||
diff --git a/networking/udhcp/common.c b/networking/udhcp/common.c index d3eea5def..fbf9c6878 100644 --- a/networking/udhcp/common.c +++ b/networking/udhcp/common.c | |||
@@ -378,34 +378,24 @@ int FAST_FUNC udhcp_str2nip(const char *str, void *arg) | |||
378 | * Called to parse "udhcpc -x OPTNAME:OPTVAL" | 378 | * Called to parse "udhcpc -x OPTNAME:OPTVAL" |
379 | * and to parse udhcpd.conf's "opt OPTNAME OPTVAL" directives. | 379 | * and to parse udhcpd.conf's "opt OPTNAME OPTVAL" directives. |
380 | */ | 380 | */ |
381 | /* helper for the helper */ | 381 | /* helper: add an option to the opt_list */ |
382 | static char *allocate_tempopt_if_needed( | 382 | static NOINLINE void attach_option( |
383 | struct option_set **opt_list, | ||
383 | const struct dhcp_optflag *optflag, | 384 | const struct dhcp_optflag *optflag, |
384 | char *buffer, | 385 | char *buffer, |
385 | int *length_p) | 386 | int length) |
386 | { | 387 | { |
388 | struct option_set *existing; | ||
387 | char *allocated = NULL; | 389 | char *allocated = NULL; |
390 | |||
388 | if ((optflag->flags & OPTION_TYPE_MASK) == OPTION_BIN) { | 391 | if ((optflag->flags & OPTION_TYPE_MASK) == OPTION_BIN) { |
389 | const char *end; | 392 | const char *end; |
390 | allocated = xstrdup(buffer); /* more than enough */ | 393 | allocated = xstrdup(buffer); /* more than enough */ |
391 | end = hex2bin(allocated, buffer, 255); | 394 | end = hex2bin(allocated, buffer, 255); |
392 | if (errno) | 395 | if (errno) |
393 | bb_error_msg_and_die("malformed hex string '%s'", buffer); | 396 | bb_error_msg_and_die("malformed hex string '%s'", buffer); |
394 | *length_p = end - allocated; | 397 | length = end - allocated; |
395 | } | 398 | } |
396 | return allocated; | ||
397 | } | ||
398 | /* helper: add an option to the opt_list */ | ||
399 | static NOINLINE void attach_option( | ||
400 | struct option_set **opt_list, | ||
401 | const struct dhcp_optflag *optflag, | ||
402 | char *buffer, | ||
403 | int length) | ||
404 | { | ||
405 | struct option_set *existing; | ||
406 | char *allocated; | ||
407 | |||
408 | allocated = allocate_tempopt_if_needed(optflag, buffer, &length); | ||
409 | #if ENABLE_FEATURE_UDHCP_RFC3397 | 399 | #if ENABLE_FEATURE_UDHCP_RFC3397 |
410 | if ((optflag->flags & OPTION_TYPE_MASK) == OPTION_DNS_STRING) { | 400 | if ((optflag->flags & OPTION_TYPE_MASK) == OPTION_DNS_STRING) { |
411 | /* reuse buffer and length for RFC1035-formatted string */ | 401 | /* reuse buffer and length for RFC1035-formatted string */ |
@@ -463,12 +453,12 @@ static NOINLINE void attach_option( | |||
463 | int FAST_FUNC udhcp_str2optset(const char *const_str, void *arg, const struct dhcp_optflag *optflags, const char *option_strings) | 453 | int FAST_FUNC udhcp_str2optset(const char *const_str, void *arg, const struct dhcp_optflag *optflags, const char *option_strings) |
464 | { | 454 | { |
465 | struct option_set **opt_list = arg; | 455 | struct option_set **opt_list = arg; |
466 | char *opt, *val; | 456 | char *opt; |
467 | char *str; | 457 | char *str; |
468 | const struct dhcp_optflag *optflag; | 458 | const struct dhcp_optflag *optflag; |
469 | struct dhcp_optflag bin_optflag; | 459 | struct dhcp_optflag userdef_optflag; |
470 | unsigned optcode; | 460 | unsigned optcode; |
471 | int retval, length; | 461 | int retval; |
472 | /* IP_PAIR needs 8 bytes, STATIC_ROUTES needs 9 max */ | 462 | /* IP_PAIR needs 8 bytes, STATIC_ROUTES needs 9 max */ |
473 | char buffer[9] ALIGNED(4); | 463 | char buffer[9] ALIGNED(4); |
474 | uint16_t *result_u16 = (uint16_t *) buffer; | 464 | uint16_t *result_u16 = (uint16_t *) buffer; |
@@ -476,28 +466,40 @@ int FAST_FUNC udhcp_str2optset(const char *const_str, void *arg, const struct dh | |||
476 | 466 | ||
477 | /* Cheat, the only *const* str possible is "" */ | 467 | /* Cheat, the only *const* str possible is "" */ |
478 | str = (char *) const_str; | 468 | str = (char *) const_str; |
479 | opt = strtok(str, " \t="); | 469 | opt = strtok(str, " \t=:"); |
480 | if (!opt) | 470 | if (!opt) |
481 | return 0; | 471 | return 0; |
482 | 472 | ||
483 | optcode = bb_strtou(opt, NULL, 0); | 473 | optcode = bb_strtou(opt, NULL, 0); |
484 | if (!errno && optcode < 255) { | 474 | if (!errno && optcode < 255) { |
485 | /* Raw (numeric) option code */ | 475 | /* Raw (numeric) option code. |
486 | bin_optflag.flags = OPTION_BIN; | 476 | * Initially assume binary (hex-str), but if "str" or 'str' |
487 | bin_optflag.code = optcode; | 477 | * is seen later, switch to STRING. |
488 | optflag = &bin_optflag; | 478 | */ |
479 | userdef_optflag.flags = OPTION_BIN; | ||
480 | userdef_optflag.code = optcode; | ||
481 | optflag = &userdef_optflag; | ||
489 | } else { | 482 | } else { |
490 | optflag = &optflags[udhcp_option_idx(opt, option_strings)]; | 483 | optflag = &optflags[udhcp_option_idx(opt, option_strings)]; |
491 | } | 484 | } |
492 | 485 | ||
486 | /* Loop to handle OPTION_LIST case, else execute just once */ | ||
493 | retval = 0; | 487 | retval = 0; |
494 | do { | 488 | do { |
495 | val = strtok(NULL, ", \t"); | 489 | int length; |
490 | char *val; | ||
491 | |||
492 | if (optflag->flags == OPTION_BIN) | ||
493 | val = trim(strtok(NULL, "")); /* do not split "'q w e'" */ | ||
494 | else | ||
495 | val = strtok(NULL, ", \t"); | ||
496 | if (!val) | 496 | if (!val) |
497 | break; | 497 | break; |
498 | |||
498 | length = dhcp_option_lengths[optflag->flags & OPTION_TYPE_MASK]; | 499 | length = dhcp_option_lengths[optflag->flags & OPTION_TYPE_MASK]; |
499 | retval = 0; | 500 | retval = 0; |
500 | opt = buffer; /* new meaning for variable opt */ | 501 | opt = buffer; /* new meaning for variable opt */ |
502 | |||
501 | switch (optflag->flags & OPTION_TYPE_MASK) { | 503 | switch (optflag->flags & OPTION_TYPE_MASK) { |
502 | case OPTION_IP: | 504 | case OPTION_IP: |
503 | retval = udhcp_str2nip(val, buffer); | 505 | retval = udhcp_str2nip(val, buffer); |
@@ -510,6 +512,7 @@ int FAST_FUNC udhcp_str2optset(const char *const_str, void *arg, const struct dh | |||
510 | if (retval) | 512 | if (retval) |
511 | retval = udhcp_str2nip(val, buffer + 4); | 513 | retval = udhcp_str2nip(val, buffer + 4); |
512 | break; | 514 | break; |
515 | case_OPTION_STRING: | ||
513 | case OPTION_STRING: | 516 | case OPTION_STRING: |
514 | case OPTION_STRING_HOST: | 517 | case OPTION_STRING_HOST: |
515 | #if ENABLE_FEATURE_UDHCP_RFC3397 | 518 | #if ENABLE_FEATURE_UDHCP_RFC3397 |
@@ -577,12 +580,26 @@ int FAST_FUNC udhcp_str2optset(const char *const_str, void *arg, const struct dh | |||
577 | } | 580 | } |
578 | break; | 581 | break; |
579 | } | 582 | } |
580 | case OPTION_BIN: /* handled in attach_option() */ | 583 | case OPTION_BIN: |
584 | /* Raw (numeric) option code. Is it a string? */ | ||
585 | if (val[0] == '"' || val[0] == '\'') { | ||
586 | char delim = val[0]; | ||
587 | char *end = last_char_is(val + 1, delim); | ||
588 | if (end) { | ||
589 | *end = '\0'; | ||
590 | val++; | ||
591 | userdef_optflag.flags = OPTION_STRING; | ||
592 | goto case_OPTION_STRING; | ||
593 | } | ||
594 | } | ||
595 | /* No: hex-str option, handled in attach_option() */ | ||
581 | opt = val; | 596 | opt = val; |
582 | retval = 1; | 597 | retval = 1; |
598 | break; | ||
583 | default: | 599 | default: |
584 | break; | 600 | break; |
585 | } | 601 | } |
602 | |||
586 | if (retval) | 603 | if (retval) |
587 | attach_option(opt_list, optflag, opt, length); | 604 | attach_option(opt_list, optflag, opt, length); |
588 | } while (retval && (optflag->flags & OPTION_LIST)); | 605 | } while (retval && (optflag->flags & OPTION_LIST)); |
diff --git a/networking/udhcp/d6_dhcpc.c b/networking/udhcp/d6_dhcpc.c index 289df66ee..85d9da724 100644 --- a/networking/udhcp/d6_dhcpc.c +++ b/networking/udhcp/d6_dhcpc.c | |||
@@ -1063,6 +1063,7 @@ static void client_background(void) | |||
1063 | //usage: "\n -x hostname:bbox - option 12" | 1063 | //usage: "\n -x hostname:bbox - option 12" |
1064 | //usage: "\n -x lease:3600 - option 51 (lease time)" | 1064 | //usage: "\n -x lease:3600 - option 51 (lease time)" |
1065 | //usage: "\n -x 0x3d:0100BEEFC0FFEE - option 61 (client id)" | 1065 | //usage: "\n -x 0x3d:0100BEEFC0FFEE - option 61 (client id)" |
1066 | //usage: "\n -x 14:'\"dumpfile\"' - option 14 (shell-quoted)" | ||
1066 | //usage: IF_UDHCP_VERBOSE( | 1067 | //usage: IF_UDHCP_VERBOSE( |
1067 | //usage: "\n -v Verbose" | 1068 | //usage: "\n -v Verbose" |
1068 | //usage: ) | 1069 | //usage: ) |
@@ -1155,15 +1156,9 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv) | |||
1155 | } | 1156 | } |
1156 | } | 1157 | } |
1157 | while (list_x) { | 1158 | while (list_x) { |
1158 | char *optstr = llist_pop(&list_x); | 1159 | char *optstr = xstrdup(llist_pop(&list_x)); |
1159 | char *colon = strchr(optstr, ':'); | ||
1160 | if (colon) | ||
1161 | *colon = ' '; | ||
1162 | /* now it looks similar to udhcpd's config file line: | ||
1163 | * "optname optval", using the common routine: */ | ||
1164 | udhcp_str2optset(optstr, &client_config.options, d6_optflags, d6_option_strings); | 1160 | udhcp_str2optset(optstr, &client_config.options, d6_optflags, d6_option_strings); |
1165 | if (colon) | 1161 | free(optstr); |
1166 | *colon = ':'; /* restore it for NOMMU reexec */ | ||
1167 | } | 1162 | } |
1168 | 1163 | ||
1169 | if (d6_read_interface(client_config.interface, | 1164 | if (d6_read_interface(client_config.interface, |
diff --git a/networking/udhcp/dhcpc.c b/networking/udhcp/dhcpc.c index 90b07bf4b..bd9e8fdc2 100644 --- a/networking/udhcp/dhcpc.c +++ b/networking/udhcp/dhcpc.c | |||
@@ -1224,6 +1224,7 @@ static void client_background(void) | |||
1224 | //usage: "\n -x hostname:bbox - option 12" | 1224 | //usage: "\n -x hostname:bbox - option 12" |
1225 | //usage: "\n -x lease:3600 - option 51 (lease time)" | 1225 | //usage: "\n -x lease:3600 - option 51 (lease time)" |
1226 | //usage: "\n -x 0x3d:0100BEEFC0FFEE - option 61 (client id)" | 1226 | //usage: "\n -x 0x3d:0100BEEFC0FFEE - option 61 (client id)" |
1227 | //usage: "\n -x 14:'\"dumpfile\"' - option 14 (shell-quoted)" | ||
1227 | //usage: "\n -F NAME Ask server to update DNS mapping for NAME" | 1228 | //usage: "\n -F NAME Ask server to update DNS mapping for NAME" |
1228 | //usage: "\n -V VENDOR Vendor identifier (default 'udhcp VERSION')" | 1229 | //usage: "\n -V VENDOR Vendor identifier (default 'udhcp VERSION')" |
1229 | //usage: "\n -C Don't send MAC as client identifier" | 1230 | //usage: "\n -C Don't send MAC as client identifier" |
@@ -1335,15 +1336,9 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv) | |||
1335 | } | 1336 | } |
1336 | } | 1337 | } |
1337 | while (list_x) { | 1338 | while (list_x) { |
1338 | char *optstr = llist_pop(&list_x); | 1339 | char *optstr = xstrdup(llist_pop(&list_x)); |
1339 | char *colon = strchr(optstr, ':'); | ||
1340 | if (colon) | ||
1341 | *colon = ' '; | ||
1342 | /* now it looks similar to udhcpd's config file line: | ||
1343 | * "optname optval", using the common routine: */ | ||
1344 | udhcp_str2optset(optstr, &client_config.options, dhcp_optflags, dhcp_option_strings); | 1340 | udhcp_str2optset(optstr, &client_config.options, dhcp_optflags, dhcp_option_strings); |
1345 | if (colon) | 1341 | free(optstr); |
1346 | *colon = ':'; /* restore it for NOMMU reexec */ | ||
1347 | } | 1342 | } |
1348 | 1343 | ||
1349 | if (udhcp_read_interface(client_config.interface, | 1344 | if (udhcp_read_interface(client_config.interface, |
diff --git a/networking/wget.c b/networking/wget.c index 5ff31d278..85eae061b 100644 --- a/networking/wget.c +++ b/networking/wget.c | |||
@@ -511,23 +511,23 @@ static void parse_url(const char *src_url, struct host_info *h) | |||
511 | *p = '\0'; | 511 | *p = '\0'; |
512 | h->host = p + 3; | 512 | h->host = p + 3; |
513 | if (strcmp(url, P_FTP) == 0) { | 513 | if (strcmp(url, P_FTP) == 0) { |
514 | h->port = bb_lookup_port(P_FTP, "tcp", 21); | 514 | h->port = bb_lookup_std_port(P_FTP, "tcp", 21); |
515 | } else | 515 | } else |
516 | #if SSL_SUPPORTED | 516 | #if SSL_SUPPORTED |
517 | # if ENABLE_FEATURE_WGET_HTTPS | 517 | # if ENABLE_FEATURE_WGET_HTTPS |
518 | if (strcmp(url, P_FTPS) == 0) { | 518 | if (strcmp(url, P_FTPS) == 0) { |
519 | h->port = bb_lookup_port(P_FTPS, "tcp", 990); | 519 | h->port = bb_lookup_std_port(P_FTPS, "tcp", 990); |
520 | h->protocol = P_FTPS; | 520 | h->protocol = P_FTPS; |
521 | } else | 521 | } else |
522 | # endif | 522 | # endif |
523 | if (strcmp(url, P_HTTPS) == 0) { | 523 | if (strcmp(url, P_HTTPS) == 0) { |
524 | h->port = bb_lookup_port(P_HTTPS, "tcp", 443); | 524 | h->port = bb_lookup_std_port(P_HTTPS, "tcp", 443); |
525 | h->protocol = P_HTTPS; | 525 | h->protocol = P_HTTPS; |
526 | } else | 526 | } else |
527 | #endif | 527 | #endif |
528 | if (strcmp(url, P_HTTP) == 0) { | 528 | if (strcmp(url, P_HTTP) == 0) { |
529 | http: | 529 | http: |
530 | h->port = bb_lookup_port(P_HTTP, "tcp", 80); | 530 | h->port = bb_lookup_std_port(P_HTTP, "tcp", 80); |
531 | h->protocol = P_HTTP; | 531 | h->protocol = P_HTTP; |
532 | } else { | 532 | } else { |
533 | *p = ':'; | 533 | *p = ':'; |
@@ -545,7 +545,7 @@ static void parse_url(const char *src_url, struct host_info *h) | |||
545 | // and saves 'index.html?var=a%2Fb' (we save 'b') | 545 | // and saves 'index.html?var=a%2Fb' (we save 'b') |
546 | // wget 'http://busybox.net?login=john@doe': | 546 | // wget 'http://busybox.net?login=john@doe': |
547 | // request: 'GET /?login=john@doe HTTP/1.0' | 547 | // request: 'GET /?login=john@doe HTTP/1.0' |
548 | // saves: 'index.html?login=john@doe' (we save '?login=john@doe') | 548 | // saves: 'index.html?login=john@doe' (we save 'login=john@doe') |
549 | // wget 'http://busybox.net#test/test': | 549 | // wget 'http://busybox.net#test/test': |
550 | // request: 'GET / HTTP/1.0' | 550 | // request: 'GET / HTTP/1.0' |
551 | // saves: 'index.html' (we save 'test') | 551 | // saves: 'index.html' (we save 'test') |
@@ -559,13 +559,13 @@ static void parse_url(const char *src_url, struct host_info *h) | |||
559 | } else if (*sp == '/') { | 559 | } else if (*sp == '/') { |
560 | *sp = '\0'; | 560 | *sp = '\0'; |
561 | h->path = sp + 1; | 561 | h->path = sp + 1; |
562 | } else { // '#' or '?' | 562 | } else { |
563 | // sp points to '#' or '?' | ||
564 | // Note: | ||
563 | // http://busybox.net?login=john@doe is a valid URL | 565 | // http://busybox.net?login=john@doe is a valid URL |
564 | // memmove converts to: | 566 | // (without '/' between ".net" and "?"), |
565 | // http:/busybox.nett?login=john@doe... | 567 | // can't store NUL at sp[-1] - this destroys hostname. |
566 | memmove(h->host - 1, h->host, sp - h->host); | 568 | *sp++ = '\0'; |
567 | h->host--; | ||
568 | sp[-1] = '\0'; | ||
569 | h->path = sp; | 569 | h->path = sp; |
570 | } | 570 | } |
571 | 571 | ||
diff --git a/shell/ash.c b/shell/ash.c index a8ba9c4ef..7fa9dae21 100644 --- a/shell/ash.c +++ b/shell/ash.c | |||
@@ -224,7 +224,20 @@ | |||
224 | #define IF_BASH_PATTERN_SUBST IF_ASH_BASH_COMPAT | 224 | #define IF_BASH_PATTERN_SUBST IF_ASH_BASH_COMPAT |
225 | #define BASH_SUBSTR ENABLE_ASH_BASH_COMPAT | 225 | #define BASH_SUBSTR ENABLE_ASH_BASH_COMPAT |
226 | #define IF_BASH_SUBSTR IF_ASH_BASH_COMPAT | 226 | #define IF_BASH_SUBSTR IF_ASH_BASH_COMPAT |
227 | /* [[ EXPR ]] */ | 227 | /* BASH_TEST2: [[ EXPR ]] |
228 | * Status of [[ support: | ||
229 | * We replace && and || with -a and -o | ||
230 | * TODO: | ||
231 | * singleword+noglob expansion: | ||
232 | * v='a b'; [[ $v = 'a b' ]]; echo 0:$? | ||
233 | * [[ /bin/n* ]]; echo 0:$? | ||
234 | * -a/-o are not AND/OR ops! (they are just strings) | ||
235 | * quoting needs to be considered (-f is an operator, "-f" and ""-f are not; etc) | ||
236 | * = is glob match operator, not equality operator: STR = GLOB | ||
237 | * (in GLOB, quoting is significant on char-by-char basis: a*cd"*") | ||
238 | * == same as = | ||
239 | * add =~ regex match operator: STR =~ REGEX | ||
240 | */ | ||
228 | #define BASH_TEST2 (ENABLE_ASH_BASH_COMPAT * ENABLE_ASH_TEST) | 241 | #define BASH_TEST2 (ENABLE_ASH_BASH_COMPAT * ENABLE_ASH_TEST) |
229 | #define BASH_SOURCE ENABLE_ASH_BASH_COMPAT | 242 | #define BASH_SOURCE ENABLE_ASH_BASH_COMPAT |
230 | #define BASH_PIPEFAIL ENABLE_ASH_BASH_COMPAT | 243 | #define BASH_PIPEFAIL ENABLE_ASH_BASH_COMPAT |
@@ -7944,9 +7957,16 @@ expandmeta(struct strlist *str /*, int flag*/) | |||
7944 | /* | 7957 | /* |
7945 | * Do metacharacter (i.e. *, ?, [...]) expansion. | 7958 | * Do metacharacter (i.e. *, ?, [...]) expansion. |
7946 | */ | 7959 | */ |
7960 | typedef struct exp_t { | ||
7961 | char *dir; | ||
7962 | unsigned dir_max; | ||
7963 | } exp_t; | ||
7947 | static void | 7964 | static void |
7948 | expmeta(char *expdir, char *enddir, char *name) | 7965 | expmeta(exp_t *exp, char *name, unsigned name_len, unsigned expdir_len) |
7949 | { | 7966 | { |
7967 | #define expdir exp->dir | ||
7968 | #define expdir_max exp->dir_max | ||
7969 | char *enddir = expdir + expdir_len; | ||
7950 | char *p; | 7970 | char *p; |
7951 | const char *cp; | 7971 | const char *cp; |
7952 | char *start; | 7972 | char *start; |
@@ -7989,15 +8009,15 @@ expmeta(char *expdir, char *enddir, char *name) | |||
7989 | } | 8009 | } |
7990 | } | 8010 | } |
7991 | if (metaflag == 0) { /* we've reached the end of the file name */ | 8011 | if (metaflag == 0) { /* we've reached the end of the file name */ |
7992 | if (enddir != expdir) | 8012 | if (!expdir_len) |
7993 | metaflag++; | 8013 | return; |
7994 | p = name; | 8014 | p = name; |
7995 | do { | 8015 | do { |
7996 | if (*p == '\\') | 8016 | if (*p == '\\') |
7997 | p++; | 8017 | p++; |
7998 | *enddir++ = *p; | 8018 | *enddir++ = *p; |
7999 | } while (*p++); | 8019 | } while (*p++); |
8000 | if (metaflag == 0 || lstat(expdir, &statb) >= 0) | 8020 | if (lstat(expdir, &statb) == 0) |
8001 | addfname(expdir); | 8021 | addfname(expdir); |
8002 | return; | 8022 | return; |
8003 | } | 8023 | } |
@@ -8010,19 +8030,14 @@ expmeta(char *expdir, char *enddir, char *name) | |||
8010 | *enddir++ = *p++; | 8030 | *enddir++ = *p++; |
8011 | } while (p < start); | 8031 | } while (p < start); |
8012 | } | 8032 | } |
8013 | if (enddir == expdir) { | 8033 | *enddir = '\0'; |
8034 | cp = expdir; | ||
8035 | expdir_len = enddir - cp; | ||
8036 | if (!expdir_len) | ||
8014 | cp = "."; | 8037 | cp = "."; |
8015 | } else if (enddir == expdir + 1 && *expdir == '/') { | ||
8016 | cp = "/"; | ||
8017 | } else { | ||
8018 | cp = expdir; | ||
8019 | enddir[-1] = '\0'; | ||
8020 | } | ||
8021 | dirp = opendir(cp); | 8038 | dirp = opendir(cp); |
8022 | if (dirp == NULL) | 8039 | if (dirp == NULL) |
8023 | return; | 8040 | return; |
8024 | if (enddir != expdir) | ||
8025 | enddir[-1] = '/'; | ||
8026 | if (*endname == 0) { | 8041 | if (*endname == 0) { |
8027 | atend = 1; | 8042 | atend = 1; |
8028 | } else { | 8043 | } else { |
@@ -8030,6 +8045,7 @@ expmeta(char *expdir, char *enddir, char *name) | |||
8030 | *endname = '\0'; | 8045 | *endname = '\0'; |
8031 | endname += esc + 1; | 8046 | endname += esc + 1; |
8032 | } | 8047 | } |
8048 | name_len -= endname - name; | ||
8033 | matchdot = 0; | 8049 | matchdot = 0; |
8034 | p = start; | 8050 | p = start; |
8035 | if (*p == '\\') | 8051 | if (*p == '\\') |
@@ -8044,16 +8060,30 @@ expmeta(char *expdir, char *enddir, char *name) | |||
8044 | strcpy(enddir, dp->d_name); | 8060 | strcpy(enddir, dp->d_name); |
8045 | addfname(expdir); | 8061 | addfname(expdir); |
8046 | } else { | 8062 | } else { |
8047 | for (p = enddir, cp = dp->d_name; (*p++ = *cp++) != '\0';) | 8063 | unsigned offset; |
8048 | continue; | 8064 | unsigned len; |
8049 | p[-1] = '/'; | 8065 | |
8050 | expmeta(expdir, p, endname); | 8066 | p = stpcpy(enddir, dp->d_name); |
8067 | *p = '/'; | ||
8068 | |||
8069 | offset = p - expdir + 1; | ||
8070 | len = offset + name_len + NAME_MAX; | ||
8071 | if (len > expdir_max) { | ||
8072 | len += PATH_MAX; | ||
8073 | expdir = ckrealloc(expdir, len); | ||
8074 | expdir_max = len; | ||
8075 | } | ||
8076 | |||
8077 | expmeta(exp, endname, name_len, offset); | ||
8078 | enddir = expdir + expdir_len; | ||
8051 | } | 8079 | } |
8052 | } | 8080 | } |
8053 | } | 8081 | } |
8054 | closedir(dirp); | 8082 | closedir(dirp); |
8055 | if (!atend) | 8083 | if (!atend) |
8056 | endname[-esc - 1] = esc ? '\\' : '/'; | 8084 | endname[-esc - 1] = esc ? '\\' : '/'; |
8085 | #undef expdir | ||
8086 | #undef expdir_max | ||
8057 | } | 8087 | } |
8058 | 8088 | ||
8059 | static struct strlist * | 8089 | static struct strlist * |
@@ -8126,10 +8156,11 @@ expandmeta(struct strlist *str /*, int flag*/) | |||
8126 | /* TODO - EXP_REDIR */ | 8156 | /* TODO - EXP_REDIR */ |
8127 | 8157 | ||
8128 | while (str) { | 8158 | while (str) { |
8129 | char *expdir; | 8159 | exp_t exp; |
8130 | struct strlist **savelastp; | 8160 | struct strlist **savelastp; |
8131 | struct strlist *sp; | 8161 | struct strlist *sp; |
8132 | char *p; | 8162 | char *p; |
8163 | unsigned len; | ||
8133 | 8164 | ||
8134 | if (fflag) | 8165 | if (fflag) |
8135 | goto nometa; | 8166 | goto nometa; |
@@ -8139,13 +8170,12 @@ expandmeta(struct strlist *str /*, int flag*/) | |||
8139 | 8170 | ||
8140 | INT_OFF; | 8171 | INT_OFF; |
8141 | p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP); | 8172 | p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP); |
8142 | { | 8173 | len = strlen(p); |
8143 | int i = strlen(str->text); | 8174 | exp.dir_max = len + PATH_MAX; |
8144 | //BUGGY estimation of how long expanded name can be | 8175 | exp.dir = ckmalloc(exp.dir_max); |
8145 | expdir = ckmalloc(i < 2048 ? 2048 : i+1); | 8176 | |
8146 | } | 8177 | expmeta(&exp, p, len, 0); |
8147 | expmeta(expdir, expdir, p); | 8178 | free(exp.dir); |
8148 | free(expdir); | ||
8149 | if (p != str->text) | 8179 | if (p != str->text) |
8150 | free(p); | 8180 | free(p); |
8151 | INT_ON; | 8181 | INT_ON; |
@@ -12128,10 +12158,12 @@ simplecmd(void) | |||
12128 | case TLP: | 12158 | case TLP: |
12129 | function_flag = 0; | 12159 | function_flag = 0; |
12130 | break; | 12160 | break; |
12161 | # if BASH_TEST2 | ||
12131 | case TWORD: | 12162 | case TWORD: |
12132 | if (strcmp("[[", wordtext) == 0) | 12163 | if (strcmp("[[", wordtext) == 0) |
12133 | goto do_func; | 12164 | goto do_func; |
12134 | /* fall through */ | 12165 | /* fall through */ |
12166 | # endif | ||
12135 | default: | 12167 | default: |
12136 | raise_error_unexpected_syntax(-1); | 12168 | raise_error_unexpected_syntax(-1); |
12137 | } | 12169 | } |
@@ -12179,7 +12211,8 @@ simplecmd(void) | |||
12179 | *vpp = NULL; | 12211 | *vpp = NULL; |
12180 | *rpp = NULL; | 12212 | *rpp = NULL; |
12181 | n = stzalloc(sizeof(struct ncmd)); | 12213 | n = stzalloc(sizeof(struct ncmd)); |
12182 | n->type = NCMD; | 12214 | if (NCMD != 0) |
12215 | n->type = NCMD; | ||
12183 | n->ncmd.linno = savelinno; | 12216 | n->ncmd.linno = savelinno; |
12184 | n->ncmd.args = args; | 12217 | n->ncmd.args = args; |
12185 | n->ncmd.assign = vars; | 12218 | n->ncmd.assign = vars; |
@@ -12500,8 +12533,11 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) | |||
12500 | CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */ | 12533 | CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */ |
12501 | switch (SIT(c, synstack->syntax)) { | 12534 | switch (SIT(c, synstack->syntax)) { |
12502 | case CNL: /* '\n' */ | 12535 | case CNL: /* '\n' */ |
12503 | if (synstack->syntax == BASESYNTAX) | 12536 | if (synstack->syntax == BASESYNTAX |
12537 | && !synstack->varnest | ||
12538 | ) { | ||
12504 | goto endword; /* exit outer loop */ | 12539 | goto endword; /* exit outer loop */ |
12540 | } | ||
12505 | USTPUTC(c, out); | 12541 | USTPUTC(c, out); |
12506 | nlprompt(); | 12542 | nlprompt(); |
12507 | c = pgetc(); | 12543 | c = pgetc(); |
diff --git a/shell/ash_test/ash-parsing/bkslash_eof1.right b/shell/ash_test/ash-parsing/bkslash_eof1.right new file mode 100644 index 000000000..6c6df0b0c --- /dev/null +++ b/shell/ash_test/ash-parsing/bkslash_eof1.right | |||
@@ -0,0 +1 @@ | |||
ok\ | |||
diff --git a/shell/ash_test/ash-parsing/bkslash_eof1.tests b/shell/ash_test/ash-parsing/bkslash_eof1.tests new file mode 100755 index 000000000..97629cb13 --- /dev/null +++ b/shell/ash_test/ash-parsing/bkslash_eof1.tests | |||
@@ -0,0 +1 @@ | |||
eval 'echo ok\' | |||
diff --git a/shell/ash_test/ash-parsing/bkslash_newline3.right b/shell/ash_test/ash-parsing/bkslash_newline3.right new file mode 100644 index 000000000..e635074e5 --- /dev/null +++ b/shell/ash_test/ash-parsing/bkslash_newline3.right | |||
@@ -0,0 +1 @@ | |||
a:[a] | |||
diff --git a/shell/ash_test/ash-parsing/bkslash_newline3.tests b/shell/ash_test/ash-parsing/bkslash_newline3.tests new file mode 100755 index 000000000..2accd4395 --- /dev/null +++ b/shell/ash_test/ash-parsing/bkslash_newline3.tests | |||
@@ -0,0 +1,4 @@ | |||
1 | for s in \ | ||
2 | a; do | ||
3 | echo "a:[$s]" | ||
4 | done | ||
diff --git a/shell/ash_test/ash-quoting/case_glob1.right b/shell/ash_test/ash-quoting/case_glob1.right new file mode 100644 index 000000000..b4785957b --- /dev/null +++ b/shell/ash_test/ash-quoting/case_glob1.right | |||
@@ -0,0 +1 @@ | |||
s | |||
diff --git a/shell/ash_test/ash-quoting/case_glob1.tests b/shell/ash_test/ash-quoting/case_glob1.tests new file mode 100755 index 000000000..8dbbc0fb1 --- /dev/null +++ b/shell/ash_test/ash-quoting/case_glob1.tests | |||
@@ -0,0 +1,8 @@ | |||
1 | g='[3](a)(b)(c)' | ||
2 | s='[3](a)(b)(c)' | ||
3 | case $g in | ||
4 | "$s") echo s | ||
5 | ;; | ||
6 | *) echo "*" | ||
7 | ;; | ||
8 | esac | ||
diff --git a/shell/ash_test/ash-redir/redir_exec1.right b/shell/ash_test/ash-redir/redir_exec1.right index d4393d10c..c98455bf5 100644 --- a/shell/ash_test/ash-redir/redir_exec1.right +++ b/shell/ash_test/ash-redir/redir_exec1.right | |||
@@ -1,2 +1,2 @@ | |||
1 | redir_exec1.tests: line 1: can't create /cant/be/created: nonexistent directory | 1 | ./redir_exec1.tests: line 1: can't create /cant/be/created: nonexistent directory |
2 | First | 2 | First |
diff --git a/shell/ash_test/ash-vars/param_expand_alt2.right b/shell/ash_test/ash-vars/param_expand_alt2.right new file mode 100644 index 000000000..fef5889ca --- /dev/null +++ b/shell/ash_test/ash-vars/param_expand_alt2.right | |||
@@ -0,0 +1,4 @@ | |||
1 | Unquoted: H H | ||
2 | Quoted: H | ||
3 | H | ||
4 | Ok:0 | ||
diff --git a/shell/ash_test/ash-vars/param_expand_alt2.tests b/shell/ash_test/ash-vars/param_expand_alt2.tests new file mode 100755 index 000000000..d8abf4c3b --- /dev/null +++ b/shell/ash_test/ash-vars/param_expand_alt2.tests | |||
@@ -0,0 +1,7 @@ | |||
1 | echo Unquoted: H${$+ | ||
2 | }H | ||
3 | |||
4 | echo Quoted: "H${$+ | ||
5 | }H" | ||
6 | |||
7 | echo Ok:$? | ||
diff --git a/shell/ash_test/ash-vars/var_wordsplit_ifs4.right b/shell/ash_test/ash-vars/var_wordsplit_ifs4.right new file mode 100644 index 000000000..c27284c31 --- /dev/null +++ b/shell/ash_test/ash-vars/var_wordsplit_ifs4.right | |||
@@ -0,0 +1,5 @@ | |||
1 | |x| | ||
2 | Ok1:0 | ||
3 | |x| | ||
4 | || | ||
5 | Ok2:0 | ||
diff --git a/shell/ash_test/ash-vars/var_wordsplit_ifs4.tests b/shell/ash_test/ash-vars/var_wordsplit_ifs4.tests new file mode 100755 index 000000000..638bfbb28 --- /dev/null +++ b/shell/ash_test/ash-vars/var_wordsplit_ifs4.tests | |||
@@ -0,0 +1,4 @@ | |||
1 | IFS=": "; x=" "; set x $x; for v; do echo "|$v|"; done | ||
2 | echo Ok1:$? | ||
3 | IFS=": "; x=":"; set x $x; for v; do echo "|$v|"; done | ||
4 | echo Ok2:$? | ||
diff --git a/shell/ash_test/ash-z_slow/many_ifs.right b/shell/ash_test/ash-z_slow/many_ifs.right new file mode 100644 index 000000000..f3bdccc6c --- /dev/null +++ b/shell/ash_test/ash-z_slow/many_ifs.right | |||
@@ -0,0 +1 @@ | |||
# tests 6856 passed 6856 failed 0 | |||
diff --git a/shell/ash_test/ash-z_slow/many_ifs.tests b/shell/ash_test/ash-z_slow/many_ifs.tests new file mode 100755 index 000000000..1f5b1b3a6 --- /dev/null +++ b/shell/ash_test/ash-z_slow/many_ifs.tests | |||
@@ -0,0 +1,257 @@ | |||
1 | # Usage: $SHELL ifs.sh | ||
2 | # | ||
3 | # This script generates 6856 tests for the set(1) and read(1) | ||
4 | # builtins w.r.t. IFS whitespace and non-whitespace characters. | ||
5 | # Each failed test produces one line on the standard output that | ||
6 | # contains the test along with the expected and actual results. | ||
7 | # The last output line contains the test result counts. ordered>0 | ||
8 | # are the number of tests where IFS=": " produced different results | ||
9 | # than IFS=" :". If a test fails the same way for IFS=": " and | ||
10 | # IFS=" :" then the second output line is suppressed. | ||
11 | |||
12 | TESTS=6856 | ||
13 | |||
14 | ksh_read=0 | ||
15 | echo 1 | read ksh_read | ||
16 | ksh_arith=0 | ||
17 | eval '((ksh_arith+=1))' 2>/dev/null | ||
18 | |||
19 | failed=0 | ||
20 | ordered=0 | ||
21 | passed=0 | ||
22 | |||
23 | split() | ||
24 | { | ||
25 | i=$1 s=$2 r=$3 S='' R='' | ||
26 | for ifs in ': ' ' :' | ||
27 | do IFS=$ifs | ||
28 | set x $i | ||
29 | shift | ||
30 | IFS=' ' | ||
31 | g="[$#]" | ||
32 | while : | ||
33 | do case $# in | ||
34 | 0) break ;; | ||
35 | esac | ||
36 | g="$g($1)" | ||
37 | shift | ||
38 | done | ||
39 | case $g in | ||
40 | "$s") case $ksh_arith in | ||
41 | 1) ((passed+=1)) ;; | ||
42 | *) passed=`expr $passed + 1` ;; | ||
43 | esac | ||
44 | case $S in | ||
45 | '') S=$g | ||
46 | ;; | ||
47 | "$g") ;; | ||
48 | *) case $ksh_arith in | ||
49 | 1) ((ordered+=1)) ;; | ||
50 | *) ordered=`expr $ordered + 1` ;; | ||
51 | esac | ||
52 | ;; | ||
53 | esac | ||
54 | ;; | ||
55 | "$S") case $ksh_arith in | ||
56 | 1) ((failed+=1)) ;; | ||
57 | *) failed=`expr $failed + 1` ;; | ||
58 | esac | ||
59 | ;; | ||
60 | *) case $ksh_arith in | ||
61 | 1) ((failed+=1)) ;; | ||
62 | *) failed=`expr $failed + 1` ;; | ||
63 | esac | ||
64 | case $s in | ||
65 | "$S") ;; | ||
66 | ?0*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#]\" # expected \"$s\" got \"$g\"" ;; | ||
67 | ?1*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#](\$1)\" # expected \"$s\" got \"$g\"" ;; | ||
68 | ?2*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#](\$1)(\$2)\" # expected \"$s\" got \"$g\"" ;; | ||
69 | ?3*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#](\$1)(\$2)(\$3)\" # expected \"$s\" got \"$g\"" ;; | ||
70 | *) echo TEST ERROR i="'$i'" s="'$s'" ;; | ||
71 | esac | ||
72 | case $S in | ||
73 | '') S=$g | ||
74 | ;; | ||
75 | "$g") ;; | ||
76 | *) case $ksh_arith in | ||
77 | 1) ((ordered+=1)) ;; | ||
78 | *) ordered=`expr $ordered + 1` ;; | ||
79 | esac | ||
80 | ;; | ||
81 | esac | ||
82 | esac | ||
83 | case $ksh_read in | ||
84 | 1) echo "$i" | IFS=$ifs read x y; g="($x)($y)" ;; | ||
85 | *) g=`export ifs; echo "$i" | ( IFS=$ifs; read x y; echo "($x)($y)" )` ;; | ||
86 | esac | ||
87 | case $g in | ||
88 | "$r") case $ksh_arith in | ||
89 | 1) ((passed+=1)) ;; | ||
90 | *) passed=`expr $passed + 1` ;; | ||
91 | esac | ||
92 | case $R in | ||
93 | '') R=$g | ||
94 | ;; | ||
95 | "$g") ;; | ||
96 | *) case $ksh_arith in | ||
97 | 1) ((ordered+=1)) ;; | ||
98 | *) ordered=`expr $ordered + 1` ;; | ||
99 | esac | ||
100 | ;; | ||
101 | esac | ||
102 | ;; | ||
103 | "$R") case $ksh_arith in | ||
104 | 1) ((failed+=1)) ;; | ||
105 | *) failed=`expr $failed + 1` ;; | ||
106 | esac | ||
107 | ;; | ||
108 | *) case $ksh_arith in | ||
109 | 1) ((failed+=1)) ;; | ||
110 | *) failed=`expr $failed + 1` ;; | ||
111 | esac | ||
112 | case $r in | ||
113 | "$R") ;; | ||
114 | *) echo "echo \"$i\" | ( IFS=\"$ifs\" read x y; echo \"(\$x)(\$y)\" ) # expected \"$r\" got \"$g\"" ;; | ||
115 | esac | ||
116 | case $R in | ||
117 | '') R=$g | ||
118 | ;; | ||
119 | "$g") ;; | ||
120 | *) case $ksh_arith in | ||
121 | 1) ((ordered+=1)) ;; | ||
122 | *) ordered=`expr $ordered + 1` ;; | ||
123 | esac | ||
124 | ;; | ||
125 | esac | ||
126 | ;; | ||
127 | esac | ||
128 | done | ||
129 | } | ||
130 | |||
131 | for str in \ | ||
132 | '-' \ | ||
133 | 'a' \ | ||
134 | '- -' \ | ||
135 | '- a' \ | ||
136 | 'a -' \ | ||
137 | 'a b' \ | ||
138 | '- - -' \ | ||
139 | '- - a' \ | ||
140 | '- a -' \ | ||
141 | '- a b' \ | ||
142 | 'a - -' \ | ||
143 | 'a - b' \ | ||
144 | 'a b -' \ | ||
145 | 'a b c' \ | ||
146 | |||
147 | do | ||
148 | IFS=' ' | ||
149 | set x $str | ||
150 | |||
151 | shift | ||
152 | case $# in | ||
153 | 0) continue ;; | ||
154 | esac | ||
155 | |||
156 | f1=$1 | ||
157 | case $f1 in | ||
158 | '-') f1='' ;; | ||
159 | esac | ||
160 | |||
161 | shift | ||
162 | case $# in | ||
163 | 0) for d0 in '' ' ' | ||
164 | do | ||
165 | for d1 in '' ' ' ':' ' :' ': ' ' : ' | ||
166 | do | ||
167 | case $f1$d1 in | ||
168 | '') split "$d0$f1$d1" "[0]" "()()" ;; | ||
169 | ' ') ;; | ||
170 | *) split "$d0$f1$d1" "[1]($f1)" "($f1)()" ;; | ||
171 | esac | ||
172 | done | ||
173 | done | ||
174 | continue | ||
175 | ;; | ||
176 | esac | ||
177 | f2=$1 | ||
178 | case $f2 in | ||
179 | '-') f2='' ;; | ||
180 | esac | ||
181 | |||
182 | shift | ||
183 | case $# in | ||
184 | 0) for d0 in '' ' ' | ||
185 | do | ||
186 | for d1 in ' ' ':' ' :' ': ' ' : ' | ||
187 | do | ||
188 | case ' ' in | ||
189 | $f1$d1|$d1$f2) continue ;; | ||
190 | esac | ||
191 | for d2 in '' ' ' ':' ' :' ': ' ' : ' | ||
192 | do | ||
193 | case $f2$d2 in | ||
194 | '') split "$d0$f1$d1$f2$d2" "[1]($f1)" "($f1)()" ;; | ||
195 | ' ') ;; | ||
196 | *) split "$d0$f1$d1$f2$d2" "[2]($f1)($f2)" "($f1)($f2)" ;; | ||
197 | esac | ||
198 | done | ||
199 | done | ||
200 | done | ||
201 | continue | ||
202 | ;; | ||
203 | esac | ||
204 | f3=$1 | ||
205 | case $f3 in | ||
206 | '-') f3='' ;; | ||
207 | esac | ||
208 | |||
209 | shift | ||
210 | case $# in | ||
211 | 0) for d0 in '' ' ' | ||
212 | do | ||
213 | for d1 in ':' ' :' ': ' ' : ' | ||
214 | do | ||
215 | case ' ' in | ||
216 | $f1$d1|$d1$f2) continue ;; | ||
217 | esac | ||
218 | for d2 in ' ' ':' ' :' ': ' ' : ' | ||
219 | do | ||
220 | case $f2$d2 in | ||
221 | ' ') continue ;; | ||
222 | esac | ||
223 | case ' ' in | ||
224 | $f2$d2|$d2$f3) continue ;; | ||
225 | esac | ||
226 | for d3 in '' ' ' ':' ' :' ': ' ' : ' | ||
227 | do | ||
228 | case $f3$d3 in | ||
229 | '') split "$d0$f1$d1$f2$d2$f3$d3" "[2]($f1)($f2)" "($f1)($f2)" ;; | ||
230 | ' ') ;; | ||
231 | *) x=$f2$d2$f3$d3 | ||
232 | x=${x# } #was x=${x#' '} hush needs fixing for this to work | ||
233 | x=${x% } #was x=${x%' '} | ||
234 | split "$d0$f1$d1$f2$d2$f3$d3" "[3]($f1)($f2)($f3)" "($f1)($x)" | ||
235 | ;; | ||
236 | esac | ||
237 | done | ||
238 | done | ||
239 | done | ||
240 | done | ||
241 | continue | ||
242 | ;; | ||
243 | esac | ||
244 | done | ||
245 | case $ksh_arith in | ||
246 | 1) ((tests=passed+failed)) ;; | ||
247 | *) tests=`expr $passed + $failed` ;; | ||
248 | esac | ||
249 | case $ordered in | ||
250 | 0) ordered="" ;; | ||
251 | *) ordered=" ordered $ordered" ;; | ||
252 | esac | ||
253 | case $tests in | ||
254 | $TESTS) fatal="" ;; | ||
255 | *) fatal=" -- fundamental IFS error -- $TESTS tests expected" | ||
256 | esac | ||
257 | echo "# tests $tests passed $passed failed $failed$ordered$fatal" | ||
diff --git a/shell/hush.c b/shell/hush.c index d5ea3b21f..c77700175 100644 --- a/shell/hush.c +++ b/shell/hush.c | |||
@@ -79,6 +79,18 @@ | |||
79 | * Some builtins mandated by standards: | 79 | * Some builtins mandated by standards: |
80 | * newgrp [GRP]: not a builtin in bash but a suid binary | 80 | * newgrp [GRP]: not a builtin in bash but a suid binary |
81 | * which spawns a new shell with new group ID | 81 | * which spawns a new shell with new group ID |
82 | * | ||
83 | * Status of [[ support: | ||
84 | * [[ args ]] are CMD_SINGLEWORD_NOGLOB: | ||
85 | * v='a b'; [[ $v = 'a b' ]]; echo 0:$? | ||
86 | * [[ /bin/n* ]]; echo 0:$? | ||
87 | * TODO: | ||
88 | * &&/|| are AND/OR ops, -a/-o are not | ||
89 | * quoting needs to be considered (-f is an operator, "-f" and ""-f are not; etc) | ||
90 | * = is glob match operator, not equality operator: STR = GLOB | ||
91 | * (in GLOB, quoting is significant on char-by-char basis: a*cd"*") | ||
92 | * == same as = | ||
93 | * add =~ regex match operator: STR =~ REGEX | ||
82 | */ | 94 | */ |
83 | //config:config HUSH | 95 | //config:config HUSH |
84 | //config: bool "hush (64 kb)" | 96 | //config: bool "hush (64 kb)" |
@@ -522,7 +534,6 @@ typedef struct o_string { | |||
522 | * possibly empty one: word"", wo''rd etc. */ | 534 | * possibly empty one: word"", wo''rd etc. */ |
523 | smallint has_quoted_part; | 535 | smallint has_quoted_part; |
524 | smallint has_empty_slot; | 536 | smallint has_empty_slot; |
525 | smallint o_assignment; /* 0:maybe, 1:yes, 2:no */ | ||
526 | } o_string; | 537 | } o_string; |
527 | enum { | 538 | enum { |
528 | EXP_FLAG_SINGLEWORD = 0x80, /* must be 0x80 */ | 539 | EXP_FLAG_SINGLEWORD = 0x80, /* must be 0x80 */ |
@@ -531,13 +542,6 @@ enum { | |||
531 | * by prepending \ to *, ?, [, \ */ | 542 | * by prepending \ to *, ?, [, \ */ |
532 | EXP_FLAG_ESC_GLOB_CHARS = 0x1, | 543 | EXP_FLAG_ESC_GLOB_CHARS = 0x1, |
533 | }; | 544 | }; |
534 | enum { | ||
535 | MAYBE_ASSIGNMENT = 0, | ||
536 | DEFINITELY_ASSIGNMENT = 1, | ||
537 | NOT_ASSIGNMENT = 2, | ||
538 | /* Not an assignment, but next word may be: "if v=xyz cmd;" */ | ||
539 | WORD_IS_KEYWORD = 3, | ||
540 | }; | ||
541 | /* Used for initialization: o_string foo = NULL_O_STRING; */ | 545 | /* Used for initialization: o_string foo = NULL_O_STRING; */ |
542 | #define NULL_O_STRING { NULL } | 546 | #define NULL_O_STRING { NULL } |
543 | 547 | ||
@@ -694,9 +698,11 @@ struct parse_context { | |||
694 | struct command *command; | 698 | struct command *command; |
695 | /* last redirect in command->redirects list */ | 699 | /* last redirect in command->redirects list */ |
696 | struct redir_struct *pending_redirect; | 700 | struct redir_struct *pending_redirect; |
701 | o_string word; | ||
697 | #if !BB_MMU | 702 | #if !BB_MMU |
698 | o_string as_string; | 703 | o_string as_string; |
699 | #endif | 704 | #endif |
705 | smallint is_assignment; /* 0:maybe, 1:yes, 2:no, 3:keyword */ | ||
700 | #if HAS_KEYWORDS | 706 | #if HAS_KEYWORDS |
701 | smallint ctx_res_w; | 707 | smallint ctx_res_w; |
702 | smallint ctx_inverted; /* "! cmd | cmd" */ | 708 | smallint ctx_inverted; /* "! cmd | cmd" */ |
@@ -717,6 +723,13 @@ struct parse_context { | |||
717 | struct parse_context *stack; | 723 | struct parse_context *stack; |
718 | #endif | 724 | #endif |
719 | }; | 725 | }; |
726 | enum { | ||
727 | MAYBE_ASSIGNMENT = 0, | ||
728 | DEFINITELY_ASSIGNMENT = 1, | ||
729 | NOT_ASSIGNMENT = 2, | ||
730 | /* Not an assignment, but next word may be: "if v=xyz cmd;" */ | ||
731 | WORD_IS_KEYWORD = 3, | ||
732 | }; | ||
720 | 733 | ||
721 | /* On program start, environ points to initial environment. | 734 | /* On program start, environ points to initial environment. |
722 | * putenv adds new pointers into it, unsetenv removes them. | 735 | * putenv adds new pointers into it, unsetenv removes them. |
@@ -917,6 +930,7 @@ struct globals { | |||
917 | unsigned getopt_count; | 930 | unsigned getopt_count; |
918 | #endif | 931 | #endif |
919 | const char *ifs; | 932 | const char *ifs; |
933 | char *ifs_whitespace; /* = G.ifs or malloced */ | ||
920 | const char *cwd; | 934 | const char *cwd; |
921 | struct variable *top_var; | 935 | struct variable *top_var; |
922 | char **expanded_assignments; | 936 | char **expanded_assignments; |
@@ -1413,8 +1427,19 @@ static char *unbackslash(char *src) | |||
1413 | { | 1427 | { |
1414 | char *dst = src = strchrnul(src, '\\'); | 1428 | char *dst = src = strchrnul(src, '\\'); |
1415 | while (1) { | 1429 | while (1) { |
1416 | if (*src == '\\') | 1430 | if (*src == '\\') { |
1417 | src++; | 1431 | src++; |
1432 | if (*src != '\0') { | ||
1433 | /* \x -> x */ | ||
1434 | *dst++ = *src++; | ||
1435 | continue; | ||
1436 | } | ||
1437 | /* else: "\<nul>". Do not delete this backslash. | ||
1438 | * Testcase: eval 'echo ok\' | ||
1439 | */ | ||
1440 | *dst++ = '\\'; | ||
1441 | /* fallthrough */ | ||
1442 | } | ||
1418 | if ((*dst++ = *src++) == '\0') | 1443 | if ((*dst++ = *src++) == '\0') |
1419 | break; | 1444 | break; |
1420 | } | 1445 | } |
@@ -2266,6 +2291,7 @@ static int set_local_var(char *str, unsigned flags) | |||
2266 | } | 2291 | } |
2267 | 2292 | ||
2268 | /* Not found or shadowed - create new variable struct */ | 2293 | /* Not found or shadowed - create new variable struct */ |
2294 | debug_printf_env("%s: alloc new var '%s'/%u\n", __func__, str, local_lvl); | ||
2269 | cur = xzalloc(sizeof(*cur)); | 2295 | cur = xzalloc(sizeof(*cur)); |
2270 | cur->var_nest_level = local_lvl; | 2296 | cur->var_nest_level = local_lvl; |
2271 | cur->next = *cur_pp; | 2297 | cur->next = *cur_pp; |
@@ -2420,7 +2446,7 @@ static void set_vars_and_save_old(char **strings) | |||
2420 | * global linked list. | 2446 | * global linked list. |
2421 | */ | 2447 | */ |
2422 | } | 2448 | } |
2423 | //bb_error_msg("G.var_nest_level:%d", G.var_nest_level); | 2449 | debug_printf_env("%s: env override '%s'/%u\n", __func__, *s, G.var_nest_level); |
2424 | set_local_var(*s, (G.var_nest_level << SETFLAG_VARLVL_SHIFT) | SETFLAG_EXPORT); | 2450 | set_local_var(*s, (G.var_nest_level << SETFLAG_VARLVL_SHIFT) | SETFLAG_EXPORT); |
2425 | } else if (HUSH_DEBUG) { | 2451 | } else if (HUSH_DEBUG) { |
2426 | bb_error_msg_and_die("BUG in varexp4"); | 2452 | bb_error_msg_and_die("BUG in varexp4"); |
@@ -3670,6 +3696,8 @@ static void done_pipe(struct parse_context *ctx, pipe_style type) | |||
3670 | static void initialize_context(struct parse_context *ctx) | 3696 | static void initialize_context(struct parse_context *ctx) |
3671 | { | 3697 | { |
3672 | memset(ctx, 0, sizeof(*ctx)); | 3698 | memset(ctx, 0, sizeof(*ctx)); |
3699 | if (MAYBE_ASSIGNMENT != 0) | ||
3700 | ctx->is_assignment = MAYBE_ASSIGNMENT; | ||
3673 | ctx->pipe = ctx->list_head = new_pipe(); | 3701 | ctx->pipe = ctx->list_head = new_pipe(); |
3674 | /* Create the memory for command, roughly: | 3702 | /* Create the memory for command, roughly: |
3675 | * ctx->pipe->cmds = new struct command; | 3703 | * ctx->pipe->cmds = new struct command; |
@@ -3751,7 +3779,7 @@ static const struct reserved_combo* match_reserved_word(o_string *word) | |||
3751 | } | 3779 | } |
3752 | /* Return NULL: not a keyword, else: keyword | 3780 | /* Return NULL: not a keyword, else: keyword |
3753 | */ | 3781 | */ |
3754 | static const struct reserved_combo* reserved_word(o_string *word, struct parse_context *ctx) | 3782 | static const struct reserved_combo* reserved_word(struct parse_context *ctx) |
3755 | { | 3783 | { |
3756 | # if ENABLE_HUSH_CASE | 3784 | # if ENABLE_HUSH_CASE |
3757 | static const struct reserved_combo reserved_match = { | 3785 | static const struct reserved_combo reserved_match = { |
@@ -3760,9 +3788,9 @@ static const struct reserved_combo* reserved_word(o_string *word, struct parse_c | |||
3760 | # endif | 3788 | # endif |
3761 | const struct reserved_combo *r; | 3789 | const struct reserved_combo *r; |
3762 | 3790 | ||
3763 | if (word->has_quoted_part) | 3791 | if (ctx->word.has_quoted_part) |
3764 | return 0; | 3792 | return 0; |
3765 | r = match_reserved_word(word); | 3793 | r = match_reserved_word(&ctx->word); |
3766 | if (!r) | 3794 | if (!r) |
3767 | return r; /* NULL */ | 3795 | return r; /* NULL */ |
3768 | 3796 | ||
@@ -3789,7 +3817,7 @@ static const struct reserved_combo* reserved_word(o_string *word, struct parse_c | |||
3789 | initialize_context(ctx); | 3817 | initialize_context(ctx); |
3790 | ctx->stack = old; | 3818 | ctx->stack = old; |
3791 | } else if (/*ctx->ctx_res_w == RES_NONE ||*/ !(ctx->old_flag & (1 << r->res))) { | 3819 | } else if (/*ctx->ctx_res_w == RES_NONE ||*/ !(ctx->old_flag & (1 << r->res))) { |
3792 | syntax_error_at(word->data); | 3820 | syntax_error_at(ctx->word.data); |
3793 | ctx->ctx_res_w = RES_SNTX; | 3821 | ctx->ctx_res_w = RES_SNTX; |
3794 | return r; | 3822 | return r; |
3795 | } else { | 3823 | } else { |
@@ -3802,8 +3830,8 @@ static const struct reserved_combo* reserved_word(o_string *word, struct parse_c | |||
3802 | 3830 | ||
3803 | ctx->ctx_res_w = r->res; | 3831 | ctx->ctx_res_w = r->res; |
3804 | ctx->old_flag = r->flag; | 3832 | ctx->old_flag = r->flag; |
3805 | word->o_assignment = r->assignment_flag; | 3833 | ctx->is_assignment = r->assignment_flag; |
3806 | debug_printf_parse("word->o_assignment='%s'\n", assignment_flag[word->o_assignment]); | 3834 | debug_printf_parse("ctx->is_assignment='%s'\n", assignment_flag[ctx->is_assignment]); |
3807 | 3835 | ||
3808 | if (ctx->old_flag & FLAG_END) { | 3836 | if (ctx->old_flag & FLAG_END) { |
3809 | struct parse_context *old; | 3837 | struct parse_context *old; |
@@ -3849,12 +3877,12 @@ static const struct reserved_combo* reserved_word(o_string *word, struct parse_c | |||
3849 | * Normal return is 0. Syntax errors return 1. | 3877 | * Normal return is 0. Syntax errors return 1. |
3850 | * Note: on return, word is reset, but not o_free'd! | 3878 | * Note: on return, word is reset, but not o_free'd! |
3851 | */ | 3879 | */ |
3852 | static int done_word(o_string *word, struct parse_context *ctx) | 3880 | static int done_word(struct parse_context *ctx) |
3853 | { | 3881 | { |
3854 | struct command *command = ctx->command; | 3882 | struct command *command = ctx->command; |
3855 | 3883 | ||
3856 | debug_printf_parse("done_word entered: '%s' %p\n", word->data, command); | 3884 | debug_printf_parse("done_word entered: '%s' %p\n", ctx->word.data, command); |
3857 | if (word->length == 0 && !word->has_quoted_part) { | 3885 | if (ctx->word.length == 0 && !ctx->word.has_quoted_part) { |
3858 | debug_printf_parse("done_word return 0: true null, ignored\n"); | 3886 | debug_printf_parse("done_word return 0: true null, ignored\n"); |
3859 | return 0; | 3887 | return 0; |
3860 | } | 3888 | } |
@@ -3884,7 +3912,7 @@ static int done_word(o_string *word, struct parse_context *ctx) | |||
3884 | // <<EOF$((1)) | 3912 | // <<EOF$((1)) |
3885 | // <<EOF`true` [this case also makes heredoc "quoted", a-la <<"EOF". Probably bash-4.3.43 bug] | 3913 | // <<EOF`true` [this case also makes heredoc "quoted", a-la <<"EOF". Probably bash-4.3.43 bug] |
3886 | 3914 | ||
3887 | ctx->pending_redirect->rd_filename = xstrdup(word->data); | 3915 | ctx->pending_redirect->rd_filename = xstrdup(ctx->word.data); |
3888 | /* Cater for >\file case: | 3916 | /* Cater for >\file case: |
3889 | * >\a creates file a; >\\a, >"\a", >"\\a" create file \a | 3917 | * >\a creates file a; >\\a, >"\a", >"\\a" create file \a |
3890 | * Same with heredocs: | 3918 | * Same with heredocs: |
@@ -3893,17 +3921,17 @@ static int done_word(o_string *word, struct parse_context *ctx) | |||
3893 | if (ctx->pending_redirect->rd_type == REDIRECT_HEREDOC) { | 3921 | if (ctx->pending_redirect->rd_type == REDIRECT_HEREDOC) { |
3894 | unbackslash(ctx->pending_redirect->rd_filename); | 3922 | unbackslash(ctx->pending_redirect->rd_filename); |
3895 | /* Is it <<"HEREDOC"? */ | 3923 | /* Is it <<"HEREDOC"? */ |
3896 | if (word->has_quoted_part) { | 3924 | if (ctx->word.has_quoted_part) { |
3897 | ctx->pending_redirect->rd_dup |= HEREDOC_QUOTED; | 3925 | ctx->pending_redirect->rd_dup |= HEREDOC_QUOTED; |
3898 | } | 3926 | } |
3899 | } | 3927 | } |
3900 | debug_printf_parse("word stored in rd_filename: '%s'\n", word->data); | 3928 | debug_printf_parse("word stored in rd_filename: '%s'\n", ctx->word.data); |
3901 | ctx->pending_redirect = NULL; | 3929 | ctx->pending_redirect = NULL; |
3902 | } else { | 3930 | } else { |
3903 | #if HAS_KEYWORDS | 3931 | #if HAS_KEYWORDS |
3904 | # if ENABLE_HUSH_CASE | 3932 | # if ENABLE_HUSH_CASE |
3905 | if (ctx->ctx_dsemicolon | 3933 | if (ctx->ctx_dsemicolon |
3906 | && strcmp(word->data, "esac") != 0 /* not "... pattern) cmd;; esac" */ | 3934 | && strcmp(ctx->word.data, "esac") != 0 /* not "... pattern) cmd;; esac" */ |
3907 | ) { | 3935 | ) { |
3908 | /* already done when ctx_dsemicolon was set to 1: */ | 3936 | /* already done when ctx_dsemicolon was set to 1: */ |
3909 | /* ctx->ctx_res_w = RES_MATCH; */ | 3937 | /* ctx->ctx_res_w = RES_MATCH; */ |
@@ -3920,7 +3948,7 @@ static int done_word(o_string *word, struct parse_context *ctx) | |||
3920 | # endif | 3948 | # endif |
3921 | ) { | 3949 | ) { |
3922 | const struct reserved_combo *reserved; | 3950 | const struct reserved_combo *reserved; |
3923 | reserved = reserved_word(word, ctx); | 3951 | reserved = reserved_word(ctx); |
3924 | debug_printf_parse("checking for reserved-ness: %d\n", !!reserved); | 3952 | debug_printf_parse("checking for reserved-ness: %d\n", !!reserved); |
3925 | if (reserved) { | 3953 | if (reserved) { |
3926 | # if ENABLE_HUSH_LINENO_VAR | 3954 | # if ENABLE_HUSH_LINENO_VAR |
@@ -3939,7 +3967,7 @@ static int done_word(o_string *word, struct parse_context *ctx) | |||
3939 | done_pipe(ctx, PIPE_SEQ); | 3967 | done_pipe(ctx, PIPE_SEQ); |
3940 | } | 3968 | } |
3941 | # endif | 3969 | # endif |
3942 | o_reset_to_empty_unquoted(word); | 3970 | o_reset_to_empty_unquoted(&ctx->word); |
3943 | debug_printf_parse("done_word return %d\n", | 3971 | debug_printf_parse("done_word return %d\n", |
3944 | (ctx->ctx_res_w == RES_SNTX)); | 3972 | (ctx->ctx_res_w == RES_SNTX)); |
3945 | return (ctx->ctx_res_w == RES_SNTX); | 3973 | return (ctx->ctx_res_w == RES_SNTX); |
@@ -3947,7 +3975,7 @@ static int done_word(o_string *word, struct parse_context *ctx) | |||
3947 | # if defined(CMD_SINGLEWORD_NOGLOB) | 3975 | # if defined(CMD_SINGLEWORD_NOGLOB) |
3948 | if (0 | 3976 | if (0 |
3949 | # if BASH_TEST2 | 3977 | # if BASH_TEST2 |
3950 | || strcmp(word->data, "[[") == 0 | 3978 | || strcmp(ctx->word.data, "[[") == 0 |
3951 | # endif | 3979 | # endif |
3952 | /* In bash, local/export/readonly are special, args | 3980 | /* In bash, local/export/readonly are special, args |
3953 | * are assignments and therefore expansion of them | 3981 | * are assignments and therefore expansion of them |
@@ -3964,9 +3992,9 @@ static int done_word(o_string *word, struct parse_context *ctx) | |||
3964 | * $ "export" i=`echo 'aaa bbb'`; echo "$i" | 3992 | * $ "export" i=`echo 'aaa bbb'`; echo "$i" |
3965 | * aaa | 3993 | * aaa |
3966 | */ | 3994 | */ |
3967 | IF_HUSH_LOCAL( || strcmp(word->data, "local") == 0) | 3995 | IF_HUSH_LOCAL( || strcmp(ctx->word.data, "local") == 0) |
3968 | IF_HUSH_EXPORT( || strcmp(word->data, "export") == 0) | 3996 | IF_HUSH_EXPORT( || strcmp(ctx->word.data, "export") == 0) |
3969 | IF_HUSH_READONLY( || strcmp(word->data, "readonly") == 0) | 3997 | IF_HUSH_READONLY(|| strcmp(ctx->word.data, "readonly") == 0) |
3970 | ) { | 3998 | ) { |
3971 | command->cmd_type = CMD_SINGLEWORD_NOGLOB; | 3999 | command->cmd_type = CMD_SINGLEWORD_NOGLOB; |
3972 | } | 4000 | } |
@@ -3977,7 +4005,7 @@ static int done_word(o_string *word, struct parse_context *ctx) | |||
3977 | 4005 | ||
3978 | if (command->group) { | 4006 | if (command->group) { |
3979 | /* "{ echo foo; } echo bar" - bad */ | 4007 | /* "{ echo foo; } echo bar" - bad */ |
3980 | syntax_error_at(word->data); | 4008 | syntax_error_at(ctx->word.data); |
3981 | debug_printf_parse("done_word return 1: syntax error, " | 4009 | debug_printf_parse("done_word return 1: syntax error, " |
3982 | "groups and arglists don't mix\n"); | 4010 | "groups and arglists don't mix\n"); |
3983 | return 1; | 4011 | return 1; |
@@ -3985,26 +4013,26 @@ static int done_word(o_string *word, struct parse_context *ctx) | |||
3985 | 4013 | ||
3986 | /* If this word wasn't an assignment, next ones definitely | 4014 | /* If this word wasn't an assignment, next ones definitely |
3987 | * can't be assignments. Even if they look like ones. */ | 4015 | * can't be assignments. Even if they look like ones. */ |
3988 | if (word->o_assignment != DEFINITELY_ASSIGNMENT | 4016 | if (ctx->is_assignment != DEFINITELY_ASSIGNMENT |
3989 | && word->o_assignment != WORD_IS_KEYWORD | 4017 | && ctx->is_assignment != WORD_IS_KEYWORD |
3990 | ) { | 4018 | ) { |
3991 | word->o_assignment = NOT_ASSIGNMENT; | 4019 | ctx->is_assignment = NOT_ASSIGNMENT; |
3992 | } else { | 4020 | } else { |
3993 | if (word->o_assignment == DEFINITELY_ASSIGNMENT) { | 4021 | if (ctx->is_assignment == DEFINITELY_ASSIGNMENT) { |
3994 | command->assignment_cnt++; | 4022 | command->assignment_cnt++; |
3995 | debug_printf_parse("++assignment_cnt=%d\n", command->assignment_cnt); | 4023 | debug_printf_parse("++assignment_cnt=%d\n", command->assignment_cnt); |
3996 | } | 4024 | } |
3997 | debug_printf_parse("word->o_assignment was:'%s'\n", assignment_flag[word->o_assignment]); | 4025 | debug_printf_parse("ctx->is_assignment was:'%s'\n", assignment_flag[ctx->is_assignment]); |
3998 | word->o_assignment = MAYBE_ASSIGNMENT; | 4026 | ctx->is_assignment = MAYBE_ASSIGNMENT; |
3999 | } | 4027 | } |
4000 | debug_printf_parse("word->o_assignment='%s'\n", assignment_flag[word->o_assignment]); | 4028 | debug_printf_parse("ctx->is_assignment='%s'\n", assignment_flag[ctx->is_assignment]); |
4001 | command->argv = add_string_to_strings(command->argv, xstrdup(word->data)); | 4029 | command->argv = add_string_to_strings(command->argv, xstrdup(ctx->word.data)); |
4002 | debug_print_strings("word appended to argv", command->argv); | 4030 | debug_print_strings("word appended to argv", command->argv); |
4003 | } | 4031 | } |
4004 | 4032 | ||
4005 | #if ENABLE_HUSH_LOOPS | 4033 | #if ENABLE_HUSH_LOOPS |
4006 | if (ctx->ctx_res_w == RES_FOR) { | 4034 | if (ctx->ctx_res_w == RES_FOR) { |
4007 | if (word->has_quoted_part | 4035 | if (ctx->word.has_quoted_part |
4008 | || !is_well_formed_var_name(command->argv[0], '\0') | 4036 | || !is_well_formed_var_name(command->argv[0], '\0') |
4009 | ) { | 4037 | ) { |
4010 | /* bash says just "not a valid identifier" */ | 4038 | /* bash says just "not a valid identifier" */ |
@@ -4025,7 +4053,7 @@ static int done_word(o_string *word, struct parse_context *ctx) | |||
4025 | } | 4053 | } |
4026 | #endif | 4054 | #endif |
4027 | 4055 | ||
4028 | o_reset_to_empty_unquoted(word); | 4056 | o_reset_to_empty_unquoted(&ctx->word); |
4029 | 4057 | ||
4030 | debug_printf_parse("done_word return 0\n"); | 4058 | debug_printf_parse("done_word return 0\n"); |
4031 | return 0; | 4059 | return 0; |
@@ -4309,14 +4337,10 @@ static struct pipe *parse_stream(char **pstring, | |||
4309 | int end_trigger); | 4337 | int end_trigger); |
4310 | 4338 | ||
4311 | 4339 | ||
4312 | #if !ENABLE_HUSH_FUNCTIONS | 4340 | static int parse_group(struct parse_context *ctx, |
4313 | #define parse_group(dest, ctx, input, ch) \ | ||
4314 | parse_group(ctx, input, ch) | ||
4315 | #endif | ||
4316 | static int parse_group(o_string *dest, struct parse_context *ctx, | ||
4317 | struct in_str *input, int ch) | 4341 | struct in_str *input, int ch) |
4318 | { | 4342 | { |
4319 | /* dest contains characters seen prior to ( or {. | 4343 | /* ctx->word contains characters seen prior to ( or {. |
4320 | * Typically it's empty, but for function defs, | 4344 | * Typically it's empty, but for function defs, |
4321 | * it contains function name (without '()'). */ | 4345 | * it contains function name (without '()'). */ |
4322 | #if BB_MMU | 4346 | #if BB_MMU |
@@ -4330,9 +4354,9 @@ static int parse_group(o_string *dest, struct parse_context *ctx, | |||
4330 | 4354 | ||
4331 | debug_printf_parse("parse_group entered\n"); | 4355 | debug_printf_parse("parse_group entered\n"); |
4332 | #if ENABLE_HUSH_FUNCTIONS | 4356 | #if ENABLE_HUSH_FUNCTIONS |
4333 | if (ch == '(' && !dest->has_quoted_part) { | 4357 | if (ch == '(' && !ctx->word.has_quoted_part) { |
4334 | if (dest->length) | 4358 | if (ctx->word.length) |
4335 | if (done_word(dest, ctx)) | 4359 | if (done_word(ctx)) |
4336 | return 1; | 4360 | return 1; |
4337 | if (!command->argv) | 4361 | if (!command->argv) |
4338 | goto skip; /* (... */ | 4362 | goto skip; /* (... */ |
@@ -4364,8 +4388,8 @@ static int parse_group(o_string *dest, struct parse_context *ctx, | |||
4364 | 4388 | ||
4365 | #if 0 /* Prevented by caller */ | 4389 | #if 0 /* Prevented by caller */ |
4366 | if (command->argv /* word [word]{... */ | 4390 | if (command->argv /* word [word]{... */ |
4367 | || dest->length /* word{... */ | 4391 | || ctx->word.length /* word{... */ |
4368 | || dest->has_quoted_part /* ""{... */ | 4392 | || ctx->word.has_quoted_part /* ""{... */ |
4369 | ) { | 4393 | ) { |
4370 | syntax_error(NULL); | 4394 | syntax_error(NULL); |
4371 | debug_printf_parse("parse_group return 1: " | 4395 | debug_printf_parse("parse_group return 1: " |
@@ -4913,8 +4937,12 @@ static int encode_string(o_string *as_string, | |||
4913 | ch, ch, !!(dest->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); | 4937 | ch, ch, !!(dest->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); |
4914 | if (process_bkslash && ch == '\\') { | 4938 | if (process_bkslash && ch == '\\') { |
4915 | if (next == EOF) { | 4939 | if (next == EOF) { |
4916 | syntax_error("\\<eof>"); | 4940 | /* Testcase: in interactive shell a file with |
4917 | xfunc_die(); | 4941 | * echo "unterminated string\<eof> |
4942 | * is sourced. | ||
4943 | */ | ||
4944 | syntax_error_unterm_ch('"'); | ||
4945 | return 0; /* error */ | ||
4918 | } | 4946 | } |
4919 | /* bash: | 4947 | /* bash: |
4920 | * "The backslash retains its special meaning [in "..."] | 4948 | * "The backslash retains its special meaning [in "..."] |
@@ -4971,29 +4999,28 @@ static struct pipe *parse_stream(char **pstring, | |||
4971 | int end_trigger) | 4999 | int end_trigger) |
4972 | { | 5000 | { |
4973 | struct parse_context ctx; | 5001 | struct parse_context ctx; |
4974 | o_string dest = NULL_O_STRING; | ||
4975 | int heredoc_cnt; | 5002 | int heredoc_cnt; |
4976 | 5003 | ||
4977 | /* Single-quote triggers a bypass of the main loop until its mate is | 5004 | /* Single-quote triggers a bypass of the main loop until its mate is |
4978 | * found. When recursing, quote state is passed in via dest->o_expflags. | 5005 | * found. When recursing, quote state is passed in via ctx.word.o_expflags. |
4979 | */ | 5006 | */ |
4980 | debug_printf_parse("parse_stream entered, end_trigger='%c'\n", | 5007 | debug_printf_parse("parse_stream entered, end_trigger='%c'\n", |
4981 | end_trigger ? end_trigger : 'X'); | 5008 | end_trigger ? end_trigger : 'X'); |
4982 | debug_enter(); | 5009 | debug_enter(); |
4983 | 5010 | ||
4984 | /* If very first arg is "" or '', dest.data may end up NULL. | 5011 | initialize_context(&ctx); |
4985 | * Preventing this: */ | 5012 | |
4986 | o_addchr(&dest, '\0'); | 5013 | /* If very first arg is "" or '', ctx.word.data may end up NULL. |
4987 | dest.length = 0; | 5014 | * Preventing this: |
5015 | */ | ||
5016 | o_addchr(&ctx.word, '\0'); | ||
5017 | ctx.word.length = 0; | ||
4988 | 5018 | ||
4989 | /* We used to separate words on $IFS here. This was wrong. | 5019 | /* We used to separate words on $IFS here. This was wrong. |
4990 | * $IFS is used only for word splitting when $var is expanded, | 5020 | * $IFS is used only for word splitting when $var is expanded, |
4991 | * here we should use blank chars as separators, not $IFS | 5021 | * here we should use blank chars as separators, not $IFS |
4992 | */ | 5022 | */ |
4993 | 5023 | ||
4994 | if (MAYBE_ASSIGNMENT != 0) | ||
4995 | dest.o_assignment = MAYBE_ASSIGNMENT; | ||
4996 | initialize_context(&ctx); | ||
4997 | heredoc_cnt = 0; | 5024 | heredoc_cnt = 0; |
4998 | while (1) { | 5025 | while (1) { |
4999 | const char *is_blank; | 5026 | const char *is_blank; |
@@ -5005,7 +5032,7 @@ static struct pipe *parse_stream(char **pstring, | |||
5005 | 5032 | ||
5006 | ch = i_getch(input); | 5033 | ch = i_getch(input); |
5007 | debug_printf_parse(": ch=%c (%d) escape=%d\n", | 5034 | debug_printf_parse(": ch=%c (%d) escape=%d\n", |
5008 | ch, ch, !!(dest.o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); | 5035 | ch, ch, !!(ctx.word.o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); |
5009 | if (ch == EOF) { | 5036 | if (ch == EOF) { |
5010 | struct pipe *pi; | 5037 | struct pipe *pi; |
5011 | 5038 | ||
@@ -5022,10 +5049,10 @@ static struct pipe *parse_stream(char **pstring, | |||
5022 | goto parse_error; | 5049 | goto parse_error; |
5023 | } | 5050 | } |
5024 | 5051 | ||
5025 | if (done_word(&dest, &ctx)) { | 5052 | if (done_word(&ctx)) { |
5026 | goto parse_error; | 5053 | goto parse_error; |
5027 | } | 5054 | } |
5028 | o_free(&dest); | 5055 | o_free(&ctx.word); |
5029 | done_pipe(&ctx, PIPE_SEQ); | 5056 | done_pipe(&ctx, PIPE_SEQ); |
5030 | pi = ctx.list_head; | 5057 | pi = ctx.list_head; |
5031 | /* If we got nothing... */ | 5058 | /* If we got nothing... */ |
@@ -5048,25 +5075,74 @@ static struct pipe *parse_stream(char **pstring, | |||
5048 | debug_printf_parse("parse_stream return %p\n", pi); | 5075 | debug_printf_parse("parse_stream return %p\n", pi); |
5049 | return pi; | 5076 | return pi; |
5050 | } | 5077 | } |
5051 | nommu_addchr(&ctx.as_string, ch); | ||
5052 | 5078 | ||
5053 | next = '\0'; | 5079 | /* Handle "'" and "\" first, as they won't play nice with |
5054 | if (ch != '\n') { | 5080 | * i_peek_and_eat_bkslash_nl() anyway: |
5055 | next = i_peek(input); | 5081 | * echo z\\ |
5056 | /* Can't use i_peek_and_eat_bkslash_nl(input) here: | 5082 | * and |
5057 | * echo '\ | 5083 | * echo '\ |
5058 | * ' | 5084 | * ' |
5059 | * will break. | 5085 | * would break. |
5086 | */ | ||
5087 | if (ch == '\\') { | ||
5088 | ch = i_getch(input); | ||
5089 | if (ch == '\n') | ||
5090 | continue; /* drop \<newline>, get next char */ | ||
5091 | nommu_addchr(&ctx.as_string, '\\'); | ||
5092 | o_addchr(&ctx.word, '\\'); | ||
5093 | if (ch == EOF) { | ||
5094 | /* Testcase: eval 'echo Ok\' */ | ||
5095 | /* bash-4.3.43 was removing backslash, | ||
5096 | * but 4.4.19 retains it, most other shells too | ||
5097 | */ | ||
5098 | continue; /* get next char */ | ||
5099 | } | ||
5100 | /* Example: echo Hello \2>file | ||
5101 | * we need to know that word 2 is quoted | ||
5060 | */ | 5102 | */ |
5103 | ctx.word.has_quoted_part = 1; | ||
5104 | nommu_addchr(&ctx.as_string, ch); | ||
5105 | o_addchr(&ctx.word, ch); | ||
5106 | continue; /* get next char */ | ||
5061 | } | 5107 | } |
5108 | nommu_addchr(&ctx.as_string, ch); | ||
5109 | if (ch == '\'') { | ||
5110 | ctx.word.has_quoted_part = 1; | ||
5111 | next = i_getch(input); | ||
5112 | if (next == '\'' && !ctx.pending_redirect) | ||
5113 | goto insert_empty_quoted_str_marker; | ||
5062 | 5114 | ||
5063 | is_special = "{}<>;&|()#'" /* special outside of "str" */ | 5115 | ch = next; |
5064 | "\\$\"" IF_HUSH_TICK("`") /* always special */ | 5116 | while (1) { |
5117 | if (ch == EOF) { | ||
5118 | syntax_error_unterm_ch('\''); | ||
5119 | goto parse_error; | ||
5120 | } | ||
5121 | nommu_addchr(&ctx.as_string, ch); | ||
5122 | if (ch == '\'') | ||
5123 | break; | ||
5124 | if (ch == SPECIAL_VAR_SYMBOL) { | ||
5125 | /* Convert raw ^C to corresponding special variable reference */ | ||
5126 | o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); | ||
5127 | o_addchr(&ctx.word, SPECIAL_VAR_QUOTED_SVS); | ||
5128 | } | ||
5129 | o_addqchr(&ctx.word, ch); | ||
5130 | ch = i_getch(input); | ||
5131 | } | ||
5132 | continue; /* get next char */ | ||
5133 | } | ||
5134 | |||
5135 | next = '\0'; | ||
5136 | if (ch != '\n') | ||
5137 | next = i_peek_and_eat_bkslash_nl(input); | ||
5138 | |||
5139 | is_special = "{}<>;&|()#" /* special outside of "str" */ | ||
5140 | "$\"" IF_HUSH_TICK("`") /* always special */ | ||
5065 | SPECIAL_VAR_SYMBOL_STR; | 5141 | SPECIAL_VAR_SYMBOL_STR; |
5066 | /* Are { and } special here? */ | 5142 | /* Are { and } special here? */ |
5067 | if (ctx.command->argv /* word [word]{... - non-special */ | 5143 | if (ctx.command->argv /* word [word]{... - non-special */ |
5068 | || dest.length /* word{... - non-special */ | 5144 | || ctx.word.length /* word{... - non-special */ |
5069 | || dest.has_quoted_part /* ""{... - non-special */ | 5145 | || ctx.word.has_quoted_part /* ""{... - non-special */ |
5070 | || (next != ';' /* }; - special */ | 5146 | || (next != ';' /* }; - special */ |
5071 | && next != ')' /* }) - special */ | 5147 | && next != ')' /* }) - special */ |
5072 | && next != '(' /* {( - special */ | 5148 | && next != '(' /* {( - special */ |
@@ -5083,14 +5159,14 @@ static struct pipe *parse_stream(char **pstring, | |||
5083 | 5159 | ||
5084 | if (!is_special && !is_blank) { /* ordinary char */ | 5160 | if (!is_special && !is_blank) { /* ordinary char */ |
5085 | ordinary_char: | 5161 | ordinary_char: |
5086 | o_addQchr(&dest, ch); | 5162 | o_addQchr(&ctx.word, ch); |
5087 | if ((dest.o_assignment == MAYBE_ASSIGNMENT | 5163 | if ((ctx.is_assignment == MAYBE_ASSIGNMENT |
5088 | || dest.o_assignment == WORD_IS_KEYWORD) | 5164 | || ctx.is_assignment == WORD_IS_KEYWORD) |
5089 | && ch == '=' | 5165 | && ch == '=' |
5090 | && is_well_formed_var_name(dest.data, '=') | 5166 | && is_well_formed_var_name(ctx.word.data, '=') |
5091 | ) { | 5167 | ) { |
5092 | dest.o_assignment = DEFINITELY_ASSIGNMENT; | 5168 | ctx.is_assignment = DEFINITELY_ASSIGNMENT; |
5093 | debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); | 5169 | debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); |
5094 | } | 5170 | } |
5095 | continue; | 5171 | continue; |
5096 | } | 5172 | } |
@@ -5112,7 +5188,7 @@ static struct pipe *parse_stream(char **pstring, | |||
5112 | } | 5188 | } |
5113 | /* ch == last eaten whitespace char */ | 5189 | /* ch == last eaten whitespace char */ |
5114 | #endif | 5190 | #endif |
5115 | if (done_word(&dest, &ctx)) { | 5191 | if (done_word(&ctx)) { |
5116 | goto parse_error; | 5192 | goto parse_error; |
5117 | } | 5193 | } |
5118 | if (ch == '\n') { | 5194 | if (ch == '\n') { |
@@ -5122,7 +5198,7 @@ static struct pipe *parse_stream(char **pstring, | |||
5122 | * "case ... in <newline> word) ..." | 5198 | * "case ... in <newline> word) ..." |
5123 | */ | 5199 | */ |
5124 | if (IS_NULL_CMD(ctx.command) | 5200 | if (IS_NULL_CMD(ctx.command) |
5125 | && dest.length == 0 && !dest.has_quoted_part | 5201 | && ctx.word.length == 0 && !ctx.word.has_quoted_part |
5126 | ) { | 5202 | ) { |
5127 | /* This newline can be ignored. But... | 5203 | /* This newline can be ignored. But... |
5128 | * Without check #1, interactive shell | 5204 | * Without check #1, interactive shell |
@@ -5157,8 +5233,8 @@ static struct pipe *parse_stream(char **pstring, | |||
5157 | } | 5233 | } |
5158 | heredoc_cnt = 0; | 5234 | heredoc_cnt = 0; |
5159 | } | 5235 | } |
5160 | dest.o_assignment = MAYBE_ASSIGNMENT; | 5236 | ctx.is_assignment = MAYBE_ASSIGNMENT; |
5161 | debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); | 5237 | debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); |
5162 | ch = ';'; | 5238 | ch = ';'; |
5163 | /* note: if (is_blank) continue; | 5239 | /* note: if (is_blank) continue; |
5164 | * will still trigger for us */ | 5240 | * will still trigger for us */ |
@@ -5170,8 +5246,8 @@ static struct pipe *parse_stream(char **pstring, | |||
5170 | * Pathological example: { ""}; } should exec "}" cmd | 5246 | * Pathological example: { ""}; } should exec "}" cmd |
5171 | */ | 5247 | */ |
5172 | if (ch == '}') { | 5248 | if (ch == '}') { |
5173 | if (dest.length != 0 /* word} */ | 5249 | if (ctx.word.length != 0 /* word} */ |
5174 | || dest.has_quoted_part /* ""} */ | 5250 | || ctx.word.has_quoted_part /* ""} */ |
5175 | ) { | 5251 | ) { |
5176 | goto ordinary_char; | 5252 | goto ordinary_char; |
5177 | } | 5253 | } |
@@ -5200,7 +5276,7 @@ static struct pipe *parse_stream(char **pstring, | |||
5200 | #if ENABLE_HUSH_CASE | 5276 | #if ENABLE_HUSH_CASE |
5201 | && (ch != ')' | 5277 | && (ch != ')' |
5202 | || ctx.ctx_res_w != RES_MATCH | 5278 | || ctx.ctx_res_w != RES_MATCH |
5203 | || (!dest.has_quoted_part && strcmp(dest.data, "esac") == 0) | 5279 | || (!ctx.word.has_quoted_part && strcmp(ctx.word.data, "esac") == 0) |
5204 | ) | 5280 | ) |
5205 | #endif | 5281 | #endif |
5206 | ) { | 5282 | ) { |
@@ -5217,17 +5293,17 @@ static struct pipe *parse_stream(char **pstring, | |||
5217 | syntax_error_unterm_str("here document"); | 5293 | syntax_error_unterm_str("here document"); |
5218 | goto parse_error; | 5294 | goto parse_error; |
5219 | } | 5295 | } |
5220 | if (done_word(&dest, &ctx)) { | 5296 | if (done_word(&ctx)) { |
5221 | goto parse_error; | 5297 | goto parse_error; |
5222 | } | 5298 | } |
5223 | done_pipe(&ctx, PIPE_SEQ); | 5299 | done_pipe(&ctx, PIPE_SEQ); |
5224 | dest.o_assignment = MAYBE_ASSIGNMENT; | 5300 | ctx.is_assignment = MAYBE_ASSIGNMENT; |
5225 | debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); | 5301 | debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); |
5226 | /* Do we sit outside of any if's, loops or case's? */ | 5302 | /* Do we sit outside of any if's, loops or case's? */ |
5227 | if (!HAS_KEYWORDS | 5303 | if (!HAS_KEYWORDS |
5228 | IF_HAS_KEYWORDS(|| (ctx.ctx_res_w == RES_NONE && ctx.old_flag == 0)) | 5304 | IF_HAS_KEYWORDS(|| (ctx.ctx_res_w == RES_NONE && ctx.old_flag == 0)) |
5229 | ) { | 5305 | ) { |
5230 | o_free(&dest); | 5306 | o_free(&ctx.word); |
5231 | #if !BB_MMU | 5307 | #if !BB_MMU |
5232 | debug_printf_parse("as_string2 '%s'\n", ctx.as_string.data); | 5308 | debug_printf_parse("as_string2 '%s'\n", ctx.as_string.data); |
5233 | if (pstring) | 5309 | if (pstring) |
@@ -5248,7 +5324,7 @@ static struct pipe *parse_stream(char **pstring, | |||
5248 | return ctx.list_head; | 5324 | return ctx.list_head; |
5249 | } | 5325 | } |
5250 | } | 5326 | } |
5251 | skip_end_trigger: | 5327 | |
5252 | if (is_blank) | 5328 | if (is_blank) |
5253 | continue; | 5329 | continue; |
5254 | 5330 | ||
@@ -5256,13 +5332,11 @@ static struct pipe *parse_stream(char **pstring, | |||
5256 | * an assignment. a=1 2>z b=2: b=2 is still assignment */ | 5332 | * an assignment. a=1 2>z b=2: b=2 is still assignment */ |
5257 | switch (ch) { | 5333 | switch (ch) { |
5258 | case '>': | 5334 | case '>': |
5259 | redir_fd = redirect_opt_num(&dest); | 5335 | redir_fd = redirect_opt_num(&ctx.word); |
5260 | if (done_word(&dest, &ctx)) { | 5336 | if (done_word(&ctx)) { |
5261 | goto parse_error; | 5337 | goto parse_error; |
5262 | } | 5338 | } |
5263 | redir_style = REDIRECT_OVERWRITE; | 5339 | redir_style = REDIRECT_OVERWRITE; |
5264 | if (next == '\\') | ||
5265 | next = i_peek_and_eat_bkslash_nl(input); | ||
5266 | if (next == '>') { | 5340 | if (next == '>') { |
5267 | redir_style = REDIRECT_APPEND; | 5341 | redir_style = REDIRECT_APPEND; |
5268 | ch = i_getch(input); | 5342 | ch = i_getch(input); |
@@ -5276,15 +5350,13 @@ static struct pipe *parse_stream(char **pstring, | |||
5276 | #endif | 5350 | #endif |
5277 | if (parse_redirect(&ctx, redir_fd, redir_style, input)) | 5351 | if (parse_redirect(&ctx, redir_fd, redir_style, input)) |
5278 | goto parse_error; | 5352 | goto parse_error; |
5279 | continue; /* back to top of while (1) */ | 5353 | continue; /* get next char */ |
5280 | case '<': | 5354 | case '<': |
5281 | redir_fd = redirect_opt_num(&dest); | 5355 | redir_fd = redirect_opt_num(&ctx.word); |
5282 | if (done_word(&dest, &ctx)) { | 5356 | if (done_word(&ctx)) { |
5283 | goto parse_error; | 5357 | goto parse_error; |
5284 | } | 5358 | } |
5285 | redir_style = REDIRECT_INPUT; | 5359 | redir_style = REDIRECT_INPUT; |
5286 | if (next == '\\') | ||
5287 | next = i_peek_and_eat_bkslash_nl(input); | ||
5288 | if (next == '<') { | 5360 | if (next == '<') { |
5289 | redir_style = REDIRECT_HEREDOC; | 5361 | redir_style = REDIRECT_HEREDOC; |
5290 | heredoc_cnt++; | 5362 | heredoc_cnt++; |
@@ -5304,9 +5376,9 @@ static struct pipe *parse_stream(char **pstring, | |||
5304 | #endif | 5376 | #endif |
5305 | if (parse_redirect(&ctx, redir_fd, redir_style, input)) | 5377 | if (parse_redirect(&ctx, redir_fd, redir_style, input)) |
5306 | goto parse_error; | 5378 | goto parse_error; |
5307 | continue; /* back to top of while (1) */ | 5379 | continue; /* get next char */ |
5308 | case '#': | 5380 | case '#': |
5309 | if (dest.length == 0 && !dest.has_quoted_part) { | 5381 | if (ctx.word.length == 0 && !ctx.word.has_quoted_part) { |
5310 | /* skip "#comment" */ | 5382 | /* skip "#comment" */ |
5311 | /* note: we do not add it to &ctx.as_string */ | 5383 | /* note: we do not add it to &ctx.as_string */ |
5312 | /* TODO: in bash: | 5384 | /* TODO: in bash: |
@@ -5325,30 +5397,20 @@ static struct pipe *parse_stream(char **pstring, | |||
5325 | if (ch == EOF) | 5397 | if (ch == EOF) |
5326 | break; | 5398 | break; |
5327 | } | 5399 | } |
5328 | continue; /* back to top of while (1) */ | 5400 | continue; /* get next char */ |
5329 | } | ||
5330 | break; | ||
5331 | case '\\': | ||
5332 | if (next == '\n') { | ||
5333 | /* It's "\<newline>" */ | ||
5334 | #if !BB_MMU | ||
5335 | /* Remove trailing '\' from ctx.as_string */ | ||
5336 | ctx.as_string.data[--ctx.as_string.length] = '\0'; | ||
5337 | #endif | ||
5338 | ch = i_getch(input); /* eat it */ | ||
5339 | continue; /* back to top of while (1) */ | ||
5340 | } | 5401 | } |
5341 | break; | 5402 | break; |
5342 | } | 5403 | } |
5404 | skip_end_trigger: | ||
5343 | 5405 | ||
5344 | if (dest.o_assignment == MAYBE_ASSIGNMENT | 5406 | if (ctx.is_assignment == MAYBE_ASSIGNMENT |
5345 | /* check that we are not in word in "a=1 2>word b=1": */ | 5407 | /* check that we are not in word in "a=1 2>word b=1": */ |
5346 | && !ctx.pending_redirect | 5408 | && !ctx.pending_redirect |
5347 | ) { | 5409 | ) { |
5348 | /* ch is a special char and thus this word | 5410 | /* ch is a special char and thus this word |
5349 | * cannot be an assignment */ | 5411 | * cannot be an assignment */ |
5350 | dest.o_assignment = NOT_ASSIGNMENT; | 5412 | ctx.is_assignment = NOT_ASSIGNMENT; |
5351 | debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); | 5413 | debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); |
5352 | } | 5414 | } |
5353 | 5415 | ||
5354 | /* Note: nommu_addchr(&ctx.as_string, ch) is already done */ | 5416 | /* Note: nommu_addchr(&ctx.as_string, ch) is already done */ |
@@ -5356,95 +5418,59 @@ static struct pipe *parse_stream(char **pstring, | |||
5356 | switch (ch) { | 5418 | switch (ch) { |
5357 | case SPECIAL_VAR_SYMBOL: | 5419 | case SPECIAL_VAR_SYMBOL: |
5358 | /* Convert raw ^C to corresponding special variable reference */ | 5420 | /* Convert raw ^C to corresponding special variable reference */ |
5359 | o_addchr(&dest, SPECIAL_VAR_SYMBOL); | 5421 | o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); |
5360 | o_addchr(&dest, SPECIAL_VAR_QUOTED_SVS); | 5422 | o_addchr(&ctx.word, SPECIAL_VAR_QUOTED_SVS); |
5361 | /* fall through */ | 5423 | /* fall through */ |
5362 | case '#': | 5424 | case '#': |
5363 | /* non-comment #: "echo a#b" etc */ | 5425 | /* non-comment #: "echo a#b" etc */ |
5364 | o_addchr(&dest, ch); | 5426 | o_addchr(&ctx.word, ch); |
5365 | break; | 5427 | continue; /* get next char */ |
5366 | case '\\': | ||
5367 | if (next == EOF) { | ||
5368 | syntax_error("\\<eof>"); | ||
5369 | xfunc_die(); | ||
5370 | } | ||
5371 | ch = i_getch(input); | ||
5372 | /* note: ch != '\n' (that case does not reach this place) */ | ||
5373 | o_addchr(&dest, '\\'); | ||
5374 | /*nommu_addchr(&ctx.as_string, '\\'); - already done */ | ||
5375 | o_addchr(&dest, ch); | ||
5376 | nommu_addchr(&ctx.as_string, ch); | ||
5377 | /* Example: echo Hello \2>file | ||
5378 | * we need to know that word 2 is quoted */ | ||
5379 | dest.has_quoted_part = 1; | ||
5380 | break; | ||
5381 | case '$': | 5428 | case '$': |
5382 | if (!parse_dollar(&ctx.as_string, &dest, input, /*quote_mask:*/ 0)) { | 5429 | if (!parse_dollar(&ctx.as_string, &ctx.word, input, /*quote_mask:*/ 0)) { |
5383 | debug_printf_parse("parse_stream parse error: " | 5430 | debug_printf_parse("parse_stream parse error: " |
5384 | "parse_dollar returned 0 (error)\n"); | 5431 | "parse_dollar returned 0 (error)\n"); |
5385 | goto parse_error; | 5432 | goto parse_error; |
5386 | } | 5433 | } |
5387 | break; | 5434 | continue; /* get next char */ |
5388 | case '\'': | 5435 | case '"': |
5389 | dest.has_quoted_part = 1; | 5436 | ctx.word.has_quoted_part = 1; |
5390 | if (next == '\'' && !ctx.pending_redirect) { | 5437 | if (next == '"' && !ctx.pending_redirect) { |
5438 | i_getch(input); /* eat second " */ | ||
5391 | insert_empty_quoted_str_marker: | 5439 | insert_empty_quoted_str_marker: |
5392 | nommu_addchr(&ctx.as_string, next); | 5440 | nommu_addchr(&ctx.as_string, next); |
5393 | i_getch(input); /* eat second ' */ | 5441 | o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); |
5394 | o_addchr(&dest, SPECIAL_VAR_SYMBOL); | 5442 | o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); |
5395 | o_addchr(&dest, SPECIAL_VAR_SYMBOL); | 5443 | continue; /* get next char */ |
5396 | } else { | ||
5397 | while (1) { | ||
5398 | ch = i_getch(input); | ||
5399 | if (ch == EOF) { | ||
5400 | syntax_error_unterm_ch('\''); | ||
5401 | goto parse_error; | ||
5402 | } | ||
5403 | nommu_addchr(&ctx.as_string, ch); | ||
5404 | if (ch == '\'') | ||
5405 | break; | ||
5406 | if (ch == SPECIAL_VAR_SYMBOL) { | ||
5407 | /* Convert raw ^C to corresponding special variable reference */ | ||
5408 | o_addchr(&dest, SPECIAL_VAR_SYMBOL); | ||
5409 | o_addchr(&dest, SPECIAL_VAR_QUOTED_SVS); | ||
5410 | } | ||
5411 | o_addqchr(&dest, ch); | ||
5412 | } | ||
5413 | } | 5444 | } |
5414 | break; | 5445 | if (ctx.is_assignment == NOT_ASSIGNMENT) |
5415 | case '"': | 5446 | ctx.word.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS; |
5416 | dest.has_quoted_part = 1; | 5447 | if (!encode_string(&ctx.as_string, &ctx.word, input, '"', /*process_bkslash:*/ 1)) |
5417 | if (next == '"' && !ctx.pending_redirect) | ||
5418 | goto insert_empty_quoted_str_marker; | ||
5419 | if (dest.o_assignment == NOT_ASSIGNMENT) | ||
5420 | dest.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS; | ||
5421 | if (!encode_string(&ctx.as_string, &dest, input, '"', /*process_bkslash:*/ 1)) | ||
5422 | goto parse_error; | 5448 | goto parse_error; |
5423 | dest.o_expflags &= ~EXP_FLAG_ESC_GLOB_CHARS; | 5449 | ctx.word.o_expflags &= ~EXP_FLAG_ESC_GLOB_CHARS; |
5424 | break; | 5450 | continue; /* get next char */ |
5425 | #if ENABLE_HUSH_TICK | 5451 | #if ENABLE_HUSH_TICK |
5426 | case '`': { | 5452 | case '`': { |
5427 | USE_FOR_NOMMU(unsigned pos;) | 5453 | USE_FOR_NOMMU(unsigned pos;) |
5428 | 5454 | ||
5429 | o_addchr(&dest, SPECIAL_VAR_SYMBOL); | 5455 | o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); |
5430 | o_addchr(&dest, '`'); | 5456 | o_addchr(&ctx.word, '`'); |
5431 | USE_FOR_NOMMU(pos = dest.length;) | 5457 | USE_FOR_NOMMU(pos = ctx.word.length;) |
5432 | if (!add_till_backquote(&dest, input, /*in_dquote:*/ 0)) | 5458 | if (!add_till_backquote(&ctx.word, input, /*in_dquote:*/ 0)) |
5433 | goto parse_error; | 5459 | goto parse_error; |
5434 | # if !BB_MMU | 5460 | # if !BB_MMU |
5435 | o_addstr(&ctx.as_string, dest.data + pos); | 5461 | o_addstr(&ctx.as_string, ctx.word.data + pos); |
5436 | o_addchr(&ctx.as_string, '`'); | 5462 | o_addchr(&ctx.as_string, '`'); |
5437 | # endif | 5463 | # endif |
5438 | o_addchr(&dest, SPECIAL_VAR_SYMBOL); | 5464 | o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); |
5439 | //debug_printf_subst("SUBST RES3 '%s'\n", dest.data + pos); | 5465 | //debug_printf_subst("SUBST RES3 '%s'\n", ctx.word.data + pos); |
5440 | break; | 5466 | continue; /* get next char */ |
5441 | } | 5467 | } |
5442 | #endif | 5468 | #endif |
5443 | case ';': | 5469 | case ';': |
5444 | #if ENABLE_HUSH_CASE | 5470 | #if ENABLE_HUSH_CASE |
5445 | case_semi: | 5471 | case_semi: |
5446 | #endif | 5472 | #endif |
5447 | if (done_word(&dest, &ctx)) { | 5473 | if (done_word(&ctx)) { |
5448 | goto parse_error; | 5474 | goto parse_error; |
5449 | } | 5475 | } |
5450 | done_pipe(&ctx, PIPE_SEQ); | 5476 | done_pipe(&ctx, PIPE_SEQ); |
@@ -5467,15 +5493,13 @@ static struct pipe *parse_stream(char **pstring, | |||
5467 | new_cmd: | 5493 | new_cmd: |
5468 | /* We just finished a cmd. New one may start | 5494 | /* We just finished a cmd. New one may start |
5469 | * with an assignment */ | 5495 | * with an assignment */ |
5470 | dest.o_assignment = MAYBE_ASSIGNMENT; | 5496 | ctx.is_assignment = MAYBE_ASSIGNMENT; |
5471 | debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); | 5497 | debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); |
5472 | break; | 5498 | continue; /* get next char */ |
5473 | case '&': | 5499 | case '&': |
5474 | if (done_word(&dest, &ctx)) { | 5500 | if (done_word(&ctx)) { |
5475 | goto parse_error; | 5501 | goto parse_error; |
5476 | } | 5502 | } |
5477 | if (next == '\\') | ||
5478 | next = i_peek_and_eat_bkslash_nl(input); | ||
5479 | if (next == '&') { | 5503 | if (next == '&') { |
5480 | ch = i_getch(input); | 5504 | ch = i_getch(input); |
5481 | nommu_addchr(&ctx.as_string, ch); | 5505 | nommu_addchr(&ctx.as_string, ch); |
@@ -5485,15 +5509,13 @@ static struct pipe *parse_stream(char **pstring, | |||
5485 | } | 5509 | } |
5486 | goto new_cmd; | 5510 | goto new_cmd; |
5487 | case '|': | 5511 | case '|': |
5488 | if (done_word(&dest, &ctx)) { | 5512 | if (done_word(&ctx)) { |
5489 | goto parse_error; | 5513 | goto parse_error; |
5490 | } | 5514 | } |
5491 | #if ENABLE_HUSH_CASE | 5515 | #if ENABLE_HUSH_CASE |
5492 | if (ctx.ctx_res_w == RES_MATCH) | 5516 | if (ctx.ctx_res_w == RES_MATCH) |
5493 | break; /* we are in case's "word | word)" */ | 5517 | break; /* we are in case's "word | word)" */ |
5494 | #endif | 5518 | #endif |
5495 | if (next == '\\') | ||
5496 | next = i_peek_and_eat_bkslash_nl(input); | ||
5497 | if (next == '|') { /* || */ | 5519 | if (next == '|') { /* || */ |
5498 | ch = i_getch(input); | 5520 | ch = i_getch(input); |
5499 | nommu_addchr(&ctx.as_string, ch); | 5521 | nommu_addchr(&ctx.as_string, ch); |
@@ -5510,14 +5532,14 @@ static struct pipe *parse_stream(char **pstring, | |||
5510 | /* "case... in [(]word)..." - skip '(' */ | 5532 | /* "case... in [(]word)..." - skip '(' */ |
5511 | if (ctx.ctx_res_w == RES_MATCH | 5533 | if (ctx.ctx_res_w == RES_MATCH |
5512 | && ctx.command->argv == NULL /* not (word|(... */ | 5534 | && ctx.command->argv == NULL /* not (word|(... */ |
5513 | && dest.length == 0 /* not word(... */ | 5535 | && ctx.word.length == 0 /* not word(... */ |
5514 | && dest.has_quoted_part == 0 /* not ""(... */ | 5536 | && ctx.word.has_quoted_part == 0 /* not ""(... */ |
5515 | ) { | 5537 | ) { |
5516 | continue; | 5538 | continue; /* get next char */ |
5517 | } | 5539 | } |
5518 | #endif | 5540 | #endif |
5519 | case '{': | 5541 | case '{': |
5520 | if (parse_group(&dest, &ctx, input, ch) != 0) { | 5542 | if (parse_group(&ctx, input, ch) != 0) { |
5521 | goto parse_error; | 5543 | goto parse_error; |
5522 | } | 5544 | } |
5523 | goto new_cmd; | 5545 | goto new_cmd; |
@@ -5574,7 +5596,7 @@ static struct pipe *parse_stream(char **pstring, | |||
5574 | IF_HAS_KEYWORDS(pctx = p2;) | 5596 | IF_HAS_KEYWORDS(pctx = p2;) |
5575 | } while (HAS_KEYWORDS && pctx); | 5597 | } while (HAS_KEYWORDS && pctx); |
5576 | 5598 | ||
5577 | o_free(&dest); | 5599 | o_free(&ctx.word); |
5578 | #if !BB_MMU | 5600 | #if !BB_MMU |
5579 | if (pstring) | 5601 | if (pstring) |
5580 | *pstring = NULL; | 5602 | *pstring = NULL; |
@@ -5589,11 +5611,10 @@ static struct pipe *parse_stream(char **pstring, | |||
5589 | 5611 | ||
5590 | /* Expansion can recurse, need forward decls: */ | 5612 | /* Expansion can recurse, need forward decls: */ |
5591 | #if !BASH_PATTERN_SUBST && !ENABLE_HUSH_CASE | 5613 | #if !BASH_PATTERN_SUBST && !ENABLE_HUSH_CASE |
5592 | /* only ${var/pattern/repl} (its pattern part) needs additional mode */ | 5614 | #define expand_string_to_string(str, EXP_flags, do_unbackslash) \ |
5593 | #define expand_string_to_string(str, do_unbackslash) \ | ||
5594 | expand_string_to_string(str) | 5615 | expand_string_to_string(str) |
5595 | #endif | 5616 | #endif |
5596 | static char *expand_string_to_string(const char *str, int do_unbackslash); | 5617 | static char *expand_string_to_string(const char *str, int EXP_flags, int do_unbackslash); |
5597 | #if ENABLE_HUSH_TICK | 5618 | #if ENABLE_HUSH_TICK |
5598 | static int process_command_subs(o_string *dest, const char *s); | 5619 | static int process_command_subs(o_string *dest, const char *s); |
5599 | #endif | 5620 | #endif |
@@ -5676,10 +5697,20 @@ static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const cha | |||
5676 | 5697 | ||
5677 | /* We know str here points to at least one IFS char */ | 5698 | /* We know str here points to at least one IFS char */ |
5678 | last_is_ifs = 1; | 5699 | last_is_ifs = 1; |
5679 | str += strspn(str, G.ifs); /* skip IFS chars */ | 5700 | str += strspn(str, G.ifs_whitespace); /* skip IFS whitespace chars */ |
5680 | if (!*str) /* EOL - do not finalize word */ | 5701 | if (!*str) /* EOL - do not finalize word */ |
5681 | break; | 5702 | break; |
5682 | 5703 | ||
5704 | if (G.ifs_whitespace != G.ifs /* usually false ($IFS is usually all whitespace), */ | ||
5705 | && strchr(G.ifs, *str) /* the second check would fail */ | ||
5706 | ) { | ||
5707 | /* This is a non-whitespace $IFS char */ | ||
5708 | /* Skip it and IFS whitespace chars, start new word */ | ||
5709 | str++; | ||
5710 | str += strspn(str, G.ifs_whitespace); | ||
5711 | goto new_word; | ||
5712 | } | ||
5713 | |||
5683 | /* Start new word... but not always! */ | 5714 | /* Start new word... but not always! */ |
5684 | /* Case "v=' a'; echo ''$v": we do need to finalize empty word: */ | 5715 | /* Case "v=' a'; echo ''$v": we do need to finalize empty word: */ |
5685 | if (output->has_quoted_part | 5716 | if (output->has_quoted_part |
@@ -5690,6 +5721,7 @@ static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const cha | |||
5690 | */ | 5721 | */ |
5691 | || (n > 0 && output->data[output->length - 1]) | 5722 | || (n > 0 && output->data[output->length - 1]) |
5692 | ) { | 5723 | ) { |
5724 | new_word: | ||
5693 | o_addchr(output, '\0'); | 5725 | o_addchr(output, '\0'); |
5694 | debug_print_list("expand_on_ifs", output, n); | 5726 | debug_print_list("expand_on_ifs", output, n); |
5695 | n = o_save_ptr(output, n); | 5727 | n = o_save_ptr(output, n); |
@@ -5739,7 +5771,10 @@ static char *encode_then_expand_string(const char *str, int process_bkslash, int | |||
5739 | encode_string(NULL, &dest, &input, EOF, process_bkslash); | 5771 | encode_string(NULL, &dest, &input, EOF, process_bkslash); |
5740 | //TODO: error check (encode_string returns 0 on error)? | 5772 | //TODO: error check (encode_string returns 0 on error)? |
5741 | //bb_error_msg("'%s' -> '%s'", str, dest.data); | 5773 | //bb_error_msg("'%s' -> '%s'", str, dest.data); |
5742 | exp_str = expand_string_to_string(dest.data, /*unbackslash:*/ do_unbackslash); | 5774 | exp_str = expand_string_to_string(dest.data, |
5775 | do_unbackslash ? EXP_FLAG_ESC_GLOB_CHARS : 0, | ||
5776 | do_unbackslash | ||
5777 | ); | ||
5743 | //bb_error_msg("'%s' -> '%s'", dest.data, exp_str); | 5778 | //bb_error_msg("'%s' -> '%s'", dest.data, exp_str); |
5744 | o_free_unsafe(&dest); | 5779 | o_free_unsafe(&dest); |
5745 | return exp_str; | 5780 | return exp_str; |
@@ -6372,10 +6407,11 @@ static char **expand_strvec_to_strvec_singleword_noglob(char **argv) | |||
6372 | * NB: should NOT do globbing! | 6407 | * NB: should NOT do globbing! |
6373 | * "export v=/bin/c*; env | grep ^v=" outputs "v=/bin/c*" | 6408 | * "export v=/bin/c*; env | grep ^v=" outputs "v=/bin/c*" |
6374 | */ | 6409 | */ |
6375 | static char *expand_string_to_string(const char *str, int do_unbackslash) | 6410 | static char *expand_string_to_string(const char *str, int EXP_flags, int do_unbackslash) |
6376 | { | 6411 | { |
6377 | #if !BASH_PATTERN_SUBST && !ENABLE_HUSH_CASE | 6412 | #if !BASH_PATTERN_SUBST && !ENABLE_HUSH_CASE |
6378 | const int do_unbackslash = 1; | 6413 | const int do_unbackslash = 1; |
6414 | const int EXP_flags = EXP_FLAG_ESC_GLOB_CHARS; | ||
6379 | #endif | 6415 | #endif |
6380 | char *argv[2], **list; | 6416 | char *argv[2], **list; |
6381 | 6417 | ||
@@ -6392,10 +6428,7 @@ static char *expand_string_to_string(const char *str, int do_unbackslash) | |||
6392 | 6428 | ||
6393 | argv[0] = (char*)str; | 6429 | argv[0] = (char*)str; |
6394 | argv[1] = NULL; | 6430 | argv[1] = NULL; |
6395 | list = expand_variables(argv, do_unbackslash | 6431 | list = expand_variables(argv, EXP_flags | EXP_FLAG_SINGLEWORD); |
6396 | ? EXP_FLAG_ESC_GLOB_CHARS | EXP_FLAG_SINGLEWORD | ||
6397 | : EXP_FLAG_SINGLEWORD | ||
6398 | ); | ||
6399 | if (HUSH_DEBUG) | 6432 | if (HUSH_DEBUG) |
6400 | if (!list[0] || list[1]) | 6433 | if (!list[0] || list[1]) |
6401 | bb_error_msg_and_die("BUG in varexp2"); | 6434 | bb_error_msg_and_die("BUG in varexp2"); |
@@ -6439,7 +6472,13 @@ static char **expand_assignments(char **argv, int count) | |||
6439 | G.expanded_assignments = p = NULL; | 6472 | G.expanded_assignments = p = NULL; |
6440 | /* Expand assignments into one string each */ | 6473 | /* Expand assignments into one string each */ |
6441 | for (i = 0; i < count; i++) { | 6474 | for (i = 0; i < count; i++) { |
6442 | G.expanded_assignments = p = add_string_to_strings(p, expand_string_to_string(argv[i], /*unbackslash:*/ 1)); | 6475 | p = add_string_to_strings(p, |
6476 | expand_string_to_string(argv[i], | ||
6477 | EXP_FLAG_ESC_GLOB_CHARS, | ||
6478 | /*unbackslash:*/ 1 | ||
6479 | ) | ||
6480 | ); | ||
6481 | G.expanded_assignments = p; | ||
6443 | } | 6482 | } |
6444 | G.expanded_assignments = NULL; | 6483 | G.expanded_assignments = NULL; |
6445 | return p; | 6484 | return p; |
@@ -7151,7 +7190,8 @@ static int setup_redirects(struct command *prog, struct squirrel **sqp) | |||
7151 | continue; | 7190 | continue; |
7152 | } | 7191 | } |
7153 | mode = redir_table[redir->rd_type].mode; | 7192 | mode = redir_table[redir->rd_type].mode; |
7154 | p = expand_string_to_string(redir->rd_filename, /*unbackslash:*/ 1); | 7193 | p = expand_string_to_string(redir->rd_filename, |
7194 | EXP_FLAG_ESC_GLOB_CHARS, /*unbackslash:*/ 1); | ||
7155 | newfd = open_or_warn(p, mode); | 7195 | newfd = open_or_warn(p, mode); |
7156 | free(p); | 7196 | free(p); |
7157 | if (newfd < 0) { | 7197 | if (newfd < 0) { |
@@ -7358,6 +7398,58 @@ static void unset_func(const char *name) | |||
7358 | } | 7398 | } |
7359 | # endif | 7399 | # endif |
7360 | 7400 | ||
7401 | static void remove_nested_vars(void) | ||
7402 | { | ||
7403 | struct variable *cur; | ||
7404 | struct variable **cur_pp; | ||
7405 | |||
7406 | cur_pp = &G.top_var; | ||
7407 | while ((cur = *cur_pp) != NULL) { | ||
7408 | if (cur->var_nest_level <= G.var_nest_level) { | ||
7409 | cur_pp = &cur->next; | ||
7410 | continue; | ||
7411 | } | ||
7412 | /* Unexport */ | ||
7413 | if (cur->flg_export) { | ||
7414 | debug_printf_env("unexporting nested '%s'/%u\n", cur->varstr, cur->var_nest_level); | ||
7415 | bb_unsetenv(cur->varstr); | ||
7416 | } | ||
7417 | /* Remove from global list */ | ||
7418 | *cur_pp = cur->next; | ||
7419 | /* Free */ | ||
7420 | if (!cur->max_len) { | ||
7421 | debug_printf_env("freeing nested '%s'/%u\n", cur->varstr, cur->var_nest_level); | ||
7422 | free(cur->varstr); | ||
7423 | } | ||
7424 | free(cur); | ||
7425 | } | ||
7426 | } | ||
7427 | |||
7428 | static void enter_var_nest_level(void) | ||
7429 | { | ||
7430 | G.var_nest_level++; | ||
7431 | debug_printf_env("var_nest_level++ %u\n", G.var_nest_level); | ||
7432 | |||
7433 | /* Try: f() { echo -n .; f; }; f | ||
7434 | * struct variable::var_nest_level is uint16_t, | ||
7435 | * thus limiting recursion to < 2^16. | ||
7436 | * In any case, with 8 Mbyte stack SEGV happens | ||
7437 | * not too long after 2^16 recursions anyway. | ||
7438 | */ | ||
7439 | if (G.var_nest_level > 0xff00) | ||
7440 | bb_error_msg_and_die("fatal recursion (depth %u)", G.var_nest_level); | ||
7441 | } | ||
7442 | |||
7443 | static void leave_var_nest_level(void) | ||
7444 | { | ||
7445 | G.var_nest_level--; | ||
7446 | debug_printf_env("var_nest_level-- %u\n", G.var_nest_level); | ||
7447 | if (HUSH_DEBUG && (int)G.var_nest_level < 0) | ||
7448 | bb_error_msg_and_die("BUG: nesting underflow"); | ||
7449 | |||
7450 | remove_nested_vars(); | ||
7451 | } | ||
7452 | |||
7361 | # if BB_MMU | 7453 | # if BB_MMU |
7362 | #define exec_function(to_free, funcp, argv) \ | 7454 | #define exec_function(to_free, funcp, argv) \ |
7363 | exec_function(funcp, argv) | 7455 | exec_function(funcp, argv) |
@@ -7392,7 +7484,7 @@ static void exec_function(char ***to_free, | |||
7392 | 7484 | ||
7393 | /* "we are in a function, ok to use return" */ | 7485 | /* "we are in a function, ok to use return" */ |
7394 | G_flag_return_in_progress = -1; | 7486 | G_flag_return_in_progress = -1; |
7395 | G.var_nest_level++; | 7487 | enter_var_nest_level(); |
7396 | IF_HUSH_LOCAL(G.func_nest_level++;) | 7488 | IF_HUSH_LOCAL(G.func_nest_level++;) |
7397 | 7489 | ||
7398 | /* On MMU, funcp->body is always non-NULL */ | 7490 | /* On MMU, funcp->body is always non-NULL */ |
@@ -7412,53 +7504,6 @@ static void exec_function(char ***to_free, | |||
7412 | # endif | 7504 | # endif |
7413 | } | 7505 | } |
7414 | 7506 | ||
7415 | static void enter_var_nest_level(void) | ||
7416 | { | ||
7417 | G.var_nest_level++; | ||
7418 | debug_printf_env("var_nest_level++ %u\n", G.var_nest_level); | ||
7419 | |||
7420 | /* Try: f() { echo -n .; f; }; f | ||
7421 | * struct variable::var_nest_level is uint16_t, | ||
7422 | * thus limiting recursion to < 2^16. | ||
7423 | * In any case, with 8 Mbyte stack SEGV happens | ||
7424 | * not too long after 2^16 recursions anyway. | ||
7425 | */ | ||
7426 | if (G.var_nest_level > 0xff00) | ||
7427 | bb_error_msg_and_die("fatal recursion (depth %u)", G.var_nest_level); | ||
7428 | } | ||
7429 | |||
7430 | static void leave_var_nest_level(void) | ||
7431 | { | ||
7432 | struct variable *cur; | ||
7433 | struct variable **cur_pp; | ||
7434 | |||
7435 | cur_pp = &G.top_var; | ||
7436 | while ((cur = *cur_pp) != NULL) { | ||
7437 | if (cur->var_nest_level < G.var_nest_level) { | ||
7438 | cur_pp = &cur->next; | ||
7439 | continue; | ||
7440 | } | ||
7441 | /* Unexport */ | ||
7442 | if (cur->flg_export) { | ||
7443 | debug_printf_env("unexporting nested '%s'/%u\n", cur->varstr, cur->var_nest_level); | ||
7444 | bb_unsetenv(cur->varstr); | ||
7445 | } | ||
7446 | /* Remove from global list */ | ||
7447 | *cur_pp = cur->next; | ||
7448 | /* Free */ | ||
7449 | if (!cur->max_len) { | ||
7450 | debug_printf_env("freeing nested '%s'/%u\n", cur->varstr, cur->var_nest_level); | ||
7451 | free(cur->varstr); | ||
7452 | } | ||
7453 | free(cur); | ||
7454 | } | ||
7455 | |||
7456 | G.var_nest_level--; | ||
7457 | debug_printf_env("var_nest_level-- %u\n", G.var_nest_level); | ||
7458 | if (HUSH_DEBUG && (int)G.var_nest_level < 0) | ||
7459 | bb_error_msg_and_die("BUG: nesting underflow"); | ||
7460 | } | ||
7461 | |||
7462 | static int run_function(const struct function *funcp, char **argv) | 7507 | static int run_function(const struct function *funcp, char **argv) |
7463 | { | 7508 | { |
7464 | int rc; | 7509 | int rc; |
@@ -7648,6 +7693,7 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, | |||
7648 | G.shadowed_vars_pp = NULL; /* "don't save, free them instead" */ | 7693 | G.shadowed_vars_pp = NULL; /* "don't save, free them instead" */ |
7649 | #else | 7694 | #else |
7650 | G.shadowed_vars_pp = &nommu_save->old_vars; | 7695 | G.shadowed_vars_pp = &nommu_save->old_vars; |
7696 | G.var_nest_level++; | ||
7651 | #endif | 7697 | #endif |
7652 | set_vars_and_save_old(new_env); | 7698 | set_vars_and_save_old(new_env); |
7653 | G.shadowed_vars_pp = sv_shadowed; | 7699 | G.shadowed_vars_pp = sv_shadowed; |
@@ -8249,9 +8295,31 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
8249 | /* Testcase: set -- q w e; (IFS='' echo "$*"; IFS=''; echo "$*"); echo "$*" | 8295 | /* Testcase: set -- q w e; (IFS='' echo "$*"; IFS=''; echo "$*"); echo "$*" |
8250 | * Result should be 3 lines: q w e, qwe, q w e | 8296 | * Result should be 3 lines: q w e, qwe, q w e |
8251 | */ | 8297 | */ |
8298 | if (G.ifs_whitespace != G.ifs) | ||
8299 | free(G.ifs_whitespace); | ||
8252 | G.ifs = get_local_var_value("IFS"); | 8300 | G.ifs = get_local_var_value("IFS"); |
8253 | if (!G.ifs) | 8301 | if (G.ifs) { |
8302 | char *p; | ||
8303 | G.ifs_whitespace = (char*)G.ifs; | ||
8304 | p = skip_whitespace(G.ifs); | ||
8305 | if (*p) { | ||
8306 | /* Not all $IFS is whitespace */ | ||
8307 | char *d; | ||
8308 | int len = p - G.ifs; | ||
8309 | p = skip_non_whitespace(p); | ||
8310 | G.ifs_whitespace = xmalloc(len + strlen(p) + 1); /* can overestimate */ | ||
8311 | d = mempcpy(G.ifs_whitespace, G.ifs, len); | ||
8312 | while (*p) { | ||
8313 | if (isspace(*p)) | ||
8314 | *d++ = *p; | ||
8315 | p++; | ||
8316 | } | ||
8317 | *d = '\0'; | ||
8318 | } | ||
8319 | } else { | ||
8254 | G.ifs = defifs; | 8320 | G.ifs = defifs; |
8321 | G.ifs_whitespace = (char*)G.ifs; | ||
8322 | } | ||
8255 | 8323 | ||
8256 | IF_HUSH_JOB(pi->pgrp = -1;) | 8324 | IF_HUSH_JOB(pi->pgrp = -1;) |
8257 | pi->stopped_cmds = 0; | 8325 | pi->stopped_cmds = 0; |
@@ -8343,7 +8411,10 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
8343 | bb_putchar_stderr('+'); | 8411 | bb_putchar_stderr('+'); |
8344 | i = 0; | 8412 | i = 0; |
8345 | while (i < command->assignment_cnt) { | 8413 | while (i < command->assignment_cnt) { |
8346 | char *p = expand_string_to_string(argv[i], /*unbackslash:*/ 1); | 8414 | char *p = expand_string_to_string(argv[i], |
8415 | EXP_FLAG_ESC_GLOB_CHARS, | ||
8416 | /*unbackslash:*/ 1 | ||
8417 | ); | ||
8347 | if (G_x_mode) | 8418 | if (G_x_mode) |
8348 | fprintf(stderr, " %s", p); | 8419 | fprintf(stderr, " %s", p); |
8349 | debug_printf_env("set shell var:'%s'->'%s'\n", *argv, p); | 8420 | debug_printf_env("set shell var:'%s'->'%s'\n", *argv, p); |
@@ -8522,6 +8593,7 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
8522 | while (cmd_no < pi->num_cmds) { | 8593 | while (cmd_no < pi->num_cmds) { |
8523 | struct fd_pair pipefds; | 8594 | struct fd_pair pipefds; |
8524 | #if !BB_MMU | 8595 | #if !BB_MMU |
8596 | int sv_var_nest_level = G.var_nest_level; | ||
8525 | volatile nommu_save_t nommu_save; | 8597 | volatile nommu_save_t nommu_save; |
8526 | nommu_save.old_vars = NULL; | 8598 | nommu_save.old_vars = NULL; |
8527 | nommu_save.argv = NULL; | 8599 | nommu_save.argv = NULL; |
@@ -8615,6 +8687,8 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
8615 | /* Clean up after vforked child */ | 8687 | /* Clean up after vforked child */ |
8616 | free(nommu_save.argv); | 8688 | free(nommu_save.argv); |
8617 | free(nommu_save.argv_from_re_execing); | 8689 | free(nommu_save.argv_from_re_execing); |
8690 | G.var_nest_level = sv_var_nest_level; | ||
8691 | remove_nested_vars(); | ||
8618 | add_vars(nommu_save.old_vars); | 8692 | add_vars(nommu_save.old_vars); |
8619 | #endif | 8693 | #endif |
8620 | free(argv_expanded); | 8694 | free(argv_expanded); |
@@ -8835,7 +8909,8 @@ static int run_list(struct pipe *pi) | |||
8835 | #if ENABLE_HUSH_CASE | 8909 | #if ENABLE_HUSH_CASE |
8836 | if (rword == RES_CASE) { | 8910 | if (rword == RES_CASE) { |
8837 | debug_printf_exec("CASE cond_code:%d\n", cond_code); | 8911 | debug_printf_exec("CASE cond_code:%d\n", cond_code); |
8838 | case_word = expand_string_to_string(pi->cmds->argv[0], 1); | 8912 | case_word = expand_string_to_string(pi->cmds->argv[0], |
8913 | EXP_FLAG_ESC_GLOB_CHARS, /*unbackslash:*/ 1); | ||
8839 | debug_printf_exec("CASE word1:'%s'\n", case_word); | 8914 | debug_printf_exec("CASE word1:'%s'\n", case_word); |
8840 | //unbackslash(case_word); | 8915 | //unbackslash(case_word); |
8841 | //debug_printf_exec("CASE word2:'%s'\n", case_word); | 8916 | //debug_printf_exec("CASE word2:'%s'\n", case_word); |
@@ -8850,12 +8925,19 @@ static int run_list(struct pipe *pi) | |||
8850 | /* all prev words didn't match, does this one match? */ | 8925 | /* all prev words didn't match, does this one match? */ |
8851 | argv = pi->cmds->argv; | 8926 | argv = pi->cmds->argv; |
8852 | while (*argv) { | 8927 | while (*argv) { |
8853 | char *pattern = expand_string_to_string(*argv, /*unbackslash:*/ 0); | 8928 | char *pattern; |
8929 | debug_printf_exec("expand_string_to_string('%s')\n", *argv); | ||
8930 | pattern = expand_string_to_string(*argv, | ||
8931 | EXP_FLAG_ESC_GLOB_CHARS, | ||
8932 | /*unbackslash:*/ 0 | ||
8933 | ); | ||
8854 | /* TODO: which FNM_xxx flags to use? */ | 8934 | /* TODO: which FNM_xxx flags to use? */ |
8855 | cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0); | 8935 | cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0); |
8856 | debug_printf_exec("fnmatch(pattern:'%s',str:'%s'):%d\n", pattern, case_word, cond_code); | 8936 | debug_printf_exec("fnmatch(pattern:'%s',str:'%s'):%d\n", |
8937 | pattern, case_word, cond_code); | ||
8857 | free(pattern); | 8938 | free(pattern); |
8858 | if (cond_code == 0) { /* match! we will execute this branch */ | 8939 | if (cond_code == 0) { |
8940 | /* match! we will execute this branch */ | ||
8859 | free(case_word); | 8941 | free(case_word); |
8860 | case_word = NULL; /* make future "word)" stop */ | 8942 | case_word = NULL; /* make future "word)" stop */ |
8861 | break; | 8943 | break; |
@@ -9394,6 +9476,13 @@ int hush_main(int argc, char **argv) | |||
9394 | optarg++; | 9476 | optarg++; |
9395 | G.depth_of_loop = bb_strtou(optarg, &optarg, 16); | 9477 | G.depth_of_loop = bb_strtou(optarg, &optarg, 16); |
9396 | # endif | 9478 | # endif |
9479 | # if ENABLE_HUSH_FUNCTIONS | ||
9480 | /* nommu uses re-exec trick for "... | func | ...", | ||
9481 | * should allow "return". | ||
9482 | * This accidentally allows returns in subshells. | ||
9483 | */ | ||
9484 | G_flag_return_in_progress = -1; | ||
9485 | # endif | ||
9397 | break; | 9486 | break; |
9398 | } | 9487 | } |
9399 | case 'R': | 9488 | case 'R': |
diff --git a/shell/hush_test/hush-parsing/bkslash_eof1.right b/shell/hush_test/hush-parsing/bkslash_eof1.right new file mode 100644 index 000000000..6c6df0b0c --- /dev/null +++ b/shell/hush_test/hush-parsing/bkslash_eof1.right | |||
@@ -0,0 +1 @@ | |||
ok\ | |||
diff --git a/shell/hush_test/hush-parsing/bkslash_eof1.tests b/shell/hush_test/hush-parsing/bkslash_eof1.tests new file mode 100755 index 000000000..97629cb13 --- /dev/null +++ b/shell/hush_test/hush-parsing/bkslash_eof1.tests | |||
@@ -0,0 +1 @@ | |||
eval 'echo ok\' | |||
diff --git a/shell/hush_test/hush-parsing/bkslash_eof2.right b/shell/hush_test/hush-parsing/bkslash_eof2.right new file mode 100644 index 000000000..8be75727f --- /dev/null +++ b/shell/hush_test/hush-parsing/bkslash_eof2.right | |||
@@ -0,0 +1,2 @@ | |||
1 | hush: syntax error: unterminated " | ||
2 | One:1 | ||
diff --git a/shell/hush_test/hush-parsing/bkslash_eof2.tests b/shell/hush_test/hush-parsing/bkslash_eof2.tests new file mode 100755 index 000000000..da1f08db6 --- /dev/null +++ b/shell/hush_test/hush-parsing/bkslash_eof2.tests | |||
@@ -0,0 +1,4 @@ | |||
1 | printf 'echo "unterminated string\\' >test.tmp.sh | ||
2 | . ./test.tmp.sh | ||
3 | echo One:$? | ||
4 | rm -f test.tmp.sh | ||
diff --git a/shell/hush_test/hush-parsing/bkslash_newline3.right b/shell/hush_test/hush-parsing/bkslash_newline3.right new file mode 100644 index 000000000..e635074e5 --- /dev/null +++ b/shell/hush_test/hush-parsing/bkslash_newline3.right | |||
@@ -0,0 +1 @@ | |||
a:[a] | |||
diff --git a/shell/hush_test/hush-parsing/bkslash_newline3.tests b/shell/hush_test/hush-parsing/bkslash_newline3.tests new file mode 100755 index 000000000..2accd4395 --- /dev/null +++ b/shell/hush_test/hush-parsing/bkslash_newline3.tests | |||
@@ -0,0 +1,4 @@ | |||
1 | for s in \ | ||
2 | a; do | ||
3 | echo "a:[$s]" | ||
4 | done | ||
diff --git a/shell/hush_test/hush-quoting/case_glob1.right b/shell/hush_test/hush-quoting/case_glob1.right new file mode 100644 index 000000000..b4785957b --- /dev/null +++ b/shell/hush_test/hush-quoting/case_glob1.right | |||
@@ -0,0 +1 @@ | |||
s | |||
diff --git a/shell/hush_test/hush-quoting/case_glob1.tests b/shell/hush_test/hush-quoting/case_glob1.tests new file mode 100755 index 000000000..8dbbc0fb1 --- /dev/null +++ b/shell/hush_test/hush-quoting/case_glob1.tests | |||
@@ -0,0 +1,8 @@ | |||
1 | g='[3](a)(b)(c)' | ||
2 | s='[3](a)(b)(c)' | ||
3 | case $g in | ||
4 | "$s") echo s | ||
5 | ;; | ||
6 | *) echo "*" | ||
7 | ;; | ||
8 | esac | ||
diff --git a/shell/hush_test/hush-read/read_ifs2.right b/shell/hush_test/hush-read/read_ifs2.right new file mode 100644 index 000000000..797137dae --- /dev/null +++ b/shell/hush_test/hush-read/read_ifs2.right | |||
@@ -0,0 +1,9 @@ | |||
1 | |X|Y:Z:| | ||
2 | |X|Y:Z| | ||
3 | |X|Y| | ||
4 | |X|Y| | ||
5 | |X|| | ||
6 | |X|| | ||
7 | ||| | ||
8 | Whitespace should be trimmed too: | ||
9 | |X|Y| | ||
diff --git a/shell/hush_test/hush-read/read_ifs2.tests b/shell/hush_test/hush-read/read_ifs2.tests new file mode 100755 index 000000000..f01a68978 --- /dev/null +++ b/shell/hush_test/hush-read/read_ifs2.tests | |||
@@ -0,0 +1,9 @@ | |||
1 | echo "X:Y:Z:" | (IFS=": " read x y; echo "|$x|$y|") | ||
2 | echo "X:Y:Z" | (IFS=": " read x y; echo "|$x|$y|") | ||
3 | echo "X:Y:" | (IFS=": " read x y; echo "|$x|$y|") | ||
4 | echo "X:Y" | (IFS=": " read x y; echo "|$x|$y|") | ||
5 | echo "X:" | (IFS=": " read x y; echo "|$x|$y|") | ||
6 | echo "X" | (IFS=": " read x y; echo "|$x|$y|") | ||
7 | echo "" | (IFS=": " read x y; echo "|$x|$y|") | ||
8 | echo Whitespace should be trimmed too: | ||
9 | echo "X:Y : " | (IFS=": " read x y; echo "|$x|$y|") | ||
diff --git a/shell/hush_test/hush-vars/param_expand_alt2.right b/shell/hush_test/hush-vars/param_expand_alt2.right new file mode 100644 index 000000000..fef5889ca --- /dev/null +++ b/shell/hush_test/hush-vars/param_expand_alt2.right | |||
@@ -0,0 +1,4 @@ | |||
1 | Unquoted: H H | ||
2 | Quoted: H | ||
3 | H | ||
4 | Ok:0 | ||
diff --git a/shell/hush_test/hush-vars/param_expand_alt2.tests b/shell/hush_test/hush-vars/param_expand_alt2.tests new file mode 100755 index 000000000..d8abf4c3b --- /dev/null +++ b/shell/hush_test/hush-vars/param_expand_alt2.tests | |||
@@ -0,0 +1,7 @@ | |||
1 | echo Unquoted: H${$+ | ||
2 | }H | ||
3 | |||
4 | echo Quoted: "H${$+ | ||
5 | }H" | ||
6 | |||
7 | echo Ok:$? | ||
diff --git a/shell/hush_test/hush-vars/var_wordsplit_ifs4.right b/shell/hush_test/hush-vars/var_wordsplit_ifs4.right new file mode 100644 index 000000000..c27284c31 --- /dev/null +++ b/shell/hush_test/hush-vars/var_wordsplit_ifs4.right | |||
@@ -0,0 +1,5 @@ | |||
1 | |x| | ||
2 | Ok1:0 | ||
3 | |x| | ||
4 | || | ||
5 | Ok2:0 | ||
diff --git a/shell/hush_test/hush-vars/var_wordsplit_ifs4.tests b/shell/hush_test/hush-vars/var_wordsplit_ifs4.tests new file mode 100755 index 000000000..638bfbb28 --- /dev/null +++ b/shell/hush_test/hush-vars/var_wordsplit_ifs4.tests | |||
@@ -0,0 +1,4 @@ | |||
1 | IFS=": "; x=" "; set x $x; for v; do echo "|$v|"; done | ||
2 | echo Ok1:$? | ||
3 | IFS=": "; x=":"; set x $x; for v; do echo "|$v|"; done | ||
4 | echo Ok2:$? | ||
diff --git a/shell/hush_test/hush-z_slow/many_ifs.right b/shell/hush_test/hush-z_slow/many_ifs.right new file mode 100644 index 000000000..f3bdccc6c --- /dev/null +++ b/shell/hush_test/hush-z_slow/many_ifs.right | |||
@@ -0,0 +1 @@ | |||
# tests 6856 passed 6856 failed 0 | |||
diff --git a/shell/hush_test/hush-z_slow/many_ifs.tests b/shell/hush_test/hush-z_slow/many_ifs.tests new file mode 100755 index 000000000..1f5b1b3a6 --- /dev/null +++ b/shell/hush_test/hush-z_slow/many_ifs.tests | |||
@@ -0,0 +1,257 @@ | |||
1 | # Usage: $SHELL ifs.sh | ||
2 | # | ||
3 | # This script generates 6856 tests for the set(1) and read(1) | ||
4 | # builtins w.r.t. IFS whitespace and non-whitespace characters. | ||
5 | # Each failed test produces one line on the standard output that | ||
6 | # contains the test along with the expected and actual results. | ||
7 | # The last output line contains the test result counts. ordered>0 | ||
8 | # are the number of tests where IFS=": " produced different results | ||
9 | # than IFS=" :". If a test fails the same way for IFS=": " and | ||
10 | # IFS=" :" then the second output line is suppressed. | ||
11 | |||
12 | TESTS=6856 | ||
13 | |||
14 | ksh_read=0 | ||
15 | echo 1 | read ksh_read | ||
16 | ksh_arith=0 | ||
17 | eval '((ksh_arith+=1))' 2>/dev/null | ||
18 | |||
19 | failed=0 | ||
20 | ordered=0 | ||
21 | passed=0 | ||
22 | |||
23 | split() | ||
24 | { | ||
25 | i=$1 s=$2 r=$3 S='' R='' | ||
26 | for ifs in ': ' ' :' | ||
27 | do IFS=$ifs | ||
28 | set x $i | ||
29 | shift | ||
30 | IFS=' ' | ||
31 | g="[$#]" | ||
32 | while : | ||
33 | do case $# in | ||
34 | 0) break ;; | ||
35 | esac | ||
36 | g="$g($1)" | ||
37 | shift | ||
38 | done | ||
39 | case $g in | ||
40 | "$s") case $ksh_arith in | ||
41 | 1) ((passed+=1)) ;; | ||
42 | *) passed=`expr $passed + 1` ;; | ||
43 | esac | ||
44 | case $S in | ||
45 | '') S=$g | ||
46 | ;; | ||
47 | "$g") ;; | ||
48 | *) case $ksh_arith in | ||
49 | 1) ((ordered+=1)) ;; | ||
50 | *) ordered=`expr $ordered + 1` ;; | ||
51 | esac | ||
52 | ;; | ||
53 | esac | ||
54 | ;; | ||
55 | "$S") case $ksh_arith in | ||
56 | 1) ((failed+=1)) ;; | ||
57 | *) failed=`expr $failed + 1` ;; | ||
58 | esac | ||
59 | ;; | ||
60 | *) case $ksh_arith in | ||
61 | 1) ((failed+=1)) ;; | ||
62 | *) failed=`expr $failed + 1` ;; | ||
63 | esac | ||
64 | case $s in | ||
65 | "$S") ;; | ||
66 | ?0*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#]\" # expected \"$s\" got \"$g\"" ;; | ||
67 | ?1*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#](\$1)\" # expected \"$s\" got \"$g\"" ;; | ||
68 | ?2*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#](\$1)(\$2)\" # expected \"$s\" got \"$g\"" ;; | ||
69 | ?3*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#](\$1)(\$2)(\$3)\" # expected \"$s\" got \"$g\"" ;; | ||
70 | *) echo TEST ERROR i="'$i'" s="'$s'" ;; | ||
71 | esac | ||
72 | case $S in | ||
73 | '') S=$g | ||
74 | ;; | ||
75 | "$g") ;; | ||
76 | *) case $ksh_arith in | ||
77 | 1) ((ordered+=1)) ;; | ||
78 | *) ordered=`expr $ordered + 1` ;; | ||
79 | esac | ||
80 | ;; | ||
81 | esac | ||
82 | esac | ||
83 | case $ksh_read in | ||
84 | 1) echo "$i" | IFS=$ifs read x y; g="($x)($y)" ;; | ||
85 | *) g=`export ifs; echo "$i" | ( IFS=$ifs; read x y; echo "($x)($y)" )` ;; | ||
86 | esac | ||
87 | case $g in | ||
88 | "$r") case $ksh_arith in | ||
89 | 1) ((passed+=1)) ;; | ||
90 | *) passed=`expr $passed + 1` ;; | ||
91 | esac | ||
92 | case $R in | ||
93 | '') R=$g | ||
94 | ;; | ||
95 | "$g") ;; | ||
96 | *) case $ksh_arith in | ||
97 | 1) ((ordered+=1)) ;; | ||
98 | *) ordered=`expr $ordered + 1` ;; | ||
99 | esac | ||
100 | ;; | ||
101 | esac | ||
102 | ;; | ||
103 | "$R") case $ksh_arith in | ||
104 | 1) ((failed+=1)) ;; | ||
105 | *) failed=`expr $failed + 1` ;; | ||
106 | esac | ||
107 | ;; | ||
108 | *) case $ksh_arith in | ||
109 | 1) ((failed+=1)) ;; | ||
110 | *) failed=`expr $failed + 1` ;; | ||
111 | esac | ||
112 | case $r in | ||
113 | "$R") ;; | ||
114 | *) echo "echo \"$i\" | ( IFS=\"$ifs\" read x y; echo \"(\$x)(\$y)\" ) # expected \"$r\" got \"$g\"" ;; | ||
115 | esac | ||
116 | case $R in | ||
117 | '') R=$g | ||
118 | ;; | ||
119 | "$g") ;; | ||
120 | *) case $ksh_arith in | ||
121 | 1) ((ordered+=1)) ;; | ||
122 | *) ordered=`expr $ordered + 1` ;; | ||
123 | esac | ||
124 | ;; | ||
125 | esac | ||
126 | ;; | ||
127 | esac | ||
128 | done | ||
129 | } | ||
130 | |||
131 | for str in \ | ||
132 | '-' \ | ||
133 | 'a' \ | ||
134 | '- -' \ | ||
135 | '- a' \ | ||
136 | 'a -' \ | ||
137 | 'a b' \ | ||
138 | '- - -' \ | ||
139 | '- - a' \ | ||
140 | '- a -' \ | ||
141 | '- a b' \ | ||
142 | 'a - -' \ | ||
143 | 'a - b' \ | ||
144 | 'a b -' \ | ||
145 | 'a b c' \ | ||
146 | |||
147 | do | ||
148 | IFS=' ' | ||
149 | set x $str | ||
150 | |||
151 | shift | ||
152 | case $# in | ||
153 | 0) continue ;; | ||
154 | esac | ||
155 | |||
156 | f1=$1 | ||
157 | case $f1 in | ||
158 | '-') f1='' ;; | ||
159 | esac | ||
160 | |||
161 | shift | ||
162 | case $# in | ||
163 | 0) for d0 in '' ' ' | ||
164 | do | ||
165 | for d1 in '' ' ' ':' ' :' ': ' ' : ' | ||
166 | do | ||
167 | case $f1$d1 in | ||
168 | '') split "$d0$f1$d1" "[0]" "()()" ;; | ||
169 | ' ') ;; | ||
170 | *) split "$d0$f1$d1" "[1]($f1)" "($f1)()" ;; | ||
171 | esac | ||
172 | done | ||
173 | done | ||
174 | continue | ||
175 | ;; | ||
176 | esac | ||
177 | f2=$1 | ||
178 | case $f2 in | ||
179 | '-') f2='' ;; | ||
180 | esac | ||
181 | |||
182 | shift | ||
183 | case $# in | ||
184 | 0) for d0 in '' ' ' | ||
185 | do | ||
186 | for d1 in ' ' ':' ' :' ': ' ' : ' | ||
187 | do | ||
188 | case ' ' in | ||
189 | $f1$d1|$d1$f2) continue ;; | ||
190 | esac | ||
191 | for d2 in '' ' ' ':' ' :' ': ' ' : ' | ||
192 | do | ||
193 | case $f2$d2 in | ||
194 | '') split "$d0$f1$d1$f2$d2" "[1]($f1)" "($f1)()" ;; | ||
195 | ' ') ;; | ||
196 | *) split "$d0$f1$d1$f2$d2" "[2]($f1)($f2)" "($f1)($f2)" ;; | ||
197 | esac | ||
198 | done | ||
199 | done | ||
200 | done | ||
201 | continue | ||
202 | ;; | ||
203 | esac | ||
204 | f3=$1 | ||
205 | case $f3 in | ||
206 | '-') f3='' ;; | ||
207 | esac | ||
208 | |||
209 | shift | ||
210 | case $# in | ||
211 | 0) for d0 in '' ' ' | ||
212 | do | ||
213 | for d1 in ':' ' :' ': ' ' : ' | ||
214 | do | ||
215 | case ' ' in | ||
216 | $f1$d1|$d1$f2) continue ;; | ||
217 | esac | ||
218 | for d2 in ' ' ':' ' :' ': ' ' : ' | ||
219 | do | ||
220 | case $f2$d2 in | ||
221 | ' ') continue ;; | ||
222 | esac | ||
223 | case ' ' in | ||
224 | $f2$d2|$d2$f3) continue ;; | ||
225 | esac | ||
226 | for d3 in '' ' ' ':' ' :' ': ' ' : ' | ||
227 | do | ||
228 | case $f3$d3 in | ||
229 | '') split "$d0$f1$d1$f2$d2$f3$d3" "[2]($f1)($f2)" "($f1)($f2)" ;; | ||
230 | ' ') ;; | ||
231 | *) x=$f2$d2$f3$d3 | ||
232 | x=${x# } #was x=${x#' '} hush needs fixing for this to work | ||
233 | x=${x% } #was x=${x%' '} | ||
234 | split "$d0$f1$d1$f2$d2$f3$d3" "[3]($f1)($f2)($f3)" "($f1)($x)" | ||
235 | ;; | ||
236 | esac | ||
237 | done | ||
238 | done | ||
239 | done | ||
240 | done | ||
241 | continue | ||
242 | ;; | ||
243 | esac | ||
244 | done | ||
245 | case $ksh_arith in | ||
246 | 1) ((tests=passed+failed)) ;; | ||
247 | *) tests=`expr $passed + $failed` ;; | ||
248 | esac | ||
249 | case $ordered in | ||
250 | 0) ordered="" ;; | ||
251 | *) ordered=" ordered $ordered" ;; | ||
252 | esac | ||
253 | case $tests in | ||
254 | $TESTS) fatal="" ;; | ||
255 | *) fatal=" -- fundamental IFS error -- $TESTS tests expected" | ||
256 | esac | ||
257 | echo "# tests $tests passed $passed failed $failed$ordered$fatal" | ||
diff --git a/shell/shell_common.c b/shell/shell_common.c index 94c94a147..82102778c 100644 --- a/shell/shell_common.c +++ b/shell/shell_common.c | |||
@@ -313,9 +313,44 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
313 | 313 | ||
314 | if (argv[0]) { | 314 | if (argv[0]) { |
315 | /* Remove trailing space $IFS chars */ | 315 | /* Remove trailing space $IFS chars */ |
316 | while (--bufpos >= 0 && isspace(buffer[bufpos]) && strchr(ifs, buffer[bufpos]) != NULL) | 316 | while (--bufpos >= 0 |
317 | && isspace(buffer[bufpos]) | ||
318 | && strchr(ifs, buffer[bufpos]) != NULL | ||
319 | ) { | ||
317 | continue; | 320 | continue; |
321 | } | ||
318 | buffer[bufpos + 1] = '\0'; | 322 | buffer[bufpos + 1] = '\0'; |
323 | |||
324 | /* Last variable takes the entire remainder with delimiters | ||
325 | * (sans trailing whitespace $IFS), | ||
326 | * but ***only "if there are fewer vars than fields"(c)***! | ||
327 | * The "X:Y:" case below: there are two fields, | ||
328 | * and therefore last delimiter (:) is eaten: | ||
329 | * IFS=": " | ||
330 | * echo "X:Y:Z:" | (read x y; echo "|$x|$y|") # |X|Y:Z:| | ||
331 | * echo "X:Y:Z" | (read x y; echo "|$x|$y|") # |X|Y:Z| | ||
332 | * echo "X:Y:" | (read x y; echo "|$x|$y|") # |X|Y|, not |X|Y:| | ||
333 | * echo "X:Y : " | (read x y; echo "|$x|$y|") # |X|Y| | ||
334 | */ | ||
335 | if (bufpos >= 0 | ||
336 | && strchr(ifs, buffer[bufpos]) != NULL | ||
337 | ) { | ||
338 | /* There _is_ a non-whitespace IFS char */ | ||
339 | /* Skip whitespace IFS char before it */ | ||
340 | while (--bufpos >= 0 | ||
341 | && isspace(buffer[bufpos]) | ||
342 | && strchr(ifs, buffer[bufpos]) != NULL | ||
343 | ) { | ||
344 | continue; | ||
345 | } | ||
346 | /* Are there $IFS chars? */ | ||
347 | if (strcspn(buffer, ifs) >= ++bufpos) { | ||
348 | /* No: last var takes one field, not more */ | ||
349 | /* So, drop trailing IFS delims */ | ||
350 | buffer[bufpos] = '\0'; | ||
351 | } | ||
352 | } | ||
353 | |||
319 | /* Use the remainder as a value for the next variable */ | 354 | /* Use the remainder as a value for the next variable */ |
320 | setvar(*argv, buffer); | 355 | setvar(*argv, buffer); |
321 | /* Set the rest to "" */ | 356 | /* Set the rest to "" */ |
diff --git a/testsuite/awk.tests b/testsuite/awk.tests index ad0583afb..3933fefc9 100755 --- a/testsuite/awk.tests +++ b/testsuite/awk.tests | |||
@@ -339,5 +339,11 @@ testing "awk handles invalid for loop" \ | |||
339 | "awk '{ for() }' 2>&1" "awk: cmd. line:1: Unexpected token\n" "" "" | 339 | "awk '{ for() }' 2>&1" "awk: cmd. line:1: Unexpected token\n" "" "" |
340 | 340 | ||
341 | # testing "description" "command" "result" "infile" "stdin" | 341 | # testing "description" "command" "result" "infile" "stdin" |
342 | testing 'awk negative field access' \ | ||
343 | 'awk 2>&1 -- '\''{ $(-1) }'\' \ | ||
344 | "awk: cmd. line:1: Access to negative field\n" \ | ||
345 | '' \ | ||
346 | 'anything' | ||
347 | |||
342 | 348 | ||
343 | exit $FAILCOUNT | 349 | exit $FAILCOUNT |
diff --git a/testsuite/cat.tests b/testsuite/cat.tests new file mode 100755 index 000000000..404ebedeb --- /dev/null +++ b/testsuite/cat.tests | |||
@@ -0,0 +1,21 @@ | |||
1 | #!/bin/sh | ||
2 | |||
3 | # Copyright 2018 by Denys Vlasenko <vda.linux@googlemail.com> | ||
4 | # Licensed under GPLv2, see file LICENSE in this source tree. | ||
5 | |||
6 | . ./testing.sh | ||
7 | |||
8 | # testing "description" "command" "result" "infile" "stdin" | ||
9 | testing 'cat -e' \ | ||
10 | 'cat -e' \ | ||
11 | 'foo$\n' \ | ||
12 | '' \ | ||
13 | 'foo\n' | ||
14 | |||
15 | testing 'cat -v' \ | ||
16 | 'cat -v' \ | ||
17 | 'foo\n' \ | ||
18 | '' \ | ||
19 | 'foo\n' | ||
20 | |||
21 | exit $FAILCOUNT | ||
diff --git a/testsuite/grep.tests b/testsuite/grep.tests index d0b0d2767..e57889790 100755 --- a/testsuite/grep.tests +++ b/testsuite/grep.tests | |||
@@ -15,7 +15,7 @@ | |||
15 | 15 | ||
16 | testing "grep (exit with error)" "grep nonexistent 2> /dev/null ; echo \$?" \ | 16 | testing "grep (exit with error)" "grep nonexistent 2> /dev/null ; echo \$?" \ |
17 | "1\n" "" "" | 17 | "1\n" "" "" |
18 | testing "grep (exit success)" "grep grep $0 > /dev/null 2>&1 ; echo \$?" "0\n" \ | 18 | testing "grep (exit success)" "grep grep '$0' > /dev/null 2>&1 ; echo \$?" "0\n" \ |
19 | "" "" | 19 | "" "" |
20 | # Test various data sources and destinations | 20 | # Test various data sources and destinations |
21 | 21 | ||
diff --git a/testsuite/printf.tests b/testsuite/printf.tests index 9a3c87473..963ded94b 100755 --- a/testsuite/printf.tests +++ b/testsuite/printf.tests | |||
@@ -20,7 +20,7 @@ testing "printf produces no further output 2" \ | |||
20 | "" "" | 20 | "" "" |
21 | 21 | ||
22 | testing "printf repeatedly uses pattern for each argv" \ | 22 | testing "printf repeatedly uses pattern for each argv" \ |
23 | "${bb}printf '%s\n' foo \$HOME" \ | 23 | "${bb}printf '%s\n' foo '$HOME'" \ |
24 | "foo\n$HOME\n" \ | 24 | "foo\n$HOME\n" \ |
25 | "" "" | 25 | "" "" |
26 | 26 | ||
diff --git a/testsuite/pwd/pwd-prints-working-directory b/testsuite/pwd/pwd-prints-working-directory index 971adb5a6..fc7fea7c9 100644 --- a/testsuite/pwd/pwd-prints-working-directory +++ b/testsuite/pwd/pwd-prints-working-directory | |||
@@ -1,4 +1,4 @@ | |||
1 | # shell's $PWD may leave symlinks unresolved. | 1 | # shell's $PWD may leave symlinks unresolved. |
2 | # "pwd" may be a built-in and have the same problem. | 2 | # "pwd" may be a built-in and have the same problem. |
3 | # External pwd _can't_ have that problem (current dir on Unix is physical). | 3 | # External pwd _can't_ have that problem (current dir on Unix is physical). |
4 | test $(`which pwd`) = $(busybox pwd) | 4 | test "$(`which pwd`)" = "$(busybox pwd)" |
diff --git a/testsuite/sum.tests b/testsuite/sum.tests index b9f4cbfa8..e6379349f 100755 --- a/testsuite/sum.tests +++ b/testsuite/sum.tests | |||
@@ -13,12 +13,12 @@ | |||
13 | # test can create a file "actual" instead of writing to stdout | 13 | # test can create a file "actual" instead of writing to stdout |
14 | 14 | ||
15 | testing "sum -r file doesn't print file's name" \ | 15 | testing "sum -r file doesn't print file's name" \ |
16 | "sum -r $0 | grep -c $0 && echo wrongly_printed_filename || echo yes" \ | 16 | "sum -r '$0' | grep -c '$0' && echo wrongly_printed_filename || echo yes" \ |
17 | "0\nyes\n" "" "" | 17 | "0\nyes\n" "" "" |
18 | testing "sum -r file file does print both names" \ | 18 | testing "sum -r file file does print both names" \ |
19 | "sum -r $0 $0 | grep -c $0 && echo yes || echo wrongly_omitted_filename" \ | 19 | "sum -r '$0' '$0' | grep -c '$0' && echo yes || echo wrongly_omitted_filename" \ |
20 | "2\nyes\n" "" "" | 20 | "2\nyes\n" "" "" |
21 | testing "sum -s file does print file's name" \ | 21 | testing "sum -s file does print file's name" \ |
22 | "sum -s $0 | grep -c $0 && echo yes || echo wrongly_omitted_filename" \ | 22 | "sum -s '$0' | grep -c '$0' && echo yes || echo wrongly_omitted_filename" \ |
23 | "1\nyes\n" "" "" | 23 | "1\nyes\n" "" "" |
24 | exit $FAILCOUNT | 24 | exit $FAILCOUNT |
diff --git a/testsuite/unzip.tests b/testsuite/unzip.tests index 2e4becdb8..6bcb6b3a2 100755 --- a/testsuite/unzip.tests +++ b/testsuite/unzip.tests | |||
@@ -14,7 +14,7 @@ | |||
14 | # Create a scratch directory | 14 | # Create a scratch directory |
15 | 15 | ||
16 | mkdir temp | 16 | mkdir temp |
17 | cd temp | 17 | cd temp || exit 90 |
18 | 18 | ||
19 | # Create test file to work with. | 19 | # Create test file to work with. |
20 | 20 | ||
@@ -52,7 +52,18 @@ NzITNFBLBQUKAC4JAA04Cw0EOhZQSwUGAQAABAIAAgCZAAAAeQAAAAIALhM= | |||
52 | " | 52 | " |
53 | SKIP= | 53 | SKIP= |
54 | 54 | ||
55 | rm * | 55 | rm -f * |
56 | |||
57 | optional CONFIG_FEATURE_UNZIP_LZMA | ||
58 | testing "unzip (archive with corrupted lzma)" "unzip -p ../unzip_bad_lzma_1.zip 2>&1; echo \$?" \ | ||
59 | "unzip: removing leading '/' from member names | ||
60 | unzip: inflate error | ||
61 | 1 | ||
62 | " \ | ||
63 | "" "" | ||
64 | SKIP= | ||
65 | |||
66 | rm -f * | ||
56 | 67 | ||
57 | # Clean up scratch directory. | 68 | # Clean up scratch directory. |
58 | 69 | ||
diff --git a/testsuite/unzip_bad_lzma_1.zip b/testsuite/unzip_bad_lzma_1.zip new file mode 100644 index 000000000..1335c96d7 --- /dev/null +++ b/testsuite/unzip_bad_lzma_1.zip | |||
Binary files differ | |||
diff --git a/testsuite/xargs/xargs-works b/testsuite/xargs/xargs-works index c95869e89..a4bba7630 100644 --- a/testsuite/xargs/xargs-works +++ b/testsuite/xargs/xargs-works | |||
@@ -1,4 +1,6 @@ | |||
1 | # FEATURE: CONFIG_FEATURE_XARGS_SUPPORT_ZERO_TERM | ||
2 | |||
1 | [ -n "$d" ] || d=.. | 3 | [ -n "$d" ] || d=.. |
2 | find "$d" -name \*works -type f | xargs md5sum > logfile.gnu | 4 | find "$d" -name \*works -type f -print0 | xargs -0 md5sum > logfile.gnu |
3 | find "$d" -name \*works -type f | busybox xargs md5sum > logfile.bb | 5 | find "$d" -name \*works -type f -print0 | busybox xargs -0 md5sum > logfile.bb |
4 | diff -u logfile.gnu logfile.bb | 6 | diff -u logfile.gnu logfile.bb |
diff --git a/util-linux/rdate.c b/util-linux/rdate.c index f27294e25..5ec795208 100644 --- a/util-linux/rdate.c +++ b/util-linux/rdate.c | |||
@@ -45,7 +45,7 @@ static time_t askremotedate(const char *host) | |||
45 | alarm(10); | 45 | alarm(10); |
46 | signal(SIGALRM, socket_timeout); | 46 | signal(SIGALRM, socket_timeout); |
47 | 47 | ||
48 | fd = create_and_connect_stream_or_die(host, bb_lookup_port("time", "tcp", 37)); | 48 | fd = create_and_connect_stream_or_die(host, bb_lookup_std_port("time", "tcp", 37)); |
49 | 49 | ||
50 | if (safe_read(fd, &nett, 4) != 4) /* read time from server */ | 50 | if (safe_read(fd, &nett, 4) != 4) /* read time from server */ |
51 | bb_error_msg_and_die("%s: %s", host, "short read"); | 51 | bb_error_msg_and_die("%s: %s", host, "short read"); |