]> git.ipfire.org Git - thirdparty/mdadm.git/blobdiff - managemon.c
Create: support --readonly flag.
[thirdparty/mdadm.git] / managemon.c
index b211d8f446b17d6e2f5111c206bd0b8f51073dde..ef351b3970b28b42f2e0c919a16030354a317f15 100644 (file)
@@ -1,3 +1,22 @@
+/*
+ * mdmon - monitor external metadata arrays
+ *
+ * Copyright (C) 2007-2009 Neil Brown <neilb@suse.de>
+ * Copyright (C) 2007-2009 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ */
 
 /*
  * The management thread for monitoring active md arrays.
@@ -93,12 +112,21 @@ static void close_aa(struct active_array *aa)
 {
        struct mdinfo *d;
 
-       for (d = aa->info.devs; d; d = d->next)
+       for (d = aa->info.devs; d; d = d->next) {
+               close(d->recovery_fd);
                close(d->state_fd);
+       }
 
-       close(aa->action_fd);
-       close(aa->info.state_fd);
-       close(aa->resync_start_fd);
+       if (aa->action_fd >= 0)
+               close(aa->action_fd);
+       if (aa->info.state_fd >= 0)
+               close(aa->info.state_fd);
+       if (aa->resync_start_fd >= 0)
+               close(aa->resync_start_fd);
+       if (aa->metadata_fd >= 0)
+               close(aa->metadata_fd);
+       if (aa->sync_completed_fd >= 0)
+               close(aa->sync_completed_fd);
 }
 
 static void free_aa(struct active_array *aa)
@@ -119,7 +147,7 @@ static void free_aa(struct active_array *aa)
 
 static struct active_array *duplicate_aa(struct active_array *aa)
 {
-       struct active_array *newa = malloc(sizeof(*newa));
+       struct active_array *newa = xmalloc(sizeof(*newa));
        struct mdinfo **dp1, **dp2;
 
        *newa = *aa;
@@ -134,7 +162,7 @@ static struct active_array *duplicate_aa(struct active_array *aa)
                if ((*dp1)->state_fd < 0)
                        continue;
 
-               d = malloc(sizeof(*d));
+               d = xmalloc(sizeof(*d));
                *d = **dp1;
                *dp2 = d;
                dp2 = & d->next;
@@ -190,16 +218,28 @@ struct metadata_update *update_queue = NULL;
 struct metadata_update *update_queue_handled = NULL;
 struct metadata_update *update_queue_pending = NULL;
 
-void check_update_queue(struct supertype *container)
+static void free_updates(struct metadata_update **update)
 {
-       while (update_queue_handled) {
-               struct metadata_update *this = update_queue_handled;
-               update_queue_handled = this->next;
+       while (*update) {
+               struct metadata_update *this = *update;
+               void **space_list = this->space_list;
+
+               *update = this->next;
                free(this->buf);
-               if (this->space)
-                       free(this->space);
+               free(this->space);
+               while (space_list) {
+                       void *space = space_list;
+                       space_list = *space_list;
+                       free(space);
+               }
                free(this);
        }
+}
+
+void check_update_queue(struct supertype *container)
+{
+       free_updates(&update_queue_handled);
+
        if (update_queue == NULL &&
            update_queue_pending) {
                update_queue = update_queue_pending;
@@ -218,26 +258,173 @@ static void queue_metadata_update(struct metadata_update *mu)
        *qp = mu;
 }
 
+static void add_disk_to_container(struct supertype *st, struct mdinfo *sd)
+{
+       int dfd;
+       char nm[20];
+       struct supertype *st2;
+       struct metadata_update *update = NULL;
+       struct mdinfo info;
+       mdu_disk_info_t dk = {
+               .number = -1,
+               .major = sd->disk.major,
+               .minor = sd->disk.minor,
+               .raid_disk = -1,
+               .state = 0,
+       };
+
+       dprintf("%s: add %d:%d to container\n",
+               __func__, sd->disk.major, sd->disk.minor);
+
+       sd->next = st->devs;
+       st->devs = sd;
+
+       sprintf(nm, "%d:%d", sd->disk.major, sd->disk.minor);
+       dfd = dev_open(nm, O_RDWR);
+       if (dfd < 0)
+               return;
+
+       /* Check the metadata and see if it is already part of this
+        * array
+        */
+       st2 = dup_super(st);
+       if (st2->ss->load_super(st2, dfd, NULL) == 0) {
+               st2->ss->getinfo_super(st, &info, NULL);
+               if (st->ss->compare_super(st, st2) == 0 &&
+                   info.disk.raid_disk >= 0) {
+                       /* Looks like a good member of array.
+                        * Just accept it.
+                        * mdadm will incorporate any parts into
+                        * active arrays.
+                        */
+                       st2->ss->free_super(st2);
+                       return;
+               }
+       }
+       st2->ss->free_super(st2);
+
+       st->update_tail = &update;
+       st->ss->add_to_super(st, &dk, dfd, NULL);
+       st->ss->write_init_super(st);
+       queue_metadata_update(update);
+       st->update_tail = NULL;
+}
+
+/*
+ * Create and queue update structure about the removed disks.
+ * The update is prepared by super type handler and passed to the monitor
+ * thread.
+ */
+static void remove_disk_from_container(struct supertype *st, struct mdinfo *sd)
+{
+       struct metadata_update *update = NULL;
+       mdu_disk_info_t dk = {
+               .number = -1,
+               .major = sd->disk.major,
+               .minor = sd->disk.minor,
+               .raid_disk = -1,
+               .state = 0,
+       };
+       dprintf("%s: remove %d:%d from container\n",
+               __func__, sd->disk.major, sd->disk.minor);
+
+       st->update_tail = &update;
+       st->ss->remove_from_super(st, &dk);
+       /* FIXME this write_init_super shouldn't be here.
+        * We have it after add_to_super to write to new device,
+        * but with 'remove' we don't ant to write to that device!
+        */
+       st->ss->write_init_super(st);
+       queue_metadata_update(update);
+       st->update_tail = NULL;
+}
+
 static void manage_container(struct mdstat_ent *mdstat,
                             struct supertype *container)
 {
-       /* The only thing of interest here is if a new device
-        * has been added to the container.  We add it to the
-        * array ignoring any metadata on it.
+       /* Of interest here are:
+        * - if a new device has been added to the container, we 
+        *   add it to the array ignoring any metadata on it.
+        * - if a device has been removed from the container, we
+        *   remove it from the device list and update the metadata.
         * FIXME should we look for compatible metadata and take hints
         * about spare assignment.... probably not.
         */
        if (mdstat->devcnt != container->devcnt) {
+               struct mdinfo **cdp, *cd, *di, *mdi;
+               int found;
+
                /* read /sys/block/NAME/md/dev-??/block/dev to find out
                 * what is there, and compare with container->info.devs
                 * To see what is removed and what is added.
                 * These need to be remove from, or added to, the array
                 */
-               // FIXME
+               mdi = sysfs_read(-1, mdstat->devnum, GET_DEVS);
+               if (!mdi) {
+                       /* invalidate the current count so we can try again */
+                       container->devcnt = -1;
+                       return;
+               }
+
+               /* check for removals */
+               for (cdp = &container->devs; *cdp; ) {
+                       found = 0;
+                       for (di = mdi->devs; di; di = di->next)
+                               if (di->disk.major == (*cdp)->disk.major &&
+                                   di->disk.minor == (*cdp)->disk.minor) {
+                                       found = 1;
+                                       break;
+                               }
+                       if (!found) {
+                               cd = *cdp;
+                               *cdp = (*cdp)->next;
+                               remove_disk_from_container(container, cd);
+                               free(cd);
+                       } else
+                               cdp = &(*cdp)->next;
+               }
+
+               /* check for additions */
+               for (di = mdi->devs; di; di = di->next) {
+                       for (cd = container->devs; cd; cd = cd->next)
+                               if (di->disk.major == cd->disk.major &&
+                                   di->disk.minor == cd->disk.minor)
+                                       break;
+                       if (!cd) {
+                               struct mdinfo *newd = xmalloc(sizeof(*newd));
+
+                               *newd = *di;
+                               add_disk_to_container(container, newd);
+                       }
+               }
+               sysfs_free(mdi);
                container->devcnt = mdstat->devcnt;
        }
 }
 
+static int disk_init_and_add(struct mdinfo *disk, struct mdinfo *clone,
+                            struct active_array *aa)
+{
+       if (!disk || !clone)
+               return -1;
+
+       *disk = *clone;
+       disk->recovery_fd = sysfs_open(aa->devnum, disk->sys_name, "recovery_start");
+       if (disk->recovery_fd < 0)
+               return -1;
+       disk->state_fd = sysfs_open(aa->devnum, disk->sys_name, "state");
+       if (disk->state_fd < 0) {
+               close(disk->recovery_fd);
+               return -1;
+       }
+       disk->prev_state = read_dev_state(disk->state_fd);
+       disk->curr_state = disk->prev_state;
+       disk->next = aa->info.devs;
+       aa->info.devs = disk;
+
+       return 0;
+}
+
 static void manage_member(struct mdstat_ent *mdstat,
                          struct active_array *a)
 {
@@ -245,55 +432,157 @@ static void manage_member(struct mdstat_ent *mdstat,
         * We do not need to look for device state changes here, that
         * is dealt with by the monitor.
         *
-        * We just look for changes which suggest that a reshape is
-        * being requested.
-        * Unfortunately decreases in raid_disks don't show up in
-        * mdstat until the reshape completes FIXME.
+        * If a reshape is being requested, monitor will have noticed
+        * that sync_action changed and will have set check_reshape.
+        * We just need to see if new devices have appeared.  All metadata
+        * updates will already have been processed.
         *
-        * Actually, we also want to handle degraded arrays here by
+        * We also want to handle degraded arrays here by
         * trying to find and assign a spare.
         * We do that whenever the monitor tells us too.
         */
+       char buf[64];
+       int frozen;
+       struct supertype *container = a->container;
+
+       if (container == NULL)
+               /* Raced with something */
+               return;
+
        // FIXME
        a->info.array.raid_disks = mdstat->raid_disks;
-       a->info.array.chunk_size = mdstat->chunk_size;
        // MORE
 
-       if (a->check_degraded) {
+       /* honor 'frozen' */
+       if (sysfs_get_str(&a->info, NULL, "metadata_version", buf, sizeof(buf)) > 0)
+               frozen = buf[9] == '-';
+       else
+               frozen = 1; /* can't read metadata_version assume the worst */
+
+       /* If sync_action is not 'idle' then don't try recovery now */
+       if (!frozen
+           && sysfs_get_str(&a->info, NULL, "sync_action", buf, sizeof(buf)) > 0
+           && strncmp(buf, "idle", 4) != 0)
+               frozen = 1;
+
+       if (mdstat->level) {
+               int level = map_name(pers, mdstat->level);
+               if (level == 0 || level == LEVEL_LINEAR) {
+                       a->to_remove = 1;
+                       wakeup_monitor();
+                       return;
+               }
+               else if (a->info.array.level != level && level > 0) {
+                       struct active_array *newa = duplicate_aa(a);
+                       if (newa) {
+                               newa->info.array.level = level;
+                               replace_array(container, a, newa);
+                               a = newa;
+                       }
+               }
+       }
+
+       /* we are after monitor kick,
+        * so container field can be cleared - check it again
+        */
+       if (a->container == NULL)
+               return;
+
+       /* We don't check the array while any update is pending, as it
+        * might container a change (such as a spare assignment) which
+        * could affect our decisions.
+        */
+       if (a->check_degraded && !frozen &&
+           update_queue == NULL && update_queue_pending == NULL) {
                struct metadata_update *updates = NULL;
-               struct mdinfo *newdev;
+               struct mdinfo *newdev = NULL;
                struct active_array *newa;
+               struct mdinfo *d;
 
                a->check_degraded = 0;
 
                /* The array may not be degraded, this is just a good time
                 * to check.
                 */
-               newdev = a->container->ss->activate_spare(a, &updates);
-               if (newdev) {
-                       struct mdinfo *d;
-                       /* Cool, we can add a device or several. */
-                       newa = duplicate_aa(a);
-                       /* suspend recovery - maybe not needed */
-
-                       /* Add device to array and set offset/size/slot.
-                        * and open files for each newdev */
-                       for (d = newdev; d ; d = d->next) {
-                               struct mdinfo *newd;
-                               if (sysfs_add_disk(&newa->info, d) < 0)
-                                       continue;
-                               newd = newa->info.devs;
-                               newd->state_fd = sysfs_open(a->devnum,
-                                                           newd->sys_name,
-                                                           "state");
-                               newd->prev_state
-                                       = read_dev_state(newd->state_fd);
-                               newd->curr_state = newd->prev_state;
+               newdev = container->ss->activate_spare(a, &updates);
+               if (!newdev)
+                       return;
+
+               newa = duplicate_aa(a);
+               if (!newa)
+                       goto out;
+               /* prevent the kernel from activating the disk(s) before we
+                * finish adding them
+                */
+               sysfs_set_str(&a->info, NULL, "sync_action", "frozen");
+
+               /* Add device to array and set offset/size/slot.
+                * and open files for each newdev */
+               for (d = newdev; d ; d = d->next) {
+                       struct mdinfo *newd;
+
+                       newd = xmalloc(sizeof(*newd));
+                       if (sysfs_add_disk(&newa->info, d, 0) < 0) {
+                               free(newd);
+                               continue;
                        }
-                       queue_metadata_update(updates);
-                       replace_array(a->container, a, newa);
-                       sysfs_set_str(&a->info, NULL, "sync_action", "recover");
+                       disk_init_and_add(newd, d, newa);
+               }
+               queue_metadata_update(updates);
+               updates = NULL;
+               replace_array(container, a, newa);
+               sysfs_set_str(&a->info, NULL, "sync_action", "recover");
+ out:
+               while (newdev) {
+                       d = newdev->next;
+                       free(newdev);
+                       newdev = d;
                }
+               free_updates(&updates);
+       }
+
+       if (a->check_reshape) {
+               /* mdadm might have added some devices to the array.
+                * We want to disk_init_and_add any such device to a
+                * duplicate_aa and replace a with that.
+                * mdstat doesn't have enough info so we sysfs_read
+                * and look for new stuff.
+                */
+               struct mdinfo *info, *d, *d2, *newd;
+               unsigned long long array_size;
+               struct active_array *newa = NULL;
+               a->check_reshape = 0;
+               info = sysfs_read(-1, mdstat->devnum,
+                                 GET_DEVS|GET_OFFSET|GET_SIZE|GET_STATE);
+               if (!info)
+                       goto out2;
+               for (d = info->devs; d; d = d->next) {
+                       if (d->disk.raid_disk < 0)
+                               continue;
+                       for (d2 = a->info.devs; d2; d2 = d2->next)
+                               if (d2->disk.raid_disk ==
+                                   d->disk.raid_disk)
+                                       break;
+                       if (d2)
+                               /* already have this one */
+                               continue;
+                       if (!newa) {
+                               newa = duplicate_aa(a);
+                               if (!newa)
+                                       break;
+                       }
+                       newd = xmalloc(sizeof(*newd));
+                       disk_init_and_add(newd, d, newa);
+               }
+               if (sysfs_get_ll(info, NULL, "array_size", &array_size) == 0
+                   && a->info.custom_array_size > array_size*2) {
+                       sysfs_set_num(info, NULL, "array_size",
+                                     a->info.custom_array_size/2);
+               }
+       out2:
+               sysfs_free(info);
+               if (newa)
+                       replace_array(container, a, newa);
        }
 }
 
@@ -333,25 +622,23 @@ static void manage_new(struct mdstat_ent *mdstat,
        char *inst;
        int i;
        int failed = 0;
+       char buf[40];
 
        /* check if array is ready to be monitored */
-       if (!mdstat->active)
+       if (!mdstat->active || !mdstat->level)
+               return;
+       if (strcmp(mdstat->level, "raid0") == 0 ||
+           strcmp(mdstat->level, "linear") == 0)
                return;
 
        mdi = sysfs_read(-1, mdstat->devnum,
                         GET_LEVEL|GET_CHUNK|GET_DISKS|GET_COMPONENT|
                         GET_DEGRADED|GET_DEVS|GET_OFFSET|GET_SIZE|GET_STATE);
 
-       new = malloc(sizeof(*new));
 
-       if (!new || !mdi) {
-               if (mdi)
-                       sysfs_free(mdi);
-               if (new)
-                       free(new);
+       if (!mdi)
                return;
-       }
-       memset(new, 0, sizeof(*new));
+       new = xcalloc(1, sizeof(*new));
 
        new->devnum = mdstat->devnum;
        strcpy(new->info.sys_name, devnum2devname(new->devnum));
@@ -361,48 +648,62 @@ static void manage_new(struct mdstat_ent *mdstat,
 
        new->container = container;
 
-       inst = &mdstat->metadata_version[10+strlen(container->devname)+1];
+       inst = to_subarray(mdstat, container->devname);
 
        new->info.array = mdi->array;
        new->info.component_size = mdi->component_size;
 
        for (i = 0; i < new->info.array.raid_disks; i++) {
-               struct mdinfo *newd = malloc(sizeof(*newd));
+               struct mdinfo *newd = xmalloc(sizeof(*newd));
 
                for (di = mdi->devs; di; di = di->next)
                        if (i == di->disk.raid_disk)
                                break;
 
-               if (di) {
-                       memcpy(newd, di, sizeof(*newd));
+               if (disk_init_and_add(newd, di, new) != 0) {
+                       if (newd)
+                               free(newd);
 
-                       newd->state_fd = sysfs_open(new->devnum,
-                                                   newd->sys_name,
-                                                   "state");
-
-                       newd->prev_state = read_dev_state(newd->state_fd);
-                       newd->curr_state = newd->prev_state;
-               } else if (failed + 1 > new->info.array.failed_disks) {
-                       /* we cannot properly monitor without all working disks */
-                       new->container = NULL;
-                       break;
-               } else {
                        failed++;
-                       free(newd);
-                       continue;
+                       if (failed > new->info.array.failed_disks) {
+                               /* we cannot properly monitor without all working disks */
+                               new->container = NULL;
+                               break;
+                       }
                }
-               sprintf(newd->sys_name, "rd%d", i);
-               newd->next = new->info.devs;
-               new->info.devs = newd;
        }
 
        new->action_fd = sysfs_open(new->devnum, NULL, "sync_action");
        new->info.state_fd = sysfs_open(new->devnum, NULL, "array_state");
        new->resync_start_fd = sysfs_open(new->devnum, NULL, "resync_start");
-       get_resync_start(new);
+       new->metadata_fd = sysfs_open(new->devnum, NULL, "metadata_version");
+       new->sync_completed_fd = sysfs_open(new->devnum, NULL, "sync_completed");
        dprintf("%s: inst: %d action: %d state: %d\n", __func__, atoi(inst),
                new->action_fd, new->info.state_fd);
 
+       /* reshape_position is set by mdadm in sysfs
+        * read this information for new arrays only (empty victim)
+        */
+       if ((victim == NULL) &&
+           (sysfs_get_str(mdi, NULL, "sync_action", buf, 40) > 0) &&
+           (strncmp(buf, "reshape", 7) == 0)) {
+               if (sysfs_get_ll(mdi, NULL, "reshape_position",
+                       &new->last_checkpoint) != 0)
+                       new->last_checkpoint = 0;
+               else {
+                       int data_disks = mdi->array.raid_disks;
+                       if (mdi->array.level == 4 || mdi->array.level == 5)
+                               data_disks--;
+                       if (mdi->array.level == 6)
+                               data_disks -= 2;
+
+                       new->last_checkpoint /= data_disks;
+               }
+               dprintf("mdmon: New monitored array is under reshape.\n"
+                       "       Last checkpoint is: %llu\n",
+                       new->last_checkpoint);
+       }
+
        sysfs_free(mdi);
 
        /* if everything checks out tell the metadata handler we want to
@@ -413,8 +714,13 @@ static void manage_new(struct mdstat_ent *mdstat,
                        mdstat->metadata_version);
                new->container = NULL;
                free_aa(new);
-       } else
+       } else {
                replace_array(container, victim, new);
+               if (failed) {
+                       new->check_degraded = 1;
+                       manage_member(mdstat, new);
+               }
+       }
 }
 
 void manage(struct mdstat_ent *mdstat, struct supertype *container)
@@ -430,18 +736,13 @@ void manage(struct mdstat_ent *mdstat, struct supertype *container)
                        manage_container(mdstat, container);
                        continue;
                }
-               if (mdstat->metadata_version == NULL ||
-                   strncmp(mdstat->metadata_version, "external:/", 10) != 0 ||
-                   strncmp(mdstat->metadata_version+10, container->devname,
-                           strlen(container->devname)) != 0 ||
-                   mdstat->metadata_version[10+strlen(container->devname)]
-                     != '/')
+               if (!is_container_member(mdstat, container->devname))
                        /* Not for this array */
                        continue;
                /* Looks like a member of this container */
                for (a = container->arrays; a; a = a->next) {
                        if (mdstat->devnum == a->devnum) {
-                               if (a->container)
+                               if (a->container && a->to_remove == 0)
                                        manage_member(mdstat, a);
                                break;
                        }
@@ -457,14 +758,15 @@ static void handle_message(struct supertype *container, struct metadata_update *
 
        struct metadata_update *mu;
 
-       if (msg->len == 0) {
-               int cnt;
-               
+       if (msg->len <= 0)
                while (update_queue_pending || update_queue) {
                        check_update_queue(container);
                        usleep(15*1000);
                }
 
+       if (msg->len == 0) { /* ping_monitor */
+               int cnt;
+               
                cnt = monitor_loop_cnt;
                if (cnt & 1)
                        cnt += 2; /* wait until next pselect */
@@ -474,12 +776,18 @@ static void handle_message(struct supertype *container, struct metadata_update *
 
                while (monitor_loop_cnt - cnt < 0)
                        usleep(10 * 1000);
-       } else {
-               mu = malloc(sizeof(*mu));
+       } else if (msg->len == -1) { /* ping_manager */
+               struct mdstat_ent *mdstat = mdstat_read(1, 0);
+
+               manage(mdstat, container);
+               free_mdstat(mdstat);
+       } else if (!sigterm) {
+               mu = xmalloc(sizeof(*mu));
                mu->len = msg->len;
                mu->buf = msg->buf;
                msg->buf = NULL;
                mu->space = NULL;
+               mu->space_list = NULL;
                mu->next = NULL;
                if (container->ss->prepare_update)
                        container->ss->prepare_update(container, mu);
@@ -509,7 +817,13 @@ void read_sock(struct supertype *container)
                /* read and validate the message */
                if (receive_message(fd, &msg, tmo) == 0) {
                        handle_message(container, &msg);
-                       if (ack(fd, tmo) < 0)
+                       if (msg.len == 0) {
+                               /* ping reply with version */
+                               msg.buf = Version;
+                               msg.len = strlen(Version) + 1;
+                               if (send_message(fd, &msg, tmo) < 0)
+                                       terminate = 1;
+                       } else if (ack(fd, tmo) < 0)
                                terminate = 1;
                } else
                        terminate = 1;
@@ -528,6 +842,7 @@ void do_manager(struct supertype *container)
 
        sigprocmask(SIG_UNBLOCK, NULL, &set);
        sigdelset(&set, SIGUSR1);
+       sigdelset(&set, SIGTERM);
 
        do {
 
@@ -553,6 +868,9 @@ void do_manager(struct supertype *container)
 
                manager_ready = 1;
 
+               if (sigterm)
+                       wakeup_monitor();
+
                if (update_queue == NULL)
                        mdstat_wait_fd(container->sock, &set);
                else