--- busybox-1.21.0/util-linux/mdev.c +++ busybox-1.21.0-mdev/util-linux/mdev.c @@ -80,7 +80,7 @@ //usage: IF_FEATURE_MDEV_CONF( //usage: "\n" //usage: "It uses /etc/mdev.conf with lines\n" -//usage: " [-]DEVNAME UID:GID PERM" +//usage: " [-][ENV=regex;]...DEVNAME UID:GID PERM" //usage: IF_FEATURE_MDEV_RENAME(" [>|=PATH]|[!]") //usage: IF_FEATURE_MDEV_EXEC(" [@|$|*PROG]") //usage: "\n" @@ -230,9 +230,34 @@ * SUBSYSTEM=block */ -static const char keywords[] ALIGN1 = "add\0remove\0change\0"; +#define DEBUG_LVL 2 + +#if DEBUG_LVL >= 1 +# define dbg1(...) do { if (G.verbose) bb_error_msg(__VA_ARGS__); } while(0) +#else +# define dbg1(...) ((void)0) +#endif +#if DEBUG_LVL >= 2 +# define dbg2(...) do { if (G.verbose >= 2) bb_error_msg(__VA_ARGS__); } while(0) +#else +# define dbg2(...) ((void)0) +#endif +#if DEBUG_LVL >= 3 +# define dbg3(...) do { if (G.verbose >= 3) bb_error_msg(__VA_ARGS__); } while(0) +#else +# define dbg3(...) ((void)0) +#endif + + +static const char keywords[] ALIGN1 = "add\0remove\0"; // "change\0" enum { OP_add, OP_remove }; +struct envmatch { + struct envmatch *next; + char *envname; + regex_t match; +}; + struct rule { bool keep_matching; bool regex_compiled; @@ -243,12 +268,14 @@ struct rule { char *ren_mov; IF_FEATURE_MDEV_EXEC(char *r_cmd;) regex_t match; + struct envmatch *envmatch; }; struct globals { int root_major, root_minor; smallint verbose; char *subsystem; + char *subsys_env; /* for putenv("SUBSYSTEM=subsystem") */ #if ENABLE_FEATURE_MDEV_CONF const char *filename; parser_t *parser; @@ -256,6 +283,7 @@ struct globals { unsigned rule_idx; #endif struct rule cur_rule; + char timestr[sizeof("60.123456")]; } FIX_ALIASING; #define G (*(struct globals*)&bb_common_bufsiz1) #define INIT_G() do { \ @@ -270,13 +298,6 @@ 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) @@ -288,14 +309,65 @@ static void make_default_cur_rule(void) static void clean_up_cur_rule(void) { + struct envmatch *e; + free(G.cur_rule.envvar); + free(G.cur_rule.ren_mov); 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);) + e = G.cur_rule.envmatch; + while (e) { + free(e->envname); + regfree(&e->match); + e = e->next; + } make_default_cur_rule(); } +/* In later versions, endofname is in libbb */ +#define endofname mdev_endofname +static +const char* FAST_FUNC +endofname(const char *name) +{ +#define is_name(c) ((c) == '_' || isalpha((unsigned char)(c))) +#define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c))) + if (!is_name(*name)) + return name; + while (*++name) { + if (!is_in_name(*name)) + break; + } + return name; +} + +static char *parse_envmatch_pfx(char *val) +{ + struct envmatch **nextp = &G.cur_rule.envmatch; + + for (;;) { + struct envmatch *e; + char *semicolon; + char *eq = strchr(val, '='); + if (!eq /* || eq == val? */) + return val; + if (endofname(val) != eq) + return val; + semicolon = strchr(eq, ';'); + if (!semicolon) + return val; + /* ENVVAR=regex;... */ + *nextp = e = xzalloc(sizeof(*e)); + nextp = &e->next; + e->envname = xstrndup(val, eq - val); + *semicolon = '\0'; + xregcomp(&e->match, eq + 1, REG_EXTENDED); + *semicolon = ';'; + val = semicolon + 1; + } +} + static void parse_next_rule(void) { /* Note: on entry, G.cur_rule is set to default */ @@ -308,12 +380,13 @@ static void parse_next_rule(void) break; /* Fields: [-]regex uid:gid mode [alias] [cmd] */ - dbg("token1:'%s'", tokens[1]); + dbg3("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 */ + val = parse_envmatch_pfx(val); if (val[0] == '@') { /* @major,minor[-minor2] */ /* (useful when name is ambiguous: @@ -328,8 +401,10 @@ static void parse_next_rule(void) if (sc == 2) G.cur_rule.min1 = G.cur_rule.min0; } else { + char *eq = strchr(val, '='); if (val[0] == '$') { - char *eq = strchr(++val, '='); + /* $ENVVAR=regex ... */ + val++; if (!eq) { bb_error_msg("bad $envvar=regex on line %d", G.parser->lineno); goto next_rule; @@ -373,7 +448,7 @@ static void parse_next_rule(void) clean_up_cur_rule(); } /* while (config_read) */ - dbg("config_close(G.parser)"); + dbg3("config_close(G.parser)"); config_close(G.parser); G.parser = NULL; @@ -390,7 +465,7 @@ static const struct rule *next_rule(void /* Open conf file if we didn't do it yet */ if (!G.parser && G.filename) { - dbg("config_open('%s')", G.filename); + dbg3("config_open('%s')", G.filename); G.parser = config_open2(G.filename, fopen_for_read); G.filename = NULL; } @@ -399,7 +474,7 @@ static const struct rule *next_rule(void /* 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]); + dbg3("< 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(); @@ -416,13 +491,28 @@ static const struct rule *next_rule(void 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]); + dbg3("> G.rule_vec[G.rule_idx:%d]=%p", G.rule_idx, G.rule_vec[G.rule_idx]); } } return rule; } +static int env_matches(struct envmatch *e) +{ + while (e) { + int r; + char *val = getenv(e->envname); + if (!val) + return 0; + r = regexec(&e->match, val, /*size*/ 0, /*range[]*/ NULL, /*eflags*/ 0); + if (r != 0) /* no match */ + return 0; + e = e->next; + } + return 1; +} + #else # define next_rule() (&G.cur_rule) @@ -479,9 +569,6 @@ static void make_device(char *device_nam { int major, minor, type, len; - if (G.verbose) - bb_error_msg("device: %s, %s", device_name, path); - /* 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 * because sscanf() will stop at the first nondigit, which \n is. @@ -500,8 +587,7 @@ static void make_device(char *device_nam /* no "dev" file, but we can still run scripts * based on device name */ } else if (sscanf(++dev_maj_min, "%u:%u", &major, &minor) == 2) { - if (G.verbose) - bb_error_msg("maj,min: %u,%u", major, minor); + dbg1("dev %u,%u", major, minor); } else { major = -1; } @@ -511,7 +597,8 @@ static void make_device(char *device_nam /* Determine device name, type, major and minor */ if (!device_name) device_name = (char*) bb_basename(path); - /* http://kernel.org/doc/pending/hotplug.txt says that only + /* + * http://kernel.org/doc/pending/hotplug.txt says that only * "/sys/block/..." is for block devices. "/sys/bus" etc is not. * But since 2.6.25 block devices are also in /sys/class/block. * We use strstr("/block/") to forestall future surprises. @@ -537,6 +624,8 @@ static void make_device(char *device_nam rule = next_rule(); #if ENABLE_FEATURE_MDEV_CONF + if (!env_matches(rule->envmatch)) + continue; if (rule->maj >= 0) { /* @maj,min rule */ if (major != rule->maj) continue; @@ -547,7 +636,7 @@ static void make_device(char *device_nam } if (rule->envvar) { /* $envvar=regex rule */ str_to_match = getenv(rule->envvar); - dbg("getenv('%s'):'%s'", rule->envvar, str_to_match); + dbg3("getenv('%s'):'%s'", rule->envvar, str_to_match); if (!str_to_match) continue; } @@ -555,7 +644,7 @@ static void make_device(char *device_nam 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); + dbg3("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; @@ -574,9 +663,8 @@ static void make_device(char *device_nam } /* else: it's final implicit "match-all" rule */ rule_matches: + dbg2("rule matched, line %d", G.parser ? G.parser->lineno : -1); #endif - dbg("rule matched"); - /* Build alias name */ alias = NULL; if (ENABLE_FEATURE_MDEV_RENAME && rule->ren_mov) { @@ -619,34 +707,30 @@ static void make_device(char *device_nam } } } - dbg("alias:'%s'", alias); + dbg3("alias:'%s'", alias); 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 + * Run @cmd on create, $cmd on delete, *cmd on any */ - if (s2 - s != (operation == OP_remove) || *s2 == '*') { - /* We are here if: '*', - * or: '@' and delete = 0, - * or: '$' and delete = 1 - */ + if ((command[0] == '@' && operation == OP_add) + || (command[0] == '$' && operation == OP_remove) + || (command[0] == '*') + ) { command++; } else { command = NULL; } } - dbg("command:'%s'", command); + dbg3("command:'%s'", command); /* "Execute" the line we found */ node_name = device_name; if (ENABLE_FEATURE_MDEV_RENAME && alias) { node_name = alias = build_alias(alias, device_name); - dbg("alias2:'%s'", alias); + dbg3("alias2:'%s'", alias); } if (operation == OP_add && major >= 0) { @@ -656,8 +740,17 @@ static void make_device(char *device_nam mkdir_recursive(node_name); *slash = '/'; } - if (G.verbose) - bb_error_msg("mknod: %s (%d,%d) %o", node_name, major, minor, rule->mode | type); + if (ENABLE_FEATURE_MDEV_CONF) { + dbg1("mknod %s (%d,%d) %o" + " %u:%u", + node_name, major, minor, rule->mode | type, + rule->ugid.uid, rule->ugid.gid + ); + } else { + dbg1("mknod %s (%d,%d) %o", + node_name, major, minor, rule->mode | type + ); + } if (mknod(node_name, rule->mode | type, makedev(major, minor)) && errno != EEXIST) bb_perror_msg("can't create '%s'", node_name); if (ENABLE_FEATURE_MDEV_CONF) { @@ -671,8 +764,7 @@ static void make_device(char *device_nam //TODO: on devtmpfs, device_name already exists and symlink() fails. //End result is that instead of symlink, we have two nodes. //What should be done? - if (G.verbose) - bb_error_msg("symlink: %s", device_name); + dbg1("symlink: %s", device_name); symlink(node_name, device_name); } } @@ -681,27 +773,21 @@ static void make_device(char *device_nam 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 (G.verbose) - bb_error_msg("running: %s", command); + dbg1("running: %s", command); if (system(command) == -1) bb_perror_msg("can't run '%s'", command); - bb_unsetenv_and_free(s1); bb_unsetenv_and_free(s); } if (operation == OP_remove && major >= -1) { if (ENABLE_FEATURE_MDEV_RENAME && alias) { if (aliaslink == '>') { - if (G.verbose) - bb_error_msg("unlink: %s", device_name); + dbg1("unlink: %s", device_name); unlink(device_name); } } - if (G.verbose) - bb_error_msg("unlink: %s", node_name); + dbg1("unlink: %s", node_name); unlink(node_name); } @@ -746,9 +832,16 @@ static int FAST_FUNC dirAction(const cha * under /sys/class/ */ if (1 == depth) { free(G.subsystem); + if (G.subsys_env) { + bb_unsetenv_and_free(G.subsys_env); + G.subsys_env = NULL; + } G.subsystem = strrchr(fileName, '/'); - if (G.subsystem) + if (G.subsystem) { G.subsystem = xstrdup(G.subsystem + 1); + G.subsys_env = xasprintf("%s=%s", "SUBSYSTEM", G.subsystem); + putenv(G.subsys_env); + } } return (depth >= MAX_SYSFS_DEPTH ? SKIP : TRUE); @@ -813,12 +906,107 @@ static void load_firmware(const char *fi full_write(loading_fd, "-1", 2); out: + xchdir("/dev"); if (ENABLE_FEATURE_CLEAN_UP) { close(firmware_fd); close(loading_fd); } } +static char *curtime(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + sprintf(G.timestr, "%u.%06u", (unsigned)tv.tv_sec % 60, (unsigned)tv.tv_usec); + return G.timestr; +} + +static void open_mdev_log(const char *seq, unsigned my_pid) +{ + int logfd = open("mdev.log", O_WRONLY | O_APPEND); + if (logfd >= 0) { + xmove_fd(logfd, STDERR_FILENO); + G.verbose = 2; + applet_name = xasprintf("%s[%s]", applet_name, seq ? seq : utoa(my_pid)); + } +} + +/* If it exists, does /dev/mdev.seq match $SEQNUM? + * If it does not match, earlier mdev is running + * in parallel, and we need to wait. + * Active mdev pokes us with SIGCHLD to check the new file. + */ +static int +wait_for_seqfile(const char *seq) +{ + /* We time out after 2 sec */ + static const struct timespec ts = { 0, 32*1000*1000 }; + int timeout = 2000 / 32; + int seq_fd = -1; + int do_once = 1; + sigset_t set_CHLD; + + sigemptyset(&set_CHLD); + sigaddset(&set_CHLD, SIGCHLD); + sigprocmask(SIG_BLOCK, &set_CHLD, NULL); + + for (;;) { + int seqlen; + char seqbuf[sizeof(int)*3 + 2]; + + if (seq_fd < 0) { + seq_fd = open("mdev.seq", O_RDWR); + if (seq_fd < 0) + break; + } + seqlen = pread(seq_fd, seqbuf, sizeof(seqbuf) - 1, 0); + if (seqlen < 0) { + close(seq_fd); + seq_fd = -1; + break; + } + seqbuf[seqlen] = '\0'; + if (seqbuf[0] == '\n') { + /* seed file: write out seq ASAP */ + xwrite_str(seq_fd, seq); + xlseek(seq_fd, 0, SEEK_SET); + dbg2("first seq written"); + break; + } + if (strcmp(seq, seqbuf) == 0) { + /* correct idx */ + break; + } + if (do_once) { + dbg2("%s waiting for '%s'", curtime(), seqbuf); + do_once = 0; + } + if (sigtimedwait(&set_CHLD, NULL, &ts) >= 0) { + dbg3("woken up"); + continue; /* don't decrement timeout! */ + } + if (--timeout == 0) { + dbg1("%s waiting for '%s'", "timed out", seqbuf); + break; + } + } + sigprocmask(SIG_UNBLOCK, &set_CHLD, NULL); + return seq_fd; +} + +static void signal_mdevs(unsigned my_pid) +{ + procps_status_t* p = NULL; + while ((p = procps_scan(p, PSSCAN_ARGV0)) != NULL) { + if (p->pid != my_pid + && p->argv0 + && strcmp(bb_basename(p->argv0), "mdev") == 0 + ) { + kill(p->pid, SIGCHLD); + } + } +} + int mdev_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int mdev_main(int argc UNUSED_PARAM, char **argv) { @@ -840,8 +1028,8 @@ int mdev_main(int argc UNUSED_PARAM, cha xchdir("/dev"); if (argv[1] && strcmp(argv[1], "-s") == 0) { - /* Scan: - * mdev -s + /* + * Scan: mdev -s */ struct stat st; @@ -853,6 +1041,8 @@ int mdev_main(int argc UNUSED_PARAM, cha G.root_major = major(st.st_dev); G.root_minor = minor(st.st_dev); + putenv((char*)"ACTION=add"); + /* ACTION_FOLLOWLINKS is needed since in newer kernels * /sys/block/loop* (for example) are symlinks to dirs, * not real directories. @@ -878,11 +1068,13 @@ int mdev_main(int argc UNUSED_PARAM, cha char *action; char *env_devname; char *env_devpath; + unsigned my_pid; + int seq_fd; smalluint op; /* Hotplug: * env ACTION=... DEVPATH=... SUBSYSTEM=... [SEQNUM=...] mdev - * ACTION can be "add" or "remove" + * ACTION can be "add", "remove", "change" * DEVPATH is like "/block/sda" or "/class/input/mice" */ action = getenv("ACTION"); @@ -893,39 +1085,20 @@ int mdev_main(int argc UNUSED_PARAM, cha if (!action || !env_devpath /*|| !G.subsystem*/) bb_show_usage(); fw = getenv("FIRMWARE"); - /* If it exists, does /dev/mdev.seq match $SEQNUM? - * If it does not match, earlier mdev is running - * in parallel, and we need to wait */ seq = getenv("SEQNUM"); - if (seq) { - int timeout = 2000 / 32; /* 2000 msec */ - do { - int seqlen; - char seqbuf[sizeof(int)*3 + 2]; - - seqlen = open_read_close("mdev.seq", seqbuf, sizeof(seqbuf) - 1); - if (seqlen < 0) { - seq = NULL; - break; - } - seqbuf[seqlen] = '\0'; - if (seqbuf[0] == '\n' /* seed file? */ - || strcmp(seq, seqbuf) == 0 /* correct idx? */ - ) { - break; - } - usleep(32*1000); - } while (--timeout); - } - { - int logfd = open("/dev/mdev.log", O_WRONLY | O_APPEND); - if (logfd >= 0) { - xmove_fd(logfd, STDERR_FILENO); - G.verbose = 1; - bb_error_msg("seq: %s action: %s", seq, action); - } - } + my_pid = getpid(); + open_mdev_log(seq, my_pid); + + seq_fd = seq ? wait_for_seqfile(seq) : -1; + + dbg1("%s " + "ACTION:%s SUBSYSTEM:%s DEVNAME:%s DEVPATH:%s" + "%s%s", + curtime(), + action, G.subsystem, env_devname, env_devpath, + fw ? " FW:" : "", fw ? fw : "" + ); snprintf(temp, PATH_MAX, "/sys%s", env_devpath); if (op == OP_remove) { @@ -935,16 +1108,18 @@ int mdev_main(int argc UNUSED_PARAM, cha if (!fw) make_device(env_devname, temp, op); } - else if (op == OP_add) { + else { make_device(env_devname, temp, op); if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE) { - if (fw) + if (op == OP_add && fw) load_firmware(fw, temp); } } - if (seq) { - xopen_xwrite_close("mdev.seq", utoa(xatou(seq) + 1)); + dbg1("%s exiting", curtime()); + if (seq_fd >= 0) { + xwrite_str(seq_fd, utoa(xatou(seq) + 1)); + signal_mdevs(my_pid); } }