--- /dev/null
+From b522adcde9c4d3fb7b579cfa9160d8bde7744be8 Mon Sep 17 00:00:00 2001
+From: Dan Williams <dan.j.williams@intel.com>
+Date: Tue, 31 Mar 2009 15:00:31 +1100
+Subject: [PATCH] md: 'array_size' sysfs attribute
+
+Allow userspace to set the size of the array according to the following
+semantics:
+
+1/ size must be <= to the size returned by mddev->pers->size(mddev, 0, 0)
+ a) If size is set before the array is running, do_md_run will fail
+ if size is greater than the default size
+ b) A reshape attempt that reduces the default size to less than the set
+ array size should be blocked
+2/ once userspace sets the size the kernel will not change it
+3/ writing 'default' to this attribute returns control of the size to the
+ kernel and reverts to the size reported by the personality
+
+Also, convert locations that need to know the default size from directly
+reading ->array_sectors to <pers>_size. Resync/reshape operations
+always follow the default size.
+
+Finally, fixup other locations that read a number of 1k-blocks from
+userspace to use strict_blocks_to_sectors() which checks for unsigned
+long long to sector_t overflow and blocks to sectors overflow.
+
+Reviewed-by: Andre Noll <maan@systemlinux.org>
+Signed-off-by: Dan Williams <dan.j.williams@intel.com>
+Acked-by: NeilBrown <neilb@suse.de>
+---
+ drivers/md/md.c | 101 +++++++++++++++++++++++++++++++++++++++++++++-
+ drivers/md/raid0.c | 2
+ drivers/md/raid1.c | 6 +-
+ drivers/md/raid10.c | 2
+ drivers/md/raid5.c | 9 +++-
+ include/linux/raid/md_k.h | 3 +
+ 6 files changed, 116 insertions(+), 7 deletions(-)
+
+--- linux-2.6.27-SLE11_BRANCH.orig/drivers/md/md.c
++++ linux-2.6.27-SLE11_BRANCH/drivers/md/md.c
+@@ -300,6 +300,11 @@ static inline int mddev_lock(mddev_t * m
+ return mutex_lock_interruptible(&mddev->reconfig_mutex);
+ }
+
++static inline int mddev_is_locked(mddev_t *mddev)
++{
++ return mutex_is_locked(&mddev->reconfig_mutex);
++}
++
+ static inline int mddev_trylock(mddev_t * mddev)
+ {
+ return mutex_trylock(&mddev->reconfig_mutex);
+@@ -2146,6 +2151,25 @@ static int overlaps(sector_t s1, sector_
+ return 1;
+ }
+
++static int strict_blocks_to_sectors(const char *buf, sector_t *sectors)
++{
++ unsigned long long blocks;
++ sector_t new;
++
++ if (strict_strtoull(buf, 10, &blocks) < 0)
++ return -EINVAL;
++
++ if (blocks & 1ULL << (8 * sizeof(blocks) - 1))
++ return -EINVAL; /* sector conversion overflow */
++
++ new = blocks * 2;
++ if (new != blocks * 2)
++ return -EINVAL; /* unsigned long long to sector_t overflow */
++
++ *sectors = new;
++ return 0;
++}
++
+ static ssize_t
+ rdev_size_store(mdk_rdev_t *rdev, const char *buf, size_t len)
+ {
+@@ -3398,6 +3422,57 @@ static struct md_sysfs_entry md_reshape_
+ __ATTR(reshape_position, S_IRUGO|S_IWUSR, reshape_position_show,
+ reshape_position_store);
+
++static ssize_t
++array_size_show(mddev_t *mddev, char *page)
++{
++ if (mddev->external_size)
++ return sprintf(page, "%llu\n",
++ (unsigned long long)mddev->array_sectors/2);
++ else
++ return sprintf(page, "default\n");
++}
++
++static ssize_t
++array_size_store(mddev_t *mddev, const char *buf, size_t len)
++{
++ sector_t sectors;
++
++ if (strncmp(buf, "default", 7) == 0) {
++ if (mddev->pers)
++ sectors = mddev->pers->size(mddev, 0, 0);
++ else
++ sectors = mddev->array_sectors;
++
++ mddev->external_size = 0;
++ } else {
++ if (strict_blocks_to_sectors(buf, §ors) < 0)
++ return -EINVAL;
++ if (mddev->pers && mddev->pers->size(mddev, 0, 0) < sectors)
++ return -EINVAL;
++
++ mddev->external_size = 1;
++ }
++
++ mddev->array_sectors = sectors;
++ set_capacity(mddev->gendisk, mddev->array_sectors);
++ if (mddev->pers) {
++ struct block_device *bdev = bdget_disk(mddev->gendisk, 0);
++
++ if (bdev) {
++ mutex_lock(&bdev->bd_inode->i_mutex);
++ i_size_write(bdev->bd_inode,
++ (loff_t)mddev->array_sectors << 9);
++ mutex_unlock(&bdev->bd_inode->i_mutex);
++ bdput(bdev);
++ }
++ }
++
++ return len;
++}
++
++static struct md_sysfs_entry md_array_size =
++__ATTR(array_size, S_IRUGO|S_IWUSR, array_size_show,
++ array_size_store);
+
+ static struct attribute *md_default_attrs[] = {
+ &md_level.attr,
+@@ -3411,6 +3486,7 @@ static struct attribute *md_default_attr
+ &md_safe_delay.attr,
+ &md_array_state.attr,
+ &md_reshape_position.attr,
++ &md_array_size.attr,
+ NULL,
+ };
+
+@@ -3722,7 +3798,17 @@ static int do_md_run(mddev_t * mddev)
+ err = mddev->pers->run(mddev);
+ if (err)
+ printk(KERN_ERR "md: pers->run() failed ...\n");
+- else if (mddev->pers->sync_request) {
++ else if (mddev->pers->size(mddev, 0, 0) < mddev->array_sectors) {
++ WARN_ONCE(!mddev->external_size, "%s: default size too small,"
++ " but 'external_size' not in effect?\n", __func__);
++ printk(KERN_ERR
++ "md: invalid array_size %llu > default size %llu\n",
++ (unsigned long long)mddev->array_sectors / 2,
++ (unsigned long long)mddev->pers->size(mddev, 0, 0) / 2);
++ err = -EINVAL;
++ mddev->pers->stop(mddev);
++ }
++ if (err == 0 && mddev->pers->sync_request) {
+ err = bitmap_create(mddev);
+ if (err) {
+ printk(KERN_ERR "%s: failed to create bitmap (%d)\n",
+@@ -4664,10 +4750,23 @@ static int set_array_info(mddev_t * mdde
+
+ void md_set_array_sectors(mddev_t *mddev, sector_t array_sectors)
+ {
++ WARN(!mddev_is_locked(mddev), "%s: unlocked mddev!\n", __func__);
++
++ if (mddev->external_size)
++ return;
++
+ mddev->array_sectors = array_sectors;
+ }
+ EXPORT_SYMBOL(md_set_array_sectors);
+
++void md_set_array_sectors_lock(mddev_t *mddev, sector_t array_sectors)
++{
++ mddev_lock(mddev);
++ md_set_array_sectors(mddev, array_sectors);
++ mddev_unlock(mddev);
++}
++EXPORT_SYMBOL(md_set_array_sectors_lock);
++
+ static int update_size(mddev_t *mddev, sector_t num_sectors)
+ {
+ mdk_rdev_t * rdev;
+--- linux-2.6.27-SLE11_BRANCH.orig/drivers/md/raid0.c
++++ linux-2.6.27-SLE11_BRANCH/drivers/md/raid0.c
+@@ -314,7 +314,7 @@ static int raid0_run (mddev_t *mddev)
+ printk("raid0 : conf->hash_spacing is %llu blocks.\n",
+ (unsigned long long)conf->hash_spacing);
+ {
+- sector_t s = mddev->array_sectors / 2;
++ sector_t s = raid0_size(mddev, 0, 0) / 2;
+ sector_t space = conf->hash_spacing;
+ int round;
+ conf->preshift = 0;
+--- linux-2.6.27-SLE11_BRANCH.orig/drivers/md/raid1.c
++++ linux-2.6.27-SLE11_BRANCH/drivers/md/raid1.c
+@@ -2116,14 +2116,16 @@ static int raid1_resize(mddev_t *mddev,
+ * worth it.
+ */
+ md_set_array_sectors(mddev, raid1_size(mddev, sectors, 0));
++ if (mddev->array_sectors > raid1_size(mddev, sectors, 0))
++ return -EINVAL;
+ set_capacity(mddev->gendisk, mddev->array_sectors);
+ mddev->changed = 1;
+- if (mddev->array_sectors / 2 > mddev->size &&
++ if (sectors / 2 > mddev->size &&
+ mddev->recovery_cp == MaxSector) {
+ mddev->recovery_cp = mddev->size << 1;
+ set_bit(MD_RECOVERY_NEEDED, &mddev->recovery);
+ }
+- mddev->size = mddev->array_sectors / 2;
++ mddev->size = sectors / 2;
+ mddev->resync_max_sectors = sectors;
+ return 0;
+ }
+--- linux-2.6.27-SLE11_BRANCH.orig/drivers/md/raid10.c
++++ linux-2.6.27-SLE11_BRANCH/drivers/md/raid10.c
+@@ -2187,7 +2187,7 @@ static int run(mddev_t *mddev)
+ * Ok, everything is just fine now
+ */
+ md_set_array_sectors(mddev, raid10_size(mddev, 0, 0));
+- mddev->resync_max_sectors = mddev->array_sectors;
++ mddev->resync_max_sectors = raid10_size(mddev, 0, 0);
+
+ mddev->queue->unplug_fn = raid10_unplug;
+ mddev->queue->backing_dev_info.congested_fn = raid10_congested;
+--- linux-2.6.27-SLE11_BRANCH.orig/drivers/md/raid5.c
++++ linux-2.6.27-SLE11_BRANCH/drivers/md/raid5.c
+@@ -3700,6 +3700,8 @@ static int make_request(struct request_q
+ return 0;
+ }
+
++static sector_t raid5_size(mddev_t *mddev, sector_t sectors, int raid_disks);
++
+ static sector_t reshape_request(mddev_t *mddev, sector_t sector_nr, int *skipped)
+ {
+ /* reshaping is quite different to recovery/resync so it is
+@@ -3778,7 +3780,7 @@ static sector_t reshape_request(mddev_t
+ j == sh->qd_idx)
+ continue;
+ s = compute_blocknr(sh, j);
+- if (s < mddev->array_sectors) {
++ if (s < raid5_size(mddev, 0, 0)) {
+ skipped = 1;
+ continue;
+ }
+@@ -4660,6 +4662,9 @@ static int raid5_resize(mddev_t *mddev,
+ sectors &= ~((sector_t)mddev->chunk_size/512 - 1);
+ md_set_array_sectors(mddev, raid5_size(mddev, sectors,
+ mddev->raid_disks));
++ if (mddev->array_sectors >
++ raid5_size(mddev, sectors, mddev->raid_disks))
++ return -EINVAL;
+ set_capacity(mddev->gendisk, mddev->array_sectors);
+ mddev->changed = 1;
+ if (sectors/2 > mddev->size && mddev->recovery_cp == MaxSector) {
+@@ -4798,7 +4803,7 @@ static void end_reshape(raid5_conf_t *co
+ if (!test_bit(MD_RECOVERY_INTR, &conf->mddev->recovery)) {
+ mddev_t *mddev = conf->mddev;
+
+- md_set_array_sectors(mddev, raid5_size(mddev, 0,
++ md_set_array_sectors_lock(mddev, raid5_size(mddev, 0,
+ conf->raid_disks));
+ set_capacity(mddev->gendisk, mddev->array_sectors);
+ mddev->changed = 1;
+--- linux-2.6.27-SLE11_BRANCH.orig/include/linux/raid/md_k.h
++++ linux-2.6.27-SLE11_BRANCH/include/linux/raid/md_k.h
+@@ -152,6 +152,8 @@ struct mddev_s
+ int max_disks;
+ sector_t size; /* used size of component devices */
+ sector_t array_sectors; /* exported array size */
++ int external_size; /* size managed
++ * externally */
+ __u64 events;
+
+ char uuid[16];
+@@ -394,3 +396,4 @@ static inline void safe_put_page(struct
+ #endif /* CONFIG_BLOCK */
+ #endif
+
++extern void md_set_array_sectors_lock(mddev_t *mddev, sector_t array_sectors);