*
* Licensed under GPLv2, see file LICENSE in this source tree.
*/
-
//config:config MDEV
-//config: bool "mdev"
+//config: bool "mdev (16 kb)"
//config: default y
//config: select PLATFORM_LINUX
//config: help
-//config: mdev is a mini-udev implementation for dynamically creating device
-//config: nodes in the /dev directory.
+//config: mdev is a mini-udev implementation for dynamically creating device
+//config: nodes in the /dev directory.
//config:
-//config: For more information, please see docs/mdev.txt
+//config: For more information, please see docs/mdev.txt
//config:
//config:config FEATURE_MDEV_CONF
//config: bool "Support /etc/mdev.conf"
//config: default y
//config: depends on MDEV
//config: help
-//config: Add support for the mdev config file to control ownership and
-//config: permissions of the device nodes.
+//config: Add support for the mdev config file to control ownership and
+//config: permissions of the device nodes.
//config:
-//config: For more information, please see docs/mdev.txt
+//config: For more information, please see docs/mdev.txt
//config:
//config:config FEATURE_MDEV_RENAME
//config: bool "Support subdirs/symlinks"
//config: default y
//config: depends on FEATURE_MDEV_CONF
//config: help
-//config: Add support for renaming devices and creating symlinks.
+//config: Add support for renaming devices and creating symlinks.
//config:
-//config: For more information, please see docs/mdev.txt
+//config: For more information, please see docs/mdev.txt
//config:
//config:config FEATURE_MDEV_RENAME_REGEXP
//config: bool "Support regular expressions substitutions when renaming device"
//config: default y
//config: depends on FEATURE_MDEV_RENAME
//config: help
-//config: Add support for regular expressions substitutions when renaming
-//config: device.
+//config: Add support for regular expressions substitutions when renaming
+//config: device.
//config:
//config:config FEATURE_MDEV_EXEC
//config: bool "Support command execution at device addition/removal"
//config: default y
//config: depends on FEATURE_MDEV_CONF
//config: help
-//config: This adds support for an optional field to /etc/mdev.conf for
-//config: executing commands when devices are created/removed.
+//config: This adds support for an optional field to /etc/mdev.conf for
+//config: executing commands when devices are created/removed.
//config:
-//config: For more information, please see docs/mdev.txt
+//config: For more information, please see docs/mdev.txt
//config:
//config:config FEATURE_MDEV_LOAD_FIRMWARE
-//config: bool "Support loading of firmwares"
+//config: bool "Support loading of firmware"
//config: default y
//config: depends on MDEV
//config: help
-//config: Some devices need to load firmware before they can be usable.
+//config: Some devices need to load firmware before they can be usable.
//config:
-//config: These devices will request userspace look up the files in
-//config: /lib/firmware/ and if it exists, send it to the kernel for
-//config: loading into the hardware.
+//config: These devices will request userspace look up the files in
+//config: /lib/firmware/ and if it exists, send it to the kernel for
+//config: loading into the hardware.
//applet:IF_MDEV(APPLET(mdev, BB_DIR_SBIN, BB_SUID_DROP))
//usage: "If /dev/mdev.log file exists, debug log will be appended to it."
#include "libbb.h"
+#include "common_bufsiz.h"
#include "xregex.h"
/* "mdev -s" scans /sys/class/xxx, looking for directories which have dev
unsigned rule_idx;
#endif
struct rule cur_rule;
- char timestr[sizeof("60.123456")];
+ char timestr[sizeof("HH:MM:SS.123456")];
} FIX_ALIASING;
-#define G (*(struct globals*)&bb_common_bufsiz1)
+#define G (*(struct globals*)bb_common_bufsiz1)
#define INIT_G() do { \
+ setup_common_bufsiz(); \
IF_NOT_FEATURE_MDEV_CONF(G.cur_rule.maj = -1;) \
IF_NOT_FEATURE_MDEV_CONF(G.cur_rule.mode = 0660;) \
} while (0)
}
/* 2nd field: uid:gid - device ownership */
- if (get_uidgid(&G.cur_rule.ugid, tokens[1], /*allow_numeric:*/ 1) == 0) {
+ if (get_uidgid(&G.cur_rule.ugid, tokens[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);
+ G.cur_rule.mode = bb_parse_mode(tokens[2], G.cur_rule.mode);
/* 4th field (opt): ">|=alias" or "!" to not create the node */
val = tokens[3];
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));
+ rule = xmemdup(&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;
dbg3("> G.rule_vec[G.rule_idx:%d]=%p", G.rule_idx, G.rule_vec[G.rule_idx]);
/* 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 NUL if needed)
- * path string.
+ * after NUL, but we promise to not mangle it (IOW: to restore NUL if needed).
* NB2: "mdev -s" may call us many times, do not leak memory/fds!
*
* device_name = $DEVNAME (may be NULL)
* We use strstr("/block/") to forestall future surprises.
*/
type = S_IFCHR;
- if (strstr(path, "/block/") || (G.subsystem && strncmp(G.subsystem, "block", 5) == 0))
+ if (strstr(path, "/block/") || (G.subsystem && is_prefixed_with(G.subsystem, "block")))
type = S_IFBLK;
#if ENABLE_FEATURE_MDEV_CONF
} /* for (;;) */
}
-/* File callback for /sys/ traversal */
+/* File callback for /sys/ traversal.
+ * We act only on "/sys/.../dev" (pseudo)file
+ */
static int FAST_FUNC fileAction(const char *fileName,
struct stat *statbuf UNUSED_PARAM,
void *userData,
int depth UNUSED_PARAM)
{
size_t len = strlen(fileName) - 4; /* can't underflow */
- char *scratch = userData;
-
- /* len check is for paranoid reasons */
- if (strcmp(fileName + len, "/dev") != 0 || len >= PATH_MAX)
- return FALSE;
-
- strcpy(scratch, fileName);
- scratch[len] = '\0';
- make_device(/*DEVNAME:*/ NULL, scratch, OP_add);
-
- return TRUE;
-}
-
-/* Directory callback for /sys/ traversal */
-static int FAST_FUNC dirAction(const char *fileName UNUSED_PARAM,
- struct stat *statbuf UNUSED_PARAM,
- void *userData UNUSED_PARAM,
- int depth)
-{
- /* Extract device subsystem -- the name of the directory
- * under /sys/class/ */
- if (1 == depth) {
+ char *path = userData; /* char array[PATH_MAX + SCRATCH_SIZE] */
+ char subsys[PATH_MAX];
+ int res;
+
+ /* Is it a ".../dev" file? (len check is for paranoid reasons) */
+ if (strcmp(fileName + len, "/dev") != 0 || len >= PATH_MAX - 32)
+ return FALSE; /* not .../dev */
+
+ strcpy(path, fileName);
+ path[len] = '\0';
+
+ /* Read ".../subsystem" symlink in the same directory where ".../dev" is */
+ strcpy(subsys, path);
+ strcpy(subsys + len, "/subsystem");
+ res = readlink(subsys, subsys, sizeof(subsys)-1);
+ if (res > 0) {
+ subsys[res] = '\0';
free(G.subsystem);
if (G.subsys_env) {
bb_unsetenv_and_free(G.subsys_env);
G.subsys_env = NULL;
}
- G.subsystem = strrchr(fileName, '/');
+ /* Set G.subsystem and $SUBSYSTEM from symlink's last component */
+ G.subsystem = strrchr(subsys, '/');
if (G.subsystem) {
G.subsystem = xstrdup(G.subsystem + 1);
G.subsys_env = xasprintf("%s=%s", "SUBSYSTEM", G.subsystem);
}
}
+ make_device(/*DEVNAME:*/ NULL, path, OP_add);
+
+ return TRUE;
+}
+
+/* Directory callback for /sys/ traversal */
+static int FAST_FUNC dirAction(const char *fileName UNUSED_PARAM,
+ struct stat *statbuf UNUSED_PARAM,
+ void *userData UNUSED_PARAM,
+ int depth)
+{
return (depth >= MAX_SYSFS_DEPTH ? SKIP : TRUE);
}
int firmware_fd, loading_fd;
/* check for /lib/firmware/$FIRMWARE */
- xchdir("/lib/firmware");
- firmware_fd = open(firmware, O_RDONLY); /* can fail */
+ firmware_fd = -1;
+ if (chdir("/lib/firmware") == 0)
+ firmware_fd = open(firmware, O_RDONLY); /* can fail */
/* check for /sys/$DEVPATH/loading ... give 30 seconds to appear */
xchdir(sysfs_path);
{
struct timeval tv;
gettimeofday(&tv, NULL);
- sprintf(G.timestr, "%u.%06u", (unsigned)tv.tv_sec % 60, (unsigned)tv.tv_usec);
+ sprintf(
+ strftime_HHMMSS(G.timestr, sizeof(G.timestr), &tv.tv_sec),
+ ".%06u",
+ (unsigned)tv.tv_usec
+ );
return G.timestr;
}
* Active mdev pokes us with SIGCHLD to check the new file.
*/
static int
-wait_for_seqfile(const char *seq)
+wait_for_seqfile(unsigned expected_seq)
{
/* We time out after 2 sec */
static const struct timespec ts = { 0, 32*1000*1000 };
for (;;) {
int seqlen;
- char seqbuf[sizeof(int)*3 + 2];
+ char seqbuf[sizeof(long)*3 + 2];
+ unsigned seqbufnum;
if (seq_fd < 0) {
seq_fd = open("mdev.seq", O_RDWR);
if (seq_fd < 0)
break;
+ close_on_exec_on(seq_fd);
}
seqlen = pread(seq_fd, seqbuf, sizeof(seqbuf) - 1, 0);
if (seqlen < 0) {
break;
}
seqbuf[seqlen] = '\0';
- if (seqbuf[0] == '\n') {
+ if (seqbuf[0] == '\n' || seqbuf[0] == '\0') {
/* seed file: write out seq ASAP */
- xwrite_str(seq_fd, seq);
+ xwrite_str(seq_fd, utoa(expected_seq));
xlseek(seq_fd, 0, SEEK_SET);
dbg2("first seq written");
break;
}
- if (strcmp(seq, seqbuf) == 0) {
+ seqbufnum = atoll(seqbuf);
+ if (seqbufnum == expected_seq) {
/* correct idx */
break;
}
+ if (seqbufnum > expected_seq) {
+ /* a later mdev runs already (this was seen by users to happen) */
+ /* do not overwrite seqfile on exit */
+ close(seq_fd);
+ seq_fd = -1;
+ break;
+ }
if (do_once) {
- dbg2("%s waiting for '%s'", curtime(), seqbuf);
+ dbg2("%s mdev.seq='%s', need '%u'", curtime(), seqbuf, expected_seq);
do_once = 0;
}
if (sigtimedwait(&set_CHLD, NULL, &ts) >= 0) {
continue; /* don't decrement timeout! */
}
if (--timeout == 0) {
- dbg1("%s waiting for '%s'", "timed out", seqbuf);
+ dbg1("%s mdev.seq='%s'", "timed out", seqbuf);
break;
}
}
putenv((char*)"ACTION=add");
- /* ACTION_FOLLOWLINKS is needed since in newer kernels
- * /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)
- */
- 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);
+ /* Create all devices from /sys/dev hierarchy */
+ recursive_action("/sys/dev",
+ ACTION_RECURSE | ACTION_FOLLOWLINKS,
+ fileAction, dirAction, temp, 0);
} else {
char *fw;
char *seq;
char *env_devname;
char *env_devpath;
unsigned my_pid;
+ unsigned seqnum = seqnum; /* for compiler */
int seq_fd;
smalluint op;
my_pid = getpid();
open_mdev_log(seq, my_pid);
- seq_fd = seq ? wait_for_seqfile(seq) : -1;
+ seq_fd = -1;
+ if (seq) {
+ seqnum = atoll(seq);
+ seq_fd = wait_for_seqfile(seqnum);
+ }
dbg1("%s "
"ACTION:%s SUBSYSTEM:%s DEVNAME:%s DEVPATH:%s"
dbg1("%s exiting", curtime());
if (seq_fd >= 0) {
- xwrite_str(seq_fd, utoa(xatou(seq) + 1));
+ xwrite_str(seq_fd, utoa(seqnum + 1));
signal_mdevs(my_pid);
}
}