chiark / gitweb /
do not remove static nodes on module unload
[elogind.git] / udev / udev-rules.c
index c24da0c4974e151922eb0e9a571b21308ca90fd4..6bf2726e1e4d9238398f2649323917efce7551a1 100644 (file)
@@ -136,6 +136,7 @@ enum token_type {
        TK_M_SUBSYSTEMS,                /* val */
        TK_M_DRIVERS,                   /* val */
        TK_M_ATTRS,                     /* val, attr */
+       TK_M_TAGS,                      /* val */
        TK_M_PARENTS_MAX,
 
        TK_M_TEST,                      /* val, mode_t */
@@ -143,6 +144,7 @@ enum token_type {
        TK_M_PROGRAM,                   /* val */
        TK_M_IMPORT_FILE,               /* val */
        TK_M_IMPORT_PROG,               /* val */
+       TK_M_IMPORT_BUILTIN,            /* val */
        TK_M_IMPORT_DB,                 /* val */
        TK_M_IMPORT_CMDLINE,            /* val */
        TK_M_IMPORT_PARENT,             /* val */
@@ -204,6 +206,7 @@ struct token {
                                int devlink_prio;
                                int event_timeout;
                                int watch;
+                               enum udev_builtin_cmd builtin_cmd;
                        };
                } key;
        };
@@ -271,6 +274,7 @@ static const char *token_str(enum token_type type)
                [TK_M_SUBSYSTEMS] =             "M SUBSYSTEMS",
                [TK_M_DRIVERS] =                "M DRIVERS",
                [TK_M_ATTRS] =                  "M ATTRS",
+               [TK_M_TAGS] =                   "M TAGS",
                [TK_M_PARENTS_MAX] =            "M PARENTS_MAX",
 
                [TK_M_TEST] =                   "M TEST",
@@ -278,6 +282,7 @@ static const char *token_str(enum token_type type)
                [TK_M_PROGRAM] =                "M PROGRAM",
                [TK_M_IMPORT_FILE] =            "M IMPORT_FILE",
                [TK_M_IMPORT_PROG] =            "M IMPORT_PROG",
+               [TK_M_IMPORT_BUILTIN] =         "M IMPORT_BUILTIN",
                [TK_M_IMPORT_DB] =              "M IMPORT_DB",
                [TK_M_IMPORT_CMDLINE] =         "M IMPORT_CMDLINE",
                [TK_M_IMPORT_PARENT] =          "M IMPORT_PARENT",
@@ -342,6 +347,7 @@ static void dump_token(struct udev_rules *rules, struct token *token)
        case TK_M_KERNELS:
        case TK_M_SUBSYSTEMS:
        case TK_M_DRIVERS:
+       case TK_M_TAGS:
        case TK_M_PROGRAM:
        case TK_M_IMPORT_FILE:
        case TK_M_IMPORT_PROG:
@@ -358,6 +364,9 @@ static void dump_token(struct udev_rules *rules, struct token *token)
                dbg(rules->udev, "%s %s '%s'(%s)\n",
                    token_str(type), operation_str(op), value, string_glob_str(glob));
                break;
+       case TK_M_IMPORT_BUILTIN:
+               dbg(rules->udev, "%s %i\n", token_str(type), token->key.builtin_cmd);
+               break;
        case TK_M_ATTR:
        case TK_M_ATTRS:
        case TK_M_ENV:
@@ -1013,6 +1022,7 @@ static int rule_add_key(struct rule_tmp *rule_tmp, enum token_type type,
        case TK_M_KERNELS:
        case TK_M_SUBSYSTEMS:
        case TK_M_DRIVERS:
+       case TK_M_TAGS:
        case TK_M_PROGRAM:
        case TK_M_IMPORT_FILE:
        case TK_M_IMPORT_PROG:
@@ -1029,6 +1039,9 @@ static int rule_add_key(struct rule_tmp *rule_tmp, enum token_type type,
        case TK_A_TAG:
                token->key.value_off = add_string(rule_tmp->rules, value);
                break;
+       case TK_M_IMPORT_BUILTIN:
+               token->key.builtin_cmd = *(enum udev_builtin_cmd *)data;
+               break;
        case TK_M_ENV:
        case TK_M_ATTR:
        case TK_M_ATTRS:
@@ -1342,6 +1355,15 @@ static int add_rule(struct udev_rules *rules, char *line,
                        continue;
                }
 
+               if (strcmp(key, "TAGS") == 0) {
+                       if (op > OP_MATCH_MAX) {
+                               err(rules->udev, "invalid TAGS operation\n");
+                               goto invalid;
+                       }
+                       rule_add_key(&rule_tmp, TK_M_TAGS, op, value, NULL);
+                       continue;
+               }
+
                if (strncmp(key, "SYSFS{", sizeof("SYSFS{")-1) == 0) {
                        if (!sysfs_warn) {
                                sysfs_warn = true;
@@ -1372,6 +1394,26 @@ static int add_rule(struct udev_rules *rules, char *line,
                                if (rule_add_key(&rule_tmp, TK_M_ENV, op, value, attr) != 0)
                                        goto invalid;
                        } else {
+                               static const char *blacklist[] = {
+                                       "ACTION",
+                                       "SUBSYSTEM",
+                                       "DEVTYPE",
+                                       "MAJOR",
+                                       "MINOR",
+                                       "DRIVER",
+                                       "IFINDEX",
+                                       "DEVNAME",
+                                       "DEVLINKS",
+                                       "DEVPATH",
+                                       "TAGS",
+                               };
+                               unsigned int i;
+
+                               for (i = 0; i < ARRAY_SIZE(blacklist); i++)
+                                       if (strcmp(attr, blacklist[i]) == 0) {
+                                               err(rules->udev, "invalid ENV attribute, '%s' can not be set %s:%u\n", attr, filename, lineno);
+                                               continue;
+                                       }
                                if (rule_add_key(&rule_tmp, TK_A_ENV, op, value, attr) != 0)
                                        goto invalid;
                        }
@@ -1402,43 +1444,50 @@ static int add_rule(struct udev_rules *rules, char *line,
 
                if (strncmp(key, "IMPORT", sizeof("IMPORT")-1) == 0) {
                        attr = get_key_attribute(rules->udev, key + sizeof("IMPORT")-1);
-                       if (attr != NULL && strstr(attr, "program")) {
+                       if (attr == NULL) {
+                               err(rules->udev, "IMPORT{} type missing, ignoring IMPORT %s:%u\n", filename, lineno);
+                               continue;
+                       }
+                       if (strstr(attr, "program")) {
+                               /* find known built-in command */
+                               if (value[0] != '/') {
+                                       char file[UTIL_PATH_SIZE];
+                                       char *pos;
+                                       enum udev_builtin_cmd cmd;
+
+                                       util_strscpy(file, sizeof(file), value);
+                                       pos = strchr(file, ' ');
+                                       if (pos)
+                                               pos[0] = '\0';
+                                       cmd = udev_builtin_lookup(file);
+                                       if (cmd < UDEV_BUILTIN_MAX) {
+                                               info(rules->udev, "IMPORT found builtin '%s', replacing %s:%u\n", file, filename, lineno);
+                                               rule_add_key(&rule_tmp, TK_M_IMPORT_BUILTIN, op, NULL, &cmd);
+                                               continue;
+                                       }
+                               }
                                dbg(rules->udev, "IMPORT will be executed\n");
                                rule_add_key(&rule_tmp, TK_M_IMPORT_PROG, op, value, NULL);
-                       } else if (attr != NULL && strstr(attr, "file")) {
+                       } else if (strstr(attr, "builtin")) {
+                               enum udev_builtin_cmd cmd = udev_builtin_lookup(value);
+
+                               dbg(rules->udev, "IMPORT execute builtin\n");
+                               if (cmd < UDEV_BUILTIN_MAX)
+                                       rule_add_key(&rule_tmp, TK_M_IMPORT_BUILTIN, op, NULL, &cmd);
+                               else
+                                       err(rules->udev, "IMPORT{builtin}: '%s' unknown %s:%u\n", value, filename, lineno);
+                       } else if (strstr(attr, "file")) {
                                dbg(rules->udev, "IMPORT will be included as file\n");
                                rule_add_key(&rule_tmp, TK_M_IMPORT_FILE, op, value, NULL);
-                       } else if (attr != NULL && strstr(attr, "db")) {
+                       } else if (strstr(attr, "db")) {
                                dbg(rules->udev, "IMPORT will include db values\n");
                                rule_add_key(&rule_tmp, TK_M_IMPORT_DB, op, value, NULL);
-                       } else if (attr != NULL && strstr(attr, "cmdline")) {
+                       } else if (strstr(attr, "cmdline")) {
                                dbg(rules->udev, "IMPORT will include db values\n");
                                rule_add_key(&rule_tmp, TK_M_IMPORT_CMDLINE, op, value, NULL);
-                       } else if (attr != NULL && strstr(attr, "parent")) {
+                       } else if (strstr(attr, "parent")) {
                                dbg(rules->udev, "IMPORT will include the parent values\n");
                                rule_add_key(&rule_tmp, TK_M_IMPORT_PARENT, op, value, NULL);
-                       } else {
-                               /* figure it out if it is executable */
-                               char file[UTIL_PATH_SIZE];
-                               char *pos;
-                               struct stat statbuf;
-
-                               /* allow programs in /lib/udev called without the path */
-                               if (value[0] != '/')
-                                       util_strscpyl(file, sizeof(file), LIBEXECDIR "/", value, NULL);
-                               else
-                                       util_strscpy(file, sizeof(file), value);
-                               pos = strchr(file, ' ');
-                               if (pos)
-                                       pos[0] = '\0';
-                               dbg(rules->udev, "IMPORT auto mode for '%s'\n", file);
-                               if (stat(file, &statbuf) == 0 && (statbuf.st_mode & S_IXUSR)) {
-                                       dbg(rules->udev, "IMPORT will be executed (autotype)\n");
-                                       rule_add_key(&rule_tmp, TK_M_IMPORT_PROG, op, value, NULL);
-                               } else {
-                                       dbg(rules->udev, "IMPORT will be included as file (autotype)\n");
-                                       rule_add_key(&rule_tmp, TK_M_IMPORT_FILE, op, value, NULL);
-                               }
                        }
                        continue;
                }
@@ -1716,7 +1765,7 @@ static int parse_file(struct udev_rules *rules, const char *filename, unsigned s
        return 0;
 }
 
-static int add_matching_files(struct udev *udev, struct udev_list_node *file_list, const char *dirname, const char *suffix)
+static int add_matching_files(struct udev *udev, struct udev_list *file_list, const char *dirname, const char *suffix)
 {
        DIR *dir;
        struct dirent *dent;
@@ -1750,7 +1799,7 @@ static int add_matching_files(struct udev *udev, struct udev_list_node *file_lis
                 * identical basenames from different directories overwrite each other
                 * entries are sorted after basename
                 */
-               udev_list_entry_add(udev, file_list, dent->d_name, filename, UDEV_LIST_UNIQUE|UDEV_LIST_SORT);
+               udev_list_entry_add(file_list, dent->d_name, filename);
        }
 
        closedir(dir);
@@ -1760,7 +1809,7 @@ static int add_matching_files(struct udev *udev, struct udev_list_node *file_lis
 struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names)
 {
        struct udev_rules *rules;
-       struct udev_list_node file_list;
+       struct udev_list file_list;
        struct udev_list_entry *file_loop;
        struct token end_token;
 
@@ -1769,7 +1818,7 @@ struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names)
                return NULL;
        rules->udev = udev;
        rules->resolve_names = resolve_names;
-       udev_list_init(&file_list);
+       udev_list_init(udev, &file_list, true);
 
        /* init token array and string buffer */
        rules->tokens = malloc(PREALLOC_TOKEN * sizeof(struct token));
@@ -1852,7 +1901,7 @@ struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names)
                }
                parse_file(rules, filename, filename_off);
        }
-       udev_list_cleanup_entries(udev, &file_list);
+       udev_list_cleanup(&file_list);
 
        memset(&end_token, 0x00, sizeof(struct token));
        end_token.type = TK_END;
@@ -2165,7 +2214,8 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event
                case TK_M_KERNELS:
                case TK_M_SUBSYSTEMS:
                case TK_M_DRIVERS:
-               case TK_M_ATTRS: {
+               case TK_M_ATTRS:
+               case TK_M_TAGS: {
                        struct token *next;
 
                        /* get whole sequence of parent matches */
@@ -2199,13 +2249,21 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event
                                                if (match_attr(rules, event->dev_parent, event, key) != 0)
                                                        goto try_parent;
                                                break;
+                                       case TK_M_TAGS: {
+                                               bool match = udev_device_has_tag(event->dev_parent, &rules->buf[cur->key.value_off]);
+
+                                               if (match && key->key.op == OP_NOMATCH)
+                                                       goto try_parent;
+                                               if (!match && key->key.op == OP_MATCH)
+                                                       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:
@@ -2307,6 +2365,35 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event
                                        goto nomatch;
                        break;
                }
+               case TK_M_IMPORT_BUILTIN: {
+                       /* check if we ran already */
+                       if (event->builtin_run & (1 << cur->key.builtin_cmd)) {
+                               info(event->udev, "IMPORT builtin skip '%s' %s:%u\n",
+                                    udev_builtin_name(cur->key.builtin_cmd),
+                                    &rules->buf[rule->rule.filename_off],
+                                    rule->rule.filename_line);
+                               /* return the result from earlier run */
+                               if (event->builtin_ret & (1 << cur->key.builtin_cmd))
+                                       if (cur->key.op != OP_NOMATCH)
+                                               goto nomatch;
+                               break;
+                       }
+                       /* mark as ran */
+                       event->builtin_run |= (1 << cur->key.builtin_cmd);
+                       info(event->udev, "IMPORT builtin '%s' %s:%u\n",
+                            udev_builtin_name(cur->key.builtin_cmd),
+                            &rules->buf[rule->rule.filename_off],
+                            rule->rule.filename_line);
+                       if (udev_builtin_run(event->dev, cur->key.builtin_cmd, false) != 0) {
+                               /* remember failure */
+                               info(rules->udev, "IMPORT builtin '%s' returned non-zero\n",
+                                    udev_builtin_name(cur->key.builtin_cmd));
+                               event->builtin_ret |= (1 << cur->key.builtin_cmd);
+                               if (cur->key.op != OP_NOMATCH)
+                                       goto nomatch;
+                       }
+                       break;
+               }
                case TK_M_IMPORT_DB: {
                        const char *key = &rules->buf[cur->key.value_off];
                        const char *value;
@@ -2485,6 +2572,7 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event
                             rule->rule.filename_line);
                        break;
                case TK_A_STATIC_NODE:
+                       event->static_node = true;
                        break;
                case TK_A_ENV: {
                        const char *name = &rules->buf[cur->key.attr_off];
@@ -2504,11 +2592,25 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event
                        }
                        break;
                }
-               case TK_A_TAG:
+               case TK_A_TAG: {
+                       char tag[UTIL_PATH_SIZE];
+                       const char *p;
+
+                       udev_event_apply_format(event, &rules->buf[cur->key.value_off], tag, sizeof(tag));
                        if (cur->key.op == OP_ASSIGN || cur->key.op == OP_ASSIGN_FINAL)
                                udev_device_cleanup_tags_list(event->dev);
-                       udev_device_add_tag(event->dev, &rules->buf[cur->key.value_off]);
+                       for (p = tag; *p != '\0'; p++) {
+                               if ((*p >= 'a' && *p <= 'z') ||
+                                   (*p >= 'A' && *p <= 'Z') ||
+                                   (*p >= '0' && *p <= '9') ||
+                                   *p == '-' || *p == '_')
+                                       continue;
+                               err(event->udev, "ignoring invalid tag name '%s'\n", tag);
+                               break;
+                       }
+                       udev_device_add_tag(event->dev, tag);
                        break;
+               }
                case TK_A_NAME: {
                        const char *name  = &rules->buf[cur->key.value_off];
                        char name_str[UTIL_PATH_SIZE];
@@ -2607,13 +2709,12 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event
                        struct udev_list_entry *list_entry;
 
                        if (cur->key.op == OP_ASSIGN || cur->key.op == OP_ASSIGN_FINAL)
-                               udev_list_cleanup_entries(event->udev, &event->run_list);
+                               udev_list_cleanup(&event->run_list);
                        info(event->udev, "RUN '%s' %s:%u\n",
                             &rules->buf[cur->key.value_off],
                             &rules->buf[rule->rule.filename_off],
                             rule->rule.filename_line);
-                       list_entry = udev_list_entry_add(event->udev, &event->run_list,
-                                                        &rules->buf[cur->key.value_off], NULL, UDEV_LIST_UNIQUE);
+                       list_entry = udev_list_entry_add(&event->run_list, &rules->buf[cur->key.value_off], NULL);
                        if (cur->key.fail_on_error)
                                udev_list_entry_set_num(list_entry, true);
                        break;
@@ -2683,6 +2784,7 @@ void udev_rules_apply_static_dev_perms(struct udev_rules *rules)
                case TK_A_STATIC_NODE: {
                        char filename[UTIL_PATH_SIZE];
                        struct stat stats;
+
                        /* we assure, that the permissions tokens are sorted before the static token */
                        if (mode == 0 && uid == 0 && gid == 0)
                                goto next;
@@ -2692,14 +2794,24 @@ void udev_rules_apply_static_dev_perms(struct udev_rules *rules)
                                goto next;
                        if (!S_ISBLK(stats.st_mode) && !S_ISCHR(stats.st_mode))
                                goto next;
-                       if (mode != 0 && mode != (stats.st_mode & 0777)) {
+                       if (mode == 0) {
+                               if (gid > 0)
+                                       mode = 0660;
+                               else
+                                       mode = 0600;
+                       }
+                       /* set sticky bit, so we do not remove the node on module unload */
+                       mode |= 01000;
+                       if (mode != (stats.st_mode & 01777)) {
                                chmod(filename, mode);
                                info(rules->udev, "chmod '%s' %#o\n", filename, mode);
                        }
+
                        if ((uid != 0 && uid != stats.st_uid) || (gid != 0 && gid != stats.st_gid)) {
                                chown(filename, uid, gid);
                                info(rules->udev, "chown '%s' %u %u\n", filename, uid, gid);
                        }
+
                        utimensat(AT_FDCWD, filename, NULL, 0);
                        break;
                }