summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2021-08-20 18:40:53 +0200
committerLennart Poettering <lennart@poettering.net>2021-08-25 22:29:07 +0200
commit04190cf1cf93f57c2114e902f6e97fc746a11df2 (patch)
treeded20c4665c692e376d3858e3524f99be181ed5f
parente447ffe4daca1d0beb57242f079125669e4e1c3c (diff)
downloadsystemd-04190cf1cf93f57c2114e902f6e97fc746a11df2.tar.gz
homed: always align home file systems to 4K boundaries
Let's carefully align all home file systems to 4K sector boundaries. It's the safest thing to do, to ensure good perfomance on 4K sector drives, i.e. today's hardware. Yes, this means we'll waste 3.5K when resizing home dirs, but I think we can live with that. This ensures both the offsets where we start and the sizes of the file systems/partitions/disk images are multiples of 4K always, both when creating a new image and when resizing things. Note that previously we aligned everything to 1024, but weren't quite as careful.
-rw-r--r--src/home/homework-luks.c69
1 files changed, 52 insertions, 17 deletions
diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c
index a2d7d388e0..2321a31021 100644
--- a/src/home/homework-luks.c
+++ b/src/home/homework-luks.c
@@ -38,10 +38,18 @@
#include "strv.h"
#include "tmpfile-util.h"
-/* Round down to the nearest 1K size. Note that Linux generally handles block devices with 512 blocks only,
- * but actually doesn't accept uneven numbers in many cases. To avoid any confusion around this we'll
- * strictly round disk sizes down to the next 1K boundary. */
-#define DISK_SIZE_ROUND_DOWN(x) ((x) & ~UINT64_C(1023))
+/* Round down to the nearest 4K size. Given that newer hardware generally prefers 4K sectors, let's align our
+ * partitions to that too. In the worst case we'll waste 3.5K per partition that way, but I think I can live
+ * with that. */
+#define DISK_SIZE_ROUND_DOWN(x) ((x) & ~UINT64_C(4095))
+
+/* Rounds up to the nearest 4K boundary. Returns UINT64_MAX on overflow */
+#define DISK_SIZE_ROUND_UP(x) \
+ ({ \
+ uint64_t _x = (x); \
+ _x > UINT64_MAX - 4095U ? UINT64_MAX : (_x + 4095U) & ~UINT64_C(4095); \
+ })
+
int run_mark_dirty(int fd, bool b) {
char x = '1';
@@ -1620,7 +1628,7 @@ static int make_partition_table(
_cleanup_(fdisk_unref_parttypep) struct fdisk_parttype *t = NULL;
_cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
_cleanup_free_ char *path = NULL, *disk_uuid_as_string = NULL;
- uint64_t offset, size;
+ uint64_t offset, size, first_lba, start, last_lba, end;
sd_id128_t disk_uuid;
int r;
@@ -1660,17 +1668,31 @@ static int make_partition_table(
if (r < 0)
return log_error_errno(r, "Failed to set partition type: %m");
- r = fdisk_partition_start_follow_default(p, 1);
- if (r < 0)
- return log_error_errno(r, "Failed to place partition at beginning of space: %m");
-
r = fdisk_partition_partno_follow_default(p, 1);
if (r < 0)
return log_error_errno(r, "Failed to place partition at first free partition index: %m");
- r = fdisk_partition_end_follow_default(p, 1);
+ first_lba = fdisk_get_first_lba(c); /* Boundary where usable space starts */
+ assert(first_lba <= UINT64_MAX/512);
+ start = DISK_SIZE_ROUND_UP(first_lba * 512); /* Round up to multiple of 4K */
+
+ if (start == UINT64_MAX)
+ return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Overflow while rounding up start LBA.");
+
+ last_lba = fdisk_get_last_lba(c); /* One sector before boundary where usable space ends */
+ assert(last_lba < UINT64_MAX/512);
+ end = DISK_SIZE_ROUND_DOWN((last_lba + 1) * 512); /* Round down to multiple of 4K */
+
+ if (end <= start)
+ return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Resulting partition size zero or negative.");
+
+ r = fdisk_partition_set_start(p, start / 512);
if (r < 0)
- return log_error_errno(r, "Failed to make partition cover all free space: %m");
+ return log_error_errno(r, "Failed to place partition at offset %" PRIu64 ": %m", start);
+
+ r = fdisk_partition_set_size(p, (end - start) / 512);
+ if (r < 0)
+ return log_error_errno(r, "Failed to end partition at offset %" PRIu64 ": %m", end);
r = fdisk_partition_set_name(p, label);
if (r < 0)
@@ -2692,6 +2714,8 @@ 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);
@@ -2702,11 +2726,16 @@ 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 = DISK_SIZE_ROUND_DOWN(h->disk_size);
- if (new_image_size == old_image_size) {
+ new_image_size_rounded = DISK_SIZE_ROUND_DOWN(h->disk_size);
+
+ 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 = new_image_size_rounded;
}
r = home_prepare_luks(h, already_activated, whole_disk, cache, setup, &header_home);
@@ -2730,15 +2759,21 @@ int home_resize_luks(
if (new_image_size <= partition_table_extra)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "New size smaller than partition table metadata.");
- new_partition_size = 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 = DISK_SIZE_ROUND_DOWN(h->disk_size);
- if (new_partition_size == setup->partition_size) {
+ new_partition_size_rounded = DISK_SIZE_ROUND_DOWN(h->disk_size);
+
+ 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 = new_partition_size_rounded;
}
if ((UINT64_MAX - setup->partition_offset) < new_partition_size ||
@@ -2752,7 +2787,7 @@ int home_resize_luks(
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 = (new_partition_size / 512U - crypto_offset) * 512U;
+ new_fs_size = DISK_SIZE_ROUND_DOWN((new_partition_size / 512U - crypto_offset) * 512U);
/* 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);