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:
+130
-67
@@ -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");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user