2025-02-14 05:52:19 +01:00
|
|
|
/*
|
|
|
|
* 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"
|
2025-02-14 06:11:53 +01:00
|
|
|
#include "xmalloc.h"
|
|
|
|
|
2025-02-14 05:52:19 +01:00
|
|
|
#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;
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2025-02-14 06:34:22 +01:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2025-02-14 05:52:19 +01:00
|
|
|
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;
|
|
|
|
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, "%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 <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, " %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)
|
|
|
|
{
|
2025-02-14 06:11:53 +01:00
|
|
|
int fd = fileno(stdout);
|
2025-02-14 05:52:19 +01:00
|
|
|
|
2025-02-14 06:11:53 +01:00
|
|
|
if (rule_name)
|
|
|
|
fd = creat(rule_name, 0644);
|
|
|
|
|
|
|
|
if (!is_fd_valid(fd))
|
|
|
|
return 1;
|
2025-02-14 05:52:19 +01:00
|
|
|
|
|
|
|
/* 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);
|
2025-02-14 06:11:53 +01:00
|
|
|
if (rule_name)
|
2025-02-14 05:52:19 +01:00
|
|
|
close(fd);
|
2025-02-14 06:11:53 +01:00
|
|
|
|
2025-02-14 05:52:19 +01:00
|
|
|
return 0;
|
|
|
|
abort:
|
|
|
|
if (rule_name) {
|
|
|
|
close(fd);
|
2025-02-14 06:11:53 +01:00
|
|
|
unlink(rule_name);
|
2025-02-14 05:52:19 +01:00
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|