From 56bca01738733709bef076e2e97bbd01e5659f24 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sat, 15 May 2010 20:09:28 +0200 Subject: block: restart partition scan after resizing a device Device resize via ->set_capacity() can reveal new partitions (e.g. in chained partition table formats such as dos extended parts). Restart partition scan from the beginning after resizing a device. This change also makes libata always revalidate the disk after resize which makes lower layer native capacity unlocking implementation simpler and more robust as resize can be handled in the usual path. Signed-off-by: Tejun Heo Reported-by: Ben Hutchings Acked-by: David S. Miller Signed-off-by: Jens Axboe --- fs/partitions/check.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) (limited to 'fs/partitions/check.c') diff --git a/fs/partitions/check.c b/fs/partitions/check.c index e238ab23a9e7..8f01df354f04 100644 --- a/fs/partitions/check.c +++ b/fs/partitions/check.c @@ -544,7 +544,7 @@ int rescan_partitions(struct gendisk *disk, struct block_device *bdev) struct hd_struct *part; struct parsed_partitions *state; int p, highest, res; - +rescan: if (bdev->bd_part_count) return -EBUSY; res = invalidate_partition(disk, 0); @@ -581,7 +581,7 @@ int rescan_partitions(struct gendisk *disk, struct block_device *bdev) /* add partitions */ for (p = 1; p < state->limit; p++) { sector_t size, from; -try_scan: + size = state->parts[p].size; if (!size) continue; @@ -596,7 +596,6 @@ try_scan: if (from + size > get_capacity(disk)) { const struct block_device_operations *bdops = disk->fops; - unsigned long long capacity; printk(KERN_WARNING "%s: p%d size %llu exceeds device capacity, ", @@ -605,14 +604,11 @@ try_scan: if (bdops->set_capacity && (disk->flags & GENHD_FL_NATIVE_CAPACITY) == 0) { printk(KERN_CONT "enabling native capacity\n"); - capacity = bdops->set_capacity(disk, ~0ULL); + bdops->set_capacity(disk, ~0ULL); disk->flags |= GENHD_FL_NATIVE_CAPACITY; - if (capacity > get_capacity(disk)) { - set_capacity(disk, capacity); - check_disk_size_change(disk, bdev); - bdev->bd_invalidated = 0; - } - goto try_scan; + /* free state and restart */ + kfree(state); + goto rescan; } else { /* * we can not ignore partitions of broken tables -- cgit v1.2.1 From c3e33e043f5e9c583aa59d5591a614b2a8243d3a Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sat, 15 May 2010 20:09:29 +0200 Subject: block,ide: simplify bdops->set_capacity() to ->unlock_native_capacity() bdops->set_capacity() is unnecessarily generic. All that's required is a simple one way notification to lower level driver telling it to try to unlock native capacity. There's no reason to pass in target capacity or return the new capacity. The former is always the inherent native capacity and the latter can be handled via the usual device resize / revalidation path. In fact, the current API is always used that way. Replace ->set_capacity() with ->unlock_native_capacity() which take only @disk and doesn't return anything. IDE which is the only current user of the API is converted accordingly. Signed-off-by: Tejun Heo Cc: Ben Hutchings Cc: Bartlomiej Zolnierkiewicz Acked-by: David S. Miller Signed-off-by: Jens Axboe --- fs/partitions/check.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'fs/partitions/check.c') diff --git a/fs/partitions/check.c b/fs/partitions/check.c index 8f01df354f04..4f1fee0355ad 100644 --- a/fs/partitions/check.c +++ b/fs/partitions/check.c @@ -601,10 +601,10 @@ rescan: "%s: p%d size %llu exceeds device capacity, ", disk->disk_name, p, (unsigned long long) size); - if (bdops->set_capacity && + if (bdops->unlock_native_capacity && (disk->flags & GENHD_FL_NATIVE_CAPACITY) == 0) { printk(KERN_CONT "enabling native capacity\n"); - bdops->set_capacity(disk, ~0ULL); + bdops->unlock_native_capacity(disk); disk->flags |= GENHD_FL_NATIVE_CAPACITY; /* free state and restart */ kfree(state); -- cgit v1.2.1 From 1493bf217f7f59a5d9e2095a7dbcec00fb36ca8b Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sat, 15 May 2010 20:09:30 +0200 Subject: block: use struct parsed_partitions *state universally in partition check code Make the following changes to partition check code. * Add ->bdev to struct parsed_partitions. * Introduce read_part_sector() which is a simple wrapper around read_dev_sector() which takes struct parsed_partitions *state instead of @bdev. * For functions which used to take @state and @bdev, drop @bdev. For functions which used to take @bdev, replace it with @state. * While updating, drop superflous checks on NULL state/bdev in ldm.c. This cleans up the API a bit and enables better handling of IO errors during partition check as the generic partition check code now has much better visibility into what went wrong in the low level code paths. Signed-off-by: Tejun Heo Cc: Ben Hutchings Acked-by: David S. Miller Signed-off-by: Jens Axboe --- fs/partitions/check.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'fs/partitions/check.c') diff --git a/fs/partitions/check.c b/fs/partitions/check.c index 4f1fee0355ad..a19995c6f6af 100644 --- a/fs/partitions/check.c +++ b/fs/partitions/check.c @@ -45,7 +45,7 @@ extern void md_autodetect_dev(dev_t dev); int warn_no_part = 1; /*This is ugly: should make genhd removable media aware*/ -static int (*check_part[])(struct parsed_partitions *, struct block_device *) = { +static int (*check_part[])(struct parsed_partitions *) = { /* * Probe partition formats with tables at disk address 0 * that also have an ADFS boot block at 0xdc0. @@ -165,6 +165,7 @@ check_partition(struct gendisk *hd, struct block_device *bdev) if (!state) return NULL; + state->bdev = bdev; disk_name(hd, 0, state->name); printk(KERN_INFO " %s:", state->name); if (isdigit(state->name[strlen(state->name)-1])) @@ -174,7 +175,7 @@ check_partition(struct gendisk *hd, struct block_device *bdev) i = res = err = 0; while (!res && check_part[i]) { memset(&state->parts, 0, sizeof(state->parts)); - res = check_part[i++](state, bdev); + res = check_part[i++](state); if (res < 0) { /* We have hit an I/O error which we don't report now. * But record it, and let the others do their job. -- cgit v1.2.1 From b403a98e260f3a8c7c33f58a07c7ae549852170f Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sat, 15 May 2010 20:09:31 +0200 Subject: block: improve automatic native capacity unlocking Currently, native capacity unlocking is initiated only when a recognized partition extends beyond the end of the disk. However, there are several other unhandled cases where truncated capacity can lead to misdetection of partitions. * Partition table is fully beyond EOD. * Partition table is partially beyond EOD (daisy chained ones). * Recognized partition starts beyond EOD. This patch updates generic partition check code such that all the above three cases are handled too. For the first two, @state tracks whether low level partition check code tried to read beyond EOD during partition scan and triggers native capacity unlocking accordingly. The third is now handled similarly to the original unlocking case. Signed-off-by: Tejun Heo Cc: Ben Hutchings Acked-by: David S. Miller Signed-off-by: Jens Axboe --- fs/partitions/check.c | 69 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 14 deletions(-) (limited to 'fs/partitions/check.c') diff --git a/fs/partitions/check.c b/fs/partitions/check.c index a19995c6f6af..5dcd4b0c5533 100644 --- a/fs/partitions/check.c +++ b/fs/partitions/check.c @@ -161,7 +161,7 @@ check_partition(struct gendisk *hd, struct block_device *bdev) struct parsed_partitions *state; int i, res, err; - state = kmalloc(sizeof(struct parsed_partitions), GFP_KERNEL); + state = kzalloc(sizeof(struct parsed_partitions), GFP_KERNEL); if (!state) return NULL; @@ -187,6 +187,8 @@ check_partition(struct gendisk *hd, struct block_device *bdev) } if (res > 0) return state; + if (state->access_beyond_eod) + err = -ENOSPC; if (err) /* The partition is unrecognized. So report I/O errors if there were any */ res = err; @@ -539,13 +541,34 @@ exit: disk_part_iter_exit(&piter); } +static bool disk_unlock_native_capacity(struct gendisk *disk) +{ + const struct block_device_operations *bdops = disk->fops; + + if (bdops->unlock_native_capacity && + !(disk->flags & GENHD_FL_NATIVE_CAPACITY)) { + printk(KERN_CONT "enabling native capacity\n"); + bdops->unlock_native_capacity(disk); + disk->flags |= GENHD_FL_NATIVE_CAPACITY; + return true; + } else { + printk(KERN_CONT "truncated\n"); + return false; + } +} + int rescan_partitions(struct gendisk *disk, struct block_device *bdev) { + struct parsed_partitions *state = NULL; struct disk_part_iter piter; struct hd_struct *part; - struct parsed_partitions *state; int p, highest, res; rescan: + if (state && !IS_ERR(state)) { + kfree(state); + state = NULL; + } + if (bdev->bd_part_count) return -EBUSY; res = invalidate_partition(disk, 0); @@ -563,8 +586,32 @@ rescan: bdev->bd_invalidated = 0; if (!get_capacity(disk) || !(state = check_partition(disk, bdev))) return 0; - if (IS_ERR(state)) /* I/O error reading the partition table */ + if (IS_ERR(state)) { + /* + * I/O error reading the partition table. If any + * partition code tried to read beyond EOD, retry + * after unlocking native capacity. + */ + if (PTR_ERR(state) == -ENOSPC) { + printk(KERN_WARNING "%s: partition table beyond EOD, ", + disk->disk_name); + if (disk_unlock_native_capacity(disk)) + goto rescan; + } return -EIO; + } + /* + * If any partition code tried to read beyond EOD, try + * unlocking native capacity even if partition table is + * sucessfully read as we could be missing some partitions. + */ + if (state->access_beyond_eod) { + printk(KERN_WARNING + "%s: partition table partially beyond EOD, ", + disk->disk_name); + if (disk_unlock_native_capacity(disk)) + goto rescan; + } /* tell userspace that the media / partition table may have changed */ kobject_uevent(&disk_to_dev(disk)->kobj, KOBJ_CHANGE); @@ -590,25 +637,20 @@ rescan: from = state->parts[p].from; if (from >= get_capacity(disk)) { printk(KERN_WARNING - "%s: p%d ignored, start %llu is behind the end of the disk\n", + "%s: p%d start %llu is beyond EOD, ", disk->disk_name, p, (unsigned long long) from); + if (disk_unlock_native_capacity(disk)) + goto rescan; continue; } if (from + size > get_capacity(disk)) { - const struct block_device_operations *bdops = disk->fops; - printk(KERN_WARNING - "%s: p%d size %llu exceeds device capacity, ", + "%s: p%d size %llu extends beyond EOD, ", disk->disk_name, p, (unsigned long long) size); - if (bdops->unlock_native_capacity && - (disk->flags & GENHD_FL_NATIVE_CAPACITY) == 0) { - printk(KERN_CONT "enabling native capacity\n"); - bdops->unlock_native_capacity(disk); - disk->flags |= GENHD_FL_NATIVE_CAPACITY; + if (disk_unlock_native_capacity(disk)) { /* free state and restart */ - kfree(state); goto rescan; } else { /* @@ -617,7 +659,6 @@ rescan: * we limit them to the end of the disk to avoid * creating invalid block devices */ - printk(KERN_CONT "limited to end of disk\n"); size = get_capacity(disk) - from; } } -- cgit v1.2.1