aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRon Yorston <rmy@pobox.com>2018-05-13 08:15:58 +0100
committerRon Yorston <rmy@pobox.com>2018-05-13 08:15:58 +0100
commit3ce461fdf3b7adfd44ea058fa0c5ca6d91a5bc5d (patch)
treea527d0db15f34a137fc11df5538c7f2f7c6d72de
parent6f7d1af269eed4b42daeb9c6dfd2ba62f9cd47e4 (diff)
parentd80eecb86812c1fbda652f9b995060c26ba0b155 (diff)
downloadbusybox-w32-3ce461fdf3b7adfd44ea058fa0c5ca6d91a5bc5d.tar.gz
busybox-w32-3ce461fdf3b7adfd44ea058fa0c5ca6d91a5bc5d.tar.bz2
busybox-w32-3ce461fdf3b7adfd44ea058fa0c5ca6d91a5bc5d.zip
Merge branch 'busybox' into merge
-rw-r--r--Makefile2
-rw-r--r--Makefile.custom3
-rw-r--r--Makefile.flags4
-rw-r--r--applets/applet_tables.c16
-rwxr-xr-xapplets/install.sh6
-rw-r--r--archival/libarchive/decompress_unlzma.c15
-rw-r--r--archival/libarchive/get_header_ar.c22
-rw-r--r--console-tools/setlogcons.c10
-rw-r--r--coreutils/cat.c6
-rw-r--r--coreutils/test.c4
-rw-r--r--debianutils/start_stop_daemon.c16
-rw-r--r--editors/awk.c3
-rw-r--r--editors/patch.c129
-rw-r--r--examples/shutdown-1.0/README30
-rwxr-xr-xexamples/shutdown-1.0/script/do_shutdown54
-rw-r--r--examples/shutdown-1.0/script/hardshutdown.c168
-rwxr-xr-xexamples/shutdown-1.0/script/hardshutdown.make.sh8
-rwxr-xr-xexamples/shutdown-1.0/script/shutdown64
-rwxr-xr-xexamples/shutdown-1.0/script/stop_storage81
-rwxr-xr-xexamples/shutdown-1.0/script/stop_tasks70
-rw-r--r--examples/udhcp/udhcpd.conf3
-rwxr-xr-xexamples/var_service/tftpd/run2
-rw-r--r--findutils/find.c2
-rw-r--r--include/bb_archive.h4
-rw-r--r--include/libbb.h13
-rw-r--r--libbb/Config.src12
-rw-r--r--libbb/xfuncs.c13
-rw-r--r--miscutils/less.c23
-rw-r--r--networking/ifplugd.c2
-rw-r--r--networking/isrv_identd.c2
-rw-r--r--networking/nslookup.c755
-rw-r--r--networking/telnet.c3
-rw-r--r--networking/udhcp/common.c71
-rw-r--r--networking/udhcp/d6_dhcpc.c11
-rw-r--r--networking/udhcp/dhcpc.c11
-rw-r--r--networking/wget.c22
-rw-r--r--shell/ash.c92
-rw-r--r--shell/ash_test/ash-parsing/bkslash_eof1.right1
-rwxr-xr-xshell/ash_test/ash-parsing/bkslash_eof1.tests1
-rw-r--r--shell/ash_test/ash-parsing/bkslash_newline3.right1
-rwxr-xr-xshell/ash_test/ash-parsing/bkslash_newline3.tests4
-rw-r--r--shell/ash_test/ash-quoting/case_glob1.right1
-rwxr-xr-xshell/ash_test/ash-quoting/case_glob1.tests8
-rw-r--r--shell/ash_test/ash-redir/redir_exec1.right2
-rw-r--r--shell/ash_test/ash-vars/param_expand_alt2.right4
-rwxr-xr-xshell/ash_test/ash-vars/param_expand_alt2.tests7
-rw-r--r--shell/ash_test/ash-vars/var_wordsplit_ifs4.right5
-rwxr-xr-xshell/ash_test/ash-vars/var_wordsplit_ifs4.tests4
-rw-r--r--shell/ash_test/ash-z_slow/many_ifs.right1
-rwxr-xr-xshell/ash_test/ash-z_slow/many_ifs.tests257
-rw-r--r--shell/hush.c617
-rw-r--r--shell/hush_test/hush-parsing/bkslash_eof1.right1
-rwxr-xr-xshell/hush_test/hush-parsing/bkslash_eof1.tests1
-rw-r--r--shell/hush_test/hush-parsing/bkslash_eof2.right2
-rwxr-xr-xshell/hush_test/hush-parsing/bkslash_eof2.tests4
-rw-r--r--shell/hush_test/hush-parsing/bkslash_newline3.right1
-rwxr-xr-xshell/hush_test/hush-parsing/bkslash_newline3.tests4
-rw-r--r--shell/hush_test/hush-quoting/case_glob1.right1
-rwxr-xr-xshell/hush_test/hush-quoting/case_glob1.tests8
-rw-r--r--shell/hush_test/hush-read/read_ifs2.right9
-rwxr-xr-xshell/hush_test/hush-read/read_ifs2.tests9
-rw-r--r--shell/hush_test/hush-vars/param_expand_alt2.right4
-rwxr-xr-xshell/hush_test/hush-vars/param_expand_alt2.tests7
-rw-r--r--shell/hush_test/hush-vars/var_wordsplit_ifs4.right5
-rwxr-xr-xshell/hush_test/hush-vars/var_wordsplit_ifs4.tests4
-rw-r--r--shell/hush_test/hush-z_slow/many_ifs.right1
-rwxr-xr-xshell/hush_test/hush-z_slow/many_ifs.tests257
-rw-r--r--shell/shell_common.c37
-rwxr-xr-xtestsuite/awk.tests6
-rwxr-xr-xtestsuite/cat.tests21
-rwxr-xr-xtestsuite/grep.tests2
-rwxr-xr-xtestsuite/printf.tests2
-rw-r--r--testsuite/pwd/pwd-prints-working-directory2
-rwxr-xr-xtestsuite/sum.tests6
-rwxr-xr-xtestsuite/unzip.tests15
-rw-r--r--testsuite/unzip_bad_lzma_1.zipbin0 -> 229 bytes
-rw-r--r--testsuite/xargs/xargs-works6
-rw-r--r--util-linux/rdate.c2
78 files changed, 2613 insertions, 469 deletions
diff --git a/Makefile b/Makefile
index 44807cd9d..dfab2e914 100644
--- a/Makefile
+++ b/Makefile
@@ -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
372PHONY += applets_dir 372PHONY += applets_dir
373applets_dir: scripts_basic gen_build_files 373applets_dir: scripts_basic gen_build_files include/config/MARKER
374 $(Q)$(MAKE) $(build)=applets 374 $(Q)$(MAKE) $(build)=applets
375 375
376applets/%: applets_dir ; 376applets/%: 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
14ifeq ($(CONFIG_INSTALL_APPLET_DONT),y)
15INSTALL_OPTS:= --none
16endif
14ifeq ($(CONFIG_INSTALL_APPLET_SYMLINKS),y) 17ifeq ($(CONFIG_INSTALL_APPLET_SYMLINKS),y)
15INSTALL_OPTS:= --symlinks 18INSTALL_OPTS:= --symlinks
16endif 19endif
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)
162LDLIBS += $(if $(SELINUX_LIBS),$(SELINUX_LIBS:-l%=%),$(SELINUX_PC_MODULES:lib%=%)) 162LDLIBS += $(if $(SELINUX_LIBS),$(SELINUX_LIBS:-l%=%),$(SELINUX_PC_MODULES:lib%=%))
163endif 163endif
164 164
165ifeq ($(CONFIG_FEATURE_NSLOOKUP_BIG),y)
166LDLIBS += resolv
167endif
168
165ifeq ($(CONFIG_EFENCE),y) 169ifeq ($(CONFIG_EFENCE),y)
166LDLIBS += efence 170LDLIBS += efence
167endif 171endif
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
6prefix=$1 6prefix=$1
7if [ -z "$prefix" ]; then 7if [ -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
10fi 12fi
11shift # Keep only remaining options 13shift # 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 @@
41int setlogcons_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 41int setlogcons_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
42int setlogcons_main(int argc UNUSED_PARAM, char **argv) 42int 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
194static int pid_is_exec(pid_t pid) 196static 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
218static int pid_is_name(pid_t pid) 221static 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";
598static const char EMSG_POSSIBLE_ERROR[] ALIGN1 = "Possible syntax error"; 598static const char EMSG_POSSIBLE_ERROR[] ALIGN1 = "Possible syntax error";
599static const char EMSG_UNDEF_FUNC[] ALIGN1 = "Call to undefined function"; 599static const char EMSG_UNDEF_FUNC[] ALIGN1 = "Call to undefined function";
600static const char EMSG_NO_MATH[] ALIGN1 = "Math support is not compiled in"; 600static const char EMSG_NO_MATH[] ALIGN1 = "Math support is not compiled in";
601static const char EMSG_NEGATIVE_FIELD[] ALIGN1 = "Access to negative field";
601 602
602static void zero_out_var(var *vp) 603static 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
145static void do_line(void *data) 137static 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#
11ln -s /app/shutdown-1.0/script/shutdown /sbin/halt
12ln -s /app/shutdown-1.0/script/shutdown /sbin/reboot
13ln -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#
30cd 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
4resetgracetime=60
5
6logfile="/var/log/reboot/`date '+%Y%m%d%H%M%S'`.log"
7mkdir -p /var/log/reboot
8
9PATH=/sbin:/bin
10
11say() {
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 ;)
18if test "$1" = "-r"; then
19 ./hardshutdown -r "$resetgracetime" &
20fi
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
24exec >/dev/console 2>&1
25
26if test "$1" = "-r"; then
27 say "* `date '+%H:%M:%S'` Scheduled hard reboot in $resetgracetime seconds"
28fi
29
30say "* `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.
36exec >/dev/null 2>&1
37chvt 1 & sleep 1
38exec >/dev/console 2>&1
39
40command -v ctrlaltdel >/dev/null && {
41 say "* `date '+%H:%M:%S'` Setting Ctrl-Alt-Del to 'hard'"
42 ctrlaltdel hard
43}
44
45say "* `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
50test "$*" && ./hardshutdown "$@"
51
52# Just sleep endlessly...
53say "* `date '+%H:%M:%S'` You may now power off or press Ctrl-Alt-Del to reboot"
54while 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
47extern int reboot(int, int, int);
48# define REBOOT(cmd) reboot(LINUX_REBOOT_MAGIC1,LINUX_REBOOT_MAGIC2,(cmd))
49#endif
50
51static 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 */
65extern int reboot(int, int, int);
66#endif
67
68static int my_reboot(int cmd)
69{
70 return reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, cmd);
71}
72
73#endif
74
75
76static void do_reboot(void)
77{
78 my_reboot(LINUX_REBOOT_CMD_RESTART);
79}
80static void do_poweroff(void)
81{
82 my_reboot(LINUX_REBOOT_CMD_POWER_OFF);
83}
84static void do_halt(void)
85{
86 my_reboot(LINUX_REBOOT_CMD_HALT);
87}
88
89static 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
98enum action_t {
99 SHUTDOWN, // do nothing
100 HALT,
101 POWEROFF,
102 REBOOT
103};
104
105int 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
3gcc -Wall -Os -o hardshutdown hardshutdown.c
4strip 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
3PATH=/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).
8cd /app/shutdown-1.0/script || exit 1
9test -x ./do_shutdown || exit 1
10test -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)
16force=""
17test x"$1" = x"-f" && {
18 force="-f"
19 shift
20}
21test ! "$*" && test x"${0##*/}" = x"halt" && exec "$0" $force -h
22test ! "$*" && test x"${0##*/}" = x"reboot" && exec "$0" $force -r
23test ! "$*" && test x"${0##*/}" = x"poweroff" && exec "$0" $force -p
24# We have something else than allowed parameters?
25test 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?
31test "$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):
40test -x /bin/killall5 -o -x /sbin/killall5 || exit 1
41test -x /bin/ps -o -x /sbin/ps || exit 1
42test -x /bin/date -o -x /sbin/date || exit 1
43test -x /bin/xargs -o -x /sbin/xargs || exit 1
44test -x /bin/wc -o -x /sbin/wc || exit 1
45test -x /bin/cat -o -x /sbin/cat || exit 1
46test -x /bin/sort -o -x /sbin/sort || exit 1
47
48i="`ulimit -n`"
49echo -n "Closing file descriptors $i-3... "
50while test "$i" -ge 3; do
51 eval "exec $i>&-"
52 i=$((i-1))
53done
54
55echo "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.
62setsid env - PATH="$PATH" ./do_shutdown "$@" </dev/null >/dev/null 2>&1 &
63
64while 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
6umountcnt=2
7writeout=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
11PATH=/sbin:/bin
12
13say() {
14 printf "\r%s\n\r" "$*"
15}
16
17showps() {
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
29say "<*> `date '+%Y-%m-%d %H:%M:%S'` Executing '$0 $*'"
30
31showps
32
33i="$umountcnt"
34while 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))
53done
54
55say "* `date '+%H:%M:%S'` Filesystem status (/proc/mounts)"
56cat /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
81sleep "$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
7killcnt=30
8
9PATH=/sbin:/usr/sbin:/bin:/usr/bin
10
11echo "<*> `date '+%Y-%m-%d %H:%M:%S'` Executing '$0 $*'"
12
13showps() {
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.
31sync &
32
33# Send SIGTERMs. If list of processes changes, proceed slower.
34# If it has stabilised (all who wanted to, exited), proceed faster.
35showps
36i="$killcnt"
37while 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))
51done
52
53echo "* `date '+%H:%M:%S'` Turning off swap"
54swapoff -a
55cat /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 }
61done
62
63echo "* /proc/swaps:"
64cat /proc/swaps
65echo "* /proc/mounts:"
66cat /proc/mounts
67echo "* ps -A e:"
68ps -A e
69echo "* top -bn1:"
70top -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
70option lease 864000 # default: 10 days 70option lease 864000 # default: 10 days
71option msstaticroutes 10.0.0.0/8 10.127.0.1 # single static route 71option msstaticroutes 10.0.0.0/8 10.127.0.1 # single static route
72option staticroutes 10.0.0.0/8 10.127.0.1, 10.11.12.0/24 10.11.12.1 72option 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:
74option 0x08 01020304 # option 8: "cookie server IP addr: 1.2.3.4" 74option 0x08 01020304 # option 8: "cookie server IP addr: 1.2.3.4"
75option 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
7user=root # for bind to port 69 7user=root # for bind to port 69
8 8
9exec \ 9exec \
10env - \ 10env - PATH="$PATH" \
11softlimit \ 11softlimit \
12setuidgid "$user" \ 12setuidgid "$user" \
13udpsvd -v -c 10 -l localhost \ 13udpsvd -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;
681int bb_getsockname(int sockfd, void *addr, socklen_t addrlen) FAST_FUNC; 681int 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 */
683unsigned bb_lookup_port(const char *port, const char *protocol, unsigned default_port) FAST_FUNC; 683unsigned 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
684typedef struct len_and_sockaddr { 689typedef 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
1645int get_terminal_width(int fd) FAST_FUNC; 1650int get_terminal_width(int fd) FAST_FUNC;
1646 1651
1647int tcsetattr_stdin_TCSANOW(const struct termios *tp) FAST_FUNC; 1652int 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)
1651int get_termios_and_make_raw(int fd, struct termios *newterm, struct termios *oldterm, int flags) FAST_FUNC; 1658int get_termios_and_make_raw(int fd, struct termios *newterm, struct termios *oldterm, int flags) FAST_FUNC;
1652int set_termios_to_raw(int fd, struct termios *oldterm, int flags) FAST_FUNC; 1659int 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
79config 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
79config FEATURE_EDITING 91config 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
257struct ns {
258 const char *name;
259 len_and_sockaddr *lsa;
260 int failures;
261 int replies;
262};
263
264struct 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
273static 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
290static 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
310static const char v4_mapped[12] = { 0,0,0,0, 0,0,0,0, 0,0,0xff,0xff };
311#endif
312
313struct 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
330enum {
331 OPT_debug = (1 << 0),
332};
333
334static 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 */
491static 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
647static 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
664static 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
690static 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
712static 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
744int nslookup_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
745int 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 */
382static char *allocate_tempopt_if_needed( 382static 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 */
399static 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(
463int FAST_FUNC udhcp_str2optset(const char *const_str, void *arg, const struct dhcp_optflag *optflags, const char *option_strings) 453int 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;
515case_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 */
7960typedef struct exp_t {
7961 char *dir;
7962 unsigned dir_max;
7963} exp_t;
7947static void 7964static void
7948expmeta(char *expdir, char *enddir, char *name) 7965expmeta(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
8059static struct strlist * 8089static 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 @@
1for s in \
2a; do
3 echo "a:[$s]"
4done
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 @@
1g='[3](a)(b)(c)'
2s='[3](a)(b)(c)'
3case $g in
4"$s") echo s
5 ;;
6*) echo "*"
7 ;;
8esac
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 @@
1redir_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
2First 2First
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 @@
1Unquoted: H H
2Quoted: H
3H
4Ok: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 @@
1echo Unquoted: H${$+
2}H
3
4echo Quoted: "H${$+
5}H"
6
7echo 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|
2Ok1:0
3|x|
4||
5Ok2: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 @@
1IFS=": "; x=" "; set x $x; for v; do echo "|$v|"; done
2echo Ok1:$?
3IFS=": "; x=":"; set x $x; for v; do echo "|$v|"; done
4echo 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
12TESTS=6856
13
14ksh_read=0
15echo 1 | read ksh_read
16ksh_arith=0
17eval '((ksh_arith+=1))' 2>/dev/null
18
19failed=0
20ordered=0
21passed=0
22
23split()
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
131for 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
147do
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
244done
245case $ksh_arith in
2461) ((tests=passed+failed)) ;;
247*) tests=`expr $passed + $failed` ;;
248esac
249case $ordered in
2500) ordered="" ;;
251*) ordered=" ordered $ordered" ;;
252esac
253case $tests in
254$TESTS) fatal="" ;;
255*) fatal=" -- fundamental IFS error -- $TESTS tests expected"
256esac
257echo "# 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;
527enum { 538enum {
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};
534enum {
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};
726enum {
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)
3670static void initialize_context(struct parse_context *ctx) 3696static 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 */
3754static const struct reserved_combo* reserved_word(o_string *word, struct parse_context *ctx) 3782static 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 */
3852static int done_word(o_string *word, struct parse_context *ctx) 3880static 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 4340static int parse_group(struct parse_context *ctx,
4313#define parse_group(dest, ctx, input, ch) \
4314 parse_group(ctx, input, ch)
4315#endif
4316static 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
5596static char *expand_string_to_string(const char *str, int do_unbackslash); 5617static char *expand_string_to_string(const char *str, int EXP_flags, int do_unbackslash);
5597#if ENABLE_HUSH_TICK 5618#if ENABLE_HUSH_TICK
5598static int process_command_subs(o_string *dest, const char *s); 5619static 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 */
6375static char *expand_string_to_string(const char *str, int do_unbackslash) 6410static 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
7401static 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
7428static 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
7443static 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
7415static 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
7430static 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
7462static int run_function(const struct function *funcp, char **argv) 7507static 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 @@
1hush: syntax error: unterminated "
2One: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 @@
1printf 'echo "unterminated string\\' >test.tmp.sh
2. ./test.tmp.sh
3echo One:$?
4rm -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 @@
1for s in \
2a; do
3 echo "a:[$s]"
4done
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 @@
1g='[3](a)(b)(c)'
2s='[3](a)(b)(c)'
3case $g in
4"$s") echo s
5 ;;
6*) echo "*"
7 ;;
8esac
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|||
8Whitespace 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 @@
1echo "X:Y:Z:" | (IFS=": " read x y; echo "|$x|$y|")
2echo "X:Y:Z" | (IFS=": " read x y; echo "|$x|$y|")
3echo "X:Y:" | (IFS=": " read x y; echo "|$x|$y|")
4echo "X:Y" | (IFS=": " read x y; echo "|$x|$y|")
5echo "X:" | (IFS=": " read x y; echo "|$x|$y|")
6echo "X" | (IFS=": " read x y; echo "|$x|$y|")
7echo "" | (IFS=": " read x y; echo "|$x|$y|")
8echo Whitespace should be trimmed too:
9echo "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 @@
1Unquoted: H H
2Quoted: H
3H
4Ok: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 @@
1echo Unquoted: H${$+
2}H
3
4echo Quoted: "H${$+
5}H"
6
7echo 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|
2Ok1:0
3|x|
4||
5Ok2: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 @@
1IFS=": "; x=" "; set x $x; for v; do echo "|$v|"; done
2echo Ok1:$?
3IFS=": "; x=":"; set x $x; for v; do echo "|$v|"; done
4echo 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
12TESTS=6856
13
14ksh_read=0
15echo 1 | read ksh_read
16ksh_arith=0
17eval '((ksh_arith+=1))' 2>/dev/null
18
19failed=0
20ordered=0
21passed=0
22
23split()
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
131for 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
147do
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
244done
245case $ksh_arith in
2461) ((tests=passed+failed)) ;;
247*) tests=`expr $passed + $failed` ;;
248esac
249case $ordered in
2500) ordered="" ;;
251*) ordered=" ordered $ordered" ;;
252esac
253case $tests in
254$TESTS) fatal="" ;;
255*) fatal=" -- fundamental IFS error -- $TESTS tests expected"
256esac
257echo "# 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"
342testing '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
343exit $FAILCOUNT 349exit $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"
9testing 'cat -e' \
10 'cat -e' \
11 'foo$\n' \
12 '' \
13 'foo\n'
14
15testing 'cat -v' \
16 'cat -v' \
17 'foo\n' \
18 '' \
19 'foo\n'
20
21exit $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
16testing "grep (exit with error)" "grep nonexistent 2> /dev/null ; echo \$?" \ 16testing "grep (exit with error)" "grep nonexistent 2> /dev/null ; echo \$?" \
17 "1\n" "" "" 17 "1\n" "" ""
18testing "grep (exit success)" "grep grep $0 > /dev/null 2>&1 ; echo \$?" "0\n" \ 18testing "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
22testing "printf repeatedly uses pattern for each argv" \ 22testing "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).
4test $(`which pwd`) = $(busybox pwd) 4test "$(`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
15testing "sum -r file doesn't print file's name" \ 15testing "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" "" ""
18testing "sum -r file file does print both names" \ 18testing "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" "" ""
21testing "sum -s file does print file's name" \ 21testing "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" "" ""
24exit $FAILCOUNT 24exit $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
16mkdir temp 16mkdir temp
17cd temp 17cd 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"
53SKIP= 53SKIP=
54 54
55rm * 55rm -f *
56
57optional CONFIG_FEATURE_UNZIP_LZMA
58testing "unzip (archive with corrupted lzma)" "unzip -p ../unzip_bad_lzma_1.zip 2>&1; echo \$?" \
59"unzip: removing leading '/' from member names
60unzip: inflate error
611
62" \
63"" ""
64SKIP=
65
66rm -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=..
2find "$d" -name \*works -type f | xargs md5sum > logfile.gnu 4find "$d" -name \*works -type f -print0 | xargs -0 md5sum > logfile.gnu
3find "$d" -name \*works -type f | busybox xargs md5sum > logfile.bb 5find "$d" -name \*works -type f -print0 | busybox xargs -0 md5sum > logfile.bb
4diff -u logfile.gnu logfile.bb 6diff -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");