]> git.ipfire.org Git - thirdparty/mdadm.git/blobdiff - Grow.c
platform-intel - cache 'intel_devices' for a few seconds.
[thirdparty/mdadm.git] / Grow.c
diff --git a/Grow.c b/Grow.c
index ea86f7bb142a5a5967d2f945f38fb9509b8b8c5e..957710a5598fd41e4f47313a54ce41f16e1776ca 100644 (file)
--- a/Grow.c
+++ b/Grow.c
@@ -1,7 +1,7 @@
 /*
  * mdadm - manage Linux "md" devices aka RAID arrays.
  *
- * Copyright (C) 2001-2009 Neil Brown <neilb@suse.de>
+ * Copyright (C) 2001-2012 Neil Brown <neilb@suse.de>
  *
  *
  *    This program is free software; you can redistribute it and/or modify
@@ -122,7 +122,8 @@ int Grow_Add_device(char *devname, int fd, char *newdev)
 
        st = super_by_fd(fd, &subarray);
        if (!st) {
-               pr_err("cannot handle arrays with superblock version %d\n", info.array.major_version);
+               pr_err("cannot handle arrays with superblock version %d\n",
+                      info.array.major_version);
                return 1;
        }
 
@@ -146,7 +147,8 @@ int Grow_Add_device(char *devname, int fd, char *newdev)
                free(st);
                return 1;
        }
-       /* now check out all the devices and make sure we can read the superblock */
+       /* now check out all the devices and make sure we can read the
+        * superblock */
        for (d=0 ; d < info.array.raid_disks ; d++) {
                mdu_disk_info_t disk;
                char *dv;
@@ -408,8 +410,8 @@ int Grow_addbitmap(char *devname, int fd, struct context *c, struct shape *s)
                                                )
                                                st->ss->write_bitmap(st, fd2);
                                        else {
-                                               pr_err("failed "
-                                                      "to create internal bitmap - chunksize problem.\n");
+                                               pr_err("failed to create internal bitmap"
+                                                      " - chunksize problem.\n");
                                                close(fd2);
                                                return 1;
                                        }
@@ -440,7 +442,7 @@ int Grow_addbitmap(char *devname, int fd, struct context *c, struct shape *s)
                int max_devs = st->max_devs;
 
                /* try to load a superblock */
-               for (d=0; d<max_devs; d++) {
+               for (d = 0; d < max_devs; d++) {
                        mdu_disk_info_t disk;
                        char *dv;
                        int fd2;
@@ -451,7 +453,8 @@ int Grow_addbitmap(char *devname, int fd, struct context *c, struct shape *s)
                            (disk.state & (1<<MD_DISK_REMOVED)))
                                continue;
                        dv = map_dev(disk.major, disk.minor, 1);
-                       if (!dv) continue;
+                       if (!dv)
+                               continue;
                        fd2 = dev_open(dv, O_RDONLY);
                        if (fd2 >= 0) {
                                if (st->ss->load_super(st, fd2, NULL) == 0) {
@@ -520,7 +523,7 @@ static __u32 bsb_csum(char *buf, int len)
 {
        int i;
        int csum = 0;
-       for (i=0; i<len; i++)
+       for (i = 0; i < len; i++)
                csum = (csum<<3) + buf[0];
        return __cpu_to_le32(csum);
 }
@@ -639,8 +642,8 @@ static void wait_reshape(struct mdinfo *sra)
        close(fd);
 }
 
-static int reshape_super(struct supertype *st, long long size, int level,
-                        int layout, int chunksize, int raid_disks,
+static int reshape_super(struct supertype *st, unsigned long long size,
+                        int level, int layout, int chunksize, int raid_disks,
                         int delta_disks, char *backup_file, char *dev,
                         int direction, int verbose)
 {
@@ -935,6 +938,17 @@ int reshape_open_backup_file(char *backup_file,
        return 1;
 }
 
+unsigned long GCD(unsigned long a, unsigned long b)
+{
+       while (a != b) {
+               if (a < b)
+                       b -= a;
+               if (b < a)
+                       a -= b;
+       }
+       return a;
+}
+
 unsigned long compute_backup_blocks(int nchunk, int ochunk,
                                    unsigned int ndata, unsigned int odata)
 {
@@ -947,12 +961,7 @@ unsigned long compute_backup_blocks(int nchunk, int ochunk,
        a = (ochunk/512) * odata;
        b = (nchunk/512) * ndata;
        /* Find GCD */
-       while (a != b) {
-               if (a < b)
-                       b -= a;
-               if (b < a)
-                       a -= b;
-       }
+       a = GCD(a, b);
        /* LCM == product / GCD */
        blocks = (ochunk/512) * (nchunk/512) * odata * ndata / a;
 
@@ -976,7 +985,9 @@ char *analyse_change(struct mdinfo *info, struct reshape *re)
         * This can be called as part of starting a reshape, or
         * when assembling an array that is undergoing reshape.
         */
+       int near, far, offset, copies;
        int new_disks;
+       int old_chunk, new_chunk;
        /* delta_parity records change in number of devices
         * caused by level change
         */
@@ -1063,38 +1074,96 @@ char *analyse_change(struct mdinfo *info, struct reshape *re)
                return "Impossibly level change request for RAID1";
 
        case 10:
-               /* RAID10 can only be converted from near mode to
-                * RAID0 by removing some devices
+               /* RAID10 can be converted from near mode to
+                * RAID0 by removing some devices.
+                * It can also be reshaped if the kernel supports
+                * new_data_offset.
                 */
-               if ((info->array.layout & ~0xff) != 0x100)
-                       return "Cannot Grow RAID10 with far/offset layout";
-               /* number of devices must be multiple of number of copies */
-               if (info->array.raid_disks % (info->array.layout & 0xff))
-                       return "RAID10 layout too complex for Grow operation";
+               switch (info->new_level) {
+               case 0:
+                       if ((info->array.layout & ~0xff) != 0x100)
+                               return "Cannot Grow RAID10 with far/offset layout";
+                       /* number of devices must be multiple of number of copies */
+                       if (info->array.raid_disks % (info->array.layout & 0xff))
+                               return "RAID10 layout too complex for Grow operation";
+
+                       new_disks = (info->array.raid_disks
+                                    / (info->array.layout & 0xff));
+                       if (info->delta_disks == UnSet)
+                               info->delta_disks = (new_disks
+                                                    - info->array.raid_disks);
 
-               if (info->new_level != 0)
-                       return "RAID10 can only be changed to RAID0";
-               new_disks = (info->array.raid_disks
-                            / (info->array.layout & 0xff));
-               if (info->delta_disks == UnSet)
-                       info->delta_disks = (new_disks
-                                            - info->array.raid_disks);
-
-               if (info->delta_disks != new_disks - info->array.raid_disks)
-                       return "New number of raid-devices impossible for RAID10";
-               if (info->new_chunk &&
-                   info->new_chunk != info->array.chunk_size)
-                       return "Cannot change chunk-size with RAID10 Grow";
-
-               /* looks good */
-               re->level = 0;
-               re->parity = 0;
-               re->before.data_disks = new_disks;
-               re->after.data_disks = re->before.data_disks;
-               re->before.layout = 0;
-               re->backup_blocks = 0;
-               return NULL;
+                       if (info->delta_disks != new_disks - info->array.raid_disks)
+                               return "New number of raid-devices impossible for RAID10";
+                       if (info->new_chunk &&
+                           info->new_chunk != info->array.chunk_size)
+                               return "Cannot change chunk-size with RAID10 Grow";
+
+                       /* looks good */
+                       re->level = 0;
+                       re->parity = 0;
+                       re->before.data_disks = new_disks;
+                       re->after.data_disks = re->before.data_disks;
+                       re->before.layout = 0;
+                       re->backup_blocks = 0;
+                       return NULL;
+
+               case 10:
+                       near = info->array.layout & 0xff;
+                       far = (info->array.layout >> 8) & 0xff;
+                       offset = info->array.layout & 0x10000;
+                       if (far > 1 && !offset)
+                               return "Cannot reshape RAID10 in far-mode";
+                       copies = near * far;
+
+                       old_chunk = info->array.chunk_size * far;
+
+                       if (info->new_layout == UnSet)
+                               info->new_layout = info->array.layout;
+                       else {
+                               near = info->new_layout & 0xff;
+                               far = (info->new_layout >> 8) & 0xff;
+                               offset = info->new_layout & 0x10000;
+                               if (far > 1 && !offset)
+                                       return "Cannot reshape RAID10 to far-mode";
+                               if (near * far != copies)
+                                       return "Cannot change number of copies"
+                                               " when reshaping RAID10";
+                       }
+                       if (info->delta_disks == UnSet)
+                               info->delta_disks = 0;
+                       new_disks = (info->array.raid_disks +
+                                    info->delta_disks);
+
+                       new_chunk = info->new_chunk * far;
+
+                       re->level = 10;
+                       re->parity = 0;
+                       re->before.layout = info->array.layout;
+                       re->before.data_disks = info->array.raid_disks;
+                       re->after.layout = info->new_layout;
+                       re->after.data_disks = new_disks;
+                       /* For RAID10 we don't do backup, and there is
+                        * no need to synchronise stripes on both
+                        * 'old' and  'new'.  So the important
+                        * number is the minimum data_offset difference
+                        * which is the larger of (offset copies * chunk).
+                        */
+
+                       re->backup_blocks = max(old_chunk, new_chunk) / 512;
+                       if (new_disks < re->before.data_disks &&
+                           info->space_after < re->backup_blocks)
+                               /* Reduce component size by one chunk */
+                               re->new_size = (info->component_size -
+                                               re->backup_blocks);
+                       else
+                               re->new_size = info->component_size;
+                       re->new_size = re->new_size * new_disks / copies;
+                       return NULL;
 
+               default:
+                       return "RAID10 can only be changed to RAID0";
+               }
        case 0:
                /* RAID0 can be converted to RAID10, or to RAID456 */
                if (info->new_level == 10) {
@@ -1212,7 +1281,9 @@ char *analyse_change(struct mdinfo *info, struct reshape *re)
                                return "Cannot set raid_disk when "
                                        "converting RAID5->RAID1";
                        re->level = 1;
-                       break;
+                       re->backup_blocks = 0;
+                       info->new_chunk = 0;
+                       return NULL;
                default:
                        return "Impossible level change requested";
                }
@@ -1250,9 +1321,11 @@ char *analyse_change(struct mdinfo *info, struct reshape *re)
 
                switch (re->level) {
                case 4:
-                       re->after.layout = 0 ; break;
+                       re->after.layout = 0;
+                       break;
                case 5:
-                       re->after.layout = ALGORITHM_PARITY_N; break;
+                       re->after.layout = ALGORITHM_PARITY_N;
+                       break;
                }
                break;
 
@@ -1263,9 +1336,11 @@ char *analyse_change(struct mdinfo *info, struct reshape *re)
 
                switch (re->level) {
                case 4:
-                       re->after.layout = 0 ; break;
+                       re->after.layout = 0;
+                       break;
                case 5:
-                       re->after.layout = ALGORITHM_PARITY_N; break;
+                       re->after.layout = ALGORITHM_PARITY_N;
+                       break;
                }
                break;
 
@@ -1337,10 +1412,13 @@ char *analyse_change(struct mdinfo *info, struct reshape *re)
                                + info->delta_disks
                                - delta_parity);
        switch (re->level) {
-       case 6: re->parity = 2; break;
+       case 6: re->parity = 2;
+               break;
        case 4:
-       case 5: re->parity = 1; break;
-       default: re->parity = 0; break;
+       case 5: re->parity = 1;
+               break;
+       default: re->parity = 0;
+               break;
        }
        /* So we have a restripe operation, we need to calculate the number
         * of blocks per reshape operation.
@@ -1418,6 +1496,7 @@ static int set_array_size(struct supertype *st, struct mdinfo *sra,
 static int reshape_array(char *container, int fd, char *devname,
                         struct supertype *st, struct mdinfo *info,
                         int force, struct mddev_dev *devlist,
+                        unsigned long long data_offset,
                         char *backup_file, int verbose, int forked,
                         int restart, int freeze_reshape);
 static int reshape_container(char *container, char *devname,
@@ -1430,6 +1509,7 @@ static int reshape_container(char *container, char *devname,
 
 int Grow_reshape(char *devname, int fd,
                 struct mddev_dev *devlist,
+                unsigned long long data_offset,
                 struct context *c, struct shape *s)
 {
        /* Make some changes in the shape of an array.
@@ -1466,11 +1546,16 @@ int Grow_reshape(char *devname, int fd,
        struct mdinfo info;
        struct mdinfo *sra;
 
+
        if (ioctl(fd, GET_ARRAY_INFO, &array) < 0) {
-               pr_err("%s is not an active md array - aborting\n",
+               fprintf(stderr, Name ": %s is not an active md array - aborting\n",
                        devname);
                return 1;
        }
+       if (data_offset != INVALID_SECTORS && array.level != 10) {
+               pr_err("--grow --data-offset not yet supported\n");
+               return 1;
+       }
 
        if (s->size > 0 &&
            (s->chunk || s->level!= UnSet || s->layout_str || s->raiddisks)) {
@@ -1609,7 +1694,7 @@ int Grow_reshape(char *devname, int fd,
        }
 
        /* ========= set size =============== */
-       if (s->size > 0 && (s->size == MAX_DISKS || s->size != (unsigned)array.size)) {
+       if (s->size > 0 && (s->size == MAX_SIZE || s->size != (unsigned)array.size)) {
                unsigned long long orig_size = get_component_size(fd)/2;
                unsigned long long min_csize;
                struct mdinfo *mdi;
@@ -1767,12 +1852,12 @@ size_change_error:
                        goto release;
                }
                if (s->assume_clean) {
-                       /* This will fail on kernels newer than 3.0 unless
+                       /* This will fail on kernels older than 3.0 unless
                         * a backport has been arranged.
                         */
                        if (sra == NULL ||
                            sysfs_set_str(sra, NULL, "resync_start", "none") < 0)
-                               pr_err("--assume-clean not support with --grow on this kernel\n");
+                               pr_err("--assume-clean not supported with --grow on this kernel\n");
                }
                ioctl(fd, GET_ARRAY_INFO, &array);
                s->size = get_component_size(fd)/2;
@@ -1989,7 +2074,7 @@ size_change_error:
                /* Impose these changes on a single array.  First
                 * check that the metadata is OK with the change. */
 
-               if (reshape_super(st, -1, info.new_level,
+               if (reshape_super(st, 0, info.new_level,
                                  info.new_layout, info.new_chunk,
                                  info.array.raid_disks, info.delta_disks,
                                  c->backup_file, devname, APPLY_METADATA_CHANGES,
@@ -1999,7 +2084,8 @@ size_change_error:
                }
                sync_metadata(st);
                rv = reshape_array(container, fd, devname, st, &info, c->force,
-                                  devlist, c->backup_file, c->verbose, 0, 0, 0);
+                                  devlist, data_offset, c->backup_file, c->verbose,
+                                  0, 0, 0);
                frozen = 0;
        }
 release:
@@ -2066,9 +2152,277 @@ static int verify_reshape_position(struct mdinfo *info, int level)
        return ret_val;
 }
 
+static int raid10_reshape(char *container, int fd, char *devname,
+                         struct supertype *st, struct mdinfo *info,
+                         struct reshape *reshape,
+                         unsigned long long data_offset,
+                         int force, int verbose)
+{
+       /* Changing raid_disks, layout, chunksize or possibly
+        * just data_offset for a RAID10.
+        * We must always change data_offset.  We change by at least
+        * ->backup_blocks which is the largest of the old and new
+        * chunk sizes.
+        * If raid_disks is increasing, then data_offset must decrease
+        * by at least this copy size.
+        * If raid_disks is unchanged, data_offset must increase or
+        * decrease by at least backup_blocks but preferably by much more.
+        * We choose half of the available space.
+        * If raid_disks is decreasing, data_offset must increase by
+        * at least backup_blocks.  To allow of this, component_size
+        * must be decreased by the same amount.
+        *
+        * So we calculate the required minimum and direction, possibly
+        * reduce the component_size, then iterate through the devices
+        * and set the new_data_offset.
+        * If that all works, we set chunk_size, layout, raid_disks, and start
+        * 'reshape'
+        */
+       struct mdinfo *sra, *sd;
+       unsigned long long min;
+       int dir = 0;
+       int err = 0;
+
+       sra = sysfs_read(fd, 0,
+                        GET_COMPONENT|GET_DEVS|GET_OFFSET|GET_STATE|GET_CHUNK
+               );
+       if (!sra) {
+               fprintf(stderr, Name ": %s: Cannot get array details from sysfs\n",
+                       devname);
+               goto release;
+       }
+       min = reshape->backup_blocks;
+
+       if (info->delta_disks)
+               sysfs_set_str(sra, NULL, "reshape_direction",
+                             info->delta_disks < 0 ? "backwards" : "forwards");
+       if (info->delta_disks < 0 &&
+           info->space_after < reshape->backup_blocks) {
+               int rv = sysfs_set_num(sra, NULL, "component_size",
+                                      (sra->component_size -
+                                       reshape->backup_blocks)/2);
+               if (rv) {
+                       fprintf(stderr, Name ": cannot reduce component size\n");
+                       goto release;
+               }
+       }
+       for (sd = sra->devs; sd; sd = sd->next) {
+               char *dn;
+               int dfd;
+               int rv;
+               struct supertype *st2;
+               struct mdinfo info2;
+
+               if (sd->disk.state & (1<<MD_DISK_FAULTY))
+                       continue;
+               dn = map_dev(sd->disk.major, sd->disk.minor, 0);
+               dfd = dev_open(dn, O_RDONLY);
+               if (dfd < 0) {
+                       fprintf(stderr,
+                               Name ": %s: cannot open component %s\n",
+                               devname, dn ? dn : "-unknown-");
+                       rv = -1;
+                       goto release;
+               }
+               st2 = dup_super(st);
+               rv = st2->ss->load_super(st2,dfd, NULL);
+               close(dfd);
+               if (rv) {
+                       free(st2);
+                       fprintf(stderr, ": %s: cannot get superblock from %s\n",
+                               devname, dn);
+                       goto release;
+               }
+               st2->ss->getinfo_super(st2, &info2, NULL);
+               st2->ss->free_super(st2);
+               free(st2);
+               if (info->delta_disks < 0) {
+                       /* Don't need any space as array is shrinking
+                        * just move data_offset up by min
+                        */
+                       if (data_offset == 1)
+                               info2.new_data_offset = info2.data_offset + min;
+                       else {
+                               if ((unsigned long long)data_offset
+                                   < info2.data_offset + min) {
+                                       fprintf(stderr, Name ": --data-offset too small for %s\n",
+                                               dn);
+                                       goto release;
+                               }
+                               info2.new_data_offset = data_offset;
+                       }
+               } else if (info->delta_disks > 0) {
+                       /* need space before */
+                       if (info2.space_before < min) {
+                               fprintf(stderr, Name ": Insufficient head-space for reshape on %s\n",
+                                       dn);
+                               goto release;
+                       }
+                       if (data_offset == 1)
+                               info2.new_data_offset = info2.data_offset - min;
+                       else {
+                               if ((unsigned long long)data_offset
+                                   > info2.data_offset - min) {
+                                       fprintf(stderr, Name ": --data-offset too large for %s\n",
+                                               dn);
+                                       goto release;
+                               }
+                               info2.new_data_offset = data_offset;
+                       }
+               } else {
+                       if (dir == 0) {
+                               /* can move up or down. 'data_offset'
+                                * might guide us, otherwise choose
+                                * direction with most space
+                                */
+                               if (data_offset == 1) {
+                                       if (info2.space_before > info2.space_after)
+                                               dir = -1;
+                                       else
+                                               dir = 1;
+                               } else if (data_offset < info2.data_offset)
+                                       dir = -1;
+                               else
+                                       dir = 1;
+                               sysfs_set_str(sra, NULL, "reshape_direction",
+                                             dir == 1 ? "backwards" : "forwards");
+                       }
+                       switch (dir) {
+                       case 1: /* Increase data offset */
+                               if (info2.space_after < min) {
+                                       fprintf(stderr, Name ": Insufficient tail-space for reshape on %s\n",
+                                               dn);
+                                       goto release;
+                               }
+                               if (data_offset != 1 &&
+                                   data_offset < info2.data_offset + min) {
+                                       fprintf(stderr, Name ": --data-offset too small on %s\n",
+                                               dn);
+                                       goto release;
+                               }
+                               if (data_offset != 1)
+                                       info2.new_data_offset = data_offset;
+                               else {
+                                       unsigned long long off =
+                                               info2.space_after / 2;
+                                       off &= ~7ULL;
+                                       if (off < min)
+                                               off = min;
+                                       info2.new_data_offset =
+                                               info2.data_offset + off;
+                               }
+                               break;
+                       case -1: /* Decrease data offset */
+                               if (info2.space_before < min) {
+                                       fprintf(stderr, Name ": insufficient head-room on %s\n",
+                                               dn);
+                                       goto release;
+                               }
+                               if (data_offset != 1 &&
+                                   data_offset < info2.data_offset - min) {
+                                       fprintf(stderr, Name ": --data-offset too small on %s\n",
+                                               dn);
+                                       goto release;
+                               }
+                               if (data_offset != 1)
+                                       info2.new_data_offset = data_offset;
+                               else {
+                                       unsigned long long off =
+                                               info2.space_before / 2;
+                                       off &= ~7ULL;
+                                       if (off < min)
+                                               off = min;
+                                       info2.new_data_offset =
+                                               info2.data_offset - off;
+                               }
+                               break;
+                       }
+               }
+               if (sysfs_set_num(sra, sd, "new_offset",
+                                 info2.new_data_offset) < 0) {
+                       err = errno;
+                       fprintf(stderr, Name ": Cannot set new_offset for %s\n",
+                               dn);
+                       break;
+               }
+       }
+       if (!err && sysfs_set_num(sra, NULL, "chunk_size", info->new_chunk) < 0)
+               err = errno;
+       if (!err && sysfs_set_num(sra, NULL, "layout", reshape->after.layout) < 0)
+               err = errno;
+       if (!err && sysfs_set_num(sra, NULL, "raid_disks",
+                                 info->array.raid_disks + info->delta_disks) < 0)
+               err = errno;
+       if (!err && sysfs_set_str(sra, NULL, "sync_action", "reshape") < 0)
+               err = errno;
+       if (err) {
+               fprintf(stderr, Name ": Cannot set array shape for %s\n",
+                       devname);
+                       if (err == EBUSY &&
+                           (info->array.state & (1<<MD_SB_BITMAP_PRESENT)))
+                               fprintf(stderr,
+                                       "       Bitmap must be removed before"
+                                       " shape can be changed\n");
+                       goto release;
+       }
+       sysfs_free(sra);
+       return 0;
+release:
+       sysfs_free(sra);
+       return 1;
+}
+
+static void get_space_after(int fd, struct supertype *st, struct mdinfo *info)
+{
+       struct mdinfo *sra, *sd;
+       /* Initialisation to silence compiler warning */
+       unsigned long long min_space_before = 0, min_space_after = 0;
+       int first = 1;
+
+       sra = sysfs_read(fd, 0, GET_DEVS);
+       if (!sra)
+               return;
+       for (sd = sra->devs; sd; sd = sd->next) {
+               char *dn;
+               int dfd;
+               struct supertype *st2;
+               struct mdinfo info2;
+
+               if (sd->disk.state & (1<<MD_DISK_FAULTY))
+                       continue;
+               dn = map_dev(sd->disk.major, sd->disk.minor, 0);
+               dfd = dev_open(dn, O_RDONLY);
+               if (dfd < 0)
+                       break;
+               st2 = dup_super(st);
+               if (st2->ss->load_super(st2,dfd, NULL)) {
+                       close(dfd);
+                       free(st2);
+                       break;
+               }
+               close(dfd);
+               st2->ss->getinfo_super(st2, &info2, NULL);
+               st2->ss->free_super(st2);
+               free(st2);
+               if (first ||
+                   min_space_before > info2.space_before)
+                       min_space_before = info2.space_before;
+               if (first ||
+                   min_space_after > info2.space_after)
+                       min_space_after = info2.space_after;
+               first = 0;
+       }
+       if (sd == NULL && !first) {
+               info->space_after = min_space_after;
+               info->space_before = min_space_before;
+       }
+       sysfs_free(sra);
+}
+
 static int reshape_array(char *container, int fd, char *devname,
                         struct supertype *st, struct mdinfo *info,
                         int force, struct mddev_dev *devlist,
+                        unsigned long long data_offset,
                         char *backup_file, int verbose, int forked,
                         int restart, int freeze_reshape)
 {
@@ -2077,6 +2431,7 @@ static int reshape_array(char *container, int fd, char *devname,
        char *msg;
        int orig_level = UnSet;
        int disks, odisks;
+       int delayed;
 
        struct mdu_array_info_s array;
        char *c;
@@ -2107,6 +2462,10 @@ static int reshape_array(char *container, int fd, char *devname,
                info->component_size = array_size / array.raid_disks;
        }
 
+       if (array.level == 10)
+               /* Need space_after info */
+               get_space_after(fd, st, info);
+
        if (info->reshape_active) {
                int new_level = info->new_level;
                info->new_level = UnSet;
@@ -2350,7 +2709,6 @@ static int reshape_array(char *container, int fd, char *devname,
         *   -  request the shape change.
         *   -  fork to handle backup etc.
         */
-started:
        /* Check that we can hold all the data */
        get_dev_size(fd, NULL, &array_size);
        if (reshape.new_size < (array_size/512)) {
@@ -2361,6 +2719,21 @@ started:
                goto release;
        }
 
+started:
+
+       if (array.level == 10) {
+               /* Reshaping RAID10 does not require and data backup by
+                * user-space.  Instead it requires that the data_offset
+                * is changed to avoid the need for backup.
+                * So this is handled very separately
+                */
+               if (restart)
+                       /* Nothing to do. */
+                       return 0;
+               return raid10_reshape(container, fd, devname, st, info,
+                                     &reshape, data_offset,
+                                     force, verbose);
+       }
        sra = sysfs_read(fd, 0,
                         GET_COMPONENT|GET_DEVS|GET_OFFSET|GET_STATE|GET_CHUNK|
                         GET_CACHE);
@@ -2563,6 +2936,39 @@ started:
                break;
        }
 
+       /* If another array on the same devices is busy, the
+        * reshape will wait for them.  This would mean that
+        * the first section that we suspend will stay suspended
+        * for a long time.  So check on that possibility
+        * by looking for "DELAYED" in /proc/mdstat, and if found,
+        * wait a while
+        */
+       do {
+               struct mdstat_ent *mds, *m;
+               delayed = 0;
+               mds = mdstat_read(0, 0);
+               for (m = mds; m; m = m->next)
+                       if (m->devnum == devname2devnum(sra->sys_name)) {
+                               if (m->resync &&
+                                   m->percent == RESYNC_DELAYED)
+                                       delayed = 1;
+                               if (m->resync == 0)
+                                       /* Haven't started the reshape thread
+                                        * yet, wait a bit
+                                        */
+                                       delayed = 2;
+                               break;
+                       }
+               free_mdstat(mds);
+               if (delayed == 1 && get_linux_version() < 3007000) {
+                       pr_err("Reshape is delayed, but cannot wait carefully with this kernel.\n"
+                              "       You might experience problems until other reshapes complete.\n");
+                       delayed = 0;
+               }
+               if (delayed)
+                       sleep(30 - (delayed-1) * 25);
+       } while (delayed);
+
        close(fd);
        if (check_env("MDADM_GROW_VERIFY"))
                fd = open(devname, O_RDONLY | O_DIRECT);
@@ -2674,10 +3080,10 @@ int reshape_container(char *container, char *devname,
        int last_devnum = -1;
 
        /* component_size is not meaningful for a container,
-        * so pass '-1' meaning 'no change'
+        * so pass '0' meaning 'no change'
         */
        if (!restart &&
-           reshape_super(st, -1, info->new_level,
+           reshape_super(st, 0, info->new_level,
                          info->new_layout, info->new_chunk,
                          info->array.raid_disks, info->delta_disks,
                          backup_file, devname, APPLY_METADATA_CHANGES,
@@ -2792,7 +3198,7 @@ int reshape_container(char *container, char *devname,
                        flush_mdmon(container);
 
                rv = reshape_array(container, fd, adev, st,
-                                  content, force, NULL,
+                                  content, force, NULL, 0ULL,
                                   backup_file, verbose, 1, restart,
                                   freeze_reshape);
                close(fd);
@@ -3731,9 +4137,8 @@ int Grow_restart(struct supertype *st, struct mdinfo *info, int *fdlist, int cnt
                                        (unsigned long)__le64_to_cpu(bsb.mtime),
                                        (unsigned long)info->array.utime);
                        } else {
-                               if (verbose)
-                                       pr_err("too-old timestamp on "
-                                               "backup-metadata on %s\n", devname);
+                               pr_err("too-old timestamp on backup-metadata on %s\n", devname);
+                               pr_err("If you think it is should be safe, try 'export MDADM_GROW_ALLOW_OLD=1'\n");
                                continue; /* time stamp is too bad */
                        }
                }
@@ -3888,7 +4293,8 @@ int Grow_restart(struct supertype *st, struct mdinfo *info, int *fdlist, int cnt
                        }
                }
                for (j=0; j<info->array.raid_disks; j++) {
-                       if (fdlist[j] < 0) continue;
+                       if (fdlist[j] < 0)
+                               continue;
                        if (st->ss->load_super(st, fdlist[j], NULL))
                                continue;
                        st->ss->getinfo_super(st, &dinfo, NULL);
@@ -3967,15 +4373,49 @@ int Grow_continue_command(char *devname, int fd,
        }
        dprintf("Grow continue is run for ");
        if (st->ss->external == 0) {
+               int d;
                dprintf("native array (%s)\n", devname);
-               if (ioctl(fd, GET_ARRAY_INFO, &array) < 0) {
+               if (ioctl(fd, GET_ARRAY_INFO, &array.array) < 0) {
                        pr_err("%s is not an active md array -"
                                " aborting\n", devname);
                        ret_val = 1;
                        goto Grow_continue_command_exit;
                }
                content = &array;
-               sysfs_init(content, fd, st->devnum);
+               /* Need to load a superblock.
+                * FIXME we should really get what we need from
+                * sysfs
+                */
+               for (d = 0; d < MAX_DISKS; d++) {
+                       mdu_disk_info_t disk;
+                       char *dv;
+                       int err;
+                       disk.number = d;
+                       if (ioctl(fd, GET_DISK_INFO, &disk) < 0)
+                               continue;
+                       if (disk.major == 0 && disk.minor == 0)
+                               continue;
+                       if ((disk.state & (1 << MD_DISK_ACTIVE)) == 0)
+                               continue;
+                       dv = map_dev(disk.major, disk.minor, 1);
+                       if (!dv)
+                               continue;
+                       fd2 = dev_open(dv, O_RDONLY);
+                       if (fd2 < 0)
+                               continue;
+                       err = st->ss->load_super(st, fd2, NULL);
+                       close(fd2);
+                       if (err)
+                               continue;
+                       break;
+               }
+               if (d == MAX_DISKS) {
+                       pr_err("Unable to load metadata for %s\n",
+                              devname);
+                       ret_val = 1;
+                       goto Grow_continue_command_exit;
+               }
+               st->ss->getinfo_super(st, content, NULL);
        } else {
                int container_dev;
 
@@ -4085,8 +4525,7 @@ int Grow_continue_command(char *devname, int fd,
        /* verify that array under reshape is started from
         * correct position
         */
-       if (verify_reshape_position(content,
-                                   map_name(pers, mdstat->level)) < 0) {
+       if (verify_reshape_position(content, content->array.level) < 0) {
                ret_val = 1;
                goto Grow_continue_command_exit;
        }
@@ -4131,7 +4570,7 @@ int Grow_continue(int mdfd, struct supertype *st, struct mdinfo *info,
                                            0, 1, freeze_reshape);
        } else
                ret_val = reshape_array(NULL, mdfd, "array", st, info, 1,
-                                       NULL, backup_file, 0, 0, 1,
+                                       NULL, 0ULL, backup_file, 0, 0, 1,
                                        freeze_reshape);
 
        return ret_val;