/* * mdadm - manage Linux "md" devices aka RAID arrays. * * Copyright (C) 2001-2009 Neil Brown * * * 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: */ #include "mdadm.h" #include #include #include #include "dlink.h" /* * Policy module for mdadm. * A policy statement about a device lists a set of values for each * of a set of names. Each value can have a metadata type as context. * * names include: * action - the actions that can be taken on hot-plug * domain - the domain(s) that the device is part of * * Policy information is extracted from various sources, but * particularly from a set of policy rules in mdadm.conf */ static void pol_new(struct dev_policy **pol, char *name, const char *val, const char *metadata) { struct dev_policy *n = xmalloc(sizeof(*n)); const char *real_metadata = NULL; int i; n->name = name; n->value = val; /* We need to normalise the metadata name */ if (metadata) { for (i = 0; superlist[i] ; i++) if (strcmp(metadata, superlist[i]->name) == 0) { real_metadata = superlist[i]->name; break; } if (!real_metadata) { if (strcmp(metadata, "1") == 0 || strcmp(metadata, "1.0") == 0 || strcmp(metadata, "1.1") == 0 || strcmp(metadata, "1.2") == 0) real_metadata = super1.name; } if (!real_metadata) { static const char *prev = NULL; if (prev != metadata) { pr_err("metadata=%s unrecognised - ignoring rule\n", metadata); prev = metadata; } real_metadata = "unknown"; } } n->metadata = real_metadata; n->next = *pol; *pol = n; } static int pol_lesseq(struct dev_policy *a, struct dev_policy *b) { int cmp; if (a->name < b->name) return 1; if (a->name > b->name) return 0; cmp = strcmp(a->value, b->value); if (cmp < 0) return 1; if (cmp > 0) return 0; return (a->metadata <= b->metadata); } static void pol_sort(struct dev_policy **pol) { /* sort policy list in *pol by name/metadata/value * using merge sort */ struct dev_policy *pl[2]; pl[0] = *pol; pl[1] = NULL; do { struct dev_policy **plp[2], *p[2]; int curr = 0; struct dev_policy nul = { NULL, NULL, NULL, NULL }; struct dev_policy *prev = &nul; int next = 0; /* p[] are the two lists that we are merging. * plp[] are the ends of the two lists we create * from the merge. * 'curr' is which of plp[] that we are currently * adding items to. * 'next' is which if p[] we will take the next * item from. * 'prev' is that last value, which was placed in * plp[curr]. */ plp[0] = &pl[0]; plp[1] = &pl[1]; p[0] = pl[0]; p[1] = pl[1]; /* take least of p[0] and p[1] * if it is larger than prev, add to * plp[curr], else swap curr then add */ while (p[0] || p[1]) { if (p[next] == NULL || (p[1-next] != NULL && !(pol_lesseq(prev, p[1-next]) ^pol_lesseq(prev, p[next]) ^pol_lesseq(p[next], p[1-next]))) ) next = 1 - next; if (!pol_lesseq(prev, p[next])) curr = 1 - curr; *plp[curr] = prev = p[next]; plp[curr] = &p[next]->next; p[next] = p[next]->next; } *plp[0] = NULL; *plp[1] = NULL; } while (pl[0] && pl[1]); if (pl[0]) *pol = pl[0]; else *pol = pl[1]; } static void pol_dedup(struct dev_policy *pol) { /* This is a sorted list - remove duplicates. */ while (pol && pol->next) { if (pol_lesseq(pol->next, pol)) { struct dev_policy *tmp = pol->next; pol->next = tmp->next; free(tmp); } else pol = pol->next; } } /* * pol_find finds the first entry in the policy * list to match name. * If it returns non-NULL there is at least one * value, but how many can only be found by * iterating through the list. */ struct dev_policy *pol_find(struct dev_policy *pol, char *name) { while (pol && pol->name < name) pol = pol->next; if (!pol || pol->name != name) return NULL; return pol; } static char **disk_paths(struct mdinfo *disk) { struct stat stb; int prefix_len; DIR *by_path; char symlink[PATH_MAX] = "/dev/disk/by-path/"; char **paths; int cnt = 0; struct dirent *ent; paths = xmalloc(sizeof(*paths) * (cnt+1)); by_path = opendir(symlink); if (by_path) { prefix_len = strlen(symlink); while ((ent = readdir(by_path)) != NULL) { if (ent->d_type != DT_LNK) continue; strncpy(symlink + prefix_len, ent->d_name, sizeof(symlink) - prefix_len); if (stat(symlink, &stb) < 0) continue; if ((stb.st_mode & S_IFMT) != S_IFBLK) continue; if (stb.st_rdev != makedev(disk->disk.major, disk->disk.minor)) continue; paths[cnt++] = xstrdup(ent->d_name); paths = xrealloc(paths, sizeof(*paths) * (cnt+1)); } closedir(by_path); } paths[cnt] = NULL; return paths; } char type_part[] = "part"; char type_disk[] = "disk"; static char *disk_type(struct mdinfo *disk) { char buf[30+20+20]; struct stat stb; sprintf(buf, "/sys/dev/block/%d:%d/partition", disk->disk.major, disk->disk.minor); if (stat(buf, &stb) == 0) return type_part; else return type_disk; } static int path_has_part(char *path, char **part) { /* check if path ends with "-partNN" and * if it does, place a pointer to "-pathNN" * in 'part'. */ int l; if (!path) return 0; l = strlen(path); while (l > 1 && isdigit(path[l-1])) l--; if (l < 5 || strncmp(path+l-5, "-part", 5) != 0) return 0; *part = path+l-5; return 1; } static int pol_match(struct rule *rule, char **paths, char *type, char **part) { /* Check if this rule matches on any path and type. * If 'part' is not NULL, then 'path' must end in -partN, which * we ignore for matching, and return in *part on success. */ int pathok = 0; /* 0 == no path, 1 == match, -1 == no match yet */ int typeok = 0; for (; rule; rule = rule->next) { if (rule->name == rule_path) { char *p = NULL; int i; if (pathok == 0) pathok = -1; if (!paths) continue; for (i = 0; paths[i]; i++) { if (part) { if (!path_has_part(paths[i], &p)) continue; *p = '\0'; *part = p+1; } if (fnmatch(rule->value, paths[i], 0) == 0) pathok = 1; if (part) *p = '-'; } } if (rule->name == rule_type) { if (typeok == 0) typeok = -1; if (type && strcmp(rule->value, type) == 0) typeok = 1; } } return pathok >= 0 && typeok >= 0; } static void pol_merge(struct dev_policy **pol, struct rule *rule) { /* copy any name assignments from rule into pol */ struct rule *r; char *metadata = NULL; for (r = rule; r ; r = r->next) if (r->name == pol_metadata) metadata = r->value; for (r = rule; r ; r = r->next) if (r->name == pol_act || r->name == pol_domain || r->name == pol_auto) pol_new(pol, r->name, r->value, metadata); } static void pol_merge_part(struct dev_policy **pol, struct rule *rule, char *part) { /* copy any name assignments from rule into pol, appending * -part to any domain. The string with -part appended is * stored with the rule so it has a lifetime to match * the rule. */ struct rule *r; char *metadata = NULL; for (r = rule; r ; r = r->next) if (r->name == pol_metadata) metadata = r->value; for (r = rule; r ; r = r->next) { if (r->name == pol_act) pol_new(pol, r->name, r->value, metadata); else if (r->name == pol_domain) { char *dom; int len; if (r->dups == NULL) r->dups = dl_head(); len = strlen(r->value); for (dom = dl_next(r->dups); dom != r->dups; dom = dl_next(dom)) if (strcmp(dom+len+1, part)== 0) break; if (dom == r->dups) { char *newdom = dl_strndup( r->value, len + 1 + strlen(part)); strcat(strcat(newdom, "-"), part); dl_add(r->dups, newdom); dom = newdom; } pol_new(pol, r->name, dom, metadata); } } } static struct pol_rule *config_rules = NULL; static struct pol_rule **config_rules_end = NULL; static int config_rules_has_path = 0; /* * most policy comes from a set policy rules that are * read from the config file. * path_policy() gathers policy information for the * disk described in the given a 'path' and a 'type'. */ struct dev_policy *path_policy(char **paths, char *type) { struct pol_rule *rules; struct dev_policy *pol = NULL; rules = config_rules; while (rules) { char *part = NULL; if (rules->type == rule_policy) if (pol_match(rules->rule, paths, type, NULL)) pol_merge(&pol, rules->rule); if (rules->type == rule_part && strcmp(type, type_part) == 0) if (pol_match(rules->rule, paths, type_disk, &part)) pol_merge_part(&pol, rules->rule, part); rules = rules->next; } pol_sort(&pol); pol_dedup(pol); return pol; } /** * drive_test_and_add_policies() - get policies for drive and add them to pols. * @st: supertype. * @pols: pointer to pointer of first list entry, cannot be NULL, may point to NULL. * @fd: device descriptor. * @verbose: verbose flag. * * If supertype doesn't support this functionality return success. Use metadata handler to get * policies. */ mdadm_status_t drive_test_and_add_policies(struct supertype *st, dev_policy_t **pols, int fd, const int verbose) { if (!st->ss->test_and_add_drive_policies) return MDADM_STATUS_SUCCESS; if (st->ss->test_and_add_drive_policies(pols, fd, verbose) == MDADM_STATUS_SUCCESS) { /* After successful call list cannot be empty */ assert(*pols); return MDADM_STATUS_SUCCESS; } return MDADM_STATUS_ERROR; } /** * sysfs_test_and_add_policies() - get policies for mddev and add them to pols. * @st: supertype. * @pols: pointer to pointer of first list entry, cannot be NULL, may point to NULL. * @mdi: mdinfo describes the MD array, must have GET_DISKS option. * @verbose: verbose flag. * * If supertype doesn't support this functionality return success. To get policies, all disks * connected to mddev are analyzed. */ mdadm_status_t sysfs_test_and_add_drive_policies(struct supertype *st, dev_policy_t **pols, struct mdinfo *mdi, const int verbose) { struct mdinfo *sd; if (!st->ss->test_and_add_drive_policies) return MDADM_STATUS_SUCCESS; for (sd = mdi->devs; sd; sd = sd->next) { char *devpath = map_dev(sd->disk.major, sd->disk.minor, 0); int fd = dev_open(devpath, O_RDONLY); int rv; if (!is_fd_valid(fd)) { pr_err("Cannot open fd for %s\n", devpath); return MDADM_STATUS_ERROR; } rv = drive_test_and_add_policies(st, pols, fd, verbose); close(fd); if (rv) return MDADM_STATUS_ERROR; } return MDADM_STATUS_SUCCESS; } /** * mddev_test_and_add_policies() - get policies for mddev and add them to pols. * @st: supertype. * @pols: pointer to pointer of first list entry, cannot be NULL, may point to NULL. * @array_fd: MD device descriptor. * @verbose: verbose flag. * * If supertype doesn't support this functionality return success. Use fd to extract disks. */ mdadm_status_t mddev_test_and_add_drive_policies(struct supertype *st, dev_policy_t **pols, int array_fd, const int verbose) { struct mdinfo *sra; int ret; if (!st->ss->test_and_add_drive_policies) return MDADM_STATUS_SUCCESS; sra = sysfs_read(array_fd, NULL, GET_DEVS); if (!sra) { pr_err("Cannot load sysfs for %s\n", fd2devnm(array_fd)); return MDADM_STATUS_ERROR; } ret = sysfs_test_and_add_drive_policies(st, pols, sra, verbose); sysfs_free(sra); return ret; } void pol_add(struct dev_policy **pol, char *name, char *val, char *metadata) { pol_new(pol, name, val, metadata); pol_sort(pol); pol_dedup(*pol); } static void free_paths(char **paths) { int i; if (!paths) return; for (i = 0; paths[i]; i++) free(paths[i]); free(paths); } /* * disk_policy() gathers policy information for the * disk described in the given mdinfo (disk.{major,minor}). */ struct dev_policy *disk_policy(struct mdinfo *disk) { char **paths = NULL; char *type = disk_type(disk); struct dev_policy *pol = NULL; if (config_rules_has_path) paths = disk_paths(disk); pol = path_policy(paths, type); free_paths(paths); return pol; } struct dev_policy *devid_policy(int dev) { struct mdinfo disk; disk.disk.major = major(dev); disk.disk.minor = minor(dev); return disk_policy(&disk); } /* * process policy rules read from config file. */ char rule_path[] = "path"; char rule_type[] = "type"; char rule_policy[] = "policy"; char rule_part[] = "part-policy"; char pol_metadata[] = "metadata"; char pol_act[] = "action"; char pol_domain[] = "domain"; char pol_auto[] = "auto"; static int try_rule(char *w, char *name, struct rule **rp) { struct rule *r; int len = strlen(name); if (strncmp(w, name, len) != 0 || w[len] != '=') return 0; r = xmalloc(sizeof(*r)); r->next = *rp; r->name = name; r->value = xstrdup(w+len+1); r->dups = NULL; *rp = r; return 1; } void policyline(char *line, char *type) { struct pol_rule *pr; char *w; if (config_rules_end == NULL) config_rules_end = &config_rules; pr = xmalloc(sizeof(*pr)); pr->type = type; pr->rule = NULL; for (w = dl_next(line); w != line ; w = dl_next(w)) { if (try_rule(w, rule_path, &pr->rule)) config_rules_has_path = 1; else if (! try_rule(w, rule_type, &pr->rule) && ! try_rule(w, pol_metadata, &pr->rule) && ! try_rule(w, pol_act, &pr->rule) && ! try_rule(w, pol_domain, &pr->rule) && ! try_rule(w, pol_auto, &pr->rule)) pr_err("policy rule %s unrecognised and ignored\n", w); } pr->next = config_rules; config_rules = pr; } void policy_add(char *type, ...) { va_list ap; struct pol_rule *pr; char *name, *val; pr = xmalloc(sizeof(*pr)); pr->type = type; pr->rule = NULL; va_start(ap, type); while ((name = va_arg(ap, char*)) != NULL) { struct rule *r; val = va_arg(ap, char*); r = xmalloc(sizeof(*r)); r->next = pr->rule; r->name = name; r->value = xstrdup(val); r->dups = NULL; pr->rule = r; } pr->next = config_rules; config_rules = pr; va_end(ap); } void policy_free(void) { while (config_rules) { struct pol_rule *pr = config_rules; struct rule *r; config_rules = config_rules->next; for (r = pr->rule; r; ) { struct rule *next = r->next; free(r->value); if (r->dups) free_line(r->dups); free(r); r = next; } free(pr); } config_rules_end = NULL; config_rules_has_path = 0; } void dev_policy_free(struct dev_policy *p) { struct dev_policy *t; while (p) { t = p; p = p->next; free(t); } } static enum policy_action map_act(const char *act) { if (strcmp(act, "include") == 0) return act_include; if (strcmp(act, "re-add") == 0) return act_re_add; if (strcmp(act, "spare") == 0) return act_spare; if (strcmp(act, "spare-same-slot") == 0) return act_spare_same_slot; if (strcmp(act, "force-spare") == 0) return act_force_spare; return act_err; } static enum policy_action policy_action(struct dev_policy *plist, const char *metadata) { enum policy_action rv = act_default; struct dev_policy *p; plist = pol_find(plist, pol_act); pol_for_each(p, plist, metadata) { enum policy_action a = map_act(p->value); if (a > rv) rv = a; } return rv; } int policy_action_allows(struct dev_policy *plist, const char *metadata, enum policy_action want) { enum policy_action act = policy_action(plist, metadata); if (act == act_err) return 0; return (act >= want); } int disk_action_allows(struct mdinfo *disk, const char *metadata, enum policy_action want) { struct dev_policy *pol = disk_policy(disk); int rv = policy_action_allows(pol, metadata, want); dev_policy_free(pol); return rv; } /* Domain policy: * Any device can have a list of domains asserted by different policy * statements. * An array also has a list of domains comprising all the domains of * all the devices in an array. * Where an array has a spare-group, that becomes an addition domain for * every device in the array and thus for the array. * * We keep the list of domains in a sorted linked list * As dev policies are already sorted, this is fairly easy to manage. */ static struct domainlist **domain_merge_one(struct domainlist **domp, const char *domain) { /* merge a domain name into a sorted list and return the * location of the insertion or match */ struct domainlist *dom = *domp; while (dom && strcmp(dom->dom, domain) < 0) { domp = &dom->next; dom = *domp; } if (dom == NULL || strcmp(dom->dom, domain) != 0) { dom = xmalloc(sizeof(*dom)); dom->next = *domp; dom->dom = domain; *domp = dom; } return domp; } #if (DEBUG) void dump_policy(struct dev_policy *policy) { while (policy) { dprintf("policy: %p name: %s value: %s metadata: %s\n", policy, policy->name, policy->value, policy->metadata); policy = policy->next; } } #endif void domain_merge(struct domainlist **domp, struct dev_policy *pollist, const char *metadata) { /* Add to 'domp' all the domains in pol that apply to 'metadata' * which are not already in domp */ struct dev_policy *pol; pollist = pol_find(pollist, pol_domain); pol_for_each(pol, pollist, metadata) domain_merge_one(domp, pol->value); } int domain_test(struct domainlist *dom, struct dev_policy *pol, const char *metadata) { /* Check that all domains in pol (for metadata) are also in * dom. Both lists are sorted. * If pol has no domains, we don't really know about this device * so we allow caller to choose: * -1: has no domains * 0: has domains, not all match * 1: has domains, all match */ int found_any = -1; int has_one_domain = 1; struct dev_policy *p; pol = pol_find(pol, pol_domain); pol_for_each(p, pol, metadata) { found_any = 1; while (dom && strcmp(dom->dom, p->value) < 0) dom = dom->next; if (!dom || strcmp(dom->dom, p->value) != 0) return 0; if (has_one_domain && metadata && strcmp(metadata, "imsm") == 0) found_any = -1; has_one_domain = 0; } return found_any; } void domainlist_add_dev(struct domainlist **dom, int devid, const char *metadata) { struct dev_policy *pol = devid_policy(devid); domain_merge(dom, pol, metadata); dev_policy_free(pol); } struct domainlist *domain_from_array(struct mdinfo *mdi, const char *metadata) { struct domainlist *domlist = NULL; if (!mdi) return NULL; for (mdi = mdi->devs ; mdi ; mdi = mdi->next) domainlist_add_dev(&domlist, makedev(mdi->disk.major, mdi->disk.minor), metadata); return domlist; } void domain_add(struct domainlist **domp, char *domain) { domain_merge_one(domp, domain); } void domain_free(struct domainlist *dl) { while (dl) { struct domainlist *head = dl; dl = dl->next; free(head); } } /* * same-path policy. * Some policy decisions are guided by knowledge of which * array previously owned the device at a given physical location (path). * When removing a device from an array we might record the array against * the path, and when finding a new device, we might look for which * array previously used that path. * * The 'array' is described by a map_ent, and the path by a the disk in an * mdinfo, or a string. */ void policy_save_path(char *id_path, struct map_ent *array) { char path[PATH_MAX]; FILE *f = NULL; if (mkdir(FAILED_SLOTS_DIR, S_IRWXU) < 0 && errno != EEXIST) { pr_err("can't create file to save path to old disk: %s\n", strerror(errno)); return; } snprintf(path, PATH_MAX, FAILED_SLOTS_DIR "/%s", id_path); f = fopen(path, "w"); if (!f) { pr_err("can't create file to save path to old disk: %s\n", strerror(errno)); return; } if (fprintf(f, "%20s %08x:%08x:%08x:%08x\n", array->metadata, array->uuid[0], array->uuid[1], array->uuid[2], array->uuid[3]) <= 0) pr_err("Failed to write to cookie\n"); fclose(f); } int policy_check_path(struct mdinfo *disk, struct map_ent *array) { char path[PATH_MAX]; FILE *f = NULL; char **id_paths = disk_paths(disk); int i; int rv = 0; for (i = 0; id_paths[i]; i++) { snprintf(path, PATH_MAX, FAILED_SLOTS_DIR "/%s", id_paths[i]); f = fopen(path, "r"); if (!f) continue; rv = fscanf(f, " %20s %x:%x:%x:%x\n", array->metadata, array->uuid, array->uuid+1, array->uuid+2, array->uuid+3); fclose(f); break; } free_paths(id_paths); return rv == 5; } /* invocation of udev rule file */ char udev_template_start[] = "# do not edit this file, it is automatically generated by mdadm\n" "\n"; /* find rule named rule_type and return its value */ char *find_rule(struct rule *rule, char *rule_type) { while (rule) { if (rule->name == rule_type) return rule->value; rule = rule->next; } return NULL; } #define UDEV_RULE_FORMAT \ "ACTION==\"add\", SUBSYSTEM==\"block\", " \ "ENV{DEVTYPE}==\"%s\", ENV{ID_PATH}==\"%s\", " \ "RUN+=\"" BINDIR "/mdadm --incremental $env{DEVNAME}\"\n" #define UDEV_RULE_FORMAT_NOTYPE \ "ACTION==\"add\", SUBSYSTEM==\"block\", " \ "ENV{ID_PATH}==\"%s\", " \ "RUN+=\"" BINDIR "/mdadm --incremental $env{DEVNAME}\"\n" /* Write rule in the rule file. Use format from UDEV_RULE_FORMAT */ int write_rule(struct rule *rule, int fd, int force_part) { char line[1024]; char *pth = find_rule(rule, rule_path); char *typ = find_rule(rule, rule_type); if (!pth) return -1; if (force_part) typ = type_part; if (typ) snprintf(line, sizeof(line) - 1, UDEV_RULE_FORMAT, typ, pth); else snprintf(line, sizeof(line) - 1, UDEV_RULE_FORMAT_NOTYPE, pth); return write(fd, line, strlen(line)) == (int)strlen(line); } /* Generate single entry in udev rule basing on POLICY line found in config * file. Take only those with paths, only first occurrence if paths are equal * and if actions supports handling of spares (>=act_spare_same_slot) */ int generate_entries(int fd) { struct pol_rule *loop, *dup; char *loop_value, *dup_value; int duplicate; for (loop = config_rules; loop; loop = loop->next) { if (loop->type != rule_policy && loop->type != rule_part) continue; duplicate = 0; /* only policies with paths and with actions supporting * bare disks are considered */ loop_value = find_rule(loop->rule, pol_act); if (!loop_value || map_act(loop_value) < act_spare_same_slot) continue; loop_value = find_rule(loop->rule, rule_path); if (!loop_value) continue; for (dup = config_rules; dup != loop; dup = dup->next) { if (dup->type != rule_policy && loop->type != rule_part) continue; dup_value = find_rule(dup->rule, pol_act); if (!dup_value || map_act(dup_value) < act_spare_same_slot) continue; dup_value = find_rule(dup->rule, rule_path); if (!dup_value) continue; if (strcmp(loop_value, dup_value) == 0) { duplicate = 1; break; } } /* not a dup or first occurrence */ if (!duplicate) if (!write_rule(loop->rule, fd, loop->type == rule_part) ) return 0; } return 1; } /* Write_rules routine creates dynamic udev rules used to handle * hot-plug events for bare devices (and making them spares) */ int Write_rules(char *rule_name) { int fd; char udev_rule_file[PATH_MAX]; if (rule_name) { strncpy(udev_rule_file, rule_name, sizeof(udev_rule_file) - 6); udev_rule_file[sizeof(udev_rule_file) - 6] = '\0'; strcat(udev_rule_file, ".temp"); fd = creat(udev_rule_file, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (fd == -1) return 1; } else fd = 1; /* write static invocation */ if (write(fd, udev_template_start, sizeof(udev_template_start) - 1) != (int)sizeof(udev_template_start) - 1) goto abort; /* iterate, if none created or error occurred, remove file */ if (generate_entries(fd) < 0) goto abort; fsync(fd); if (rule_name) { close(fd); rename(udev_rule_file, rule_name); } return 0; abort: if (rule_name) { close(fd); unlink(udev_rule_file); } return 1; }