loop-util: create loop device for block devices with sector size mismatch
Previously, loop_device_make_internal() always used the block device directly (via loop_device_open_from_fd()) for whole-device access, regardless of sector size. This is incorrect when the GPT partition table was written with a different sector size than the device reports, as happens with CD-ROM/ISO boot via El Torito: the device has 2048-byte blocks but the GPT uses 512-byte sectors. Restructure the sector size handling in loop_device_make_internal(): - Move GPT sector size probing (UINT32_MAX case) before the block-vs-regular-file split so both paths share the same logic and O_DIRECT handling. Check f_flags instead of loop_flags for O_DIRECT detection, since we're probing the original fd before any reopening. - For block devices, get the device sector size and compare it against the resolved sector_size. Only use the block device directly when sector sizes match. When they differ (probed GPT mismatch or explicit sector size request), fall through to create a real loop device with the correct sector size. - Default sector_size=0 to the device sector size for block devices (instead of always 512), so "no preference" correctly matches the device's sector size. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
committed by
Daan De Meyer
parent
d49df940ec
commit
eb584f164f
+61
-44
@@ -413,6 +413,33 @@ static int fd_set_max_discard(int fd, uint64_t max_discard) {
|
||||
return write_string_filef(sysfs_path, WRITE_STRING_FILE_DISABLE_BUFFER, "%" PRIu64, max_discard);
|
||||
}
|
||||
|
||||
static int probe_sector_size_harder(int fd, uint32_t *ret) {
|
||||
_cleanup_close_ int non_direct_io_fd = -EBADF;
|
||||
int probe_fd, f_flags;
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(ret);
|
||||
|
||||
/* Wraps probe_sector_size() but handles O_DIRECT: if the fd is opened with O_DIRECT there are
|
||||
* strict alignment requirements for reads, so we temporarily reopen it without O_DIRECT for the
|
||||
* probing logic. */
|
||||
|
||||
f_flags = fcntl(fd, F_GETFL);
|
||||
if (f_flags < 0)
|
||||
return -errno;
|
||||
|
||||
if (FLAGS_SET(f_flags, O_DIRECT)) {
|
||||
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;
|
||||
|
||||
return probe_sector_size(probe_fd, ret);
|
||||
}
|
||||
|
||||
static int loop_device_make_internal(
|
||||
const char *path,
|
||||
int fd,
|
||||
@@ -435,6 +462,11 @@ static int loop_device_make_internal(
|
||||
assert(open_flags < 0 || IN_SET(open_flags, O_RDWR, O_RDONLY));
|
||||
assert(ret);
|
||||
|
||||
/* sector_size interpretation:
|
||||
* 0 → use device sector size for block devices, 512 for regular files
|
||||
* UINT32_MAX → probe GPT header to find the right sector size, fall back to 0 behavior
|
||||
* other → use the specified sector size explicitly */
|
||||
|
||||
f_flags = fcntl(fd, F_GETFL);
|
||||
if (f_flags < 0)
|
||||
return -errno;
|
||||
@@ -449,19 +481,45 @@ static int loop_device_make_internal(
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EBADFD), "Access mode of image file is write only (?)");
|
||||
}
|
||||
|
||||
if (sector_size == UINT32_MAX) {
|
||||
/* If sector size is specified as UINT32_MAX, we'll try to probe the right sector size
|
||||
* by looking for the GPT partition header at various offsets. This of course only works
|
||||
* if the image already has a disk label. */
|
||||
|
||||
r = probe_sector_size_harder(fd, §or_size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
sector_size = 0; /* If we can't probe anything, use default sector size. */
|
||||
}
|
||||
|
||||
if (fstat(fd, &st) < 0)
|
||||
return -errno;
|
||||
|
||||
if (S_ISBLK(st.st_mode)) {
|
||||
if (offset == 0 && IN_SET(size, 0, UINT64_MAX))
|
||||
uint32_t device_ssz;
|
||||
r = blockdev_get_sector_size(fd, &device_ssz);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (sector_size == 0)
|
||||
sector_size = device_ssz;
|
||||
|
||||
if (offset == 0 && IN_SET(size, 0, UINT64_MAX) && sector_size == device_ssz)
|
||||
/* If this is already a block device and we are supposed to cover the whole of it
|
||||
* then store an fd to the original open device node — and do not actually create an
|
||||
* unnecessary loopback device for it. */
|
||||
* then store an fd to the original open device node — and do not actually create
|
||||
* an unnecessary loopback device for it. If an explicit sector size was requested
|
||||
* that differs from the device sector size, or if the probed GPT sector size
|
||||
* differs (e.g. CD-ROMs with 2048-byte blocks but a 512-byte sector GPT), create
|
||||
* a real loop device to change the sector size. */
|
||||
return loop_device_open_from_fd(fd, open_flags, lock_op, ret);
|
||||
} else {
|
||||
r = stat_verify_regular(&st);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (sector_size == 0)
|
||||
sector_size = 512;
|
||||
}
|
||||
|
||||
if (path) {
|
||||
@@ -500,47 +558,6 @@ 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 = -EBADF;
|
||||
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;
|
||||
}
|
||||
|
||||
/* Strip LO_FLAGS_PARTSCAN from LOOP_CONFIGURE and enable it afterwards via
|
||||
* LOOP_SET_STATUS64 to work around a kernel race: LOOP_CONFIGURE sends a uevent with
|
||||
* GD_NEED_PART_SCAN set before calling loop_reread_partitions(). If udev opens the device in
|
||||
|
||||
Reference in New Issue
Block a user