From 7f1d6c8fec531fa1762d6d65576aecbee837982c Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 10 Apr 2024 22:55:34 +0200 Subject: Adding upstream version 4.3. Signed-off-by: Daniel Baumann --- policy.c | 931 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 931 insertions(+) create mode 100644 policy.c (limited to 'policy.c') diff --git a/policy.c b/policy.c new file mode 100644 index 0000000..eee9ef6 --- /dev/null +++ b/policy.c @@ -0,0 +1,931 @@ +/* + * mdadm - manage Linux "md" devices aka RAID arrays. + * + * Copyright (C) 2001-2009 Neil Brown + * + * + * 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: + */ + +#include "mdadm.h" +#include +#include +#include +#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, "%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 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) +{ + 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; +} -- cgit v1.2.3