| /* |
| * 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; |
| } |