]> git.ipfire.org Git - thirdparty/mdadm.git/blobdiff - monitor.c
Create.c: fix uclibc build
[thirdparty/mdadm.git] / monitor.c
index 0a7d0f471e74d93e65f44f2a520d00b3e9ae47e7..be0bec785080351412f4a1fcd07e031e1d356b68 100644 (file)
--- a/monitor.c
+++ b/monitor.c
 #include "mdmon.h"
 #include <sys/syscall.h>
 #include <sys/select.h>
-#include <signal.h>
 
 static char *array_states[] = {
        "clear", "inactive", "suspended", "readonly", "read-auto",
-       "clean", "active", "write-pending", "active-idle", NULL };
+       "clean", "active", "write-pending", "active-idle", "broken", NULL };
 static char *sync_actions[] = {
        "idle", "reshape", "resync", "recover", "check", "repair", NULL
 };
 
+enum bb_action {
+       RECORD_BB = 1,
+       COMPARE_BB,
+};
+
 static int write_attr(char *attr, int fd)
 {
        return write(fd, attr, strlen(attr));
@@ -38,8 +42,17 @@ static int write_attr(char *attr, int fd)
 
 static void add_fd(fd_set *fds, int *maxfd, int fd)
 {
+       struct stat st;
        if (fd < 0)
                return;
+       if (fstat(fd, &st) == -1) {
+               dprintf("Invalid fd %d\n", fd);
+               return;
+       }
+       if (st.st_nlink == 0) {
+               dprintf("fd %d was deleted\n", fd);
+               return;
+       }
        if (fd > *maxfd)
                *maxfd = fd;
        FD_SET(fd, fds);
@@ -66,28 +79,30 @@ static int read_attr(char *buf, int len, int fd)
        return n;
 }
 
-static unsigned long long read_resync_start(int fd)
+static void read_resync_start(int fd, unsigned long long *v)
 {
-       char buf[30];
+       char buf[SYSFS_MAX_BUF_SIZE];
        int n;
 
-       n = read_attr(buf, 30, fd);
-       if (n <= 0)
-               return 0;
-       if (strncmp(buf, "none", 4) == 0)
-               return MaxSector;
+       n = read_attr(buf, sizeof(buf), fd);
+       if (n <= 0) {
+               dprintf("Failed to read resync_start (%d)\n", fd);
+               return;
+       }
+       if (str_is_none(buf) == true)
+               *v = MaxSector;
        else
-               return strtoull(buf, NULL, 10);
+               *v = strtoull(buf, NULL, 10);
 }
 
 static unsigned long long read_sync_completed(int fd)
 {
        unsigned long long val;
-       char buf[50];
+       char buf[SYSFS_MAX_BUF_SIZE];
        int n;
        char *ep;
 
-       n = read_attr(buf, 50, fd);
+       n = read_attr(buf, sizeof(buf), fd);
 
        if (n <= 0)
                return 0;
@@ -100,8 +115,8 @@ static unsigned long long read_sync_completed(int fd)
 
 static enum array_state read_state(int fd)
 {
-       char buf[20];
-       int n = read_attr(buf, 20, fd);
+       char buf[SYSFS_MAX_BUF_SIZE];
+       int n = read_attr(buf, sizeof(buf), fd);
 
        if (n <= 0)
                return bad_word;
@@ -110,8 +125,8 @@ static enum array_state read_state(int fd)
 
 static enum sync_action read_action( int fd)
 {
-       char buf[20];
-       int n = read_attr(buf, 20, fd);
+       char buf[SYSFS_MAX_BUF_SIZE];
+       int n = read_attr(buf, sizeof(buf), fd);
 
        if (n <= 0)
                return bad_action;
@@ -120,8 +135,8 @@ static enum sync_action read_action( int fd)
 
 int read_dev_state(int fd)
 {
-       char buf[60];
-       int n = read_attr(buf, 60, fd);
+       char buf[SYSFS_MAX_BUF_SIZE];
+       int n = read_attr(buf, sizeof(buf), fd);
        char *cp;
        int rv = 0;
 
@@ -147,6 +162,180 @@ int read_dev_state(int fd)
        return rv;
 }
 
+int process_ubb(struct active_array *a, struct mdinfo *mdi, const unsigned long
+               long sector, const int length, const char *buf,
+               const int buf_len)
+{
+       struct superswitch *ss = a->container->ss;
+
+       /*
+        * record bad block in metadata first, then acknowledge it to the driver
+        * via sysfs file
+        */
+       if ((ss->record_bad_block(a, mdi->disk.raid_disk, sector, length)) &&
+           (write(mdi->bb_fd, buf, buf_len) == buf_len))
+               return 1;
+
+       /*
+        * failed to store or acknowledge bad block, switch of bad block support
+        * to get it out of blocked state
+        */
+       sysfs_set_str(&a->info, mdi, "state", "-external_bbl");
+       return -1;
+}
+
+int compare_bb(struct active_array *a, struct mdinfo *mdi, const unsigned long
+              long sector, const unsigned int length, void *arg)
+{
+       struct superswitch *ss = a->container->ss;
+       struct md_bb *bb = (struct md_bb *) arg;
+       int record = 1;
+       int i;
+
+       for (i = 0; i < bb->count; i++) {
+               unsigned long long start = bb->entries[i].sector;
+               unsigned long long len = bb->entries[i].length;
+
+               /*
+                * bad block in metadata exactly matches bad block in kernel
+                * list, just remove it from a list
+                */
+               if ((start == sector) && (len == length)) {
+                       if (i < bb->count - 1)
+                               bb->entries[i] = bb->entries[bb->count - 1];
+                       bb->count -= 1;
+                       record = 0;
+                       break;
+               }
+               /*
+                * bad block in metadata spans bad block in kernel list,
+                * clear it and record new bad block
+                */
+               if ((sector >= start) && (sector + length <= start + len)) {
+                       ss->clear_bad_block(a, mdi->disk.raid_disk, start, len);
+                       break;
+               }
+       }
+
+       /* record all bad blocks not in metadata list */
+       if (record && (ss->record_bad_block(a, mdi->disk.raid_disk, sector,
+                                            length) <= 0)) {
+               sysfs_set_str(&a->info, mdi, "state", "-external_bbl");
+               return -1;
+       }
+
+       return 1;
+}
+
+static int read_bb_file(int fd, struct active_array *a, struct mdinfo *mdi,
+                       enum bb_action action, void *arg)
+{
+       char buf[30];
+       int n = 0;
+       int ret = 0;
+       int read_again = 0;
+       int off = 0;
+       int pos = 0;
+       int preserve_pos = (action == RECORD_BB ? 0 : 1);
+
+       if (lseek(fd, 0, SEEK_SET) == (off_t) -1)
+               return -1;
+
+       do {
+               read_again = 0;
+               n = read(fd, buf + pos, sizeof(buf) - 1 - pos);
+               if (n < 0)
+                       return -1;
+               n += pos;
+
+               buf[n] = '\0';
+               off = 0;
+
+               while (off < n) {
+                       unsigned long long sector;
+                       int length;
+                       char newline;
+                       int consumed;
+                       int matched;
+                       int rc;
+
+                       /* kernel sysfs file format: "sector length\n" */
+                       matched = sscanf(buf + off, "%llu %d%c%n", &sector,
+                                        &length, &newline, &consumed);
+                       if ((matched != 3) && (off > 0)) {
+                               /* truncated entry, read again */
+                               if (preserve_pos) {
+                                       pos = sizeof(buf) - off - 1;
+                                       memmove(buf, buf + off, pos);
+                               } else {
+                                       if (lseek(fd, 0, SEEK_SET) ==
+                                           (off_t) -1)
+                                               return -1;
+                               }
+                               read_again = 1;
+                               break;
+                       }
+                       if (matched != 3)
+                               return -1;
+                       if (newline != '\n')
+                               return -1;
+                       if (length <= 0)
+                               return -1;
+
+                       if (action == RECORD_BB)
+                               rc = process_ubb(a, mdi, sector, length,
+                                                 buf + off, consumed);
+                       else if (action == COMPARE_BB)
+                               rc = compare_bb(a, mdi, sector, length, arg);
+                       else
+                               rc = -1;
+
+                       if (rc < 0)
+                               return rc;
+                       ret += rc;
+                       off += consumed;
+               }
+       } while (read_again);
+
+       return ret;
+}
+
+static int process_dev_ubb(struct active_array *a, struct mdinfo *mdi)
+{
+       return read_bb_file(mdi->ubb_fd, a, mdi, RECORD_BB, NULL);
+}
+
+static int check_for_cleared_bb(struct active_array *a, struct mdinfo *mdi)
+{
+       struct superswitch *ss = a->container->ss;
+       struct md_bb *bb;
+       int i;
+
+       if (!ss->get_bad_blocks)
+               return -1;
+
+       /*
+        * Get a list of bad blocks for an array, then read list of
+        * acknowledged bad blocks from kernel and compare it against metadata
+        * list, clear all bad blocks remaining in metadata list
+        */
+       bb = ss->get_bad_blocks(a, mdi->disk.raid_disk);
+       if (!bb)
+               return -1;
+
+       if (read_bb_file(mdi->bb_fd, a, mdi, COMPARE_BB, bb) < 0)
+               return -1;
+
+       for (i = 0; i < bb->count; i++) {
+               unsigned long long sector = bb->entries[i].sector;
+               int length = bb->entries[i].length;
+
+               ss->clear_bad_block(a, mdi->disk.raid_disk, sector, length);
+       }
+
+       return 0;
+}
+
 static void signal_manager(void)
 {
        /* tgkill(getpid(), mon_tid, SIGUSR1); */
@@ -211,31 +400,67 @@ static void signal_manager(void)
  *
  */
 
-static int read_and_act(struct active_array *a)
+#define ARRAY_DIRTY 1
+#define ARRAY_BUSY 2
+static int read_and_act(struct active_array *a, fd_set *fds)
 {
        unsigned long long sync_completed;
        int check_degraded = 0;
+       int check_reshape = 0;
        int deactivate = 0;
        struct mdinfo *mdi;
-       int dirty = 0;
+       int ret = 0;
+       int count = 0;
+       struct timeval tv;
+       bool write_checkpoint = false;
 
        a->next_state = bad_word;
        a->next_action = bad_action;
 
        a->curr_state = read_state(a->info.state_fd);
        a->curr_action = read_action(a->action_fd);
-       a->info.resync_start = read_resync_start(a->resync_start_fd);
+       if (a->curr_state != clear)
+               /*
+                * In "clear" state, resync_start may wrongly be set to "0"
+                * when the kernel called md_clean but didn't remove the
+                * sysfs attributes yet
+                */
+               read_resync_start(a->resync_start_fd, &a->info.resync_start);
        sync_completed = read_sync_completed(a->sync_completed_fd);
        for (mdi = a->info.devs; mdi ; mdi = mdi->next) {
                mdi->next_state = 0;
                mdi->curr_state = 0;
                if (mdi->state_fd >= 0) {
-                       mdi->recovery_start = read_resync_start(mdi->recovery_fd);
+                       read_resync_start(mdi->recovery_fd,
+                                         &mdi->recovery_start);
                        mdi->curr_state = read_dev_state(mdi->state_fd);
                }
+               /*
+                * If array is blocked and metadata handler is able to handle
+                * BB, check if you can acknowledge them to md driver. If
+                * successful, clear faulty state and unblock the array.
+                */
+               if ((mdi->curr_state & DS_BLOCKED) &&
+                   a->container->ss->record_bad_block &&
+                   (process_dev_ubb(a, mdi) > 0)) {
+                       mdi->next_state |= DS_UNBLOCK;
+               }
+               if (FD_ISSET(mdi->bb_fd, fds))
+                       check_for_cleared_bb(a, mdi);
        }
 
-       if (a->curr_state <= inactive &&
+       gettimeofday(&tv, NULL);
+       dprintf("(%d): %ld.%06ld state:%s prev:%s action:%s prev: %s start:%llu\n",
+               a->info.container_member,
+               tv.tv_sec, tv.tv_usec,
+               array_states[a->curr_state],
+               array_states[a->prev_state],
+               sync_actions[a->curr_action],
+               sync_actions[a->prev_action],
+               a->info.resync_start
+               );
+
+       if ((a->curr_state == bad_word || a->curr_state <= inactive) &&
            a->prev_state > inactive) {
                /* array has been stopped */
                a->container->ss->set_array_state(a, 1);
@@ -245,22 +470,21 @@ static int read_and_act(struct active_array *a)
        if (a->curr_state == write_pending) {
                a->container->ss->set_array_state(a, 0);
                a->next_state = active;
-               dirty = 1;
+               ret |= ARRAY_DIRTY;
        }
        if (a->curr_state == active_idle) {
                /* Set array to 'clean' FIRST, then mark clean
                 * in the metadata
                 */
                a->next_state = clean;
-               dirty = 1;
+               ret |= ARRAY_DIRTY;
        }
-       if (a->curr_state == clean) {
+       if ((a->curr_state == clean) || (a->curr_state == broken)) {
                a->container->ss->set_array_state(a, 1);
        }
        if (a->curr_state == active ||
-           a->curr_state == suspended ||
-           a->curr_state == bad_word)
-               dirty = 1;
+           a->curr_state == suspended)
+               ret |= ARRAY_DIRTY;
        if (a->curr_state == readonly) {
                /* Well, I'm ready to handle things.  If readonly
                 * wasn't requested, transition to read-auto.
@@ -275,7 +499,7 @@ static int read_and_act(struct active_array *a)
                                a->next_state = read_auto; /* array is clean */
                        else {
                                a->next_state = active; /* Now active for recovery etc */
-                               dirty = 1;
+                               ret |= ARRAY_DIRTY;
                        }
                }
        }
@@ -303,9 +527,21 @@ static int read_and_act(struct active_array *a)
                                                   mdi->curr_state);
                        if (! (mdi->curr_state & DS_INSYNC))
                                check_degraded = 1;
+                       count++;
                }
+               if (count != a->info.array.raid_disks)
+                       check_degraded = 1;
        }
 
+       if (!deactivate &&
+           a->curr_action == reshape &&
+           a->prev_action != reshape)
+               /* reshape was requested by mdadm.  Need to see if
+                * new devices have been added.  Manager does that
+                * when it sees check_reshape
+                */
+               check_reshape = 1;
+
        /* Check for failures and if found:
         * 1/ Record the failure in the metadata and unblock the device.
         *    FIXME update the kernel to stop notifying on failed drives when
@@ -318,7 +554,8 @@ static int read_and_act(struct active_array *a)
                        a->container->ss->set_disk(a, mdi->disk.raid_disk,
                                                   mdi->curr_state);
                        check_degraded = 1;
-                       mdi->next_state |= DS_UNBLOCK;
+                       if (mdi->curr_state & DS_BLOCKED)
+                               mdi->next_state |= DS_UNBLOCK;
                        if (a->curr_state == read_auto) {
                                a->container->ss->set_array_state(a, 0);
                                a->next_state = active;
@@ -328,62 +565,86 @@ static int read_and_act(struct active_array *a)
                }
        }
 
-       /* Check for recovery checkpoint notifications.  We need to be a
-        * minimum distance away from the last checkpoint to prevent
-        * over checkpointing.  Note reshape checkpointing is not
-        * handled here.
-        */
-       if (sync_completed > a->last_checkpoint &&
-           sync_completed - a->last_checkpoint > a->info.component_size >> 4 &&
-           a->curr_action > reshape) {
-               /* A (non-reshape) sync_action has reached a checkpoint.
-                * Record the updated position in the metadata
+       /* Update reshape checkpoint, depending if it finished or progressed */
+       if (a->curr_action == idle && a->prev_action == reshape) {
+               char buf[SYSFS_MAX_BUF_SIZE];
+
+               if (sync_completed != 0)
+                       a->last_checkpoint = sync_completed;
+
+               /*
+                * If reshape really finished, set checkpoint to the end to finalize it.
+                * Do not set checkpoint if reshape is broken.
+                * Reshape will restart from last checkpoint.
                 */
+               if (sysfs_get_str(&a->info, NULL, "reshape_position", buf, sizeof(buf)) >= 0)
+                       if (str_is_none(buf) == true)
+                               a->last_checkpoint = a->info.component_size;
+
+               write_checkpoint = true;
+       }
+
+       if (a->curr_action >= reshape && sync_completed > a->last_checkpoint) {
+               /* Update checkpoint if neither reshape nor idle action */
                a->last_checkpoint = sync_completed;
+
+               write_checkpoint = true;
+       }
+
+       /* Save checkpoint */
+       if (write_checkpoint) {
                a->container->ss->set_array_state(a, a->curr_state <= clean);
-       } else if (sync_completed > a->last_checkpoint)
-               a->last_checkpoint = sync_completed;
+
+               if (a->curr_action <= reshape)
+                       a->last_checkpoint = sync_completed;
+       }
+
+       if (sync_completed >= a->info.component_size)
+               a->last_checkpoint = 0;
 
        a->container->ss->sync_metadata(a->container);
-       dprintf("%s(%d): state:%s action:%s next(", __func__, a->info.container_member,
+       dprintf("(%d): state:%s action:%s next(", a->info.container_member,
                array_states[a->curr_state], sync_actions[a->curr_action]);
 
        /* Effect state changes in the array */
        if (a->next_state != bad_word) {
-               dprintf(" state:%s", array_states[a->next_state]);
+               dprintf_cont(" state:%s", array_states[a->next_state]);
                write_attr(array_states[a->next_state], a->info.state_fd);
        }
        if (a->next_action != bad_action) {
                write_attr(sync_actions[a->next_action], a->action_fd);
-               dprintf(" action:%s", sync_actions[a->next_action]);
+               dprintf_cont(" action:%s", sync_actions[a->next_action]);
        }
        for (mdi = a->info.devs; mdi ; mdi = mdi->next) {
                if (mdi->next_state & DS_UNBLOCK) {
-                       dprintf(" %d:-blocked", mdi->disk.raid_disk);
+                       dprintf_cont(" %d:-blocked", mdi->disk.raid_disk);
                        write_attr("-blocked", mdi->state_fd);
                }
 
                if ((mdi->next_state & DS_REMOVE) && mdi->state_fd >= 0) {
                        int remove_result;
 
-                       /* the kernel may not be able to immediately remove the
-                        * disk, we can simply wait until the next event to try
-                        * again.
+                       /* The kernel may not be able to immediately remove the
+                        * disk.  In that case we wait a little while and
+                        * try again.
                         */
                        remove_result = write_attr("remove", mdi->state_fd);
                        if (remove_result > 0) {
-                               dprintf(" %d:removed", mdi->disk.raid_disk);
+                               dprintf_cont(" %d:removed", mdi->disk.raid_disk);
                                close(mdi->state_fd);
                                close(mdi->recovery_fd);
+                               close(mdi->bb_fd);
+                               close(mdi->ubb_fd);
                                mdi->state_fd = -1;
-                       }
+                       } else
+                               ret |= ARRAY_BUSY;
                }
                if (mdi->next_state & DS_INSYNC) {
                        write_attr("+in_sync", mdi->state_fd);
-                       dprintf(" %d:+in_sync", mdi->disk.raid_disk);
+                       dprintf_cont(" %d:+in_sync", mdi->disk.raid_disk);
                }
        }
-       dprintf(" )\n");
+       dprintf_cont(" )\n");
 
        /* move curr_ to prev_ */
        a->prev_state = a->curr_state;
@@ -395,16 +656,19 @@ static int read_and_act(struct active_array *a)
                mdi->next_state = 0;
        }
 
-       if (check_degraded) {
+       if (check_degraded || check_reshape) {
                /* manager will do the actual check */
-               a->check_degraded = 1;
+               if (check_degraded)
+                       a->check_degraded = 1;
+               if (check_reshape)
+                       a->check_reshape = 1;
                signal_manager();
        }
 
        if (deactivate)
                a->container = NULL;
 
-       return dirty;
+       return ret;
 }
 
 static struct mdinfo *
@@ -425,7 +689,7 @@ static void reconcile_failed(struct active_array *aa, struct mdinfo *failed)
        struct mdinfo *victim;
 
        for (a = aa; a; a = a->next) {
-               if (!a->container)
+               if (!a->container || a->to_remove)
                        continue;
                victim = find_device(a, failed->disk.major, failed->disk.minor);
                if (!victim)
@@ -485,7 +749,7 @@ static int wait_and_act(struct supertype *container, int nowait)
                /* once an array has been deactivated we want to
                 * ask the manager to discard it.
                 */
-               if (!a->container) {
+               if (!a->container || a->to_remove) {
                        if (discard_this) {
                                ap = &(*ap)->next;
                                continue;
@@ -500,8 +764,11 @@ static int wait_and_act(struct supertype *container, int nowait)
                add_fd(&rfds, &maxfd, a->info.state_fd);
                add_fd(&rfds, &maxfd, a->action_fd);
                add_fd(&rfds, &maxfd, a->sync_completed_fd);
-               for (mdi = a->info.devs ; mdi ; mdi = mdi->next)
+               for (mdi = a->info.devs ; mdi ; mdi = mdi->next) {
                        add_fd(&rfds, &maxfd, mdi->state_fd);
+                       add_fd(&rfds, &maxfd, mdi->bb_fd);
+                       add_fd(&rfds, &maxfd, mdi->ubb_fd);
+               }
 
                ap = &(*ap)->next;
        }
@@ -513,7 +780,11 @@ static int wait_and_act(struct supertype *container, int nowait)
                 * problem as there are no active arrays, there is
                 * nothing that we need to be ready to do.
                 */
-               int fd = open_dev_excl(container->devnum);
+               int fd;
+               if (sigterm)
+                       fd = open_dev_excl(container->devnm);
+               else
+                       fd = open_dev_flags(container->devnm, O_RDONLY|O_EXCL);
                if (fd >= 0 || errno != EBUSY) {
                        /* OK, we are safe to leave */
                        if (sigterm && !dirty_arrays)
@@ -524,26 +795,43 @@ static int wait_and_act(struct supertype *container, int nowait)
                                /* On SIGTERM, someone (the take-over mdmon) will
                                 * clean up
                                 */
-                               remove_pidfile(container->devname);
+                               remove_pidfile(container->devnm);
                        exit_now = 1;
                        signal_manager();
+                       close(fd);
                        exit(0);
                }
        }
 
        if (!nowait) {
                sigset_t set;
+               struct timespec ts;
+               ts.tv_sec = 24*3600;
+               ts.tv_nsec = 0;
+               if (*aap == NULL || container->retry_soon) {
+                       /* just waiting to get O_EXCL access */
+                       ts.tv_sec = 0;
+                       ts.tv_nsec = 20000000ULL;
+               }
                sigprocmask(SIG_UNBLOCK, NULL, &set);
                sigdelset(&set, SIGUSR1);
                monitor_loop_cnt |= 1;
-               rv = pselect(maxfd+1, NULL, NULL, &rfds, NULL, &set);
+               rv = pselect(maxfd+1, NULL, NULL, &rfds, &ts, &set);
                monitor_loop_cnt += 1;
-               if (rv == -1 && errno == EINTR)
-                       rv = 0;
+               if (rv == -1) {
+                       if (errno == EINTR) {
+                               rv = 0;
+                               FD_ZERO(&rfds);
+                               dprintf("monitor: caught signal\n");
+                       } else
+                               dprintf("monitor: error %d in pselect\n",
+                                       errno);
+               }
                #ifdef DEBUG
-               dprint_wake_reasons(&rfds);
+               else
+                       dprint_wake_reasons(&rfds);
                #endif
-
+               container->retry_soon = 0;
        }
 
        if (update_queue) {
@@ -561,7 +849,6 @@ static int wait_and_act(struct supertype *container, int nowait)
        rv = 0;
        dirty_arrays = 0;
        for (a = *aap; a ; a = a->next) {
-               int is_dirty;
 
                if (a->replaces && !discard_this) {
                        struct active_array **ap;
@@ -575,22 +862,24 @@ static int wait_and_act(struct supertype *container, int nowait)
                        /* FIXME check if device->state_fd need to be cleared?*/
                        signal_manager();
                }
-               if (a->container) {
-                       is_dirty = read_and_act(a);
+               if (a->container && !a->to_remove) {
+                       int ret = read_and_act(a, &rfds);
                        rv |= 1;
-                       dirty_arrays += is_dirty;
+                       dirty_arrays += !!(ret & ARRAY_DIRTY);
                        /* when terminating stop manipulating the array after it
                         * is clean, but make sure read_and_act() is given a
                         * chance to handle 'active_idle'
                         */
-                       if (sigterm && !is_dirty)
+                       if (sigterm && !(ret & ARRAY_DIRTY))
                                a->container = NULL; /* stop touching this array */
+                       if (ret & ARRAY_BUSY)
+                               container->retry_soon = 1;
                }
        }
 
        /* propagate failures across container members */
        for (a = *aap; a ; a = a->next) {
-               if (!a->container)
+               if (!a->container || a->to_remove)
                        continue;
                for (mdi = a->info.devs ; mdi ; mdi = mdi->next)
                        if (mdi->curr_state & DS_FAULTY)