summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2021-11-16 22:53:24 +0100
committerGitHub <noreply@github.com>2021-11-16 22:53:24 +0100
commit2939fff2351742028a4a4bca016f48acfeed524c (patch)
treeec3993befeef014e4a158c8f9d4666a7c14bef67
parent1fbe8d0cf265ee82345aa288f8cc249419e22f1e (diff)
parenta89b67509fb8d4f80a918f9ac93b2d759f386820 (diff)
downloadsystemd-2939fff2351742028a4a4bca016f48acfeed524c.tar.gz
Merge pull request #21391 from poettering/homed-minimize
homed: add ability to "minimize" home dirs, i.e. shrink to smallest possible size
-rw-r--r--src/home/home-util.h7
-rw-r--r--src/home/homework-luks.c430
-rw-r--r--src/home/homework.h10
-rw-r--r--src/home/user-record-util.c3
-rw-r--r--src/shared/resize-fs.c5
-rw-r--r--src/shared/resize-fs.h2
-rw-r--r--src/shared/user-record.c24
-rw-r--r--src/shared/user-record.h7
-rwxr-xr-xtest/units/testsuite-46.sh33
9 files changed, 393 insertions, 128 deletions
diff --git a/src/home/home-util.h b/src/home/home-util.h
index 5e633ea4af..ca4c068f37 100644
--- a/src/home/home-util.h
+++ b/src/home/home-util.h
@@ -12,6 +12,13 @@
#define HOME_UID_MIN 60001
#define HOME_UID_MAX 60513
+/* Put some limits on disk sizes: not less than 5M, not more than 5T */
+#define USER_DISK_SIZE_MIN (UINT64_C(5)*1024*1024)
+#define USER_DISK_SIZE_MAX (UINT64_C(5)*1024*1024*1024*1024)
+
+/* The default disk size to use when nothing else is specified, relative to free disk space */
+#define USER_DISK_SIZE_DEFAULT_PERCENT 85
+
bool suitable_user_name(const char *name);
int suitable_realm(const char *realm);
int suitable_image_path(const char *path);
diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c
index 8ad6499d8f..294c052720 100644
--- a/src/home/homework-luks.c
+++ b/src/home/homework-luks.c
@@ -25,6 +25,7 @@
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
+#include "filesystems.h"
#include "fs-util.h"
#include "fsck-util.h"
#include "home-util.h"
@@ -2513,16 +2514,16 @@ static int prepare_resize_partition(
int fd,
uint64_t partition_offset,
uint64_t old_partition_size,
- uint64_t new_partition_size,
sd_id128_t *ret_disk_uuid,
- struct fdisk_table **ret_table) {
+ struct fdisk_table **ret_table,
+ struct fdisk_partition **ret_partition) {
_cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
_cleanup_(fdisk_unref_tablep) struct fdisk_table *t = NULL;
_cleanup_free_ char *path = NULL, *disk_uuid_as_string = NULL;
- size_t n_partitions;
+ struct fdisk_partition *found = NULL;
sd_id128_t disk_uuid;
- bool found = false;
+ size_t n_partitions;
int r;
assert(fd >= 0);
@@ -2531,9 +2532,7 @@ static int prepare_resize_partition(
assert((partition_offset & 511) == 0);
assert((old_partition_size & 511) == 0);
- assert((new_partition_size & 511) == 0);
assert(UINT64_MAX - old_partition_size >= partition_offset);
- assert(UINT64_MAX - new_partition_size >= partition_offset);
if (partition_offset == 0) {
/* If the offset is at the beginning we assume no partition table, let's exit early. */
@@ -2588,30 +2587,17 @@ static int prepare_resize_partition(
if (found)
return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "Partition found twice, refusing.");
- /* Found our partition, now patch it */
- r = fdisk_partition_size_explicit(p, 1);
- if (r < 0)
- return log_error_errno(r, "Failed to enable explicit partition size: %m");
-
- r = fdisk_partition_set_size(p, new_partition_size / 512U);
- if (r < 0)
- return log_error_errno(r, "Failed to change partition size: %m");
-
- found = true;
- continue;
-
- } else {
- if (fdisk_partition_get_start(p) < partition_offset + new_partition_size / 512U &&
- fdisk_partition_get_end(p) >= partition_offset / 512)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't extend, conflicting partition found.");
- }
+ found = p;
+ } else if (fdisk_partition_get_end(p) > partition_offset / 512U)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't extend, not last partition in image.");
}
if (!found)
return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "Failed to find matching partition to resize.");
- *ret_table = TAKE_PTR(t);
*ret_disk_uuid = disk_uuid;
+ *ret_table = TAKE_PTR(t);
+ *ret_partition = found;
return 1;
}
@@ -2638,7 +2624,13 @@ static int ask_cb(struct fdisk_context *c, struct fdisk_ask *ask, void *userdata
return 0;
}
-static int apply_resize_partition(int fd, sd_id128_t disk_uuids, struct fdisk_table *t) {
+static int apply_resize_partition(
+ int fd,
+ sd_id128_t disk_uuids,
+ struct fdisk_table *t,
+ struct fdisk_partition *p,
+ size_t new_partition_size) {
+
_cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
_cleanup_free_ void *two_zero_lbas = NULL;
_cleanup_free_ char *path = NULL;
@@ -2646,10 +2638,22 @@ static int apply_resize_partition(int fd, sd_id128_t disk_uuids, struct fdisk_ta
int r;
assert(fd >= 0);
+ assert(!t == !p);
if (!t) /* no partition table to apply, exit early */
return 0;
+ assert(p);
+
+ /* Before writing our partition patch the final size in */
+ r = fdisk_partition_size_explicit(p, 1);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable explicit partition size: %m");
+
+ r = fdisk_partition_set_size(p, new_partition_size / 512U);
+ if (r < 0)
+ return log_error_errno(r, "Failed to change partition size: %m");
+
two_zero_lbas = malloc0(1024U);
if (!two_zero_lbas)
return log_oom();
@@ -2695,6 +2699,139 @@ static int apply_resize_partition(int fd, sd_id128_t disk_uuids, struct fdisk_ta
return 1;
}
+/* Always keep at least 16M free, so that we can safely log in and update the user record while doing so */
+#define HOME_MIN_FREE (16U*1024U*1024U)
+
+static int get_smallest_fs_size(int fd, uint64_t *ret) {
+ uint64_t minsz, needed;
+ struct statfs sfs;
+
+ assert(fd >= 0);
+ assert(ret);
+
+ /* Determines the minimal disk size we might be able to shrink the file system referenced by the fd to. */
+
+ if (syncfs(fd) < 0) /* let's sync before we query the size, so that the values returned are accurate */
+ return log_error_errno(errno, "Failed to synchronize home file system: %m");
+
+ if (fstatfs(fd, &sfs) < 0)
+ return log_error_errno(errno, "Failed to statfs() home file system: %m");
+
+ /* Let's determine the minimal file syste size of the used fstype */
+ minsz = minimal_size_by_fs_magic(sfs.f_type);
+ if (minsz == UINT64_MAX)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Don't know minimum file system size of file system type '%s' of home directory.", fs_type_to_string(sfs.f_type));
+
+ if (minsz < USER_DISK_SIZE_MIN)
+ minsz = USER_DISK_SIZE_MIN;
+
+ if (sfs.f_bfree > sfs.f_blocks)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Detected amount of free blocks is greater than the total amount of file system blocks. Refusing.");
+
+ /* Calculate how much disk space is currently in use. */
+ needed = sfs.f_blocks - sfs.f_bfree;
+ if (needed > UINT64_MAX / sfs.f_bsize)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File system size out of range.");
+
+ needed *= sfs.f_bsize;
+
+ /* Add some safety margin of free space we'll always keep */
+ if (needed > UINT64_MAX - HOME_MIN_FREE) /* Check for overflow */
+ needed = UINT64_MAX;
+ else
+ needed += HOME_MIN_FREE;
+
+ *ret = DISK_SIZE_ROUND_UP(MAX(needed, minsz));
+ return 0;
+}
+
+static int resize_fs_loop(
+ UserRecord *h,
+ HomeSetup *setup,
+ int resize_type,
+ uint64_t old_fs_size,
+ uint64_t new_fs_size,
+ uint64_t *ret_fs_size) {
+
+ uint64_t current_fs_size;
+ unsigned n_iterations = 0;
+ int r;
+
+ assert(h);
+ assert(setup);
+ assert(setup->root_fd >= 0);
+
+ /* A bisection loop trying to find the closest size to what the user asked for. (Well, we bisect like
+ * this only when we *shrink* the fs — if we grow the fs there's no need to bisect.) */
+
+ current_fs_size = old_fs_size;
+ for (uint64_t lower_boundary = new_fs_size, upper_boundary = old_fs_size, try_fs_size = new_fs_size;;) {
+ bool worked;
+
+ n_iterations++;
+
+ /* Now resize the file system */
+ if (resize_type == CAN_RESIZE_ONLINE) {
+ r = resize_fs(setup->root_fd, try_fs_size, NULL);
+ if (r < 0) {
+ if (!ERRNO_IS_DISK_SPACE(r) || new_fs_size > old_fs_size) /* Not a disk space issue? Not trying to shrink? */
+ return log_error_errno(r, "Failed to resize file system: %m");
+
+ log_debug_errno(r, "Shrinking from %s to %s didn't work, not enough space for contained data.", FORMAT_BYTES(current_fs_size), FORMAT_BYTES(try_fs_size));
+ worked = false;
+ } else {
+ log_debug("Successfully resized from %s to %s.", FORMAT_BYTES(current_fs_size), FORMAT_BYTES(try_fs_size));
+ current_fs_size = try_fs_size;
+ worked = true;
+ }
+
+ /* If we hit a disk space issue and are shrinking the fs, then maybe it helps to
+ * increase the image size. */
+ } else {
+ r = ext4_offline_resize_fs(setup, try_fs_size, user_record_luks_discard(h), user_record_mount_flags(h), h->luks_extra_mount_options);
+ if (r < 0)
+ return r;
+
+ /* For now, when we fail to shrink an ext4 image we'll not try again via the
+ * bisection logic. We might add that later, but give this involves shelling out
+ * multiple programs it's a bit too cumbersome to my taste. */
+
+ worked = true;
+ current_fs_size = try_fs_size;
+ }
+
+ if (new_fs_size > old_fs_size) /* If we are growing we are done after one iteration */
+ break;
+
+ /* If we are shrinking then let's adjust our bisection boundaries and try again. */
+ if (worked)
+ upper_boundary = MIN(upper_boundary, try_fs_size);
+ else
+ lower_boundary = MAX(lower_boundary, try_fs_size);
+
+ /* OK, this attempt to shrink didn't work. Let's try between the old size and what worked. */
+ if (lower_boundary >= upper_boundary) {
+ log_debug("Image can't be shrunk further (range to try is empty).");
+ break;
+ }
+
+ /* Let's find a new value to try half-way between the lower boundary and the upper boundary
+ * to try now. */
+ try_fs_size = DISK_SIZE_ROUND_DOWN(lower_boundary + (upper_boundary - lower_boundary) / 2);
+ if (try_fs_size <= lower_boundary || try_fs_size >= upper_boundary) {
+ log_debug("Image can't be shrunk further (remaining range to try too small).");
+ break;
+ }
+ }
+
+ log_debug("Bisection loop completed after %u iterations.", n_iterations);
+
+ if (ret_fs_size)
+ *ret_fs_size = current_fs_size;
+
+ return 0;
+}
+
int home_resize_luks(
UserRecord *h,
HomeSetupFlags flags,
@@ -2702,9 +2839,11 @@ int home_resize_luks(
PasswordCache *cache,
UserRecord **ret_home) {
- uint64_t old_image_size, new_image_size, old_fs_size, new_fs_size, crypto_offset, new_partition_size;
+ uint64_t old_image_size, new_image_size, old_fs_size, new_fs_size, crypto_offset, crypto_offset_bytes,
+ new_partition_size, smallest_fs_size, resized_fs_size;
_cleanup_(user_record_unrefp) UserRecord *header_home = NULL, *embedded_home = NULL, *new_home = NULL;
_cleanup_(fdisk_unref_tablep) struct fdisk_table *table = NULL;
+ struct fdisk_partition *partition = NULL;
_cleanup_close_ int opened_image_fd = -1;
_cleanup_free_ char *whole_disk = NULL;
int r, resize_type, image_fd = -1;
@@ -2712,11 +2851,15 @@ int home_resize_luks(
const char *ip, *ipo;
struct statfs sfs;
struct stat st;
+ enum {
+ INTENTION_DONT_KNOW = 0, /* These happen to match the return codes of CMP() */
+ INTENTION_SHRINK = -1,
+ INTENTION_GROW = 1,
+ } intention = INTENTION_DONT_KNOW;
assert(h);
assert(user_record_storage(h) == USER_LUKS);
assert(setup);
- assert(ret_home);
r = dlopen_cryptsetup();
if (r < 0)
@@ -2774,8 +2917,6 @@ int home_resize_luks(
new_image_size = old_image_size; /* we can't resize physical block devices */
} else {
- uint64_t new_image_size_rounded;
-
r = stat_verify_regular(&st);
if (r < 0)
return log_error_errno(r, "Image %s is not a block device nor regular file: %m", ip);
@@ -2786,25 +2927,40 @@ int home_resize_luks(
* apply onto the loopback file as a whole. When we operate on block devices we instead apply
* to the partition itself only. */
- new_image_size_rounded = DISK_SIZE_ROUND_DOWN(h->disk_size);
+ if (FLAGS_SET(flags, HOME_SETUP_RESIZE_MINIMIZE)) {
+ new_image_size = 0;
+ intention = INTENTION_SHRINK;
+ } else {
+ uint64_t new_image_size_rounded;
- if (old_image_size == h->disk_size ||
- old_image_size == new_image_size_rounded) {
- /* If exact match, or a match after we rounded down, don't do a thing */
- log_info("Image size already matching, skipping operation.");
- return 0;
- }
+ new_image_size_rounded = DISK_SIZE_ROUND_DOWN(h->disk_size);
- new_image_size = new_image_size_rounded;
+ if (old_image_size >= new_image_size_rounded && old_image_size <= h->disk_size) {
+ /* If exact match, or a match after we rounded down, don't do a thing */
+ log_info("Image size already matching, skipping operation.");
+ return 0;
+ }
+
+ new_image_size = new_image_size_rounded;
+ intention = CMP(new_image_size, old_image_size); /* Is this a shrink */
+ }
}
- r = home_setup_luks(h, flags, whole_disk, setup, cache, &header_home);
+ r = home_setup_luks(
+ h,
+ flags,
+ whole_disk,
+ setup,
+ cache,
+ FLAGS_SET(flags, HOME_SETUP_RESIZE_DONT_SYNC_IDENTITIES) ? NULL : &header_home);
if (r < 0)
return r;
- r = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, cache, &embedded_home, &new_home);
- if (r < 0)
- return r;
+ if (!FLAGS_SET(flags, HOME_SETUP_RESIZE_DONT_SYNC_IDENTITIES)) {
+ r = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, cache, &embedded_home, &new_home);
+ if (r < 0)
+ return r;
+ }
log_info("offset = %" PRIu64 ", size = %" PRIu64 ", image = %" PRIu64, setup->partition_offset, setup->partition_size, old_image_size);
@@ -2816,24 +2972,31 @@ int home_resize_luks(
uint64_t partition_table_extra;
partition_table_extra = old_image_size - setup->partition_size;
+
if (new_image_size <= partition_table_extra)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "New size smaller than partition table metadata.");
+ new_image_size = partition_table_extra;
new_partition_size = DISK_SIZE_ROUND_DOWN(new_image_size - partition_table_extra);
} else {
- uint64_t new_partition_size_rounded;
-
assert(S_ISBLK(st.st_mode));
- new_partition_size_rounded = DISK_SIZE_ROUND_DOWN(h->disk_size);
+ if (FLAGS_SET(flags, HOME_SETUP_RESIZE_MINIMIZE)) {
+ new_partition_size = 0;
+ intention = INTENTION_SHRINK;
+ } else {
+ uint64_t new_partition_size_rounded;
- if (h->disk_size == setup->partition_size ||
- new_partition_size_rounded == setup->partition_size) {
- log_info("Partition size already matching, skipping operation.");
- return 0;
- }
+ new_partition_size_rounded = DISK_SIZE_ROUND_DOWN(h->disk_size);
- new_partition_size = new_partition_size_rounded;
+ if (setup->partition_size >= new_partition_size_rounded &&
+ setup->partition_size <= h->disk_size) {
+ log_info("Partition size already matching, skipping operation.");
+ return 0;
+ }
+
+ new_partition_size = new_partition_size_rounded;
+ intention = CMP(new_partition_size, setup->partition_size);
+ }
}
if ((UINT64_MAX - setup->partition_offset) < new_partition_size ||
@@ -2841,13 +3004,63 @@ int home_resize_luks(
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "New partition doesn't fit into backing storage, refusing.");
crypto_offset = sym_crypt_get_data_offset(setup->crypt_device);
- if (setup->partition_size / 512U <= crypto_offset)
+ if (crypto_offset > UINT64_MAX/512U)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "LUKS2 data offset out of range, refusing.");
+ crypto_offset_bytes = (uint64_t) crypto_offset * 512U;
+ if (setup->partition_size <= crypto_offset_bytes)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Weird, old crypto payload offset doesn't actually fit in partition size?");
- if (new_partition_size / 512U <= crypto_offset)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "New size smaller than crypto payload offset?");
- old_fs_size = (setup->partition_size / 512U - crypto_offset) * 512U;
- new_fs_size = DISK_SIZE_ROUND_DOWN((new_partition_size / 512U - crypto_offset) * 512U);
+ /* Make sure at least the LUKS header fit in */
+ if (new_partition_size <= crypto_offset_bytes) {
+ uint64_t add;
+
+ add = DISK_SIZE_ROUND_UP(crypto_offset_bytes) - new_partition_size;
+ new_partition_size += add;
+ if (S_ISREG(st.st_mode))
+ new_image_size += add;
+ }
+
+ old_fs_size = setup->partition_size - crypto_offset_bytes;
+ new_fs_size = DISK_SIZE_ROUND_DOWN(new_partition_size - crypto_offset_bytes);
+
+ r = get_smallest_fs_size(setup->root_fd, &smallest_fs_size);
+ if (r < 0)
+ return r;
+
+ if (new_fs_size < smallest_fs_size) {
+ uint64_t add;
+
+ add = DISK_SIZE_ROUND_UP(smallest_fs_size) - new_fs_size;
+ new_fs_size += add;
+ new_partition_size += add;
+ if (S_ISREG(st.st_mode))
+ new_image_size += add;
+ }
+
+ if (new_fs_size == old_fs_size) {
+ log_info("New file system size identical to old file system size, skipping operation.");
+ return 0;
+ }
+
+ if (FLAGS_SET(flags, HOME_SETUP_RESIZE_DONT_GROW) && new_fs_size > old_fs_size) {
+ log_info("New file system size would be larger than old, but shrinking requested, skipping operation.");
+ return 0;
+ }
+
+ if (FLAGS_SET(flags, HOME_SETUP_RESIZE_DONT_SHRINK) && new_fs_size < old_fs_size) {
+ log_info("New file system size would be smaller than old, but growing requested, skipping operation.");
+ return 0;
+ }
+
+ if (CMP(new_fs_size, old_fs_size) != intention) {
+ if (intention < 0)
+ log_info("Shrink operation would enlarge file system, skipping operation.");
+ else {
+ assert(intention > 0);
+ log_info("Grow operation would shrink file system, skipping operation.");
+ }
+ return 0;
+ }
/* Before we start doing anything, let's figure out if we actually can */
resize_type = can_resize_fs(setup->root_fd, old_fs_size, new_fs_size);
@@ -2868,13 +3081,13 @@ int home_resize_luks(
image_fd,
setup->partition_offset,
setup->partition_size,
- new_partition_size,
&disk_uuid,
- &table);
+ &table,
+ &partition);
if (r < 0)
return r;
- if (new_fs_size > old_fs_size) {
+ if (new_fs_size > old_fs_size) { /* → Grow */
if (S_ISREG(st.st_mode)) {
/* Grow file size */
@@ -2883,6 +3096,9 @@ int home_resize_luks(
return r;
log_info("Growing of image file completed.");
+ } else {
+ assert(S_ISBLK(st.st_mode));
+ assert(new_image_size == old_image_size);
}
/* Make sure loopback device sees the new bigger size */
@@ -2894,7 +3110,7 @@ int home_resize_luks(
else
log_info("Refreshing loop device size completed.");
- r = apply_resize_partition(image_fd, disk_uuid, table);
+ r = apply_resize_partition(image_fd, disk_uuid, table, partition, new_partition_size);
if (r < 0)
return r;
if (r > 0)
@@ -2910,9 +3126,13 @@ int home_resize_luks(
log_info("LUKS device growing completed.");
} else {
- r = home_store_embedded_identity(new_home, setup->root_fd, h->uid, embedded_home);
- if (r < 0)
- return r;
+ /* → Shrink */
+
+ if (!FLAGS_SET(flags, HOME_SETUP_RESIZE_DONT_SYNC_IDENTITIES)) {
+ r = home_store_embedded_identity(new_home, setup->root_fd, h->uid, embedded_home);
+ if (r < 0)
+ return r;
+ }
if (S_ISREG(st.st_mode)) {
if (user_record_luks_discard(h))
@@ -2927,25 +3147,37 @@ int home_resize_luks(
}
}
- /* Now resize the file system */
- if (resize_type == CAN_RESIZE_ONLINE) {
- r = resize_fs(setup->root_fd, new_fs_size, NULL);
- if (r < 0)
- return log_error_errno(r, "Failed to resize file system: %m");
- } else {
- r = ext4_offline_resize_fs(setup, new_fs_size, user_record_luks_discard(h), user_record_mount_flags(h), h->luks_extra_mount_options);
- if (r < 0)
- return r;
+ /* Now try to resize the file system. The requested size might not always be possible, in which case
+ * we'll try to get as close as we can get. The result is returned in 'resized_fs_size' */
+ r = resize_fs_loop(h, setup, resize_type, old_fs_size, new_fs_size, &resized_fs_size);
+ if (r < 0)
+ return r;
+
+ if (resized_fs_size == old_fs_size) {
+ log_info("Couldn't change file system size.");
+ return 0;
+ }
+
+ log_info("File system resizing from %s to %s completed.", FORMAT_BYTES(old_fs_size), FORMAT_BYTES(resized_fs_size));
+
+ if (resized_fs_size > new_fs_size) {
+ uint64_t add;
+
+ /* If the shrinking we managed to do is larger than what we wanted we need to adjust the partition/image sizes. */
+ add = resized_fs_size - new_fs_size;
+ new_partition_size += add;
+ if (S_ISREG(st.st_mode))
+ new_image_size += add;
}
- log_info("File system resizing completed.");
+ new_fs_size = resized_fs_size;
/* Immediately sync afterwards */
r = home_sync_and_statfs(setup->root_fd, NULL);
if (r < 0)
return r;
- if (new_fs_size < old_fs_size) {
+ if (new_fs_size < old_fs_size) { /* → Shrink */
/* Shrink the LUKS device now, matching the new file system size */
r = sym_crypt_resize(setup->crypt_device, setup->dm_name, new_fs_size / 512);
@@ -2954,14 +3186,6 @@ int home_resize_luks(
log_info("LUKS device shrinking completed.");
- if (S_ISREG(st.st_mode)) {
- /* Shrink the image file */
- if (ftruncate(image_fd, new_image_size) < 0)
- return log_error_errno(errno, "Failed to shrink image file %s: %m", ip);
-
- log_info("Shrinking of image file completed.");
- }
-
/* Refresh the loop devices size */
r = loop_device_refresh_size(setup->loop, UINT64_MAX, new_partition_size);
if (r == -ENOTTY)
@@ -2971,7 +3195,18 @@ int home_resize_luks(
else
log_info("Refreshing loop device size completed.");
- r = apply_resize_partition(image_fd, disk_uuid, table);
+ if (S_ISREG(st.st_mode)) {
+ /* Shrink the image file */
+ if (ftruncate(image_fd, new_image_size) < 0)
+ return log_error_errno(errno, "Failed to shrink image file %s: %m", ip);
+
+ log_info("Shrinking of image file completed.");
+ } else {
+ assert(S_ISBLK(st.st_mode));
+ assert(new_image_size == old_image_size);
+ }
+
+ r = apply_resize_partition(image_fd, disk_uuid, table, partition, new_partition_size);
if (r < 0)
return r;
if (r > 0)
@@ -2979,19 +3214,24 @@ int home_resize_luks(
if (S_ISBLK(st.st_mode) && ioctl(image_fd, BLKRRPART, 0) < 0)
log_debug_errno(errno, "BLKRRPART failed on block device, ignoring: %m");
- } else {
- r = home_store_embedded_identity(new_home, setup->root_fd, h->uid, embedded_home);
- if (r < 0)
- return r;
+
+ } else { /* → Grow */
+ if (!FLAGS_SET(flags, HOME_SETUP_RESIZE_DONT_SYNC_IDENTITIES)) {
+ r = home_store_embedded_identity(new_home, setup->root_fd, h->uid, embedded_home);
+ if (r < 0)
+ return r;
+ }
}
- r = home_store_header_identity_luks(new_home, setup, header_home);
- if (r < 0)
- return r;
+ if (!FLAGS_SET(flags, HOME_SETUP_RESIZE_DONT_SYNC_IDENTITIES)) {
+ r = home_store_header_identity_luks(new_home, setup, header_home);
+ if (r < 0)
+ return r;
- r = home_extend_embedded_identity(new_home, h, setup);
- if (r < 0)
- return r;
+ r = home_extend_embedded_identity(new_home, h, setup);
+ if (r < 0)
+ return r;
+ }
if (user_record_luks_discard(h))
(void) run_fitrim(setup->root_fd);
@@ -3008,7 +3248,9 @@ int home_resize_luks(
print_size_summary(new_image_size, new_fs_size, &sfs);
- *ret_home = TAKE_PTR(new_home);
+ if (ret_home)
+ *ret_home = TAKE_PTR(new_home);
+
return 0;
}
diff --git a/src/home/homework.h b/src/home/homework.h
index 551f0d0153..55c2f5b2df 100644
--- a/src/home/homework.h
+++ b/src/home/homework.h
@@ -53,10 +53,16 @@ typedef struct HomeSetup {
/* Various flags for the operation of setting up a home directory */
typedef enum HomeSetupFlags {
- HOME_SETUP_ALREADY_ACTIVATED = 1 << 0, /* Open an already activated home, rather than activate it afresh */
+ HOME_SETUP_ALREADY_ACTIVATED = 1 << 0, /* Open an already activated home, rather than activate it afresh */
/* CIFS backend: */
- HOME_SETUP_CIFS_MKDIR = 1 << 1, /* Create CIFS subdir when missing */
+ HOME_SETUP_CIFS_MKDIR = 1 << 1, /* Create CIFS subdir when missing */
+
+ /* Applies only for resize operations */
+ HOME_SETUP_RESIZE_DONT_SYNC_IDENTITIES = 1 << 2, /* Don't sync identity records into home and LUKS header */
+ HOME_SETUP_RESIZE_MINIMIZE = 1 << 3, /* Shrink to minimal size */
+ HOME_SETUP_RESIZE_DONT_GROW = 1 << 4, /* If the resize would grow, gracefully terminate operation */
+ HOME_SETUP_RESIZE_DONT_SHRINK = 1 << 5, /* If the resize would shrink, gracefully terminate operation */
} HomeSetupFlags;
int home_setup_done(HomeSetup *setup);
diff --git a/src/home/user-record-util.c b/src/home/user-record-util.c
index 464f1dbb7a..276caaa172 100644
--- a/src/home/user-record-util.c
+++ b/src/home/user-record-util.c
@@ -639,9 +639,6 @@ int user_record_set_disk_size(UserRecord *h, uint64_t disk_size) {
if (!h->json)
return -EUNATCH;
- if (disk_size < USER_DISK_SIZE_MIN || disk_size > USER_DISK_SIZE_MAX)
- return -ERANGE;
-
r = sd_id128_get_machine(&mid);
if (r < 0)
return r;
diff --git a/src/shared/resize-fs.c b/src/shared/resize-fs.c
index 33cb78babf..178aefac21 100644
--- a/src/shared/resize-fs.c
+++ b/src/shared/resize-fs.c
@@ -119,3 +119,8 @@ uint64_t minimal_size_by_fs_name(const char *name) {
return UINT64_MAX;
}
+
+/* Returns true for the only fs that can online shrink *and* grow */
+bool fs_can_online_shrink_and_grow(statfs_f_type_t magic) {
+ return magic == (statfs_f_type_t) BTRFS_SUPER_MAGIC;
+}
diff --git a/src/shared/resize-fs.h b/src/shared/resize-fs.h
index 8831fd8b40..312005f7e2 100644
--- a/src/shared/resize-fs.h
+++ b/src/shared/resize-fs.h
@@ -13,3 +13,5 @@ int resize_fs(int fd, uint64_t sz, uint64_t *ret_size);
uint64_t minimal_size_by_fs_magic(statfs_f_type_t magic);
uint64_t minimal_size_by_fs_name(const char *str);
+
+bool fs_can_online_shrink_and_grow(statfs_f_type_t magic);
diff --git a/src/shared/user-record.c b/src/shared/user-record.c
index b68b6a98d2..e16395f032 100644
--- a/src/shared/user-record.c
+++ b/src/shared/user-record.c
@@ -563,26 +563,6 @@ static int json_dispatch_storage(const char *name, JsonVariant *variant, JsonDis
return 0;
}
-static int json_dispatch_disk_size(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
- uint64_t *size = userdata;
- uintmax_t k;
-
- if (json_variant_is_null(variant)) {
- *size = UINT64_MAX;
- return 0;
- }
-
- if (!json_variant_is_unsigned(variant))
- return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer.", strna(name));
-
- k = json_variant_unsigned(variant);
- if (k < USER_DISK_SIZE_MIN || k > USER_DISK_SIZE_MAX)
- return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' is not in valid range %" PRIu64 "…%" PRIu64 ".", strna(name), USER_DISK_SIZE_MIN, USER_DISK_SIZE_MAX);
-
- *size = k;
- return 0;
-}
-
static int json_dispatch_tasks_or_memory_max(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
uint64_t *limit = userdata;
uintmax_t k;
@@ -1135,7 +1115,7 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp
{ "notBeforeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, not_before_usec), 0 },
{ "notAfterUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, not_after_usec), 0 },
{ "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 },
- { "diskSize", JSON_VARIANT_UNSIGNED, json_dispatch_disk_size, offsetof(UserRecord, disk_size), 0 },
+ { "diskSize", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 },
{ "diskSizeRelative", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_size_relative), 0 },
{ "skeletonDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, skeleton_directory), 0 },
{ "accessMode", JSON_VARIANT_UNSIGNED, json_dispatch_access_mode, offsetof(UserRecord, access_mode), 0 },
@@ -1484,7 +1464,7 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla
{ "notBeforeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, not_before_usec), 0 },
{ "notAfterUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, not_after_usec), 0 },
{ "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 },
- { "diskSize", JSON_VARIANT_UNSIGNED, json_dispatch_disk_size, offsetof(UserRecord, disk_size), 0 },
+ { "diskSize", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 },
{ "diskSizeRelative", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_size_relative), 0 },
{ "skeletonDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, skeleton_directory), 0 },
{ "accessMode", JSON_VARIANT_UNSIGNED, json_dispatch_access_mode, offsetof(UserRecord, access_mode), 0 },
diff --git a/src/shared/user-record.h b/src/shared/user-record.h
index c72bef4a72..22a6bb28f4 100644
--- a/src/shared/user-record.h
+++ b/src/shared/user-record.h
@@ -10,13 +10,6 @@
#include "missing_resource.h"
#include "time-util.h"
-/* But some limits on disk sizes: not less than 5M, not more than 5T */
-#define USER_DISK_SIZE_MIN (UINT64_C(5)*1024*1024)
-#define USER_DISK_SIZE_MAX (UINT64_C(5)*1024*1024*1024*1024)
-
-/* The default disk size to use when nothing else is specified, relative to free disk space */
-#define USER_DISK_SIZE_DEFAULT_PERCENT 85
-
typedef enum UserDisposition {
USER_INTRINSIC, /* root and nobody */
USER_SYSTEM, /* statically allocated users for system services */
diff --git a/test/units/testsuite-46.sh b/test/units/testsuite-46.sh
index fc4fc50297..6c70b32d45 100755
--- a/test/units/testsuite-46.sh
+++ b/test/units/testsuite-46.sh
@@ -69,6 +69,39 @@ inspect test-user
PASSWORD=xEhErW0ndafV4s homectl deactivate test-user
inspect test-user
+# Do some resize tests, but only if we run on real kernels, as quota inside of containers will fail
+if ! systemd-detect-virt -cq ; then
+ # grow while inactive
+ PASSWORD=xEhErW0ndafV4s homectl resize test-user 300M
+ inspect test-user
+
+ # minimize while inactive
+ PASSWORD=xEhErW0ndafV4s homectl resize test-user 0
+ inspect test-user
+
+ PASSWORD=xEhErW0ndafV4s homectl activate test-user
+ inspect test-user
+
+ # grow while active
+ PASSWORD=xEhErW0ndafV4s homectl resize test-user 300M
+ inspect test-user
+
+ # minimize while active
+ PASSWORD=xEhErW0ndafV4s homectl resize test-user 0
+ inspect test-user
+
+ # grow while active
+ PASSWORD=xEhErW0ndafV4s homectl resize test-user 300M
+ inspect test-user
+
+ # shrink to original size while active
+ PASSWORD=xEhErW0ndafV4s homectl resize test-user 256M
+ inspect test-user
+
+ PASSWORD=xEhErW0ndafV4s homectl deactivate test-user
+ inspect test-user
+fi
+
PASSWORD=xEhErW0ndafV4s homectl with test-user -- test ! -f /home/test-user/xyz
PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz \
&& { echo 'unexpected success'; exit 1; }