From a6d9111c67264a8828a563ccc23a24144b879942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 18 Sep 2020 14:28:08 +0200 Subject: [PATCH] core/execute: fall back to execve() for scripts fexecve() fails with ENOENT and we need a fallback. Add appropriate test. --- src/core/execute.c | 3 +-- src/shared/exec-util.c | 20 ++++++++++++++++++++ src/shared/exec-util.h | 2 ++ src/test/test-path-util.c | 9 +++++++-- test/meson.build | 2 ++ test/test-path-util/script.sh | 6 ++++++ 6 files changed, 38 insertions(+), 4 deletions(-) create mode 100755 test/test-path-util/script.sh diff --git a/src/core/execute.c b/src/core/execute.c index f1d6f87755..11e172f61b 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -4578,8 +4578,7 @@ static int exec_child( } } - fexecve(executable_fd, final_argv, accum_env); - r = -errno; + r = fexecve_or_execve(executable_fd, executable, final_argv, accum_env); if (exec_fd >= 0) { uint8_t hot = 0; diff --git a/src/shared/exec-util.c b/src/shared/exec-util.c index a93c206d9a..a0bb1567ce 100644 --- a/src/shared/exec-util.c +++ b/src/shared/exec-util.c @@ -443,3 +443,23 @@ ExecCommandFlags exec_command_flags_from_string(const char *s) { else return 1 << idx; } + +int fexecve_or_execve(int executable_fd, const char *executable, char *const argv[], char *const envp[]) { + fexecve(executable_fd, argv, envp); + if (errno == ENOENT) + /* A script? Let's fall back to execve(). + * + * fexecve(3): "If fd refers to a script (i.e., it is an executable text file that names a + * script interpreter with a first line that begins with the characters #!) and the + * close-on-exec flag has been set for fd, then fexecve() fails with the error ENOENT. This + * error occurs because, by the time the script interpreter is executed, fd has already been + * closed because of the close-on-exec flag. Thus, the close-on-exec flag can't be set on fd + * if it refers to a script." + * + * Unfortunately, if we unset close-on-exec, the script will be executed just fine, but (at + * least in case of bash) the script name, $0, will be shown as /dev/fd/nnn, which breaks + * scripts which make use of $0. Thus, let's fall back to execve() in this case. + */ + execve(executable, argv, envp); + return -errno; +} diff --git a/src/shared/exec-util.h b/src/shared/exec-util.h index 9fe9012516..65556249c1 100644 --- a/src/shared/exec-util.h +++ b/src/shared/exec-util.h @@ -45,3 +45,5 @@ extern const gather_stdout_callback_t gather_environment[_STDOUT_CONSUME_MAX]; const char* exec_command_flags_to_string(ExecCommandFlags i); ExecCommandFlags exec_command_flags_from_string(const char *s); + +int fexecve_or_execve(int executable_fd, const char *executable, char *const argv[], char *const envp[]); diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c index 3ee3795b48..c05a5d5b4a 100644 --- a/src/test/test-path-util.c +++ b/src/test/test-path-util.c @@ -4,6 +4,7 @@ #include #include "alloc-util.h" +#include "exec-util.h" #include "fd-util.h" #include "macro.h" #include "mountpoint-util.h" @@ -255,8 +256,8 @@ static void test_find_executable_exec_one(const char *path) { pid = fork(); assert_se(pid >= 0); if (pid == 0) { - fexecve(fd, STRV_MAKE(t, "--version"), STRV_MAKE(NULL)); - log_error_errno(errno, "fexecve: %m"); + r = fexecve_or_execve(fd, t, STRV_MAKE(t, "--version"), STRV_MAKE(NULL)); + log_error_errno(r, "[f]execve: %m"); _exit(EXIT_FAILURE); } @@ -268,6 +269,10 @@ static void test_find_executable_exec(void) { test_find_executable_exec_one("touch"); test_find_executable_exec_one("/bin/touch"); + + _cleanup_free_ char *script = NULL; + assert_se(get_testdata_dir("test-path-util/script.sh", &script) >= 0); + test_find_executable_exec_one(script); } static void test_prefixes(void) { diff --git a/test/meson.build b/test/meson.build index 5656abdf72..d9ff25f829 100644 --- a/test/meson.build +++ b/test/meson.build @@ -11,6 +11,8 @@ if install_tests install_dir : testdata_dir) install_subdir('test-path', install_dir : testdata_dir) + install_subdir('test-path-util', + install_dir : testdata_dir) install_subdir('test-umount', install_dir : testdata_dir) install_subdir('test-network-generator-conversion', diff --git a/test/test-path-util/script.sh b/test/test-path-util/script.sh new file mode 100755 index 0000000000..57c93e7476 --- /dev/null +++ b/test/test-path-util/script.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +echo "$0 $@" +test "$(basename $0)" = "script.sh" || exit 1 +test "$1" = "--version" || exit 2 +echo "Life is good"