+ st->active = array.active_disks;
+ st->working = array.working_disks;
+ st->spare = array.spare_disks;
+ st->failed = array.failed_disks;
+ st->utime = array.utime;
+ st->raid = array.raid_disks;
+ st->err = 0;
+ if ((st->active < st->raid) && st->spare == 0)
+ return 1;
+ return 0;
+}
+
+static int add_new_arrays(struct mdstat_ent *mdstat, struct state **statelist,
+ int test, struct alert_info *info)
+{
+ struct mdstat_ent *mse;
+ int new_found = 0;
+
+ for (mse=mdstat; mse; mse=mse->next)
+ if (mse->devnum != INT_MAX &&
+ (!mse->level || /* retrieve containers */
+ (strcmp(mse->level, "raid0") != 0 &&
+ strcmp(mse->level, "linear") != 0))
+ ) {
+ struct state *st = calloc(1, sizeof *st);
+ mdu_array_info_t array;
+ int fd;
+ if (st == NULL)
+ continue;
+ st->devname = strdup(get_md_name(mse->devnum));
+ if ((fd = open(st->devname, O_RDONLY)) < 0 ||
+ ioctl(fd, GET_ARRAY_INFO, &array)< 0) {
+ /* no such array */
+ if (fd >=0) close(fd);
+ put_md_name(st->devname);
+ free(st->devname);
+ if (st->metadata) {
+ st->metadata->ss->free_super(st->metadata);
+ free(st->metadata);
+ }
+ free(st);
+ continue;
+ }
+ close(fd);
+ st->next = *statelist;
+ st->err = 1;
+ st->devnum = mse->devnum;
+ st->percent = -2;
+ st->expected_spares = -1;
+ if (mse->metadata_version &&
+ strncmp(mse->metadata_version, "external:", 9) == 0 &&
+ is_subarray(mse->metadata_version+9))
+ st->parent_dev =
+ devname2devnum(mse->metadata_version+10);
+ else
+ st->parent_dev = NoMdDev;
+ *statelist = st;
+ if (test)
+ alert("TestMessage", st->devname, NULL, info);
+ alert("NewArray", st->devname, NULL, info);
+ new_found = 1;
+ }
+ return new_found;
+}
+
+static int get_min_spare_size_required(struct state *st, unsigned long long *sizep)
+{
+ int fd;
+
+ if (!st->metadata ||
+ !st->metadata->ss->min_acceptable_spare_size) {
+ *sizep = 0;
+ return 0;
+ }
+
+ fd = open(st->devname, O_RDONLY);
+ if (fd < 0)
+ return 1;
+ if (st->metadata->ss->external)
+ st->metadata->ss->load_container(st->metadata, fd, st->devname);
+ else
+ st->metadata->ss->load_super(st->metadata, fd, st->devname);
+ close(fd);
+ if (!st->metadata->sb)
+ return 1;
+ *sizep = st->metadata->ss->min_acceptable_spare_size(st->metadata);
+ st->metadata->ss->free_super(st->metadata);
+
+ return 0;
+}
+
+static int check_donor(struct state *from, struct state *to)
+{
+ struct state *sub;
+
+ if (from == to)
+ return 0;
+ if (from->parent)
+ /* Cannot move from a member */
+ return 0;
+ if (from->err)
+ return 0;
+ for (sub = from->subarray; sub; sub = sub->subarray)
+ /* If source array has degraded subarrays, don't
+ * remove anything
+ */
+ if (sub->active < sub->raid)
+ return 0;
+ if (from->metadata->ss->external == 0)
+ if (from->active < from->raid)
+ return 0;
+ if (from->spare <= 0)
+ return 0;
+ return 1;
+}
+
+static dev_t choose_spare(struct state *from, struct state *to,
+ struct domainlist *domlist, unsigned long long min_size)
+{
+ int d;
+ dev_t dev = 0;
+
+ for (d = from->raid; !dev && d < MaxDisks; d++) {
+ if (from->devid[d] > 0 &&
+ from->devstate[d] == 0) {
+ struct dev_policy *pol;
+ unsigned long long dev_size;
+
+ if (to->metadata->ss->external &&
+ test_partition_from_id(from->devid[d]))
+ continue;
+
+ if (min_size &&
+ dev_size_from_id(from->devid[d], &dev_size) &&
+ dev_size < min_size)
+ continue;
+
+ pol = devnum_policy(from->devid[d]);
+ if (from->spare_group)
+ pol_add(&pol, pol_domain,
+ from->spare_group, NULL);
+ if (domain_test(domlist, pol, to->metadata->ss->name) == 1)
+ dev = from->devid[d];
+ dev_policy_free(pol);
+ }
+ }
+ return dev;
+}
+
+static dev_t container_choose_spare(struct state *from, struct state *to,
+ struct domainlist *domlist,
+ unsigned long long min_size, int active)
+{
+ /* This is similar to choose_spare, but we cannot trust devstate,
+ * so we need to read the metadata instead
+ */
+ struct mdinfo *list;
+ struct supertype *st = from->metadata;
+ int fd = open(from->devname, O_RDONLY);
+ int err;
+ dev_t dev = 0;
+
+ if (fd < 0)
+ return 0;
+ if (!st->ss->getinfo_super_disks) {
+ close(fd);
+ return 0;
+ }
+
+ err = st->ss->load_container(st, fd, NULL);
+ close(fd);
+ if (err)
+ return 0;
+
+ if (from == to) {
+ /* We must check if number of active disks has not increased
+ * since ioctl in main loop. mdmon may have added spare
+ * to subarray. If so we do not need to look for more spares
+ * so return non zero value */
+ int active_cnt = 0;
+ struct mdinfo *dp;
+ list = st->ss->getinfo_super_disks(st);
+ if (!list) {
+ st->ss->free_super(st);
+ return 1;
+ }
+ dp = list->devs;
+ while (dp) {
+ if (dp->disk.state & (1<<MD_DISK_SYNC) &&
+ !(dp->disk.state & (1<<MD_DISK_FAULTY)))
+ active_cnt++;
+ dp = dp->next;
+ }
+ sysfs_free(list);
+ if (active < active_cnt) {
+ /* Spare just activated.*/
+ st->ss->free_super(st);
+ return 1;
+ }
+ }
+
+ /* We only need one spare so full list not needed */
+ list = container_choose_spares(st, min_size, domlist, from->spare_group,
+ to->metadata->ss->name, 1);
+ if (list) {
+ struct mdinfo *disks = list->devs;
+ if (disks)
+ dev = makedev(disks->disk.major, disks->disk.minor);
+ sysfs_free(list);
+ }
+ st->ss->free_super(st);
+ return dev;
+}
+
+
+static void try_spare_migration(struct state *statelist, struct alert_info *info)
+{
+ struct state *from;
+ struct state *st;
+
+ link_containers_with_subarrays(statelist);
+ for (st = statelist; st; st = st->next)
+ if (st->active < st->raid &&
+ st->spare == 0 && !st->err) {
+ struct domainlist *domlist = NULL;
+ int d;
+ struct state *to = st;
+ unsigned long long min_size;
+
+ if (to->parent_dev != NoMdDev && !to->parent)
+ /* subarray monitored without parent container
+ * we can't move spares here */
+ continue;
+
+ if (to->parent)
+ /* member of a container */
+ to = to->parent;
+
+ if (get_min_spare_size_required(to, &min_size))
+ continue;
+ if (to->metadata->ss->external) {
+ /* We must make sure there is
+ * no suitable spare in container already.
+ * If there is we don't add more */
+ dev_t devid = container_choose_spare(
+ to, to, NULL, min_size, st->active);
+ if (devid > 0)
+ continue;
+ }
+ for (d = 0; d < MaxDisks; d++)
+ if (to->devid[d])
+ domainlist_add_dev(&domlist,
+ to->devid[d],
+ to->metadata->ss->name);
+ if (to->spare_group)
+ domain_add(&domlist, to->spare_group);
+ /*
+ * No spare migration if the destination
+ * has no domain. Skip this array.
+ */
+ if (!domlist)
+ continue;
+ for (from=statelist ; from ; from=from->next) {
+ dev_t devid;
+ if (!check_donor(from, to))
+ continue;
+ if (from->metadata->ss->external)
+ devid = container_choose_spare(
+ from, to, domlist, min_size, 0);
+ else
+ devid = choose_spare(from, to, domlist,
+ min_size);
+ if (devid > 0
+ && move_spare(from->devname, to->devname, devid)) {
+ alert("MoveSpare", to->devname, from->devname, info);
+ break;
+ }
+ }
+ domain_free(domlist);
+ }
+}
+
+/* search the statelist to connect external
+ * metadata subarrays with their containers
+ * We always completely rebuild the tree from scratch as
+ * that is safest considering the possibility of entries
+ * disappearing or changing.
+ */
+static void link_containers_with_subarrays(struct state *list)
+{
+ struct state *st;
+ struct state *cont;
+ for (st = list; st; st = st->next) {
+ st->parent = NULL;
+ st->subarray = NULL;
+ }
+ for (st = list; st; st = st->next)
+ if (st->parent_dev != NoMdDev)
+ for (cont = list; cont; cont = cont->next)
+ if (!cont->err &&
+ cont->parent_dev == NoMdDev &&
+ cont->devnum == st->parent_dev) {
+ st->parent = cont;
+ st->subarray = cont->subarray;
+ cont->subarray = st;
+ break;
+ }