#define PREALLOC_TOKEN 2048
#define PREALLOC_STRBUF 32 * 1024
+/* KEY=="", KEY!="", KEY+="", KEY="", KEY:="" */
enum key_operation {
KEY_OP_UNSET,
KEY_OP_MATCH,
[KEY_OP_ASSIGN_FINAL] = "assign-final",
};
+/* tokens of a rule are sorted/handled in this order */
enum token_type {
TK_UNDEF,
TK_RULE,
- TK_M_WAITFOR, /* val */
TK_M_ACTION, /* val */
TK_M_DEVPATH, /* val */
TK_M_KERNEL, /* val */
TK_M_ENV, /* val, attr */
TK_M_SUBSYSTEM, /* val */
TK_M_DRIVER, /* val */
+ TK_M_WAITFOR, /* val */
TK_M_ATTR, /* val, attr */
TK_M_KERNELS, /* val */
[TK_UNDEF] = "UNDEF",
[TK_RULE] = "RULE",
- [TK_M_WAITFOR] = "M WAITFOR",
[TK_M_ACTION] = "M ACTION",
[TK_M_DEVPATH] = "M DEVPATH",
[TK_M_KERNEL] = "M KERNEL",
[TK_M_ENV] = "M ENV",
[TK_M_SUBSYSTEM] = "M SUBSYSTEM",
[TK_M_DRIVER] = "M DRIVER",
+ [TK_M_WAITFOR] = "M WAITFOR",
[TK_M_ATTR] = "M ATTR",
[TK_M_KERNELS] = "M KERNELS",
unsigned int token_cur;
};
+struct uid_gid {
+ unsigned int name_off;
+ union {
+ uid_t uid;
+ gid_t gid;
+ };
+};
+
struct udev_rules {
struct udev *udev;
int resolve_names;
+
+ /* every key in the rules file becomes a token */
struct token *tokens;
unsigned int token_cur;
unsigned int token_max;
+
+ /* all key strings are copied to a single string buffer */
char *buf;
size_t buf_cur;
size_t buf_max;
unsigned int buf_count;
+
+ /* during rule parsing, we cache uid/gid lookup results */
+ struct uid_gid *uids;
+ unsigned int uids_cur;
+ unsigned int uids_max;
+ struct uid_gid *gids;
+ unsigned int gids_cur;
+ unsigned int gids_max;
};
-/* we could lookup and return existing strings, or tails of strings */
+/* NOTE: we could lookup and return existing strings, or tails of strings */
static int add_string(struct udev_rules *rules, const char *str)
{
size_t len = strlen(str)+1;
int off;
+ /* offset 0 is always '\0' */
+ if (str[0] == '\0')
+ return 0;
+
+ /* grow buffer if needed */
if (rules->buf_cur + len+1 >= rules->buf_max) {
char *buf;
unsigned int add;
/* double the buffer size */
add = rules->buf_max;
- if (add < len)
- add = len;
+ if (add < len * 8)
+ add = len * 8;
buf = realloc(rules->buf, rules->buf_max + add);
if (buf == NULL)
static int add_token(struct udev_rules *rules, struct token *token)
{
+ /* grow buffer if needed */
if (rules->token_cur+1 >= rules->token_max) {
struct token *tokens;
unsigned int add;
/* double the buffer size */
add = rules->token_max;
- if (add < 1)
- add = 1;
+ if (add < 8)
+ add = 8;
tokens = realloc(rules->tokens, (rules->token_max + add ) * sizeof(struct token));
if (tokens == NULL)
return 0;
}
+static uid_t add_uid(struct udev_rules *rules, const char *owner)
+{
+ unsigned int i;
+ uid_t uid;
+ unsigned int off;
+
+ /* lookup, if we know it already */
+ for (i = 0; i < rules->uids_cur; i++) {
+ off = rules->uids[i].name_off;
+ if (strcmp(&rules->buf[off], owner) == 0) {
+ uid = rules->uids[i].uid;
+ dbg(rules->udev, "return existing %u for '%s'\n", uid, owner);
+ return uid;
+ }
+ }
+ uid = util_lookup_user(rules->udev, owner);
+
+ /* grow buffer if needed */
+ if (rules->uids_cur+1 >= rules->uids_max) {
+ struct uid_gid *uids;
+ unsigned int add;
+
+ /* double the buffer size */
+ add = rules->uids_max;
+ if (add < 1)
+ add = 8;
+
+ uids = realloc(rules->uids, (rules->uids_max + add ) * sizeof(struct uid_gid));
+ if (uids == NULL)
+ return uid;
+ info(rules->udev, "extend uids from %u to %u\n", rules->uids_max, rules->uids_max + add);
+ rules->uids = uids;
+ rules->uids_max += add;
+ }
+ rules->uids[rules->uids_cur].uid = uid;
+ off = add_string(rules, owner);
+ if (off <= 0)
+ return uid;
+ rules->uids[rules->uids_cur].name_off = off;
+ rules->uids_cur++;
+ return uid;
+}
+
+static gid_t add_gid(struct udev_rules *rules, const char *group)
+{
+ unsigned int i;
+ gid_t gid;
+ unsigned int off;
+
+ /* lookup, if we know it already */
+ for (i = 0; i < rules->gids_cur; i++) {
+ off = rules->gids[i].name_off;
+ if (strcmp(&rules->buf[off], group) == 0) {
+ gid = rules->gids[i].gid;
+ info(rules->udev, "return existing %u for '%s'\n", gid, group);
+ return gid;
+ }
+ }
+ gid = util_lookup_group(rules->udev, group);
+
+ /* grow buffer if needed */
+ if (rules->gids_cur+1 >= rules->gids_max) {
+ struct uid_gid *gids;
+ unsigned int add;
+
+ /* double the buffer size */
+ add = rules->gids_max;
+ if (add < 1)
+ add = 8;
+
+ gids = realloc(rules->gids, (rules->gids_max + add ) * sizeof(struct uid_gid));
+ if (gids == NULL)
+ return gid;
+ info(rules->udev, "extend gids from %u to %u\n", rules->gids_max, rules->gids_max + add);
+ rules->gids = gids;
+ rules->gids_max += add;
+ }
+ rules->gids[rules->gids_cur].gid = gid;
+ off = add_string(rules, group);
+ if (off <= 0)
+ return gid;
+ rules->gids[rules->gids_cur].name_off = off;
+ rules->gids_cur++;
+ return gid;
+}
+
static int import_property_from_string(struct udev_device *dev, char *line)
{
struct udev *udev = udev_device_get_udev(dev);
mode_t mode = 0000;
switch (type) {
- case TK_M_WAITFOR:
case TK_M_ACTION:
case TK_M_DEVPATH:
case TK_M_KERNEL:
case TK_M_SUBSYSTEM:
case TK_M_DRIVER:
+ case TK_M_WAITFOR:
case TK_M_DEVLINK:
case TK_M_NAME:
case TK_M_KERNELS:
&rules->buf[token->rule.label_off]);
break;
}
- case TK_M_WAITFOR:
case TK_M_ACTION:
case TK_M_DEVPATH:
case TK_M_KERNEL:
case TK_M_SUBSYSTEM:
case TK_M_DRIVER:
+ case TK_M_WAITFOR:
case TK_M_DEVLINK:
case TK_M_NAME:
case TK_M_KERNELS:
if (endptr[0] == '\0') {
rule_add_token(&rule_tmp, TK_A_OWNER_ID, op, NULL, &uid);
} else if (rules->resolve_names && strchr("$%", value[0]) == NULL) {
- uid = util_lookup_user(rules->udev, value);
+ uid = add_uid(rules, value);
rule_add_token(&rule_tmp, TK_A_OWNER_ID, op, NULL, &uid);
} else {
rule_add_token(&rule_tmp, TK_A_OWNER, op, value, NULL);
if (endptr[0] == '\0') {
rule_add_token(&rule_tmp, TK_A_GROUP_ID, op, NULL, &gid);
} else if (rules->resolve_names && strchr("$%", value[0]) == NULL) {
- gid = util_lookup_group(rules->udev, value);
+ gid = add_gid(rules, value);
rule_add_token(&rule_tmp, TK_A_GROUP_ID, op, NULL, &gid);
} else {
rule_add_token(&rule_tmp, TK_A_GROUP, op, value, NULL);
/* init token array and string buffer */
rules->tokens = malloc(PREALLOC_TOKEN * sizeof(struct token));
- if (rules->tokens != NULL)
- rules->token_max = PREALLOC_TOKEN;
+ if (rules->tokens == NULL)
+ return NULL;
+ rules->token_max = PREALLOC_TOKEN;
rules->buf = malloc(PREALLOC_STRBUF);
- if (rules->buf != NULL)
- rules->buf_max = PREALLOC_STRBUF;
+ if (rules->buf == NULL)
+ return NULL;
+ rules->buf_max = PREALLOC_STRBUF;
+ /* offset 0 is always '\0' */
+ rules->buf[0] = '\0';
+ rules->buf_cur = 1;
info(udev, "prealloc %zu bytes tokens (%u * %zu bytes), %zu bytes buffer\n",
rules->token_max * sizeof(struct token), rules->token_max, sizeof(struct token), rules->buf_max);
- /* offset 0 in the string buffer is always empty */
- add_string(rules, "");
if (udev_get_rules_path(udev) != NULL) {
/* custom rules location for testing */
end_token.type = TK_END;
add_token(rules, &end_token);
- /* shrink allocate buffers */
+ /* link all TK_RULE tokens to be able to fast-forward to next TK_RULE */
+ prev_rule = 0;
+ for (i = 1; i < rules->token_cur; i++) {
+ if (rules->tokens[i].type == TK_RULE) {
+ rules->tokens[prev_rule].rule.next_rule = i;
+ prev_rule = i;
+ }
+ }
+
+ /* shrink allocated token and string buffer */
if (rules->token_cur < rules->token_max) {
struct token *tokens;
info(udev, "shrunk to %lu bytes tokens (%u * %zu bytes), %zu bytes buffer\n",
rules->token_max * sizeof(struct token), rules->token_max, sizeof(struct token), rules->buf_max);
- /* link all TK_RULE tokens to be able to fast-forward to next TK_RULE */
- prev_rule = 0;
- for (i = 1; i < rules->token_cur; i++) {
- if (rules->tokens[i].type == TK_RULE) {
- rules->tokens[prev_rule].rule.next_rule = i;
- prev_rule = i;
- }
- }
+ /* cleanup uid/gid cache */
+ free(rules->uids);
+ rules->uids = NULL;
+ rules->uids_cur = 0;
+ rules->uids_max = 0;
+ free(rules->gids);
+ rules->gids = NULL;
+ rules->gids_cur = 0;
+ rules->gids_max = 0;
+
dump_rules(rules);
return rules;
}
return;
free(rules->tokens);
free(rules->buf);
+ free(rules->uids);
+ free(rules->gids);
free(rules);
}
int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event)
{
- struct token *rule;
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;
rule = cur;
esc = ESCAPE_UNSET;
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_ACTION:
if (match_key(rules, cur, udev_device_get_action(event->dev)) != 0)
goto nomatch;
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;
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)