]> git.ipfire.org Git - thirdparty/mdadm.git/blobdiff - Monitor.c
Monitor: array that has disappeared doesn't need spares
[thirdparty/mdadm.git] / Monitor.c
index 7d6e8bfd38b1bc5da137a01934208feee0790f28..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,64 +694,222 @@ 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,
+                     dev_t devid,
+                     struct alert_info *info)
+{
+       struct mddev_dev devlist;
+       char devname[20];
+
+       /* try to remove and add */
+       int fd1 = open(to->devname, O_RDONLY);
+       int fd2 = open(from->devname, O_RDONLY);
+
+       if (fd1 < 0 || fd2 < 0) {
+               if (fd1>=0) close(fd1);
+               if (fd2>=0) 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(devid), minor(devid));
+
+       devlist.disposition = 'r';
+       if (Manage_subdevs(from->devname, fd2, &devlist, -1, 0) == 0) {
+               devlist.disposition = 'a';
+               if (Manage_subdevs(to->devname, fd1, &devlist, -1, 0) == 0) {
+                       alert("MoveSpare", to->devname, from->devname, info);
+                       close(fd1);
+                       close(fd2);
+                       return 1;
+               }
+               else Manage_subdevs(from->devname, fd2, &devlist, -1, 0);
+       }
+       close(fd1);
+       close(fd2);
+       return 0;
+}
+
+static int check_donor(struct state *from, struct state *to,
+                      struct domainlist *domlist)
+{
+       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;
+       if (domlist == NULL)
+               return 0;
+       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;
        struct state *st;
 
        link_containers_with_subarrays(statelist);
-       for (st = statelist; st; st=st->next)
+       for (st = statelist; st; st = st->next)
                if (st->active < st->raid &&
-                   st->spare == 0 &&
-                   st->spare_group != NULL) {
-                       struct state *st2;
-                       for (st2=statelist ; st2 ; st2=st2->next)
-                               if (st2 != st &&
-                                   st2->spare > 0 &&
-                                   st2->active == st2->raid &&
-                                   st2->spare_group != NULL &&
-                                   strcmp(st->spare_group, st2->spare_group) == 0) {
-                                       /* try to remove and add */
-                                       int fd1 = open(st->devname, O_RDONLY);
-                                       int fd2 = open(st2->devname, O_RDONLY);
-                                       int dev = -1;
-                                       int d;
-                                       if (fd1 < 0 || fd2 < 0) {
-                                               if (fd1>=0) close(fd1);
-                                               if (fd2>=0) close(fd2);
-                                               continue;
-                                       }
-                                       for (d=st2->raid; d < MaxDisks; d++) {
-                                               if (st2->devid[d] > 0 &&
-                                                   st2->devstate[d] == 0) {
-                                                       dev = st2->devid[d];
-                                                       break;
-                                               }
-                                       }
-                                       if (dev > 0) {
-                                               struct mddev_dev devlist;
-                                               char devname[20];
-                                               devlist.next = NULL;
-                                               devlist.used = 0;
-                                               devlist.re_add = 0;
-                                               devlist.writemostly = 0;
-                                               devlist.devname = devname;
-                                               sprintf(devname, "%d:%d", major(dev), minor(dev));
-
-                                               devlist.disposition = 'r';
-                                               if (Manage_subdevs(st2->devname, fd2, &devlist, -1, 0) == 0) {
-                                                       devlist.disposition = 'a';
-                                                       if (Manage_subdevs(st->devname, fd1, &devlist, -1, 0) == 0) {
-                                                               alert("MoveSpare", st->devname, st2->devname, info);
-                                                               close(fd1);
-                                                               close(fd2);
-                                                               break;
-                                                       }
-                                                       else Manage_subdevs(st2->devname, fd2, &devlist, -1, 0);
-                                               }
-                                       }
-                                       close(fd1);
-                                       close(fd2);
-                               }
+                   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])
+                                       domainlist_add_dev(&domlist,
+                                                          to->devid[d],
+                                                          to->metadata->ss->name);
+                       if (to->spare_group)
+                               domain_add(&domlist, to->spare_group);
+
+                       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);
                }
 }