]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/basic/fs-util.c
Merge pull request #15703 from poettering/homed-tweak-default-storage
[thirdparty/systemd.git] / src / basic / fs-util.c
index 3ed8e2c8a9371bf62865fbac4cebb0cc5493433b..e568c70684219870eb8497bf7daee180c6cb8950 100644 (file)
@@ -2,23 +2,23 @@
 
 #include <errno.h>
 #include <stddef.h>
-#include <stdio.h>
 #include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
 #include <linux/falloc.h>
 #include <linux/magic.h>
-#include <time.h>
 #include <unistd.h>
 
 #include "alloc-util.h"
+#include "blockdev-util.h"
 #include "dirent-util.h"
 #include "fd-util.h"
+#include "fileio.h"
 #include "fs-util.h"
 #include "locale-util.h"
 #include "log.h"
 #include "macro.h"
-#include "missing.h"
+#include "missing_fcntl.h"
+#include "missing_fs.h"
+#include "missing_syscall.h"
 #include "mkdir.h"
 #include "parse-util.h"
 #include "path-util.h"
@@ -274,6 +274,52 @@ int fchmod_and_chown(int fd, mode_t mode, uid_t uid, gid_t gid) {
         return do_chown || do_chmod;
 }
 
+int chmod_and_chown_unsafe(const char *path, mode_t mode, uid_t uid, gid_t gid) {
+        bool do_chown, do_chmod;
+        struct stat st;
+
+        assert(path);
+
+        /* Change ownership and access mode of the specified path, see description of fchmod_and_chown().
+         * Should only be used on trusted paths. */
+
+        if (lstat(path, &st) < 0)
+                return -errno;
+
+        do_chown =
+                (uid != UID_INVALID && st.st_uid != uid) ||
+                (gid != GID_INVALID && st.st_gid != gid);
+
+        do_chmod =
+                !S_ISLNK(st.st_mode) && /* chmod is not defined on symlinks */
+                ((mode != MODE_INVALID && ((st.st_mode ^ mode) & 07777) != 0) ||
+                 do_chown); /* If we change ownership, make sure we reset the mode afterwards, since chown()
+                             * modifies the access mode too */
+
+        if (mode == MODE_INVALID)
+                mode = st.st_mode; /* If we only shall do a chown(), save original mode, since chown() might break it. */
+        else if ((mode & S_IFMT) != 0 && ((mode ^ st.st_mode) & S_IFMT) != 0)
+                return -EINVAL; /* insist on the right file type if it was specified */
+
+        if (do_chown && do_chmod) {
+                mode_t minimal = st.st_mode & mode; /* the subset of the old and the new mask */
+
+                if (((minimal ^ st.st_mode) & 07777) != 0)
+                        if (chmod(path, minimal & 07777) < 0)
+                                return -errno;
+        }
+
+        if (do_chown)
+                if (lchown(path, uid, gid) < 0)
+                        return -errno;
+
+        if (do_chmod)
+                if (chmod(path, mode & 07777) < 0)
+                        return -errno;
+
+        return do_chown || do_chmod;
+}
+
 int fchmod_umask(int fd, mode_t m) {
         mode_t u;
         int r;
@@ -293,8 +339,15 @@ int fchmod_opath(int fd, mode_t m) {
          * fchownat() does. */
 
         xsprintf(procfs_path, "/proc/self/fd/%i", fd);
-        if (chmod(procfs_path, m) < 0)
-                return -errno;
+        if (chmod(procfs_path, m) < 0) {
+                if (errno != ENOENT)
+                        return -errno;
+
+                if (proc_mounted() == 0)
+                        return -ENOSYS; /* if we have no /proc/, the concept is not implementable */
+
+                return -ENOENT;
+        }
 
         return 0;
 }
@@ -648,16 +701,30 @@ int unlink_or_warn(const char *filename) {
 
 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;
+        int wd;
 
         /* This is like inotify_add_watch(), except that the file to watch is not referenced by a path, but by an fd */
         xsprintf(path, "/proc/self/fd/%i", what);
 
-        r = inotify_add_watch(fd, path, mask);
-        if (r < 0)
+        wd = inotify_add_watch(fd, path, mask);
+        if (wd < 0)
                 return -errno;
 
-        return r;
+        return wd;
+}
+
+int inotify_add_watch_and_warn(int fd, const char *pathname, uint32_t mask) {
+        int wd;
+
+        wd = inotify_add_watch(fd, pathname, mask);
+        if (wd < 0) {
+                if (errno == ENOSPC)
+                        return log_error_errno(errno, "Failed to add a watch for %s: inotify watch limit reached", pathname);
+
+                return log_error_errno(errno, "Failed to add a watch for %s: %m", pathname);
+        }
+
+        return wd;
 }
 
 static bool unsafe_transition(const struct stat *a, const struct stat *b) {
@@ -698,7 +765,7 @@ static int log_autofs_mount_point(int fd, const char *path, unsigned flags) {
                                  n1, path);
 }
 
-int chase_symlinks(const char *path, const char *original_root, unsigned flags, char **ret) {
+int chase_symlinks(const char *path, const char *original_root, unsigned flags, char **ret_path, int *ret_fd) {
         _cleanup_free_ char *buffer = NULL, *done = NULL, *root = NULL;
         _cleanup_close_ int fd = -1;
         unsigned max_follow = CHASE_SYMLINKS_MAX; /* how many symlinks to follow before giving up and returning ELOOP */
@@ -710,10 +777,10 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
         assert(path);
 
         /* Either the file may be missing, or we return an fd to the final object, but both make no sense */
-        if (FLAGS_SET(flags, CHASE_NONEXISTENT | CHASE_OPEN))
+        if ((flags & CHASE_NONEXISTENT) && ret_fd)
                 return -EINVAL;
 
-        if (FLAGS_SET(flags, CHASE_STEP | CHASE_OPEN))
+        if ((flags & CHASE_STEP) && ret_fd)
                 return -EINVAL;
 
         if (isempty(path))
@@ -732,24 +799,24 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
          * 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.
+         * to 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.
          *
-         * There are three ways to invoke this function:
+         * There are five ways to invoke this function:
          *
-         * 1. Without CHASE_STEP or CHASE_OPEN: in this case the path is resolved and the normalized path is returned
-         *    in `ret`. The return value is < 0 on error. If CHASE_NONEXISTENT is also set 0 is returned if the file
-         *    doesn't exist, > 0 otherwise. If CHASE_NONEXISTENT is not set >= 0 is returned if the destination was
-         *    found, -ENOENT if it doesn't.
+         * 1. Without CHASE_STEP or ret_fd: in this case the path is resolved and the normalized path is
+         *    returned in `ret_path`. The return value is < 0 on error. If CHASE_NONEXISTENT is also set, 0
+         *    is returned if the file doesn't exist, > 0 otherwise. If CHASE_NONEXISTENT is not set, >= 0 is
+         *    returned if the destination was found, -ENOENT if it wasn't.
          *
-         * 2. With CHASE_OPEN: in this case the destination is opened after chasing it as O_PATH and this file
+         * 2. With ret_fd: in this case the destination is opened after chasing it as O_PATH and this file
          *    descriptor is returned as return value. This is useful to open files relative to some root
          *    directory. Note that the returned O_PATH file descriptors must be converted into a regular one (using
-         *    fd_reopen() or such) before it can be used for reading/writing. CHASE_OPEN may not be combined with
+         *    fd_reopen() or such) before it can be used for reading/writing. ret_fd may not be combined with
          *    CHASE_NONEXISTENT.
          *
          * 3. With CHASE_STEP: in this case only a single step of the normalization is executed, i.e. only the first
@@ -760,26 +827,26 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
          *
          * 4. With CHASE_SAFE: in this case the path must not contain unsafe transitions, i.e. transitions from
          *    unprivileged to privileged files or directories. In such cases the return value is -ENOLINK. If
-         *    CHASE_WARN is also set a warning describing the unsafe transition is emitted.
-         *
-         * 5. With CHASE_NO_AUTOFS: in this case if an autofs mount point is encountered, the path normalization is
-         *    aborted and -EREMOTE is returned. If CHASE_WARN is also set a warning showing the path of the mount point
-         *    is emitted.
+         *    CHASE_WARN is also set, a warning describing the unsafe transition is emitted.
          *
-         * */
+         * 5. With CHASE_NO_AUTOFS: in this case if an autofs mount point is encountered, path normalization
+         *    is aborted and -EREMOTE is returned. If CHASE_WARN is also set, a warning showing the path of
+         *    the mount point is emitted.
+         */
 
         /* A root directory of "/" or "" is identical to none */
         if (empty_or_root(original_root))
                 original_root = NULL;
 
-        if (!original_root && !ret && (flags & (CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_OPEN|CHASE_STEP)) == CHASE_OPEN) {
-                /* Shortcut the CHASE_OPEN case if the caller isn't interested in the actual path and has no root set
+        if (!original_root && !ret_path && !(flags & (CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_STEP)) && ret_fd) {
+                /* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root set
                  * and doesn't care about any of the other special features we provide either. */
                 r = open(path, O_PATH|O_CLOEXEC|((flags & CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
                 if (r < 0)
                         return -errno;
 
-                return r;
+                *ret_fd = r;
+                return 0;
         }
 
         if (original_root) {
@@ -787,8 +854,15 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                 if (r < 0)
                         return r;
 
-                if (flags & CHASE_PREFIX_ROOT) {
+                /* Simplify the root directory, so that it has no duplicate slashes and nothing at the
+                 * end. While we won't resolve the root path we still simplify it. Note that dropping the
+                 * trailing slash should not change behaviour, since when opening it we specify O_DIRECTORY
+                 * anyway. Moreover at the end of this function after processing everything we'll always turn
+                 * the empty string back to "/". */
+                delete_trailing_chars(root, "/");
+                path_simplify(root, true);
 
+                if (flags & CHASE_PREFIX_ROOT) {
                         /* We don't support relative paths in combination with a root directory */
                         if (!path_is_absolute(path))
                                 return -EINVAL;
@@ -801,7 +875,7 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
         if (r < 0)
                 return r;
 
-        fd = open("/", O_CLOEXEC|O_NOFOLLOW|O_PATH);
+        fd = open(root ?: "/", O_CLOEXEC|O_DIRECTORY|O_PATH);
         if (fd < 0)
                 return -errno;
 
@@ -810,6 +884,31 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                         return -errno;
         }
 
+        if (root) {
+                _cleanup_free_ char *absolute = NULL;
+                const char *e;
+
+                /* If we are operating on a root directory, let's take the root directory as it is. */
+
+                e = path_startswith(buffer, root);
+                if (!e)
+                        return log_full_errno(flags & CHASE_WARN ? LOG_WARNING : LOG_DEBUG,
+                                              SYNTHETIC_ERRNO(ECHRNG),
+                                              "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
+                                              path, root);
+
+                done = strdup(root);
+                if (!done)
+                        return -ENOMEM;
+
+                /* Make sure "todo" starts with a slash */
+                absolute = strjoin("/", e);
+                if (!absolute)
+                        return -ENOMEM;
+
+                free_and_replace(buffer, absolute);
+        }
+
         todo = buffer;
         for (;;) {
                 _cleanup_free_ char *first = NULL;
@@ -819,6 +918,15 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
 
                 /* Determine length of first component in the path */
                 n = strspn(todo, "/");                  /* The slashes */
+
+                if (n > 1) {
+                        /* If we are looking at more than a single slash then skip all but one, so that when
+                         * we are done with everything we have a normalized path with only single slashes
+                         * separating the path components. */
+                        todo += n - 1;
+                        n = 1;
+                }
+
                 m = n + strcspn(todo + n, "/");         /* The entire length of the component */
 
                 /* Extract the first component. */
@@ -921,7 +1029,6 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                 if (fstat(child, &st) < 0)
                         return -errno;
                 if ((flags & CHASE_SAFE) &&
-                    (empty_or_root(root) || (size_t)(todo - buffer) > strlen(root)) &&
                     unsafe_transition(&previous_stat, &st))
                         return log_unsafe_transition(fd, child, path, flags);
 
@@ -933,7 +1040,6 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
 
                 if (S_ISLNK(st.st_mode) && !((flags & CHASE_NOFOLLOW) && isempty(todo))) {
                         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
@@ -953,7 +1059,7 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                                  * directory as base. */
 
                                 safe_close(fd);
-                                fd = open(root ?: "/", O_CLOEXEC|O_NOFOLLOW|O_PATH);
+                                fd = open(root ?: "/", O_CLOEXEC|O_DIRECTORY|O_PATH);
                                 if (fd < 0)
                                         return -errno;
 
@@ -1019,15 +1125,15 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                         return -ENOMEM;
         }
 
-        if (ret)
-                *ret = TAKE_PTR(done);
+        if (ret_path)
+                *ret_path = TAKE_PTR(done);
 
-        if (flags & CHASE_OPEN) {
-                /* Return the O_PATH fd we currently are looking to the caller. It can translate it to a proper fd by
-                 * opening /proc/self/fd/xyz. */
+        if (ret_fd) {
+                /* Return the O_PATH fd we currently are looking to the caller. It can translate it to a
+                 * proper fd by opening /proc/self/fd/xyz. */
 
                 assert(fd >= 0);
-                return TAKE_FD(fd);
+                *ret_fd = TAKE_FD(fd);
         }
 
         if (flags & CHASE_STEP)
@@ -1036,14 +1142,14 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
         return exists;
 
 chased_one:
-        if (ret) {
+        if (ret_path) {
                 char *c;
 
                 c = strjoin(strempty(done), todo);
                 if (!c)
                         return -ENOMEM;
 
-                *ret = c;
+                *ret_path = c;
         }
 
         return 0;
@@ -1072,9 +1178,10 @@ int chase_symlinks_and_open(
                 return r;
         }
 
-        path_fd = chase_symlinks(path, root, chase_flags|CHASE_OPEN, ret_path ? &p : NULL);
-        if (path_fd < 0)
-                return path_fd;
+        r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
+        if (r < 0)
+                return r;
+        assert(path_fd >= 0);
 
         r = fd_reopen(path_fd, open_flags);
         if (r < 0)
@@ -1097,6 +1204,7 @@ int chase_symlinks_and_opendir(
         _cleanup_close_ int path_fd = -1;
         _cleanup_free_ char *p = NULL;
         DIR *d;
+        int r;
 
         if (!ret_dir)
                 return -EINVAL;
@@ -1113,9 +1221,10 @@ int chase_symlinks_and_opendir(
                 return 0;
         }
 
-        path_fd = chase_symlinks(path, root, chase_flags|CHASE_OPEN, ret_path ? &p : NULL);
-        if (path_fd < 0)
-                return path_fd;
+        r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
+        if (r < 0)
+                return r;
+        assert(path_fd >= 0);
 
         xsprintf(procfs_path, "/proc/self/fd/%i", path_fd);
         d = opendir(procfs_path);
@@ -1134,10 +1243,12 @@ int chase_symlinks_and_stat(
                 const char *root,
                 unsigned chase_flags,
                 char **ret_path,
-                struct stat *ret_stat) {
+                struct stat *ret_stat,
+                int *ret_fd) {
 
         _cleanup_close_ int path_fd = -1;
         _cleanup_free_ char *p = NULL;
+        int r;
 
         assert(path);
         assert(ret_stat);
@@ -1153,18 +1264,18 @@ int chase_symlinks_and_stat(
                 return 1;
         }
 
-        path_fd = chase_symlinks(path, root, chase_flags|CHASE_OPEN, ret_path ? &p : NULL);
-        if (path_fd < 0)
-                return path_fd;
+        r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
+        if (r < 0)
+                return r;
+        assert(path_fd >= 0);
 
         if (fstat(path_fd, ret_stat) < 0)
                 return -errno;
 
         if (ret_path)
                 *ret_path = TAKE_PTR(p);
-
-        if (chase_flags & CHASE_OPEN)
-                return TAKE_FD(path_fd);
+        if (ret_fd)
+                *ret_fd = TAKE_FD(path_fd);
 
         return 1;
 }
@@ -1298,6 +1409,17 @@ int fsync_directory_of_file(int fd) {
         return 0;
 }
 
+int fsync_full(int fd) {
+        int r, q;
+
+        /* Sync both the file and the directory */
+
+        r = fsync(fd) < 0 ? -errno : 0;
+        q = fsync_directory_of_file(fd);
+
+        return r < 0 ? r : q;
+}
+
 int fsync_path_at(int at_fd, const char *path) {
         _cleanup_close_ int opened_fd = -1;
         int fd;
@@ -1368,3 +1490,26 @@ int open_parent(const char *path, int flags, mode_t mode) {
 
         return fd;
 }
+
+int path_is_encrypted(const char *path) {
+        _cleanup_free_ char *uuids = NULL;
+        char p[SYS_BLOCK_PATH_MAX("/dm/uuid")];
+        dev_t devt;
+        int r;
+
+        r = get_block_device(path, &devt);
+        if (r < 0)
+                return r;
+        if (r == 0) /* doesn't have a block device */
+                return false;
+
+        xsprintf_sys_block_path(p, "/dm/uuid", devt);
+        r = read_one_line_file(p, &uuids);
+        if (r == -ENOENT)
+                return false;
+        if (r < 0)
+                return r;
+
+        /* The DM device's uuid attribute is prefixed with "CRYPT-" if this is a dm-crypt device. */
+        return !!startswith(uuids, "CRYPT-");
+}