chiark / gitweb /
Prep v233: Add missing updates from upstream in src/basic
[elogind.git] / src / basic / fs-util.c
index 0d96302bef5c5d9979da838b6a038718d104d664..eefdbb52d84243c9f6c3d4a091d4a88df2e621c2 100644 (file)
@@ -17,7 +17,6 @@
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
-#include <dirent.h>
 #include <errno.h>
 #include <stddef.h>
 #include <stdio.h>
@@ -38,6 +37,7 @@
 #include "mkdir.h"
 #include "parse-util.h"
 #include "path-util.h"
+#include "stat-util.h"
 #include "string-util.h"
 #include "strv.h"
 //#include "time-util.h"
@@ -236,7 +236,7 @@ int readlink_and_canonicalize(const char *p, const char *root, char **ret) {
         if (r < 0)
                 return r;
 
-        r = chase_symlinks(t, root, &s);
+        r = chase_symlinks(t, root, 0, &s);
         if (r < 0)
                 /* If we can't follow up, then let's return the original string, slightly cleaned up. */
                 *ret = path_kill_slashes(t);
@@ -450,6 +450,7 @@ int mkfifo_atomic(const char *path, mode_t mode) {
 
 int get_files_in_directory(const char *path, char ***list) {
         _cleanup_closedir_ DIR *d = NULL;
+        struct dirent *de;
         size_t bufsize = 0, n = 0;
         _cleanup_strv_free_ char **l = NULL;
 
@@ -463,16 +464,7 @@ int get_files_in_directory(const char *path, char ***list) {
         if (!d)
                 return -errno;
 
-        for (;;) {
-                struct dirent *de;
-
-                errno = 0;
-                de = readdir(d);
-                if (!de && errno > 0)
-                        return -errno;
-                if (!de)
-                        break;
-
+        FOREACH_DIRENT_ALL(de, d, return -errno) {
                 dirent_ensure_type(d, de);
 
                 if (!dirent_is_file(de))
@@ -500,7 +492,6 @@ int get_files_in_directory(const char *path, char ***list) {
         return n;
 }
 
-#if 0 /// UNNEEDED by elogind
 static int getenv_tmp_dir(const char **ret_path) {
         const char *n;
         int r, ret = 0;
@@ -571,6 +562,7 @@ static int tmp_dir_internal(const char *def, const char **ret) {
         return 0;
 }
 
+#if 0 /// UNNEEDED by elogind
 int var_tmp_dir(const char **ret) {
 
         /* Returns the location for "larger" temporary files, that is backed by physical storage if available, and thus
@@ -580,6 +572,7 @@ int var_tmp_dir(const char **ret) {
 
         return tmp_dir_internal("/var/tmp", ret);
 }
+#endif // 0
 
 int tmp_dir(const char **ret) {
 
@@ -589,6 +582,7 @@ int tmp_dir(const char **ret) {
         return tmp_dir_internal("/tmp", ret);
 }
 
+#if 0 /// UNNEEDED by elogind
 int inotify_add_watch_fd(int fd, int what, uint32_t mask) {
         char path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1];
         int r;
@@ -602,11 +596,13 @@ int inotify_add_watch_fd(int fd, int what, uint32_t mask) {
 
         return r;
 }
+#endif // 0
 
-int chase_symlinks(const char *path, const char *_root, char **ret) {
+int chase_symlinks(const char *path, const char *original_root, unsigned flags, char **ret) {
         _cleanup_free_ char *buffer = NULL, *done = NULL, *root = NULL;
         _cleanup_close_ int fd = -1;
         unsigned max_follow = 32; /* how many symlinks to follow before giving up and returning ELOOP */
+        bool exists = true;
         char *todo;
         int r;
 
@@ -615,26 +611,39 @@ int chase_symlinks(const char *path, const char *_root, char **ret) {
         /* This is a lot like canonicalize_file_name(), but takes an additional "root" parameter, that allows following
          * symlinks relative to a root directory, instead of the root of the host.
          *
-         * Note that "root" matters only if we encounter an absolute symlink, it's unused otherwise. Most importantly
-         * this means the path parameter passed in is not prefixed by it.
+         * Note that "root" primarily matters if we encounter an absolute symlink. It is also used when following
+         * relative symlinks to ensure they cannot be used to "escape" the root directory. The path parameter passed is
+         * assumed to be already prefixed by it, except if the CHASE_PREFIX_ROOT flag is set, in which case it is first
+         * prefixed accordingly.
          *
          * Algorithmically this operates on two path buffers: "done" are the components of the path we already
          * processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we still need to
          * process. On each iteration, we move one component from "todo" to "done", processing it's special meaning
          * each time. The "todo" path always starts with at least one slash, the "done" path always ends in no
          * slash. We always keep an O_PATH fd to the component we are currently processing, thus keeping lookup races
-         * at a minimum. */
-
-        r = path_make_absolute_cwd(path, &buffer);
-        if (r < 0)
-                return r;
+         * at a minimum.
+         *
+         * Suggested usage: whenever you want to canonicalize a path, use this function. Pass the absolute path you got
+         * as-is: fully qualified and relative to your host's root. Optionally, specify the root parameter to tell this
+         * function what to do when encountering a symlink with an absolute path as directory: prefix it by the
+         * specified path.
+         *
+         * Note: there's also chase_symlinks_prefix() (see below), which as first step prefixes the passed path by the
+         * passed root. */
 
-        if (_root) {
-                r = path_make_absolute_cwd(_root, &root);
+        if (original_root) {
+                r = path_make_absolute_cwd(original_root, &root);
                 if (r < 0)
                         return r;
+
+                if (flags & CHASE_PREFIX_ROOT)
+                        path = prefix_roota(root, path);
         }
 
+        r = path_make_absolute_cwd(path, &buffer);
+        if (r < 0)
+                return r;
+
         fd = open("/", O_CLOEXEC|O_NOFOLLOW|O_PATH);
         if (fd < 0)
                 return -errno;
@@ -670,18 +679,20 @@ int chase_symlinks(const char *path, const char *_root, char **ret) {
                         _cleanup_free_ char *parent = NULL;
                         int fd_parent = -1;
 
+                        /* If we already are at the top, then going up will not change anything. This is in-line with
+                         * how the kernel handles this. */
                         if (isempty(done) || path_equal(done, "/"))
-                                return -EINVAL;
+                                continue;
 
                         parent = dirname_malloc(done);
                         if (!parent)
                                 return -ENOMEM;
 
-                        /* Don't allow this to leave the root dir */
+                        /* Don't allow this to leave the root dir */
                         if (root &&
                             path_startswith(done, root) &&
                             !path_startswith(parent, root))
-                                return -EINVAL;
+                                continue;
 
                         free_and_replace(done, parent);
 
@@ -697,13 +708,32 @@ int chase_symlinks(const char *path, const char *_root, char **ret) {
 
                 /* Otherwise let's see what this is. */
                 child = openat(fd, first + n, O_CLOEXEC|O_NOFOLLOW|O_PATH);
-                if (child < 0)
+                if (child < 0) {
+
+                        if (errno == ENOENT &&
+                            (flags & CHASE_NONEXISTENT) &&
+                            (isempty(todo) || path_is_safe(todo))) {
+
+                                /* If CHASE_NONEXISTENT is set, and the path does not exist, then that's OK, return
+                                 * what we got so far. But don't allow this if the remaining path contains "../ or "./"
+                                 * or something else weird. */
+
+                                if (!strextend(&done, first, todo, NULL))
+                                        return -ENOMEM;
+
+                                exists = false;
+                                break;
+                        }
+
                         return -errno;
+                }
 
                 if (fstat(child, &st) < 0)
                         return -errno;
 
                 if (S_ISLNK(st.st_mode)) {
+                        char *joined;
+
                         _cleanup_free_ char *destination = NULL;
 
                         /* This is a symlink, in this case read the destination. But let's make sure we don't follow
@@ -727,9 +757,6 @@ int chase_symlinks(const char *path, const char *_root, char **ret) {
                                 if (fd < 0)
                                         return -errno;
 
-                                free_and_replace(buffer, destination);
-
-                                todo = buffer;
                                 free(done);
 
                                 /* Note that we do not revalidate the root, we take it as is. */
@@ -741,19 +768,17 @@ int chase_symlinks(const char *path, const char *_root, char **ret) {
                                                 return -ENOMEM;
                                 }
 
-                        } else {
-                                char *joined;
+                        }
 
-                                /* A relative destination. If so, this is what we'll prefix what's left to do with what
-                                 * we just read, and start the loop again, but remain in the current directory. */
+                        /* Prefix what's left to do with what we just read, and start the loop again,
+                         * but remain in the current directory. */
 
-                                joined = strjoin("/", destination, todo);
-                                if (!joined)
-                                        return -ENOMEM;
+                        joined = strjoin("/", destination, todo);
+                        if (!joined)
+                                return -ENOMEM;
 
-                                free(buffer);
-                                todo = buffer = joined;
-                        }
+                        free(buffer);
+                        todo = buffer = joined;
 
                         continue;
                 }
@@ -780,9 +805,10 @@ int chase_symlinks(const char *path, const char *_root, char **ret) {
                         return -ENOMEM;
         }
 
-        *ret = done;
-        done = NULL;
+        if (ret) {
+                *ret = done;
+                done = NULL;
+        }
 
-        return 0;
+        return exists;
 }
-#endif // 0