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
  • ndev Vladimir Dronnikov
    • Re: ndev Souf Oued

Reply via email to