Fixes & improvments for using homed-luks on 4k disks (#35776)

Mostly consists of fixes to 

- use the same sector_size as the fdisk context we are using, when
converting between sectors returned by libfdisk to bytes. Fixes #30394 ,
Fixes #30393
- Use the explicit sector size if specified in the home record when are
probing the image file using libblkid. Fixes #30393

Also contains some other improvements with using physical block devices.

- Automatically probe sector size of physical block device, if user does
not pass luks-sector-size explicitly.
- Assign partitions to 1 MiB boundaries, as it is the standard practice
followed by all tools, fdisk, gptfdisk, gnu parted etc.
- Avoid stacking of loop device on top of physical block device in
home_create_luks as it leads to degradation of discard operations, and
mkfs getting stuck.
This commit is contained in:
Yu Watanabe
2026-02-17 23:23:23 +09:00
committed by GitHub
+130 -67
View File
@@ -66,16 +66,16 @@
#include "user-record.h"
#include "user-util.h"
/* 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
/* Round down to the nearest 1 MiB size. Given that most tools generally align partitions to 1 MiB boundaries, let's align our
* partitions to that too. In the worst case we'll waste 1 MiB per partition that way, but I think I can live
* with that. */
#define DISK_SIZE_ROUND_DOWN(x) ((x) & ~UINT64_C(4095))
#define DISK_SIZE_ROUND_DOWN(x) ((x) & ~(U64_MB - 1))
/* Rounds up to the nearest 4K boundary. Returns UINT64_MAX on overflow */
/* Rounds up to the nearest 1 MiB 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); \
_x > UINT64_MAX - (U64_MB - 1) ? UINT64_MAX : (DISK_SIZE_ROUND_DOWN(_x + U64_MB - 1)); \
})
/* How much larger will the image on disk be than the fs inside it, i.e. the space we pay for the GPT and
@@ -666,6 +666,7 @@ static int luks_validate(
int fd,
const char *label,
sd_id128_t partition_uuid,
uint64_t sector_size,
sd_id128_t *ret_partition_uuid,
uint64_t *ret_offset,
uint64_t *ret_size) {
@@ -683,6 +684,7 @@ static int luks_validate(
assert(label);
assert(ret_offset);
assert(ret_size);
assert(sector_size > 0);
r = dlopen_libblkid();
if (r < 0)
@@ -697,6 +699,12 @@ static int luks_validate(
if (r != 0)
return errno_or_else(ENOMEM);
/* Set probing sector size if explicitly specified */
if (sector_size != UINT32_MAX) {
r = sym_blkid_probe_set_sectorsize(b, sector_size);
if (r != 0)
return errno_or_else(EINVAL);
}
(void) sym_blkid_probe_enable_superblocks(b, 1);
(void) sym_blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE);
(void) sym_blkid_probe_enable_partitions(b, 1);
@@ -778,6 +786,7 @@ static int luks_validate(
if ((uint64_t) size > UINT64_MAX / 512U)
return -EINVAL;
/* libblkid returns partitions sizes in count of 512-sectors. This does not necessarily need to match the device sector size */
*ret_offset = offset * 512U;
*ret_size = size * 512U;
*ret_partition_uuid = found_partition_uuid;
@@ -1401,7 +1410,15 @@ int home_setup_luks(
if (!subdir)
return log_oom();
r = luks_validate(setup->image_fd, user_record_user_name_and_realm(h), h->partition_uuid, &found_partition_uuid, &offset, &size);
r = luks_validate(
setup->image_fd,
user_record_user_name_and_realm(h),
h->partition_uuid,
/* if sector size is not specified, select UINT32_MAX, i.e. auto-probe */
h->luks_sector_size == UINT64_MAX ? UINT32_MAX : user_record_luks_sector_size(h),
&found_partition_uuid,
&offset,
&size);
if (r < 0)
return log_error_errno(r, "Failed to validate disk label: %m");
@@ -1416,6 +1433,11 @@ int home_setup_luks(
return r;
}
/* Before we make the loop device, make sure offset is zero & we are using the full partition
* If our offset is not zero, loop_device_make will create a loop device on top of the block device */
if (S_ISBLK(st.st_mode))
assert(offset == 0 && size == UINT64_MAX);
r = loop_device_make(
setup->image_fd,
O_RDWR,
@@ -1755,6 +1777,7 @@ static int luks_format(
const PasswordCache *cache,
char **effective_passwords,
bool discard,
uint64_t sector_size,
UserRecord *hr,
struct crypt_device **ret) {
@@ -1809,7 +1832,7 @@ static int luks_format(
&(struct crypt_params_luks2) {
.label = label,
.subsystem = "systemd-home",
.sector_size = user_record_luks_sector_size(hr),
.sector_size = sector_size, /* sector-size of 0 is auto for libcryptsetup */
.pbkdf = &good_pbkdf,
});
if (r < 0)
@@ -1887,7 +1910,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 *disk_uuid_as_string = NULL;
uint64_t offset, size, first_lba, start, last_lba, end;
uint64_t offset, size, first_lba, start, last_lba, end, fdisk_sector_size;
sd_id128_t disk_uuid;
int r;
@@ -1924,9 +1947,13 @@ static int make_partition_table(
if (r < 0)
return log_error_errno(r, "Failed to place partition at first free partition index: %m");
/* Use same sector size as the fdisk context when converting to bytes */
fdisk_sector_size = fdisk_get_sector_size(c);
assert(fdisk_sector_size > 0);
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 */
assert(first_lba <= UINT64_MAX / fdisk_sector_size);
start = DISK_SIZE_ROUND_UP(first_lba * fdisk_sector_size);
log_debug("Starting partition at offset %" PRIu64, start);
@@ -1934,17 +1961,17 @@ static int make_partition_table(
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 */
assert(last_lba < UINT64_MAX / fdisk_sector_size);
end = DISK_SIZE_ROUND_DOWN((last_lba + 1) * fdisk_sector_size);
if (end <= start)
return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Resulting partition size zero or negative.");
r = fdisk_partition_set_start(p, start / 512);
r = fdisk_partition_set_start(p, start / fdisk_sector_size);
if (r < 0)
return log_error_errno(r, "Failed to place partition at offset %" PRIu64 ": %m", start);
r = fdisk_partition_set_size(p, (end - start) / 512);
r = fdisk_partition_set_size(p, (end - start) / fdisk_sector_size);
if (r < 0)
return log_error_errno(r, "Failed to end partition at offset %" PRIu64 ": %m", end);
@@ -1978,16 +2005,16 @@ static int make_partition_table(
assert(fdisk_partition_has_start(q));
offset = fdisk_partition_get_start(q);
if (offset > UINT64_MAX / 512U)
if (offset > UINT64_MAX / fdisk_sector_size)
return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Partition offset too large.");
assert(fdisk_partition_has_size(q));
size = fdisk_partition_get_size(q);
if (size > UINT64_MAX / 512U)
if (size > UINT64_MAX / fdisk_sector_size)
return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Partition size too large.");
*ret_offset = offset * 512U;
*ret_size = size * 512U;
*ret_offset = offset * fdisk_sector_size;
*ret_size = size * fdisk_sector_size;
*ret_disk_uuid = disk_uuid;
return 0;
@@ -2162,13 +2189,14 @@ int home_create_luks(
UserRecord **ret_home) {
_cleanup_free_ char *subdir = NULL, *disk_uuid_path = NULL;
uint64_t encrypted_size,
uint64_t encrypted_size, image_sector_size, luks_sector_size,
host_size = 0, partition_offset = 0, partition_size = 0; /* Unnecessary initialization to appease gcc */
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
sd_id128_t partition_uuid, fs_uuid, luks_uuid, disk_uuid;
_cleanup_close_ int mount_fd = -EBADF;
const char *fstype, *ip;
struct statfs sfs;
struct stat st;
int r;
_cleanup_strv_free_ char **extra_mkfs_options = NULL;
@@ -2241,7 +2269,6 @@ int home_create_luks(
if (path_startswith(ip, "/dev/")) {
_cleanup_free_ char *sysfs = NULL;
uint64_t block_device_size;
struct stat st;
/* Let's place the home directory on a real device, i.e. a USB stick or such */
@@ -2333,9 +2360,23 @@ int home_create_luks(
log_info("Allocating image file completed.");
}
if (h->luks_sector_size == UINT64_MAX) {
/* If sector size is not specified, select UINT32_MAX, i.e. auto-probe */
image_sector_size = UINT32_MAX;
/* Let cryptsetup decide if the sector size is not specified in home record */
luks_sector_size = 0;
} else {
if (S_ISBLK(st.st_mode)) {
/* For physical block devices always use the actual device logical
* sector size. Else the partition will not be discoverable by kernel. */
image_sector_size = UINT32_MAX;
luks_sector_size = user_record_luks_sector_size(h);
} else
image_sector_size = luks_sector_size = user_record_luks_sector_size(h);
}
r = make_partition_table(
setup->image_fd,
user_record_luks_sector_size(h),
image_sector_size,
user_record_user_name_and_realm(h),
partition_uuid,
&partition_offset,
@@ -2346,21 +2387,48 @@ int home_create_luks(
log_info("Writing of partition table completed.");
r = loop_device_make(
setup->image_fd,
O_RDWR,
partition_offset,
partition_size,
user_record_luks_sector_size(h),
0,
LOCK_EX,
&setup->loop);
if (r == -ENOENT) /* this means /dev/loop-control doesn't exist, i.e. we are in a container
* or similar and loopback bock devices are not available, return a
* recognizable error in this case. */
return log_error_errno(SYNTHETIC_ERRNO(ENOLINK), "Loopback block device support is not available on this system.");
if (r < 0)
return log_error_errno(r, "Failed to set up loopback device for %s: %m", setup->temporary_image_path);
if (fstat(setup->image_fd, &st) < 0)
return log_error_errno(errno, "Failed to fstat home image: %m");
/* Ensure we don't create a loop device over block device as it leads to huge overhead for discard operations
* if the device does not support discard_zeroes_data */
if (S_ISBLK(st.st_mode)) {
_cleanup_free_ char *partition_path = NULL;
assert(!sd_id128_is_null(partition_uuid));
if (asprintf(&partition_path, "/dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(partition_uuid)) < 0)
return log_oom();
/* Release the lock, so that udev can find the partition */
setup->image_fd = safe_close(setup->image_fd);
(void) wait_for_devlink(partition_path);
setup->image_fd = open_image_file(h, ip, &st);
if (setup->image_fd < 0)
return setup->image_fd;
r = loop_device_open_from_path(
partition_path,
O_RDWR,
LOCK_EX,
&setup->loop);
if (r < 0)
return log_error_errno(r, "Failed to open newly written partition device: %s", partition_path);
} else {
r = loop_device_make(
setup->image_fd,
O_RDWR,
partition_offset,
partition_size,
image_sector_size,
0,
LOCK_EX,
&setup->loop);
if (r == -ENOENT) /* this means /dev/loop-control doesn't exist, i.e. we are in a container
* or similar and loopback bock devices are not available, return a
* recognizable error in this case. */
return log_error_errno(SYNTHETIC_ERRNO(ENOLINK), "Loopback block device support is not available on this system.");
if (r < 0)
return log_error_errno(r, "Failed to set up loopback device for %s: %m", setup->temporary_image_path);
}
log_info("Setting up loopback device %s completed.", setup->loop->node ?: ip);
@@ -2371,6 +2439,7 @@ int home_create_luks(
cache,
effective_passwords,
user_record_luks_discard(h) || user_record_luks_offline_discard(h),
luks_sector_size,
h,
&setup->crypt_device);
if (r < 0)
@@ -2753,6 +2822,7 @@ static int prepare_resize_partition(
n_partitions = fdisk_table_get_nents(t);
for (size_t i = 0; i < n_partitions; i++) {
struct fdisk_partition *p;
uint64_t fdisk_sector_size;
p = fdisk_table_get_partition(t, i);
if (!p)
@@ -2763,14 +2833,16 @@ static int prepare_resize_partition(
if (fdisk_partition_has_start(p) <= 0 || fdisk_partition_has_size(p) <= 0 || fdisk_partition_has_end(p) <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found partition without a size.");
if (fdisk_partition_get_start(p) == partition_offset / 512U &&
fdisk_partition_get_size(p) == old_partition_size / 512U) {
fdisk_sector_size = fdisk_get_sector_size(c);
assert(fdisk_sector_size > 0);
if (fdisk_partition_get_start(p) == partition_offset / fdisk_sector_size &&
fdisk_partition_get_size(p) == old_partition_size / fdisk_sector_size) {
if (found)
return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "Partition found twice, refusing.");
found = p;
} else if (fdisk_partition_get_end(p) > partition_offset / 512U)
} else if (fdisk_partition_get_end(p) > partition_offset / fdisk_sector_size)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't extend, not last partition in image.");
}
@@ -2790,7 +2862,7 @@ static int get_maximum_partition_size(
uint64_t *ret_maximum_partition_size) {
_cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
uint64_t start_lba, start, last_lba, end;
uint64_t start_lba, start, last_lba, end, fdisk_sector_size;
int r;
assert(fd >= 0);
@@ -2801,13 +2873,15 @@ static int get_maximum_partition_size(
if (r < 0)
return log_error_errno(r, "Failed to create fdisk context: %m");
/* Get the probed sector size by fdisk */
fdisk_sector_size = fdisk_get_sector_size(c);
start_lba = fdisk_partition_get_start(p);
assert(start_lba <= UINT64_MAX/512);
start = start_lba * 512;
assert(start_lba <= UINT64_MAX / fdisk_sector_size);
start = start_lba * fdisk_sector_size;
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 */
assert(last_lba < UINT64_MAX / fdisk_sector_size);
end = DISK_SIZE_ROUND_DOWN((last_lba + 1) * fdisk_sector_size);
if (start > end)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Last LBA is before partition start.");
@@ -2847,9 +2921,7 @@ static int apply_resize_partition(
size_t new_partition_size) {
_cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
_cleanup_free_ void *two_zero_lbas = NULL;
uint32_t ssz;
ssize_t n;
int r;
assert(fd >= 0);
@@ -2860,34 +2932,23 @@ static int apply_resize_partition(
assert(p);
r = probe_sector_size(fd, &ssz);
if (r < 0)
return log_error_errno(r, "Failed to determine current sector size: %m");
r = fdisk_new_context_at(fd, /* path= */ NULL, /* read_only= */ false, ssz, &c);
if (r < 0)
return log_error_errno(r, "Failed to open device: %m");
/* 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);
r = fdisk_partition_set_size(p, new_partition_size / ssz);
if (r < 0)
return log_error_errno(r, "Failed to change partition size: %m");
r = probe_sector_size(fd, &ssz);
if (r < 0)
return log_error_errno(r, "Failed to determine current sector size: %m");
two_zero_lbas = malloc0(ssz * 2);
if (!two_zero_lbas)
return log_oom();
/* libfdisk appears to get confused by the existing PMBR. Let's explicitly flush it out. */
n = pwrite(fd, two_zero_lbas, ssz * 2, 0);
if (n < 0)
return log_error_errno(errno, "Failed to wipe partition table: %m");
if ((size_t) n != ssz * 2)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while wiping partition table.");
r = fdisk_new_context_at(fd, /* path= */ NULL, /* read_only= */ false, ssz, &c);
if (r < 0)
return log_error_errno(r, "Failed to open device: %m");
r = fdisk_create_disklabel(c, "gpt");
if (r < 0)
return log_error_errno(r, "Failed to create GPT disk label: %m");
@@ -3472,6 +3533,7 @@ int home_resize_luks(
(void) reread_partition_table_fd(image_fd, /* flags= */ 0);
/* Tell LUKS about the new bigger size too */
/* libcrypsetup uses units of 512B sectors for size */
r = sym_crypt_resize(setup->crypt_device, setup->dm_name, new_fs_size / 512U);
if (r < 0)
return log_error_errno(r, "Failed to grow LUKS device: %m");
@@ -3536,7 +3598,8 @@ int home_resize_luks(
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);
/* libcrypsetup uses units of 512B sectors for size */
r = sym_crypt_resize(setup->crypt_device, setup->dm_name, new_fs_size / 512U);
if (r < 0)
return log_error_errno(r, "Failed to shrink LUKS device: %m");