I suppose it's time to dig out code from the secret archives of my secret lair again. Someone named Vladimir Dronnikov called this "ndev" and proposed it as a patch to Busybox in 2009. I dug it out and separated it from Busybox, and probably made some other changes I don't remember, for some nefarious agenda I again don't remember. It is a modified version of mdev that seems to do some of the things you all have been talking about. I'm not maintaining it in any way, so you all are welcome to do whatever you want with it. I have no idea whether it works or not, other than that it apparently was once useful to me.
William Haddon On 03/11/2015 11:21:42 AM, Laurent Bercot wrote: > On 11/03/2015 15:56, Harald Becker wrote: > > And one point to state clearly: I do not want to go the way to fork > a > > project (that is the worst expected), but I'm at a point, I like / > > need to have Busybox to allow for some additional or modified > > solutions to fit my preferences > > I don't understand that binary choice... You can work on your > own project without forking Busybox. You can use both Busybox and > your own project on your systems. Busybox is a set of tools, why > should it be THE set of tools ? > > I'm not sure how heavily mdev [-s] relies on libbb and how hard it > would be to extract the source and make it into a standalone, easily > hackable project, but if you want to test architecture ideas, that's > the way to go - copy stuff and tinker with it until you have > something > satisfying. Then, if upstream wants to integrate your modifications, > if you find a reasonable compromise to merge, great; if not, you > still > have your harald-mdev, and you can still use it along with Busybox - > you'll have two binaries instead of one, but even on whatever tiny > noMMU you can work on, you'll be fine. > > That does not preclude design discussions, which I like, and which > can happen here (unless moderators object), and people like Isaac, > me, > or obviously Denys, wouldn't be so defensive - because it's now about > your project, not Busybox; you have the final say, and it's totally > fine, because I don't have to use harald-mdev if I don't want to. > > Forks are bad, but alternatives are good. > > -- > Laurent > _______________________________________________ > busybox mailing list > [email protected] > http://lists.busybox.net/mailman/listinfo/busybox >
/* * ndev - hardware detection daemon - based on Busybox's * mdev * * Copyright 2005 Rob Landley <[email protected]> * Copyright 2005 Frank Sorenson <[email protected]> * Copyright 2009 Vladimir Dronnikov <[email protected]> * Copyright 2013, 2014 William Haddon <[email protected]> * * Licensed under GPL version 2; see the file COPYING for details. */ /* Set your tabstop to 4 */ #define _BSD_SOURCE #include <dirent.h> #include <errno.h> #include <fcntl.h> #include <grp.h> #include <libgen.h> #include <limits.h> #include <poll.h> #include <pwd.h> #include <regex.h> #include <signal.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <syslog.h> #include <unistd.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/types.h> #include <linux/netlink.h> #ifndef NDEV_CONF #define NDEV_CONF "/etc/ndev.conf" #endif #define SCRATCH_SIZE 80 int scan = 0; void emsg(char *fmt, ...) { va_list val; va_start(val, fmt); vfprintf(stderr, fmt, val); va_end(val); va_start(val, fmt); vsyslog(LOG_ERR, fmt, val); va_end(val); } void pemsg(char *fmt, ...) { va_list val; char *err, *nfmt; size_t l1, l2; l1 = strlen(fmt); err = strerror(errno); l2 = strlen(err); nfmt = malloc(l1 + l2 + 4); if (!nfmt) nfmt = fmt; else snprintf(nfmt, l1+l2+4, "%s: %s\n", fmt, err); va_start(val, fmt); vfprintf(stderr, nfmt, val); va_end(val); va_start(val, fmt); vsyslog(LOG_ERR, nfmt, val); va_end(val); if (nfmt != fmt) free(nfmt); } char *_strchrnul(char *s, int c) { char *r; r = strchr(s, c); if (!r) return s + strlen(s); return r; } /* Writes an entire buffer to a file descriptor. Returns count on success and -1 on error. */ ssize_t full_write(int fd, char *buf, size_t count) { ssize_t len; ssize_t result; len = count; while (len) { result = write(fd, buf, len); if (result < 0 && errno != EINTR) return result; if (result > 0) len -= result; } return count; } /* Reads a given number of bytes from a file descriptor. Returns the number of bytes read on success and -1 on error. Less than the full number of bytes are read only if the file ends */ ssize_t full_read(int fd, char *buf, size_t count) { ssize_t len; ssize_t result; len = count; while (len) { result = read(fd, buf, len); if (result < 0 && errno != EINTR) return result; if (result == 0) break; if (result > 0) len -= result; } return count - len; } ssize_t copy_until_eof(int infd, int outfd) { char buffer[1024]; ssize_t result; ssize_t len; len = 0; while (1) { result = read(infd, buffer, 1024); if (result < 0 && errno != EINTR) return result; if (result == 0) break; if (result > 0) { len += result; if (full_write(outfd, buffer, result) < 0) return -1; } } return len; } ssize_t open_read_close(char *filename, char *buf, size_t len) { ssize_t result; int fd; fd = open(filename, O_RDONLY); if (fd < 0) return -1; result = full_read(fd, buf, len); close(fd); return result; } /* Recursively makes a directory. Returns 0 on success; -1 on error. */ int make_directory(char *path, mode_t mode) { char *s, *t, c; s = t = path; while (1) { for (; *s && *s != '/'; s++); c = *s; if (s != t) { *s = 0; if (mkdir(path, mode) < 0) return -1; *s = c; } if (!c) break; t = ++s; } return 0; } /* Form a path from a directory path and a file. Return NULL on failure due to out of memory. Memory is allocated -- must be freed */ char *concat_path_file(char *path, char *file) { char *result; size_t l1, l2; l1 = strlen(path); l2 = strlen(file); result = malloc(l1 + l2 + 2); if (!result) return result; strcpy(result, path); if (path[l1-1] != '/') { result[l1] = '/'; strcpy(result+l1+1, file); result[l1+l2+1] = 0; } else { strcpy(result+l1, file); result[l1+l2] = 0; } return result; } /* Parse a mode string. Return 0 on success; -1 on failure. */ int parse_mode(char *s, mode_t *mode) { char *eptr; long val; val = strtol(s, &eptr, 8); if (*eptr) return -1; *mode = val; return 0; } /* Builds an alias path. Returns NULL on failure. If the return value is a different address from the alias parameter, the alias parameter has been freed. If the new address is not NULL, the memory has been allocated. */ static char *build_alias(char *alias, 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 */ if (make_directory(alias, 0755) < 0 && errno != EEXIST) { free(alias); return NULL; } *dest = '/'; if (dest[1] == '\0') { /* ">bar/" => ">bar/device_name" */ dest = alias; alias = concat_path_file(alias, device_name); free(dest); if (!alias) return NULL; } } return alias; } /* Read a logical line of the config file. Return the number of physical lines read. Return -1 on error. The pointer to the data from the line in allocated memory is returned in line. This pointer is freed if -1 is returned. 0 is only returned at EOF. */ int config_read_line(FILE *file, char **line) { size_t s, u; s = 128; char *tmp; *line = malloc(s); if (!(*line)) return -1; if (!fgets(*line, s, file)) { if (feof(file)) { free(*line); return 0; } else { free(*line); return -1; } } u = strlen(*line); while (u + 1 == s && *line[u] != '\n' && !feof(file)) { s += 128; tmp = realloc(*line, s); if (!tmp) { free(*line); return -1; } *line = tmp; if (!fgets(*line + u, s - u, file) && !feof(file)) { free(*line); return -1; } u = strlen(*line); } return 1; } /* Allocates memory into which the region between the two pointers is copied. Returns NULL on allocation error */ char *copy_substring(char *begin, char *end) { char *result = malloc(end - begin + 1); if (!result) return result; memcpy(result, begin, end - begin); result[end-begin] = 0; return result; } /* Reads and tokenizes a line of the config file. Each token is separately allocated. The first character of delims is a comment separator. Return number of physical lines read or -1 on error. 0 is only returned on EOF. */ int config_read(FILE *file, char **tokens, int maxtokens, char *delims) { char *line, *s, *t, *tokstart; int nline, i; nline = config_read_line(file, &line); if (nline <= 0) return nline; s = line; for (i = 0; i < maxtokens; i++) { while (1) { if (*s == 0 || *s == '\n' || *s == *delims) goto endline; for (t = delims+1; *t; t++) if (*s == *t) goto nchar; tokstart = s; break; nchar: s++; } while (1) { if (*s == 0 || *s == '\n' || *s == *delims) goto ntok; if (i+1 < maxtokens) for (t = delims+1; *t; t++) if (*s == *t) goto ntok; s++; continue; ntok: tokens[i] = copy_substring(tokstart, s); break; } } endline: for (; i < maxtokens; i++) tokens[i] = NULL; free(line); return nline; } struct uidgid_t { uid_t uid; gid_t gid; }; /* Decodes a user/group pair. Return 0 on success; -1 on failure. Do not alter the uidgid_t struct on failure. */ int get_uidgid(struct uidgid_t *ug, char *s) { char *t; struct passwd *p; struct group *g; uid_t uid; gid_t gid; for (t = s; *t && *t != ':'; t++); if (!*t) return -1; *t = 0; t++; p = getpwnam(s); if (!p) return -1; uid = p->pw_uid; g = getgrnam(t); if (!g) return -1; gid = p->pw_gid; ug->uid = uid; ug->gid = gid; *(t-1) = ':'; return 0; } struct cfgline { char *pattern; char *action; struct uidgid_t ugid; mode_t mode; int lineno; }; struct cfgline *cfg; /* Read and parse the configuration file */ struct cfgline *read_config() { struct cfgline *result, *tmp; size_t result_size; size_t result_no; FILE *cfgfile; int lineno; cfgfile = fopen(NDEV_CONF, "r"); if (!cfgfile) { pemsg("error opening "NDEV_CONF); return NULL; } result = malloc((result_size = 32) * sizeof(struct cfgline)); lineno = 0; result_no = 0; while (1) { int r; char *tokens[4]; r = config_read(cfgfile, tokens, 4, "# \t"); if (!r) /* EOF */ break; if (r < 0) { pemsg("error reading "NDEV_CONF); fclose(cfgfile); return NULL; } if (result_no + 1 == result_size) { tmp = realloc(result, (result_size *= 2) * sizeof(struct cfgline)); if (!tmp) { emsg("out of memory reading "NDEV_CONF); fclose(cfgfile); free(result); return NULL; } result = tmp; } lineno += r; if (!tokens[0]) goto cont; /* 1st field: regex */ result[result_no].pattern = strdup(tokens[0]); /* 2nd field: uid:gid - device ownership */ if (!strcmp(tokens[1], "-")) result[result_no].ugid.uid = result[result_no].ugid.gid = 0; else if (get_uidgid(&(result[result_no].ugid), tokens[1])) { free(result[result_no].pattern); emsg(NDEV_CONF" line %d: unknown user/group %s\n", lineno, tokens[1]); goto cont; } /* 3rd field: mode - device permissions */ if (!strcmp(tokens[2], "-")) result[result_no].mode = 0660; else if (parse_mode(tokens[2], &(result[result_no].mode))) { free(result[result_no].pattern); emsg(NDEV_CONF" line %d: invalid mode %s\n", lineno, tokens[2]); goto cont; } /* 4th field (opt): >|=alias */ if (tokens[3]) result[result_no].action = strdup(tokens[3]); else result[result_no].action = NULL; result[result_no].lineno = lineno; result_no++; cont: if (tokens[0]) free(tokens[0]); if (tokens[1]) free(tokens[1]); if (tokens[2]) free(tokens[2]); if (tokens[3]) free(tokens[3]); } fclose(cfgfile); result[result_no].pattern = NULL; return result; } /* Respond to a hotplug event, creating a node in /dev */ void hotplug(int delete) { char *devpath, *subsystem; char *device_name, path[PATH_MAX]; int major, minor, type, keep_matching; mode_t mode; struct uidgid_t ugid; struct cfgline *p; if (chdir("/dev")) { pemsg("cannot access /dev"); return; } devpath = getenv("DEVPATH"); if (!devpath) { emsg("invalid hotplug request\n"); return; } subsystem = getenv("SUBSYSTEM"); if (!subsystem) { emsg("invalid hotplug request\n"); return; } char *s; s = getenv("MAJOR"); major = s ? atol(s) : -1; s = getenv("MINOR"); minor = s ? atol(s) : -1; /* device name is the last component of devpath */ device_name = (char*) basename(devpath); type = (0 == strcmp(subsystem, "block")) ? S_IFBLK : S_IFCHR; /* make path point to "subsystem/device_name" */ if (snprintf(path, PATH_MAX, "%s/%s", subsystem, device_name) >= PATH_MAX) { emsg("pathname %s/%s is too long\n", subsystem, device_name); return; } for (p = cfg; p && p->pattern; p++) { int keep_matching, r; struct uidgid_t ugid; mode_t mode; char *command = NULL; char *alias = NULL; char aliaslink; char *val; char *str_to_match; regmatch_t off[10]; val = p->pattern; keep_matching = ('-' == val[0]); val += keep_matching; /* swallow leading dash */ if (val[0] == '$') { /* regex to match an environment variable */ char *eq = strchr(++val, '='); if (!eq) goto cont; /* we must be very cautious to fix this as soon as possible as this info is read into memory only once */ *eq = '\0'; str_to_match = getenv(val); *eq = '='; if (!str_to_match) goto cont; val = eq + 1; } else /* regex to match [subsystem/]device_name */ /* Match against either "subsystem/device_name" * or "device_name" alone */ str_to_match = strchr(val, '/') ? path : device_name; /* either way, the string str_to_match must be compared with * the regular expression val */ regex_t match; int result; if (regcomp(&match, val, REG_EXTENDED)) goto cont; result = regexec(&match, str_to_match, 10, off, 0); regfree(&match); /* 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))) goto cont; /* This line doesn't match */ /* This line matches. Stop parsing after parsing * the rest the line unless keep_matching == 1 */ ugid = p->ugid; mode = p->mode; val = p->action; 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'; /* substitute %1..9 with off[1..9], if any */ n = 0; s = a; while (*s) if (*s++ == '%') n++; p = alias = calloc(strlen(a)+n*strlen(str_to_match),1); 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++; } } } if (val) { const char *s = "$@*"; const char *s2 = strchr(s, val[0]); if (!s2) { emsg("bad line %u", p->lineno); goto cont; } /* Are we running this command now? * Run $cmd on delete, @cmd on create, *cmd on both */ if (s2-s != delete) { command = strdup(val + 1); if (!command) emsg("out of memory\n"); } } /* 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 (!alias) pemsg("can't create alias %s", alias); } if (!delete && major >= 0) { unlink(node_name); /* Ensure no device is present */ if (mknod(node_name, mode | type, makedev(major, minor))) { pemsg("can't create %s", node_name); goto cont; } if (chown(node_name, ugid.uid, ugid.gid)) pemsg("can't set owner on %s", node_name); if (alias && aliaslink == '>' && symlink(node_name, device_name)) pemsg("can't make alias %s", node_name); } if (command) { if (system(command) == -1) pemsg("can't run '%s'", command); free(command); } if (delete) { if (alias && aliaslink == '>') unlink(device_name); unlink(node_name); } /* We found matching line. * Stop unless it was prefixed with '-' */ if (!keep_matching) break; cont: if (alias) free(alias); } } /* Handle a /sys event directory. */ void event_dir(char *filename) { int len; /* we must fetch device path, subsystem */ char buf[PATH_MAX + SCRATCH_SIZE]; /* fetch device subsystem */ char s[PATH_MAX]; strcpy(buf, filename); strncat(buf, "/subsystem", PATH_MAX + SCRATCH_SIZE - strlen(buf) - 1); len = readlink(buf, s, PATH_MAX); if (len < 0) { /* If the symlink doesn't exist, it's not an error; we just shouldn't be here. */ if (errno != ENOENT) pemsg("error reading symlink %s", buf); return; } /* subsystem must not be empty */ if (len > 0) { s[len] = '\0'; /* s now contains the value of subsystem link: the last path component is subsystem itself */ /* read uevent environment */ strcpy(buf + strlen(filename), "/uevent"); len = open_read_close(buf, buf, PATH_MAX + SCRATCH_SIZE); if (len < 0) pemsg("error reading uevent environment"); /* uevent environment must not be empty */ if (len > 0) { char *env, *ptr; buf[len] = '\0'; /* reset current environment */ if (clearenv()) { pemsg("can't clear environment to simulate hotplug"); return; } /* put environment read from uevent */ if (setenv("DEVPATH", filename + (sizeof("/sys")-1), 0)) { pemsg("can't set environment to simulate hotplug"); return; } if (setenv("SUBSYSTEM", basename(s), 0)) { pemsg("can't set environment to simulate hotplug"); return; } if (setenv("ACTION", "add", 0)) { pemsg("can't set environment to simulate hotplug"); return; } for (env = strtok_r(buf, "\n", &ptr); env; env = strtok_r(NULL, "\n", &ptr)) { if (putenv(env)) { pemsg("can't set environment to simulate hotplug"); return; } } /* simulate hotplug add event */ hotplug(0); } } } /* 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 */ if (chdir("/lib/firmware")) { pemsg("Can not open /lib/firmware"); return; } /* in case we goto out ... */ data_fd = -1; loading_fd = -1; firmware_fd = open(firmware, O_RDONLY); if (firmware_fd < 0) { pemsg("Can not open firmware %s", firmware); goto out; } /* check for /sys/$DEVPATH/loading ... give 30 seconds to appear */ if (chdir("/sys")) { pemsg("Can not open /sys"); goto out; } if (chdir(devpath + 1)) { /* +1 to skip leading '/' */ pemsg("Can not open %s", devpath+1); goto out; } for (cnt = 0; cnt < 30; ++cnt) { loading_fd = open("loading", O_WRONLY); if (loading_fd != -1) { pemsg("Can not communicate with kernel"); goto loading; } sleep(1); } emsg("Can not communicate with kernel\n"); goto out; loading: /* tell kernel we're loading by "echo 1 > /sys/$DEVPATH/loading" */ if (full_write(loading_fd, "1", 1) != 1) { pemsg("Can not communicate with kernel"); goto out; } /* load firmware into /sys/$DEVPATH/data */ data_fd = open("data", O_WRONLY); if (data_fd == -1) { pemsg("Can not transfer firmware to kernel"); goto out; } cnt = copy_until_eof(firmware_fd, data_fd); if (cnt < 0) { pemsg("Can not transfer firmware to kernel"); goto out; } /* 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: close(firmware_fd); close(loading_fd); close(data_fd); } int got_signal; void record_signo(int signo) { got_signal = signo; } void coldplug_scan(char *filename) { size_t l1, l2; ssize_t len; DIR *dir; struct dirent *entry; char *subdir; char link[PATH_MAX+1]; struct dirent link_entry; struct stat st; dir = opendir(filename); if (!dir) { pemsg("can not open %s", filename); return; } while (entry = readdir(dir)) { if (chdir(filename) < 0) { pemsg("can not chdir to %s", filename); return; } if (!entry) { pemsg("can not list directory %s", filename); goto ex; } if (entry->d_type == DT_DIR && strcmp(entry->d_name, ".") && strcmp(entry->d_name, "..")) { /* Construct the full path so we can chdir to it */ l1 = strlen(filename); l2 = strlen(entry->d_name); subdir = malloc(l1 + l2 + 2); if (!subdir) { emsg("out of memory\n"); goto ex; } strcpy(subdir, filename); subdir[l1] = '/'; strcpy(subdir+l1+1, entry->d_name); subdir[l1+l2+1] = 0; event_dir(subdir); coldplug_scan(subdir); free(subdir); } } ex: closedir(dir); } int main(int argc, char **argv) { int opt, _wait; sigset_t tmpsigset, oldsigset; char temp[PATH_MAX + SCRATCH_SIZE]; /* force the configuration file settings exactly */ umask(0); if (chdir("/dev")) { pemsg("Can not open /dev"); return 1; } /* read the configuration file */ cfg = read_config(); if (!cfg) return 1; _wait = 0; /* parse options */ while ((opt = getopt(argc, argv, "sw")) != -1) { switch (opt) { case 's': scan = 1; break; case 'w': _wait = 1; break; } } /* coldplug branch */ if (scan) { if (_wait) { emsg("-s and -w options not intended to be used together"); return 1; } coldplug_scan("/sys/block"); coldplug_scan("/sys/class"); coldplug_scan("/sys/devices"); /* uevent listener */ } else { struct sockaddr_nl sa; struct pollfd pfd; close(STDIN_FILENO); if (socket(AF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT)) { pemsg("Can not connect to netlink"); return 1; } sa.nl_family = AF_NETLINK; sa.nl_pad = 0; sa.nl_pid = getpid(); sa.nl_groups = 1 << 0; if (bind(STDIN_FILENO, (struct sockaddr *) &sa, sizeof(sa))) { pemsg("Can not connect to netlink"); return 1; } /* block signals except during the ppoll call to avoid race conditions */ sigemptyset(&tmpsigset); sigaddset(&tmpsigset, SIGHUP); sigaddset(&tmpsigset, SIGINT); sigaddset(&tmpsigset, SIGTERM); sigprocmask(SIG_BLOCK, &tmpsigset, &oldsigset); /* set signal handlers */ signal(SIGHUP, record_signo); signal(SIGINT, record_signo); signal(SIGTERM, record_signo); /* dump any string chunks we read from stdin */ pfd.fd = STDIN_FILENO; pfd.events = POLLIN; if (_wait) kill(getppid(), SIGTERM); while (ppoll(&pfd, 1, NULL, &oldsigset) > 0 && !got_signal) { char *s = temp; ssize_t len = recv(STDIN_FILENO, temp, PATH_MAX + SCRATCH_SIZE, MSG_DONTWAIT); if (len < 0) { pemsg("Can not receive from kernel on netlink"); continue; } while (len && *s) { int i = strlen(s) + 1; if (0 == strncmp(s, "add@/", 5) || 0 == strncmp(s, "remove@/", 8) || 0 == strncmp(s, "change@/", 8)) { if (clearenv()) { pemsg("Can not clear environment to simulate hotplug"); return 1; } } else if (strchr(s, '=')) { if (putenv(s)) { pemsg("Can not set environment to simulate hotplug"); return 1; } 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 { /* We're ignoring "change" events for now */ } } } } s += i; len -= i; } } } return got_signal; }
_______________________________________________ busybox mailing list [email protected] http://lists.busybox.net/mailman/listinfo/busybox
