chiark / gitweb /
cache uid/gid during rule parsing
[elogind.git] / udev / udev-rules.c
index 19ee06d3798c996a50ce2ae6b3ce6b0e9ff329c0..b19edc5ed68c03fc7e5981628d4e5d2e0f15dea2 100644 (file)
@@ -31,6 +31,7 @@
 #define PREALLOC_TOKEN                 2048
 #define PREALLOC_STRBUF                        32 * 1024
 
+/* KEY=="", KEY!="", KEY+="", KEY="", KEY:="" */
 enum key_operation {
        KEY_OP_UNSET,
        KEY_OP_MATCH,
@@ -48,11 +49,11 @@ static const char *operation_str[] = {
        [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 */
@@ -61,6 +62,7 @@ enum token_type {
        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 */
@@ -104,7 +106,6 @@ static const char *token_str[] = {
        [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",
@@ -113,6 +114,7 @@ static const char *token_str[] = {
        [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",
@@ -187,32 +189,57 @@ struct rule_tmp {
        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)
@@ -231,14 +258,15 @@ static int add_string(struct udev_rules *rules, const char *str)
 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)
@@ -252,6 +280,92 @@ static int add_token(struct udev_rules *rules, struct token *token)
        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);
@@ -591,12 +705,12 @@ static int rule_add_token(struct rule_tmp *rule_tmp, enum token_type type,
        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:
@@ -696,12 +810,12 @@ static void dump_token(struct udev_rules *rules, struct token *token)
                            &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:
@@ -1142,7 +1256,7 @@ static int add_rule(struct udev_rules *rules, char *line,
                        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);
@@ -1159,7 +1273,7 @@ static int add_rule(struct udev_rules *rules, char *line,
                        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);
@@ -1388,15 +1502,18 @@ struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names)
 
        /* 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 */
@@ -1467,7 +1584,16 @@ struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names)
        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;
 
@@ -1489,14 +1615,16 @@ struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names)
        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;
 }
@@ -1507,6 +1635,8 @@ void udev_rules_unref(struct udev_rules *rules)
                return;
        free(rules->tokens);
        free(rules->buf);
+       free(rules->uids);
+       free(rules->gids);
        free(rules);
 }
 
@@ -1593,14 +1723,15 @@ enum escape_type {
 
 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;
@@ -1612,18 +1743,6 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event
                        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;
@@ -1684,6 +1803,18 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event
                        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;
@@ -1970,6 +2101,7 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event
                                        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;
@@ -1983,6 +2115,8 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event
 
                                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)