| /* |
| * 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. |
| * This thread does things which might block such as memory |
| * allocation. |
| * In particular: |
| * |
| * - Find out about new arrays in this container. |
| * Allocate the data structures and open the files. |
| * |
| * For this we watch /proc/mdstat and find new arrays with |
| * metadata type that confirms sharing. e.g. "md4" |
| * When we find a new array we slip it into the list of |
| * arrays and signal 'monitor' by writing to a pipe. |
| * |
| * - Respond to reshape requests by allocating new data structures |
| * and opening new files. |
| * |
| * These come as a change to raid_disks. We allocate a new |
| * version of the data structures and slip it into the list. |
| * 'monitor' will notice and release the old version. |
| * Changes to level, chunksize, layout.. do not need re-allocation. |
| * Reductions in raid_disks don't really either, but we handle |
| * them the same way for consistency. |
| * |
| * - When a device is added to the container, we add it to the metadata |
| * as a spare. |
| * |
| * - Deal with degraded array |
| * We only do this when first noticing the array is degraded. |
| * This can be when we first see the array, when sync completes or |
| * when recovery completes. |
| * |
| * Check if number of failed devices suggests recovery is needed, and |
| * skip if not. |
| * Ask metadata to allocate a spare device |
| * Add device as not in_sync and give a role |
| * Update metadata. |
| * Open sysfs files and pass to monitor. |
| * Make sure that monitor Starts recovery.... |
| * |
| * - Pass on metadata updates from external programs such as |
| * mdadm creating a new array. |
| * |
| * This is most-messy. |
| * It might involve adding a new array or changing the status of |
| * a spare, or any reconfig that the kernel doesn't get involved in. |
| * |
| * The required updates are received via a named pipe. There will |
| * be one named pipe for each container. Each message contains a |
| * sync marker: 0x5a5aa5a5, A byte count, and the message. This is |
| * passed to the metadata handler which will interpret and process it. |
| * For 'DDF' messages are internal data blocks with the leading |
| * 'magic number' signifying what sort of data it is. |
| * |
| */ |
| |
| /* |
| * We select on /proc/mdstat and the named pipe. |
| * We create new arrays or updated version of arrays and slip |
| * them into the head of the list, then signal 'monitor' via a pipe write. |
| * 'monitor' will notice and place the old array on a return list. |
| * Metadata updates are placed on a queue just like they arrive |
| * from the named pipe. |
| * |
| * When new arrays are found based on correct metadata string, we |
| * need to identify them with an entry in the metadata. Maybe we require |
| * the metadata to be mdX/NN when NN is the index into an appropriate table. |
| * |
| */ |
| |
| /* |
| * List of tasks: |
| * - Watch for spares to be added to the container, and write updated |
| * metadata to them. |
| * - Watch for new arrays using this container, confirm they match metadata |
| * and if so, start monitoring them |
| * - Watch for spares being added to monitored arrays. This shouldn't |
| * happen, as we should do all the adding. Just remove them. |
| * - Watch for change in raid-disks, chunk-size, etc. Update metadata and |
| * start a reshape. |
| */ |
| #ifndef _GNU_SOURCE |
| #define _GNU_SOURCE |
| #endif |
| #include "mdadm.h" |
| #include "mdmon.h" |
| #include <sys/syscall.h> |
| #include <sys/socket.h> |
| #include <signal.h> |
| |
| static void close_aa(struct active_array *aa) |
| { |
| struct mdinfo *d; |
| |
| for (d = aa->info.devs; d; d = d->next) |
| close(d->state_fd); |
| |
| close(aa->action_fd); |
| close(aa->info.state_fd); |
| close(aa->resync_start_fd); |
| } |
| |
| static void free_aa(struct active_array *aa) |
| { |
| /* Note that this doesn't close fds if they are being used |
| * by a clone. ->container will be set for a clone |
| */ |
| dprintf("%s: devnum: %d\n", __func__, aa->devnum); |
| if (!aa->container) |
| close_aa(aa); |
| while (aa->info.devs) { |
| struct mdinfo *d = aa->info.devs; |
| aa->info.devs = d->next; |
| free(d); |
| } |
| free(aa); |
| } |
| |
| static struct active_array *duplicate_aa(struct active_array *aa) |
| { |
| struct active_array *newa = malloc(sizeof(*newa)); |
| struct mdinfo **dp1, **dp2; |
| |
| *newa = *aa; |
| newa->next = NULL; |
| newa->replaces = NULL; |
| newa->info.next = NULL; |
| |
| dp2 = &newa->info.devs; |
| |
| for (dp1 = &aa->info.devs; *dp1; dp1 = &(*dp1)->next) { |
| struct mdinfo *d; |
| if ((*dp1)->state_fd < 0) |
| continue; |
| |
| d = malloc(sizeof(*d)); |
| *d = **dp1; |
| *dp2 = d; |
| dp2 = & d->next; |
| } |
| *dp2 = NULL; |
| |
| return newa; |
| } |
| |
| static void wakeup_monitor(void) |
| { |
| /* tgkill(getpid(), mon_tid, SIGUSR1); */ |
| int pid = getpid(); |
| syscall(SYS_tgkill, pid, mon_tid, SIGUSR1); |
| } |
| |
| static void remove_old(void) |
| { |
| if (discard_this) { |
| discard_this->next = NULL; |
| free_aa(discard_this); |
| if (pending_discard == discard_this) |
| pending_discard = NULL; |
| discard_this = NULL; |
| wakeup_monitor(); |
| } |
| } |
| |
| static void replace_array(struct supertype *container, |
| struct active_array *old, |
| struct active_array *new) |
| { |
| /* To replace an array, we add it to the top of the list |
| * marked with ->replaces to point to the original. |
| * 'monitor' will take the original out of the list |
| * and put it on 'discard_this'. We take it from there |
| * and discard it. |
| */ |
| remove_old(); |
| while (pending_discard) { |
| while (discard_this == NULL) |
| sleep(1); |
| remove_old(); |
| } |
| pending_discard = old; |
| new->replaces = old; |
| new->next = container->arrays; |
| container->arrays = new; |
| wakeup_monitor(); |
| } |
| |
| 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) |
| { |
| while (update_queue_handled) { |
| struct metadata_update *this = update_queue_handled; |
| update_queue_handled = this->next; |
| free(this->buf); |
| if (this->space) |
| free(this->space); |
| free(this); |
| } |
| if (update_queue == NULL && |
| update_queue_pending) { |
| update_queue = update_queue_pending; |
| update_queue_pending = NULL; |
| wakeup_monitor(); |
| } |
| } |
| |
| static void queue_metadata_update(struct metadata_update *mu) |
| { |
| struct metadata_update **qp; |
| |
| qp = &update_queue_pending; |
| while (*qp) |
| qp = & ((*qp)->next); |
| *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); |
| 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; |
| } |
| |
| 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. |
| * 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 |
| */ |
| mdi = sysfs_read(-1, mdstat->devnum, GET_DEVS|SKIP_GONE_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; |
| 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 = malloc(sizeof(*newd)); |
| |
| if (!newd) { |
| container->devcnt = -1; |
| continue; |
| } |
| *newd = *di; |
| add_disk_to_container(container, newd); |
| } |
| } |
| sysfs_free(mdi); |
| container->devcnt = mdstat->devcnt; |
| } |
| } |
| |
| static void manage_member(struct mdstat_ent *mdstat, |
| struct active_array *a) |
| { |
| /* Compare mdstat info with known state of member array. |
| * 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. |
| * |
| * Actually, 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. |
| */ |
| // FIXME |
| a->info.array.raid_disks = mdstat->raid_disks; |
| a->info.array.chunk_size = mdstat->chunk_size; |
| // MORE |
| |
| if (a->check_degraded) { |
| struct metadata_update *updates = NULL; |
| struct mdinfo *newdev; |
| struct active_array *newa; |
| |
| 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) < 0) |
| continue; |
| newd = malloc(sizeof(*newd)); |
| *newd = *d; |
| newd->next = newa->info.devs; |
| newa->info.devs = newd; |
| |
| 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; |
| } |
| queue_metadata_update(updates); |
| replace_array(a->container, a, newa); |
| sysfs_set_str(&a->info, NULL, "sync_action", "recover"); |
| } |
| } |
| } |
| |
| static int aa_ready(struct active_array *aa) |
| { |
| struct mdinfo *d; |
| int level = aa->info.array.level; |
| |
| for (d = aa->info.devs; d; d = d->next) |
| if (d->state_fd < 0) |
| return 0; |
| |
| if (aa->info.state_fd < 0) |
| return 0; |
| |
| if (level > 0 && (aa->action_fd < 0 || aa->resync_start_fd < 0)) |
| return 0; |
| |
| if (!aa->container) |
| return 0; |
| |
| return 1; |
| } |
| |
| static void manage_new(struct mdstat_ent *mdstat, |
| struct supertype *container, |
| struct active_array *victim) |
| { |
| /* A new array has appeared in this container. |
| * Hopefully it is already recorded in the metadata. |
| * Check, then create the new array to report it to |
| * the monitor. |
| */ |
| |
| struct active_array *new; |
| struct mdinfo *mdi, *di; |
| char *inst; |
| int i; |
| int failed = 0; |
| |
| /* check if array is ready to be monitored */ |
| if (!mdstat->active) |
| 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); |
| return; |
| } |
| memset(new, 0, sizeof(*new)); |
| |
| new->devnum = mdstat->devnum; |
| strcpy(new->info.sys_name, devnum2devname(new->devnum)); |
| |
| new->prev_state = new->curr_state = new->next_state = inactive; |
| new->prev_action= new->curr_action= new->next_action= idle; |
| |
| new->container = container; |
| |
| inst = &mdstat->metadata_version[10+strlen(container->devname)+1]; |
| |
| 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)); |
| |
| for (di = mdi->devs; di; di = di->next) |
| if (i == di->disk.raid_disk) |
| break; |
| |
| if (di && newd) { |
| memcpy(newd, di, sizeof(*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 (newd) |
| free(newd); |
| |
| failed++; |
| if (failed > new->info.array.failed_disks) { |
| /* we cannot properly monitor without all working disks */ |
| new->container = NULL; |
| break; |
| } |
| continue; |
| } |
| 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"); |
| new->metadata_fd = sysfs_open(new->devnum, NULL, "metadata_version"); |
| get_resync_start(new); |
| dprintf("%s: inst: %d action: %d state: %d\n", __func__, atoi(inst), |
| new->action_fd, new->info.state_fd); |
| |
| sysfs_free(mdi); |
| |
| /* if everything checks out tell the metadata handler we want to |
| * manage this instance |
| */ |
| if (!aa_ready(new) || container->ss->open_new(container, new, inst) < 0) { |
| fprintf(stderr, "mdmon: failed to monitor %s\n", |
| mdstat->metadata_version); |
| new->container = NULL; |
| free_aa(new); |
| } 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) |
| { |
| /* We have just read mdstat and need to compare it with |
| * the known active arrays. |
| * Arrays with the wrong metadata are ignored. |
| */ |
| |
| for ( ; mdstat ; mdstat = mdstat->next) { |
| struct active_array *a; |
| if (mdstat->devnum == container->devnum) { |
| manage_container(mdstat, container); |
| continue; |
| } |
| 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) |
| manage_member(mdstat, a); |
| break; |
| } |
| } |
| if (a == NULL || !a->container) |
| manage_new(mdstat, container, a); |
| } |
| } |
| |
| static void handle_message(struct supertype *container, struct metadata_update *msg) |
| { |
| /* queue this metadata update through to the monitor */ |
| |
| struct metadata_update *mu; |
| |
| 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 */ |
| else |
| cnt += 3; /* wait for 2 pselects */ |
| wakeup_monitor(); |
| |
| while (monitor_loop_cnt - cnt < 0) |
| usleep(10 * 1000); |
| } 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 = malloc(sizeof(*mu)); |
| mu->len = msg->len; |
| mu->buf = msg->buf; |
| msg->buf = NULL; |
| mu->space = NULL; |
| mu->next = NULL; |
| if (container->ss->prepare_update) |
| container->ss->prepare_update(container, mu); |
| queue_metadata_update(mu); |
| } |
| } |
| |
| void read_sock(struct supertype *container) |
| { |
| int fd; |
| struct metadata_update msg; |
| int terminate = 0; |
| long fl; |
| int tmo = 3; /* 3 second timeout before hanging up the socket */ |
| |
| fd = accept(container->sock, NULL, NULL); |
| if (fd < 0) |
| return; |
| |
| fl = fcntl(fd, F_GETFL, 0); |
| fl |= O_NONBLOCK; |
| fcntl(fd, F_SETFL, fl); |
| |
| do { |
| msg.buf = NULL; |
| |
| /* read and validate the message */ |
| if (receive_message(fd, &msg, tmo) == 0) { |
| handle_message(container, &msg); |
| if (ack(fd, tmo) < 0) |
| terminate = 1; |
| } else |
| terminate = 1; |
| |
| } while (!terminate); |
| |
| close(fd); |
| } |
| |
| int exit_now = 0; |
| int manager_ready = 0; |
| void do_manager(struct supertype *container) |
| { |
| struct mdstat_ent *mdstat; |
| sigset_t set; |
| int proc_fd; |
| |
| sigprocmask(SIG_UNBLOCK, NULL, &set); |
| sigdelset(&set, SIGUSR1); |
| sigdelset(&set, SIGHUP); |
| sigdelset(&set, SIGALRM); |
| sigdelset(&set, SIGTERM); |
| proc_fd = open("/proc/mounts", O_RDONLY); |
| |
| do { |
| |
| if (exit_now) |
| exit(0); |
| |
| /* Can only 'manage' things if 'monitor' is not making |
| * structural changes to metadata, so need to check |
| * update_queue |
| */ |
| if (update_queue == NULL) { |
| mdstat = mdstat_read(1, 0); |
| |
| manage(mdstat, container); |
| |
| read_sock(container); |
| |
| if (container->sock < 0 || socket_hup_requested) { |
| /* If this fails, we hope it already exists |
| * pid file lives in /var/run/mdadm/mdXX.pid |
| */ |
| mkdir("/var", 0600); |
| mkdir("/var/run", 0600); |
| mkdir("/var/run/mdadm", 0600); |
| close(container->sock); |
| container->sock = make_control_sock(container->devname); |
| make_pidfile(container->devname, 0); |
| socket_hup_requested = 0; |
| } |
| if (container->sock < 0) |
| alarm(30); |
| |
| free_mdstat(mdstat); |
| } |
| remove_old(); |
| |
| check_update_queue(container); |
| |
| manager_ready = 1; |
| |
| if (sigterm) |
| wakeup_monitor(); |
| |
| if (update_queue == NULL) { |
| if (container->sock < 0) |
| mdstat_wait_fd(proc_fd, &set); |
| else |
| mdstat_wait_fd(container->sock, &set); |
| } else |
| /* If an update is happening, just wait for signal */ |
| pselect(0, NULL, NULL, NULL, NULL, &set); |
| } while(1); |
| } |