#include "alloc-util.h"
#include "architecture.h"
#include "conf-files.h"
-#include "def.h"
+#include "conf-parser.h"
+#include "constants.h"
#include "device-private.h"
#include "device-util.h"
#include "dirent-util.h"
-#include "escape.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-util.h"
#include "syslog-util.h"
#include "udev-builtin.h"
#include "udev-event.h"
+#include "udev-node.h"
#include "udev-rules.h"
#include "udev-util.h"
#include "user-util.h"
};
struct UdevRules {
- usec_t dirs_ts_usec;
ResolveNameTiming resolve_name_timing;
Hashmap *known_users;
Hashmap *known_groups;
+ Hashmap *stats_by_path;
UdevRuleFile *current_file;
LIST_HEAD(UdevRuleFile, rule_files);
};
}
static void udev_rule_line_clear_tokens(UdevRuleLine *rule_line) {
- UdevRuleToken *i, *next;
-
assert(rule_line);
- LIST_FOREACH_SAFE(tokens, i, next, rule_line->tokens)
+ LIST_FOREACH(tokens, i, rule_line->tokens)
udev_rule_token_free(i);
rule_line->tokens = NULL;
DEFINE_TRIVIAL_CLEANUP_FUNC(UdevRuleLine*, udev_rule_line_free);
static void udev_rule_file_free(UdevRuleFile *rule_file) {
- UdevRuleLine *i, *next;
-
if (!rule_file)
return;
- LIST_FOREACH_SAFE(rule_lines, i, next, rule_file->rule_lines)
+ LIST_FOREACH(rule_lines, i, rule_file->rule_lines)
udev_rule_line_free(i);
free(rule_file->filename);
}
UdevRules *udev_rules_free(UdevRules *rules) {
- UdevRuleFile *i, *next;
-
if (!rules)
return NULL;
- LIST_FOREACH_SAFE(rule_files, i, next, rules->rule_files)
+ LIST_FOREACH(rule_files, i, rules->rule_files)
udev_rule_file_free(i);
hashmap_free_free_key(rules->known_users);
hashmap_free_free_key(rules->known_groups);
+ hashmap_free(rules->stats_by_path);
return mfree(rules);
}
}
static void sort_tokens(UdevRuleLine *rule_line) {
- UdevRuleToken *head_old;
-
assert(rule_line);
- head_old = TAKE_PTR(rule_line->tokens);
+ UdevRuleToken *old_tokens = TAKE_PTR(rule_line->tokens);
rule_line->current_token = NULL;
- while (!LIST_IS_EMPTY(head_old)) {
- UdevRuleToken *t, *min_token = NULL;
+ while (old_tokens) {
+ UdevRuleToken *min_token = NULL;
- LIST_FOREACH(tokens, t, head_old)
+ LIST_FOREACH(tokens, t, old_tokens)
if (!min_token || min_token->type > t->type)
min_token = t;
- LIST_REMOVE(tokens, head_old, min_token);
+ LIST_REMOVE(tokens, old_tokens, min_token);
rule_line_append_token(rule_line, min_token);
}
}
}
static void rule_resolve_goto(UdevRuleFile *rule_file) {
- UdevRuleLine *line, *line_next, *i;
-
assert(rule_file);
/* link GOTOs to LABEL rules in this file to be able to fast-forward */
- LIST_FOREACH_SAFE(rule_lines, line, line_next, rule_file->rule_lines) {
+ LIST_FOREACH(rule_lines, line, rule_file->rule_lines) {
if (!FLAGS_SET(line->type, LINE_HAS_GOTO))
continue;
UdevRuleFile *rule_file;
bool ignore_line = false;
unsigned line_nr = 0;
+ struct stat st;
int r;
f = fopen(filename, "re");
if (errno == ENOENT)
return 0;
- return -errno;
+ return log_warning_errno(errno, "Failed to open %s, ignoring: %m", filename);
}
- (void) fd_warn_permissions(filename, fileno(f));
+ if (fstat(fileno(f), &st) < 0)
+ return log_warning_errno(errno, "Failed to stat %s, ignoring: %m", filename);
- if (null_or_empty_fd(fileno(f))) {
+ if (null_or_empty(&st)) {
log_debug("Skipping empty file: %s", filename);
return 0;
}
+ r = hashmap_put_stats_by_path(&rules->stats_by_path, filename, &st);
+ if (r < 0)
+ return log_warning_errno(errno, "Failed to save stat for %s, ignoring: %m", filename);
+
+ (void) fd_warn_permissions(filename, fileno(f));
+
log_debug("Reading rules file: %s", filename);
name = strdup(filename);
int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing) {
_cleanup_(udev_rules_freep) UdevRules *rules = NULL;
_cleanup_strv_free_ char **files = NULL;
- char **f;
int r;
rules = udev_rules_new(resolve_name_timing);
if (!rules)
return -ENOMEM;
- (void) udev_rules_check_timestamp(rules);
-
r = conf_files_list_strv(&files, ".rules", NULL, 0, RULES_DIRS);
if (r < 0)
return log_debug_errno(r, "Failed to enumerate rules files: %m");
return 0;
}
-bool udev_rules_check_timestamp(UdevRules *rules) {
+bool udev_rules_should_reload(UdevRules *rules) {
+ _cleanup_hashmap_free_ Hashmap *stats_by_path = NULL;
+ int r;
+
if (!rules)
- return false;
+ return true;
- return paths_check_timestamp(RULES_DIRS, &rules->dirs_ts_usec, true);
+ r = config_get_stats_by_path(".rules", NULL, 0, RULES_DIRS, /* check_dropins = */ false, &stats_by_path);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to get stats of udev rules, ignoring: %m");
+ return true;
+ }
+
+ if (!stats_by_path_equal(rules->stats_by_path, stats_by_path)) {
+ log_debug("Udev rules need reloading");
+ return true;
+ }
+
+ return false;
}
static bool token_match_string(UdevRuleToken *token, const char *str) {
- const char *i, *value;
+ const char *value;
bool match = false;
assert(token);
return token->op == (match ? OP_MATCH : OP_NOMATCH);
}
-static bool token_match_attr(UdevRuleToken *token, sd_device *dev, UdevEvent *event) {
+static bool token_match_attr(UdevRules *rules, UdevRuleToken *token, sd_device *dev, UdevEvent *event) {
char nbuf[UDEV_NAME_SIZE], vbuf[UDEV_NAME_SIZE];
const char *name, *value;
+ bool truncated;
+ assert(rules);
assert(token);
+ assert(IN_SET(token->type, TK_M_ATTR, TK_M_PARENTS_ATTR));
assert(dev);
assert(event);
switch (token->attr_subst_type) {
case SUBST_TYPE_FORMAT:
- (void) udev_event_apply_format(event, name, nbuf, sizeof(nbuf), false);
+ (void) udev_event_apply_format(event, name, nbuf, sizeof(nbuf), false, &truncated);
+ if (truncated) {
+ log_rule_debug(dev, rules,
+ "The sysfs attribute name '%s' is truncated while substituting into '%s', "
+ "assuming the %s key does not match.", nbuf, name,
+ token->type == TK_M_ATTR ? "ATTR" : "ATTRS");
+ return false;
+ }
+
name = nbuf;
_fallthrough_;
case SUBST_TYPE_PLAIN:
static int attr_subst_subdir(char attr[static UDEV_PATH_SIZE]) {
_cleanup_closedir_ DIR *dir = NULL;
- struct dirent *dent;
char buf[UDEV_PATH_SIZE], *p;
const char *tail;
size_t len, size;
+ bool truncated;
assert(attr);
tail = strstr(attr, "/*/");
if (!tail)
- return 0;
+ return 0;
len = tail - attr + 1; /* include slash at the end */
tail += 2; /* include slash at the beginning */
p = buf;
size = sizeof(buf);
- size -= strnpcpy(&p, size, attr, len);
+ size -= strnpcpy_full(&p, size, attr, len, &truncated);
+ if (truncated)
+ return -ENOENT;
dir = opendir(buf);
if (!dir)
return -errno;
- FOREACH_DIRENT_ALL(dent, dir, break) {
- if (dent->d_name[0] == '.')
+ FOREACH_DIRENT_ALL(de, dir, break) {
+ if (de->d_name[0] == '.')
+ continue;
+
+ strscpyl_full(p, size, &truncated, de->d_name, tail, NULL);
+ if (truncated)
continue;
- strscpyl(p, size, dent->d_name, tail, NULL);
if (faccessat(dirfd(dir), p, F_OK, 0) < 0)
continue;
}
case TK_M_ATTR:
case TK_M_PARENTS_ATTR:
- return token_match_attr(token, dev, event);
+ return token_match_attr(rules, token, dev, event);
case TK_M_SYSCTL: {
_cleanup_free_ char *value = NULL;
char buf[UDEV_PATH_SIZE];
+ bool truncated;
+
+ (void) udev_event_apply_format(event, token->data, buf, sizeof(buf), false, &truncated);
+ if (truncated) {
+ log_rule_debug(dev, rules, "The sysctl entry name '%s' is truncated while substituting into '%s', "
+ "assuming the SYSCTL key does not match.", buf, (const char*) token->data);
+ return false;
+ }
- (void) udev_event_apply_format(event, token->data, buf, sizeof(buf), false);
r = sysctl_read(sysctl_normalize(buf), &value);
if (r < 0 && r != -ENOENT)
return log_rule_error_errno(dev, rules, r, "Failed to read sysctl '%s': %m", buf);
mode_t mode = PTR_TO_MODE(token->data);
char buf[UDEV_PATH_SIZE];
struct stat statbuf;
- bool match;
+ bool match, truncated;
+
+ (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated);
+ if (truncated) {
+ log_rule_debug(dev, rules, "The file name '%s' is truncated while substituting into '%s', "
+ "assuming the TEST key does not match", buf, token->value);
+ return false;
+ }
- (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false);
if (!path_is_absolute(buf) &&
udev_resolve_subsys_kernel(buf, buf, sizeof(buf), false) < 0) {
char tmp[UDEV_PATH_SIZE];
if (r < 0)
return log_rule_error_errno(dev, rules, r, "Failed to get syspath: %m");
- strscpy(tmp, sizeof(tmp), buf);
- strscpyl(buf, sizeof(buf), val, "/", tmp, NULL);
+ strscpy_full(tmp, sizeof(tmp), buf, &truncated);
+ assert(!truncated);
+ strscpyl_full(buf, sizeof(buf), &truncated, val, "/", tmp, NULL);
+ if (truncated)
+ return false;
}
r = attr_subst_subdir(buf);
return token->op == (match ? OP_MATCH : OP_NOMATCH);
}
case TK_M_PROGRAM: {
- char buf[UDEV_PATH_SIZE], result[UDEV_LINE_SIZE];
+ char buf[UDEV_LINE_SIZE], result[UDEV_LINE_SIZE];
+ bool truncated;
size_t count;
event->program_result = mfree(event->program_result);
- (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false);
+ (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated);
+ if (truncated) {
+ log_rule_debug(dev, rules, "The command '%s' is truncated while substituting into '%s', "
+ "assuming the PROGRAM key does not match.", buf, token->value);
+ return false;
+ }
+
log_rule_debug(dev, rules, "Running PROGRAM '%s'", buf);
- r = udev_event_spawn(event, timeout_usec, timeout_signal, true, buf, result, sizeof(result));
+ r = udev_event_spawn(event, timeout_usec, timeout_signal, true, buf, result, sizeof(result), NULL);
if (r != 0) {
if (r < 0)
log_rule_warning_errno(dev, rules, r, "Failed to execute \"%s\": %m", buf);
case TK_M_IMPORT_FILE: {
_cleanup_fclose_ FILE *f = NULL;
char buf[UDEV_PATH_SIZE];
+ bool truncated;
+
+ (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated);
+ if (truncated) {
+ log_rule_debug(dev, rules, "The file name '%s' to be imported is truncated while substituting into '%s', "
+ "assuming the IMPORT key does not match.", buf, token->value);
+ return false;
+ }
- (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false);
log_rule_debug(dev, rules, "Importing properties from '%s'", buf);
f = fopen(buf, "re");
}
case TK_M_IMPORT_PROGRAM: {
_cleanup_strv_free_ char **lines = NULL;
- char buf[UDEV_PATH_SIZE], result[UDEV_LINE_SIZE], **line;
+ char buf[UDEV_LINE_SIZE], result[UDEV_LINE_SIZE];
+ bool truncated;
+
+ (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated);
+ if (truncated) {
+ log_rule_debug(dev, rules, "The command '%s' is truncated while substituting into '%s', "
+ "assuming the IMPORT key does not match.", buf, token->value);
+ return false;
+ }
- (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false);
log_rule_debug(dev, rules, "Importing properties from results of '%s'", buf);
- r = udev_event_spawn(event, timeout_usec, timeout_signal, true, buf, result, sizeof result);
+ r = udev_event_spawn(event, timeout_usec, timeout_signal, true, buf, result, sizeof result, &truncated);
if (r != 0) {
if (r < 0)
log_rule_warning_errno(dev, rules, r, "Failed to execute '%s', ignoring: %m", buf);
return token->op == OP_NOMATCH;
}
+ if (truncated) {
+ bool found = false;
+
+ /* Drop the last line. */
+ for (char *p = PTR_SUB1(buf + strlen(buf), buf); p; p = PTR_SUB1(p, buf))
+ if (strchr(NEWLINE, *p)) {
+ *p = '\0';
+ found = true;
+ } else if (found)
+ break;
+ }
+
r = strv_split_newlines_full(&lines, result, EXTRACT_RETAIN_ESCAPE);
- if (r < 0)
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
log_rule_warning_errno(dev, rules, r,
"Failed to extract lines from result of command \"%s\", ignoring: %m", buf);
+ return false;
+ }
STRV_FOREACH(line, lines) {
char *key, *value;
UdevBuiltinCommand cmd = PTR_TO_UDEV_BUILTIN_CMD(token->data);
assert(cmd >= 0 && cmd < _UDEV_BUILTIN_MAX);
unsigned mask = 1U << (int) cmd;
- char buf[UDEV_PATH_SIZE];
+ char buf[UDEV_LINE_SIZE];
+ bool truncated;
if (udev_builtin_run_once(cmd)) {
/* check if we ran already */
event->builtin_run |= mask;
}
- (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false);
+ (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated);
+ if (truncated) {
+ log_rule_debug(dev, rules, "The builtin command '%s' is truncated while substituting into '%s', "
+ "assuming the IMPORT key does not match", buf, token->value);
+ return false;
+ }
+
log_rule_debug(dev, rules, "Importing properties from results of builtin command '%s'", buf);
- r = udev_builtin_run(dev, cmd, buf, false);
+ r = udev_builtin_run(dev, &event->rtnl, cmd, buf, false);
if (r < 0) {
/* remember failure */
log_rule_debug_errno(dev, rules, r, "Failed to run builtin '%s': %m", buf);
}
case TK_M_IMPORT_PARENT: {
char buf[UDEV_PATH_SIZE];
+ bool truncated;
+
+ (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated);
+ if (truncated) {
+ log_rule_debug(dev, rules, "The property name '%s' is truncated while substituting into '%s', "
+ "assuming the IMPORT key does not match.", buf, token->value);
+ return false;
+ }
- (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false);
r = import_parent_into_properties(dev, buf);
if (r < 0)
return log_rule_error_errno(dev, rules, r,
case TK_A_OWNER: {
char owner[UDEV_NAME_SIZE];
const char *ow = owner;
+ bool truncated;
if (event->owner_final)
break;
if (token->op == OP_ASSIGN_FINAL)
event->owner_final = true;
- (void) udev_event_apply_format(event, token->value, owner, sizeof(owner), false);
+ (void) udev_event_apply_format(event, token->value, owner, sizeof(owner), false, &truncated);
+ if (truncated) {
+ log_rule_warning(dev, rules, "The user name '%s' is truncated while substituting into '%s', "
+ "refusing to apply the OWNER key.", owner, token->value);
+ break;
+ }
+
r = get_user_creds(&ow, &event->uid, NULL, NULL, NULL, USER_CREDS_ALLOW_MISSING);
if (r < 0)
log_unknown_owner(dev, rules, r, "user", owner);
case TK_A_GROUP: {
char group[UDEV_NAME_SIZE];
const char *gr = group;
+ bool truncated;
if (event->group_final)
break;
if (token->op == OP_ASSIGN_FINAL)
event->group_final = true;
- (void) udev_event_apply_format(event, token->value, group, sizeof(group), false);
+ (void) udev_event_apply_format(event, token->value, group, sizeof(group), false, &truncated);
+ if (truncated) {
+ log_rule_warning(dev, rules, "The group name '%s' is truncated while substituting into '%s', "
+ "refusing to apply the GROUP key.", group, token->value);
+ break;
+ }
+
r = get_group_creds(&gr, &event->gid, USER_CREDS_ALLOW_MISSING);
if (r < 0)
log_unknown_owner(dev, rules, r, "group", group);
}
case TK_A_MODE: {
char mode_str[UDEV_NAME_SIZE];
+ bool truncated;
if (event->mode_final)
break;
if (token->op == OP_ASSIGN_FINAL)
event->mode_final = true;
- (void) udev_event_apply_format(event, token->value, mode_str, sizeof(mode_str), false);
+ (void) udev_event_apply_format(event, token->value, mode_str, sizeof(mode_str), false, &truncated);
+ if (truncated) {
+ log_rule_warning(dev, rules, "The mode '%s' is truncated while substituting into %s, "
+ "refusing to apply the MODE key.", mode_str, token->value);
+ break;
+ }
+
r = parse_mode(mode_str, &event->mode);
if (r < 0)
log_rule_error_errno(dev, rules, r, "Failed to parse mode '%s', ignoring: %m", mode_str);
case TK_A_SECLABEL: {
_cleanup_free_ char *name = NULL, *label = NULL;
char label_str[UDEV_LINE_SIZE] = {};
+ bool truncated;
name = strdup(token->data);
if (!name)
return log_oom();
- (void) udev_event_apply_format(event, token->value, label_str, sizeof(label_str), false);
+ (void) udev_event_apply_format(event, token->value, label_str, sizeof(label_str), false, &truncated);
+ if (truncated) {
+ log_rule_warning(dev, rules, "The security label '%s' is truncated while substituting into '%s', "
+ "refusing to apply the SECLABEL key.", label_str, token->value);
+ break;
+ }
+
if (!isempty(label_str))
label = strdup(label_str);
else
if (r == -ENOMEM)
return log_oom();
if (r < 0)
- return log_rule_error_errno(dev, rules, r, "Failed to store SECLABEL{%s}='%s': %m", name, label);;
+ return log_rule_error_errno(dev, rules, r, "Failed to store SECLABEL{%s}='%s': %m", name, label);
log_rule_debug(dev, rules, "SECLABEL{%s}='%s'", name, label);
const char *val, *name = token->data;
char value_new[UDEV_NAME_SIZE], *p = value_new;
size_t count, l = sizeof(value_new);
+ bool truncated;
if (isempty(token->value)) {
if (token->op == OP_ADD)
}
if (token->op == OP_ADD &&
- sd_device_get_property_value(dev, name, &val) >= 0)
- l = strpcpyl(&p, l, val, " ", NULL);
+ sd_device_get_property_value(dev, name, &val) >= 0) {
+ l = strpcpyl_full(&p, l, &truncated, val, " ", NULL);
+ if (truncated) {
+ log_rule_warning(dev, rules, "The buffer for the property '%s' is full, "
+ "refusing to append the new value '%s'.", name, token->value);
+ break;
+ }
+ }
+
+ (void) udev_event_apply_format(event, token->value, p, l, false, &truncated);
+ if (truncated) {
+ log_rule_warning(dev, rules, "The property value '%s' is truncated while substituting into '%s', "
+ "refusing to add property '%s'.", p, token->value, name);
+ break;
+ }
- (void) udev_event_apply_format(event, token->value, p, l, false);
if (event->esc == ESCAPE_REPLACE) {
count = udev_replace_chars(p, NULL);
if (count > 0)
}
case TK_A_TAG: {
char buf[UDEV_PATH_SIZE];
+ bool truncated;
+
+ (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated);
+ if (truncated) {
+ log_rule_warning(dev, rules, "The tag name '%s' is truncated while substituting into '%s',"
+ "refusing to %s the tag.", buf, token->value,
+ token->op == OP_REMOVE ? "remove" : "add");
+ break;
+ }
- (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false);
if (token->op == OP_ASSIGN)
device_cleanup_tags(dev);
}
case TK_A_NAME: {
char buf[UDEV_PATH_SIZE];
+ bool truncated;
size_t count;
if (event->name_final)
break;
}
- (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false);
+ (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated);
+ if (truncated) {
+ log_rule_warning(dev, rules, "The network interface name '%s' is truncated while substituting into '%s', "
+ "refusing to set the name.", buf, token->value);
+ break;
+ }
+
if (IN_SET(event->esc, ESCAPE_UNSET, ESCAPE_REPLACE)) {
if (naming_scheme_has(NAMING_REPLACE_STRICTLY))
count = udev_replace_ifname(buf);
}
case TK_A_DEVLINK: {
char buf[UDEV_PATH_SIZE], *p;
+ bool truncated;
size_t count;
if (event->devlink_final)
device_cleanup_devlinks(dev);
/* allow multiple symlinks separated by spaces */
- (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), event->esc != ESCAPE_NONE);
+ (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), event->esc != ESCAPE_NONE, &truncated);
+ if (truncated) {
+ log_rule_warning(dev, rules, "The symbolic link path '%s' is truncated while substituting into '%s', "
+ "refusing to add the device symbolic link.", buf, token->value);
+ break;
+ }
+
if (event->esc == ESCAPE_UNSET)
count = udev_replace_chars(buf, "/ ");
else if (event->esc == ESCAPE_REPLACE)
next = skip_leading_chars(next, NULL);
}
- strscpyl(filename, sizeof(filename), "/dev/", p, NULL);
+ strscpyl_full(filename, sizeof(filename), &truncated, "/dev/", p, NULL);
+ if (truncated)
+ continue;
+
r = device_add_devlink(dev, filename);
if (r < 0)
return log_rule_error_errno(dev, rules, r, "Failed to add devlink '%s': %m", filename);
case TK_A_ATTR: {
char buf[UDEV_PATH_SIZE], value[UDEV_NAME_SIZE];
const char *val, *key_name = token->data;
+ bool truncated;
if (udev_resolve_subsys_kernel(key_name, buf, sizeof(buf), false) < 0 &&
- sd_device_get_syspath(dev, &val) >= 0)
- strscpyl(buf, sizeof(buf), val, "/", key_name, NULL);
+ sd_device_get_syspath(dev, &val) >= 0) {
+ strscpyl_full(buf, sizeof(buf), &truncated, val, "/", key_name, NULL);
+ if (truncated) {
+ log_rule_warning(dev, rules,
+ "The path to the attribute '%s/%s' is too long, refusing to set the attribute.",
+ val, key_name);
+ break;
+ }
+ }
r = attr_subst_subdir(buf);
if (r < 0) {
log_rule_error_errno(dev, rules, r, "Could not find file matches '%s', ignoring: %m", buf);
break;
}
- (void) udev_event_apply_format(event, token->value, value, sizeof(value), false);
+ (void) udev_event_apply_format(event, token->value, value, sizeof(value), false, &truncated);
+ if (truncated) {
+ log_rule_warning(dev, rules, "The attribute value '%s' is truncated while substituting into '%s', "
+ "refusing to set the attribute '%s'", value, token->value, buf);
+ break;
+ }
log_rule_debug(dev, rules, "ATTR '%s' writing '%s'", buf, value);
r = write_string_file(buf, value,
}
case TK_A_SYSCTL: {
char buf[UDEV_PATH_SIZE], value[UDEV_NAME_SIZE];
+ bool truncated;
+
+ (void) udev_event_apply_format(event, token->data, buf, sizeof(buf), false, &truncated);
+ if (truncated) {
+ log_rule_warning(dev, rules, "The sysctl entry name '%s' is truncated while substituting into '%s', "
+ "refusing to set the sysctl entry.", buf, (const char*) token->data);
+ break;
+ }
+
+ (void) udev_event_apply_format(event, token->value, value, sizeof(value), false, &truncated);
+ if (truncated) {
+ log_rule_warning(dev, rules, "The sysctl value '%s' is truncated while substituting into '%s', "
+ "refusing to set the sysctl entry '%s'", value, token->value, buf);
+ break;
+ }
- (void) udev_event_apply_format(event, token->data, buf, sizeof(buf), false);
- (void) udev_event_apply_format(event, token->value, value, sizeof(value), false);
sysctl_normalize(buf);
log_rule_debug(dev, rules, "SYSCTL '%s' writing '%s'", buf, value);
r = sysctl_write(buf, value);
case TK_A_RUN_BUILTIN:
case TK_A_RUN_PROGRAM: {
_cleanup_free_ char *cmd = NULL;
- char buf[UDEV_PATH_SIZE];
+ char buf[UDEV_LINE_SIZE];
+ bool truncated;
if (event->run_final)
break;
if (IN_SET(token->op, OP_ASSIGN, OP_ASSIGN_FINAL))
ordered_hashmap_clear_free_key(event->run_list);
- (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false);
+ (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated);
+ if (truncated) {
+ log_rule_warning(dev, rules, "The command '%s' is truncated while substituting into '%s', "
+ "refusing to invoke the command.", buf, token->value);
+ break;
+ }
cmd = strdup(buf);
if (!cmd)
UdevRuleToken *head;
int r;
- line = rules->current_file->current_line;
- head = rules->current_file->current_line->current_token;
- event->dev_parent = event->dev;
+ assert(rules);
+ assert(rules->current_file);
+ assert(event);
+
+ line = ASSERT_PTR(rules->current_file->current_line);
+ head = ASSERT_PTR(rules->current_file->current_line->current_token);
+ event->dev_parent = ASSERT_PTR(event->dev);
+
for (;;) {
- LIST_FOREACH(tokens, line->current_token, head) {
- if (!token_is_for_parents(line->current_token))
+ LIST_FOREACH(tokens, token, head) {
+ if (!token_is_for_parents(token))
return true; /* All parent tokens match. */
+
+ line->current_token = token;
r = udev_rule_apply_token_to_event(rules, event->dev_parent, event, 0, timeout_signal, NULL);
if (r < 0)
return r;
if (r == 0)
break;
}
- if (!line->current_token)
- /* All parent tokens match. But no assign tokens in the line. Hmm... */
+ if (r > 0)
+ /* All parent tokens match, and no more token (except for GOTO) in the line. */
return true;
if (sd_device_get_parent(event->dev_parent, &event->dev_parent) < 0) {
UdevRuleLine *line = rules->current_file->current_line;
UdevRuleLineType mask = LINE_HAS_GOTO | LINE_UPDATE_SOMETHING;
- UdevRuleToken *token, *next_token;
bool parents_done = false;
sd_device_action_t action;
int r;
DEVICE_TRACE_POINT(rules_apply_line, event->dev, line->rule_file->filename, line->line_number);
- LIST_FOREACH_SAFE(tokens, token, next_token, line->tokens) {
+ LIST_FOREACH(tokens, token, line->tokens) {
line->current_token = token;
if (token_is_for_parents(token)) {
int timeout_signal,
Hashmap *properties_list) {
- UdevRuleFile *file;
- UdevRuleLine *next_line;
int r;
assert(rules);
LIST_FOREACH(rule_files, file, rules->rule_files) {
rules->current_file = file;
- LIST_FOREACH_SAFE(rule_lines, file->current_line, next_line, file->rule_lines) {
+ LIST_FOREACH_WITH_NEXT(rule_lines, line, next_line, file->rule_lines) {
+ file->current_line = line;
r = udev_rule_apply_line_to_event(rules, event, timeout_usec, timeout_signal, properties_list, &next_line);
if (r < 0)
return r;
return 0;
}
-static int apply_static_dev_perms(const char *devnode, uid_t uid, gid_t gid, mode_t mode, char **tags) {
- char device_node[UDEV_PATH_SIZE], tags_dir[UDEV_PATH_SIZE], tag_symlink[UDEV_PATH_SIZE];
- _cleanup_free_ char *unescaped_filename = NULL;
- struct stat stats;
- char **t;
- int r;
-
- assert(devnode);
-
- if (uid == UID_INVALID && gid == GID_INVALID && mode == MODE_INVALID && !tags)
- return 0;
-
- strscpyl(device_node, sizeof(device_node), "/dev/", devnode, NULL);
- if (stat(device_node, &stats) < 0) {
- if (errno != ENOENT)
- return log_error_errno(errno, "Failed to stat %s: %m", device_node);
- return 0;
- }
-
- if (!S_ISBLK(stats.st_mode) && !S_ISCHR(stats.st_mode)) {
- log_warning("%s is neither block nor character device, ignoring.", device_node);
- return 0;
- }
-
- if (!strv_isempty(tags)) {
- unescaped_filename = xescape(devnode, "/.");
- if (!unescaped_filename)
- return log_oom();
- }
-
- /* export the tags to a directory as symlinks, allowing otherwise dead nodes to be tagged */
- STRV_FOREACH(t, tags) {
- strscpyl(tags_dir, sizeof(tags_dir), "/run/udev/static_node-tags/", *t, "/", NULL);
- r = mkdir_p(tags_dir, 0755);
- if (r < 0)
- return log_error_errno(r, "Failed to create %s: %m", tags_dir);
-
- strscpyl(tag_symlink, sizeof(tag_symlink), tags_dir, unescaped_filename, NULL);
- r = symlink(device_node, tag_symlink);
- if (r < 0 && errno != EEXIST)
- return log_error_errno(errno, "Failed to create symlink %s -> %s: %m",
- tag_symlink, device_node);
- }
-
- /* don't touch the permissions if only the tags were set */
- if (uid == UID_INVALID && gid == GID_INVALID && mode == MODE_INVALID)
- return 0;
-
- if (mode == MODE_INVALID)
- mode = gid_is_valid(gid) ? 0660 : 0600;
- if (!uid_is_valid(uid))
- uid = 0;
- if (!gid_is_valid(gid))
- gid = 0;
-
- r = chmod_and_chown(device_node, mode, uid, gid);
- if (r == -ENOENT)
- return 0;
- if (r < 0)
- return log_error_errno(r, "Failed to chown '%s' %u %u: %m", device_node, uid, gid);
- else
- log_debug("chown '%s' %u:%u with mode %#o", device_node, uid, gid, mode);
-
- (void) utimensat(AT_FDCWD, device_node, NULL, 0);
- return 0;
-}
-
static int udev_rule_line_apply_static_dev_perms(UdevRuleLine *rule_line) {
- UdevRuleToken *token;
_cleanup_strv_free_ char **tags = NULL;
uid_t uid = UID_INVALID;
gid_t gid = GID_INVALID;
if (r < 0)
return log_oom();
} else if (token->type == TK_A_OPTIONS_STATIC_NODE) {
- r = apply_static_dev_perms(token->value, uid, gid, mode, tags);
+ r = static_node_apply_permissions(token->value, mode, uid, gid, tags);
if (r < 0)
return r;
}
}
int udev_rules_apply_static_dev_perms(UdevRules *rules) {
- UdevRuleFile *file;
- UdevRuleLine *line;
int r;
assert(rules);