]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/basic/fs-util.c
tree-wide: avoid some loaded terms
[thirdparty/systemd.git] / src / basic / fs-util.c
index e568c70684219870eb8497bf7daee180c6cb8950..34a2260783bb1c94dff6b65710d9673b1084f114 100644 (file)
@@ -23,6 +23,7 @@
 #include "parse-util.h"
 #include "path-util.h"
 #include "process-util.h"
+#include "random-util.h"
 #include "stat-util.h"
 #include "stdio-util.h"
 #include "string-util.h"
@@ -352,28 +353,38 @@ int fchmod_opath(int fd, mode_t m) {
         return 0;
 }
 
-int fd_warn_permissions(const char *path, int fd) {
-        struct stat st;
-
-        if (fstat(fd, &st) < 0)
-                return -errno;
+int stat_warn_permissions(const char *path, const struct stat *st) {
+        assert(path);
+        assert(st);
 
         /* Don't complain if we are reading something that is not a file, for example /dev/null */
-        if (!S_ISREG(st.st_mode))
+        if (!S_ISREG(st->st_mode))
                 return 0;
 
-        if (st.st_mode & 0111)
+        if (st->st_mode & 0111)
                 log_warning("Configuration file %s is marked executable. Please remove executable permission bits. Proceeding anyway.", path);
 
-        if (st.st_mode & 0002)
+        if (st->st_mode & 0002)
                 log_warning("Configuration file %s is marked world-writable. Please remove world writability permission bits. Proceeding anyway.", path);
 
-        if (getpid_cached() == 1 && (st.st_mode & 0044) != 0044)
+        if (getpid_cached() == 1 && (st->st_mode & 0044) != 0044)
                 log_warning("Configuration file %s is marked world-inaccessible. This has no effect as configuration data is accessible via APIs without restrictions. Proceeding anyway.", path);
 
         return 0;
 }
 
+int fd_warn_permissions(const char *path, int fd) {
+        struct stat st;
+
+        assert(path);
+        assert(fd >= 0);
+
+        if (fstat(fd, &st) < 0)
+                return -errno;
+
+        return stat_warn_permissions(path, &st);
+}
+
 int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode) {
         char fdpath[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
         _cleanup_close_ int fd = -1;
@@ -1303,11 +1314,13 @@ void unlink_tempfilep(char (*p)[]) {
                 (void) unlink_noerrno(*p);
 }
 
-int unlinkat_deallocate(int fd, const char *name, int flags) {
+int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) {
         _cleanup_close_ int truncate_fd = -1;
         struct stat st;
         off_t l, bs;
 
+        assert((flags & ~(UNLINK_REMOVEDIR|UNLINK_ERASE)) == 0);
+
         /* Operates like unlinkat() but also deallocates the file contents if it is a regular file and there's no other
          * link to it. This is useful to ensure that other processes that might have the file open for reading won't be
          * able to keep the data pinned on disk forever. This call is particular useful whenever we execute clean-up
@@ -1324,7 +1337,7 @@ int unlinkat_deallocate(int fd, const char *name, int flags) {
          * Note that we attempt deallocation, but failure to succeed with that is not considered fatal, as long as the
          * primary job â€“ to delete the file â€“ is accomplished. */
 
-        if ((flags & AT_REMOVEDIR) == 0) {
+        if (!FLAGS_SET(flags, UNLINK_REMOVEDIR)) {
                 truncate_fd = openat(fd, name, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|O_NONBLOCK);
                 if (truncate_fd < 0) {
 
@@ -1340,7 +1353,7 @@ int unlinkat_deallocate(int fd, const char *name, int flags) {
                 }
         }
 
-        if (unlinkat(fd, name, flags) < 0)
+        if (unlinkat(fd, name, FLAGS_SET(flags, UNLINK_REMOVEDIR) ? AT_REMOVEDIR : 0) < 0)
                 return -errno;
 
         if (truncate_fd < 0) /* Don't have a file handle, can't do more â˜šī¸ */
@@ -1351,7 +1364,45 @@ int unlinkat_deallocate(int fd, const char *name, int flags) {
                 return 0;
         }
 
-        if (!S_ISREG(st.st_mode) || st.st_blocks == 0 || st.st_nlink > 0)
+        if (!S_ISREG(st.st_mode))
+                return 0;
+
+        if (FLAGS_SET(flags, UNLINK_ERASE) && st.st_size > 0 && st.st_nlink == 0) {
+                uint64_t left = st.st_size;
+                char buffer[64 * 1024];
+
+                /* If erasing is requested, let's overwrite the file with random data once before deleting
+                 * it. This isn't going to give you shred(1) semantics, but hopefully should be good enough
+                 * for stuff backed by tmpfs at least.
+                 *
+                 * Note that we only erase like this if the link count of the file is zero. If it is higher it
+                 * is still linked by someone else and we'll leave it to them to remove it securely
+                 * eventually! */
+
+                random_bytes(buffer, sizeof(buffer));
+
+                while (left > 0) {
+                        ssize_t n;
+
+                        n = write(truncate_fd, buffer, MIN(sizeof(buffer), left));
+                        if (n < 0) {
+                                log_debug_errno(errno, "Failed to erase data in file '%s', ignoring.", name);
+                                break;
+                        }
+
+                        assert(left >= (size_t) n);
+                        left -= n;
+                }
+
+                /* Let's refresh metadata */
+                if (fstat(truncate_fd, &st) < 0) {
+                        log_debug_errno(errno, "Failed to stat file '%s' for deallocation, ignoring: %m", name);
+                        return 0;
+                }
+        }
+
+        /* Don't dallocate if there's nothing to deallocate or if the file is linked elsewhere */
+        if (st.st_blocks == 0 || st.st_nlink > 0)
                 return 0;
 
         /* If this is a regular file, it actually took up space on disk and there are no other links it's time to
@@ -1491,9 +1542,77 @@ int open_parent(const char *path, int flags, mode_t mode) {
         return fd;
 }
 
+static int blockdev_is_encrypted(const char *sysfs_path, unsigned depth_left) {
+        _cleanup_free_ char *p = NULL, *uuids = NULL;
+        _cleanup_closedir_ DIR *d = NULL;
+        int r, found_encrypted = false;
+
+        assert(sysfs_path);
+
+        if (depth_left == 0)
+                return -EINVAL;
+
+        p = path_join(sysfs_path, "dm/uuid");
+        if (!p)
+                return -ENOMEM;
+
+        r = read_one_line_file(p, &uuids);
+        if (r != -ENOENT) {
+                if (r < 0)
+                        return r;
+
+                /* The DM device's uuid attribute is prefixed with "CRYPT-" if this is a dm-crypt device. */
+                if (startswith(uuids, "CRYPT-"))
+                        return true;
+        }
+
+        /* Not a dm-crypt device itself. But maybe it is on top of one? Follow the links in the "slaves/"
+         * subdir. */
+
+        p = mfree(p);
+        p = path_join(sysfs_path, "slaves");
+        if (!p)
+                return -ENOMEM;
+
+        d = opendir(p);
+        if (!d) {
+                if (errno == ENOENT) /* Doesn't have underlying devices */
+                        return false;
+
+                return -errno;
+        }
+
+        for (;;) {
+                _cleanup_free_ char *q = NULL;
+                struct dirent *de;
+
+                errno = 0;
+                de = readdir_no_dot(d);
+                if (!de) {
+                        if (errno != 0)
+                                return -errno;
+
+                        break; /* No more underlying devices */
+                }
+
+                q = path_join(p, de->d_name);
+                if (!q)
+                        return -ENOMEM;
+
+                r = blockdev_is_encrypted(q, depth_left - 1);
+                if (r < 0)
+                        return r;
+                if (r == 0) /* we found one that is not encrypted? then propagate that immediately */
+                        return false;
+
+                found_encrypted = true;
+        }
+
+        return found_encrypted;
+}
+
 int path_is_encrypted(const char *path) {
-        _cleanup_free_ char *uuids = NULL;
-        char p[SYS_BLOCK_PATH_MAX("/dm/uuid")];
+        char p[SYS_BLOCK_PATH_MAX(NULL)];
         dev_t devt;
         int r;
 
@@ -1503,13 +1622,7 @@ int path_is_encrypted(const char *path) {
         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;
+        xsprintf_sys_block_path(p, NULL, devt);
 
-        /* The DM device's uuid attribute is prefixed with "CRYPT-" if this is a dm-crypt device. */
-        return !!startswith(uuids, "CRYPT-");
+        return blockdev_is_encrypted(p, 10 /* safety net: maximum recursion depth */);
 }