+
+static int match_key(struct udev_rules *rules, struct token *token, const char *val)
+{
+ const char *key_name = token_str[token->type];
+ char *key_value = &rules->buf[token->key.value_off];
+ char *pos;
+ int match = 0;
+
+ if (val == NULL)
+ val = "";
+
+ /* look for a matching string, parts are separated by '|' */
+ if (strchr(key_value, '|') != NULL) {
+ char value[UTIL_PATH_SIZE];
+
+ util_strlcpy(value, &rules->buf[token->key.value_off], sizeof(value));
+ key_value = value;
+ while (key_value != NULL) {
+ pos = strchr(key_value, '|');
+ if (pos != NULL) {
+ pos[0] = '\0';
+ pos = &pos[1];
+ }
+ dbg(rules->udev, "match %s '%s' <-> '%s'\n", key_name, key_value, val);
+ match = (fnmatch(key_value, val, 0) == 0);
+ if (match)
+ break;
+ key_value = pos;
+ }
+ } else {
+ match = (fnmatch(key_value, val, 0) == 0);
+ }
+
+ if (match && (token->key.op == KEY_OP_MATCH)) {
+ dbg(rules->udev, "%s is true (matching value)\n", key_name);
+ return 0;
+ }
+ if (!match && (token->key.op == KEY_OP_NOMATCH)) {
+ dbg(rules->udev, "%s is true (non-matching value)\n", key_name);
+ return 0;
+ }
+ dbg(rules->udev, "%s is not true\n", key_name);
+ return -1;
+}
+
+static int match_attr(struct udev_rules *rules, struct udev_device *dev, struct udev_event *event, struct token *cur)
+{
+ char attr[UTIL_PATH_SIZE];
+ const char *key_name = &rules->buf[cur->key.attr_off];
+ const char *key_value = &rules->buf[cur->key.value_off];
+ char value[UTIL_NAME_SIZE] = "";
+ size_t len;
+
+ util_strlcpy(attr, key_name, sizeof(attr));
+ util_resolve_subsys_kernel(event->udev, attr, value, sizeof(value), 1);
+ if (value[0] == '\0') {
+ const char *val;
+
+ val = udev_device_get_sysattr_value(dev, key_name);
+ if (val != NULL)
+ util_strlcpy(value, val, sizeof(value));
+ }
+ if (value[0]=='\0')
+ return -1;
+
+ /* strip trailing whitespace of value, if not asked to match for it */
+ len = strlen(key_value);
+ if (len > 0 && !isspace(key_value[len-1])) {
+ len = strlen(value);
+ while (len > 0 && isspace(value[--len]))
+ value[len] = '\0';
+ dbg(rules->udev, "removed trailing whitespace from '%s'\n", value);
+ }
+ return match_key(rules, cur, value);
+}
+
+enum escape_type {
+ ESCAPE_UNSET,
+ ESCAPE_NONE,
+ ESCAPE_REPLACE,
+};
+
+int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event)
+{
+ struct token *cur;
+ struct token *rule;
+
+ if (rules->tokens == NULL)
+ return -1;
+
+ /* loop through token list, match, run actions or forward to next rule */
+ cur = &rules->tokens[0];
+ rule = cur;
+ while (cur != NULL && cur->type != TK_END) {
+ enum escape_type esc = ESCAPE_UNSET;
+ unsigned int idx;
+
+ dump_token(rules, cur);
+ switch (cur->type) {
+ case TK_RULE:
+ /* current rule */
+ rule = cur;
+ esc = ESCAPE_UNSET;
+ break;
+ case TK_M_ACTION:
+ if (match_key(rules, cur, udev_device_get_action(event->dev)) != 0)
+ goto nomatch;
+ break;
+ case TK_M_DEVPATH:
+ if (match_key(rules, cur, udev_device_get_devpath(event->dev)) != 0)
+ goto nomatch;
+ break;
+ case TK_M_KERNEL:
+ if (match_key(rules, cur, udev_device_get_sysname(event->dev)) != 0)
+ goto nomatch;
+ break;
+ case TK_M_DEVLINK:
+ {
+ size_t devlen = strlen(udev_get_dev_path(event->udev))+1;
+ struct udev_list_entry *list_entry;
+ int match = 0;
+
+ udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(event->dev)) {
+ const char *devlink;
+
+ devlink = &udev_list_entry_get_name(list_entry)[devlen];
+ if (match_key(rules, cur, devlink) == 0) {
+ match = 1;
+ break;
+ }
+ }
+ if (!match)
+ goto nomatch;
+ break;
+ }
+ case TK_M_NAME:
+ if (match_key(rules, cur, event->name) != 0)
+ goto nomatch;
+ break;
+ case TK_M_ENV:
+ {
+ struct udev_list_entry *list_entry;
+ const char *key_name = &rules->buf[cur->key.attr_off];
+ const char *value;
+
+ list_entry = udev_device_get_properties_list_entry(event->dev);
+ list_entry = udev_list_entry_get_by_name(list_entry, key_name);
+ value = udev_list_entry_get_value(list_entry);
+ if (value == NULL) {
+ dbg(event->udev, "ENV{%s} is not set, treat as empty\n", key_name);
+ value = "";
+ }
+ if (match_key(rules, cur, value))
+ goto nomatch;
+ break;
+ }
+ case TK_M_SUBSYSTEM:
+ if (match_key(rules, cur, udev_device_get_subsystem(event->dev)) != 0)
+ goto nomatch;
+ break;
+ case TK_M_DRIVER:
+ if (match_key(rules, cur, udev_device_get_driver(event->dev)) != 0)
+ goto nomatch;
+ break;
+ case TK_M_WAITFOR:
+ {
+ char filename[UTIL_PATH_SIZE];
+ int found;
+
+ util_strlcpy(filename, &rules->buf[cur->key.value_off], sizeof(filename));
+ udev_event_apply_format(event, filename, sizeof(filename));
+ found = (wait_for_file(event->dev, filename, 10) == 0);
+ if (!found && (cur->key.op != KEY_OP_NOMATCH))
+ goto nomatch;
+ break;
+ }
+ case TK_M_ATTR:
+ if (match_attr(rules, event->dev, event, cur) != 0)
+ goto nomatch;
+ break;
+ case TK_M_KERNELS:
+ case TK_M_SUBSYSTEMS:
+ case TK_M_DRIVERS:
+ case TK_M_ATTRS:
+ {
+ struct token *next;
+
+ /* get whole sequence of parent matches */
+ next = cur;
+ while (next->type < TK_PARENTS_MAX)
+ next++;
+
+ /* loop over parents */
+ event->dev_parent = event->dev;
+ while (1) {
+ struct token *key;
+
+ dbg(event->udev, "parent: '%s'\n", udev_device_get_syspath(event->dev_parent));
+ /* loop over sequence of parent match keys */
+ for (key = cur; key < next; key++ ) {
+ dump_token(rules, key);
+ switch(key->type) {
+ case TK_M_KERNELS:
+ if (match_key(rules, key, udev_device_get_sysname(event->dev_parent)) != 0)
+ goto try_parent;
+ break;
+ case TK_M_SUBSYSTEMS:
+ if (match_key(rules, key, udev_device_get_subsystem(event->dev_parent)) != 0)
+ goto try_parent;
+ break;
+ case TK_M_DRIVERS:
+ if (match_key(rules, key, udev_device_get_driver(event->dev_parent)) != 0)
+ goto try_parent;
+ break;
+ case TK_M_ATTRS:
+ if (match_attr(rules, event->dev_parent, event, key) != 0)
+ goto try_parent;
+ break;
+ default:
+ goto nomatch;
+ }
+ dbg(event->udev, "parent key matched\n");
+ }
+ dbg(event->udev, "all parent keys matched\n");
+ /* all keys matched */
+ break;
+
+ try_parent:
+ event->dev_parent = udev_device_get_parent(event->dev_parent);
+ if (event->dev_parent == NULL)
+ goto nomatch;
+ }
+ /* move behind our sequence of parent match keys */
+ cur = next;
+ continue;
+ }
+ case TK_M_TEST:
+ {
+ char filename[UTIL_PATH_SIZE];
+ struct stat statbuf;
+ int match;
+
+ util_strlcpy(filename, &rules->buf[cur->key.value_off], sizeof(filename));
+ udev_event_apply_format(event, filename, sizeof(filename));
+ if (util_resolve_subsys_kernel(event->udev, NULL, filename, sizeof(filename), 0) != 0)
+ if (filename[0] != '/') {
+ char tmp[UTIL_PATH_SIZE];
+
+ util_strlcpy(tmp, udev_device_get_syspath(event->dev), sizeof(tmp));
+ util_strlcat(tmp, "/", sizeof(tmp));
+ util_strlcat(tmp, filename, sizeof(tmp));
+ util_strlcpy(filename, tmp, sizeof(filename));
+ }
+
+ attr_subst_subdir(filename, sizeof(filename));
+
+ match = (stat(filename, &statbuf) == 0);
+ info(event->udev, "'%s' %s", filename, match ? "exists\n" : "does not exist\n");
+ if (match && cur->key.mode > 0) {
+ match = ((statbuf.st_mode & cur->key.mode) > 0);
+ info(event->udev, "'%s' has mode=%#o and %s %#o\n", filename, statbuf.st_mode,
+ match ? "matches" : "does not match", cur->key.mode);
+ }
+ if (match && cur->key.op == KEY_OP_NOMATCH)
+ goto nomatch;
+ if (!match && cur->key.op == KEY_OP_MATCH)
+ goto nomatch;
+ break;
+ }
+ case TK_M_PROGRAM:
+ {
+ char program[UTIL_PATH_SIZE];
+ char **envp;
+ char result[UTIL_PATH_SIZE];
+
+ free(event->program_result);
+ event->program_result = NULL;
+ util_strlcpy(program, &rules->buf[cur->key.value_off], sizeof(program));
+ udev_event_apply_format(event, program, sizeof(program));
+ envp = udev_device_get_properties_envp(event->dev);
+ if (util_run_program(event->udev, program, envp, result, sizeof(result), NULL) != 0) {
+ if (cur->key.op != KEY_OP_NOMATCH)
+ goto nomatch;
+ } else {
+ int count;
+
+ util_remove_trailing_chars(result, '\n');
+ if (esc == ESCAPE_UNSET || esc == ESCAPE_REPLACE) {
+ count = util_replace_chars(result, ALLOWED_CHARS_INPUT);
+ if (count > 0)
+ info(event->udev, "%i character(s) replaced\n" , count);
+ }
+ event->program_result = strdup(result);
+ dbg(event->udev, "storing result '%s'\n", event->program_result);
+ if (cur->key.op == KEY_OP_NOMATCH)
+ goto nomatch;
+ }
+ break;
+ }
+ case TK_M_IMPORT_FILE:
+ {
+ char import[UTIL_PATH_SIZE];
+
+ util_strlcpy(import, &rules->buf[cur->key.value_off], sizeof(import));
+ udev_event_apply_format(event, import, sizeof(import));
+ if (import_file_into_properties(event->dev, import) != 0)
+ if (cur->key.op != KEY_OP_NOMATCH)
+ goto nomatch;
+ break;
+ }
+ case TK_M_IMPORT_PROG:
+ {
+ char import[UTIL_PATH_SIZE];
+
+ util_strlcpy(import, &rules->buf[cur->key.value_off], sizeof(import));
+ udev_event_apply_format(event, import, sizeof(import));
+ if (import_program_into_properties(event->dev, import) != 0)
+ if (cur->key.op != KEY_OP_NOMATCH)
+ goto nomatch;
+ break;
+ }
+ case TK_M_IMPORT_PARENT:
+ {
+ char import[UTIL_PATH_SIZE];
+
+ util_strlcpy(import, &rules->buf[cur->key.value_off], sizeof(import));
+ udev_event_apply_format(event, import, sizeof(import));
+ if (import_parent_into_properties(event->dev, import) != 0)
+ if (cur->key.op != KEY_OP_NOMATCH)
+ goto nomatch;
+ break;
+ }
+ case TK_M_RESULT:
+ if (match_key(rules, cur, event->program_result) != 0)
+ goto nomatch;
+ break;
+
+ case TK_A_IGNORE_DEVICE:
+ event->ignore_device = 1;
+ return 0;
+ break;
+ case TK_A_STRING_ESCAPE_NONE:
+ esc = ESCAPE_NONE;
+ break;
+ case TK_A_STRING_ESCAPE_REPLACE:
+ esc = ESCAPE_REPLACE;
+ break;
+ case TK_A_NUM_FAKE_PART:
+ if (strcmp(udev_device_get_subsystem(event->dev), "block") != 0)
+ break;
+ if (udev_device_get_sysnum(event->dev) != NULL)
+ break;
+ udev_device_set_num_fake_partitions(event->dev, cur->key.num_fake_part);
+ break;
+ case TK_A_DEVLINK_PRIO:
+ udev_device_set_devlink_priority(event->dev, cur->key.devlink_prio);
+ break;
+ case TK_A_OWNER:
+ {
+ char owner[UTIL_NAME_SIZE];
+
+ if (event->owner_final)
+ break;
+ if (cur->key.op == KEY_OP_ASSIGN_FINAL)
+ event->owner_final = 1;
+ util_strlcpy(owner, &rules->buf[cur->key.value_off], sizeof(owner));
+ udev_event_apply_format(event, owner, sizeof(owner));
+ event->uid = util_lookup_user(event->udev, owner);
+ break;
+ }
+ case TK_A_GROUP:
+ {
+ char group[UTIL_NAME_SIZE];
+
+ if (event->group_final)
+ break;
+ if (cur->key.op == KEY_OP_ASSIGN_FINAL)
+ event->group_final = 1;
+ util_strlcpy(group, &rules->buf[cur->key.value_off], sizeof(group));
+ udev_event_apply_format(event, group, sizeof(group));
+ event->gid = util_lookup_group(event->udev, group);
+ break;
+ }
+ case TK_A_MODE:
+ {
+ char mode[UTIL_NAME_SIZE];
+ char *endptr;
+
+ if (event->mode_final)
+ break;
+ if (cur->key.op == KEY_OP_ASSIGN_FINAL)
+ event->mode_final = 1;
+ util_strlcpy(mode, &rules->buf[cur->key.value_off], sizeof(mode));
+ udev_event_apply_format(event, mode, sizeof(mode));
+ event->mode = strtol(mode, &endptr, 8);
+ if (endptr[0] != '\0') {
+ err(event->udev, "invalide mode '%s' set default mode 0660\n", mode);
+ event->mode = 0660;
+ }
+ break;
+ }
+ case TK_A_OWNER_ID:
+ if (event->owner_final)
+ break;
+ if (cur->key.op == KEY_OP_ASSIGN_FINAL)
+ event->owner_final = 1;
+ event->uid = cur->key.uid;
+ break;
+ case TK_A_GROUP_ID:
+ if (event->group_final)
+ break;
+ if (cur->key.op == KEY_OP_ASSIGN_FINAL)
+ event->group_final = 1;
+ event->gid = cur->key.gid;
+ break;
+ case TK_A_MODE_ID:
+ if (event->mode_final)
+ break;
+ if (cur->key.op == KEY_OP_ASSIGN_FINAL)
+ event->mode_final = 1;
+ event->mode = cur->key.mode;
+ break;
+ case TK_A_ENV:
+ {
+ const char *name = &rules->buf[cur->key.attr_off];
+ char *value = &rules->buf[cur->key.value_off];
+
+ if (value[0] != '\0') {
+ char temp_value[UTIL_NAME_SIZE];
+ struct udev_list_entry *entry;
+
+ util_strlcpy(temp_value, value, sizeof(temp_value));
+ udev_event_apply_format(event, temp_value, sizeof(temp_value));
+ entry = udev_device_add_property(event->dev, name, temp_value);
+ /* store in db */
+ udev_list_entry_set_flag(entry, 1);
+ } else {
+ udev_device_add_property(event->dev, name, NULL);
+ }
+ break;
+ }
+ case TK_A_NAME:
+ {
+ const char *name = &rules->buf[cur->key.value_off];
+ char name_str[UTIL_PATH_SIZE];
+ int count;
+
+ if (event->name_final)
+ break;
+ if (cur->key.op == KEY_OP_ASSIGN_FINAL)
+ event->name_final = 1;
+ if (name[0] == '\0') {
+ free(event->name);
+ event->name = NULL;
+ break;
+ }
+ util_strlcpy(name_str, name, sizeof(name_str));
+ udev_event_apply_format(event, name_str, sizeof(name_str));
+ if (esc == ESCAPE_UNSET || esc == ESCAPE_REPLACE) {
+ count = util_replace_chars(name_str, ALLOWED_CHARS_FILE);
+ if (count > 0)
+ info(event->udev, "%i character(s) replaced\n", count);
+ free(event->name);
+ event->name = strdup(name_str);
+ }
+ break;
+ }
+ case TK_A_DEVLINK:
+ {
+ char temp[UTIL_PATH_SIZE];
+ char filename[UTIL_PATH_SIZE];
+ char *pos, *next;
+ int count = 0;
+
+ if (event->devlink_final)
+ break;
+ if (major(udev_device_get_devnum(event->dev)) == 0)
+ break;
+ if (cur->key.op == KEY_OP_ASSIGN_FINAL)
+ event->devlink_final = 1;
+ if (cur->key.op == KEY_OP_ASSIGN || cur->key.op == KEY_OP_ASSIGN_FINAL)
+ udev_device_cleanup_devlinks_list(event->dev);
+
+ /* allow multiple symlinks separated by spaces */
+ util_strlcpy(temp, &rules->buf[cur->key.value_off], sizeof(temp));
+ udev_event_apply_format(event, temp, sizeof(temp));
+ if (esc == ESCAPE_UNSET)
+ count = util_replace_chars(temp, ALLOWED_CHARS_FILE " ");
+ else if (esc == ESCAPE_REPLACE)
+ count = util_replace_chars(temp, ALLOWED_CHARS_FILE);
+ if (count > 0)
+ info(event->udev, "%i character(s) replaced\n" , count);
+ dbg(event->udev, "rule applied, added symlink(s) '%s'\n", temp);
+ pos = temp;
+ while (isspace(pos[0]))
+ pos++;
+ next = strchr(pos, ' ');
+ while (next) {
+ next[0] = '\0';
+ info(event->udev, "add symlink '%s'\n", pos);
+ util_strlcpy(filename, udev_get_dev_path(event->udev), sizeof(filename));
+ util_strlcat(filename, "/", sizeof(filename));
+ util_strlcat(filename, pos, sizeof(filename));
+ udev_device_add_devlink(event->dev, filename);
+ while (isspace(next[1]))
+ next++;
+ pos = &next[1];
+ next = strchr(pos, ' ');
+ }
+ if (pos[0] != '\0') {
+ info(event->udev, "add symlink '%s'\n", pos);
+ util_strlcpy(filename, udev_get_dev_path(event->udev), sizeof(filename));
+ util_strlcat(filename, "/", sizeof(filename));
+ util_strlcat(filename, pos, sizeof(filename));
+ udev_device_add_devlink(event->dev, filename);
+ }
+ }
+ break;
+ case TK_A_EVENT_TIMEOUT:
+ udev_device_set_event_timeout(event->dev, cur->key.event_timeout);
+ break;
+ case TK_A_IGNORE_REMOVE:
+ udev_device_set_ignore_remove(event->dev, 1);
+ break;
+ case TK_A_ATTR:
+ {
+ const char *key_name = &rules->buf[cur->key.attr_off];
+ char attr[UTIL_PATH_SIZE];
+ char value[UTIL_NAME_SIZE];
+ FILE *f;
+
+ util_strlcpy(attr, key_name, sizeof(attr));
+ if (util_resolve_subsys_kernel(event->udev, key_name, attr, sizeof(attr), 0) != 0) {
+ util_strlcpy(attr, udev_device_get_syspath(event->dev), sizeof(attr));
+ util_strlcat(attr, "/", sizeof(attr));
+ util_strlcat(attr, key_name, sizeof(attr));
+ }
+
+ attr_subst_subdir(attr, sizeof(attr));
+
+ util_strlcpy(value, &rules->buf[cur->key.value_off], sizeof(value));
+ udev_event_apply_format(event, value, sizeof(value));
+ info(event->udev, "writing '%s' to sysfs file '%s'\n", value, attr);
+ f = fopen(attr, "w");
+ if (f != NULL) {
+ if (!event->test)
+ if (fprintf(f, "%s", value) <= 0)
+ err(event->udev, "error writing ATTR{%s}: %m\n", attr);
+ fclose(f);
+ } else {
+ err(event->udev, "error opening ATTR{%s} for writing: %m\n", attr);
+ }
+ break;
+ }
+ case TK_A_RUN:
+ {
+ struct udev_list_entry *list_entry;
+
+ if (cur->key.op == KEY_OP_ASSIGN || cur->key.op == KEY_OP_ASSIGN_FINAL)
+ udev_list_cleanup_entries(event->udev, &event->run_list);
+ list_entry = udev_list_entry_add(event->udev, &event->run_list,
+ &rules->buf[cur->key.value_off], NULL, 1, 0);
+ if (cur->key.ignore_error)
+ udev_list_entry_set_flag(list_entry, 1);
+ break;
+ }
+ case TK_A_GOTO:
+ cur = &rules->tokens[cur->key.rule_goto];
+ continue;
+ case TK_A_LAST_RULE:
+ break;
+
+ case TK_PARENTS_MAX:
+ case TK_END:
+ case TK_UNDEF:
+ err(rules->udev, "wrong type %u\n", cur->type);
+ goto nomatch;
+ }
+
+ cur++;
+ continue;
+ nomatch:
+ /* fast-forward to next rule */
+ idx = rule->rule.next_rule;
+ if (idx == 0)
+ break;
+ dbg(rules->udev, "forward to rule: %u\n", idx);
+ cur = &rules->tokens[idx];
+ }
+ return 0;
+}