mdadm/mdstat.c

442 lines
10 KiB
C

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