+ syslog(priority,
+ "%s event detected on md device %s",
+ event, dev);
+ }
+}
+
+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;
+ char *dev = st->devname;
+ int fd;
+ int i;
+
+ if (test)
+ alert("TestMessage", dev, NULL, ainfo);
+ fd = open(dev, O_RDONLY);
+ if (fd < 0) {
+ if (!st->err)
+ alert("DeviceDisappeared", dev, NULL, ainfo);
+ st->err=1;
+ return 0;
+ }
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+ if (ioctl(fd, GET_ARRAY_INFO, &array)<0) {
+ if (!st->err)
+ alert("DeviceDisappeared", dev, NULL, ainfo);
+ st->err=1;
+ close(fd);
+ return 0;
+ }
+ /* It's much easier to list what array levels can't
+ * have a device disappear than all of them that can
+ */
+ if (array.level == 0 || array.level == -1) {
+ if (!st->err)
+ alert("DeviceDisappeared", dev, "Wrong-Level", ainfo);
+ st->err = 1;
+ close(fd);
+ return 0;
+ }
+ if (st->devnum == INT_MAX) {
+ struct stat stb;
+ if (fstat(fd, &stb) == 0 &&
+ (S_IFMT&stb.st_mode)==S_IFBLK) {
+ if (major(stb.st_rdev) == MD_MAJOR)
+ st->devnum = minor(stb.st_rdev);
+ else
+ st->devnum = -1- (minor(stb.st_rdev)>>6);
+ }
+ }
+
+ for (mse2 = mdstat ; mse2 ; mse2=mse2->next)
+ if (mse2->devnum == st->devnum) {
+ mse2->devnum = INT_MAX; /* flag it as "used" */
+ mse = mse2;
+ }
+
+ if (!mse) {
+ /* duplicated array in statelist
+ * or re-created after reading mdstat*/
+ st->err = 1;
+ close(fd);
+ return 0;
+ }
+ /* this array is in /proc/mdstat */
+ if (array.utime == 0)
+ /* external arrays don't update utime, so
+ * just make sure it is always different. */
+ array.utime = st->utime + 1;;
+
+ if (st->utime == array.utime &&
+ st->failed == array.failed_disks &&
+ st->working == array.working_disks &&
+ st->spare == array.spare_disks &&
+ (mse == NULL || (
+ mse->percent == st->percent
+ ))) {
+ close(fd);
+ st->err = 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 */
+ )
+ alert("DegradedArray", dev, NULL, ainfo);
+
+ if (st->utime == 0 && /* new array */
+ st->expected_spares > 0 &&
+ array.spare_disks < st->expected_spares)
+ alert("SparesMissing", dev, NULL, ainfo);
+ if (st->percent == -1 &&
+ mse->percent >= 0)
+ alert("RebuildStarted", dev, NULL, ainfo);
+ if (st->percent >= 0 &&
+ mse->percent >= 0 &&
+ (mse->percent / increments) > (st->percent / increments)) {
+ char percentalert[15]; // "RebuildNN" (10 chars) or "RebuildStarted" (15 chars)
+
+ if((mse->percent / increments) == 0)
+ snprintf(percentalert, sizeof(percentalert), "RebuildStarted");
+ else
+ snprintf(percentalert, sizeof(percentalert), "Rebuild%02d", mse->percent);
+
+ alert(percentalert, dev, NULL, ainfo);
+ }
+
+ if (mse->percent == -1 &&
+ st->percent >= 0) {
+ /* Rebuild/sync/whatever just finished.
+ * If there is a number in /mismatch_cnt,
+ * we should report that.
+ */
+ struct mdinfo *sra =
+ sysfs_read(-1, st->devnum, GET_MISMATCH);
+ if (sra && sra->mismatch_cnt > 0) {
+ char cnt[40];
+ sprintf(cnt, " mismatches found: %d", sra->mismatch_cnt);
+ alert("RebuildFinished", dev, cnt, ainfo);
+ } else
+ alert("RebuildFinished", dev, NULL, ainfo);
+ if (sra)
+ free(sra);
+ }
+ st->percent = mse->percent;
+
+ for (i=0; i<MaxDisks && i <= array.raid_disks + array.nr_disks;
+ i++) {
+ mdu_disk_info_t disc;
+ disc.number = i;
+ if (ioctl(fd, GET_DISK_INFO, &disc) >= 0) {
+ info[i].state = disc.state;
+ info[i].major = disc.major;
+ info[i].minor = disc.minor;
+ } else
+ info[i].major = info[i].minor = 0;
+ }
+
+ if (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;
+ if (st->metadata == NULL &&
+ st->parent_dev == NoMdDev)
+ st->metadata = super_by_fd(fd, NULL);
+
+ close(fd);
+
+ for (i=0; i<MaxDisks; i++) {
+ mdu_disk_info_t disc = {0,0,0,0,0};
+ int newstate=0;
+ int change;
+ char *dv = NULL;
+ disc.number = i;
+ if (i > array.raid_disks + array.nr_disks) {
+ newstate = 0;
+ disc.major = disc.minor = 0;
+ } else if (info[i].major || info[i].minor) {
+ newstate = info[i].state;
+ dv = map_dev(info[i].major, info[i].minor, 1);
+ disc.state = newstate;
+ disc.major = info[i].major;
+ disc.minor = info[i].minor;
+ } else if (mse && mse->pattern && i < (int)strlen(mse->pattern)) {
+ switch(mse->pattern[i]) {
+ case 'U': newstate = 6 /* ACTIVE/SYNC */; break;
+ case '_': newstate = 0; break;
+ }
+ disc.major = disc.minor = 0;
+ }
+ if (dv == NULL && st->devid[i])
+ dv = map_dev(major(st->devid[i]),
+ minor(st->devid[i]), 1);
+ change = newstate ^ st->devstate[i];
+ if (st->utime && change && !st->err) {
+ if (i < array.raid_disks &&
+ (((newstate&change)&(1<<MD_DISK_FAULTY)) ||
+ ((st->devstate[i]&change)&(1<<MD_DISK_ACTIVE)) ||
+ ((st->devstate[i]&change)&(1<<MD_DISK_SYNC)))
+ )
+ alert("Fail", dev, dv, ainfo);
+ else if (i >= array.raid_disks &&
+ (disc.major || disc.minor) &&
+ st->devid[i] == makedev(disc.major, disc.minor) &&
+ ((newstate&change)&(1<<MD_DISK_FAULTY))
+ )
+ alert("FailSpare", dev, dv, ainfo);
+ else if (i < array.raid_disks &&
+ ! (newstate & (1<<MD_DISK_REMOVED)) &&
+ (((st->devstate[i]&change)&(1<<MD_DISK_FAULTY)) ||
+ ((newstate&change)&(1<<MD_DISK_ACTIVE)) ||
+ ((newstate&change)&(1<<MD_DISK_SYNC)))
+ )
+ alert("SpareActive", dev, dv, ainfo);
+ }
+ st->devstate[i] = newstate;
+ st->devid[i] = makedev(disc.major, disc.minor);
+ }
+ 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 (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;
+}
+
+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);
+ /* make sure we will see newly added spare before next
+ * time through loop
+ */
+ ping_manager(to->devname);
+ ping_manager(from->devname);
+ 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, 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 (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,
+ unsigned long long min_size)
+{
+ /* 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;
+ 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;
+ }
+ if (from == to)
+ /* Just checking if destination already has
+ * a spare, no need to check policy, we are
+ * done.
+ */
+ break;
+
+ 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)
+ 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)
+ /* member of a container */
+ to = to->parent;
+
+ min_size = min_spare_size_required(to);
+ 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);
+ 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);
+
+ 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, min_size);
+ else
+ devid = choose_spare(from, to, domlist,
+ min_size);
+ if (devid > 0
+ && move_spare(from, to, devid, 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;