]> git.ipfire.org Git - thirdparty/mdadm.git/blobdiff - Monitor.c
Disk removal support for Raid10->Raid0 takeover
[thirdparty/mdadm.git] / Monitor.c
index ba6735c4b139eece19ec0d95fb028b43cbf3e101..d5514e9c77241c9c8de128dc7088cb42e8d53bfa 100644 (file)
--- a/Monitor.c
+++ b/Monitor.c
@@ -44,7 +44,7 @@ struct state {
        int active, working, failed, spare, raid;
        int expected_spares;
        int devstate[MaxDisks];
-       unsigned devid[MaxDisks];
+       dev_t devid[MaxDisks];
        int percent;
        int parent_dev; /* For subarray, devnum of parent.
                         * For others, NoMdDev
@@ -296,21 +296,27 @@ static int check_one_sharer(int scan)
                        } else {
                                fprintf(stderr, Name ": Warning: One"
                                        " autorebuild process already"
-                                       " running.");
+                                       " running.\n");
                        }
                }
                fclose(fp);
        }
        if (scan) {
-               fp = fopen("/var/run/mdadm/autorebuild.pid", "w");
-               if (!fp)
-                       fprintf(stderr, Name ": Cannot create"
-                               " autorebuild.pid "
-                               "file\n");
-               else {
-                       pid = getpid();
-                       fprintf(fp, "%d\n", pid);
-                       fclose(fp);
+               if (mkdir("/var/run/mdadm", S_IRWXU) < 0 &&
+                   errno != EEXIST) {
+                       fprintf(stderr, Name ": Can't create "
+                               "autorebuild.pid file\n");
+               } else {
+                       fp = fopen("/var/run/mdadm/autorebuild.pid", "w");
+                       if (!fp)
+                               fprintf(stderr, Name ": Cannot create"
+                                       " autorebuild.pid"
+                                       "file\n");
+                       else {
+                               pid = getpid();
+                               fprintf(fp, "%d\n", pid);
+                               fclose(fp);
+                       }
                }
        }
        return 0;
@@ -424,6 +430,10 @@ static int check_array(struct state *st, struct mdstat_ent *mdstat,
                       int test, struct alert_info *ainfo,
                       int increments)
 {
+       /* Update the state 'st' to reflect any changes shown in mdstat,
+        * or found by directly examining the array, and return
+        * '1' if the array is degraded, or '0' if it is optimal (or dead).
+        */
        struct { int state, major, minor; } info[MaxDisks];
        mdu_array_info_t array;
        struct mdstat_ent *mse = NULL, *mse2;
@@ -497,7 +507,10 @@ static int check_array(struct state *st, struct mdstat_ent *mdstat,
                    ))) {
                close(fd);
                st->err = 0;
-               return 0;
+               if ((st->active < st->raid) && st->spare == 0)
+                       return 1;
+               else
+                       return 0;
        }
        if (st->utime == 0 && /* new array */
            mse->pattern && strchr(mse->pattern, '_') /* degraded */
@@ -681,8 +694,28 @@ static int add_new_arrays(struct mdstat_ent *mdstat, struct state *statelist,
        return new_found;
 }
 
+unsigned long long min_spare_size_required(struct state *st)
+{
+       int fd;
+       unsigned long long rv = 0;
+
+       if (!st->metadata ||
+           !st->metadata->ss->min_acceptable_spare_size)
+               return rv;
+
+       fd = open(st->devname, O_RDONLY);
+       if (fd < 0)
+               return 0;
+       st->metadata->ss->load_super(st->metadata, fd, st->devname);
+       close(fd);
+       rv = st->metadata->ss->min_acceptable_spare_size(st->metadata);
+       st->metadata->ss->free_super(st->metadata);
+
+       return rv;
+}
+
 static int move_spare(struct state *from, struct state *to,
-                     struct domainlist *domlist,
+                     dev_t devid,
                      struct alert_info *info)
 {
        struct mddev_dev devlist;
@@ -691,35 +724,19 @@ static int move_spare(struct state *from, struct state *to,
        /* try to remove and add */
        int fd1 = open(to->devname, O_RDONLY);
        int fd2 = open(from->devname, O_RDONLY);
-       int dev = -1;
-       int d;
+
        if (fd1 < 0 || fd2 < 0) {
                if (fd1>=0) close(fd1);
                if (fd2>=0) close(fd2);
                return 0;
        }
-       for (d = from->raid; dev < 0 && d < MaxDisks; d++) {
-               if (from->devid[d] > 0 &&
-                   from->devstate[d] == 0) {
-                       struct dev_policy *pol = devnum_policy(from->devid[d]);
-                       pol_add(&pol, pol_domain, from->spare_group, NULL);
-                       if (domain_test(domlist, pol, to->metadata->ss->name))
-                           dev = from->devid[d];
-                       dev_policy_free(pol);
-               }
-       }
-       if (dev < 0) {
-               close(fd1);
-               close(fd2);
-               return 0;
-       }
 
        devlist.next = NULL;
        devlist.used = 0;
        devlist.re_add = 0;
        devlist.writemostly = 0;
        devlist.devname = devname;
-       sprintf(devname, "%d:%d", major(dev), minor(dev));
+       sprintf(devname, "%d:%d", major(devid), minor(devid));
 
        devlist.disposition = 'r';
        if (Manage_subdevs(from->devname, fd2, &devlist, -1, 0) == 0) {
@@ -740,10 +757,24 @@ static int move_spare(struct state *from, struct state *to,
 static int check_donor(struct state *from, struct state *to,
                       struct domainlist *domlist)
 {
+       struct state *sub;
+
        if (from == to)
                return 0;
-       if (from->active < from->raid)
+       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;
        if (domlist == NULL)
@@ -751,16 +782,111 @@ static int check_donor(struct state *from, struct state *to,
        return 1;
 }
 
+static dev_t choose_spare(struct state *from, struct state *to,
+                       struct domainlist *domlist)
+{
+       int d;
+       dev_t dev = 0;
+       unsigned long long min_size
+               = min_spare_size_required(to);
+
+       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 (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))
+                           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)
+{
+       /* This is similar to choose_spare, but we cannot trust devstate,
+        * so we need to read the metadata instead
+        */
+
+       struct supertype *st = from->metadata;
+       int fd = open(from->devname, O_RDONLY);
+       int err;
+       struct mdinfo *disks, *d;
+       unsigned long long min_size
+               = min_spare_size_required(to);
+       dev_t dev = 0;
+
+       if (fd < 0)
+               return 0;
+       if (!st->ss->getinfo_super_disks)
+               return 0;
+       
+       err = st->ss->load_container(st, fd, NULL);
+       close(fd);
+       if (err)
+               return 0;
+
+       disks = st->ss->getinfo_super_disks(st);
+       st->ss->free_super(st);
+
+       if (!disks)
+               return 0;
+       
+       for (d = disks->devs ; d && !dev ; d = d->next) {
+               if (d->disk.state == 0) {
+                       struct dev_policy *pol;
+                       unsigned long long dev_size;
+                       dev = makedev(d->disk.major,d->disk.minor);
+                       
+                       if (min_size &&
+                           dev_size_from_id(dev,  &dev_size) &&
+                           dev_size < min_size) {
+                               dev = 0;
+                               continue;
+                       }
+                       pol = devnum_policy(dev);
+                       if (from->spare_group)
+                               pol_add(&pol, pol_domain,
+                                       from->spare_group, NULL);
+                       if (!domain_test(domlist, pol, to->metadata->ss->name))
+                               dev = 0;
+                       
+                       dev_policy_free(pol);
+               }
+       }
+       sysfs_free(disks);
+       return dev;
+}
+
+
 static void try_spare_migration(struct state *statelist, struct alert_info *info)
 {
-       struct state *from, *to;
+       struct state *from;
+       struct state *st;
 
        link_containers_with_subarrays(statelist);
-       for (to = statelist; to; to = to->next)
-               if (to->active < to->raid &&
-                   to->spare == 0) {
+       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;
+
+                       if (to->parent)
+                               /* member of a container */
+                               to = to->parent;
 
                        for (d = 0; d < MaxDisks; d++)
                                if (to->devid[d])
@@ -770,10 +896,19 @@ static void try_spare_migration(struct state *statelist, struct alert_info *info
                        if (to->spare_group)
                                domain_add(&domlist, to->spare_group);
 
-                       for (from=statelist ; from ; from=from->next)
-                               if (check_donor(from, to, domlist)
-                                   && move_spare(from, to, domlist, info))
+                       for (from=statelist ; from ; from=from->next) {
+                               dev_t devid;
+                               if (!check_donor(from, to, domlist))
+                                       continue;
+                               if (from->metadata->ss->external)
+                                       devid = container_choose_spare(
+                                               from, to, domlist);
+                               else
+                                       devid = choose_spare(from, to, domlist);
+                               if (devid > 0
+                                   && move_spare(from, to, devid, info))
                                                break;
+                       }
                        domain_free(domlist);
                }
 }