X-Git-Url: http://git.ipfire.org/?a=blobdiff_plain;f=policy.c;h=064d34919ea5a750ca4394d7aa3d019392e44549;hb=5cb859962febacba3bb9257fc6ed9553ecc16752;hp=8b37ead60bc69b2ece0c00478d37c5a4c43be173;hpb=e3bb5f144bfdfb3ae66be8de2393e87162f01264;p=thirdparty%2Fmdadm.git diff --git a/policy.c b/policy.c index 8b37ead6..064d3491 100644 --- a/policy.c +++ b/policy.c @@ -40,9 +40,10 @@ * particularly from a set of policy rules in mdadm.conf */ -void pol_new(struct dev_policy **pol, char *name, char *val, char *metadata) +static void pol_new(struct dev_policy **pol, char *name, const char *val, + const char *metadata) { - struct dev_policy *n = malloc(sizeof(*n)); + struct dev_policy *n = xmalloc(sizeof(*n)); const char *real_metadata = NULL; int i; @@ -64,9 +65,9 @@ void pol_new(struct dev_policy **pol, char *name, char *val, char *metadata) real_metadata = super1.name; } if (!real_metadata) { - static char *prev = NULL; + static const char *prev = NULL; if (prev != metadata) { - fprintf(stderr, Name ": metadata=%s unrecognised - ignoring rule\n", + pr_err("metadata=%s unrecognised - ignoring rule\n", metadata); prev = metadata; } @@ -137,8 +138,8 @@ static void pol_sort(struct dev_policy **pol) if (p[next] == NULL || (p[1-next] != NULL && !(pol_lesseq(prev, p[1-next]) - ^pol_lesseq(p[1-next], p[next]) - ^pol_lesseq(p[next], prev))) + ^pol_lesseq(prev, p[next]) + ^pol_lesseq(p[next], p[1-next]))) ) next = 1 - next; @@ -194,30 +195,41 @@ static char *disk_path(struct mdinfo *disk) int prefix_len; DIR *by_path; char symlink[PATH_MAX] = "/dev/disk/by-path/"; + char nm[PATH_MAX]; struct dirent *ent; + int rv; by_path = opendir(symlink); - if (!by_path) - return NULL; - 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; + 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; + closedir(by_path); + return xstrdup(ent->d_name); + } closedir(by_path); - return strdup(ent->d_name); } - closedir(by_path); - return NULL; + /* A NULL path isn't really acceptable - use the devname.. */ + sprintf(symlink, "/sys/dev/block/%d:%d", disk->disk.major, disk->disk.minor); + rv = readlink(symlink, nm, sizeof(nm)-1); + if (rv > 0) { + char *dname; + nm[rv] = 0; + dname = strrchr(nm, '/'); + if (dname) + return xstrdup(dname + 1); + } + return xstrdup("unknown"); } char type_part[] = "part"; @@ -244,13 +256,13 @@ static int pol_match(struct rule *rule, char *path, char *type) if (rule->name == rule_path) { if (pathok == 0) pathok = -1; - if (fnmatch(rule->value, path, 0) == 0) + if (path && fnmatch(rule->value, path, 0) == 0) pathok = 1; } if (rule->name == rule_type) { if (typeok == 0) typeok = -1; - if (strcmp(rule->value, type) == 0) + if (type && strcmp(rule->value, type) == 0) typeok = 1; } rule = rule->next; @@ -269,7 +281,8 @@ static void pol_merge(struct dev_policy **pol, struct rule *rule) for (r = rule; r ; r = r->next) if (r->name == pol_act || - r->name == pol_domain) + r->name == pol_domain || + r->name == pol_auto) pol_new(pol, r->name, r->value, metadata); } @@ -279,7 +292,10 @@ static int path_has_part(char *path, char **part) * if it does, place a pointer to "-pathNN" * in 'part'. */ - int l = strlen(path); + 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) @@ -333,23 +349,14 @@ static int config_rules_has_path = 0; /* * most policy comes from a set policy rules that are * read from the config file. - * disk_policy() gathers policy information for the - * disk described in the given mdinfo (disk.{major,minor}). + * path_policy() gathers policy information for the + * disk described in the given a 'path' and a 'type'. */ -struct dev_policy *disk_policy(struct mdinfo *disk) +struct dev_policy *path_policy(char *path, char *type) { - char *path = NULL; - char *type = disk_type(disk); struct pol_rule *rules; struct dev_policy *pol = NULL; - - if (!type) - return NULL; - if (config_rules_has_path) { - path = disk_path(disk); - if (!path) - return NULL; - } + int i; rules = config_rules; @@ -367,12 +374,59 @@ struct dev_policy *disk_policy(struct mdinfo *disk) } rules = rules->next; } + + /* Now add any metadata-specific internal knowledge + * about this path + */ + for (i=0; path && superlist[i]; i++) + if (superlist[i]->get_disk_controller_domain) { + const char *d = + superlist[i]->get_disk_controller_domain(path); + 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); +} + +/* + * 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 *path = NULL; + char *type = disk_type(disk); + struct dev_policy *pol = NULL; + + if (config_rules_has_path) + path = disk_path(disk); + + pol = path_policy(path, type); + free(path); 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. */ @@ -386,6 +440,7 @@ 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) { @@ -394,10 +449,10 @@ static int try_rule(char *w, char *name, struct rule **rp) if (strncmp(w, name, len) != 0 || w[len] != '=') return 0; - r = malloc(sizeof(*r)); + r = xmalloc(sizeof(*r)); r->next = *rp; r->name = name; - r->value = strdup(w+len+1); + r->value = xstrdup(w+len+1); r->dups = NULL; *rp = r; return 1; @@ -411,7 +466,7 @@ void policyline(char *line, char *type) if (config_rules_end == NULL) config_rules_end = &config_rules; - pr = malloc(sizeof(*pr)); + pr = xmalloc(sizeof(*pr)); pr->type = type; pr->rule = NULL; for (w = dl_next(line); w != line ; w = dl_next(w)) { @@ -420,14 +475,42 @@ void policyline(char *line, char *type) 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)) - fprintf(stderr, Name ": policy rule %s unrecognised and ignored\n", + ! 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) { @@ -460,7 +543,7 @@ void dev_policy_free(struct dev_policy *p) } } -static enum policy_action map_act(char *act) +static enum policy_action map_act(const char *act) { if (strcmp(act, "include") == 0) return act_include; @@ -468,6 +551,8 @@ static enum policy_action map_act(char *act) 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; @@ -504,3 +589,323 @@ int disk_action_allows(struct mdinfo *disk, const char *metadata, enum policy_ac 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; + 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; + } + 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 cookie\n"); + + fclose(f); +} + +int policy_check_path(struct mdinfo *disk, struct map_ent *array) +{ + char path[PATH_MAX]; + FILE *f = NULL; + char *id_path = disk_path(disk); + int rv; + + if (!id_path) + return 0; + + snprintf(path, PATH_MAX, FAILED_SLOTS_DIR "/%s", id_path); + f = fopen(path, "r"); + if (!f) { + free(id_path); + return 0; + } + + rv = fscanf(f, " %s %x:%x:%x:%x\n", + array->metadata, + array->uuid, + array->uuid+1, + array->uuid+2, + array->uuid+3); + fclose(f); + free(id_path); + 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; +}