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 | ||
