Skip to contentSkip to conversation

detect online disk resize

PATCH New Andrew Patterson 1 participant 6 messages May 8, 2008

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

cover detect online disk resize

This patch series handles online disk resizes that are currently not completely recognized by the kernel using the existing revalidate_disk routines. An online resize can occur when growing or shrinking a Fibre Channel LUN or perhaps by adding a disk to an existing RAID volume.

The kernel currently recognizes a device size change when revalidate_disk() is called; however, the block layer does not use the new size while it has any current openers on the device. So, for example, if LVM has an volume open on the device, you will generally not see the size change until after a reboot. We fix this problem by creating a wrapper to be used with lower-level revalidate_disk routines. This wrapper first calls the lower-level driver's revalidate_disk routine. It then compares the gendisk capacity to the block devices inode size. If there is a difference, we adjust the block device's size. If the size has changed, we then flush the disk for safety.

This patch series only modifies the sd driver to use these changes as that is all that I currently have to test with. Device drivers like cciss and DAC960 should probably use it as well.

Diff stats:

 drivers/scsi/sd.c  |    4 +--
 fs/block_dev.c     |   76 +++++++++++++++++++++++++++++++++++++++++++++++++---
 include/linux/fs.h |    1 +
 3 files changed, 74 insertions(+), 7 deletions(-)

Commits:

- Wrapper for lower-level revalidate_disk routines.
- Adjust block device size after an online resize of a disk.
- SCSI sd driver calls revalidate_disk wrapper.
- Added flush_disk to factor out common buffer cache flushing code.
- Call flush_disk() after detecting an online resize.

Signature · 1 line
Andrew Patterson
#1 lore raw

PATCH 1/5 Wrapper for lower-level revalidate_disk routines.

Wrapper for lower-level revalidate_disk routines.

This is a wrapper for the lower-level revalidate_disk call-backs such as sd_revalidate_disk(). It allows us to perform pre and post operations when calling them.

We will use this wrapper in a later patch to adjust block device sizes after an online resize (a _post_ operation).

---

 fs/block_dev.c     |   21 +++++++++++++++++++++
 include/linux/fs.h |    1 +
 2 files changed, 22 insertions(+), 0 deletions(-)
fs/block_dev.c +21 -0
index 7d822fa..72aaf59 100644
@@ -867,6 +867,27 @@ struct block_device *open_by_devnum(dev_t dev, unsigned mode)
 
 EXPORT_SYMBOL(open_by_devnum);
 
+/**
+ * revalidate_disk - wrapper for lower-level driver's revalidate_disk
+ *                   call-back
+ *
+ * @disk: struct gendisk to be revalidated
+ *
+ * This routine is a wrapper for lower-level driver's revalidate_disk
+ * call-backs.  It is used to do common pre and post operations needed
+ * for all revalidate_disk operations.
+ */
+int revalidate_disk(struct gendisk *disk)
+{
+	int ret = 0;
+
+	if (disk->fops->revalidate_disk)
+		ret = disk->fops->revalidate_disk(disk);
+
+	return ret;
+}
+EXPORT_SYMBOL(revalidate_disk);
+
 /*
  * This routine checks whether a removable media has been changed,
  * and invalidates all buffer-cache-entries in that case. This
include/linux/fs.h +1 -0
index a1ba005..bdc396c 100644
@@ -1709,6 +1709,7 @@ extern int fs_may_remount_ro(struct super_block *);
  */
 #define bio_data_dir(bio)	((bio)->bi_rw & 1)
 
+extern int revalidate_disk(struct gendisk *);
 extern int check_disk_change(struct block_device *);
 extern int __invalidate_device(struct block_device *);
 extern int invalidate_partition(struct gendisk *, int);
#2 lore raw

PATCH 2/5 Adjust block device size after an online resize of a disk.

In reply to Andrew Patterson

Adjust block device size after an online resize of a disk.

The revalidate_disk routine now checks if a disk has been resized by comparing the gendisk capacity to the bdev inode size. If they are different (usually because the disk has been resized underneath the kernel) the bdev inode size is adjusted to match the capacity.

---

 fs/block_dev.c |   21 +++++++++++++++++++++
 1 files changed, 21 insertions(+), 0 deletions(-)
fs/block_dev.c +21 -0
index 72aaf59..87014e6 100644
@@ -879,11 +879,32 @@ EXPORT_SYMBOL(open_by_devnum);
  */
 int revalidate_disk(struct gendisk *disk)
 {
+	struct block_device *bdev;
+	loff_t disk_size, bdev_size;
 	int ret = 0;
 
 	if (disk->fops->revalidate_disk)
 		ret = disk->fops->revalidate_disk(disk);
 
+	bdev = bdget_disk(disk, 0);
+	if (!bdev)
+		return ret;
+
+	disk_size = (loff_t)get_capacity(disk) << 9;
+	mutex_lock(&bdev->bd_mutex);
+	bdev_size = i_size_read(bdev->bd_inode);
+	if (disk_size != bdev_size) {
+		char name[BDEVNAME_SIZE];
+
+		disk_name(disk, 0, name);
+		printk(KERN_INFO
+		       "%s: detected capacity change from %lld to %lld\n",
+		       name, bdev_size, disk_size);
+		i_size_write(bdev->bd_inode, disk_size);
+	}
+
+	mutex_unlock(&bdev->bd_mutex);
+	bdput(bdev);
 	return ret;
 }
 EXPORT_SYMBOL(revalidate_disk);
#3 lore raw

PATCH 3/5 SCSI sd driver calls revalidate_disk wrapper.

In reply to Andrew Patterson

SCSI sd driver calls revalidate_disk wrapper.

Modify the SCSI disk driver to call the revalidate_disk() wrapper. This allows us to do some housekeeping such as accounting for a disk being resized online. The wrapper will call sd_revalidate_disk() at the appropriate time.

---

 drivers/scsi/sd.c |    4 ++--
 1 files changed, 2 insertions(+), 2 deletions(-)
drivers/scsi/sd.c +2 -2
index 01cefbb..7aedeb3 100644
@@ -160,7 +160,7 @@ sd_store_cache_type(struct device *dev, struct device_attribute *attr,
 			sd_print_sense_hdr(sdkp, &sshdr);
 		return -EINVAL;
 	}
-	sd_revalidate_disk(sdkp->disk);
+	revalidate_disk(sdkp->disk);
 	return count;
 }
 
@@ -871,7 +871,7 @@ static void sd_rescan(struct device *dev)
 	struct scsi_disk *sdkp = scsi_disk_get_from_dev(dev);
 
 	if (sdkp) {
-		sd_revalidate_disk(sdkp->disk);
+		revalidate_disk(sdkp->disk);
 		scsi_disk_put(sdkp);
 	}
 }
#4 lore raw

PATCH 4/5 Added flush_disk to factor out common buffer cache flushing code.

In reply to Andrew Patterson

Added flush_disk to factor out common buffer cache flushing code.

We need to be able to flush the buffer cache for for more than just when a disk is changed, so we factor out common cache flush code in check_disk_change() to an internal flush_disk() routine. This routine will then be used for both disk changes and disk resizes (in a later patch).

Include the disk name in the text indicating that there are busy inodes on the device and increase the KERN severity of the message.

---

 fs/block_dev.c |   33 ++++++++++++++++++++++++++++-----
 1 files changed, 28 insertions(+), 5 deletions(-)
fs/block_dev.c +28 -5
index 87014e6..eb2bc5c 100644
@@ -867,6 +867,33 @@ struct block_device *open_by_devnum(dev_t dev, unsigned mode)
 
 EXPORT_SYMBOL(open_by_devnum);
 
+
+/**
+ * flush_disk - invalidates all buffer-cache entries on a disk
+ *
+ * @bdev:      struct block device to be flushed
+ *
+ * Invalidates all buffer-cache entries on a disk. It should be called
+ * when a disk has been changed -- either by a media change or online
+ * resize.
+ */
+static void flush_disk(struct block_device *bdev)
+{
+	if (__invalidate_device(bdev)) {
+		char name[BDEVNAME_SIZE] = "";
+
+		if (bdev->bd_disk)
+			disk_name(bdev->bd_disk, 0, name);
+		printk(KERN_WARNING "VFS: busy inodes on changed media %s\n",
+		       name);
+	}
+
+	if (!bdev->bd_disk)
+		return;
+	if (bdev->bd_disk->minors > 1)
+		bdev->bd_invalidated = 1;
+}
+
 /**
  * revalidate_disk - wrapper for lower-level driver's revalidate_disk
  *                   call-back
@@ -928,13 +955,9 @@ int check_disk_change(struct block_device *bdev)
 	if (!bdops->media_changed(bdev->bd_disk))
 		return 0;
 
-	if (__invalidate_device(bdev))
-		printk("VFS: busy inodes on changed media.\n");
-
+	flush_disk(bdev);
 	if (bdops->revalidate_disk)
 		bdops->revalidate_disk(bdev->bd_disk);
-	if (bdev->bd_disk->minors > 1)
-		bdev->bd_invalidated = 1;
 	return 1;
 }
 
#5 lore raw

PATCH 5/5 Call flush_disk() after detecting an online resize.

In reply to Andrew Patterson

Call flush_disk() after detecting an online resize.

We call flush_disk() to make sure the buffer cache for the disk is flushed after a disk resize. There are two resize cases, growing and shrinking. Given that users can shrink/then grow a disk before revalidate_disk() is called, we treat the grow case identically to shrinking. We need to flush the buffer cache after an online shrink because, as James Bottomley puts it,

The two use cases for shrinking I can see are

1. planned: the fs is already shrunk to within the new boundaries and all data is relocated, so invalidate is fine (any dirty buffers that might exist in the shrunk region are there only because they were relocated but not yet written to their original location).
2. unplanned: In this case, the fs is probably toast, so whether we invalidate or not isn't going to make a whole lot of difference; it's still going to try to read or write from sectors beyond the new size and get I/O errors.

Immediately invalidating shrunk disks will cause errors for outstanding I/Os for reads/write beyond the new end of the disk to be generated earlier then if we waited for the normal buffer cache operation. It also removes a potential security hole where we might keep old data around from beyond the end of the shrunk disk if the disk was not invalidated.

---

 fs/block_dev.c |    5 +++--
 1 files changed, 3 insertions(+), 2 deletions(-)
fs/block_dev.c +3 -2
index eb2bc5c..41ef08d 100644
@@ -884,8 +884,8 @@ static void flush_disk(struct block_device *bdev)
 
 		if (bdev->bd_disk)
 			disk_name(bdev->bd_disk, 0, name);
-		printk(KERN_WARNING "VFS: busy inodes on changed media %s\n",
-		       name);
+		printk(KERN_WARNING "VFS: busy inodes on changed media or "
+		       "resized disk %s\n", name);
 	}
 
 	if (!bdev->bd_disk)
@@ -928,6 +928,7 @@ int revalidate_disk(struct gendisk *disk)
 		       "%s: detected capacity change from %lld to %lld\n",
 		       name, bdev_size, disk_size);
 		i_size_write(bdev->bd_inode, disk_size);
+		flush_disk(bdev);
 	}
 
 	mutex_unlock(&bdev->bd_mutex);