mdev: don't reparse rules on -s
authorDenys Vlasenko <vda.linux@googlemail.com>
Thu, 22 Sep 2011 01:46:30 +0000 (03:46 +0200)
committerDenys Vlasenko <vda.linux@googlemail.com>
Thu, 22 Sep 2011 01:46:30 +0000 (03:46 +0200)
function                                             old     new   delta
make_device                                         1648    1843    +195
clean_up_cur_rule                                      -      61     +61
make_default_cur_rule                                  -      41     +41
mdev_main                                            690     712     +22
------------------------------------------------------------------------------
(add/remove: 2/0 grow/shrink: 2/0 up/down: 319/0)             Total: 319 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
util-linux/mdev.c

index c56741b0891eb24177348dfd56187a0782945269..0b3e06cf18a9104ba14622d41e95d003ea46c1e8 100644 (file)
  * This happens regardless of /sys/class/.../dev existence.
  */
 
+struct rule {
+       bool keep_matching;
+       bool regex_compiled;
+       bool regex_has_slash;
+       mode_t mode;
+       int maj, min0, min1;
+       struct bb_uidgid_t ugid;
+       char *envvar;
+       char *ren_mov;
+       IF_FEATURE_MDEV_EXEC(char *r_cmd;)
+       regex_t match;
+};
+
 struct globals {
        int root_major, root_minor;
        char *subsystem;
+#if ENABLE_FEATURE_MDEV_CONF
+       const char *filename;
+       parser_t *parser;
+       struct rule **rule_vec;
+       unsigned rule_idx;
+#endif
+       struct rule cur_rule;
 } FIX_ALIASING;
 #define G (*(struct globals*)&bb_common_bufsiz1)
 #define INIT_G() do { } while (0)
@@ -182,6 +202,164 @@ struct globals {
 /* We use additional 64+ bytes in make_device() */
 #define SCRATCH_SIZE 80
 
+#if 0
+# define dbg(...) bb_error_msg(__VA_ARGS__)
+#else
+# define dbg(...) ((void)0)
+#endif
+
+
+#if ENABLE_FEATURE_MDEV_CONF
+
+static void make_default_cur_rule(void)
+{
+       memset(&G.cur_rule, 0, sizeof(G.cur_rule));
+       G.cur_rule.maj = -1; /* "not a @major,minor rule" */
+       G.cur_rule.mode = 0660;
+}
+
+static void clean_up_cur_rule(void)
+{
+       free(G.cur_rule.envvar);
+       if (G.cur_rule.regex_compiled)
+               regfree(&G.cur_rule.match);
+       free(G.cur_rule.ren_mov);
+       IF_FEATURE_MDEV_EXEC(free(G.cur_rule.r_cmd);)
+       make_default_cur_rule();
+}
+
+static void parse_next_rule(void)
+{
+       /* Note: on entry, G.cur_rule is set to default */
+       while (1) {
+               char *tokens[4];
+               char *val;
+
+               if (!config_read(G.parser, tokens, 4, 3, "# \t", PARSE_NORMAL))
+                       break;
+
+               /* Fields: [-]regex uid:gid mode [alias] [cmd] */
+               dbg("token1:'%s'", tokens[1]);
+
+               /* 1st field */
+               val = tokens[0];
+               G.cur_rule.keep_matching = ('-' == val[0]);
+               val += G.cur_rule.keep_matching; /* swallow leading dash */
+               if (val[0] == '@') {
+                       /* @major,minor[-minor2] */
+                       /* (useful when name is ambiguous:
+                        * "/sys/class/usb/lp0" and
+                        * "/sys/class/printer/lp0")
+                        */
+                       int sc = sscanf(val, "@%u,%u-%u", &G.cur_rule.maj, &G.cur_rule.min0, &G.cur_rule.min1);
+                       if (sc < 2 || G.cur_rule.maj < 0) {
+                               bb_error_msg("bad @maj,min on line %d", G.parser->lineno);
+                               goto next_rule;
+                       }
+                       if (sc == 2)
+                               G.cur_rule.min1 = G.cur_rule.min0;
+               } else {
+                       if (val[0] == '$') {
+                               char *eq = strchr(++val, '=');
+                               if (!eq) {
+                                       bb_error_msg("bad $envvar=regex on line %d", G.parser->lineno);
+                                       goto next_rule;
+                               }
+                               G.cur_rule.envvar = xstrndup(val, eq - val);
+                               val = eq + 1;
+                       }
+                       xregcomp(&G.cur_rule.match, val, REG_EXTENDED);
+                       G.cur_rule.regex_compiled = 1;
+                       G.cur_rule.regex_has_slash = (strchr(val, '/') != NULL);
+               }
+
+               /* 2nd field: uid:gid - device ownership */
+               if (get_uidgid(&G.cur_rule.ugid, tokens[1], /*allow_numeric:*/ 1) == 0) {
+                       bb_error_msg("unknown user/group '%s' on line %d", tokens[1], G.parser->lineno);
+                       goto next_rule;
+               }
+
+               /* 3rd field: mode - device permissions */
+               bb_parse_mode(tokens[2], &G.cur_rule.mode);
+
+               /* 4th field (opt): ">|=alias" or "!" to not create the node */
+               val = tokens[3];
+               if (ENABLE_FEATURE_MDEV_RENAME && val && strchr(">=!", val[0])) {
+                       char *s = skip_non_whitespace(val);
+                       G.cur_rule.ren_mov = xstrndup(val, s - val);
+                       val = skip_whitespace(s);
+               }
+
+               if (ENABLE_FEATURE_MDEV_EXEC && val && val[0]) {
+                       const char *s = "$@*";
+                       const char *s2 = strchr(s, val[0]);
+                       if (!s2) {
+                               bb_error_msg("bad line %u", G.parser->lineno);
+                               goto next_rule;
+                       }
+                       IF_FEATURE_MDEV_EXEC(G.cur_rule.r_cmd = xstrdup(val);)
+               }
+
+               return;
+ next_rule:
+               clean_up_cur_rule();
+       } /* while (config_read) */
+
+       dbg("config_close(G.parser)");
+       config_close(G.parser);
+       G.parser = NULL;
+
+       return;
+}
+
+/* If mdev -s, we remember rules in G.rule_vec[].
+ * Otherwise, there is no point in doing it, and we just
+ * save only one parsed rule in G.cur_rule.
+ */
+static const struct rule *next_rule(void)
+{
+       struct rule *rule;
+
+       /* Open conf file if we didn't do it yet */
+       if (!G.parser && G.filename) {
+               dbg("config_open('%s')", G.filename);
+               G.parser = config_open2(G.filename, fopen_for_read);
+               G.filename = NULL;
+       }
+
+       if (G.rule_vec) {
+               /* mdev -s */
+               /* Do we have rule parsed already? */
+               if (G.rule_vec[G.rule_idx]) {
+                       dbg("< G.rule_vec[G.rule_idx:%d]=%p", G.rule_idx, G.rule_vec[G.rule_idx]);
+                       return G.rule_vec[G.rule_idx++];
+               }
+               make_default_cur_rule();
+       } else {
+               /* not mdev -s */
+               clean_up_cur_rule();
+       }
+
+       /* Parse one more rule if file isn't fully read */
+       rule = &G.cur_rule;
+       if (G.parser) {
+               parse_next_rule();
+               if (G.rule_vec) { /* mdev -s */
+                       rule = memcpy(xmalloc(sizeof(G.cur_rule)), &G.cur_rule, sizeof(G.cur_rule));
+                       G.rule_vec = xrealloc_vector(G.rule_vec, 4, G.rule_idx);
+                       G.rule_vec[G.rule_idx++] = rule;
+                       dbg("> G.rule_vec[G.rule_idx:%d]=%p", G.rule_idx, G.rule_vec[G.rule_idx]);
+               }
+       }
+
+       return rule;
+}
+
+#else
+
+# define next_rule() (&G.cur_rule)
+
+#endif
 
 /* Builds an alias path.
  * This function potentionally reallocates the alias parameter.
@@ -218,8 +396,8 @@ static void make_device(char *path, int delete)
 {
        char *device_name, *subsystem_slash_devname;
        int major, minor, type, len;
-       mode_t mode;
-       parser_t *parser;
+
+       dbg("%s('%s', delete:%d)", __func__, path, delete);
 
        /* Try to read major/minor string.  Note that the kernel puts \n after
         * the data, so we don't need to worry about null terminating the string
@@ -272,246 +450,184 @@ static void make_device(char *path, int delete)
                path = subsystem_slash_devname;
        }
 
-       /* If we have config file, look up user settings */
-       if (ENABLE_FEATURE_MDEV_CONF)
-               parser = config_open2("/etc/mdev.conf", fopen_for_read);
-
-       do {
-               int keep_matching;
-               struct bb_uidgid_t ugid;
-               char *tokens[4];
-               char *command = NULL;
-               char *alias = NULL;
+#if ENABLE_FEATURE_MDEV_CONF
+       G.rule_idx = 0; /* restart from the beginning (think mdev -s) */
+#endif
+       for (;;) {
+               const char *str_to_match;
+               regmatch_t off[1 + 9 * ENABLE_FEATURE_MDEV_RENAME_REGEXP];
+               char *command;
+               char *alias;
                char aliaslink = aliaslink; /* for compiler */
+               const char *node_name;
+               const struct rule *rule;
 
-               /* Defaults in case we won't match any line */
-               ugid.uid = ugid.gid = 0;
-               keep_matching = 0;
-               mode = 0660;
-
-               if (ENABLE_FEATURE_MDEV_CONF
-                && config_read(parser, tokens, 4, 3, "# \t", PARSE_NORMAL)
-               ) {
-                       char *val;
-                       char *str_to_match;
-                       regmatch_t off[1 + 9 * ENABLE_FEATURE_MDEV_RENAME_REGEXP];
-
-                       val = tokens[0];
-                       keep_matching = ('-' == val[0]);
-                       val += keep_matching; /* swallow leading dash */
-
-                       /* Match against either "subsystem/device_name"
-                        * or "device_name" alone */
-                       str_to_match = strchr(val, '/') ? path : device_name;
-
-                       /* Fields: regex uid:gid mode [alias] [cmd] */
-
-                       if (val[0] == '@') {
-                               /* @major,minor[-minor2] */
-                               /* (useful when name is ambiguous:
-                                * "/sys/class/usb/lp0" and
-                                * "/sys/class/printer/lp0") */
-                               int cmaj, cmin0, cmin1, sc;
-                               if (major < 0)
-                                       continue; /* no dev, no match */
-                               sc = sscanf(val, "@%u,%u-%u", &cmaj, &cmin0, &cmin1);
-                               if (sc < 1
-                                || major != cmaj
-                                || (sc == 2 && minor != cmin0)
-                                || (sc == 3 && (minor < cmin0 || minor > cmin1))
-                               ) {
-                                       continue; /* this line doesn't match */
-                               }
-                               goto line_matches;
-                       }
-                       if (val[0] == '$') {
-                               /* regex to match an environment variable */
-                               char *eq = strchr(++val, '=');
-                               if (!eq)
-                                       continue;
-                               *eq = '\0';
-                               str_to_match = getenv(val);
-                               if (!str_to_match)
-                                       continue;
-                               str_to_match -= strlen(val) + 1;
-                               *eq = '=';
+               str_to_match = "";
+
+               rule = next_rule();
+
+#if ENABLE_FEATURE_MDEV_CONF
+               if (rule->maj >= 0) {  /* @maj,min rule */
+                       if (major != rule->maj)
+                               continue;
+                       if (minor < rule->min0 || minor > rule->min1)
+                               continue;
+                       memset(off, 0, sizeof(off));
+                       goto rule_matches;
+               }
+               if (rule->envvar) { /* $envvar=regex rule */
+                       str_to_match = getenv(rule->envvar);
+                       dbg("getenv('%s'):'%s'", rule->envvar, str_to_match);
+                       if (!str_to_match)
+                               continue;
+               } else {
+                       /* regex to match [subsystem/]device_name */
+                       str_to_match = (rule->regex_has_slash ? path : device_name);
+               }
+
+               if (rule->regex_compiled) {
+                       int regex_match = regexec(&rule->match, str_to_match, ARRAY_SIZE(off), off, 0);
+                       dbg("regex_match for '%s':%d", str_to_match, regex_match);
+                       //bb_error_msg("matches:");
+                       //for (int i = 0; i < ARRAY_SIZE(off); i++) {
+                       //      if (off[i].rm_so < 0) continue;
+                       //      bb_error_msg("match %d: '%.*s'\n", i,
+                       //              (int)(off[i].rm_eo - off[i].rm_so),
+                       //              device_name + off[i].rm_so);
+                       //}
+
+                       if (regex_match != 0
+                       /* regexec returns whole pattern as "range" 0 */
+                        || off[0].rm_so != 0
+                        || (int)off[0].rm_eo != (int)strlen(str_to_match)
+                       ) {
+                               continue; /* this rule doesn't match */
                        }
-                       /* else: regex to match [subsystem/]device_name */
-
-                       {
-                               regex_t match;
-                               int result;
-
-                               xregcomp(&match, val, REG_EXTENDED);
-                               result = regexec(&match, str_to_match, ARRAY_SIZE(off), off, 0);
-                               regfree(&match);
-                               //bb_error_msg("matches:");
-                               //for (int i = 0; i < ARRAY_SIZE(off); i++) {
-                               //      if (off[i].rm_so < 0) continue;
-                               //      bb_error_msg("match %d: '%.*s'\n", i,
-                               //              (int)(off[i].rm_eo - off[i].rm_so),
-                               //              device_name + off[i].rm_so);
-                               //}
-
-                               /* If no match, skip rest of line */
-                               /* (regexec returns whole pattern as "range" 0) */
-                               if (result
-                                || off[0].rm_so
-                                || ((int)off[0].rm_eo != (int)strlen(str_to_match))
-                               ) {
-                                       continue; /* this line doesn't match */
-                               }
+               }
+               /* else: it's final implicit "match-all" rule */
+#endif
+
+ rule_matches:
+               dbg("rule matched");
+
+               /* Build alias name */
+               alias = NULL;
+               if (ENABLE_FEATURE_MDEV_RENAME && rule->ren_mov) {
+                       aliaslink = rule->ren_mov[0];
+                       if (aliaslink == '!') {
+                               /* "!": suppress node creation/deletion */
+                               major = -2;
                        }
- line_matches:
-                       /* This line matches. Stop parsing after parsing
-                        * the rest the line unless keep_matching == 1 */
-
-                       /* 2nd field: uid:gid - device ownership */
-                       if (get_uidgid(&ugid, tokens[1], /*allow_numeric:*/ 1) == 0)
-                               bb_error_msg("unknown user/group %s on line %d", tokens[1], parser->lineno);
-
-                       /* 3rd field: mode - device permissions */
-                       bb_parse_mode(tokens[2], &mode);
-
-                       val = tokens[3];
-                       /* 4th field (opt): ">|=alias" or "!" to not create the node */
-
-                       if (ENABLE_FEATURE_MDEV_RENAME && val) {
-                               char *a, *s, *st;
-
-                               a = val;
-                               s = strchrnul(val, ' ');
-                               st = strchrnul(val, '\t');
-                               if (st < s)
-                                       s = st;
-                               st = (s[0] && s[1]) ? s+1 : NULL;
-
-                               aliaslink = a[0];
-                               if (aliaslink == '!' && s == a+1) {
-                                       val = st;
-                                       /* "!": suppress node creation/deletion */
-                                       major = -2;
-                               }
-                               else if (aliaslink == '>' || aliaslink == '=') {
-                                       val = st;
-                                       s[0] = '\0';
-                                       if (ENABLE_FEATURE_MDEV_RENAME_REGEXP) {
-                                               char *p;
-                                               unsigned i, n;
-
-                                               /* substitute %1..9 with off[1..9], if any */
-                                               n = 0;
-                                               s = a;
-                                               while (*s)
-                                                       if (*s++ == '%')
-                                                               n++;
-
-                                               p = alias = xzalloc(strlen(a) + n * strlen(str_to_match));
-                                               s = a + 1;
-                                               while (*s) {
-                                                       *p = *s;
-                                                       if ('%' == *s) {
-                                                               i = (s[1] - '0');
-                                                               if (i <= 9 && off[i].rm_so >= 0) {
-                                                                       n = off[i].rm_eo - off[i].rm_so;
-                                                                       strncpy(p, str_to_match + off[i].rm_so, n);
-                                                                       p += n - 1;
-                                                                       s++;
-                                                               }
+                       else if (aliaslink == '>' || aliaslink == '=') {
+                               if (ENABLE_FEATURE_MDEV_RENAME_REGEXP) {
+                                       char *s;
+                                       char *p;
+                                       unsigned n;
+
+                                       /* substitute %1..9 with off[1..9], if any */
+                                       n = 0;
+                                       s = rule->ren_mov;
+                                       while (*s)
+                                               if (*s++ == '%')
+                                                       n++;
+
+                                       p = alias = xzalloc(strlen(rule->ren_mov) + n * strlen(str_to_match));
+                                       s = rule->ren_mov + 1;
+                                       while (*s) {
+                                               *p = *s;
+                                               if ('%' == *s) {
+                                                       unsigned i = (s[1] - '0');
+                                                       if (i <= 9 && off[i].rm_so >= 0) {
+                                                               n = off[i].rm_eo - off[i].rm_so;
+                                                               strncpy(p, str_to_match + off[i].rm_so, n);
+                                                               p += n - 1;
+                                                               s++;
                                                        }
-                                                       p++;
-                                                       s++;
                                                }
-                                       } else {
-                                               alias = xstrdup(a + 1);
+                                               p++;
+                                               s++;
                                        }
+                               } else {
+                                       alias = xstrdup(rule->ren_mov + 1);
                                }
                        }
+               }
+               dbg("alias:'%s'", alias);
 
-                       if (ENABLE_FEATURE_MDEV_EXEC && val) {
-                               const char *s = "$@*";
-                               const char *s2 = strchr(s, val[0]);
-
-                               if (!s2) {
-                                       bb_error_msg("bad line %u", parser->lineno);
-                                       if (ENABLE_FEATURE_MDEV_RENAME)
-                                               free(alias);
-                                       continue;
-                               }
+               command = NULL;
+               IF_FEATURE_MDEV_EXEC(command = rule->r_cmd;)
+               if (command) {
+                       const char *s = "$@*";
+                       const char *s2 = strchr(s, command[0]);
 
-                               /* Are we running this command now?
-                                * Run $cmd on delete, @cmd on create, *cmd on both
+                       /* Are we running this command now?
+                        * Run $cmd on delete, @cmd on create, *cmd on both
+                        */
+                       if (s2 - s != delete) {
+                               /* We are here if: '*',
+                                * or: '@' and delete = 0,
+                                * or: '$' and delete = 1
                                 */
-                               if (s2 - s != delete) {
-                                       /* We are here if: '*',
-                                        * or: '@' and delete = 0,
-                                        * or: '$' and delete = 1
-                                        */
-                                       command = xstrdup(val + 1);
-                               }
+                               command++;
+                       } else {
+                               command = NULL;
                        }
                }
-
-               /* End of field parsing */
+               dbg("command:'%s'", command);
 
                /* "Execute" the line we found */
-               {
-                       const char *node_name;
-
-                       node_name = device_name;
-                       if (ENABLE_FEATURE_MDEV_RENAME && alias)
-                               node_name = alias = build_alias(alias, device_name);
-
-                       if (!delete && major >= 0) {
-                               if (mknod(node_name, mode | type, makedev(major, minor)) && errno != EEXIST)
-                                       bb_perror_msg("can't create '%s'", node_name);
-                               if (major == G.root_major && minor == G.root_minor)
-                                       symlink(node_name, "root");
-                               if (ENABLE_FEATURE_MDEV_CONF) {
-                                       chmod(node_name, mode);
-                                       chown(node_name, ugid.uid, ugid.gid);
-                               }
-                               if (ENABLE_FEATURE_MDEV_RENAME && alias) {
-                                       if (aliaslink == '>')
-                                               symlink(node_name, device_name);
-                               }
-                       }
+               node_name = device_name;
+               if (ENABLE_FEATURE_MDEV_RENAME && alias) {
+                       node_name = alias = build_alias(alias, device_name);
+                       dbg("alias2:'%s'", alias);
+               }
 
-                       if (ENABLE_FEATURE_MDEV_EXEC && command) {
-                               /* setenv will leak memory, use putenv/unsetenv/free */
-                               char *s = xasprintf("%s=%s", "MDEV", node_name);
-                               char *s1 = xasprintf("%s=%s", "SUBSYSTEM", G.subsystem);
-                               putenv(s);
-                               putenv(s1);
-                               if (system(command) == -1)
-                                       bb_perror_msg("can't run '%s'", command);
-                               bb_unsetenv_and_free(s1);
-                               bb_unsetenv_and_free(s);
-                               free(command);
+               if (!delete && major >= 0) {
+                       dbg("mknod('%s',%o,(%d,%d))", node_name, rule->mode | type, major, minor);
+                       if (mknod(node_name, rule->mode | type, makedev(major, minor)) && errno != EEXIST)
+                               bb_perror_msg("can't create '%s'", node_name);
+                       if (major == G.root_major && minor == G.root_minor)
+                               symlink(node_name, "root");
+                       if (ENABLE_FEATURE_MDEV_CONF) {
+                               chmod(node_name, rule->mode);
+                               chown(node_name, rule->ugid.uid, rule->ugid.gid);
                        }
-
-                       if (delete && major >= -1) {
-                               if (ENABLE_FEATURE_MDEV_RENAME && alias) {
-                                       if (aliaslink == '>')
-                                               unlink(device_name);
-                               }
-                               unlink(node_name);
+                       if (ENABLE_FEATURE_MDEV_RENAME && alias) {
+                               if (aliaslink == '>')
+                                       symlink(node_name, device_name);
                        }
+               }
 
-                       if (ENABLE_FEATURE_MDEV_RENAME)
-                               free(alias);
+               if (ENABLE_FEATURE_MDEV_EXEC && command) {
+                       /* setenv will leak memory, use putenv/unsetenv/free */
+                       char *s = xasprintf("%s=%s", "MDEV", node_name);
+                       char *s1 = xasprintf("%s=%s", "SUBSYSTEM", G.subsystem);
+                       putenv(s);
+                       putenv(s1);
+                       if (system(command) == -1)
+                               bb_perror_msg("can't run '%s'", command);
+                       bb_unsetenv_and_free(s1);
+                       bb_unsetenv_and_free(s);
                }
 
+               if (delete && major >= -1) {
+                       if (ENABLE_FEATURE_MDEV_RENAME && alias) {
+                               if (aliaslink == '>')
+                                       unlink(device_name);
+                       }
+                       unlink(node_name);
+               }
+
+               if (ENABLE_FEATURE_MDEV_RENAME)
+                       free(alias);
+
                /* We found matching line.
-                * Stop unless it was prefixed with '-' */
-               if (ENABLE_FEATURE_MDEV_CONF && !keep_matching)
+                * Stop unless it was prefixed with '-'
+                */
+               if (!ENABLE_FEATURE_MDEV_CONF || !rule->keep_matching)
                        break;
+       } /* for (;;) */
 
-       /* end of "while line is read from /etc/mdev.conf" */
-       } while (ENABLE_FEATURE_MDEV_CONF);
-
-       if (ENABLE_FEATURE_MDEV_CONF)
-               config_close(parser);
        free(subsystem_slash_devname);
 }
 
@@ -618,6 +734,10 @@ int mdev_main(int argc UNUSED_PARAM, char **argv)
 
        INIT_G();
 
+#if ENABLE_FEATURE_MDEV_CONF
+       G.filename = "/etc/mdev.conf";
+#endif
+
        /* We can be called as hotplug helper */
        /* Kernel cannot provide suitable stdio fds for us, do it ourself */
        bb_sanitize_stdio();
@@ -633,6 +753,10 @@ int mdev_main(int argc UNUSED_PARAM, char **argv)
                 */
                struct stat st;
 
+#if ENABLE_FEATURE_MDEV_CONF
+               /* Same as xrealloc_vector(NULL, 4, 0): */
+               G.rule_vec = xzalloc((1 << 4) * sizeof(*G.rule_vec));
+#endif
                xstat("/", &st);
                G.root_major = major(st.st_dev);
                G.root_minor = minor(st.st_dev);