diff options
-rw-r--r-- | util-linux/mdev.c | 558 |
1 files changed, 341 insertions, 217 deletions
diff --git a/util-linux/mdev.c b/util-linux/mdev.c index c56741b08..0b3e06cf1 100644 --- a/util-linux/mdev.c +++ b/util-linux/mdev.c @@ -168,9 +168,29 @@ * 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); |