]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
homed: mark LUKS loopback file as "dirty" via xattr when in use
authorLennart Poettering <lennart@poettering.net>
Mon, 17 Aug 2020 18:37:04 +0000 (20:37 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 25 Aug 2020 16:18:46 +0000 (18:18 +0200)
Let's track the "dirty" state of a home directory backed by a LUKS
volume by setting a new xattr "home.home-dirty" on the backing file
whenever it is in use.

This allows us to later user this information to show a home directory
as "dirty". This is useful because we trim/allocate on log-out, and
if we don't do that a home directory will be larger than necessary. This
fact is something we should communicate to the admin.

The idea is that when an admin sees a user with a "dirty" home directory
they can ask them to log in, to clean up the dirty state, and thus trim
everything again.

src/home/homework-luks.c
src/home/homework-luks.h
src/home/homework.c
src/home/homework.h

index 1fe0465cfbd7758b364a2b384fda55857b588c24..b263d758270d773ea1eb2c2b7415727b69aaf13e 100644 (file)
@@ -5,6 +5,7 @@
 #include <poll.h>
 #include <sys/file.h>
 #include <sys/ioctl.h>
+#include <sys/xattr.h>
 
 #include "blkid-util.h"
 #include "blockdev-util.h"
  * strictly round disk sizes down to the next 1K boundary.*/
 #define DISK_SIZE_ROUND_DOWN(x) ((x) & ~UINT64_C(1023))
 
+int run_mark_dirty(int fd, bool b) {
+        char x = '1';
+        int r, ret;
+
+        /* Sets or removes the 'user.home-dirty' xattr on the specified file. We use this to detect when a
+         * home directory was not properly unmounted. */
+
+        assert(fd >= 0);
+
+        r = fd_verify_regular(fd);
+        if (r < 0)
+                return r;
+
+        if (b) {
+                ret = fsetxattr(fd, "user.home-dirty", &x, 1, XATTR_CREATE);
+                if (ret < 0 && errno != EEXIST)
+                        return log_debug_errno(errno, "Could not mark home directory as dirty: %m");
+
+        } else {
+                r = fsync_full(fd);
+                if (r < 0)
+                        return log_debug_errno(r, "Failed to synchronize image before marking it clean: %m");
+
+                ret = fremovexattr(fd, "user.home-dirty");
+                if (ret < 0 && errno != ENODATA)
+                        return log_debug_errno(errno, "Could not mark home directory as clean: %m");
+        }
+
+        r = fsync_full(fd);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to synchronize dirty flag to disk: %m");
+
+        return ret >= 0;
+}
+
+int run_mark_dirty_by_path(const char *path, bool b) {
+        _cleanup_close_ int fd = -1;
+
+        assert(path);
+
+        fd = open(path, O_RDWR|O_CLOEXEC|O_NOCTTY);
+        if (fd < 0)
+                return log_debug_errno(errno, "Failed to open %s to mark dirty or clean: %m", path);
+
+        return run_mark_dirty(fd, b);
+}
+
 static int probe_file_system_by_fd(
                 int fd,
                 char **ret_fstype,
@@ -998,9 +1046,10 @@ int home_prepare_luks(
         _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL;
         _cleanup_(crypt_freep) struct crypt_device *cd = NULL;
         _cleanup_(erase_and_freep) void *volume_key = NULL;
+        _cleanup_close_ int root_fd = -1, image_fd = -1;
         bool dm_activated = false, mounted = false;
-        _cleanup_close_ int root_fd = -1;
         size_t volume_key_size = 0;
+        bool marked_dirty = false;
         uint64_t offset, size;
         int r;
 
@@ -1094,7 +1143,6 @@ int home_prepare_luks(
                 }
         } else {
                 _cleanup_free_ char *fstype = NULL, *subdir = NULL;
-                _cleanup_close_ int fd = -1;
                 const char *ip;
                 struct stat st;
 
@@ -1104,28 +1152,32 @@ int home_prepare_luks(
                 if (!subdir)
                         return log_oom();
 
-                fd = open(ip, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
-                if (fd < 0)
+                image_fd = open(ip, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+                if (image_fd < 0)
                         return log_error_errno(errno, "Failed to open image file %s: %m", ip);
 
-                if (fstat(fd, &st) < 0)
+                if (fstat(image_fd, &st) < 0)
                         return log_error_errno(errno, "Failed to fstat() image file: %m");
                 if (!S_ISREG(st.st_mode) && !S_ISBLK(st.st_mode))
                         return log_error_errno(
                                         S_ISDIR(st.st_mode) ? SYNTHETIC_ERRNO(EISDIR) : SYNTHETIC_ERRNO(EBADFD),
                                         "Image file %s is not a regular file or block device: %m", ip);
 
-                r = luks_validate(fd, user_record_user_name_and_realm(h), h->partition_uuid, &found_partition_uuid, &offset, &size);
+                r = luks_validate(image_fd, user_record_user_name_and_realm(h), h->partition_uuid, &found_partition_uuid, &offset, &size);
                 if (r < 0)
                         return log_error_errno(r, "Failed to validate disk label: %m");
 
+                /* Everything before this point left the image untouched. We are now starting to make
+                 * changes, hence mark the image dirty */
+                marked_dirty = run_mark_dirty(image_fd, true) > 0;
+
                 if (!user_record_luks_discard(h)) {
-                        r = run_fallocate(fd, &st);
+                        r = run_fallocate(image_fd, &st);
                         if (r < 0)
                                 return r;
                 }
 
-                r = loop_device_make(fd, O_RDWR, offset, size, 0, &loop);
+                r = loop_device_make(image_fd, O_RDWR, offset, size, 0, &loop);
                 if (r == -ENOENT) {
                         log_error_errno(r, "Loopback block device support is not available on this system.");
                         return -ENOLINK; /* make recognizable */
@@ -1180,8 +1232,9 @@ int home_prepare_luks(
                 if (user_record_luks_discard(h))
                         (void) run_fitrim(root_fd);
 
-                setup->image_fd = TAKE_FD(fd);
+                setup->image_fd = TAKE_FD(image_fd);
                 setup->do_offline_fallocate = !(setup->do_offline_fitrim = user_record_luks_offline_discard(h));
+                setup->do_mark_clean = marked_dirty;
         }
 
         setup->loop = TAKE_PTR(loop);
@@ -1210,6 +1263,9 @@ fail:
         if (dm_activated)
                 (void) crypt_deactivate(cd, setup->dm_name);
 
+        if (image_fd >= 0 && marked_dirty)
+                (void) run_mark_dirty(image_fd, false);
+
         return r;
 }
 
@@ -1304,6 +1360,7 @@ int home_activate_luks(
 
         setup.undo_dm = false;
         setup.do_offline_fallocate = false;
+        setup.do_mark_clean = false;
 
         log_info("Everything completed.");
 
@@ -1357,6 +1414,7 @@ int home_deactivate_luks(UserRecord *h) {
         else
                 (void) run_fallocate_by_path(user_record_image_path(h));
 
+        run_mark_dirty_by_path(user_record_image_path(h), false);
         return we_detached;
 }
 
index b51f1ad7a01d07f747ed67b9f6d1ad86c8c359f7..4d3e085ff004f6dfccf1526f1456bc0ae5651118 100644 (file)
@@ -42,3 +42,5 @@ int run_fitrim(int root_fd);
 int run_fitrim_by_path(const char *root_path);
 int run_fallocate(int backing_fd, const struct stat *st);
 int run_fallocate_by_path(const char *backing_path);
+int run_mark_dirty(int fd, bool b);
+int run_mark_dirty_by_path(const char *path, bool b);
index 49bc1efe99480dd8624a1c4c721f02c2a307c88d..594c4a05bbf2ef2f2aa464f0c89920f22508600c 100644 (file)
@@ -308,6 +308,12 @@ int home_setup_undo(HomeSetup *setup) {
                                 r = q;
                 }
 
+                if (setup->do_mark_clean) {
+                        q = run_mark_dirty(setup->image_fd, false);
+                        if (q < 0)
+                                r = q;
+                }
+
                 setup->image_fd = safe_close(setup->image_fd);
         }
 
@@ -315,6 +321,7 @@ int home_setup_undo(HomeSetup *setup) {
         setup->undo_dm = false;
         setup->do_offline_fitrim = false;
         setup->do_offline_fallocate = false;
+        setup->do_mark_clean = false;
 
         setup->dm_name = mfree(setup->dm_name);
         setup->dm_node = mfree(setup->dm_node);
index ce8f2a461fe827e81f2669886de97f206171b4da..c9b0d3b4325dcd9627822c5340ad75be63c60e9e 100644 (file)
@@ -31,6 +31,7 @@ typedef struct HomeSetup {
         bool undo_mount;
         bool do_offline_fitrim;
         bool do_offline_fallocate;
+        bool do_mark_clean;
 
         uint64_t partition_offset;
         uint64_t partition_size;