diff options
author | Ron Yorston <rmy@pobox.com> | 2016-11-02 10:19:18 +0000 |
---|---|---|
committer | Ron Yorston <rmy@pobox.com> | 2016-11-02 10:19:18 +0000 |
commit | e45ff573dc9707b1ea71f490ef16cd9d08feaaf2 (patch) | |
tree | 09980b02b9c8efc8d772c7379abdcd4eef4ddad8 | |
parent | 7bc3efa4f0c7608723d301bda5ee006a0f6141fe (diff) | |
parent | 2e6af549715f5d7b4c2ab204e46c8b8f6f057045 (diff) | |
download | busybox-w32-e45ff573dc9707b1ea71f490ef16cd9d08feaaf2.tar.gz busybox-w32-e45ff573dc9707b1ea71f490ef16cd9d08feaaf2.tar.bz2 busybox-w32-e45ff573dc9707b1ea71f490ef16cd9d08feaaf2.zip |
Merge branch 'busybox' into merge
-rw-r--r-- | libbb/xfuncs.c | 47 | ||||
-rw-r--r-- | miscutils/man.c | 105 | ||||
-rw-r--r-- | shell/ash.c | 122 | ||||
-rw-r--r-- | shell/ash_test/ash-vars/readonly1.right | 2 | ||||
-rwxr-xr-x | shell/ash_test/ash-vars/readonly1.tests | 7 | ||||
-rw-r--r-- | shell/hush.c | 418 | ||||
-rw-r--r-- | shell/hush_test/hush-misc/wait1.right | 2 | ||||
-rwxr-xr-x | shell/hush_test/hush-misc/wait1.tests | 3 | ||||
-rw-r--r-- | shell/hush_test/hush-misc/wait2.right | 2 | ||||
-rwxr-xr-x | shell/hush_test/hush-misc/wait2.tests | 4 | ||||
-rw-r--r-- | shell/hush_test/hush-misc/wait3.right | 2 | ||||
-rwxr-xr-x | shell/hush_test/hush-misc/wait3.tests | 3 |
12 files changed, 466 insertions, 251 deletions
diff --git a/libbb/xfuncs.c b/libbb/xfuncs.c index 3f9a84ad4..45650edba 100644 --- a/libbb/xfuncs.c +++ b/libbb/xfuncs.c | |||
@@ -237,16 +237,27 @@ ssize_t FAST_FUNC full_write2_str(const char *str) | |||
237 | 237 | ||
238 | static int wh_helper(int value, int def_val, const char *env_name, int *err) | 238 | static int wh_helper(int value, int def_val, const char *env_name, int *err) |
239 | { | 239 | { |
240 | if (value == 0) { | 240 | /* Envvars override even if "value" from ioctl is valid (>0). |
241 | char *s = getenv(env_name); | 241 | * Rationale: it's impossible to guess what user wants. |
242 | if (s) { | 242 | * For example: "man CMD | ...": should "man" format output |
243 | value = atoi(s); | 243 | * to stdout's width? stdin's width? /dev/tty's width? 80 chars? |
244 | /* If LINES/COLUMNS are set, pretend that there is | 244 | * We _cant_ know it. If "..." saves text for e.g. email, |
245 | * no error getting w/h, this prevents some ugly | 245 | * then it's probably 80 chars. |
246 | * cursor tricks by our callers */ | 246 | * If "..." is, say, "grep -v DISCARD | $PAGER", then user |
247 | *err = 0; | 247 | * would prefer his tty's width to be used! |
248 | } | 248 | * |
249 | * Since we don't know, at least allow user to do this: | ||
250 | * "COLUMNS=80 man CMD | ..." | ||
251 | */ | ||
252 | char *s = getenv(env_name); | ||
253 | if (s) { | ||
254 | value = atoi(s); | ||
255 | /* If LINES/COLUMNS are set, pretend that there is | ||
256 | * no error getting w/h, this prevents some ugly | ||
257 | * cursor tricks by our callers */ | ||
258 | *err = 0; | ||
249 | } | 259 | } |
260 | |||
250 | if (value <= 1 || value >= 30000) | 261 | if (value <= 1 || value >= 30000) |
251 | value = def_val; | 262 | value = def_val; |
252 | return value; | 263 | return value; |
@@ -258,6 +269,20 @@ int FAST_FUNC get_terminal_width_height(int fd, unsigned *width, unsigned *heigh | |||
258 | { | 269 | { |
259 | struct winsize win; | 270 | struct winsize win; |
260 | int err; | 271 | int err; |
272 | int close_me = -1; | ||
273 | |||
274 | if (fd == -1) { | ||
275 | if (isatty(STDOUT_FILENO)) | ||
276 | fd = STDOUT_FILENO; | ||
277 | else | ||
278 | if (isatty(STDERR_FILENO)) | ||
279 | fd = STDERR_FILENO; | ||
280 | else | ||
281 | if (isatty(STDIN_FILENO)) | ||
282 | fd = STDIN_FILENO; | ||
283 | else | ||
284 | close_me = fd = open("/dev/tty", O_RDONLY); | ||
285 | } | ||
261 | 286 | ||
262 | win.ws_row = 0; | 287 | win.ws_row = 0; |
263 | win.ws_col = 0; | 288 | win.ws_col = 0; |
@@ -268,6 +293,10 @@ int FAST_FUNC get_terminal_width_height(int fd, unsigned *width, unsigned *heigh | |||
268 | *height = wh_helper(win.ws_row, 24, "LINES", &err); | 293 | *height = wh_helper(win.ws_row, 24, "LINES", &err); |
269 | if (width) | 294 | if (width) |
270 | *width = wh_helper(win.ws_col, 80, "COLUMNS", &err); | 295 | *width = wh_helper(win.ws_col, 80, "COLUMNS", &err); |
296 | |||
297 | if (close_me >= 0) | ||
298 | close(close_me); | ||
299 | |||
271 | return err; | 300 | return err; |
272 | } | 301 | } |
273 | int FAST_FUNC get_terminal_width(int fd) | 302 | int FAST_FUNC get_terminal_width(int fd) |
diff --git a/miscutils/man.c b/miscutils/man.c index 6d32890d1..a8c2d4047 100644 --- a/miscutils/man.c +++ b/miscutils/man.c | |||
@@ -9,8 +9,11 @@ | |||
9 | //usage: "Format and display manual page\n" | 9 | //usage: "Format and display manual page\n" |
10 | //usage: "\n -a Display all pages" | 10 | //usage: "\n -a Display all pages" |
11 | //usage: "\n -w Show page locations" | 11 | //usage: "\n -w Show page locations" |
12 | //usage: "\n" | ||
13 | //usage: "\n$COLUMNS overrides output width" | ||
12 | 14 | ||
13 | #include "libbb.h" | 15 | #include "libbb.h" |
16 | #include "common_bufsiz.h" | ||
14 | 17 | ||
15 | enum { | 18 | enum { |
16 | OPT_a = 1, /* all */ | 19 | OPT_a = 1, /* all */ |
@@ -18,7 +21,6 @@ enum { | |||
18 | }; | 21 | }; |
19 | 22 | ||
20 | /* This is what I see on my desktop system being executed: | 23 | /* This is what I see on my desktop system being executed: |
21 | |||
22 | ( | 24 | ( |
23 | echo ".ll 12.4i" | 25 | echo ".ll 12.4i" |
24 | echo ".nr LL 12.4i" | 26 | echo ".nr LL 12.4i" |
@@ -28,11 +30,41 @@ echo ".\\\"" | |||
28 | echo ".pl \n(nlu+10" | 30 | echo ".pl \n(nlu+10" |
29 | ) | gtbl | nroff -Tlatin1 -mandoc | less | 31 | ) | gtbl | nroff -Tlatin1 -mandoc | less |
30 | 32 | ||
31 | */ | 33 | Some systems use -Tascii. |
34 | |||
35 | On another system I see this: | ||
36 | |||
37 | ... | tbl | nroff -mandoc -rLL=<NNN>n -rLT=<NNN>n -Tutf8 | less | ||
32 | 38 | ||
33 | static int show_manpage(const char *pager, char *man_filename, int man, int level); | 39 | where <NNN> is screen width minus 5. |
40 | Replacing "DEFINE nroff nroff -mandoc" in /etc/man_db.conf | ||
41 | changes "nroff -mandoc" part; -rLL=<NNN>n, -rLT=<NNN>n and -Tutf8 parts are | ||
42 | appended to the user-specified command. | ||
34 | 43 | ||
35 | static int run_pipe(const char *pager, char *man_filename, int man, int level) | 44 | Redirecting to a pipe or file sets GROFF_NO_SGR=1 to prevent color escapes, |
45 | and uses "col -b -p -x" instead of pager, this filters out backspace | ||
46 | and underscore tricks. | ||
47 | */ | ||
48 | |||
49 | struct globals { | ||
50 | const char *col; | ||
51 | const char *tbl; | ||
52 | const char *nroff; | ||
53 | const char *pager; | ||
54 | } FIX_ALIASING; | ||
55 | #define G (*(struct globals*)bb_common_bufsiz1) | ||
56 | #define INIT_G() do { \ | ||
57 | setup_common_bufsiz(); \ | ||
58 | G.col = "col"; \ | ||
59 | G.tbl = "tbl"; \ | ||
60 | /* Removed -Tlatin1. Assuming system nroff has suitable default */ \ | ||
61 | G.nroff = "nroff -mandoc"; \ | ||
62 | G.pager = ENABLE_LESS ? "less" : "more"; \ | ||
63 | } while (0) | ||
64 | |||
65 | static int show_manpage(char *man_filename, int man, int level); | ||
66 | |||
67 | static int run_pipe(char *man_filename, int man, int level) | ||
36 | { | 68 | { |
37 | char *cmd; | 69 | char *cmd; |
38 | 70 | ||
@@ -95,7 +127,7 @@ static int run_pipe(const char *pager, char *man_filename, int man, int level) | |||
95 | man_filename = xasprintf("%s/%s", man_filename, linkname); | 127 | man_filename = xasprintf("%s/%s", man_filename, linkname); |
96 | free(line); | 128 | free(line); |
97 | /* Note: we leak "new" man_filename string as well... */ | 129 | /* Note: we leak "new" man_filename string as well... */ |
98 | if (show_manpage(pager, man_filename, man, level + 1)) | 130 | if (show_manpage(man_filename, man, level + 1)) |
99 | return 1; | 131 | return 1; |
100 | /* else: show the link, it's better than nothing */ | 132 | /* else: show the link, it's better than nothing */ |
101 | } | 133 | } |
@@ -103,20 +135,26 @@ static int run_pipe(const char *pager, char *man_filename, int man, int level) | |||
103 | ordinary_manpage: | 135 | ordinary_manpage: |
104 | close(STDIN_FILENO); | 136 | close(STDIN_FILENO); |
105 | open_zipped(man_filename, /*fail_if_not_compressed:*/ 0); /* guaranteed to use fd 0 (STDIN_FILENO) */ | 137 | open_zipped(man_filename, /*fail_if_not_compressed:*/ 0); /* guaranteed to use fd 0 (STDIN_FILENO) */ |
106 | /* "2>&1" is added so that nroff errors are shown in pager too. | 138 | if (man) { |
107 | * Otherwise it may show just empty screen */ | 139 | int w = get_terminal_width(-1); |
108 | cmd = xasprintf( | 140 | if (w > 10) |
109 | /* replaced -Tlatin1 with -Tascii for non-UTF8 displays */ | 141 | w -= 2; |
110 | man ? "gtbl | nroff -Tascii -mandoc 2>&1 | %s" | 142 | /* "2>&1" is added so that nroff errors are shown in pager too. |
111 | : "%s", | 143 | * Otherwise it may show just empty screen. |
112 | pager); | 144 | */ |
145 | cmd = xasprintf("%s | %s -rLL=%un -rLT=%un 2>&1 | %s", | ||
146 | G.tbl, G.nroff, w, w, | ||
147 | G.pager); | ||
148 | } else { | ||
149 | cmd = xstrdup(G.pager); | ||
150 | } | ||
113 | system(cmd); | 151 | system(cmd); |
114 | free(cmd); | 152 | free(cmd); |
115 | return 1; | 153 | return 1; |
116 | } | 154 | } |
117 | 155 | ||
118 | /* man_filename is of the form "/dir/dir/dir/name.s" */ | 156 | /* man_filename is of the form "/dir/dir/dir/name.s" */ |
119 | static int show_manpage(const char *pager, char *man_filename, int man, int level) | 157 | static int show_manpage(char *man_filename, int man, int level) |
120 | { | 158 | { |
121 | #if SEAMLESS_COMPRESSION | 159 | #if SEAMLESS_COMPRESSION |
122 | /* We leak this allocation... */ | 160 | /* We leak this allocation... */ |
@@ -125,26 +163,26 @@ static int show_manpage(const char *pager, char *man_filename, int man, int leve | |||
125 | #endif | 163 | #endif |
126 | 164 | ||
127 | #if ENABLE_FEATURE_SEAMLESS_LZMA | 165 | #if ENABLE_FEATURE_SEAMLESS_LZMA |
128 | if (run_pipe(pager, filename_with_zext, man, level)) | 166 | if (run_pipe(filename_with_zext, man, level)) |
129 | return 1; | 167 | return 1; |
130 | #endif | 168 | #endif |
131 | #if ENABLE_FEATURE_SEAMLESS_XZ | 169 | #if ENABLE_FEATURE_SEAMLESS_XZ |
132 | strcpy(ext, "xz"); | 170 | strcpy(ext, "xz"); |
133 | if (run_pipe(pager, filename_with_zext, man, level)) | 171 | if (run_pipe(filename_with_zext, man, level)) |
134 | return 1; | 172 | return 1; |
135 | #endif | 173 | #endif |
136 | #if ENABLE_FEATURE_SEAMLESS_BZ2 | 174 | #if ENABLE_FEATURE_SEAMLESS_BZ2 |
137 | strcpy(ext, "bz2"); | 175 | strcpy(ext, "bz2"); |
138 | if (run_pipe(pager, filename_with_zext, man, level)) | 176 | if (run_pipe(filename_with_zext, man, level)) |
139 | return 1; | 177 | return 1; |
140 | #endif | 178 | #endif |
141 | #if ENABLE_FEATURE_SEAMLESS_GZ | 179 | #if ENABLE_FEATURE_SEAMLESS_GZ |
142 | strcpy(ext, "gz"); | 180 | strcpy(ext, "gz"); |
143 | if (run_pipe(pager, filename_with_zext, man, level)) | 181 | if (run_pipe(filename_with_zext, man, level)) |
144 | return 1; | 182 | return 1; |
145 | #endif | 183 | #endif |
146 | 184 | ||
147 | return run_pipe(pager, man_filename, man, level); | 185 | return run_pipe(man_filename, man, level); |
148 | } | 186 | } |
149 | 187 | ||
150 | static char **add_MANPATH(char **man_path_list, int *count_mp, char *path) | 188 | static char **add_MANPATH(char **man_path_list, int *count_mp, char *path) |
@@ -186,11 +224,20 @@ static char **add_MANPATH(char **man_path_list, int *count_mp, char *path) | |||
186 | return man_path_list; | 224 | return man_path_list; |
187 | } | 225 | } |
188 | 226 | ||
227 | static const char *if_redefined(const char *var, const char *key, const char *line) | ||
228 | { | ||
229 | if (!is_prefixed_with(line, key)) | ||
230 | return var; | ||
231 | line += strlen(key); | ||
232 | if (!isspace(line[0])) | ||
233 | return var; | ||
234 | return xstrdup(skip_whitespace(line)); | ||
235 | } | ||
236 | |||
189 | int man_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | 237 | int man_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
190 | int man_main(int argc UNUSED_PARAM, char **argv) | 238 | int man_main(int argc UNUSED_PARAM, char **argv) |
191 | { | 239 | { |
192 | parser_t *parser; | 240 | parser_t *parser; |
193 | const char *pager = ENABLE_LESS ? "less" : "more"; | ||
194 | char *sec_list; | 241 | char *sec_list; |
195 | char *cur_path, *cur_sect; | 242 | char *cur_path, *cur_sect; |
196 | char **man_path_list; | 243 | char **man_path_list; |
@@ -199,6 +246,8 @@ int man_main(int argc UNUSED_PARAM, char **argv) | |||
199 | int opt, not_found; | 246 | int opt, not_found; |
200 | char *token[2]; | 247 | char *token[2]; |
201 | 248 | ||
249 | INIT_G(); | ||
250 | |||
202 | opt_complementary = "-1"; /* at least one argument */ | 251 | opt_complementary = "-1"; /* at least one argument */ |
203 | opt = getopt32(argv, "+aw"); | 252 | opt = getopt32(argv, "+aw"); |
204 | argv += optind; | 253 | argv += optind; |
@@ -232,9 +281,10 @@ int man_main(int argc UNUSED_PARAM, char **argv) | |||
232 | if (!token[1]) | 281 | if (!token[1]) |
233 | continue; | 282 | continue; |
234 | if (strcmp("DEFINE", token[0]) == 0) { | 283 | if (strcmp("DEFINE", token[0]) == 0) { |
235 | if (is_prefixed_with(token[1], "pager")) { | 284 | G.col = if_redefined(G.tbl , "col", token[1]); |
236 | pager = xstrdup(skip_whitespace(token[1] + 5)); | 285 | G.tbl = if_redefined(G.tbl , "tbl", token[1]); |
237 | } | 286 | G.nroff = if_redefined(G.nroff, "nroff", token[1]); |
287 | G.pager = if_redefined(G.pager, "pager", token[1]); | ||
238 | } else | 288 | } else |
239 | if (strcmp("MANDATORY_MANPATH"+10, token[0]) == 0 /* "MANPATH"? */ | 289 | if (strcmp("MANDATORY_MANPATH"+10, token[0]) == 0 /* "MANPATH"? */ |
240 | || strcmp("MANDATORY_MANPATH", token[0]) == 0 | 290 | || strcmp("MANDATORY_MANPATH", token[0]) == 0 |
@@ -254,7 +304,12 @@ int man_main(int argc UNUSED_PARAM, char **argv) | |||
254 | if (!env_pager) | 304 | if (!env_pager) |
255 | env_pager = getenv("PAGER"); | 305 | env_pager = getenv("PAGER"); |
256 | if (env_pager) | 306 | if (env_pager) |
257 | pager = env_pager; | 307 | G.pager = env_pager; |
308 | } | ||
309 | |||
310 | if (!isatty(STDOUT_FILENO)) { | ||
311 | putenv((char*)"GROFF_NO_SGR=1"); | ||
312 | G.pager = xasprintf("%s -b -p -x", G.col); | ||
258 | } | 313 | } |
259 | 314 | ||
260 | not_found = 0; | 315 | not_found = 0; |
@@ -263,7 +318,7 @@ int man_main(int argc UNUSED_PARAM, char **argv) | |||
263 | cur_mp = 0; | 318 | cur_mp = 0; |
264 | 319 | ||
265 | if (strchr(*argv, '/')) { | 320 | if (strchr(*argv, '/')) { |
266 | found = show_manpage(pager, *argv, /*man:*/ 1, 0); | 321 | found = show_manpage(*argv, /*man:*/ 1, 0); |
267 | goto check_found; | 322 | goto check_found; |
268 | } | 323 | } |
269 | while ((cur_path = man_path_list[cur_mp++]) != NULL) { | 324 | while ((cur_path = man_path_list[cur_mp++]) != NULL) { |
@@ -284,7 +339,7 @@ int man_main(int argc UNUSED_PARAM, char **argv) | |||
284 | sect_len, cur_sect, | 339 | sect_len, cur_sect, |
285 | *argv, | 340 | *argv, |
286 | sect_len, cur_sect); | 341 | sect_len, cur_sect); |
287 | found_here = show_manpage(pager, man_filename, cat0man1, 0); | 342 | found_here = show_manpage(man_filename, cat0man1, 0); |
288 | found |= found_here; | 343 | found |= found_here; |
289 | cat0man1 += found_here + 1; | 344 | cat0man1 += found_here + 1; |
290 | free(man_filename); | 345 | free(man_filename); |
diff --git a/shell/ash.c b/shell/ash.c index dd320355e..a709c3602 100644 --- a/shell/ash.c +++ b/shell/ash.c | |||
@@ -362,8 +362,7 @@ struct globals_misc { | |||
362 | volatile int suppress_int; /* counter */ | 362 | volatile int suppress_int; /* counter */ |
363 | volatile /*sig_atomic_t*/ smallint pending_int; /* 1 = got SIGINT */ | 363 | volatile /*sig_atomic_t*/ smallint pending_int; /* 1 = got SIGINT */ |
364 | volatile /*sig_atomic_t*/ smallint got_sigchld; /* 1 = got SIGCHLD */ | 364 | volatile /*sig_atomic_t*/ smallint got_sigchld; /* 1 = got SIGCHLD */ |
365 | /* last pending signal */ | 365 | volatile /*sig_atomic_t*/ smallint pending_sig; /* last pending signal */ |
366 | volatile /*sig_atomic_t*/ smallint pending_sig; | ||
367 | smallint exception_type; /* kind of exception (0..5) */ | 366 | smallint exception_type; /* kind of exception (0..5) */ |
368 | /* exceptions */ | 367 | /* exceptions */ |
369 | #define EXINT 0 /* SIGINT received */ | 368 | #define EXINT 0 /* SIGINT received */ |
@@ -915,13 +914,8 @@ trace_vprintf(const char *fmt, va_list va) | |||
915 | { | 914 | { |
916 | if (debug != 1) | 915 | if (debug != 1) |
917 | return; | 916 | return; |
918 | if (DEBUG_TIME) | ||
919 | fprintf(tracefile, "%u ", (int) time(NULL)); | ||
920 | if (DEBUG_PID) | ||
921 | fprintf(tracefile, "[%u] ", (int) getpid()); | ||
922 | if (DEBUG_SIG) | ||
923 | fprintf(tracefile, "pending s:%d i:%d(supp:%d) ", pending_sig, pending_int, suppress_int); | ||
924 | vfprintf(tracefile, fmt, va); | 917 | vfprintf(tracefile, fmt, va); |
918 | fprintf(tracefile, "\n"); | ||
925 | } | 919 | } |
926 | 920 | ||
927 | static void | 921 | static void |
@@ -1314,11 +1308,10 @@ ash_vmsg_and_raise(int cond, const char *msg, va_list ap) | |||
1314 | { | 1308 | { |
1315 | #if DEBUG | 1309 | #if DEBUG |
1316 | if (msg) { | 1310 | if (msg) { |
1317 | TRACE(("ash_vmsg_and_raise(%d, \"", cond)); | 1311 | TRACE(("ash_vmsg_and_raise(%d):", cond)); |
1318 | TRACEV((msg, ap)); | 1312 | TRACEV((msg, ap)); |
1319 | TRACE(("\") pid=%d\n", getpid())); | ||
1320 | } else | 1313 | } else |
1321 | TRACE(("ash_vmsg_and_raise(%d, NULL) pid=%d\n", cond, getpid())); | 1314 | TRACE(("ash_vmsg_and_raise(%d):NULL\n", cond)); |
1322 | if (msg) | 1315 | if (msg) |
1323 | #endif | 1316 | #endif |
1324 | ash_vmsg(msg, ap); | 1317 | ash_vmsg(msg, ap); |
@@ -2250,6 +2243,7 @@ setvareq(char *s, int flags) | |||
2250 | if (flags & VNOSAVE) | 2243 | if (flags & VNOSAVE) |
2251 | free(s); | 2244 | free(s); |
2252 | n = vp->var_text; | 2245 | n = vp->var_text; |
2246 | exitstatus = 1; | ||
2253 | ash_msg_and_raise_error("%.*s: is read only", strchrnul(n, '=') - n, n); | 2247 | ash_msg_and_raise_error("%.*s: is read only", strchrnul(n, '=') - n, n); |
2254 | } | 2248 | } |
2255 | 2249 | ||
@@ -3715,11 +3709,6 @@ setsignal(int signo) | |||
3715 | #define CUR_RUNNING 1 | 3709 | #define CUR_RUNNING 1 |
3716 | #define CUR_STOPPED 0 | 3710 | #define CUR_STOPPED 0 |
3717 | 3711 | ||
3718 | /* mode flags for dowait */ | ||
3719 | #define DOWAIT_NONBLOCK 0 | ||
3720 | #define DOWAIT_BLOCK 1 | ||
3721 | #define DOWAIT_BLOCK_OR_SIG 2 | ||
3722 | |||
3723 | #if JOBS | 3712 | #if JOBS |
3724 | /* pgrp of shell on invocation */ | 3713 | /* pgrp of shell on invocation */ |
3725 | static int initialpgrp; //references:2 | 3714 | static int initialpgrp; //references:2 |
@@ -4231,24 +4220,30 @@ waitpid_child(int *status, int wait_flags) | |||
4231 | #else | 4220 | #else |
4232 | 4221 | ||
4233 | static int | 4222 | static int |
4234 | wait_block_or_sig(int *status, int wait_flags) | 4223 | wait_block_or_sig(int *status) |
4235 | { | 4224 | { |
4236 | sigset_t mask; | ||
4237 | int pid; | 4225 | int pid; |
4238 | 4226 | ||
4239 | do { | 4227 | do { |
4240 | /* Poll all children for changes in their state */ | 4228 | /* Poll all children for changes in their state */ |
4241 | got_sigchld = 0; | 4229 | got_sigchld = 0; |
4242 | pid = waitpid(-1, status, wait_flags | WNOHANG); | 4230 | /* if job control is active, accept stopped processes too */ |
4231 | pid = waitpid(-1, status, doing_jobctl ? (WNOHANG|WUNTRACED) : WNOHANG); | ||
4243 | if (pid != 0) | 4232 | if (pid != 0) |
4244 | break; /* Error (e.g. EINTR) or pid */ | 4233 | break; /* Error (e.g. EINTR, ECHILD) or pid */ |
4245 | 4234 | ||
4246 | /* No child is ready. Sleep until interesting signal is received */ | 4235 | /* Children exist, but none are ready. Sleep until interesting signal */ |
4236 | #if 0 /* dash does this */ | ||
4237 | sigset_t mask; | ||
4247 | sigfillset(&mask); | 4238 | sigfillset(&mask); |
4248 | sigprocmask(SIG_SETMASK, &mask, &mask); | 4239 | sigprocmask(SIG_SETMASK, &mask, &mask); |
4249 | while (!got_sigchld && !pending_sig) | 4240 | while (!got_sigchld && !pending_sig) |
4250 | sigsuspend(&mask); | 4241 | sigsuspend(&mask); |
4251 | sigprocmask(SIG_SETMASK, &mask, NULL); | 4242 | sigprocmask(SIG_SETMASK, &mask, NULL); |
4243 | #else | ||
4244 | while (!got_sigchld && !pending_sig) | ||
4245 | pause(); | ||
4246 | #endif | ||
4252 | 4247 | ||
4253 | /* If it was SIGCHLD, poll children again */ | 4248 | /* If it was SIGCHLD, poll children again */ |
4254 | } while (got_sigchld); | 4249 | } while (got_sigchld); |
@@ -4257,11 +4252,13 @@ wait_block_or_sig(int *status, int wait_flags) | |||
4257 | } | 4252 | } |
4258 | #endif | 4253 | #endif |
4259 | 4254 | ||
4255 | #define DOWAIT_NONBLOCK 0 | ||
4256 | #define DOWAIT_BLOCK 1 | ||
4257 | #define DOWAIT_BLOCK_OR_SIG 2 | ||
4260 | 4258 | ||
4261 | static int | 4259 | static int |
4262 | dowait(int block, struct job *job) | 4260 | dowait(int block, struct job *job) |
4263 | { | 4261 | { |
4264 | int wait_flags; | ||
4265 | int pid; | 4262 | int pid; |
4266 | int status; | 4263 | int status; |
4267 | struct job *jp; | 4264 | struct job *jp; |
@@ -4269,13 +4266,6 @@ dowait(int block, struct job *job) | |||
4269 | 4266 | ||
4270 | TRACE(("dowait(0x%x) called\n", block)); | 4267 | TRACE(("dowait(0x%x) called\n", block)); |
4271 | 4268 | ||
4272 | wait_flags = 0; | ||
4273 | if (block == DOWAIT_NONBLOCK) | ||
4274 | wait_flags = WNOHANG; | ||
4275 | /* If job control is compiled in, we accept stopped processes too. */ | ||
4276 | if (doing_jobctl) | ||
4277 | wait_flags |= WUNTRACED; | ||
4278 | |||
4279 | /* It's wrong to call waitpid() outside of INT_OFF region: | 4269 | /* It's wrong to call waitpid() outside of INT_OFF region: |
4280 | * signal can arrive just after syscall return and handler can | 4270 | * signal can arrive just after syscall return and handler can |
4281 | * longjmp away, losing stop/exit notification processing. | 4271 | * longjmp away, losing stop/exit notification processing. |
@@ -4286,17 +4276,27 @@ dowait(int block, struct job *job) | |||
4286 | * in INT_OFF region: "wait" needs to wait for any running job | 4276 | * in INT_OFF region: "wait" needs to wait for any running job |
4287 | * to change state, but should exit on any trap too. | 4277 | * to change state, but should exit on any trap too. |
4288 | * In INT_OFF region, a signal just before syscall entry can set | 4278 | * In INT_OFF region, a signal just before syscall entry can set |
4289 | * pending_sig valiables, but we can't check them, and we would | 4279 | * pending_sig variables, but we can't check them, and we would |
4290 | * either enter a sleeping waitpid() (BUG), or need to busy-loop. | 4280 | * either enter a sleeping waitpid() (BUG), or need to busy-loop. |
4281 | * | ||
4291 | * Because of this, we run inside INT_OFF, but use a special routine | 4282 | * Because of this, we run inside INT_OFF, but use a special routine |
4292 | * which combines waitpid() and sigsuspend(). | 4283 | * which combines waitpid() and pause(). |
4284 | * This is the reason why we need to have a handler for SIGCHLD: | ||
4285 | * SIG_DFL handler does not wake pause(). | ||
4293 | */ | 4286 | */ |
4294 | INT_OFF; | 4287 | INT_OFF; |
4295 | if (block == DOWAIT_BLOCK_OR_SIG) | 4288 | if (block == DOWAIT_BLOCK_OR_SIG) { |
4296 | pid = wait_block_or_sig(&status, wait_flags); | 4289 | pid = wait_block_or_sig(&status); |
4297 | else | 4290 | } else { |
4298 | /* NB: _not_ safe_waitpid, we need to detect EINTR. */ | 4291 | int wait_flags = 0; |
4292 | if (block == DOWAIT_NONBLOCK) | ||
4293 | wait_flags = WNOHANG; | ||
4294 | /* if job control is active, accept stopped processes too */ | ||
4295 | if (doing_jobctl) | ||
4296 | wait_flags |= WUNTRACED; | ||
4297 | /* NB: _not_ safe_waitpid, we need to detect EINTR */ | ||
4299 | pid = waitpid(-1, &status, wait_flags); | 4298 | pid = waitpid(-1, &status, wait_flags); |
4299 | } | ||
4300 | TRACE(("wait returns pid=%d, status=0x%x, errno=%d(%s)\n", | 4300 | TRACE(("wait returns pid=%d, status=0x%x, errno=%d(%s)\n", |
4301 | pid, status, errno, strerror(errno))); | 4301 | pid, status, errno, strerror(errno))); |
4302 | if (pid <= 0) | 4302 | if (pid <= 0) |
@@ -7410,6 +7410,21 @@ expandmeta(struct strlist *str /*, int flag*/) | |||
7410 | 7410 | ||
7411 | if (fflag) | 7411 | if (fflag) |
7412 | goto nometa; | 7412 | goto nometa; |
7413 | |||
7414 | /* Avoid glob() (and thus, stat() et al) for words like "echo" */ | ||
7415 | p = str->text; | ||
7416 | while (*p) { | ||
7417 | if (*p == '*') | ||
7418 | goto need_glob; | ||
7419 | if (*p == '?') | ||
7420 | goto need_glob; | ||
7421 | if (*p == '[') | ||
7422 | goto need_glob; | ||
7423 | p++; | ||
7424 | } | ||
7425 | goto nometa; | ||
7426 | |||
7427 | need_glob: | ||
7413 | INT_OFF; | 7428 | INT_OFF; |
7414 | p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP); | 7429 | p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP); |
7415 | // GLOB_NOMAGIC (GNU): if no *?[ chars in pattern, return it even if no match | 7430 | // GLOB_NOMAGIC (GNU): if no *?[ chars in pattern, return it even if no match |
@@ -10064,7 +10079,7 @@ evalcommand(union node *cmd, int flags) | |||
10064 | if (evalbltin(cmdentry.u.cmd, argc, argv, flags)) { | 10079 | if (evalbltin(cmdentry.u.cmd, argc, argv, flags)) { |
10065 | if (exception_type == EXERROR && spclbltin <= 0) { | 10080 | if (exception_type == EXERROR && spclbltin <= 0) { |
10066 | FORCE_INT_ON; | 10081 | FORCE_INT_ON; |
10067 | break; | 10082 | goto readstatus; |
10068 | } | 10083 | } |
10069 | raise: | 10084 | raise: |
10070 | longjmp(exception_handler->loc, 1); | 10085 | longjmp(exception_handler->loc, 1); |
@@ -10584,6 +10599,9 @@ popfile(void) | |||
10584 | { | 10599 | { |
10585 | struct parsefile *pf = g_parsefile; | 10600 | struct parsefile *pf = g_parsefile; |
10586 | 10601 | ||
10602 | if (pf == &basepf) | ||
10603 | return; | ||
10604 | |||
10587 | INT_OFF; | 10605 | INT_OFF; |
10588 | if (pf->pf_fd >= 0) | 10606 | if (pf->pf_fd >= 0) |
10589 | close(pf->pf_fd); | 10607 | close(pf->pf_fd); |
@@ -12747,6 +12765,10 @@ expandstr(const char *ps) | |||
12747 | static int | 12765 | static int |
12748 | evalstring(char *s, int flags) | 12766 | evalstring(char *s, int flags) |
12749 | { | 12767 | { |
12768 | struct jmploc *volatile savehandler; | ||
12769 | struct jmploc jmploc; | ||
12770 | int ex; | ||
12771 | |||
12750 | union node *n; | 12772 | union node *n; |
12751 | struct stackmark smark; | 12773 | struct stackmark smark; |
12752 | int status; | 12774 | int status; |
@@ -12756,6 +12778,19 @@ evalstring(char *s, int flags) | |||
12756 | setstackmark(&smark); | 12778 | setstackmark(&smark); |
12757 | 12779 | ||
12758 | status = 0; | 12780 | status = 0; |
12781 | /* On exception inside execution loop, we must popfile(). | ||
12782 | * Try interactively: | ||
12783 | * readonly a=a | ||
12784 | * command eval "a=b" # throws "is read only" error | ||
12785 | * "command BLTIN" is not supposed to abort (even in non-interactive use). | ||
12786 | * But if we skip popfile(), we hit EOF in eval's string, and exit. | ||
12787 | */ | ||
12788 | savehandler = exception_handler; | ||
12789 | ex = setjmp(jmploc.loc); | ||
12790 | if (ex) | ||
12791 | goto out; | ||
12792 | exception_handler = &jmploc; | ||
12793 | |||
12759 | while ((n = parsecmd(0)) != NODE_EOF) { | 12794 | while ((n = parsecmd(0)) != NODE_EOF) { |
12760 | int i; | 12795 | int i; |
12761 | 12796 | ||
@@ -12766,10 +12801,15 @@ evalstring(char *s, int flags) | |||
12766 | if (evalskip) | 12801 | if (evalskip) |
12767 | break; | 12802 | break; |
12768 | } | 12803 | } |
12804 | out: | ||
12769 | popstackmark(&smark); | 12805 | popstackmark(&smark); |
12770 | popfile(); | 12806 | popfile(); |
12771 | stunalloc(s); | 12807 | stunalloc(s); |
12772 | 12808 | ||
12809 | exception_handler = savehandler; | ||
12810 | if (ex) | ||
12811 | longjmp(exception_handler->loc, ex); | ||
12812 | |||
12773 | return status; | 12813 | return status; |
12774 | } | 12814 | } |
12775 | 12815 | ||
@@ -13982,11 +14022,6 @@ int ash_main(int argc UNUSED_PARAM, char **argv) | |||
13982 | goto state4; | 14022 | goto state4; |
13983 | } | 14023 | } |
13984 | exception_handler = &jmploc; | 14024 | exception_handler = &jmploc; |
13985 | #if DEBUG | ||
13986 | opentrace(); | ||
13987 | TRACE(("Shell args: ")); | ||
13988 | trace_puts_args(argv); | ||
13989 | #endif | ||
13990 | rootpid = getpid(); | 14025 | rootpid = getpid(); |
13991 | 14026 | ||
13992 | init(IF_PLATFORM_MINGW32(argc >= 2 && strcmp(argv[1], "-X") == 0)); | 14027 | init(IF_PLATFORM_MINGW32(argc >= 2 && strcmp(argv[1], "-X") == 0)); |
@@ -14003,6 +14038,11 @@ int ash_main(int argc UNUSED_PARAM, char **argv) | |||
14003 | } | 14038 | } |
14004 | #endif | 14039 | #endif |
14005 | procargs(argv); | 14040 | procargs(argv); |
14041 | #if DEBUG | ||
14042 | TRACE(("Shell args: ")); | ||
14043 | trace_puts_args(argv); | ||
14044 | #endif | ||
14045 | |||
14006 | #if ENABLE_PLATFORM_MINGW32 | 14046 | #if ENABLE_PLATFORM_MINGW32 |
14007 | if ( noconsole ) { | 14047 | if ( noconsole ) { |
14008 | DWORD dummy; | 14048 | DWORD dummy; |
diff --git a/shell/ash_test/ash-vars/readonly1.right b/shell/ash_test/ash-vars/readonly1.right new file mode 100644 index 000000000..2b363e325 --- /dev/null +++ b/shell/ash_test/ash-vars/readonly1.right | |||
@@ -0,0 +1,2 @@ | |||
1 | One:1 | ||
2 | One:1 | ||
diff --git a/shell/ash_test/ash-vars/readonly1.tests b/shell/ash_test/ash-vars/readonly1.tests new file mode 100755 index 000000000..81b461f5f --- /dev/null +++ b/shell/ash_test/ash-vars/readonly1.tests | |||
@@ -0,0 +1,7 @@ | |||
1 | readonly bla=123 | ||
2 | # Bare "eval bla=123" should abort ("eval" is a special builtin): | ||
3 | (eval bla=123 2>/dev/null; echo BUG) | ||
4 | echo One:$? | ||
5 | # "command BLTIN" disables "special-ness", should not abort: | ||
6 | command eval bla=123 2>/dev/null | ||
7 | echo One:$? | ||
diff --git a/shell/hush.c b/shell/hush.c index c80429d5c..7c2f157b8 100644 --- a/shell/hush.c +++ b/shell/hush.c | |||
@@ -44,6 +44,8 @@ | |||
44 | * special variables (done: PWD, PPID, RANDOM) | 44 | * special variables (done: PWD, PPID, RANDOM) |
45 | * tilde expansion | 45 | * tilde expansion |
46 | * aliases | 46 | * aliases |
47 | * kill %jobspec | ||
48 | * wait %jobspec | ||
47 | * follow IFS rules more precisely, including update semantics | 49 | * follow IFS rules more precisely, including update semantics |
48 | * builtins mandated by standards we don't support: | 50 | * builtins mandated by standards we don't support: |
49 | * [un]alias, command, fc, getopts, newgrp, readonly, times | 51 | * [un]alias, command, fc, getopts, newgrp, readonly, times |
@@ -7041,16 +7043,134 @@ static void delete_finished_bg_job(struct pipe *pi) | |||
7041 | } | 7043 | } |
7042 | #endif /* JOB */ | 7044 | #endif /* JOB */ |
7043 | 7045 | ||
7044 | /* Check to see if any processes have exited -- if they | 7046 | static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status) |
7045 | * have, figure out why and see if a job has completed */ | ||
7046 | static int checkjobs(struct pipe *fg_pipe) | ||
7047 | { | 7047 | { |
7048 | int attributes; | ||
7049 | int status; | ||
7050 | #if ENABLE_HUSH_JOB | 7048 | #if ENABLE_HUSH_JOB |
7051 | struct pipe *pi; | 7049 | struct pipe *pi; |
7052 | #endif | 7050 | #endif |
7053 | pid_t childpid; | 7051 | int i, dead; |
7052 | |||
7053 | dead = WIFEXITED(status) || WIFSIGNALED(status); | ||
7054 | |||
7055 | #if DEBUG_JOBS | ||
7056 | if (WIFSTOPPED(status)) | ||
7057 | debug_printf_jobs("pid %d stopped by sig %d (exitcode %d)\n", | ||
7058 | childpid, WSTOPSIG(status), WEXITSTATUS(status)); | ||
7059 | if (WIFSIGNALED(status)) | ||
7060 | debug_printf_jobs("pid %d killed by sig %d (exitcode %d)\n", | ||
7061 | childpid, WTERMSIG(status), WEXITSTATUS(status)); | ||
7062 | if (WIFEXITED(status)) | ||
7063 | debug_printf_jobs("pid %d exited, exitcode %d\n", | ||
7064 | childpid, WEXITSTATUS(status)); | ||
7065 | #endif | ||
7066 | /* Were we asked to wait for a fg pipe? */ | ||
7067 | if (fg_pipe) { | ||
7068 | i = fg_pipe->num_cmds; | ||
7069 | while (--i >= 0) { | ||
7070 | debug_printf_jobs("check pid %d\n", fg_pipe->cmds[i].pid); | ||
7071 | if (fg_pipe->cmds[i].pid != childpid) | ||
7072 | continue; | ||
7073 | if (dead) { | ||
7074 | int ex; | ||
7075 | fg_pipe->cmds[i].pid = 0; | ||
7076 | fg_pipe->alive_cmds--; | ||
7077 | ex = WEXITSTATUS(status); | ||
7078 | /* bash prints killer signal's name for *last* | ||
7079 | * process in pipe (prints just newline for SIGINT/SIGPIPE). | ||
7080 | * Mimic this. Example: "sleep 5" + (^\ or kill -QUIT) | ||
7081 | */ | ||
7082 | if (WIFSIGNALED(status)) { | ||
7083 | int sig = WTERMSIG(status); | ||
7084 | if (i == fg_pipe->num_cmds-1) | ||
7085 | /* TODO: use strsignal() instead for bash compat? but that's bloat... */ | ||
7086 | puts(sig == SIGINT || sig == SIGPIPE ? "" : get_signame(sig)); | ||
7087 | /* TODO: if (WCOREDUMP(status)) + " (core dumped)"; */ | ||
7088 | /* TODO: MIPS has 128 sigs (1..128), what if sig==128 here? | ||
7089 | * Maybe we need to use sig | 128? */ | ||
7090 | ex = sig + 128; | ||
7091 | } | ||
7092 | fg_pipe->cmds[i].cmd_exitcode = ex; | ||
7093 | } else { | ||
7094 | fg_pipe->stopped_cmds++; | ||
7095 | } | ||
7096 | debug_printf_jobs("fg_pipe: alive_cmds %d stopped_cmds %d\n", | ||
7097 | fg_pipe->alive_cmds, fg_pipe->stopped_cmds); | ||
7098 | if (fg_pipe->alive_cmds == fg_pipe->stopped_cmds) { | ||
7099 | /* All processes in fg pipe have exited or stopped */ | ||
7100 | int rcode = 0; | ||
7101 | i = fg_pipe->num_cmds; | ||
7102 | while (--i >= 0) { | ||
7103 | rcode = fg_pipe->cmds[i].cmd_exitcode; | ||
7104 | /* usually last process gives overall exitstatus, | ||
7105 | * but with "set -o pipefail", last *failed* process does */ | ||
7106 | if (G.o_opt[OPT_O_PIPEFAIL] == 0 || rcode != 0) | ||
7107 | break; | ||
7108 | } | ||
7109 | IF_HAS_KEYWORDS(if (fg_pipe->pi_inverted) rcode = !rcode;) | ||
7110 | /* Note: *non-interactive* bash does not continue if all processes in fg pipe | ||
7111 | * are stopped. Testcase: "cat | cat" in a script (not on command line!) | ||
7112 | * and "killall -STOP cat" */ | ||
7113 | if (G_interactive_fd) { | ||
7114 | #if ENABLE_HUSH_JOB | ||
7115 | if (fg_pipe->alive_cmds != 0) | ||
7116 | insert_bg_job(fg_pipe); | ||
7117 | #endif | ||
7118 | return rcode; | ||
7119 | } | ||
7120 | if (fg_pipe->alive_cmds == 0) | ||
7121 | return rcode; | ||
7122 | } | ||
7123 | /* There are still running processes in the fg_pipe */ | ||
7124 | return -1; | ||
7125 | } | ||
7126 | /* It wasnt in fg_pipe, look for process in bg pipes */ | ||
7127 | } | ||
7128 | |||
7129 | #if ENABLE_HUSH_JOB | ||
7130 | /* We were asked to wait for bg or orphaned children */ | ||
7131 | /* No need to remember exitcode in this case */ | ||
7132 | for (pi = G.job_list; pi; pi = pi->next) { | ||
7133 | for (i = 0; i < pi->num_cmds; i++) { | ||
7134 | if (pi->cmds[i].pid == childpid) | ||
7135 | goto found_pi_and_prognum; | ||
7136 | } | ||
7137 | } | ||
7138 | /* Happens when shell is used as init process (init=/bin/sh) */ | ||
7139 | debug_printf("checkjobs: pid %d was not in our list!\n", childpid); | ||
7140 | return -1; /* this wasn't a process from fg_pipe */ | ||
7141 | |||
7142 | found_pi_and_prognum: | ||
7143 | if (dead) { | ||
7144 | /* child exited */ | ||
7145 | pi->cmds[i].pid = 0; | ||
7146 | pi->cmds[i].cmd_exitcode = WEXITSTATUS(status); | ||
7147 | if (WIFSIGNALED(status)) | ||
7148 | pi->cmds[i].cmd_exitcode = 128 + WTERMSIG(status); | ||
7149 | pi->alive_cmds--; | ||
7150 | if (!pi->alive_cmds) { | ||
7151 | if (G_interactive_fd) | ||
7152 | printf(JOB_STATUS_FORMAT, pi->jobid, | ||
7153 | "Done", pi->cmdtext); | ||
7154 | delete_finished_bg_job(pi); | ||
7155 | } | ||
7156 | } else { | ||
7157 | /* child stopped */ | ||
7158 | pi->stopped_cmds++; | ||
7159 | } | ||
7160 | #endif | ||
7161 | return -1; /* this wasn't a process from fg_pipe */ | ||
7162 | } | ||
7163 | |||
7164 | /* Check to see if any processes have exited -- if they have, | ||
7165 | * figure out why and see if a job has completed. | ||
7166 | * Alternatively (fg_pipe == NULL, waitfor_pid != 0), | ||
7167 | * wait for a specific pid to complete, return exitcode+1 | ||
7168 | * (this allows to distinguish zero as "no children exited" result). | ||
7169 | */ | ||
7170 | static int checkjobs(struct pipe *fg_pipe, pid_t waitfor_pid) | ||
7171 | { | ||
7172 | int attributes; | ||
7173 | int status; | ||
7054 | int rcode = 0; | 7174 | int rcode = 0; |
7055 | 7175 | ||
7056 | debug_printf_jobs("checkjobs %p\n", fg_pipe); | 7176 | debug_printf_jobs("checkjobs %p\n", fg_pipe); |
@@ -7087,12 +7207,10 @@ static int checkjobs(struct pipe *fg_pipe) | |||
7087 | * 1 <========== bg pipe is not fully done, but exitcode is already known! | 7207 | * 1 <========== bg pipe is not fully done, but exitcode is already known! |
7088 | * [hush 1.14.0: yes we do it right] | 7208 | * [hush 1.14.0: yes we do it right] |
7089 | */ | 7209 | */ |
7090 | wait_more: | ||
7091 | while (1) { | 7210 | while (1) { |
7092 | int i; | 7211 | pid_t childpid; |
7093 | int dead; | ||
7094 | |||
7095 | #if ENABLE_HUSH_FAST | 7212 | #if ENABLE_HUSH_FAST |
7213 | int i; | ||
7096 | i = G.count_SIGCHLD; | 7214 | i = G.count_SIGCHLD; |
7097 | #endif | 7215 | #endif |
7098 | childpid = waitpid(-1, &status, attributes); | 7216 | childpid = waitpid(-1, &status, attributes); |
@@ -7106,112 +7224,24 @@ static int checkjobs(struct pipe *fg_pipe) | |||
7106 | //bb_error_msg("[%d] checkjobs: waitpid returned <= 0, G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); | 7224 | //bb_error_msg("[%d] checkjobs: waitpid returned <= 0, G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); |
7107 | } | 7225 | } |
7108 | #endif | 7226 | #endif |
7227 | /* ECHILD (no children), or 0 (no change in children status) */ | ||
7228 | rcode = childpid; | ||
7109 | break; | 7229 | break; |
7110 | } | 7230 | } |
7111 | dead = WIFEXITED(status) || WIFSIGNALED(status); | 7231 | rcode = process_wait_result(fg_pipe, childpid, status); |
7112 | 7232 | if (rcode >= 0) { | |
7113 | #if DEBUG_JOBS | 7233 | /* fg_pipe exited or stopped */ |
7114 | if (WIFSTOPPED(status)) | 7234 | break; |
7115 | debug_printf_jobs("pid %d stopped by sig %d (exitcode %d)\n", | ||
7116 | childpid, WSTOPSIG(status), WEXITSTATUS(status)); | ||
7117 | if (WIFSIGNALED(status)) | ||
7118 | debug_printf_jobs("pid %d killed by sig %d (exitcode %d)\n", | ||
7119 | childpid, WTERMSIG(status), WEXITSTATUS(status)); | ||
7120 | if (WIFEXITED(status)) | ||
7121 | debug_printf_jobs("pid %d exited, exitcode %d\n", | ||
7122 | childpid, WEXITSTATUS(status)); | ||
7123 | #endif | ||
7124 | /* Were we asked to wait for fg pipe? */ | ||
7125 | if (fg_pipe) { | ||
7126 | i = fg_pipe->num_cmds; | ||
7127 | while (--i >= 0) { | ||
7128 | debug_printf_jobs("check pid %d\n", fg_pipe->cmds[i].pid); | ||
7129 | if (fg_pipe->cmds[i].pid != childpid) | ||
7130 | continue; | ||
7131 | if (dead) { | ||
7132 | int ex; | ||
7133 | fg_pipe->cmds[i].pid = 0; | ||
7134 | fg_pipe->alive_cmds--; | ||
7135 | ex = WEXITSTATUS(status); | ||
7136 | /* bash prints killer signal's name for *last* | ||
7137 | * process in pipe (prints just newline for SIGINT/SIGPIPE). | ||
7138 | * Mimic this. Example: "sleep 5" + (^\ or kill -QUIT) | ||
7139 | */ | ||
7140 | if (WIFSIGNALED(status)) { | ||
7141 | int sig = WTERMSIG(status); | ||
7142 | if (i == fg_pipe->num_cmds-1) | ||
7143 | /* TODO: use strsignal() instead for bash compat? but that's bloat... */ | ||
7144 | puts(sig == SIGINT || sig == SIGPIPE ? "" : get_signame(sig)); | ||
7145 | /* TODO: if (WCOREDUMP(status)) + " (core dumped)"; */ | ||
7146 | /* TODO: MIPS has 128 sigs (1..128), what if sig==128 here? | ||
7147 | * Maybe we need to use sig | 128? */ | ||
7148 | ex = sig + 128; | ||
7149 | } | ||
7150 | fg_pipe->cmds[i].cmd_exitcode = ex; | ||
7151 | } else { | ||
7152 | fg_pipe->stopped_cmds++; | ||
7153 | } | ||
7154 | debug_printf_jobs("fg_pipe: alive_cmds %d stopped_cmds %d\n", | ||
7155 | fg_pipe->alive_cmds, fg_pipe->stopped_cmds); | ||
7156 | if (fg_pipe->alive_cmds == fg_pipe->stopped_cmds) { | ||
7157 | /* All processes in fg pipe have exited or stopped */ | ||
7158 | i = fg_pipe->num_cmds; | ||
7159 | while (--i >= 0) { | ||
7160 | rcode = fg_pipe->cmds[i].cmd_exitcode; | ||
7161 | /* usually last process gives overall exitstatus, | ||
7162 | * but with "set -o pipefail", last *failed* process does */ | ||
7163 | if (G.o_opt[OPT_O_PIPEFAIL] == 0 || rcode != 0) | ||
7164 | break; | ||
7165 | } | ||
7166 | IF_HAS_KEYWORDS(if (fg_pipe->pi_inverted) rcode = !rcode;) | ||
7167 | /* Note: *non-interactive* bash does not continue if all processes in fg pipe | ||
7168 | * are stopped. Testcase: "cat | cat" in a script (not on command line!) | ||
7169 | * and "killall -STOP cat" */ | ||
7170 | if (G_interactive_fd) { | ||
7171 | #if ENABLE_HUSH_JOB | ||
7172 | if (fg_pipe->alive_cmds != 0) | ||
7173 | insert_bg_job(fg_pipe); | ||
7174 | #endif | ||
7175 | return rcode; | ||
7176 | } | ||
7177 | if (fg_pipe->alive_cmds == 0) | ||
7178 | return rcode; | ||
7179 | } | ||
7180 | /* There are still running processes in the fg pipe */ | ||
7181 | goto wait_more; /* do waitpid again */ | ||
7182 | } | ||
7183 | /* it wasnt fg_pipe, look for process in bg pipes */ | ||
7184 | } | ||
7185 | |||
7186 | #if ENABLE_HUSH_JOB | ||
7187 | /* We asked to wait for bg or orphaned children */ | ||
7188 | /* No need to remember exitcode in this case */ | ||
7189 | for (pi = G.job_list; pi; pi = pi->next) { | ||
7190 | for (i = 0; i < pi->num_cmds; i++) { | ||
7191 | if (pi->cmds[i].pid == childpid) | ||
7192 | goto found_pi_and_prognum; | ||
7193 | } | ||
7194 | } | 7235 | } |
7195 | /* Happens when shell is used as init process (init=/bin/sh) */ | 7236 | if (childpid == waitfor_pid) { |
7196 | debug_printf("checkjobs: pid %d was not in our list!\n", childpid); | 7237 | rcode = WEXITSTATUS(status); |
7197 | continue; /* do waitpid again */ | 7238 | if (WIFSIGNALED(status)) |
7198 | 7239 | rcode = 128 + WTERMSIG(status); | |
7199 | found_pi_and_prognum: | 7240 | rcode++; |
7200 | if (dead) { | 7241 | break; /* "wait PID" called us, give it exitcode+1 */ |
7201 | /* child exited */ | ||
7202 | pi->cmds[i].pid = 0; | ||
7203 | pi->alive_cmds--; | ||
7204 | if (!pi->alive_cmds) { | ||
7205 | if (G_interactive_fd) | ||
7206 | printf(JOB_STATUS_FORMAT, pi->jobid, | ||
7207 | "Done", pi->cmdtext); | ||
7208 | delete_finished_bg_job(pi); | ||
7209 | } | ||
7210 | } else { | ||
7211 | /* child stopped */ | ||
7212 | pi->stopped_cmds++; | ||
7213 | } | 7242 | } |
7214 | #endif | 7243 | /* This wasn't one of our processes, or */ |
7244 | /* fg_pipe still has running processes, do waitpid again */ | ||
7215 | } /* while (waitpid succeeds)... */ | 7245 | } /* while (waitpid succeeds)... */ |
7216 | 7246 | ||
7217 | return rcode; | 7247 | return rcode; |
@@ -7221,7 +7251,7 @@ static int checkjobs(struct pipe *fg_pipe) | |||
7221 | static int checkjobs_and_fg_shell(struct pipe *fg_pipe) | 7251 | static int checkjobs_and_fg_shell(struct pipe *fg_pipe) |
7222 | { | 7252 | { |
7223 | pid_t p; | 7253 | pid_t p; |
7224 | int rcode = checkjobs(fg_pipe); | 7254 | int rcode = checkjobs(fg_pipe, 0 /*(no pid to wait for)*/); |
7225 | if (G_saved_tty_pgrp) { | 7255 | if (G_saved_tty_pgrp) { |
7226 | /* Job finished, move the shell to the foreground */ | 7256 | /* Job finished, move the shell to the foreground */ |
7227 | p = getpgrp(); /* our process group id */ | 7257 | p = getpgrp(); /* our process group id */ |
@@ -7893,7 +7923,7 @@ static int run_list(struct pipe *pi) | |||
7893 | /* else: e.g. "continue 2" should *break* once, *then* continue */ | 7923 | /* else: e.g. "continue 2" should *break* once, *then* continue */ |
7894 | } /* else: "while... do... { we are here (innermost list is not a loop!) };...done" */ | 7924 | } /* else: "while... do... { we are here (innermost list is not a loop!) };...done" */ |
7895 | if (G.depth_break_continue != 0 || fbc == BC_BREAK) { | 7925 | if (G.depth_break_continue != 0 || fbc == BC_BREAK) { |
7896 | checkjobs(NULL); | 7926 | checkjobs(NULL, 0 /*(no pid to wait for)*/); |
7897 | break; | 7927 | break; |
7898 | } | 7928 | } |
7899 | /* "continue": simulate end of loop */ | 7929 | /* "continue": simulate end of loop */ |
@@ -7902,7 +7932,7 @@ static int run_list(struct pipe *pi) | |||
7902 | } | 7932 | } |
7903 | #endif | 7933 | #endif |
7904 | if (G_flag_return_in_progress == 1) { | 7934 | if (G_flag_return_in_progress == 1) { |
7905 | checkjobs(NULL); | 7935 | checkjobs(NULL, 0 /*(no pid to wait for)*/); |
7906 | break; | 7936 | break; |
7907 | } | 7937 | } |
7908 | } else if (pi->followup == PIPE_BG) { | 7938 | } else if (pi->followup == PIPE_BG) { |
@@ -7929,7 +7959,7 @@ static int run_list(struct pipe *pi) | |||
7929 | } else | 7959 | } else |
7930 | #endif | 7960 | #endif |
7931 | { /* This one just waits for completion */ | 7961 | { /* This one just waits for completion */ |
7932 | rcode = checkjobs(pi); | 7962 | rcode = checkjobs(pi, 0 /*(no pid to wait for)*/); |
7933 | debug_printf_exec(": checkjobs exitcode %d\n", rcode); | 7963 | debug_printf_exec(": checkjobs exitcode %d\n", rcode); |
7934 | check_and_run_traps(); | 7964 | check_and_run_traps(); |
7935 | } | 7965 | } |
@@ -7943,7 +7973,7 @@ static int run_list(struct pipe *pi) | |||
7943 | cond_code = rcode; | 7973 | cond_code = rcode; |
7944 | #endif | 7974 | #endif |
7945 | check_jobs_and_continue: | 7975 | check_jobs_and_continue: |
7946 | checkjobs(NULL); | 7976 | checkjobs(NULL, 0 /*(no pid to wait for)*/); |
7947 | dont_check_jobs_but_continue: ; | 7977 | dont_check_jobs_but_continue: ; |
7948 | #if ENABLE_HUSH_LOOPS | 7978 | #if ENABLE_HUSH_LOOPS |
7949 | /* Beware of "while false; true; do ..."! */ | 7979 | /* Beware of "while false; true; do ..."! */ |
@@ -9408,9 +9438,68 @@ static int FAST_FUNC builtin_umask(char **argv) | |||
9408 | } | 9438 | } |
9409 | 9439 | ||
9410 | /* http://www.opengroup.org/onlinepubs/9699919799/utilities/wait.html */ | 9440 | /* http://www.opengroup.org/onlinepubs/9699919799/utilities/wait.html */ |
9441 | static int wait_for_child_or_signal(pid_t waitfor_pid) | ||
9442 | { | ||
9443 | int ret = 0; | ||
9444 | for (;;) { | ||
9445 | int sig; | ||
9446 | sigset_t oldset, allsigs; | ||
9447 | |||
9448 | /* waitpid is not interruptible by SA_RESTARTed | ||
9449 | * signals which we use. Thus, this ugly dance: | ||
9450 | */ | ||
9451 | |||
9452 | /* Make sure possible SIGCHLD is stored in kernel's | ||
9453 | * pending signal mask before we call waitpid. | ||
9454 | * Or else we may race with SIGCHLD, lose it, | ||
9455 | * and get stuck in sigwaitinfo... | ||
9456 | */ | ||
9457 | sigfillset(&allsigs); | ||
9458 | sigprocmask(SIG_SETMASK, &allsigs, &oldset); | ||
9459 | |||
9460 | if (!sigisemptyset(&G.pending_set)) { | ||
9461 | /* Crap! we raced with some signal! */ | ||
9462 | // sig = 0; | ||
9463 | goto restore; | ||
9464 | } | ||
9465 | |||
9466 | /*errno = 0; - checkjobs does this */ | ||
9467 | ret = checkjobs(NULL, waitfor_pid); /* waitpid(WNOHANG) inside */ | ||
9468 | /* if ECHILD, there are no children (ret is -1 or 0) */ | ||
9469 | /* if ret == 0, no children changed state */ | ||
9470 | /* if ret != 0, it's exitcode+1 of exited waitfor_pid child */ | ||
9471 | if (errno == ECHILD || ret--) { | ||
9472 | if (ret < 0) /* if ECHILD, may need to fix */ | ||
9473 | ret = 0; | ||
9474 | sigprocmask(SIG_SETMASK, &oldset, NULL); | ||
9475 | break; | ||
9476 | } | ||
9477 | |||
9478 | /* Wait for SIGCHLD or any other signal */ | ||
9479 | //sig = sigwaitinfo(&allsigs, NULL); | ||
9480 | /* It is vitally important for sigsuspend that SIGCHLD has non-DFL handler! */ | ||
9481 | /* Note: sigsuspend invokes signal handler */ | ||
9482 | sigsuspend(&oldset); | ||
9483 | restore: | ||
9484 | sigprocmask(SIG_SETMASK, &oldset, NULL); | ||
9485 | |||
9486 | /* So, did we get a signal? */ | ||
9487 | //if (sig > 0) | ||
9488 | // raise(sig); /* run handler */ | ||
9489 | sig = check_and_run_traps(); | ||
9490 | if (sig /*&& sig != SIGCHLD - always true */) { | ||
9491 | /* see note 2 */ | ||
9492 | ret = 128 + sig; | ||
9493 | break; | ||
9494 | } | ||
9495 | /* SIGCHLD, or no signal, or ignored one, such as SIGQUIT. Repeat */ | ||
9496 | } | ||
9497 | return ret; | ||
9498 | } | ||
9499 | |||
9411 | static int FAST_FUNC builtin_wait(char **argv) | 9500 | static int FAST_FUNC builtin_wait(char **argv) |
9412 | { | 9501 | { |
9413 | int ret = EXIT_SUCCESS; | 9502 | int ret; |
9414 | int status; | 9503 | int status; |
9415 | 9504 | ||
9416 | argv = skip_dash_dash(argv); | 9505 | argv = skip_dash_dash(argv); |
@@ -9431,74 +9520,51 @@ static int FAST_FUNC builtin_wait(char **argv) | |||
9431 | * ^C <-- after ~4 sec from keyboard | 9520 | * ^C <-- after ~4 sec from keyboard |
9432 | * $ | 9521 | * $ |
9433 | */ | 9522 | */ |
9434 | while (1) { | 9523 | return wait_for_child_or_signal(0 /*(no pid to wait for)*/); |
9435 | int sig; | ||
9436 | sigset_t oldset, allsigs; | ||
9437 | |||
9438 | /* waitpid is not interruptible by SA_RESTARTed | ||
9439 | * signals which we use. Thus, this ugly dance: | ||
9440 | */ | ||
9441 | |||
9442 | /* Make sure possible SIGCHLD is stored in kernel's | ||
9443 | * pending signal mask before we call waitpid. | ||
9444 | * Or else we may race with SIGCHLD, lose it, | ||
9445 | * and get stuck in sigwaitinfo... | ||
9446 | */ | ||
9447 | sigfillset(&allsigs); | ||
9448 | sigprocmask(SIG_SETMASK, &allsigs, &oldset); | ||
9449 | |||
9450 | if (!sigisemptyset(&G.pending_set)) { | ||
9451 | /* Crap! we raced with some signal! */ | ||
9452 | // sig = 0; | ||
9453 | goto restore; | ||
9454 | } | ||
9455 | |||
9456 | checkjobs(NULL); /* waitpid(WNOHANG) inside */ | ||
9457 | if (errno == ECHILD) { | ||
9458 | sigprocmask(SIG_SETMASK, &oldset, NULL); | ||
9459 | break; | ||
9460 | } | ||
9461 | |||
9462 | /* Wait for SIGCHLD or any other signal */ | ||
9463 | //sig = sigwaitinfo(&allsigs, NULL); | ||
9464 | /* It is vitally important for sigsuspend that SIGCHLD has non-DFL handler! */ | ||
9465 | /* Note: sigsuspend invokes signal handler */ | ||
9466 | sigsuspend(&oldset); | ||
9467 | restore: | ||
9468 | sigprocmask(SIG_SETMASK, &oldset, NULL); | ||
9469 | |||
9470 | /* So, did we get a signal? */ | ||
9471 | //if (sig > 0) | ||
9472 | // raise(sig); /* run handler */ | ||
9473 | sig = check_and_run_traps(); | ||
9474 | if (sig /*&& sig != SIGCHLD - always true */) { | ||
9475 | /* see note 2 */ | ||
9476 | ret = 128 + sig; | ||
9477 | break; | ||
9478 | } | ||
9479 | /* SIGCHLD, or no signal, or ignored one, such as SIGQUIT. Repeat */ | ||
9480 | } | ||
9481 | return ret; | ||
9482 | } | 9524 | } |
9483 | 9525 | ||
9484 | /* This is probably buggy wrt interruptible-ness */ | 9526 | /* TODO: support "wait %jobspec" */ |
9485 | while (*argv) { | 9527 | do { |
9486 | pid_t pid = bb_strtou(*argv, NULL, 10); | 9528 | pid_t pid = bb_strtou(*argv, NULL, 10); |
9487 | if (errno) { | 9529 | if (errno || pid <= 0) { |
9488 | /* mimic bash message */ | 9530 | /* mimic bash message */ |
9489 | bb_error_msg("wait: '%s': not a pid or valid job spec", *argv); | 9531 | bb_error_msg("wait: '%s': not a pid or valid job spec", *argv); |
9490 | return EXIT_FAILURE; | 9532 | ret = EXIT_FAILURE; |
9533 | continue; /* bash checks all argv[] */ | ||
9534 | } | ||
9535 | /* Do we have such child? */ | ||
9536 | ret = waitpid(pid, &status, WNOHANG); | ||
9537 | if (ret < 0) { | ||
9538 | /* No */ | ||
9539 | if (errno == ECHILD) { | ||
9540 | if (G.last_bg_pid > 0 && pid == G.last_bg_pid) { | ||
9541 | /* "wait $!" but last bg task has already exited. Try: | ||
9542 | * (sleep 1; exit 3) & sleep 2; echo $?; wait $!; echo $? | ||
9543 | * In bash it prints exitcode 0, then 3. | ||
9544 | */ | ||
9545 | ret = 0; /* FIXME */ | ||
9546 | continue; | ||
9547 | } | ||
9548 | /* Example: "wait 1". mimic bash message */ | ||
9549 | bb_error_msg("wait: pid %d is not a child of this shell", (int)pid); | ||
9550 | } else { | ||
9551 | /* ??? */ | ||
9552 | bb_perror_msg("wait %s", *argv); | ||
9553 | } | ||
9554 | ret = 127; | ||
9555 | continue; /* bash checks all argv[] */ | ||
9491 | } | 9556 | } |
9492 | if (waitpid(pid, &status, 0) == pid) { | 9557 | if (ret == 0) { |
9558 | /* Yes, and it still runs */ | ||
9559 | ret = wait_for_child_or_signal(pid); | ||
9560 | } else { | ||
9561 | /* Yes, and it just exited */ | ||
9562 | process_wait_result(NULL, pid, status); | ||
9493 | ret = WEXITSTATUS(status); | 9563 | ret = WEXITSTATUS(status); |
9494 | if (WIFSIGNALED(status)) | 9564 | if (WIFSIGNALED(status)) |
9495 | ret = 128 + WTERMSIG(status); | 9565 | ret = 128 + WTERMSIG(status); |
9496 | } else { | ||
9497 | bb_perror_msg("wait %s", *argv); | ||
9498 | ret = 127; | ||
9499 | } | 9566 | } |
9500 | argv++; | 9567 | } while (*++argv); |
9501 | } | ||
9502 | 9568 | ||
9503 | return ret; | 9569 | return ret; |
9504 | } | 9570 | } |
diff --git a/shell/hush_test/hush-misc/wait1.right b/shell/hush_test/hush-misc/wait1.right new file mode 100644 index 000000000..20f514da0 --- /dev/null +++ b/shell/hush_test/hush-misc/wait1.right | |||
@@ -0,0 +1,2 @@ | |||
1 | 0 | ||
2 | [1] Running sleep 2 | ||
diff --git a/shell/hush_test/hush-misc/wait1.tests b/shell/hush_test/hush-misc/wait1.tests new file mode 100755 index 000000000..f9cf6d48c --- /dev/null +++ b/shell/hush_test/hush-misc/wait1.tests | |||
@@ -0,0 +1,3 @@ | |||
1 | sleep 2 & sleep 1 & wait $! | ||
2 | echo $? | ||
3 | jobs | ||
diff --git a/shell/hush_test/hush-misc/wait2.right b/shell/hush_test/hush-misc/wait2.right new file mode 100644 index 000000000..f018c2c98 --- /dev/null +++ b/shell/hush_test/hush-misc/wait2.right | |||
@@ -0,0 +1,2 @@ | |||
1 | 0 | ||
2 | [1] Running sleep 3 | ||
diff --git a/shell/hush_test/hush-misc/wait2.tests b/shell/hush_test/hush-misc/wait2.tests new file mode 100755 index 000000000..be20f95a5 --- /dev/null +++ b/shell/hush_test/hush-misc/wait2.tests | |||
@@ -0,0 +1,4 @@ | |||
1 | sleep 3 & sleep 2 & sleep 1 | ||
2 | wait $! | ||
3 | echo $? | ||
4 | jobs | ||
diff --git a/shell/hush_test/hush-misc/wait3.right b/shell/hush_test/hush-misc/wait3.right new file mode 100644 index 000000000..5437b1d73 --- /dev/null +++ b/shell/hush_test/hush-misc/wait3.right | |||
@@ -0,0 +1,2 @@ | |||
1 | 3 | ||
2 | [1] Running sleep 2 | ||
diff --git a/shell/hush_test/hush-misc/wait3.tests b/shell/hush_test/hush-misc/wait3.tests new file mode 100755 index 000000000..ac541c3fc --- /dev/null +++ b/shell/hush_test/hush-misc/wait3.tests | |||
@@ -0,0 +1,3 @@ | |||
1 | sleep 2 & (sleep 1;exit 3) & wait $! | ||
2 | echo $? | ||
3 | jobs | ||