summaryrefslogtreecommitdiffstats
path: root/mdstat.c
diff options
context:
space:
mode:
Diffstat (limited to 'mdstat.c')
-rw-r--r--mdstat.c441
1 files changed, 441 insertions, 0 deletions
diff --git a/mdstat.c b/mdstat.c
new file mode 100644
index 0000000..2fd792c
--- /dev/null
+++ b/mdstat.c
@@ -0,0 +1,441 @@
+/*
+ * mdstat - parse /proc/mdstat file. Part of:
+ * mdadm - manage Linux "md" devices aka RAID arrays.
+ *
+ * Copyright (C) 2002-2009 Neil Brown <neilb@suse.de>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: Neil Brown
+ * Email: <neilb@suse.de>
+ */
+
+/*
+ * The /proc/mdstat file comes in at least 3 flavours:
+ * In an unpatched 2.2 kernel (md 0.36.6):
+ * Personalities : [n raidx] ...
+ * read_ahead {not set|%d sectors}
+ * md0 : {in}active{ raidX /dev/hda... %d blocks{ maxfault=%d}}
+ * md1 : .....
+ *
+ * Normally only 4 md lines, but all are listed.
+ *
+ * In a patched 2.2 kernel (md 0.90.0)
+ * Personalities : [raidx] ...
+ * read_ahead {not set|%d sectors}
+ * mdN : {in}active {(readonly)} raidX dev[%d]{(F)} ... %d blocks STATUS RESYNC
+ * ... Only initialised arrays listed
+ * unused devices: {dev dev ... | <none>}
+ *
+ * STATUS is personality dependant:
+ * linear: %dk rounding
+ * raid0: %dk chunks
+ * raid1: [%d/%d] [U_U] ( raid/working. operational or not)
+ * raid5: level 4/5, %dk chunk, algorithm %d [%d/%d] [U_U]
+ *
+ * RESYNC is empty or:
+ * {resync|recovery}=%u%% finish=%u.%umin
+ * or
+ * resync=DELAYED
+ *
+ * In a 2.4 kernel (md 0.90.0/2.4)
+ * Personalities : [raidX] ...
+ * read_ahead {not set|%d sectors}
+ * mdN : {in}active {(read-only)} raidX dev[%d]{(F)} ...
+ * %d blocks STATUS
+ * RESYNC
+ * unused devices: {dev dev .. | <none>}
+ *
+ * STATUS matches 0.90.0/2.2
+ * RESYNC includes [===>....],
+ * adds a space after {resync|recovery} and before and after '='
+ * adds a decimal to the recovery percent.
+ * adds (%d/%d) resync amount and max_blocks, before finish.
+ * adds speed=%dK/sec after finish
+ *
+ *
+ *
+ * Out of this we want to extract:
+ * list of devices, active or not
+ * pattern of failed drives (so need number of drives)
+ * percent resync complete
+ *
+ * As continuation is indicated by leading space, we use
+ * conf_line from config.c to read logical lines
+ *
+ */
+
+#include "mdadm.h"
+#include "dlink.h"
+#include <sys/select.h>
+#include <ctype.h>
+
+static void free_member_devnames(struct dev_member *m)
+{
+ while(m) {
+ struct dev_member *t = m;
+
+ m = m->next;
+ free(t->name);
+ free(t);
+ }
+}
+
+static int add_member_devname(struct dev_member **m, char *name)
+{
+ struct dev_member *new;
+ char *t;
+
+ if ((t = strchr(name, '[')) == NULL)
+ /* not a device */
+ return 0;
+
+ new = xmalloc(sizeof(*new));
+ new->name = strndup(name, t - name);
+ new->next = *m;
+ *m = new;
+ return 1;
+}
+
+void free_mdstat(struct mdstat_ent *ms)
+{
+ while (ms) {
+ struct mdstat_ent *t;
+ free(ms->level);
+ free(ms->pattern);
+ free(ms->metadata_version);
+ free_member_devnames(ms->members);
+ t = ms;
+ ms = ms->next;
+ free(t);
+ }
+}
+
+static int mdstat_fd = -1;
+struct mdstat_ent *mdstat_read(int hold, int start)
+{
+ FILE *f;
+ struct mdstat_ent *all, *rv, **end, **insert_here;
+ char *line;
+ int fd;
+
+ if (hold && mdstat_fd != -1) {
+ off_t offset = lseek(mdstat_fd, 0L, 0);
+ if (offset == (off_t)-1) {
+ return NULL;
+ }
+ fd = dup(mdstat_fd);
+ if (fd >= 0)
+ f = fdopen(fd, "r");
+ else
+ return NULL;
+ } else
+ f = fopen("/proc/mdstat", "r");
+ if (f == NULL)
+ return NULL;
+ else
+ fcntl(fileno(f), F_SETFD, FD_CLOEXEC);
+
+ all = NULL;
+ end = &all;
+ for (; (line = conf_line(f)) ; free_line(line)) {
+ struct mdstat_ent *ent;
+ char *w;
+ char devnm[32];
+ int in_devs = 0;
+
+ if (strcmp(line, "Personalities") == 0)
+ continue;
+ if (strcmp(line, "read_ahead") == 0)
+ continue;
+ if (strcmp(line, "unused") == 0)
+ continue;
+ insert_here = NULL;
+ /* Better be an md line.. */
+ if (strncmp(line, "md", 2)!= 0 || strlen(line) >= 32 ||
+ (line[2] != '_' && !isdigit(line[2])))
+ continue;
+ strcpy(devnm, line);
+
+ ent = xmalloc(sizeof(*ent));
+ ent->level = ent->pattern= NULL;
+ ent->next = NULL;
+ ent->percent = RESYNC_NONE;
+ ent->active = -1;
+ ent->resync = 0;
+ ent->metadata_version = NULL;
+ ent->raid_disks = 0;
+ ent->devcnt = 0;
+ ent->members = NULL;
+
+ strcpy(ent->devnm, devnm);
+
+ for (w=dl_next(line); w!= line ; w=dl_next(w)) {
+ int l = strlen(w);
+ char *eq;
+ if (strcmp(w, "active") == 0)
+ ent->active = 1;
+ else if (strcmp(w, "inactive") == 0) {
+ ent->active = 0;
+ in_devs = 1;
+ } else if (strcmp(w, "bitmap:") == 0) {
+ /* We need to stop parsing here;
+ * otherwise, ent->raid_disks will be
+ * overwritten by the wrong value.
+ */
+ break;
+ } else if (ent->active > 0 &&
+ ent->level == NULL &&
+ w[0] != '(' /*readonly*/) {
+ ent->level = xstrdup(w);
+ in_devs = 1;
+ } else if (in_devs && strcmp(w, "blocks") == 0)
+ in_devs = 0;
+ else if (in_devs) {
+ char *ep = strchr(w, '[');
+ ent->devcnt +=
+ add_member_devname(&ent->members, w);
+ if (ep && strncmp(w, "md", 2) == 0) {
+ /* This has an md device as a component.
+ * If that device is already in the
+ * list, make sure we insert before
+ * there.
+ */
+ struct mdstat_ent **ih;
+ ih = &all;
+ while (ih != insert_here && *ih &&
+ ((int)strlen((*ih)->devnm) !=
+ ep-w ||
+ strncmp((*ih)->devnm, w,
+ ep-w) != 0))
+ ih = & (*ih)->next;
+ insert_here = ih;
+ }
+ } else if (strcmp(w, "super") == 0 &&
+ dl_next(w) != line) {
+ w = dl_next(w);
+ ent->metadata_version = xstrdup(w);
+ } else if (w[0] == '[' && isdigit(w[1])) {
+ ent->raid_disks = atoi(w+1);
+ } else if (!ent->pattern &&
+ w[0] == '[' &&
+ (w[1] == 'U' || w[1] == '_')) {
+ ent->pattern = xstrdup(w+1);
+ if (ent->pattern[l-2] == ']')
+ ent->pattern[l-2] = '\0';
+ } else if (ent->percent == RESYNC_NONE &&
+ strncmp(w, "re", 2) == 0 &&
+ w[l-1] == '%' &&
+ (eq = strchr(w, '=')) != NULL ) {
+ ent->percent = atoi(eq+1);
+ if (strncmp(w,"resync", 6) == 0)
+ ent->resync = 1;
+ else if (strncmp(w, "reshape", 7) == 0)
+ ent->resync = 2;
+ else
+ ent->resync = 0;
+ } else if (ent->percent == RESYNC_NONE &&
+ (w[0] == 'r' || w[0] == 'c')) {
+ if (strncmp(w, "resync", 6) == 0)
+ ent->resync = 1;
+ if (strncmp(w, "reshape", 7) == 0)
+ ent->resync = 2;
+ if (strncmp(w, "recovery", 8) == 0)
+ ent->resync = 0;
+ if (strncmp(w, "check", 5) == 0)
+ ent->resync = 3;
+
+ if (l > 8 && strcmp(w+l-8, "=DELAYED") == 0)
+ ent->percent = RESYNC_DELAYED;
+ if (l > 8 && strcmp(w+l-8, "=PENDING") == 0)
+ ent->percent = RESYNC_PENDING;
+ if (l > 7 && strcmp(w+l-7, "=REMOTE") == 0)
+ ent->percent = RESYNC_REMOTE;
+ } else if (ent->percent == RESYNC_NONE &&
+ w[0] >= '0' &&
+ w[0] <= '9' &&
+ w[l-1] == '%') {
+ ent->percent = atoi(w);
+ }
+ }
+ if (insert_here && (*insert_here)) {
+ ent->next = *insert_here;
+ *insert_here = ent;
+ } else {
+ *end = ent;
+ end = &ent->next;
+ }
+ }
+ if (hold && mdstat_fd == -1) {
+ mdstat_fd = dup(fileno(f));
+ fcntl(mdstat_fd, F_SETFD, FD_CLOEXEC);
+ }
+ fclose(f);
+
+ /* If we might want to start array,
+ * reverse the order, so that components comes before composites
+ */
+ if (start) {
+ rv = NULL;
+ while (all) {
+ struct mdstat_ent *e = all;
+ all = all->next;
+ e->next = rv;
+ rv = e;
+ }
+ } else
+ rv = all;
+ return rv;
+}
+
+void mdstat_close(void)
+{
+ if (mdstat_fd >= 0)
+ close(mdstat_fd);
+ mdstat_fd = -1;
+}
+
+/*
+ * function: mdstat_wait
+ * Description: Function waits for event on mdstat.
+ * Parameters:
+ * seconds - timeout for waiting
+ * Returns:
+ * > 0 - detected event
+ * 0 - timeout
+ * < 0 - detected error
+ */
+int mdstat_wait(int seconds)
+{
+ fd_set fds;
+ struct timeval tm;
+ int maxfd = 0;
+ FD_ZERO(&fds);
+ if (mdstat_fd >= 0) {
+ FD_SET(mdstat_fd, &fds);
+ maxfd = mdstat_fd;
+ } else
+ return -1;
+
+ tm.tv_sec = seconds;
+ tm.tv_usec = 0;
+
+ return select(maxfd + 1, NULL, NULL, &fds, &tm);
+}
+
+void mdstat_wait_fd(int fd, const sigset_t *sigmask)
+{
+ fd_set fds, rfds;
+ int maxfd = 0;
+
+ FD_ZERO(&fds);
+ FD_ZERO(&rfds);
+ if (mdstat_fd >= 0)
+ FD_SET(mdstat_fd, &fds);
+
+ if (fd >= 0) {
+ struct stat stb;
+ fstat(fd, &stb);
+ if ((stb.st_mode & S_IFMT) == S_IFREG)
+ /* Must be a /proc or /sys fd, so expect
+ * POLLPRI
+ * i.e. an 'exceptional' event.
+ */
+ FD_SET(fd, &fds);
+ else
+ FD_SET(fd, &rfds);
+
+ if (fd > maxfd)
+ maxfd = fd;
+
+ }
+ if (mdstat_fd > maxfd)
+ maxfd = mdstat_fd;
+
+ pselect(maxfd + 1, &rfds, NULL, &fds,
+ NULL, sigmask);
+}
+
+int mddev_busy(char *devnm)
+{
+ struct mdstat_ent *mdstat = mdstat_read(0, 0);
+ struct mdstat_ent *me;
+
+ for (me = mdstat ; me ; me = me->next)
+ if (strcmp(me->devnm, devnm) == 0)
+ break;
+ free_mdstat(mdstat);
+ return me != NULL;
+}
+
+struct mdstat_ent *mdstat_by_component(char *name)
+{
+ struct mdstat_ent *mdstat = mdstat_read(0, 0);
+
+ while (mdstat) {
+ struct dev_member *m;
+ struct mdstat_ent *ent;
+ if (mdstat->metadata_version &&
+ strncmp(mdstat->metadata_version, "external:", 9) == 0 &&
+ is_subarray(mdstat->metadata_version+9))
+ /* don't return subarrays, only containers */
+ ;
+ else for (m = mdstat->members; m; m = m->next) {
+ if (strcmp(m->name, name) == 0) {
+ free_mdstat(mdstat->next);
+ mdstat->next = NULL;
+ return mdstat;
+ }
+ }
+ ent = mdstat;
+ mdstat = mdstat->next;
+ ent->next = NULL;
+ free_mdstat(ent);
+ }
+ return NULL;
+}
+
+struct mdstat_ent *mdstat_by_subdev(char *subdev, char *container)
+{
+ struct mdstat_ent *mdstat = mdstat_read(0, 0);
+ struct mdstat_ent *ent = NULL;
+
+ while (mdstat) {
+ /* metadata version must match:
+ * external:[/-]%s/%s
+ * where first %s is 'container' and second %s is 'subdev'
+ */
+ if (ent)
+ free_mdstat(ent);
+ ent = mdstat;
+ mdstat = mdstat->next;
+ ent->next = NULL;
+
+ if (ent->metadata_version == NULL ||
+ strncmp(ent->metadata_version, "external:", 9) != 0)
+ continue;
+
+ if (!metadata_container_matches(ent->metadata_version+9,
+ container) ||
+ !metadata_subdev_matches(ent->metadata_version+9,
+ subdev))
+ continue;
+
+ free_mdstat(mdstat);
+ return ent;
+ }
+ return NULL;
+}