]> git.ipfire.org Git - thirdparty/mdadm.git/blobdiff - policy.c
mdadm.h: Introduced unaligned {get,put}_unaligned{16,32}()
[thirdparty/mdadm.git] / policy.c
index 0e343b989748ee77a7913a985bc7a449508dc06a..3c53bd35e0b19a693ff84b81a927f27c3084048d 100644 (file)
--- a/policy.c
+++ b/policy.c
  * 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;
 
@@ -188,36 +189,40 @@ struct dev_policy *pol_find(struct dev_policy *pol, char *name)
        return pol;
 }
 
-static char *disk_path(struct mdinfo *disk)
+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;
 
-       by_path = opendir(symlink);
-       if (!by_path)
-               return NULL;
-       prefix_len = strlen(symlink);
+       paths = xmalloc(sizeof(*paths) * (cnt+1));
 
-       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;
+       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);
-               return strdup(ent->d_name);
        }
-       closedir(by_path);
-       return NULL;
+       paths[cnt] = NULL;
+       return paths;
 }
 
 char type_part[] = "part";
@@ -234,26 +239,60 @@ static char *disk_type(struct mdinfo *disk)
                return type_disk;
 }
 
-static int pol_match(struct rule *rule, char *path, char *type)
+static int path_has_part(char *path, char **part)
 {
-       /* check if this rule matches on path and type */
+       /* 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;
 
-       while (rule) {
+       for (; rule; rule = rule->next) {
                if (rule->name == rule_path) {
+                       char *p = NULL;
+                       int i;
                        if (pathok == 0)
                                pathok = -1;
-                       if (fnmatch(rule->value, path, 0) == 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 (strcmp(rule->value, type) == 0)
+                       if (type && strcmp(rule->value, type) == 0)
                                typeok = 1;
                }
-               rule = rule->next;
        }
        return pathok >= 0 && typeok >= 0;
 }
@@ -269,25 +308,11 @@ 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);
 }
 
-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 = 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-4;
-       return 1;
-}
-
 static void pol_merge_part(struct dev_policy **pol, struct rule *rule, char *part)
 {
        /* copy any name assignments from rule into pol, appending
@@ -333,46 +358,93 @@ 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 **paths, 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;
 
        while (rules) {
-               char *part;
+               char *part = NULL;
                if (rules->type == rule_policy)
-                       if (pol_match(rules->rule, path, type))
+                       if (pol_match(rules->rule, paths, type, NULL))
                                pol_merge(&pol, rules->rule);
                if (rules->type == rule_part && strcmp(type, type_part) == 0)
-                       if (path_has_part(path, &part)) {
-                               *part = 0;
-                               if (pol_match(rules->rule, path, type_disk))
-                                       pol_merge_part(&pol, rules->rule, part+1);
-                               *part = '-';
-                       }
+                       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);
-       free(path);
        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.
  */
@@ -386,6 +458,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 +467,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 +484,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 +493,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 +561,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 +569,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;
@@ -505,7 +608,6 @@ int disk_action_allows(struct mdinfo *disk, const char *metadata, enum policy_ac
        return rv;
 }
 
-
 /* Domain policy:
  * Any device can have a list of domains asserted by different policy
  * statements.
@@ -518,7 +620,8 @@ int disk_action_allows(struct mdinfo *disk, const char *metadata, enum policy_ac
  * As dev policies are already sorted, this is fairly easy to manage.
  */
 
-static struct domainlist **domain_merge_one(struct domainlist **domp, char *domain)
+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
@@ -530,7 +633,7 @@ static struct domainlist **domain_merge_one(struct domainlist **domp, char *doma
                dom = *domp;
        }
        if (dom == NULL || strcmp(dom->dom, domain) != 0) {
-               dom = malloc(sizeof(*dom));
+               dom = xmalloc(sizeof(*dom));
                dom->next = *domp;
                dom->dom = domain;
                *domp = dom;
@@ -538,6 +641,20 @@ static struct domainlist **domain_merge_one(struct domainlist **domp, char *doma
        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)
 {
@@ -547,7 +664,7 @@ void domain_merge(struct domainlist **domp, struct dev_policy *pollist,
        struct dev_policy *pol;
        pollist = pol_find(pollist, pol_domain);
        pol_for_each(pol, pollist, metadata)
-               domp = domain_merge_one(domp, pol->value);
+               domain_merge_one(domp, pol->value);
 }
 
 int domain_test(struct domainlist *dom, struct dev_policy *pol,
@@ -556,35 +673,55 @@ int domain_test(struct domainlist *dom, struct dev_policy *pol,
        /* 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 reject the match.
+        * so we allow caller to choose:
+        * -1:  has no domains
+        *  0:  has domains, not all match
+        *  1:  has domains, all match
         */
-       int found_any = 0;
+       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, pol->value) < 0)
+               while (dom && strcmp(dom->dom, p->value) < 0)
                        dom = dom->next;
-               if (!dom || strcmp(dom->dom, pol->value) != 0)
+               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;
 
-       for (mdi = mdi->devs ; mdi ; mdi = mdi->next) {
-               struct dev_policy *pol = disk_policy(mdi);
+       if (!mdi)
+               return NULL;
+       for (mdi = mdi->devs ; mdi ; mdi = mdi->next)
+               domainlist_add_dev(&domlist, makedev(mdi->disk.major,
+                                                    mdi->disk.minor),
+                                  metadata);
 
-               domain_merge(&domlist, pol, metadata);
-               dev_policy_free(pol);
-       }
        return domlist;
 }
 
+void domain_add(struct domainlist **domp, char *domain)
+{
+       domain_merge_one(domp, domain);
+}
+
 void domain_free(struct domainlist *dl)
 {
        while (dl) {
@@ -593,3 +730,202 @@ void domain_free(struct domainlist *dl)
                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;
+}