+static int set_new_data_offset(struct mdinfo *sra, struct supertype *st,
+ char *devname, int delta_disks,
+ unsigned long long data_offset,
+ unsigned long long min)
+{
+ struct mdinfo *sd;
+ int dir = 0;
+ int err = 0;
+
+ 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) {
+ pr_err("%s: cannot open component %s\n",
+ devname, dn ? dn : "-unknown-");
+ goto release;
+ }
+ st2 = dup_super(st);
+ rv = st2->ss->load_super(st2,dfd, NULL);
+ close(dfd);
+ if (rv) {
+ free(st2);
+ pr_err("%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 (delta_disks < 0) {
+ /* Don't need any space as array is shrinking
+ * just move data_offset up by min
+ */
+ if (data_offset == INVALID_SECTORS)
+ info2.new_data_offset = info2.data_offset + min;
+ else {
+ if (data_offset < info2.data_offset + min) {
+ pr_err("--data-offset too small for %s\n",
+ dn);
+ goto release;
+ }
+ info2.new_data_offset = data_offset;
+ }
+ } else if (delta_disks > 0) {
+ /* need space before */
+ if (info2.space_before < min) {
+ pr_err("Insufficient head-space for reshape on %s\n",
+ dn);
+ goto release;
+ }
+ if (data_offset == INVALID_SECTORS)
+ info2.new_data_offset = info2.data_offset - min;
+ else {
+ if (data_offset > info2.data_offset - min) {
+ pr_err("--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 == INVALID_SECTORS) {
+ 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) {
+ pr_err("Insufficient tail-space for reshape on %s\n",
+ dn);
+ goto release;
+ }
+ if (data_offset != INVALID_SECTORS &&
+ data_offset < info2.data_offset + min) {
+ pr_err("--data-offset too small on %s\n",
+ dn);
+ goto release;
+ }
+ if (data_offset != INVALID_SECTORS)
+ 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) {
+ pr_err("insufficient head-room on %s\n",
+ dn);
+ goto release;
+ }
+ if (data_offset != INVALID_SECTORS &&
+ data_offset < info2.data_offset - min) {
+ pr_err("--data-offset too small on %s\n",
+ dn);
+ goto release;
+ }
+ if (data_offset != INVALID_SECTORS)
+ 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;
+ if (sd == sra->devs && err == ENOENT)
+ /* Early kernel, no 'new_offset' file.
+ * For RAID5/6 this is not fatal
+ */
+ return 1;
+ pr_err("Cannot set new_offset for %s\n",
+ dn);
+ break;
+ }
+ }
+ return err;
+release:
+ return -1;
+}
+
+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
+ * ->min_offset_change 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 min_offset_change but preferably by much more.
+ * We choose half of the available space.
+ * If raid_disks is decreasing, data_offset must increase by
+ * at least min_offset_change. 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;
+ unsigned long long min;
+ int err = 0;
+
+ sra = sysfs_read(fd, NULL,
+ GET_COMPONENT|GET_DEVS|GET_OFFSET|GET_STATE|GET_CHUNK
+ );
+ if (!sra) {
+ pr_err("%s: Cannot get array details from sysfs\n",
+ devname);
+ goto release;
+ }
+ min = reshape->min_offset_change;
+
+ 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 < min) {
+ int rv = sysfs_set_num(sra, NULL, "component_size",
+ (sra->component_size -
+ min)/2);
+ if (rv) {
+ pr_err("cannot reduce component size\n");
+ goto release;
+ }
+ }
+ err = set_new_data_offset(sra, st, devname, info->delta_disks, data_offset,
+ min);
+ if (err == 1) {
+ pr_err("Cannot set new_data_offset: RAID10 reshape not\n");
+ cont_err("supported on this kernel\n");
+ err = -1;
+ }
+ if (err < 0)
+ goto release;
+
+ 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) {
+ pr_err("Cannot set array shape for %s\n",
+ devname);
+ if (err == EBUSY &&
+ (info->array.state & (1<<MD_SB_BITMAP_PRESENT)))
+ cont_err(" 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, NULL, 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 void update_cache_size(char *container, struct mdinfo *sra,
+ struct mdinfo *info,
+ int disks, unsigned long long blocks)
+{
+ /* Check that the internal stripe cache is
+ * large enough, or it won't work.
+ * It must hold at least 4 stripes of the larger
+ * chunk size
+ */
+ unsigned long cache;
+ cache = max(info->array.chunk_size, info->new_chunk);
+ cache *= 4; /* 4 stripes minimum */
+ cache /= 512; /* convert to sectors */
+ /* make sure there is room for 'blocks' with a bit to spare */
+ if (cache < 16 + blocks / disks)
+ cache = 16 + blocks / disks;
+ cache /= (4096/512); /* Covert from sectors to pages */
+
+ if (sra->cache_size < cache)
+ subarray_set_num(container, sra, "stripe_cache_size",
+ cache+1);
+}
+
+static int impose_reshape(struct mdinfo *sra,
+ struct mdinfo *info,
+ struct supertype *st,
+ int fd,
+ int restart,
+ char *devname, char *container,
+ struct reshape *reshape)
+{
+ struct mdu_array_info_s array;
+
+ sra->new_chunk = info->new_chunk;
+
+ if (restart) {
+ /* for external metadata checkpoint saved by mdmon can be lost
+ * or missed /due to e.g. crash/. Check if md is not during
+ * restart farther than metadata points to.
+ * If so, this means metadata information is obsolete.
+ */
+ if (st->ss->external)
+ verify_reshape_position(info, reshape->level);
+ sra->reshape_progress = info->reshape_progress;
+ } else {
+ sra->reshape_progress = 0;
+ if (reshape->after.data_disks < reshape->before.data_disks)
+ /* start from the end of the new array */
+ sra->reshape_progress = (sra->component_size
+ * reshape->after.data_disks);
+ }
+
+ ioctl(fd, GET_ARRAY_INFO, &array);
+ if (info->array.chunk_size == info->new_chunk &&
+ reshape->before.layout == reshape->after.layout &&
+ st->ss->external == 0) {
+ /* use SET_ARRAY_INFO but only if reshape hasn't started */
+ array.raid_disks = reshape->after.data_disks + reshape->parity;
+ if (!restart &&
+ ioctl(fd, SET_ARRAY_INFO, &array) != 0) {
+ int err = errno;
+
+ pr_err("Cannot set device shape for %s: %s\n",
+ devname, strerror(errno));
+
+ if (err == EBUSY &&
+ (array.state & (1<<MD_SB_BITMAP_PRESENT)))
+ cont_err("Bitmap must be removed before"
+ " shape can be changed\n");
+
+ goto release;
+ }
+ } else if (!restart) {
+ /* set them all just in case some old 'new_*' value
+ * persists from some earlier problem.
+ */
+ int err = 0;
+ if (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 && subarray_set_num(container, sra, "raid_disks",
+ reshape->after.data_disks +
+ reshape->parity) < 0)
+ err = errno;
+ if (err) {
+ pr_err("Cannot set device shape for %s\n",
+ devname);
+
+ if (err == EBUSY &&
+ (array.state & (1<<MD_SB_BITMAP_PRESENT)))
+ cont_err("Bitmap must be removed before"
+ " shape can be changed\n");
+ goto release;
+ }
+ }
+ return 0;
+release:
+ return -1;
+}
+