| /* |
| * mdadm - manage Linux "md" devices aka RAID arrays. |
| * |
| * Copyright (C) 2001-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> |
| */ |
| |
| #include "mdadm.h" |
| #include <dirent.h> |
| #include <fnmatch.h> |
| #include <ctype.h> |
| #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; |
| int i; |
| |
| 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; |
| } |
| |
| /* Now add any metadata-specific internal knowledge |
| * about this path |
| */ |
| for (i=0; paths && paths[0] && superlist[i]; i++) |
| if (superlist[i]->get_disk_controller_domain) { |
| const char *d = |
| superlist[i]->get_disk_controller_domain( |
| paths[0]); |
| if (d) |
| pol_new(&pol, pol_domain, d, superlist[i]->name); |
| } |
| |
| pol_sort(&pol); |
| pol_dedup(pol); |
| return pol; |
| } |
| |
| 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, "%s %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 <id_path> 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, " %s %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; |
| } |