fexecve() fails with ENOENT and we need a fallback. Add appropriate test.
}
}
- 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;
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;
+}
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[]);
#include <unistd.h>
#include "alloc-util.h"
+#include "exec-util.h"
#include "fd-util.h"
#include "macro.h"
#include "mountpoint-util.h"
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);
}
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) {
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',
--- /dev/null
+#!/bin/sh
+
+echo "$0 $@"
+test "$(basename $0)" = "script.sh" || exit 1
+test "$1" = "--version" || exit 2
+echo "Life is good"