* 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));
const char *real_metadata = NULL;
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",
metadata);
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;
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 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 strdup(dname + 1);
+ }
+ return strdup("unknown");
}
char type_part[] = "part";
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;
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);
}
* 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)
/*
* 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;
}
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 *devnum_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 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)
{
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_domain, &pr->rule) &&
+ ! try_rule(w, pol_auto, &pr->rule))
fprintf(stderr, Name ": policy rule %s unrecognised and ignored\n",
w);
}
config_rules = pr;
}
+void policy_add(char *type, ...)
+{
+ va_list ap;
+ struct pol_rule *pr;
+ char *name, *val;
+
+ pr = malloc(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 = malloc(sizeof(*r));
+ r->next = pr->rule;
+ r->name = name;
+ r->value = strdup(val);
+ r->dups = NULL;
+ pr->rule = r;
+ }
+ pr->next = config_rules;
+ config_rules = pr;
+ va_end(ap);
+}
+
void policy_free(void)
{
while (config_rules) {
}
}
-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;
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;
* 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
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)
{
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,
/* 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;
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;
}
return found_any;
}
+void domainlist_add_dev(struct domainlist **dom, int devnum, const char *metadata)
+{
+ struct dev_policy *pol = devnum_policy(devnum);
+ 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) {
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) {
+ fprintf(stderr, Name ": 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) {
+ fprintf(stderr, Name ": 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)
+ fprintf(stderr, Name ": 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_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+=\"/sbin/mdadm --incremental $env{DEVNAME}\"\n"
+
+#define UDEV_RULE_FORMAT_NOTYPE \
+"ACTION==\"add\", SUBSYSTEM==\"block\", " \
+"ENV{ID_PATH}==\"%s\", " \
+"RUN+=\"/sbin/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;
+}