]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
basic/path-util: let find_executable_full() optionally return an fd
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Thu, 17 Sep 2020 13:02:47 +0000 (15:02 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Fri, 6 Nov 2020 14:12:54 +0000 (15:12 +0100)
src/basic/path-util.c
src/basic/path-util.h
src/core/execute.c
src/test/test-path-util.c

index a36cf8332c2df79fa1557bbe0d32138a886fed19..c14d885d1a7790dc1a18a773cd0e35ee99939b53 100644 (file)
@@ -585,22 +585,53 @@ char* path_join_internal(const char *first, ...) {
         return joined;
 }
 
-int find_executable_full(const char *name, bool use_path_envvar, char **ret) {
+static int check_x_access(const char *path, int *ret_fd) {
+        if (ret_fd) {
+                _cleanup_close_ int fd = -1;
+                int r;
+
+                /* We need to use O_PATH because there may be executables for which we have only exec
+                 * permissions, but not read (usually suid executables). */
+                fd = open(path, O_PATH|O_CLOEXEC);
+                if (fd < 0)
+                        return -errno;
+
+                r = access_fd(fd, X_OK);
+                if (r < 0)
+                        return r;
+
+                *ret_fd = TAKE_FD(fd);
+        } else {
+                /* Let's optimize things a bit by not opening the file if we don't need the fd. */
+                if (access(path, X_OK) < 0)
+                        return -errno;
+        }
+
+        return 0;
+}
+
+int find_executable_full(const char *name, bool use_path_envvar, char **ret_filename, int *ret_fd) {
         int last_error, r;
         const char *p = NULL;
 
         assert(name);
 
         if (is_path(name)) {
-                if (access(name, X_OK) < 0)
-                        return -errno;
+                _cleanup_close_ int fd = -1;
 
-                if (ret) {
-                        r = path_make_absolute_cwd(name, ret);
+                r = check_x_access(name, ret_fd ? &fd : NULL);
+                if (r < 0)
+                        return r;
+
+                if (ret_filename) {
+                        r = path_make_absolute_cwd(name, ret_filename);
                         if (r < 0)
                                 return r;
                 }
 
+                if (ret_fd)
+                        *ret_fd = TAKE_FD(fd);
+
                 return 0;
         }
 
@@ -613,8 +644,10 @@ int find_executable_full(const char *name, bool use_path_envvar, char **ret) {
 
         last_error = -ENOENT;
 
+        /* Resolve a single-component name to a full path */
         for (;;) {
                 _cleanup_free_ char *j = NULL, *element = NULL;
+                _cleanup_close_ int fd = -1;
 
                 r = extract_first_word(&p, &element, ":", EXTRACT_RELAX|EXTRACT_DONT_COALESCE_SEPARATORS);
                 if (r < 0)
@@ -629,7 +662,8 @@ int find_executable_full(const char *name, bool use_path_envvar, char **ret) {
                 if (!j)
                         return -ENOMEM;
 
-                if (access(j, X_OK) >= 0) {
+                r = check_x_access(j, ret_fd ? &fd : NULL);
+                if (r >= 0) {
                         _cleanup_free_ char *with_dash;
 
                         with_dash = strjoin(j, "/");
@@ -643,8 +677,10 @@ int find_executable_full(const char *name, bool use_path_envvar, char **ret) {
                         /* We can't just `continue` inverting this case, since we need to update last_error. */
                         if (errno == ENOTDIR) {
                                 /* Found it! */
-                                if (ret)
-                                        *ret = path_simplify(TAKE_PTR(j), false);
+                                if (ret_filename)
+                                        *ret_filename = path_simplify(TAKE_PTR(j), false);
+                                if (ret_fd)
+                                        *ret_fd = TAKE_FD(fd);
 
                                 return 0;
                         }
index bd8c14903e54094761f71121a71e5eb6e28f33a5..13e87731a6bab71f241f5a18136429b05e596ba3 100644 (file)
@@ -88,9 +88,9 @@ int path_strv_make_absolute_cwd(char **l);
 char** path_strv_resolve(char **l, const char *root);
 char** path_strv_resolve_uniq(char **l, const char *root);
 
-int find_executable_full(const char *name, bool use_path_envvar, char **ret);
-static inline int find_executable(const char *name, char **ret) {
-        return find_executable_full(name, true, ret);
+int find_executable_full(const char *name, bool use_path_envvar, char **ret_filename, int *ret_fd);
+static inline int find_executable(const char *name, char **ret_filename) {
+        return find_executable_full(name, true, ret_filename, NULL);
 }
 
 bool paths_check_timestamp(const char* const* paths, usec_t *paths_ts_usec, bool update);
index a78954c0c3f1d583fa0257b754afef79862d193c..bdd6857fe103a04ffdffdc3de887a551dd1f04d7 100644 (file)
@@ -4230,7 +4230,7 @@ static int exec_child(
          * shall execute. */
 
         _cleanup_free_ char *executable = NULL;
-        r = find_executable_full(command->path, false, &executable);
+        r = find_executable_full(command->path, false, &executable, NULL);
         if (r < 0) {
                 if (r != -ENOMEM && (command->flags & EXEC_COMMAND_IGNORE_FAILURE)) {
                         log_struct_errno(LOG_INFO, r,
index e98c19dd6cc5e2bc7f512f46bd585261cfa22c76..3ee3795b486ab9cea65ba432783f2690853ce41f 100644 (file)
@@ -8,6 +8,7 @@
 #include "macro.h"
 #include "mountpoint-util.h"
 #include "path-util.h"
+#include "process-util.h"
 #include "rm-rf.h"
 #include "stat-util.h"
 #include "string-util.h"
@@ -169,12 +170,12 @@ static void test_find_executable_full(void) {
 
         log_info("/* %s */", __func__);
 
-        assert_se(find_executable_full("sh", true, &p) == 0);
+        assert_se(find_executable_full("sh", true, &p, NULL) == 0);
         puts(p);
         assert_se(streq(basename(p), "sh"));
         free(p);
 
-        assert_se(find_executable_full("sh", false, &p) == 0);
+        assert_se(find_executable_full("sh", false, &p, NULL) == 0);
         puts(p);
         assert_se(streq(basename(p), "sh"));
         free(p);
@@ -186,12 +187,12 @@ static void test_find_executable_full(void) {
 
         assert_se(unsetenv("PATH") >= 0);
 
-        assert_se(find_executable_full("sh", true, &p) == 0);
+        assert_se(find_executable_full("sh", true, &p, NULL) == 0);
         puts(p);
         assert_se(streq(basename(p), "sh"));
         free(p);
 
-        assert_se(find_executable_full("sh", false, &p) == 0);
+        assert_se(find_executable_full("sh", false, &p, NULL) == 0);
         puts(p);
         assert_se(streq(basename(p), "sh"));
         free(p);
@@ -236,6 +237,39 @@ static void test_find_executable(const char *self) {
         assert_se(find_executable("/proc/filesystems", &p) == -EACCES);
 }
 
+static void test_find_executable_exec_one(const char *path) {
+        _cleanup_free_ char *t = NULL;
+        _cleanup_close_ int fd = -1;
+        pid_t pid;
+        int r;
+
+        r = find_executable_full(path, false, &t, &fd);
+
+        log_info_errno(r, "%s: %s → %s: %d/%m", __func__, path, t ?: "-", fd);
+
+        assert_se(fd > STDERR_FILENO);
+        assert_se(path_is_absolute(t));
+        if (path_is_absolute(path))
+                assert_se(streq(t, 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");
+                _exit(EXIT_FAILURE);
+        }
+
+        assert_se(wait_for_terminate_and_check(t, pid, WAIT_LOG) == 0);
+}
+
+static void test_find_executable_exec(void) {
+        log_info("/* %s */", __func__);
+
+        test_find_executable_exec_one("touch");
+        test_find_executable_exec_one("/bin/touch");
+}
+
 static void test_prefixes(void) {
         static const char* const values[] = {
                 "/a/b/c/d",
@@ -717,6 +751,7 @@ int main(int argc, char **argv) {
         test_path_equal_root();
         test_find_executable_full();
         test_find_executable(argv[0]);
+        test_find_executable_exec();
         test_prefixes();
         test_path_join();
         test_fsck_exists();