]> git.ipfire.org Git - thirdparty/mdadm.git/blobdiff - super-intel.c
Release 3.2.6 - stability release
[thirdparty/mdadm.git] / super-intel.c
index c65d39bab3f5ba9bf6c92bac1060b83974ff4593..544cb05967dc8a7f8a33457e853798211b1952fe 100644 (file)
@@ -405,6 +405,7 @@ struct extent {
 enum imsm_reshape_type {
        CH_TAKEOVER,
        CH_MIGRATION,
+       CH_ARRAY_SIZE,
 };
 
 /* definition of messages passed to imsm_process_update */
@@ -418,6 +419,7 @@ enum imsm_update_type {
        update_reshape_migration,
        update_takeover,
        update_general_migration_checkpoint,
+       update_size_change,
 };
 
 struct imsm_update_activate_spare {
@@ -470,6 +472,12 @@ struct imsm_update_reshape_migration {
        int new_disks[1]; /* new_raid_disks - old_raid_disks makedev number */
 };
 
+struct imsm_update_size_change {
+       enum imsm_update_type type;
+       int subdev;
+       long long new_size;
+};
+
 struct imsm_update_general_migration_checkpoint {
        enum imsm_update_type type;
        __u32 curr_migr_unit;
@@ -910,6 +918,7 @@ static unsigned long long blocks_per_member(struct imsm_map *map)
        return join_u32(map->blocks_per_member_lo, map->blocks_per_member_hi);
 }
 
+#ifndef MDASSEMBLE
 static unsigned long long num_data_stripes(struct imsm_map *map)
 {
        if (map == NULL)
@@ -921,6 +930,7 @@ static void set_total_blocks(struct imsm_disk *disk, unsigned long long n)
 {
        split_ull(n, &disk->total_blocks_lo, &disk->total_blocks_hi);
 }
+#endif
 
 static void set_pba_of_lba0(struct imsm_map *map, unsigned long long n)
 {
@@ -1783,8 +1793,13 @@ static void print_imsm_capability(const struct imsm_orom *orom)
               imsm_orom_has_chunk(orom, 1024*16) ? " 16M" : "",
               imsm_orom_has_chunk(orom, 1024*32) ? " 32M" : "",
               imsm_orom_has_chunk(orom, 1024*64) ? " 64M" : "");
+       printf("    2TB volumes :%s supported\n",
+              (orom->attr & IMSM_OROM_ATTR_2TB)?"":" not");
+       printf("      2TB disks :%s supported\n",
+              (orom->attr & IMSM_OROM_ATTR_2TB_DISK)?"":" not");
        printf("      Max Disks : %d\n", orom->tds);
-       printf("    Max Volumes : %d\n", orom->vpa);
+       printf("    Max Volumes : %d per array, %d per controller\n",
+              orom->vpa, orom->vphba);
        return;
 }
 
@@ -2027,9 +2042,11 @@ static __u8 imsm_num_data_members(struct imsm_dev *dev, int second_map)
 
        switch (get_imsm_raid_level(map)) {
        case 0:
+               return map->num_members;
+               break;
        case 1:
        case 10:
-               return map->num_members;
+               return map->num_members/2;
        case 5:
                return map->num_members - 1;
        default:
@@ -2439,6 +2456,32 @@ int imsm_reshape_blocks_arrays_changes(struct intel_super *super)
        }
        return rv;
 }
+static unsigned long long imsm_component_size_aligment_check(int level,
+                                             int chunk_size,
+                                             unsigned long long component_size)
+{
+       unsigned int component_size_alligment;
+
+       /* check component size aligment
+       */
+       component_size_alligment = component_size % (chunk_size/512);
+
+       dprintf("imsm_component_size_aligment_check(Level: %i, "
+               "chunk_size = %i, component_size = %llu), "
+               "component_size_alligment = %u\n",
+               level, chunk_size, component_size,
+               component_size_alligment);
+
+       if (component_size_alligment && (level != 1) && (level != UnSet)) {
+               dprintf("imsm: reported component size alligned from %llu ",
+                       component_size);
+               component_size -= component_size_alligment;
+               dprintf("to %llu (%i).\n",
+                       component_size, component_size_alligment);
+       }
+
+       return component_size;
+}
 
 static void getinfo_super_imsm_volume(struct supertype *st, struct mdinfo *info, char *dmap)
 {
@@ -2450,7 +2493,6 @@ static void getinfo_super_imsm_volume(struct supertype *st, struct mdinfo *info,
        struct imsm_map *map_to_analyse = map;
        struct dl *dl;
        char *devname;
-       unsigned int component_size_alligment;
        int map_disks = info->array.raid_disks;
 
        memset(info, 0, sizeof(*info));
@@ -2533,19 +2575,10 @@ static void getinfo_super_imsm_volume(struct supertype *st, struct mdinfo *info,
        info->data_offset         = pba_of_lba0(map_to_analyse);
        info->component_size      = blocks_per_member(map_to_analyse);
 
-       /* check component size aligment
-        */
-       component_size_alligment =
-               info->component_size % (info->array.chunk_size/512);
-
-       if (component_size_alligment &&
-           (info->array.level != 1) && (info->array.level != UnSet)) {
-               dprintf("imsm: reported component size alligned from %llu ",
-                       info->component_size);
-               info->component_size -= component_size_alligment;
-               dprintf("to %llu (%i).\n",
-                       info->component_size, component_size_alligment);
-       }
+       info->component_size = imsm_component_size_aligment_check(
+                                                       info->array.level,
+                                                       info->array.chunk_size,
+                                                       info->component_size);
 
        memset(info->uuid, 0, sizeof(info->uuid));
        info->recovery_start = MaxSector;
@@ -3590,6 +3623,32 @@ static int load_imsm_mpb(int fd, struct intel_super *super, char *devname)
 
 static int read_imsm_migr_rec(int fd, struct intel_super *super);
 
+/* clears hi bits in metadata if MPB_ATTRIB_2TB_DISK not set */
+static void clear_hi(struct intel_super *super)
+{
+       struct imsm_super *mpb = super->anchor;
+       int i, n;
+       if (mpb->attributes & MPB_ATTRIB_2TB_DISK)
+               return;
+       for (i = 0; i < mpb->num_disks; ++i) {
+               struct imsm_disk *disk = &mpb->disk[i];
+               disk->total_blocks_hi = 0;
+       }
+       for (i = 0; i < mpb->num_raid_devs; ++i) {
+               struct imsm_dev *dev = get_imsm_dev(super, i);
+               if (!dev)
+                       return;
+               for (n = 0; n < 2; ++n) {
+                       struct imsm_map *map = get_imsm_map(dev, n);
+                       if (!map)
+                               continue;
+                       map->pba_of_lba0_hi = 0;
+                       map->blocks_per_member_hi = 0;
+                       map->num_data_stripes_hi = 0;
+               }
+       }
+}
+
 static int
 load_and_parse_mpb(int fd, struct intel_super *super, char *devname, int keep_fd)
 {
@@ -3602,7 +3661,7 @@ load_and_parse_mpb(int fd, struct intel_super *super, char *devname, int keep_fd
        if (err)
                return err;
        err = parse_raid_devices(super);
-
+       clear_hi(super);
        return err;
 }
 
@@ -4049,6 +4108,8 @@ imsm_thunderdome(struct intel_super **super_list, int len)
                if (s == champion)
                        continue;
 
+               mpb->attributes |= s->anchor->attributes & MPB_ATTRIB_2TB_DISK;
+
                for (i = 0; i < mpb->num_disks; i++) {
                        struct imsm_disk *disk;
 
@@ -4292,7 +4353,7 @@ static int get_super_block(struct intel_super **super_list, int devnum, char *de
        } else {
                if (s)
                        free(s);
-               if (dfd)
+               if (dfd >= 0)
                        close(dfd);
        }
        if ((dfd >= 0) && (!keep_fd))
@@ -4597,10 +4658,7 @@ static int init_super_imsm_volume(struct supertype *st, mdu_array_info_t *info,
        }
 
        strncpy((char *) dev->volume, name, MAX_RAID_SERIAL_LEN);
-       if (info->level == 1)
-               array_blocks = info_to_blocks_per_member(info, size);
-       else
-               array_blocks = calc_array_size(info->level, info->raid_disks,
+       array_blocks = calc_array_size(info->level, info->raid_disks,
                                               info->layout, info->chunk_size,
                                               size * 2);
        /* round array size down to closest MB */
@@ -4620,7 +4678,8 @@ static int init_super_imsm_volume(struct supertype *st, mdu_array_info_t *info,
        map->blocks_per_strip = __cpu_to_le16(info_to_blocks_per_strip(info));
        map->failed_disk_num = ~0;
        if (info->level > 0)
-               map->map_state = IMSM_T_STATE_UNINITIALIZED;
+               map->map_state = (info->state ? IMSM_T_STATE_NORMAL
+                                 : IMSM_T_STATE_UNINITIALIZED);
        else
                map->map_state = info->failed_disks ? IMSM_T_STATE_FAILED :
                                                      IMSM_T_STATE_NORMAL;
@@ -5021,6 +5080,9 @@ static int write_super_imsm_spares(struct intel_super *super, int doclose)
                        continue;
 
                spare->disk[0] = d->disk;
+               if (__le32_to_cpu(d->disk.total_blocks_hi) > 0)
+                       spare->attributes |= MPB_ATTRIB_2TB_DISK;
+
                sum = __gen_imsm_checksum(spare);
                spare->family_num = __cpu_to_le32(sum);
                spare->orig_family_num = 0;
@@ -5304,14 +5366,22 @@ static int validate_geometry_imsm_container(struct supertype *st, int level,
                return 0;
        }
        close(fd);
-       if (super->orom && raiddisks > super->orom->tds) {
-               if (verbose)
-                       fprintf(stderr, Name ": %d exceeds maximum number of"
-                               " platform supported disks: %d\n",
-                               raiddisks, super->orom->tds);
-
-               free_imsm(super);
-               return 0;
+       if (super->orom) {
+               if (raiddisks > super->orom->tds) {
+                       if (verbose)
+                               fprintf(stderr, Name ": %d exceeds maximum number of"
+                                       " platform supported disks: %d\n",
+                                       raiddisks, super->orom->tds);
+                       free_imsm(super);
+                       return 0;
+               }
+               if ((super->orom->attr & IMSM_OROM_ATTR_2TB_DISK) == 0 &&
+                   (ldsize >> 9) >> 32 > 0) {
+                       if (verbose)
+                               fprintf(stderr, Name ": %s exceeds maximum platform supported size\n", dev);
+                       free_imsm(super);
+                       return 0;
+               }
        }
 
        *freesize = avail_size_imsm(st, ldsize >> 9);
@@ -5617,6 +5687,7 @@ get_devices(const char *hba_path)
                        free(dv);
                }
        }
+       closedir(dir);
        return devlist;
 }
 
@@ -5833,7 +5904,7 @@ static int imsm_default_chunk(const struct imsm_orom *orom)
 
 static int
 validate_geometry_imsm_orom(struct intel_super *super, int level, int layout,
-                           int raiddisks, int *chunk, int verbose)
+                           int raiddisks, int *chunk, unsigned long long size, int verbose)
 {
        /* check/set platform and metadata limits/defaults */
        if (super->orom && raiddisks > super->orom->dpa) {
@@ -5868,6 +5939,12 @@ validate_geometry_imsm_orom(struct intel_super *super, int level, int layout,
                                layout, level);
                return 0;
        }
+
+       if (super->orom && (super->orom->attr & IMSM_OROM_ATTR_2TB) == 0 && chunk &&
+                       (calc_array_size(level, raiddisks, layout, *chunk, size) >> 32) > 0) {
+               pr_vrb(": platform does not support a volume size over 2TB\n");
+               return 0;
+       }
        return 1;
 }
 
@@ -5895,7 +5972,7 @@ static int validate_geometry_imsm_volume(struct supertype *st, int level,
 
        mpb = super->anchor;
 
-       if (!validate_geometry_imsm_orom(super, level, layout, raiddisks, chunk, verbose)) {
+       if (!validate_geometry_imsm_orom(super, level, layout, raiddisks, chunk, size, verbose)) {
                fprintf(stderr, Name ": RAID gemetry validation failed. "
                        "Cannot proceed with the action(s).\n");
                return 0;
@@ -6045,7 +6122,7 @@ static int validate_geometry_imsm_volume(struct supertype *st, int level,
                int count = count_volumes(super->hba->path,
                                      super->orom->dpa, verbose);
                if (super->orom->vphba <= count) {
-                       pr_vrb(": platform does not support more then %d raid volumes.\n",
+                       pr_vrb(": platform does not support more than %d raid volumes.\n",
                               super->orom->vphba);
                        return 0;
                }
@@ -6053,7 +6130,7 @@ static int validate_geometry_imsm_volume(struct supertype *st, int level,
        return 1;
 }
 
-static int reserve_space(struct supertype *st, int raiddisks,
+static int imsm_get_free_size(struct supertype *st, int raiddisks,
                         unsigned long long size, int chunk,
                         unsigned long long *freesize)
 {
@@ -6131,9 +6208,32 @@ static int reserve_space(struct supertype *st, int raiddisks,
 
        *freesize = size;
 
+       dprintf("imsm: imsm_get_free_size() returns : %llu\n", size);
+
        return 1;
 }
 
+static int reserve_space(struct supertype *st, int raiddisks,
+                        unsigned long long size, int chunk,
+                        unsigned long long *freesize)
+{
+       struct intel_super *super = st->sb;
+       struct dl *dl;
+       int cnt;
+       int rv = 0;
+
+       rv = imsm_get_free_size(st, raiddisks, size, chunk, freesize);
+       if (rv) {
+               cnt = 0;
+               for (dl = super->disks; dl; dl = dl->next)
+                       if (dl->e)
+                               dl->raiddisk = cnt++;
+               rv = 1;
+       }
+
+       return rv;
+}
+
 static int validate_geometry_imsm(struct supertype *st, int level, int layout,
                                  int raiddisks, int *chunk, unsigned long long size,
                                  char *dev, unsigned long long *freesize,
@@ -6160,7 +6260,7 @@ static int validate_geometry_imsm(struct supertype *st, int level, int layout,
                if (st->sb) {
                        struct intel_super *super = st->sb;
                        if (!validate_geometry_imsm_orom(st->sb, level, layout,
-                                                        raiddisks, chunk,
+                                                        raiddisks, chunk, size,
                                                         verbose))
                                return 0;
                        /* we are being asked to automatically layout a
@@ -6179,7 +6279,7 @@ static int validate_geometry_imsm(struct supertype *st, int level, int layout,
                                                      super->orom->dpa, verbose);
                                if (super->orom->vphba <= count) {
                                        pr_vrb(": platform does not support more"
-                                              "then %d raid volumes.\n",
+                                              " than %d raid volumes.\n",
                                               super->orom->vphba);
                                        return 0;
                                }
@@ -6571,7 +6671,7 @@ static struct mdinfo *container_content_imsm(struct supertype *st, char *subarra
                                                 get_imsm_raid_level(map), /* RAID level */
                                                 imsm_level_to_layout(get_imsm_raid_level(map)),
                                                 map->num_members, /* raid disks */
-                                                &chunk,
+                                                &chunk, join_u32(dev->size_low, dev->size_high),
                                                 1 /* verbose */)) {
                        fprintf(stderr, Name ": IMSM RAID geometry validation"
                                " failed.  Array %s activation is blocked.\n",
@@ -6926,7 +7026,8 @@ static void handle_missing(struct intel_super *super, struct imsm_dev *dev)
        super->updates_pending++;
 }
 
-static unsigned long long imsm_set_array_size(struct imsm_dev *dev)
+static unsigned long long imsm_set_array_size(struct imsm_dev *dev,
+                                             long long new_size)
 {
        int used_disks = imsm_num_data_members(dev, MAP_0);
        unsigned long long array_blocks;
@@ -6945,8 +7046,17 @@ static unsigned long long imsm_set_array_size(struct imsm_dev *dev)
 
        /* set array size in metadata
         */
-       map = get_imsm_map(dev, MAP_0);
-       array_blocks = blocks_per_member(map) * used_disks;
+       if (new_size <= 0) {
+               /* OLCE size change is caused by added disks
+                */
+               map = get_imsm_map(dev, MAP_0);
+               array_blocks = blocks_per_member(map) * used_disks;
+       } else {
+               /* Online Volume Size Change
+                * Using  available free space
+                */
+               array_blocks = new_size;
+       }
 
        /* round array size down to closest MB
         */
@@ -7003,7 +7113,7 @@ static void imsm_progress_container_reshape(struct intel_super *super)
                memcpy(map2, map, copy_map_size);
                map2->num_members = prev_num_members;
 
-               imsm_set_array_size(dev);
+               imsm_set_array_size(dev, -1);
                super->clean_migration_record_by_mdmon = 1;
                super->updates_pending++;
        }
@@ -7167,6 +7277,8 @@ static void imsm_set_disk(struct active_array *a, int n, int state)
        struct imsm_dev *dev = get_imsm_dev(super, inst);
        struct imsm_map *map = get_imsm_map(dev, MAP_0);
        struct imsm_disk *disk;
+       struct mdinfo *mdi;
+       int recovery_not_finished = 0;
        int failed;
        __u32 ord;
        __u8 map_state;
@@ -7207,6 +7319,21 @@ static void imsm_set_disk(struct active_array *a, int n, int state)
                dprintf("normal: ");
                if (is_rebuilding(dev)) {
                        dprintf("while rebuilding");
+                       /* check if recovery is really finished */
+                       for (mdi = a->info.devs; mdi ; mdi = mdi->next)
+                               if (mdi->recovery_start != MaxSector) {
+                                       recovery_not_finished = 1;
+                                       break;
+                               }
+                       if (recovery_not_finished) {
+                               dprintf("\nimsm: Rebuild has not finished yet, "
+                                               "state not changed");
+                               if (a->last_checkpoint < mdi->recovery_start) {
+                                       a->last_checkpoint = mdi->recovery_start;
+                                       super->updates_pending++;
+                               }
+                               break;
+                       }
                        end_migration(dev, super, map_state);
                        map = get_imsm_map(dev, MAP_0);
                        map->failed_disk_num = ~0;
@@ -7893,7 +8020,7 @@ skip_disk_add:
                        *tofree = *space_list;
                        /* calculate new size
                         */
-                       imsm_set_array_size(new_dev);
+                       imsm_set_array_size(new_dev, -1);
 
                        ret_val = 1;
                }
@@ -7908,6 +8035,44 @@ error_disk_add:
        return ret_val;
 }
 
+static int apply_size_change_update(struct imsm_update_size_change *u,
+               struct intel_super *super)
+{
+       struct intel_dev *id;
+       int ret_val = 0;
+
+       dprintf("apply_size_change_update()\n");
+       if ((u->subdev < 0) ||
+           (u->subdev > 1)) {
+               dprintf("imsm: Error: Wrong subdev: %i\n", u->subdev);
+               return ret_val;
+       }
+
+       for (id = super->devlist ; id; id = id->next) {
+               if (id->index == (unsigned)u->subdev) {
+                       struct imsm_dev *dev = get_imsm_dev(super, u->subdev);
+                       struct imsm_map *map = get_imsm_map(dev, MAP_0);
+                       int used_disks = imsm_num_data_members(dev, MAP_0);
+                       unsigned long long blocks_per_member;
+
+                       /* calculate new size
+                        */
+                       blocks_per_member = u->new_size / used_disks;
+                       dprintf("imsm: apply_size_change_update(size: %llu, "
+                               "blocks per member: %llu)\n",
+                               u->new_size, blocks_per_member);
+                       set_blocks_per_member(map, blocks_per_member);
+                       imsm_set_array_size(dev, u->new_size);
+
+                       ret_val = 1;
+                       break;
+               }
+       }
+
+       return ret_val;
+}
+
+
 static int apply_update_activate_spare(struct imsm_update_activate_spare *u,
                                       struct intel_super *super,
                                       struct active_array *active_array)
@@ -8107,7 +8272,7 @@ static int apply_reshape_container_disks_update(struct imsm_update_reshape *u,
                        newmap = get_imsm_map(newdev, MAP_1);
                        memcpy(newmap, oldmap, sizeof_imsm_map(oldmap));
 
-                       imsm_set_array_size(newdev);
+                       imsm_set_array_size(newdev, -1);
                }
 
                sp = (void **)id->dev;
@@ -8315,6 +8480,12 @@ static void imsm_process_update(struct supertype *st,
                        super->updates_pending++;
                break;
        }
+       case update_size_change: {
+               struct imsm_update_size_change *u = (void *)update->buf;
+               if (apply_size_change_update(u, super))
+                       super->updates_pending++;
+               break;
+       }
        case update_activate_spare: {
                struct imsm_update_activate_spare *u = (void *) update->buf; 
                if (apply_update_activate_spare(u, super, st->arrays))
@@ -8709,6 +8880,9 @@ static void imsm_prepare_update(struct supertype *st,
                dprintf("New anchor length is %llu\n", (unsigned long long)len);
                break;
        }
+       case update_size_change: {
+               break;
+       }
        case update_create_array: {
                struct imsm_update_create_array *u = (void *) update->buf;
                struct intel_dev *dv;
@@ -9358,7 +9532,8 @@ static int imsm_find_array_minor_by_subdev(int subdev, int container, int *minor
 
 static int imsm_reshape_is_allowed_on_container(struct supertype *st,
                                                struct geo_params *geo,
-                                               int *old_raid_disks)
+                                               int *old_raid_disks,
+                                               int direction)
 {
        /* currently we only support increasing the number of devices
         * for a container.  This increases the number of device for each
@@ -9382,6 +9557,12 @@ static int imsm_reshape_is_allowed_on_container(struct supertype *st,
                return ret_val;
        }
 
+       if (direction == ROLLBACK_METADATA_CHANGES) {
+               dprintf("imsm: Metadata changes rollback is not supported for "
+                       "container operation.\n");
+               return ret_val;
+       }
+
        info = container_content_imsm(st, NULL);
        for (member = info; member; member = member->next) {
                int result;
@@ -9566,6 +9747,43 @@ abort:
        return 0;
 }
 
+
+/******************************************************************************
+ * function: imsm_create_metadata_update_for_size_change()
+ *           Creates update for IMSM array for array size change.
+ *
+ ******************************************************************************/
+static int imsm_create_metadata_update_for_size_change(
+                               struct supertype *st,
+                               struct geo_params *geo,
+                               struct imsm_update_size_change **updatep)
+{
+       struct intel_super *super = st->sb;
+       int update_memory_size = 0;
+       struct imsm_update_size_change *u = NULL;
+
+       dprintf("imsm_create_metadata_update_for_size_change(enter)"
+               " New size = %llu\n", geo->size);
+
+       /* size of all update data without anchor */
+       update_memory_size = sizeof(struct imsm_update_size_change);
+
+       u = calloc(1, update_memory_size);
+       if (u == NULL) {
+               dprintf("error: cannot get memory for "
+                       "imsm_create_metadata_update_for_size_change\n");
+               return 0;
+       }
+       u->type = update_size_change;
+       u->subdev = super->current_vol;
+       u->new_size = geo->size;
+
+       dprintf("imsm: reshape update preparation : OK\n");
+       *updatep = u;
+
+       return update_memory_size;
+}
+
 /******************************************************************************
  * function: imsm_create_metadata_update_for_migration()
  *           Creates update for IMSM array.
@@ -9665,11 +9883,13 @@ static void imsm_update_metadata_locally(struct supertype *st,
 * Function:    imsm_analyze_change
 * Description: Function analyze change for single volume
 *              and validate if transition is supported
-* Parameters:  Geometry parameters, supertype structure
+* Parameters:  Geometry parameters, supertype structure,
+*              metadata change direction (apply/rollback)
 * Returns:     Operation type code on success, -1 if fail
 ****************************************************************************/
 enum imsm_reshape_type imsm_analyze_change(struct supertype *st,
-                                          struct geo_params *geo)
+                                          struct geo_params *geo,
+                                          int direction)
 {
        struct mdinfo info;
        int change = -1;
@@ -9679,6 +9899,13 @@ enum imsm_reshape_type imsm_analyze_change(struct supertype *st,
        int devNumChange = 0;
        /* imsm compatible layout value for array geometry verification */
        int imsm_layout = -1;
+       int data_disks;
+       struct imsm_dev *dev;
+       struct intel_super *super;
+       long long current_size;
+       unsigned long long free_size;
+       long long max_size;
+       int rv;
 
        getinfo_super_imsm_volume(st, &info, NULL);
        if ((geo->level != info.array.level) &&
@@ -9760,6 +9987,99 @@ enum imsm_reshape_type imsm_analyze_change(struct supertype *st,
                geo->chunksize = info.array.chunk_size;
 
        chunk = geo->chunksize / 1024;
+
+       super = st->sb;
+       dev = get_imsm_dev(super, super->current_vol);
+       data_disks = imsm_num_data_members(dev , MAP_0);
+       /* compute current size per disk member
+        */
+       current_size = info.custom_array_size / data_disks;
+
+       if (geo->size > 0) {
+               /* align component size
+                */
+               geo->size = imsm_component_size_aligment_check(
+                                   get_imsm_raid_level(dev->vol.map),
+                                   chunk * 1024,
+                                   geo->size * 2);
+       }
+
+       if ((current_size != geo->size) && (geo->size >= 0)) {
+               if (change != -1) {
+                       fprintf(stderr,
+                               Name " Error. Size change should be the only "
+                               "one at a time.\n");
+                       change = -1;
+                       goto analyse_change_exit;
+               }
+               if ((super->current_vol + 1) != super->anchor->num_raid_devs) {
+                       fprintf(stderr,
+                               Name " Error. The last volume in container "
+                               "can be expanded only (%i/%i).\n",
+                               super->current_vol, st->devnum);
+                       goto analyse_change_exit;
+               }
+               /* check the maximum available size
+                */
+               rv =  imsm_get_free_size(st, dev->vol.map->num_members,
+                                        0, chunk, &free_size);
+               if (rv == 0)
+                       /* Cannot find maximum available space
+                        */
+                       max_size = 0;
+               else {
+                       max_size = free_size + current_size;
+                       /* align component size
+                        */
+                       max_size = imsm_component_size_aligment_check(
+                                       get_imsm_raid_level(dev->vol.map),
+                                       chunk * 1024,
+                                       max_size);
+               }
+               if (geo->size == 0) {
+                       /* requested size change to the maximum available size
+                        */
+                       if (max_size == 0) {
+                               fprintf(stderr, Name " Error. Cannot find "
+                                       "maximum available space.\n");
+                               change = -1;
+                               goto analyse_change_exit;
+                       } else
+                               geo->size = max_size;
+               }
+
+               if ((direction == ROLLBACK_METADATA_CHANGES)) {
+                       /* accept size for rollback only
+                       */
+               } else {
+                       /* round size due to metadata compatibility
+                       */
+                       geo->size = (geo->size >> SECT_PER_MB_SHIFT)
+                                   << SECT_PER_MB_SHIFT;
+                       dprintf("Prepare update for size change to %llu\n",
+                               geo->size );
+                       if (current_size >= geo->size) {
+                               fprintf(stderr,
+                                       Name " Error. Size expansion is "
+                                       "supported only (current size is %llu, "
+                                       "requested size /rounded/ is %llu).\n",
+                                       current_size, geo->size);
+                               goto analyse_change_exit;
+                       }
+                       if (max_size && geo->size > max_size) {
+                               fprintf(stderr,
+                                       Name " Error. Requested size is larger "
+                                       "than maximum available size (maximum "
+                                       "available size is %llu, "
+                                       "requested size /rounded/ is %llu).\n",
+                                       max_size, geo->size);
+                               goto analyse_change_exit;
+                       }
+               }
+               geo->size *= data_disks;
+               geo->raid_disks = dev->vol.map->num_members;
+               change = CH_ARRAY_SIZE;
+       }
        if (!validate_geometry_imsm(st,
                                    geo->level,
                                    imsm_layout,
@@ -9784,7 +10104,12 @@ enum imsm_reshape_type imsm_analyze_change(struct supertype *st,
        }
 
 analyse_change_exit:
-
+       if ((direction == ROLLBACK_METADATA_CHANGES) &&
+            ((change == CH_MIGRATION) || (change == CH_TAKEOVER))) {
+               dprintf("imsm: Metadata changes rollback is not supported for "
+                       "migration and takeover operations.\n");
+               change = -1;
+       }
        return change;
 }
 
@@ -9824,7 +10149,7 @@ int imsm_takeover(struct supertype *st, struct geo_params *geo)
 static int imsm_reshape_super(struct supertype *st, long long size, int level,
                              int layout, int chunksize, int raid_disks,
                              int delta_disks, char *backup, char *dev,
-                             int verbose)
+                             int direction, int verbose)
 {
        int ret_val = 1;
        struct geo_params geo;
@@ -9855,7 +10180,7 @@ static int imsm_reshape_super(struct supertype *st, long long size, int level,
                int old_raid_disks = 0;
 
                if (imsm_reshape_is_allowed_on_container(
-                           st, &geo, &old_raid_disks)) {
+                           st, &geo, &old_raid_disks, direction)) {
                        struct imsm_update_reshape *u = NULL;
                        int len;
 
@@ -9904,7 +10229,7 @@ static int imsm_reshape_super(struct supertype *st, long long size, int level,
                        goto exit_imsm_reshape_super;
                }
                super->current_vol = dev->index;
-               change = imsm_analyze_change(st, &geo);
+               change = imsm_analyze_change(st, &geo, direction);
                switch (change) {
                case CH_TAKEOVER:
                        ret_val = imsm_takeover(st, &geo);
@@ -9929,6 +10254,26 @@ static int imsm_reshape_super(struct supertype *st, long long size, int level,
                                free(u);
                }
                break;
+               case CH_ARRAY_SIZE: {
+                       struct imsm_update_size_change *u = NULL;
+                       int len =
+                               imsm_create_metadata_update_for_size_change(
+                                       st, &geo, &u);
+                       if (len < 1) {
+                               dprintf("imsm: "
+                                       "Cannot prepare update\n");
+                               break;
+                       }
+                       ret_val = 0;
+                       /* update metadata locally */
+                       imsm_update_metadata_locally(st, u, len);
+                       /* and possibly remotely */
+                       if (st->update_tail)
+                               append_metadata_update(st, u, len);
+                       else
+                               free(u);
+               }
+               break;
                default:
                        ret_val = 1;
                }
@@ -10026,8 +10371,10 @@ int check_degradation_change(struct mdinfo *info,
                             int degraded)
 {
        unsigned long long new_degraded;
-       sysfs_get_ll(info, NULL, "degraded", &new_degraded);
-       if (new_degraded != (unsigned long long)degraded) {
+       int rv;
+
+       rv = sysfs_get_ll(info, NULL, "degraded", &new_degraded);
+       if ((rv == -1) || (new_degraded != (unsigned long long)degraded)) {
                /* check each device to ensure it is still working */
                struct mdinfo *sd;
                new_degraded = 0;