From 716bc20034bb54a0c1b1dd5125929d900026670b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 4 Nov 2021 21:54:46 +0100 Subject: [PATCH 1/4] homework: when creating home dir also treat specified size as hint The resize logic is now able to handle resize requests that cannot be fulfilled in full gracefully. Let's do the same when allocating new home directories. This means "homectl create foo --disk-size=min" and "homectl create foo --disk-size=max" may now be used to create the smallest or largest home directory we support. --- src/home/homework-luks.c | 61 ++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index 57b41d26b8..0beee8ecfd 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -63,6 +63,7 @@ _x > UINT64_MAX - 4095U ? UINT64_MAX : (_x + 4095U) & ~UINT64_C(4095); \ }) +static int resize_image_loop(UserRecord *h, HomeSetup *setup, uint64_t old_image_size, uint64_t new_image_size, uint64_t *ret_image_size); int run_mark_dirty(int fd, bool b) { char x = '1'; @@ -1914,36 +1915,33 @@ static int wait_for_devlink(const char *path) { } } -static int calculate_disk_size(UserRecord *h, const char *parent_dir, uint64_t *ret) { +static int calculate_initial_image_size(UserRecord *h, int image_fd, const char *fstype, uint64_t *ret) { + uint64_t upper_boundary, lower_boundary; struct statfs sfs; - uint64_t m; assert(h); - assert(parent_dir); + assert(image_fd >= 0); assert(ret); - if (h->disk_size != UINT64_MAX) { - *ret = DISK_SIZE_ROUND_DOWN(h->disk_size); - return 0; - } + if (fstatfs(image_fd, &sfs) < 0) + return log_error_errno(errno, "statfs() on image failed: %m"); - if (statfs(parent_dir, &sfs) < 0) - return log_error_errno(errno, "statfs() on %s failed: %m", parent_dir); + upper_boundary = DISK_SIZE_ROUND_DOWN((uint64_t) sfs.f_bsize * sfs.f_bavail); - m = sfs.f_bsize * sfs.f_bavail; + if (h->disk_size != UINT64_MAX) + *ret = MIN(DISK_SIZE_ROUND_DOWN(h->disk_size), upper_boundary); + else if (h->disk_size_relative == UINT64_MAX) { - if (h->disk_size_relative == UINT64_MAX) { - - if (m > UINT64_MAX / USER_DISK_SIZE_DEFAULT_PERCENT) + if (upper_boundary > UINT64_MAX / USER_DISK_SIZE_DEFAULT_PERCENT) return log_error_errno(SYNTHETIC_ERRNO(EOVERFLOW), "Disk size too large."); - *ret = DISK_SIZE_ROUND_DOWN(m * USER_DISK_SIZE_DEFAULT_PERCENT / 100); + *ret = DISK_SIZE_ROUND_DOWN(upper_boundary * USER_DISK_SIZE_DEFAULT_PERCENT / 100); log_info("Sizing home to %u%% of available disk space, which is %s.", USER_DISK_SIZE_DEFAULT_PERCENT, FORMAT_BYTES(*ret)); } else { - *ret = DISK_SIZE_ROUND_DOWN((uint64_t) ((double) m * (double) h->disk_size_relative / (double) UINT32_MAX)); + *ret = DISK_SIZE_ROUND_DOWN((uint64_t) ((double) upper_boundary * (double) CLAMP(h->disk_size_relative, 0U, UINT32_MAX) / (double) UINT32_MAX)); log_info("Sizing home to %" PRIu64 ".%01" PRIu64 "%% of available disk space, which is %s.", (h->disk_size_relative * 100) / UINT32_MAX, @@ -1951,8 +1949,12 @@ static int calculate_disk_size(UserRecord *h, const char *parent_dir, uint64_t * FORMAT_BYTES(*ret)); } - if (*ret < USER_DISK_SIZE_MIN) - *ret = USER_DISK_SIZE_MIN; + lower_boundary = minimal_size_by_fs_name(fstype); + if (lower_boundary == UINT64_MAX || lower_boundary < USER_DISK_SIZE_MIN) + lower_boundary = USER_DISK_SIZE_MIN; + + if (*ret < lower_boundary) + *ret = lower_boundary; return 0; } @@ -2141,22 +2143,11 @@ int home_create_luks( log_info("Full device discard completed."); } } else { - _cleanup_free_ char *parent = NULL, *t = NULL; + _cleanup_free_ char *t = NULL; - parent = dirname_malloc(ip); - if (!parent) - return log_oom(); - - r = mkdir_p(parent, 0755); + r = mkdir_parents(ip, 0755); if (r < 0) - return log_error_errno(r, "Failed to create parent directory %s: %m", parent); - - r = calculate_disk_size(h, parent, &host_size); - if (r < 0) - return r; - - if (!supported_fs_size(fstype, host_size)) - return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Selected file system size too small for %s.", fstype); + return log_error_errno(r, "Failed to create parent directory of %s: %m", ip); r = tempfn_random(ip, "homework", &t); if (r < 0) @@ -2173,7 +2164,11 @@ int home_create_luks( log_full_errno(ERRNO_IS_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING, r, "Failed to set file attributes on %s, ignoring: %m", setup->temporary_image_path); - r = home_truncate(h, setup->image_fd, host_size); + r = calculate_initial_image_size(h, setup->image_fd, fstype, &host_size); + if (r < 0) + return r; + + r = resize_image_loop(h, setup, 0, host_size, &host_size); if (r < 0) return r; @@ -2351,7 +2346,7 @@ int home_create_luks( if (disk_uuid_path) (void) wait_for_devlink(disk_uuid_path); - log_info("Everything completed."); + log_info("Creation completed."); print_size_summary(host_size, encrypted_size, &sfs); From 31ea1bfec1e56fd2697839f0471609528b809f89 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 4 Nov 2021 23:49:17 +0100 Subject: [PATCH 2/4] homework: correct initial minimal fs size calculations by LUKS2/GPT overhead So far we assumed we ignore the LUKS2/GPT header overhead when determining what the lower bound for images sizes is. Let's correct this. --- src/home/homework-luks.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index 0beee8ecfd..7bd4ff6c8d 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -63,6 +63,10 @@ _x > UINT64_MAX - 4095U ? UINT64_MAX : (_x + 4095U) & ~UINT64_C(4095); \ }) +/* How much larger will the image on disk be than the fs inside it, i.e. the space we pay for the GPT and + * LUKS2 envelope. (As measured on cryptsetup 2.4.1) */ +#define GPT_LUKS2_OVERHEAD UINT64_C(18874368) + static int resize_image_loop(UserRecord *h, HomeSetup *setup, uint64_t old_image_size, uint64_t new_image_size, uint64_t *ret_image_size); int run_mark_dirty(int fd, bool b) { @@ -1782,6 +1786,8 @@ static int make_partition_table( assert(first_lba <= UINT64_MAX/512); start = DISK_SIZE_ROUND_UP(first_lba * 512); /* Round up to multiple of 4K */ + log_debug("Starting partition at offset %" PRIu64, start); + if (start == UINT64_MAX) return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Overflow while rounding up start LBA."); @@ -1950,6 +1956,10 @@ static int calculate_initial_image_size(UserRecord *h, int image_fd, const char } lower_boundary = minimal_size_by_fs_name(fstype); + if (lower_boundary != UINT64_MAX) { + assert(GPT_LUKS2_OVERHEAD < UINT64_MAX - lower_boundary); + lower_boundary += GPT_LUKS2_OVERHEAD; + } if (lower_boundary == UINT64_MAX || lower_boundary < USER_DISK_SIZE_MIN) lower_boundary = USER_DISK_SIZE_MIN; @@ -2123,7 +2133,7 @@ int home_create_luks( else host_size = DISK_SIZE_ROUND_DOWN(h->disk_size); - if (!supported_fs_size(fstype, host_size)) + if (!supported_fs_size(fstype, LESS_BY(host_size, GPT_LUKS2_OVERHEAD))) return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Selected file system size too small for %s.", fstype); @@ -2350,6 +2360,8 @@ int home_create_luks( print_size_summary(host_size, encrypted_size, &sfs); + log_debug("GPT + LUKS2 overhead is %" PRIu64 " (expected %" PRIu64 ")", host_size - encrypted_size, GPT_LUKS2_OVERHEAD); + *ret_home = TAKE_PTR(new_home); return 0; } From 078dfb0641787d7b06cef8b8bbe6a0c1aa96378a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 19 Nov 2021 09:58:34 +0100 Subject: [PATCH 3/4] man: document min/max for --disk-space= too --- man/homectl.xml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/man/homectl.xml b/man/homectl.xml index 89af3d77c7..76743e9d65 100644 --- a/man/homectl.xml +++ b/man/homectl.xml @@ -504,10 +504,13 @@ BYTES Either takes a size in bytes as argument (possibly using the usual K, M, G, … - suffixes for 1024 base values), or a percentage value and configures the disk space to assign to the - user. If a percentage value is specified (i.e. the argument suffixed with %) it is - taken relative to the available disk space of the backing file system. If the LUKS2 backend is used - this configures the size of the loopback file and file system contained therein. For the other + suffixes for 1024 base values), a percentage value, or the special strings min or + max, and configures the disk space to assign to the user. If a percentage value is + specified (i.e. the argument suffixed with %) it is taken relative to the + available disk space of the backing file system. If specified as min assigns the + minimal disk space permitted by the constraints of the backing file system and other limits, when + specified as max assigns the maximum disk space available. If the LUKS2 backend is + used this configures the size of the loopback file and file system contained therein. For the other storage backends configures disk quota using the filesystem's native quota logic, if available. If not specified, defaults to 85% of the available disk space for the LUKS2 backend and to no quota for the others. From 75f6ae064e80b830d3320e1c01c4ab052b3298a1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 19 Nov 2021 09:58:50 +0100 Subject: [PATCH 4/4] test: update test to use --disk-size=min --- test/units/testsuite-46.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/units/testsuite-46.sh b/test/units/testsuite-46.sh index 16329fec36..daec1536b0 100755 --- a/test/units/testsuite-46.sh +++ b/test/units/testsuite-46.sh @@ -35,7 +35,7 @@ mount -t tmpfs tmpfs /home-pool -o size=290M # needlessly pressure for storage. We also set the cheapest KDF, since we don't # want to waste CI CPU cycles on it. NEWPASSWORD=xEhErW0ndafV4s homectl create test-user \ - --disk-size=256M \ + --disk-size=min \ --luks-discard=yes \ --image-path=/home-pool/test-user.home \ --luks-pbkdf-type=pbkdf2 \