From 65046b92dcdc017f34e170a0e0f46ffc80b1dcdc Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 17 Jan 2023 20:12:30 +0100 Subject: [PATCH 1/7] blockdev-util: add simple wrapper around BLKSSZGET Just adds some typesafety and generates an error if the field is not initialized in the block device yet. --- src/shared/blockdev-util.c | 15 +++++++++++++++ src/shared/blockdev-util.h | 2 ++ src/shared/loop-util.c | 12 ++++++------ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/shared/blockdev-util.c b/src/shared/blockdev-util.c index 7a4d21d02e..ee134146a1 100644 --- a/src/shared/blockdev-util.c +++ b/src/shared/blockdev-util.c @@ -762,3 +762,18 @@ int blockdev_reread_partition_table(sd_device *dev) { return 0; } + +int blockdev_get_sector_size(int fd, uint32_t *ret) { + int ssz = 0; + + assert(fd >= 0); + assert(ret); + + if (ioctl(fd, BLKSSZGET, &ssz) < 0) + return -errno; + if (ssz <= 0) /* make sure the field is initialized */ + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Block device reported invalid sector size %i.", ssz); + + *ret = ssz; + return 0; +} diff --git a/src/shared/blockdev-util.h b/src/shared/blockdev-util.h index b2c14102ae..5b27d23e8a 100644 --- a/src/shared/blockdev-util.h +++ b/src/shared/blockdev-util.h @@ -54,3 +54,5 @@ int partition_enumerator_new(sd_device *dev, sd_device_enumerator **ret); int block_device_remove_all_partitions(sd_device *dev, int fd); int block_device_has_partitions(sd_device *dev); int blockdev_reread_partition_table(sd_device *dev); + +int blockdev_get_sector_size(int fd, uint32_t *ret); diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c index 3224286186..4b63e0340b 100644 --- a/src/shared/loop-util.c +++ b/src/shared/loop-util.c @@ -125,14 +125,14 @@ static int loop_configure_verify(int fd, const struct loop_config *c) { assert(c); if (c->block_size != 0) { - int z; + uint32_t ssz; - if (ioctl(fd, BLKSSZGET, &z) < 0) - return -errno; + r = blockdev_get_sector_size(fd, &ssz); + if (r < 0) + return r; - assert(z >= 0); - if ((uint32_t) z != c->block_size) - log_debug("LOOP_CONFIGURE didn't honour requested block size %u, got %i instead. Ignoring.", c->block_size, z); + if (ssz != c->block_size) + log_debug("LOOP_CONFIGURE didn't honour requested block size %" PRIu32 ", got %" PRIu32 " instead. Ignoring.", c->block_size, ssz); } if (c->info.lo_sizelimit != 0) { From 1163ddb386ef46f63942171e6eab0ca64eb818e4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 17 Jan 2023 15:49:31 +0100 Subject: [PATCH 2/7] loop-util: insist on setting the sector size correctly If we attach a disk image to a loopback device the sector size of the image must match the one of the loopback device, hence be more careful here. --- src/shared/loop-util.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c index 4b63e0340b..f6ae769d89 100644 --- a/src/shared/loop-util.c +++ b/src/shared/loop-util.c @@ -131,8 +131,10 @@ static int loop_configure_verify(int fd, const struct loop_config *c) { if (r < 0) return r; - if (ssz != c->block_size) + if (ssz != c->block_size) { log_debug("LOOP_CONFIGURE didn't honour requested block size %" PRIu32 ", got %" PRIu32 " instead. Ignoring.", c->block_size, ssz); + broken = true; + } } if (c->info.lo_sizelimit != 0) { @@ -173,6 +175,7 @@ static int loop_configure_verify(int fd, const struct loop_config *c) { static int loop_configure_fallback(int fd, const struct loop_config *c) { struct loop_info64 info_copy; + int r; assert(fd >= 0); assert(c); @@ -220,6 +223,21 @@ static int loop_configure_fallback(int fd, const struct loop_config *c) { if (ioctl(fd, BLKFLSBUF, 0) < 0) log_debug_errno(errno, "Failed to issue BLKFLSBUF ioctl, ignoring: %m"); + /* If a block size is requested then try to configure it. If that doesn't work, ignore errors, but + * afterwards, let's validate what is in effect, and if it doesn't match what we want, fail */ + if (c->block_size != 0) { + uint32_t ssz; + + if (ioctl(fd, LOOP_SET_BLOCK_SIZE, (unsigned long) c->block_size) < 0) + log_debug_errno(errno, "Failed to set sector size, ignoring: %m"); + + r = blockdev_get_sector_size(fd, &ssz); + if (r < 0) + return log_debug_errno(r, "Failed to read sector size: %m"); + if (ssz != c->block_size) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Sector size of loopback device doesn't match what we requested, refusing."); + } + /* LO_FLAGS_DIRECT_IO is a flags we need to configure via explicit ioctls. */ if (FLAGS_SET(c->info.lo_flags, LO_FLAGS_DIRECT_IO)) if (ioctl(fd, LOOP_SET_DIRECT_IO, 1UL) < 0) From 05c4c59ff127668ddaa85f0a9fd67cee3c41ce00 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 17 Jan 2023 18:06:05 +0100 Subject: [PATCH 3/7] dissect-image: add probe_sector_size() helper for detecting sector size of a GPT disk image When we operate with DDIs with sector sizes != 512 we need to configure the loopback device to match it, otherwise the image and the kernel block device will disagree what things are. Let's add a prober that tries to determine the sector size of a GPT DDI. It does this by looking for the GPT partition table header at the various byte offsets they must be located on, given a specific sector size. It will try sector size 512, 1024, 2048 and 4096. Of these only the 512 and 4096 really make sense IRL I guess, but let's be thorough. --- src/shared/dissect-image.c | 82 ++++++++++++++++++++++++++++++++++++++ src/shared/dissect-image.h | 2 + 2 files changed, 84 insertions(+) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 45a9e9413c..d78568d344 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -62,6 +62,7 @@ #include "raw-clone.h" #include "resize-fs.h" #include "signal-util.h" +#include "sparse-endian.h" #include "stat-util.h" #include "stdio-util.h" #include "string-table.h" @@ -107,6 +108,87 @@ int dissect_fstype_ok(const char *fstype) { return false; } +int probe_sector_size(int fd, uint32_t *ret) { + + struct gpt_header { + char signature[8]; + le32_t revision; + le32_t header_size; + le32_t crc32; + le32_t reserved; + le64_t my_lba; + le64_t alternate_lba; + le64_t first_usable_lba; + le64_t last_usable_lba; + sd_id128_t disk_guid; + le64_t partition_entry_lba; + le32_t number_of_partition_entries; + le32_t size_of_partition_entry; + le32_t partition_entry_array_crc32; + } _packed_; + + /* Disk images might be for 512B or for 4096 sector sizes, let's try to auto-detect that by searching + * for the GPT headers at the relevant byte offsets */ + + assert_cc(sizeof(struct gpt_header) == 92); + + /* We expect a sector size in the range 512…4096. The GPT header is located in the second + * sector. Hence it could be at byte 512 at the earliest, and at byte 4096 at the latest. And we must + * read with granularity of the largest sector size we care about. Which means 8K. */ + uint8_t sectors[2 * 4096]; + uint32_t found = 0; + ssize_t n; + + assert(fd >= 0); + assert(ret); + + n = pread(fd, sectors, sizeof(sectors), 0); + if (n < 0) + return -errno; + if (n != sizeof(sectors)) /* too short? */ + goto not_found; + + /* Let's see if we find the GPT partition header with various expected sector sizes */ + for (uint32_t sz = 512; sz <= 4096; sz <<= 1) { + struct gpt_header *p; + + assert(sizeof(sectors) >= sz * 2); + p = (struct gpt_header*) (sectors + sz); + + if (memcmp(p->signature, (const char[8]) { 'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T' }, 8) != 0) + continue; + + if (le32toh(p->revision) != UINT32_C(0x00010000)) /* the only known revision of the spec: 1.0 */ + continue; + + if (le32toh(p->header_size) < sizeof(struct gpt_header)) + continue; + + if (le32toh(p->header_size) > 4096) /* larger than a sector? something is off… */ + continue; + + if (le64toh(p->my_lba) != 1) /* this sector must claim to be at sector offset 1 */ + continue; + + if (found != 0) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTUNIQ), + "Detected valid partition table at offsets matching multiple sector sizes, refusing."); + + found = sz; + } + + if (found != 0) { + log_debug("Determined sector size %" PRIu32 " based on discovered partition table.", found); + *ret = found; + return 1; /* indicate we *did* find it */ + } + +not_found: + log_debug("Couldn't find any partition table to derive sector size of."); + *ret = 512; /* pick the traditional default */ + return 0; /* indicate we didn't find it */ +} + int probe_filesystem_full( int fd, const char *path, diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index e9ab1f89a3..638524f716 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -186,3 +186,5 @@ int mount_image_privately_interactively(const char *path, DissectImageFlags flag int verity_dissect_and_mount(int src_fd, const char *src, const char *dest, const MountOptions *options, const char *required_host_os_release_id, const char *required_host_os_release_version_id, const char *required_host_os_release_sysext_level, const char *required_sysext_scope); int dissect_fstype_ok(const char *fstype); + +int probe_sector_size(int fd, uint32_t *ret); From 22ee78a8987f29e7f837efab86ed090ab78c1170 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 17 Jan 2023 18:50:59 +0100 Subject: [PATCH 4/7] loop-util: always tell kernel explicitly about loopback sector size Let's not leave the sector size unspecified: either set a user supplied value, or auto-detect the right size by probing the disk image accordingly. --- src/core/namespace.c | 1 + src/dissect/dissect.c | 4 +-- src/home/homework-luks.c | 20 ++++++++++-- src/nspawn/nspawn.c | 1 + src/portable/portable.c | 2 +- src/shared/discover-image.c | 2 +- src/shared/dissect-image.c | 15 ++++++++- src/shared/dissect-image.h | 2 ++ src/shared/loop-util.c | 63 +++++++++++++++++++++++++++++++++---- src/shared/loop-util.h | 7 +++-- src/sysext/sysext.c | 1 + 11 files changed, 102 insertions(+), 16 deletions(-) diff --git a/src/core/namespace.c b/src/core/namespace.c index d46daa3c05..feae4dcbbf 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -2093,6 +2093,7 @@ int setup_namespace( r = loop_device_make_by_path( root_image, FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : -1 /* < 0 means writable if possible, read-only as fallback */, + /* sector_size= */ UINT32_MAX, FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN, LOCK_SH, &loop_device); diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 25272c5e1b..df08cc2b74 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -1437,9 +1437,9 @@ static int run(int argc, char *argv[]) { loop_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN; if (arg_in_memory) - r = loop_device_make_by_path_memory(arg_image, open_flags, loop_flags, LOCK_SH, &d); + r = loop_device_make_by_path_memory(arg_image, open_flags, /* sector_size= */ UINT32_MAX, loop_flags, LOCK_SH, &d); else - r = loop_device_make_by_path(arg_image, open_flags, loop_flags, LOCK_SH, &d); + r = loop_device_make_by_path(arg_image, open_flags, /* sector_size= */ UINT32_MAX, loop_flags, LOCK_SH, &d); if (r < 0) return log_error_errno(r, "Failed to set up loopback device for %s: %m", arg_image); diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index 3433cf7d40..5ad7706636 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -1378,7 +1378,15 @@ int home_setup_luks( return r; } - r = loop_device_make(setup->image_fd, O_RDWR, offset, size, user_record_luks_sector_size(h), 0, LOCK_UN, &setup->loop); + r = loop_device_make( + setup->image_fd, + O_RDWR, + offset, + size, + h->luks_sector_size == UINT64_MAX ? UINT32_MAX : user_record_luks_sector_size(h), /* if sector size is not specified, select UINT32_MAX, i.e. auto-probe */ + /* loop_flags= */ 0, + LOCK_UN, + &setup->loop); if (r == -ENOENT) { log_error_errno(r, "Loopback block device support is not available on this system."); return -ENOLINK; /* make recognizable */ @@ -2319,7 +2327,15 @@ 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); + 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 < 0) { 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 diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 551f8f975c..9f747a72b1 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -5726,6 +5726,7 @@ static int run(int argc, char *argv[]) { r = loop_device_make_by_path( arg_image, arg_read_only ? O_RDONLY : O_RDWR, + /* sector_size= */ UINT32_MAX, FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN, LOCK_SH, &loop); diff --git a/src/portable/portable.c b/src/portable/portable.c index 7be1afbcc4..7811833fac 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -335,7 +335,7 @@ static int portable_extract_by_path( assert(path); - r = loop_device_make_by_path(path, O_RDONLY, LO_FLAGS_PARTSCAN, LOCK_SH, &d); + r = loop_device_make_by_path(path, O_RDONLY, /* sector_size= */ UINT32_MAX, LO_FLAGS_PARTSCAN, LOCK_SH, &d); if (r == -EISDIR) { _cleanup_free_ char *image_name = NULL; diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c index 1f0f7ca7da..7f1fe4f4dc 100644 --- a/src/shared/discover-image.c +++ b/src/shared/discover-image.c @@ -1197,7 +1197,7 @@ int image_read_metadata(Image *i) { _cleanup_(loop_device_unrefp) LoopDevice *d = NULL; _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL; - r = loop_device_make_by_path(i->path, O_RDONLY, LO_FLAGS_PARTSCAN, LOCK_SH, &d); + r = loop_device_make_by_path(i->path, O_RDONLY, /* sector_size= */ UINT32_MAX, LO_FLAGS_PARTSCAN, LOCK_SH, &d); if (r < 0) return r; diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index d78568d344..da8eba83de 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -546,6 +546,7 @@ static int dissect_image( assert(!verity || verity->root_hash_sig || verity->root_hash_sig_size == 0); assert(!verity || (verity->root_hash || !verity->root_hash_sig)); assert(!((flags & DISSECT_IMAGE_GPT_ONLY) && (flags & DISSECT_IMAGE_NO_PARTITION_TABLE))); + assert(m->sector_size > 0); /* Probes a disk image, and returns information about what it found in *ret. * @@ -594,6 +595,11 @@ static int dissect_image( if (r != 0) return errno_or_else(ENOMEM); + errno = 0; + r = blkid_probe_set_sectorsize(b, m->sector_size); + if (r != 0) + return errno_or_else(EIO); + if ((flags & DISSECT_IMAGE_GPT_ONLY) == 0) { /* Look for file system superblocks, unless we only shall look for GPT partition tables */ blkid_probe_enable_superblocks(b, 1); @@ -1326,6 +1332,10 @@ int dissect_image_file( if (r < 0) return r; + r = probe_sector_size(fd, &m->sector_size); + if (r < 0) + return r; + r = dissect_image(m, fd, path, verity, mount_options, flags); if (r < 0) return r; @@ -3143,6 +3153,7 @@ int dissect_loop_device( return r; m->loop = loop_device_ref(loop); + m->sector_size = m->loop->sector_size; r = dissect_image(m, loop->fd, loop->node, verity, mount_options, flags); if (r < 0) @@ -3318,6 +3329,7 @@ int mount_image_privately_interactively( r = loop_device_make_by_path( image, FLAGS_SET(flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : O_RDWR, + /* sector_size= */ UINT32_MAX, FLAGS_SET(flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN, LOCK_SH, &d); @@ -3409,7 +3421,8 @@ int verity_dissect_and_mount( * accepted by LOOP_CONFIGURE, so just let loop_device_make_by_path reopen it as a regular FD. */ r = loop_device_make_by_path( src_fd >= 0 ? FORMAT_PROC_FD_PATH(src_fd) : src, - -1, + /* open_flags= */ -1, + /* sector_size= */ UINT32_MAX, verity.data_path ? 0 : LO_FLAGS_PARTSCAN, LOCK_SH, &loop_device); diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 638524f716..a69482d237 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -93,6 +93,8 @@ struct DissectedImage { DissectedPartition partitions[_PARTITION_DESIGNATOR_MAX]; DecryptedImage *decrypted_image; + uint32_t sector_size; + /* Meta information extracted from /etc/os-release and similar */ char *image_name; sd_id128_t image_uuid; diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c index f6ae769d89..5945ba2ad4 100644 --- a/src/shared/loop-util.c +++ b/src/shared/loop-util.c @@ -20,6 +20,7 @@ #include "data-fd-util.h" #include "device-util.h" #include "devnum-util.h" +#include "dissect-image.h" #include "env-util.h" #include "errno-util.h" #include "fd-util.h" @@ -409,6 +410,7 @@ static int loop_configure( .diskseq = diskseq, .uevent_seqnum_not_before = seqnum, .timestamp_not_before = timestamp, + .sector_size = c->block_size, }; *ret = TAKE_PTR(d); @@ -421,7 +423,7 @@ static int loop_device_make_internal( int open_flags, uint64_t offset, uint64_t size, - uint32_t block_size, + uint32_t sector_size, uint32_t loop_flags, int lock_op, LoopDevice **ret) { @@ -492,9 +494,50 @@ static int loop_device_make_internal( if (control < 0) return -errno; + if (sector_size == 0) + /* If no sector size is specified, default to the classic default */ + sector_size = 512; + else if (sector_size == UINT32_MAX) { + + if (S_ISBLK(st.st_mode)) + /* If the sector size is specified as UINT32_MAX we'll propagate the sector size of + * the underlying block device. */ + r = blockdev_get_sector_size(fd, §or_size); + else { + _cleanup_close_ int non_direct_io_fd = -1; + int probe_fd; + + assert(S_ISREG(st.st_mode)); + + /* If sector size is specified as UINT32_MAX, we'll try to probe the right sector + * size of the image in question by looking for the GPT partition header at various + * offsets. This of course only works if the image already has a disk label. + * + * So here we actually want to read the file contents ourselves. This is quite likely + * not going to work if we managed to enable O_DIRECT, because in such a case there + * are some pretty strict alignment requirements to offset, size and target, but + * there's no way to query what alignment specifically is actually required. Hence, + * let's avoid the mess, and temporarily open an fd without O_DIRECT for the probing + * logic. */ + + if (FLAGS_SET(loop_flags, LO_FLAGS_DIRECT_IO)) { + non_direct_io_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK); + if (non_direct_io_fd < 0) + return non_direct_io_fd; + + probe_fd = non_direct_io_fd; + } else + probe_fd = fd; + + r = probe_sector_size(probe_fd, §or_size); + } + if (r < 0) + return r; + } + config = (struct loop_config) { .fd = fd, - .block_size = block_size, + .block_size = sector_size, .info = { /* Use the specified flags, but configure the read-only flag from the open flags, and force autoclear */ .lo_flags = (loop_flags & ~LO_FLAGS_READ_ONLY) | ((open_flags & O_ACCMODE) == O_RDONLY ? LO_FLAGS_READ_ONLY : 0) | LO_FLAGS_AUTOCLEAR, @@ -578,7 +621,7 @@ int loop_device_make( int open_flags, uint64_t offset, uint64_t size, - uint32_t block_size, + uint32_t sector_size, uint32_t loop_flags, int lock_op, LoopDevice **ret) { @@ -592,7 +635,7 @@ int loop_device_make( open_flags, offset, size, - block_size, + sector_size, loop_flags_mangle(loop_flags), lock_op, ret); @@ -601,6 +644,7 @@ int loop_device_make( int loop_device_make_by_path( const char *path, int open_flags, + uint32_t sector_size, uint32_t loop_flags, int lock_op, LoopDevice **ret) { @@ -656,12 +700,13 @@ int loop_device_make_by_path( direct ? "enabled" : "disabled", direct != (direct_flags != 0) ? " (O_DIRECT was requested but not supported)" : ""); - return loop_device_make_internal(path, fd, open_flags, 0, 0, 0, loop_flags, lock_op, ret); + return loop_device_make_internal(path, fd, open_flags, 0, 0, sector_size, loop_flags, lock_op, ret); } int loop_device_make_by_path_memory( const char *path, int open_flags, + uint32_t sector_size, uint32_t loop_flags, int lock_op, LoopDevice **ret) { @@ -697,7 +742,7 @@ int loop_device_make_by_path_memory( fd = safe_close(fd); /* Let's close the original early */ - return loop_device_make_internal(NULL, mfd, open_flags, 0, 0, 0, loop_flags, lock_op, ret); + return loop_device_make_internal(NULL, mfd, open_flags, 0, 0, sector_size, loop_flags, lock_op, ret); } static LoopDevice* loop_device_free(LoopDevice *d) { @@ -839,6 +884,11 @@ int loop_device_open( if (r < 0 && r != -EOPNOTSUPP) return r; + uint32_t sector_size; + r = blockdev_get_sector_size(fd, §or_size); + if (r < 0) + return r; + r = sd_device_get_devnum(dev, &devnum); if (r < 0) return r; @@ -868,6 +918,7 @@ int loop_device_open( .diskseq = diskseq, .uevent_seqnum_not_before = UINT64_MAX, .timestamp_not_before = USEC_INFINITY, + .sector_size = sector_size, }; *ret = d; diff --git a/src/shared/loop-util.h b/src/shared/loop-util.h index 512b5ab4a2..5c79ed7cce 100644 --- a/src/shared/loop-util.h +++ b/src/shared/loop-util.h @@ -23,14 +23,15 @@ struct LoopDevice { uint64_t diskseq; /* Block device sequence number, monothonically incremented by the kernel on create/attach, or 0 if we don't know */ uint64_t uevent_seqnum_not_before; /* uevent sequm right before we attached the loopback device, or UINT64_MAX if we don't know */ usec_t timestamp_not_before; /* CLOCK_MONOTONIC timestamp taken immediately before attaching the loopback device, or USEC_INFINITY if we don't know */ + uint32_t sector_size; }; /* Returns true if LoopDevice object is not actually a loopback device but some other block device we just wrap */ #define LOOP_DEVICE_IS_FOREIGN(d) ((d)->nr < 0) -int loop_device_make(int fd, int open_flags, uint64_t offset, uint64_t size, uint32_t block_size, uint32_t loop_flags, int lock_op, LoopDevice **ret); -int loop_device_make_by_path(const char *path, int open_flags, uint32_t loop_flags, int lock_op, LoopDevice **ret); -int loop_device_make_by_path_memory(const char *path, int open_flags, uint32_t loop_flags, int lock_op, LoopDevice **ret); +int loop_device_make(int fd, int open_flags, uint64_t offset, uint64_t size, uint32_t sector_size, uint32_t loop_flags, int lock_op, LoopDevice **ret); +int loop_device_make_by_path(const char *path, int open_flags, uint32_t sector_size, uint32_t loop_flags, int lock_op, LoopDevice **ret); +int loop_device_make_by_path_memory(const char *path, int open_flags, uint32_t sector_size, uint32_t loop_flags, int lock_op, LoopDevice **ret); int loop_device_open(sd_device *dev, int open_flags, int lock_op, LoopDevice **ret); int loop_device_open_from_fd(int fd, int open_flags, int lock_op, LoopDevice **ret); int loop_device_open_from_path(const char *path, int open_flags, int lock_op, LoopDevice **ret); diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 4fbc5e877e..0c4391991b 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -540,6 +540,7 @@ static int merge_subprocess(Hashmap *images, const char *workspace) { r = loop_device_make_by_path( img->path, O_RDONLY, + /* sector_size= */ UINT32_MAX, FLAGS_SET(flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN, LOCK_SH, &d); From 1d93c00386212bf3749e5657de9da99cdd429239 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 17 Jan 2023 18:52:09 +0100 Subject: [PATCH 5/7] dissect: show sector size in info output --- src/dissect/dissect.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index df08cc2b74..f435932815 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -601,9 +601,13 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) { else if (arg_json_format_flags & JSON_FORMAT_OFF) printf(" Size: %s\n", FORMAT_BYTES(size)); + printf(" Sec. Size: %" PRIu32 "\n", m->sector_size); + if (arg_json_format_flags & JSON_FORMAT_OFF) putc('\n', stdout); + fflush(stdout); + r = dissected_image_acquire_metadata(m, 0); if (r == -ENXIO) return log_error_errno(r, "No root partition discovered."); @@ -704,6 +708,7 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) { JSON_BUILD_PAIR("name", JSON_BUILD_STRING(bn)), JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(m->image_uuid), "imageUuid", JSON_BUILD_UUID(m->image_uuid)), JSON_BUILD_PAIR("size", JSON_BUILD_INTEGER(size)), + JSON_BUILD_PAIR("sectorSize", JSON_BUILD_INTEGER(m->sector_size)), JSON_BUILD_PAIR_CONDITION(m->hostname, "hostname", JSON_BUILD_STRING(m->hostname)), JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(m->machine_id), "machineId", JSON_BUILD_ID128(m->machine_id)), JSON_BUILD_PAIR_CONDITION(mi, "machineInfo", JSON_BUILD_VARIANT(mi)), From 81dde3d85d57e1c91ac23208ce006f43502b54b2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 17 Jan 2023 21:42:02 +0100 Subject: [PATCH 6/7] homework: when creating/resizing GPT partitions, also set sector size explicitly --- src/home/homework-luks.c | 19 +++++++++++++------ src/partition/repart.c | 15 +++++++++------ src/shared/dissect-image.c | 18 ++++++++++++++++++ src/shared/dissect-image.h | 1 + src/shared/fdisk-util.c | 20 +++++++++++++++++++- src/shared/fdisk-util.h | 2 +- 6 files changed, 61 insertions(+), 14 deletions(-) diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index 5ad7706636..2ea9887853 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -1840,6 +1840,7 @@ static int luks_format( static int make_partition_table( int fd, + uint32_t sector_size, const char *label, sd_id128_t uuid, uint64_t *ret_offset, @@ -1867,7 +1868,7 @@ static int make_partition_table( if (r < 0) return log_error_errno(r, "Failed to initialize partition type: %m"); - r = fdisk_new_context_fd(fd, /* read_only= */ false, &c); + r = fdisk_new_context_fd(fd, /* read_only= */ false, sector_size, &c); if (r < 0) return log_error_errno(r, "Failed to open device: %m"); @@ -2317,6 +2318,7 @@ int home_create_luks( r = make_partition_table( setup->image_fd, + user_record_luks_sector_size(h), user_record_user_name_and_realm(h), partition_uuid, &partition_offset, @@ -2704,7 +2706,7 @@ static int prepare_resize_partition( return 0; } - r = fdisk_new_context_fd(fd, /* read_only= */ false, &c); + r = fdisk_new_context_fd(fd, /* read_only= */ false, UINT32_MAX, &c); if (r < 0) return log_error_errno(r, "Failed to open device: %m"); @@ -2788,6 +2790,7 @@ static int apply_resize_partition( _cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL; _cleanup_free_ void *two_zero_lbas = NULL; + uint32_t ssz; ssize_t n; int r; @@ -2808,18 +2811,22 @@ static int apply_resize_partition( if (r < 0) return log_error_errno(r, "Failed to change partition size: %m"); - two_zero_lbas = malloc0(1024U); + 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, 1024U, 0); + n = pwrite(fd, two_zero_lbas, ssz * 2, 0); if (n < 0) return log_error_errno(errno, "Failed to wipe partition table: %m"); - if (n != 1024) + if ((size_t) n != ssz * 2) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while wiping partition table."); - r = fdisk_new_context_fd(fd, /* read_only= */ false, &c); + r = fdisk_new_context_fd(fd, /* read_only= */ false, ssz, &c); if (r < 0) return log_error_errno(r, "Failed to open device: %m"); diff --git a/src/partition/repart.c b/src/partition/repart.c index 9698f1be07..1fe1c06a3c 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -6160,7 +6160,7 @@ static int find_root(Context *context) { return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "Failed to discover root block device."); } -static int resize_pt(int fd) { +static int resize_pt(int fd, uint64_t sector_size) { _cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL; int r; @@ -6168,7 +6168,7 @@ static int resize_pt(int fd) { * possession of the enlarged backing file. For this it suffices to open the device with libfdisk and * immediately write it again, with no changes. */ - r = fdisk_new_context_fd(fd, /* read_only= */ false, &c); + r = fdisk_new_context_fd(fd, /* read_only= */ false, sector_size, &c); if (r < 0) return log_error_errno(r, "Failed to open device '%s': %m", FORMAT_PROC_FD_PATH(fd)); @@ -6192,7 +6192,8 @@ static int resize_backing_fd( const char *node, /* The primary way we access the disk image to operate on */ int *fd, /* An O_RDONLY fd referring to that inode */ const char *backing_file, /* If the above refers to a loopback device, the backing regular file for that, which we can grow */ - LoopDevice *loop_device) { + LoopDevice *loop_device, + uint64_t sector_size) { _cleanup_close_ int writable_fd = -EBADF; uint64_t current_size; @@ -6304,7 +6305,7 @@ static int resize_backing_fd( node, FORMAT_BYTES(current_size), FORMAT_BYTES(arg_size)); done: - r = resize_pt(writable_fd); + r = resize_pt(writable_fd, sector_size); if (r < 0) return r; @@ -6429,7 +6430,8 @@ static int run(int argc, char *argv[]) { context->node, &context->backing_fd, node_is_our_loop ? arg_image : NULL, - node_is_our_loop ? loop_device : NULL); + node_is_our_loop ? loop_device : NULL, + context->sector_size); if (r < 0) return r; } @@ -6503,7 +6505,8 @@ static int run(int argc, char *argv[]) { context->node, &context->backing_fd, node_is_our_loop ? arg_image : NULL, - node_is_our_loop ? loop_device : NULL); + node_is_our_loop ? loop_device : NULL, + context->sector_size); if (r < 0) return r; diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index da8eba83de..c0d052e0ea 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -189,6 +189,24 @@ not_found: return 0; /* indicate we didn't find it */ } +int probe_sector_size_prefer_ioctl(int fd, uint32_t *ret) { + struct stat st; + + assert(fd >= 0); + assert(ret); + + /* Just like probe_sector_size(), but if we are looking at a block device, will use the already + * configured sector size rather than probing by contents */ + + if (fstat(fd, &st) < 0) + return -errno; + + if (S_ISBLK(st.st_mode)) + return blockdev_get_sector_size(fd, ret); + + return probe_sector_size(fd, ret); +} + int probe_filesystem_full( int fd, const char *path, diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index a69482d237..143d6ff501 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -190,3 +190,4 @@ int verity_dissect_and_mount(int src_fd, const char *src, const char *dest, cons int dissect_fstype_ok(const char *fstype); int probe_sector_size(int fd, uint32_t *ret); +int probe_sector_size_prefer_ioctl(int fd, uint32_t *ret); diff --git a/src/shared/fdisk-util.c b/src/shared/fdisk-util.c index eeed1840aa..e88adb2d43 100644 --- a/src/shared/fdisk-util.c +++ b/src/shared/fdisk-util.c @@ -1,11 +1,17 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "dissect-image.h" #include "fd-util.h" #include "fdisk-util.h" #if HAVE_LIBFDISK -int fdisk_new_context_fd(int fd, bool read_only, struct fdisk_context **ret) { +int fdisk_new_context_fd( + int fd, + bool read_only, + uint32_t sector_size, + struct fdisk_context **ret) { + _cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL; int r; @@ -18,6 +24,18 @@ int fdisk_new_context_fd(int fd, bool read_only, struct fdisk_context **ret) { if (!c) return -ENOMEM; + if (sector_size == UINT32_MAX) { + r = probe_sector_size_prefer_ioctl(fd, §or_size); + if (r < 0) + return r; + } + + if (sector_size != 0) { + r = fdisk_save_user_sector_size(c, /* phy= */ 0, sector_size); + if (r < 0) + return r; + } + r = fdisk_assign_device(c, FORMAT_PROC_FD_PATH(fd), read_only); if (r < 0) return r; diff --git a/src/shared/fdisk-util.h b/src/shared/fdisk-util.h index 7f34a042ec..4845132927 100644 --- a/src/shared/fdisk-util.h +++ b/src/shared/fdisk-util.h @@ -14,7 +14,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct fdisk_partition*, fdisk_unref_partition, DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct fdisk_parttype*, fdisk_unref_parttype, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct fdisk_table*, fdisk_unref_table, NULL); -int fdisk_new_context_fd(int fd, bool read_only, struct fdisk_context **ret); +int fdisk_new_context_fd(int fd, bool read_only, uint32_t sector_size, struct fdisk_context **ret); int fdisk_partition_get_uuid_as_id128(struct fdisk_partition *p, sd_id128_t *ret); int fdisk_partition_get_type_as_id128(struct fdisk_partition *p, sd_id128_t *ret); From 6c54e1d61fe731c8fc4e86594a78b3d152670cc8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 17 Jan 2023 21:42:21 +0100 Subject: [PATCH 7/7] repart: auto-probe sector size when not specified --- src/partition/repart.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/partition/repart.c b/src/partition/repart.c index 1fe1c06a3c..586a5edd2d 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -1978,11 +1978,26 @@ static int context_load_partition_table(Context *context) { if (!c) return log_oom(); - if (arg_sector_size > 0) { + if (arg_sector_size > 0) r = fdisk_save_user_sector_size(c, /* phy= */ 0, arg_sector_size); + else { + uint32_t ssz; + + if (context->backing_fd < 0) { + context->backing_fd = open(context->node, O_RDONLY|O_CLOEXEC); + if (context->backing_fd < 0) + return log_error_errno(errno, "Failed to open device '%s': %m", context->node); + } + + /* Auto-detect sector size if not specified. */ + r = probe_sector_size_prefer_ioctl(context->backing_fd, &ssz); if (r < 0) - return log_error_errno(r, "Failed to set sector size: %m"); + return log_error_errno(r, "Failed to probe sector size of '%s': %m", context->node); + + r = fdisk_save_user_sector_size(c, /* phy= */ 0, ssz); } + if (r < 0) + return log_error_errno(r, "Failed to set sector size: %m"); /* libfdisk doesn't have an API to operate on arbitrary fds, hence reopen the fd going via the * /proc/self/fd/ magic path if we have an existing fd. Open the original file otherwise. */ @@ -1990,7 +2005,6 @@ static int context_load_partition_table(Context *context) { c, context->backing_fd >= 0 ? FORMAT_PROC_FD_PATH(context->backing_fd) : context->node, arg_dry_run); - if (r == -EINVAL && arg_size_auto) { struct stat st;