Hello all !
Le 30/10/2009 13:49, Vladimir Dronnikov a écrit :
Hello, list!
I've been developing my flavor of BB mdev, but eventually realized it
takes much effort to patch it. As the patch itself grew more than
original utility code, I decided to leave mdev alone and to move
changes to a new applet -- ndev -- source attached.
Reasons to do that:
* I haven't convinced BB to include a part for scanning /sys/devices
(I call it deep coldplug) -- so some other unknown utility/script is
responsible to load missed modules and so on;
* I want to employ netlink -- that way ndev is ready to become runit
service -- thus to be reliably restarted/logged/controlled. Further
improvement can be preparsing of /etc/mdev.conf upon ndev starting;
* netlink messages provide for environment variables to come in
uniform way: just read $DEVPATH/uevent line-by-line, fetch and analyse
needed strings;
I use it as follows:
* in initramfs /init: "ndev -sc" to fast create block device nodes --
they are used to locate root filesystem. -c means don't snoop into
/etc/mdev.conf
* in /sbin/init: "ndev -S&" to perform in background deep search for
all present devices (say, network adapters, too)
* during runtime as daemon to operate hotplug events: "ndev&"
I'd greatly appreciate your reviews, colleagues.
TIA,
--
Vladimir
Great !!
I made some changes to ndev:
1/ Since kernel version 2.6.27, there are a new directories
/sys/dev/(block,char),
please read linux/Documentation/ABI/testing/sysfs-dev, I used only one
time recursive_action("/sys/dev",...).
2/ Run only one instance of ndev as daemon using /dev/.ndev lock file.
3/ Use 2 options -c(onfig_file) to parse mdev.conf, -f(foregound), I
removed -s option for make ndev scan automatically /sys/dev
4/Create a simple symlinks: fd -> /proc/self/fd
Thanks
#include "libbb.h"
#include "xregex.h"
#include <linux/netlink.h>
#define SCRATCH_SIZE 80
#define OPT_c (1 << 0)
#define OPT_f (1 << 1)
static char *build_alias(char *alias, const char *device_name)
{
char *dest;
/* ">bar/": rename to bar/device_name */
/* ">bar[/]baz": rename to bar[/]baz */
dest = strrchr(alias, '/');
if (dest) { /* ">bar/[baz]" ? */
*dest = '\0'; /* mkdir bar */
bb_make_directory(alias, 0755, FILEUTILS_RECUR);
*dest = '/';
if (dest[1] == '\0') { /* ">bar/" => ">bar/device_name" */
dest = alias;
alias = concat_path_file(alias, device_name);
free(dest);
}
}
return alias;
}
/* 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 hotplug(int delete)
{
const char *devpath, *subsystem;
char *device_name, path[PATH_MAX];
int major, minor, type;
mode_t mode;
parser_t *parser = NULL;
/* for (char **env = environ; env && *env; env++) {
bb_info_msg("%s", *env);
}*/
devpath = getenv("DEVPATH");
subsystem = getenv("SUBSYSTEM");
{
char *s;
s = getenv("MAJOR"); major = s ? bb_strtou(s, NULL, 10) : -1;
s = getenv("MINOR"); minor = s ? bb_strtou(s, NULL, 10) : -1;
}
// device name is the last component of devpath
device_name = (char*) bb_basename(devpath);
type = (0 == strcmp(subsystem, "block")) ? S_IFBLK : S_IFCHR;
// make path point to "subsystem/device_name"
sprintf(path, "%s/%s", subsystem, device_name);
/* If we want and have config file, look up user settings */
if (option_mask32 & OPT_c)
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;
char aliaslink = aliaslink; /* for compiler */
// defaults in case we won't match any line
ugid.uid = ugid.gid = 0; // root:root
keep_matching = 0;
mode = 0660;
if (parser && config_read(parser, tokens, 4, 3, "# \t",
PARSE_NORMAL)) {
char *val;
char *str_to_match;
regmatch_t off[1 + 9];
/*
* If the 2nd field equals -> then assume that it is a
symlink
* for example : fd -> /proc/self/fd
*/
if (!strcmp(tokens[1],"->")) {
if (symlink(tokens[2], tokens[0]) && errno !=
EEXIST)
bb_perror_msg_and_die("symlink %s",
tokens[0]);
continue;
}
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] == '$') {
/* 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;
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 */
}
}
/* This line matches. Stop parsing after parsing
* the rest the line unless keep_matching == 1 */
/* 2nd field: uid:gid - device ownership */
if (!LONE_DASH(tokens[1]) && 0 == get_uidgid(&ugid,
tokens[1], 1))
bb_error_msg("unknown user/group %s",
tokens[1]);
/* 3rd field: mode - device permissions */
if (!LONE_DASH(tokens[2]))
bb_parse_mode(tokens[2], &mode);
val = tokens[3];
/* 4th field (opt): >|=alias */
if (val) {
aliaslink = val[0];
if (aliaslink == '>' || aliaslink == '=') {
char *a, *s, *st;
char *p;
unsigned i, n;
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 (1) {
/* 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++;
}
}
p++;
s++;
}
} else {
alias = xstrdup(a + 1);
}
}
}
if (val) {
const char *s = "$...@*";
const char *s2 = strchr(s, val[0]);
if (!s2) {
bb_error_msg("bad line %u",
parser->lineno);
if (1)
free(alias);
continue;
}
/* Are we running this command now?
* Run $cmd on delete, @cmd on create, *cmd on
both
*/
if (s2-s != delete)
command = xstrdup(val + 1);
}
}
/* End of field parsing */
/* "Execute" the line we found */
{
const char *node_name;
node_name = device_name;
if (alias)
node_name = alias = build_alias(alias,
device_name);
if (!delete && major >= 0) {
unlink(node_name); /* Ensure no device is
present */
if (mknod(node_name, mode | type,
makedev(major, minor)) && errno != EEXIST) {
bb_perror_msg("can't create %s",
node_name);
continue;
}
if (parser) {
chmod(node_name, mode);
chown(node_name, ugid.uid, ugid.gid);
}
if (alias) {
if (aliaslink == '>')
symlink(node_name, device_name);
}
}
if (command) {
if (system(command) == -1)
bb_perror_msg("can't run '%s'",
command);
free(command);
}
if (delete) {
if (alias) {
if (aliaslink == '>')
unlink(device_name);
}
unlink(node_name);
}
if (1)
free(alias);
}
/* We found matching line.
* Stop unless it was prefixed with '-' */
if (parser && !keep_matching)
break;
/* end of "while line is read from /etc/mdev.conf" */
} while (parser);
if (parser)
config_close(parser);
}
/* Directory callback for /sys/ traversal */
static int FAST_FUNC fileAction(const char *fileName, struct stat *statbuf
UNUSED_PARAM, void *temp, int depth)
{
// we must fetch device path, subsystem
#define len depth
#define buf ((char *) temp)
char *s = NULL ;
s = realpath(fileName, s);
if (s) {
// read uevent environment
strcpy(buf, s);
strcat(buf, "/uevent");
len = open_read_close(buf, buf, PATH_MAX + SCRATCH_SIZE);
// uevent environment must not be empty
if (len > 0) {
char *env, *ptr;
buf[len] = '\0';
// reset current environment
clearenv();
// put environment read from uevent
setenv("DEVPATH", s, 0);
setenv("SUBSYSTEM", strstr(s, "block")?"block":"char",
0);
setenv("ACTION", "add", 0);
for (env = strtok_r(buf, "\n", &ptr); env; env =
strtok_r(NULL, "\n", &ptr)) {
putenv(env);
}
// simulate hotplug add event
hotplug(0);
}
}
#undef buf
#undef len
return TRUE;
}
/* For the full gory details, see linux/Documentation/firmware_class/README
*
* Firmware loading works like this:
* - kernel sets FIRMWARE env var
* - userspace checks /lib/firmware/$FIRMWARE
* - userspace waits for /sys/$DEVPATH/loading to appear
* - userspace writes "1" to /sys/$DEVPATH/loading
* - userspace copies /lib/firmware/$FIRMWARE into /sys/$DEVPATH/data
* - userspace writes "0" (worked) or "-1" (failed) to /sys/$DEVPATH/loading
* - kernel loads firmware into device
*/
static void load_firmware(const char *firmware, const char *devpath)
{
int cnt;
int firmware_fd, loading_fd, data_fd;
/* check for /lib/firmware/$FIRMWARE */
xchdir("/lib/firmware");
firmware_fd = xopen(firmware, O_RDONLY);
/* in case we goto out ... */
data_fd = -1;
/* check for /sys/$DEVPATH/loading ... give 30 seconds to appear */
xchdir("/sys"); xchdir(devpath + 1); // +1 to skip leading '/'
for (cnt = 0; cnt < 30; ++cnt) {
loading_fd = open("loading", O_WRONLY);
if (loading_fd != -1)
goto loading;
sleep(1);
}
goto out;
loading:
/* tell kernel we're loading by "echo 1 > /sys/$DEVPATH/loading" */
if (full_write(loading_fd, "1", 1) != 1)
goto out;
/* 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" */
if (cnt > 0)
full_write(loading_fd, "0", 1);
else
full_write(loading_fd, "-1", 2);
out:
if (ENABLE_FEATURE_CLEAN_UP) {
close(firmware_fd);
close(loading_fd);
close(data_fd);
}
}
int ndev_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int ndev_main(int argc UNUSED_PARAM, char **argv)
{
struct sockaddr_nl sa;
struct pollfd pfd;
RESERVE_CONFIG_BUFFER(temp, PATH_MAX + SCRATCH_SIZE);
// force the configuration file settings exactly
umask(0);
xchdir("/dev");
// parse options
getopt32(argv, "cf");
recursive_action("/sys/dev",
ACTION_RECURSE,
fileAction, NULL, temp, 0);
/* Run only one instance as daemon */
if ((open(".mdev", O_WRONLY | O_CREAT | O_EXCL, 0666) < 0) && errno ==
EEXIST) {
goto skip;
}
if (!(option_mask32 & OPT_f)) { // ! -f(oreground)
bb_daemonize_or_rexec(0, argv);
}
// subscribe for UEVENT kernel messages
close(STDIN_FILENO);
xsocket(AF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); // will return
STDIN_FILENO
sa.nl_family = AF_NETLINK;
sa.nl_pad = 0;
sa.nl_pid = getpid();
sa.nl_groups = 1 << 0;
xbind(STDIN_FILENO, (struct sockaddr *) &sa, sizeof(sa));
// setup signals
bb_signals(0
+ (1 << SIGHUP)
+ (1 << SIGINT)
+ (1 << SIGTERM)
, record_signo);
// dump any string chunks we read from stdin
pfd.fd = STDIN_FILENO;
pfd.events = POLLIN;
while (!bb_got_signal && poll(&pfd, 1, -1) > 0) {
char *s = temp;
ssize_t len = recv(STDIN_FILENO, temp, PATH_MAX + SCRATCH_SIZE,
MSG_DONTWAIT);
if (len < 0) {
// N.B. really don't know what to do: bail out, or just
warn?
bb_perror_msg("recv");
continue;
}
while (len && *s) {
int i = strlen(s) + 1;
//puts(s);
if (0 == strncmp(s, "add@/", 5) || 0 == strncmp(s,
"remove@/", 8) || 0 == strncmp(s, "change@/", 8)) {
clearenv();
} else if (strchr(s, '=')) {
putenv(s);
if (0 == strncmp(s, "SEQNUM=", 7)) {
char *fw = getenv("FIRMWARE");
char *action = getenv("ACTION");
char *devpath = getenv("DEVPATH");
char *subsystem = getenv("SUBSYSTEM");
if (action && devpath && subsystem) {
if (strcmp(action, "remove") ==
0) {
/* Ignoring "remove
firmware". It was reported
* to happen and to
cause erroneous deletion
* of device nodes. */
if (!fw)
hotplug(1);
} else if (strcmp(action,
"add") == 0) {
hotplug(0);
if (fw)
load_firmware(fw, devpath);
} else {
// TODO: change event:
what to do?
}
}
}
}
s += i;
len -= i;
}
}
skip:
if (ENABLE_FEATURE_CLEAN_UP)
RELEASE_CONFIG_BUFFER(temp);
return bb_got_signal;
}
_______________________________________________
busybox mailing list
busybox@busybox.net
http://lists.busybox.net/mailman/listinfo/busybox