The HDIO_GETGEO ioctl() does not work for querying the kernel about partition start sector offset, because the 'start' field of struct hd_geometry is represented as unsigned long. On 32bit systems, this imposes a limit of 2TiB, assuming the device uses 512 bytes logical sectors. Therefore, the start field in struct hd_geometry is wrong if the partition starts beyond 2 TiB limit.
Work around this limitation by reading the partition starting offset from sysfs, while still falling back to HDIO_GETGEO ioctl if the sysfs file is not available. Another advantage of this approach is that now we can get the starting sector of a partition even if the device does not support HDIO_GETGEO ioctl(), which is the case with e.g. partitionable loop devices. * libparted/arch/linux.c (_sysfs_ull_entry_from_part): New function. (_kernel_get_partition_start_sector): New function. (_disk_sync_part_table): Do not use HDIO_GETGEO ioctl, but call _kernel_get_partition_start_sector(). Addresses: http://osdir.com/ml/bug-parted-gnu/2011-10/msg00016.html Signed-off-by: Petr Uzel <[email protected]> --- libparted/arch/linux.c | 69 +++++++++++++++++++++++++++++++++++++++++++++--- 1 files changed, 65 insertions(+), 4 deletions(-) diff --git a/libparted/arch/linux.c b/libparted/arch/linux.c index ab3d904..9e4dd45 100644 --- a/libparted/arch/linux.c +++ b/libparted/arch/linux.c @@ -2450,6 +2450,67 @@ _sysfs_int_entry_from_dev(PedDevice const* dev, const char *entry, int *val) return ok; } + +/* Read the unsigned long long from /sys/block/DEV_BASE/PART_BASE/ENTRY + and set *VAL to that value, where DEV_BASE is the last component of path to + block device corresponding to PART and PART_BASE is the sysfs name of PART. + Upon success, return true. Otherwise, return false. */ +static bool +_sysfs_ull_entry_from_part(PedPartition const* part, const char *entry, unsigned long long *val) +{ + char path[128]; + char *part_name = linux_partition_get_path(part); + if (!part_name) + return false; + + int r = snprintf(path, sizeof(path), "/sys/block/%s/%s/%s", + last_component(part->disk->dev->path), + last_component(part_name), entry); + free(part_name); + if (r < 0 || r >= sizeof(path)) + return false; + + FILE *fp = fopen(path, "r"); + if (!fp) + return false; + + bool ok = fscanf(fp, "%llu", val) == 1; + fclose(fp); + + return ok; +} + + +/* Get the starting sector of a partition PART within a block device and + * store the result to *VAL. First check sysfs and then use HDIO_GETGEO + * ioctl as fallback. Upon success, return true. Otherwise, return false. */ +static bool +_kernel_get_partition_start_sector(PedPartition const *part, unsigned long long *val) +{ + PED_ASSERT(part); + PED_ASSERT(val); + + char *dev_name = linux_partition_get_path (part); + if (!dev_name) + return false; + + int ok = _sysfs_ull_entry_from_part (part, "start", val); + if (!ok) { + struct hd_geometry geom; + int fd = open (dev_name, O_RDONLY); + if (fd != -1 && ioctl (fd, HDIO_GETGEO, &geom)) { + *val = geom.start; + ok = true; + } + if (fd != -1) + close (fd); + } + + free (dev_name); + return ok; +} + + /* Return the maximum number of partitions that the loopback device can hold. First, check the loop-module-exported max_part parameter (since linux-3.0). If that is not available, fall back to checking ext_range, which seems to @@ -2570,16 +2631,16 @@ _disk_sync_part_table (PedDisk* disk) const PedPartition *part = ped_disk_get_partition (disk, i); if (part) { if (!ok[i - 1] && errnums[i - 1] == EBUSY) { - struct hd_geometry geom; unsigned long long length = 0; + unsigned long long start = 0; /* get start and length of existing partition */ char *dev_name = _device_get_part_path (disk->dev, i); if (!dev_name) goto cleanup; int fd = open (dev_name, O_RDONLY); if (fd == -1 - || ioctl (fd, HDIO_GETGEO, &geom) - || ioctl (fd, BLKGETSIZE64, &length)) { + || !_kernel_get_partition_start_sector(part, &start) + || ioctl (fd, BLKGETSIZE64, &length)) { ped_exception_throw ( PED_EXCEPTION_BUG, PED_EXCEPTION_CANCEL, @@ -2593,7 +2654,7 @@ _disk_sync_part_table (PedDisk* disk) free (dev_name); length /= disk->dev->sector_size; close (fd); - if (geom.start == part->geom.start + if (start == part->geom.start && length == part->geom.length) ok[i - 1] = 1; /* If the new partition is unchanged and the -- 1.7.7

