summaryrefslogtreecommitdiff
path: root/shell
diff options
context:
space:
mode:
Diffstat (limited to 'shell')
-rw-r--r--shell/hush.c93
-rw-r--r--shell/hush_test/hush-misc/func3.right4
-rwxr-xr-xshell/hush_test/hush-misc/func3.tests8
3 files changed, 91 insertions, 14 deletions
diff --git a/shell/hush.c b/shell/hush.c
index 84364e0..82634ff 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -444,6 +444,13 @@ struct globals {
#if ENABLE_HUSH_LOOPS
smallint flag_break_continue;
#endif
+#if ENABLE_HUSH_FUNCTIONS
+ /* 0: outside of a function (or sourced file)
+ * -1: inside of a function, ok to use return builtin
+ * 1: return is invoked, skip all till end of func.
+ */
+ smallint flag_return_in_progress;
+#endif
smallint fake_mode;
smallint exiting; /* used to prevent EXIT trap recursion */
/* These four support $?, $#, and $1 */
@@ -522,6 +529,9 @@ static int builtin_wait(char **argv);
static int builtin_break(char **argv);
static int builtin_continue(char **argv);
#endif
+#if ENABLE_HUSH_FUNCTIONS
+static int builtin_return(char **argv);
+#endif
/* Table of built-in functions. They can be forked or not, depending on
* context: within pipes, they fork. As simple commands, they do not.
@@ -575,7 +585,9 @@ static const struct built_in_command bltins[] = {
#endif
BLTIN("pwd" , builtin_pwd , "Print current directory"),
BLTIN("read" , builtin_read , "Input environment variable"),
-// BLTIN("return" , builtin_return , "Return from a function"),
+#if ENABLE_HUSH_FUNCTIONS
+ BLTIN("return" , builtin_return , "Return from a function"),
+#endif
BLTIN("set" , builtin_set , "Set/unset shell local variables"),
BLTIN("shift" , builtin_shift , "Shift positional parameters"),
BLTIN("test" , builtin_test , "Test condition"),
@@ -2849,9 +2861,14 @@ static void exec_function(nommu_save_t *nommu_save,
static int run_function(const struct function *funcp, char **argv)
{
int rc;
+ smallint sv_flg;
save_arg_t sv;
save_and_replace_G_args(&sv, argv);
+ /* "we are in function, ok to use return" */
+ sv_flg = G.flag_return_in_progress;
+ G.flag_return_in_progress = -1;
+
/* On MMU, funcp->body is always non-NULL */
#if !BB_MMU
if (!funcp->body) {
@@ -2863,6 +2880,8 @@ static int run_function(const struct function *funcp, char **argv)
{
rc = run_list(funcp->body);
}
+
+ G.flag_return_in_progress = sv_flg;
restore_G_args(&sv, argv);
return rc;
@@ -3886,7 +3905,8 @@ static int run_list(struct pipe *pi)
#endif
rcode = r = run_pipe(pi); /* NB: rcode is a smallint */
if (r != -1) {
- /* We only ran a builtin: rcode is already known
+ /* We ran a builtin, function, or group.
+ * rcode is already known
* and we don't need to wait for anything. */
G.last_exitcode = rcode;
debug_printf_exec(": builtin/func exitcode %d\n", rcode);
@@ -3910,6 +3930,10 @@ static int run_list(struct pipe *pi)
continue;
}
#endif
+#if ENABLE_HUSH_FUNCTIONS
+ if (G.flag_return_in_progress == 1)
+ goto check_jobs_and_break;
+#endif
} else if (pi->followup == PIPE_BG) {
/* What does bash do with attempts to background builtins? */
/* even bash 3.2 doesn't do that well with nested bg:
@@ -6675,6 +6699,7 @@ static int builtin_shift(char **argv)
static int builtin_source(char **argv)
{
FILE *input;
+ smallint sv_flg;
save_arg_t sv;
if (*++argv == NULL)
@@ -6688,12 +6713,17 @@ static int builtin_source(char **argv)
}
close_on_exec_on(fileno(input));
- /* Now run the file */
+ sv_flg = G.flag_return_in_progress;
+ /* "we are inside sourced file, ok to use return" */
+ G.flag_return_in_progress = -1;
save_and_replace_G_args(&sv, argv);
+
parse_and_run_file(input);
- restore_G_args(&sv, argv);
fclose(input);
+ restore_G_args(&sv, argv);
+ G.flag_return_in_progress = sv_flg;
+
return G.last_exitcode;
}
@@ -6833,25 +6863,36 @@ static int builtin_wait(char **argv)
return ret;
}
+#if ENABLE_HUSH_LOOPS || ENABLE_HUSH_FUNCTIONS
+static unsigned parse_numeric_argv1(char **argv, unsigned def, unsigned def_min)
+{
+ if (argv[1]) {
+ def = bb_strtou(argv[1], NULL, 10);
+ if (errno || def < def_min || argv[2]) {
+ bb_error_msg("%s: bad arguments", argv[0]);
+ def = UINT_MAX;
+ }
+ }
+ return def;
+}
+#endif
+
#if ENABLE_HUSH_LOOPS
static int builtin_break(char **argv)
{
+ unsigned depth;
if (G.depth_of_loop == 0) {
bb_error_msg("%s: only meaningful in a loop", argv[0]);
return EXIT_SUCCESS; /* bash compat */
}
G.flag_break_continue++; /* BC_BREAK = 1 */
- G.depth_break_continue = 1;
- if (argv[1]) {
- G.depth_break_continue = bb_strtou(argv[1], NULL, 10);
- if (errno || !G.depth_break_continue || argv[2]) {
- bb_error_msg("%s: bad arguments", argv[0]);
- G.flag_break_continue = BC_BREAK;
- G.depth_break_continue = UINT_MAX;
- }
- }
- if (G.depth_of_loop < G.depth_break_continue)
+
+ G.depth_break_continue = depth = parse_numeric_argv1(argv, 1, 1);
+ if (depth == UINT_MAX)
+ G.flag_break_continue = BC_BREAK;
+ if (G.depth_of_loop < depth)
G.depth_break_continue = G.depth_of_loop;
+
return EXIT_SUCCESS;
}
@@ -6861,3 +6902,27 @@ static int builtin_continue(char **argv)
return builtin_break(argv);
}
#endif
+
+#if ENABLE_HUSH_FUNCTIONS
+static int builtin_return(char **argv UNUSED_PARAM)
+{
+ int rc;
+
+ if (G.flag_return_in_progress != -1) {
+ bb_error_msg("%s: not in a function or sourced script", argv[0]);
+ return EXIT_FAILURE; /* bash compat */
+ }
+
+ G.flag_return_in_progress = 1;
+
+ /* bash:
+ * out of range: wraps around at 256, does not error out
+ * non-numeric param:
+ * f() { false; return qwe; }; f; echo $?
+ * bash: return: qwe: numeric argument required <== we do this
+ * 255 <== we also do this
+ */
+ rc = parse_numeric_argv1(argv, G.last_exitcode, 0);
+ return rc;
+}
+#endif
diff --git a/shell/hush_test/hush-misc/func3.right b/shell/hush_test/hush-misc/func3.right
new file mode 100644
index 0000000..b6d7345
--- /dev/null
+++ b/shell/hush_test/hush-misc/func3.right
@@ -0,0 +1,4 @@
+One:1
+Zero:0
+One:1
+Five:5
diff --git a/shell/hush_test/hush-misc/func3.tests b/shell/hush_test/hush-misc/func3.tests
new file mode 100755
index 0000000..fa6f26a
--- /dev/null
+++ b/shell/hush_test/hush-misc/func3.tests
@@ -0,0 +1,8 @@
+f() { false; return; echo BAD; };
+{ f; echo One:$?; }; echo Zero:$?
+
+f() { false; return; };
+f; echo One:$?
+
+f() { return 5; };
+f; echo Five:$?