/* vi: set sw=4 ts=4: */
/*
- *
* mdev - Mini udev for busybox
*
* Copyright 2005 Rob Landley <rob@landley.net>
*
* Licensed under GPL version 2, see file LICENSE in this tarball for details.
*/
-
#include "libbb.h"
#include "xregex.h"
struct globals {
int root_major, root_minor;
+ char *subsystem;
};
#define G (*(struct globals*)&bb_common_bufsiz1)
#define root_major (G.root_major)
#define root_minor (G.root_minor)
+#define subsystem (G.subsystem)
/* Prevent infinite loops in /sys symlinks */
#define MAX_SYSFS_DEPTH 3
/* NB: "mdev -s" may call us many times, do not leak memory/fds! */
static void make_device(char *path, int delete)
{
- const char *device_name;
- int major, minor, type, len;
- int mode = 0660;
#if ENABLE_FEATURE_MDEV_CONF
- struct bb_uidgid_t ugid = { 0, 0 };
parser_t *parser;
- char *tokens[5];
-#endif
-#if ENABLE_FEATURE_MDEV_EXEC
- char *command = NULL;
-#endif
-#if ENABLE_FEATURE_MDEV_RENAME
- char *alias = NULL;
- char aliaslink = aliaslink; /* for compiler */
#endif
+ const char *device_name;
+ int major, minor, type, len;
+ int mode;
char *dev_maj_min = path + strlen(path);
/* Force the configuration file settings exactly. */
if (strstr(path, "/block/"))
type = S_IFBLK;
-#if ENABLE_FEATURE_MDEV_CONF
- parser = config_open2("/etc/mdev.conf", fopen_for_read);
-
+#if !ENABLE_FEATURE_MDEV_CONF
+ mode = 0660;
+#else
/* If we have config file, look up user settings */
- while (config_read(parser, tokens, 4, 3, "# \t", PARSE_NORMAL)) {
+ parser = config_open2("/etc/mdev.conf", fopen_for_read);
+ while (1) {
regmatch_t off[1 + 9*ENABLE_FEATURE_MDEV_RENAME_REGEXP];
+ int keep_matching;
char *val;
+ struct bb_uidgid_t ugid;
+ char *tokens[4];
+# if ENABLE_FEATURE_MDEV_EXEC
+ char *command = NULL;
+# endif
+# if ENABLE_FEATURE_MDEV_RENAME
+ char *alias = NULL;
+ char aliaslink = aliaslink; /* for compiler */
+# endif
+ /* Defaults in case we won't match any line */
+ ugid.uid = ugid.gid = 0;
+ keep_matching = 0;
+ mode = 0660;
+
+ if (!config_read(parser, tokens, 4, 3, "# \t", PARSE_NORMAL)) {
+ /* End of file, create dev node with default params */
+ goto line_matches;
+ }
+
+ val = tokens[0];
+ keep_matching = ('-' == val[0]);
+ val += keep_matching; /* swallow leading dash */
/* Fields: regex uid:gid mode [alias] [cmd] */
/* 1st field: @<numeric maj,min>... */
- if (tokens[0][0] == '@') {
+ if (val[0] == '@') {
/* @major,minor[-last] */
/* (useful when name is ambiguous:
* "/sys/class/usb/lp0" and
int cmaj, cmin0, cmin1, sc;
if (major < 0)
continue; /* no dev, no match */
- sc = sscanf(tokens[0], "@%u,%u-%u", &cmaj, &cmin0, &cmin1);
+ 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; /* no match */
+ continue; /* this line doesn't match */
}
} else { /* ... or regex to match device name */
regex_t match;
int result;
+ const char *dev_name_or_subsystem = device_name;
+ if ('/' == val[0] && subsystem) {
+ dev_name_or_subsystem = subsystem;
+ val++;
+ }
/* Is this it? */
- xregcomp(&match, tokens[0], REG_EXTENDED);
- result = regexec(&match, device_name, ARRAY_SIZE(off), off, 0);
+ xregcomp(&match, val, REG_EXTENDED);
+ result = regexec(&match, dev_name_or_subsystem, ARRAY_SIZE(off), off, 0);
regfree(&match);
//bb_error_msg("matches:");
/* If not this device, skip rest of line */
/* (regexec returns whole pattern as "range" 0) */
if (result || off[0].rm_so
- || ((int)off[0].rm_eo != (int)strlen(device_name))
+ || ((int)off[0].rm_eo != (int)strlen(dev_name_or_subsystem))
) {
- continue;
+ continue; /* this line doesn't match */
}
}
- /* This line matches: stop parsing the file
- * after parsing the rest of fields */
+ /* This line matches: stop parsing the file after parsing
+ * the rest of fields unless keep_matching == 1 */
/* 2nd field: uid:gid - device ownership */
parse_chown_usergroup_or_die(&ugid, tokens[1]);
mode = strtoul(tokens[2], NULL, 8);
val = tokens[3];
- /* 4th field (opt): >alias */
-#if ENABLE_FEATURE_MDEV_RENAME
+ /* 4th field (opt): >|=alias */
+# if ENABLE_FEATURE_MDEV_RENAME
if (!val)
- break;
- aliaslink = *val;
+ goto line_matches;
+ aliaslink = val[0];
if (aliaslink == '>' || aliaslink == '=') {
- char *s;
-#if ENABLE_FEATURE_MDEV_RENAME_REGEXP
+ char *a, *s, *st;
+# if ENABLE_FEATURE_MDEV_RENAME_REGEXP
char *p;
unsigned i, n;
-#endif
- char *a = val;
- s = strchr(val, ' ');
- val = (s && s[1]) ? s+1 : NULL;
-#if ENABLE_FEATURE_MDEV_RENAME_REGEXP
+# endif
+ a = val;
+ s = strchrnul(val, ' ');
+ st = strchrnul(val, '\t');
+ if (st < s)
+ s = st;
+ val = (s[0] && s[1]) ? s+1 : NULL;
+ s[0] = '\0';
+
+# if ENABLE_FEATURE_MDEV_RENAME_REGEXP
/* substitute %1..9 with off[1..9], if any */
n = 0;
s = a;
p++;
s++;
}
-#else
+# else
alias = xstrdup(a + 1);
-#endif
+# endif
}
-#endif /* ENABLE_FEATURE_MDEV_RENAME */
+# endif /* ENABLE_FEATURE_MDEV_RENAME */
-#if ENABLE_FEATURE_MDEV_EXEC
- /* The rest (opt): command to run */
+# if ENABLE_FEATURE_MDEV_EXEC
+ /* The rest (opt): @|$|*command */
if (!val)
- break;
+ goto line_matches;
{
const char *s = "@$*";
- const char *s2 = strchr(s, *val);
+ const char *s2 = strchr(s, val[0]);
if (!s2)
bb_error_msg_and_die("bad line %u", parser->lineno);
command = xstrdup(val + 1);
}
}
-#endif
- /* end of field parsing */
- break; /* we found matching line, stop */
- } /* end of "while line is read from /etc/mdev.conf" */
-
- config_close(parser);
+# endif
+ /* End of field parsing */
+ line_matches:
#endif /* ENABLE_FEATURE_MDEV_CONF */
- 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");
+ /* "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
- chown(device_name, ugid.uid, ugid.gid);
-
-#if ENABLE_FEATURE_MDEV_RENAME
- if (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);
- }
-#endif
+ chown(device_name, ugid.uid, ugid.gid);
+# if ENABLE_FEATURE_MDEV_RENAME
+ if (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);
+ }
+# endif
#endif
- }
-
+ }
#if ENABLE_FEATURE_MDEV_EXEC
- if (command) {
- /* setenv will leak memory, use putenv/unsetenv/free */
- char *s = xasprintf("MDEV=%s", device_name);
- putenv(s);
- if (system(command) == -1)
- bb_perror_msg_and_die("can't run '%s'", command);
- unsetenv("MDEV");
- free(s);
- free(command);
- }
+ if (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);
+ }
#endif
-
- if (delete) {
- unlink(device_name);
- /* At creation time, device might have been moved
- * and a symlink might have been created. Undo that. */
+ if (delete) {
+ unlink(device_name);
+ /* At creation time, device might have been moved
+ * and a symlink might have been created. Undo that. */
#if ENABLE_FEATURE_MDEV_RENAME
- if (alias) {
- alias = build_alias(alias, device_name);
- unlink(alias);
- free(alias);
- }
+ if (alias) {
+ alias = build_alias(alias, device_name);
+ unlink(alias);
+ free(alias);
+ }
#endif
- }
+ }
+
+#if ENABLE_FEATURE_MDEV_CONF
+ /* We found matching line.
+ * Stop unless it was prefixed with '-' */
+ if (!keep_matching)
+ break;
+ } /* end of "while line is read from /etc/mdev.conf" */
+
+ config_close(parser);
+#endif /* ENABLE_FEATURE_MDEV_CONF */
}
/* File callback for /sys/ traversal */
void *userData UNUSED_PARAM,
int depth)
{
+ /* Extract device subsystem -- the name of the directory
+ * under /sys/class/ */
+ if (1 == depth) {
+ free(subsystem);
+ subsystem = strrchr(fileName, '/');
+ if (subsystem)
+ subsystem = xstrdup(subsystem + 1);
+ }
+
return (depth >= MAX_SYSFS_DEPTH ? SKIP : TRUE);
}
goto out;
loading:
- /* tell kernel we're loading by `echo 1 > /sys/$DEVPATH/loading` */
+ /* tell kernel we're loading by "echo 1 > /sys/$DEVPATH/loading" */
if (full_write(loading_fd, "1", 1) != 1)
goto out;
- /* load firmware by `cat /lib/firmware/$FIRMWARE > /sys/$DEVPATH/data */
+ /* load firmware into /sys/$DEVPATH/data */
data_fd = open("data", O_WRONLY);
if (data_fd == -1)
goto out;
cnt = bb_copyfd_eof(firmware_fd, data_fd);
- /* tell kernel result by `echo [0|-1] > /sys/$DEVPATH/loading` */
+ /* tell kernel result by "echo [0|-1] > /sys/$DEVPATH/loading" */
if (cnt > 0)
full_write(loading_fd, "0", 1);
else
xchdir("/dev");
- if (argv[1] && !strcmp(argv[1], "-s")) {
+ if (argv[1] && strcmp(argv[1], "-s") == 0) {
/* Scan:
* mdev -s
*/
* /sys/block/loop* (for example) are symlinks to dirs,
* not real directories.
* (kernel's CONFIG_SYSFS_DEPRECATED makes them real dirs,
- * but we can't enforce that on users) */
- recursive_action("/sys/block",
- ACTION_RECURSE | ACTION_FOLLOWLINKS,
- fileAction, dirAction, temp, 0);
+ * but we can't enforce that on users)
+ */
+ if (access("/sys/class/block", F_OK) != 0) {
+ /* Scan obsolete /sys/block only if /sys/class/block
+ * doesn't exist. Otherwise we'll have dupes.
+ * Also, do not complain if it doesn't exist.
+ * Some people configure kernel to have no blockdevs.
+ */
+ recursive_action("/sys/block",
+ ACTION_RECURSE | ACTION_FOLLOWLINKS | ACTION_QUIET,
+ fileAction, dirAction, temp, 0);
+ }
recursive_action("/sys/class",
ACTION_RECURSE | ACTION_FOLLOWLINKS,
fileAction, dirAction, temp, 0);
} else {
+ char *fw;
char *seq;
char *action;
char *env_path;
- char seqbuf[sizeof(int)*3 + 2];
- int seqlen = seqlen; /* for compiler */
/* Hotplug:
- * env ACTION=... DEVPATH=... [SEQNUM=...] mdev
+ * env ACTION=... DEVPATH=... SUBSYSTEM=... [SEQNUM=...] mdev
* ACTION can be "add" or "remove"
* DEVPATH is like "/block/sda" or "/class/input/mice"
*/
action = getenv("ACTION");
env_path = getenv("DEVPATH");
- if (!action || !env_path)
+ subsystem = getenv("SUBSYSTEM");
+ if (!action || !env_path /*|| !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;
+ 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)
+ if (seqlen < 0) {
+ seq = NULL;
break;
+ }
seqbuf[seqlen] = '\0';
if (seqbuf[0] == '\n' /* seed file? */
|| strcmp(seq, seqbuf) == 0 /* correct idx? */
}
snprintf(temp, PATH_MAX, "/sys%s", env_path);
- if (!strcmp(action, "remove"))
- make_device(temp, 1);
- else if (!strcmp(action, "add")) {
+ if (strcmp(action, "remove") == 0) {
+ /* 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) {
make_device(temp, 0);
-
if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE) {
- char *fw = getenv("FIRMWARE");
if (fw)
load_firmware(fw, temp);
}
}
- if (seq && seqlen >= 0) {
+ if (seq) {
xopen_xwrite_close("mdev.seq", utoa(xatou(seq) + 1));
}
}
if (ENABLE_FEATURE_CLEAN_UP)
RELEASE_CONFIG_BUFFER(temp);
- return 0;
+ return EXIT_SUCCESS;
}