#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-netlink.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);
name = nbuf;
_fallthrough_;
case SUBST_TYPE_PLAIN:
- if (device_get_sysattr_value_maybe_from_netlink(dev, &event->rtnl, name, &value) < 0)
+ if (sd_device_get_sysattr_value(dev, name, &value) < 0)
return false;
break;
case SUBST_TYPE_SUBSYS:
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, &truncated);
if (truncated) {
- log_rule_debug(dev, rules, "The command '%s' is trucated while substituting into '%s', "
+ 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_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);
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)) {
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);
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)
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);