X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=util-linux%2Fmdev.c;h=80549d1b7c98d0193f6697506ce04130177ec92b;hb=f5914992f316f8a628505067e108e7ba5a9590ba;hp=a3275f881c8a0c17ff024bbb6e4e4f41f3cb8641;hpb=c7cc5a9432d2224d4e0fe3cf8ae72abb7ef25e2c;p=oweals%2Fbusybox.git diff --git a/util-linux/mdev.c b/util-linux/mdev.c index a3275f881..80549d1b7 100644 --- a/util-linux/mdev.c +++ b/util-linux/mdev.c @@ -15,6 +15,8 @@ * contains "4:0\n". Directory name is taken as device name, path component * directly after /sys/class/ as subsystem. In this example, "tty0" and "tty". * Then mdev creates the /dev/device_name node. + * If /sys/class/.../dev file does not exist, mdev still may act + * on this device: see "@|$|*command args..." parameter in config file. * * mdev w/o parameters is called as hotplug helper. It takes device * and subsystem names from $DEVPATH and $SUBSYSTEM, extracts @@ -29,20 +31,34 @@ * * [-][subsystem/]device user:grp mode [>|=path] [@|$|*command args...] * [-]@maj,min[-min2] user:grp mode [>|=path] [@|$|*command args...] - * - * The device name or "subsystem/device" combo is matched against 1st field - * (which is a regex), or maj,min is matched against 1st field. + * [-]$envvar=val user:grp mode [>|=path] [@|$|*command args...] * * Leading minus in 1st field means "don't stop on this line", otherwise * search is stopped after the matching line is encountered. * - * When line matches, the device node is created, chmod'ed and chown'ed. - * Then it moved to path, and if >path, a symlink to moved node is created + * The device name or "subsystem/device" combo is matched against 1st field + * (which is a regex), or maj,min is matched against 1st field, + * or specified environment variable (as regex) is matched against 1st field. + * + * $envvar=val format is useful for loading modules for hot-plugged devices + * which do not have driver loaded yet. In this case /sys/class/.../dev + * does not exist, but $MODALIAS is set to needed module's name + * (actually, an alias to it) by kernel. This rule instructs mdev + * to load the module and exit: + * $MODALIAS=.* 0:0 660 @modprobe "$MODALIAS" + * The kernel will generate another hotplug event when /sys/class/.../dev + * file appears. + * + * When line matches, the device node is created, chmod'ed and chown'ed, + * moved to path, and if >path, a symlink to moved node is created, + * all this if /sys/class/.../dev exists. * Examples: * =loop/ - moves to /dev/loop * >disk/sda%1 - moves to /dev/disk/sdaN, makes /dev/sdaN a symlink - * Then "command args" is executed (via sh -c 'command args'). + * + * Then "command args..." is executed (via sh -c 'command args...'). * @:execute on creation, $:on deletion, *:on both. + * This happens regardless of /sys/class/.../dev existence. */ struct globals { @@ -85,19 +101,19 @@ static char *build_alias(char *alias, const char *device_name) return alias; } -/* mknod in /dev based on a path like "/sys/block/hda/hda1" */ -/* NB: "mdev -s" may call us many times, do not leak memory/fds! */ +/* mknod in /dev based on a path like "/sys/block/hda/hda1" + * NB1: path parameter needs to have SCRATCH_SIZE scratch bytes + * after NUL, but we promise to not mangle (IOW: to restore if needed) + * path string. + * NB2: "mdev -s" may call us many times, do not leak memory/fds! + */ static void make_device(char *path, int delete) { - const char *device_name; + char *device_name; int major, minor, type, len; - int mode; - char *dev_maj_min = path + strlen(path); + mode_t mode; parser_t *parser; - /* Force the configuration file settings exactly. */ - umask(0); - /* 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. @@ -105,21 +121,23 @@ static void make_device(char *path, int delete) */ major = -1; if (!delete) { + char *dev_maj_min = path + strlen(path); + strcpy(dev_maj_min, "/dev"); len = open_read_close(path, dev_maj_min + 1, 64); - *dev_maj_min++ = '\0'; + *dev_maj_min = '\0'; if (len < 1) { if (!ENABLE_FEATURE_MDEV_EXEC) return; - /* no "dev" file, so just try to run script */ - *dev_maj_min = '\0'; - } else if (sscanf(dev_maj_min, "%u:%u", &major, &minor) != 2) { + /* no "dev" file, but we can still run scripts + * based on device name */ + } else if (sscanf(++dev_maj_min, "%u:%u", &major, &minor) != 2) { major = -1; } } /* Determine device name, type, major and minor */ - device_name = bb_basename(path); + device_name = (char*) bb_basename(path); /* 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, @@ -139,9 +157,7 @@ static void make_device(char *path, int delete) parser = config_open2("/etc/mdev.conf", fopen_for_read); do { - regmatch_t off[1 + 9 * ENABLE_FEATURE_MDEV_RENAME_REGEXP]; int keep_matching; - char *val, *name; struct bb_uidgid_t ugid; char *tokens[4]; char *command = NULL; @@ -156,19 +172,22 @@ static void make_device(char *path, int delete) 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 */ - name = strchr(val, '/') ? path : (char *) device_name; + str_to_match = strchr(val, '/') ? path : device_name; /* Fields: regex uid:gid mode [alias] [cmd] */ - /* 1st field: @... */ if (val[0] == '@') { - /* @major,minor[-last] */ + /* @major,minor[-minor2] */ /* (useful when name is ambiguous: * "/sys/class/usb/lp0" and * "/sys/class/printer/lp0") */ @@ -182,15 +201,29 @@ static void make_device(char *path, int delete) ) { continue; /* this line doesn't match */ } - } else { /* ... or regex to match device name */ + 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 = '='; + } + /* else: regex to match [subsystem/]device_name */ + + { regex_t match; int result; - /* Is this it? */ xregcomp(&match, val, REG_EXTENDED); - result = regexec(&match, name, ARRAY_SIZE(off), off, 0); + 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; @@ -199,23 +232,25 @@ static void make_device(char *path, int delete) // device_name + off[i].rm_so); //} - /* If not this device, skip rest of line */ + /* 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(name)) + || ((int)off[0].rm_eo != (int)strlen(str_to_match)) ) { continue; /* this line doesn't match */ } } - - /* This line matches: stop parsing the file after parsing - * the rest of fields unless keep_matching == 1 */ + line_matches: + /* This line matches. Stop parsing after parsing + * the rest the line unless keep_matching == 1 */ /* 2nd field: uid:gid - device ownership */ - parse_chown_usergroup_or_die(&ugid, tokens[1]); + if (get_uidgid(&ugid, tokens[1], 1) == 0) + bb_error_msg("unknown user/group %s on line %d", tokens[1], parser->lineno); /* 3rd field: mode - device permissions */ - mode = strtoul(tokens[2], NULL, 8); + /* mode = strtoul(tokens[2], NULL, 8); */ + bb_parse_mode(tokens[2], &mode); val = tokens[3]; /* 4th field (opt): >|=alias */ @@ -243,7 +278,7 @@ static void make_device(char *path, int delete) if (*s++ == '%') n++; - p = alias = xzalloc(strlen(a) + n * strlen(name)); + p = alias = xzalloc(strlen(a) + n * strlen(str_to_match)); s = a + 1; while (*s) { *p = *s; @@ -251,7 +286,7 @@ static void make_device(char *path, int delete) i = (s[1] - '0'); if (i <= 9 && off[i].rm_so >= 0) { n = off[i].rm_eo - off[i].rm_so; - strncpy(p, name + off[i].rm_so, n); + strncpy(p, str_to_match + off[i].rm_so, n); p += n - 1; s++; } @@ -269,8 +304,12 @@ static void make_device(char *path, int delete) const char *s = "$@*"; const char *s2 = strchr(s, val[0]); - if (!s2) - bb_error_msg_and_die("bad line %u", parser->lineno); + if (!s2) { + bb_error_msg("bad line %u", parser->lineno); + if (ENABLE_FEATURE_MDEV_RENAME) + free(alias); + continue; + } /* Are we running this command now? * Run $cmd on delete, @cmd on create, *cmd on both @@ -283,53 +322,53 @@ static void make_device(char *path, int delete) /* End of field parsing */ /* "Execute" the line we found */ - - if (!delete && major >= 0) { - if (ENABLE_FEATURE_MDEV_RENAME) - unlink(device_name); - if (mknod(device_name, mode | type, makedev(major, minor)) && errno != EEXIST) - bb_perror_msg_and_die("mknod %s", device_name); - if (major == root_major && minor == root_minor) - symlink(device_name, "root"); - if (ENABLE_FEATURE_MDEV_CONF) { - chmod(device_name, mode); - chown(device_name, ugid.uid, ugid.gid); - } - if (ENABLE_FEATURE_MDEV_RENAME && alias) { - alias = build_alias(alias, device_name); - /* move the device, and optionally - * make a symlink to moved device node */ - if (rename(device_name, alias) == 0 && aliaslink == '>') - symlink(alias, device_name); - free(alias); + { + 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 == root_major && minor == 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); + } } - } - if (ENABLE_FEATURE_MDEV_EXEC && command) { - /* setenv will leak memory, use putenv/unsetenv/free */ - char *s = xasprintf("%s=%s", "MDEV", device_name); - char *s1 = xasprintf("%s=%s", "SUBSYSTEM", subsystem); - putenv(s); - putenv(s1); - if (system(command) == -1) - bb_perror_msg_and_die("can't run '%s'", command); - unsetenv("SUBSYSTEM"); - free(s1); - unsetenv("MDEV"); - free(s); - free(command); - } + 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", subsystem); + putenv(s); + putenv(s1); + if (system(command) == -1) + bb_perror_msg("can't run '%s'", command); + unsetenv("SUBSYSTEM"); + free(s1); + unsetenv("MDEV"); + free(s); + free(command); + } - if (delete) { - unlink(device_name); - /* At creation time, device might have been moved - * and a symlink might have been created. Undo that. */ + if (delete) { + if (ENABLE_FEATURE_MDEV_RENAME && alias) { + if (aliaslink == '>') + unlink(device_name); + } + unlink(node_name); + } - if (ENABLE_FEATURE_MDEV_RENAME && alias) { - alias = build_alias(alias, device_name); - unlink(alias); + if (ENABLE_FEATURE_MDEV_RENAME) free(alias); - } } /* We found matching line. @@ -447,9 +486,11 @@ int mdev_main(int argc UNUSED_PARAM, char **argv) /* We can be called as hotplug helper */ /* Kernel cannot provide suitable stdio fds for us, do it ourself */ - bb_sanitize_stdio(); + /* Force the configuration file settings exactly */ + umask(0); + xchdir("/dev"); if (argv[1] && strcmp(argv[1], "-s") == 0) { @@ -486,6 +527,9 @@ int mdev_main(int argc UNUSED_PARAM, char **argv) char *seq; char *action; char *env_path; + static const char keywords[] ALIGN1 = "remove\0add\0"; + enum { OP_remove = 0, OP_add }; + smalluint op; /* Hotplug: * env ACTION=... DEVPATH=... SUBSYSTEM=... [SEQNUM=...] mdev @@ -498,7 +542,7 @@ int mdev_main(int argc UNUSED_PARAM, char **argv) if (!action || !env_path /*|| !subsystem*/) bb_show_usage(); fw = getenv("FIRMWARE"); - + op = index_in_strings(keywords, action); /* 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 */ @@ -525,14 +569,14 @@ int mdev_main(int argc UNUSED_PARAM, char **argv) } snprintf(temp, PATH_MAX, "/sys%s", env_path); - if (strcmp(action, "remove") == 0) { + if (op == OP_remove) { /* Ignoring "remove firmware". It was reported * to happen and to cause erroneous deletion * of device nodes. */ if (!fw) make_device(temp, 1); } - else if (strcmp(action, "add") == 0) { + else if (op == OP_add) { make_device(temp, 0); if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE) { if (fw)