From 565ac8b1c85acb9550d0645f65ac63ec9a255ffd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 17 Aug 2020 20:37:04 +0200 Subject: [PATCH] homed: mark LUKS loopback file as "dirty" via xattr when in use 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 | 76 +++++++++++++++++++++++++++++++++++----- src/home/homework-luks.h | 2 ++ src/home/homework.c | 7 ++++ src/home/homework.h | 1 + 4 files changed, 77 insertions(+), 9 deletions(-) diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index 1fe0465cfb..b263d75827 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -5,6 +5,7 @@ #include #include #include +#include #include "blkid-util.h" #include "blockdev-util.h" @@ -41,6 +42,53 @@ * 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; } diff --git a/src/home/homework-luks.h b/src/home/homework-luks.h index b51f1ad7a0..4d3e085ff0 100644 --- a/src/home/homework-luks.h +++ b/src/home/homework-luks.h @@ -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); diff --git a/src/home/homework.c b/src/home/homework.c index 49bc1efe99..594c4a05bb 100644 --- a/src/home/homework.c +++ b/src/home/homework.c @@ -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); diff --git a/src/home/homework.h b/src/home/homework.h index ce8f2a461f..c9b0d3b432 100644 --- a/src/home/homework.h +++ b/src/home/homework.h @@ -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;