matching devies.
*/
+#define FOR_mdev
#include "toys.h"
// todo, open() block devices to trigger partition scanning.
+GLOBALS(
+ char *devname;
+ int root_maj, root_min, verbose;
+)
+
+
// mknod in /dev based on a path like "/sys/block/hda/hda1"
-static void make_device(char *path)
+static void make_device(char *path, char *operation)
{
- char *device_name, *s, *temp;
- int major, minor, type, len, fd;
- int mode = 0660;
+ char *device_name, *s, *temp, *alias = NULL, *cmd = NULL, buf[PATH_MAX];
+ char sign = 0, op_pref = 0, *str2 = NULL, *ln = NULL;
+ int major, minor, type, len, fd, ufd, mode = 0660;
uid_t uid = 0;
gid_t gid = 0;
temp = strrchr(path, '/');
fd = open(path, O_RDONLY);
- *temp=0;
+ *temp = 0;
temp = toybuf;
len = read(fd, temp, 64);
close(fd);
- if (len<1) return;
+ if (!strcmp(operation, "add") && len < 1) return;
temp[len] = 0;
-
- // Determine device name, type, major and minor
-
- device_name = strrchr(path, '/') + 1;
- type = path[5]=='c' ? S_IFCHR : S_IFBLK;
major = minor = 0;
- sscanf(temp, "%u:%u", &major, &minor);
+ if (sscanf(temp, "%u:%u", &major, &minor) != 2) major = -1;
+
+ memset(buf, 0, sizeof(buf));
+ device_name = TT.devname;
+ if (!device_name) {
+ sprintf(buf,"%s/uevent", path);
+ if ((ufd = open(buf, O_RDONLY)) >= 0) {
+ for (; (ln = get_line(ufd)); free(ln)) {
+ if (strstr(ln, "DEVNAME=")) {
+ device_name = ln + strlen("DEVNAME=") ;
+ break;
+ }
+ }
+ close(ufd);
+ }
+ }
+ if (!device_name) device_name = strrchr(path, '/') + 1;
+ type = S_IFCHR;
+ if (strstr(path, "/block/")) type = S_IFBLK;
// If we have a config file, look up permissions for this device
char *conf, *pos, *end;
// mmap the config file
- if (-1!=(fd = open("/etc/mdev.conf", O_RDONLY))) {
+ if (-1 != (fd = open("/etc/mdev.conf", O_RDONLY))) {
len = fdlength(fd);
conf = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
if (conf) {
int line = 0;
// Loop through lines in mmaped file
- for (pos = conf; pos-conf<len;) {
+ for (pos = conf; pos-conf < len;) {
int field;
char *end2;
line++;
// find end of this line
- for(end = pos; end-conf<len && *end!='\n'; end++);
+ for(end = pos; end-conf < len && *end != '\n'; end++);
- // Three fields: regex, uid:gid, mode
- for (field = 3; field; field--) {
+ // Fields: regex uid:gid mode [alias] [cmd]
+ for (field = 3; field >= 0; field--) {
// Skip whitespace
- while (pos<end && isspace(*pos)) pos++;
- if (pos==end || *pos=='#') break;
+ while (pos < end && isspace(*pos)) pos++;
+ if (pos == end || *pos == '#') break;
for (end2 = pos;
- end2<end && !isspace(*end2) && *end2!='#'; end2++);
+ end2 < end && !isspace(*end2) && *end2 != '#'; end2++);
switch(field) {
// Regex to match this device
case 3:
- {
- char *regex = strndup(pos, end2-pos);
- regex_t match;
- regmatch_t off;
- int result;
-
- // Is this it?
- xregcomp(&match, regex, REG_EXTENDED);
- result=regexec(&match, device_name, 1, &off, 0);
- regfree(&match);
- free(regex);
-
- // If not this device, skip rest of line
- if (result || off.rm_so
- || off.rm_eo!=strlen(device_name))
- goto end_line;
-
- break;
- }
- // uid:gid
- case 2:
- {
- char *s2;
-
- // Find :
- for(s = pos; s<end2 && *s!=':'; s++);
- if (s==end2) goto end_line;
-
- // Parse UID
- uid = strtoul(pos,&s2,10);
- if (s!=s2) {
- struct passwd *pass;
- char *str = strndup(pos, s-pos);
- pass = getpwnam(str);
- free(str);
- if (!pass) goto end_line;
- uid = pass->pw_uid;
+ {
+ char *str = strndup(pos, end2-pos), *regex;
+ regex_t match;
+ regmatch_t off;
+ int result;
+
+ if (*str == '$') {
+ char *p = strchr(str, '=');
+
+ if (!p) {
+ free(str);
+ error_msg("bad $envvar=regex");
+ goto end_line;
+ } else {
+ p++;
+ regex = xstrdup(p);
+ free(str);
+ }
+ } else {
+ regex = xstrdup(str);
+ free(str);
+ }
+ // Is this it?
+ if (*regex == '@') {
+ int maj, min1, min2;
+
+ int ret = sscanf(regex, "@%u,%u-%u", &maj, &min1, &min2);
+ free(regex);
+ if (ret < 2 || maj < 0) {
+ error_msg("bad @maj,min @line %d",line);
+ goto end_line;
+ }
+ if (ret == 2) min2 = min1;
+ if (!(major == maj && ( min1 <= minor || minor <= min2)))
+ goto end_line;
+ } else {
+ xregcomp(&match, regex, REG_EXTENDED);
+ result = regexec(&match, device_name, 1, &off, 0);
+ regfree(&match);
+ free(regex);
+
+ // If not this device, skip rest of line
+ if (result || off.rm_so
+ || off.rm_eo != strlen(device_name))
+ goto end_line;
+ }
+ break;
}
- s++;
- // parse GID
- gid = strtoul(s,&s2,10);
- if (end2!=s2) {
- struct group *grp;
- char *str = strndup(s, end2-s);
- grp = getgrnam(str);
- free(str);
- if (!grp) goto end_line;
- gid = grp->gr_gid;
+ // uid:gid
+ case 2:
+ {
+ char *s2;
+
+ // Find :
+ for(s = pos; s < end2 && *s != ':'; s++);
+ if (s == end2) goto end_line;
+
+ // Parse UID
+ uid = strtoul(pos,&s2,10);
+ if (s != s2) {
+ struct passwd *pass;
+ char *str = xstrndup(pos, s-pos);
+
+ pass = getpwnam(str);
+ free(str);
+ if (!pass) goto end_line;
+ uid = pass->pw_uid;
+ }
+ s++;
+ // parse GID
+ gid = strtoul(s,&s2,10);
+ if (end2 != s2) {
+ struct group *grp;
+ char *str = xstrndup(s, end2-s);
+
+ grp = getgrnam(str);
+ free(str);
+ if (!grp) goto end_line;
+ gid = grp->gr_gid;
+ }
+ break;
}
- break;
- }
- // mode
+ // mode
case 1:
- {
- mode = strtoul(pos, &pos, 8);
- if (pos!=end2) goto end_line;
- goto found_device;
- }
+ {
+ mode = strtoul(pos, &pos, 8);
+ if (pos != end2) goto end_line;
+ //goto found_device;
+ break;
+ }
+ // handle 4th and 5th field
+ case 0:
+ {
+ char *str1 = NULL;
+
+ str2 = xstrndup(pos, end-pos);
+ if (strchr(">=!", str2[0])) {
+ str1 = strtok(str2, " ");
+ sign = str1[0];
+ alias = str1 + 1;
+ str1 += strlen(alias) +2;
+ } else str1 = str2;
+ if (str1 && strchr("$@*",str1[0])) {
+ op_pref = str1[0];
+ cmd = str1 + 1;
+ } else error_exit("Bad Line %d", line);
+ goto found_device;
+ }
}
- pos=end2;
+ pos = end2;
}
end_line:
// Did everything parse happily?
- if (field && field!=3) error_exit("Bad line %d", line);
-
+ if (field && field != 3) error_exit("bad line %d", line);
// Next line
pos = ++end;
}
}
}
- sprintf(temp, "/dev/%s", device_name);
- if (mknod(temp, mode | type, makedev(major, minor)) && errno != EEXIST)
- perror_exit("mknod %s failed", temp);
+ if (operation && !((op_pref == '@' && !strcmp(operation, "add")) ||
+ (op_pref == '$' && !strcmp(operation, "remove")) || (op_pref == '*')))
+ cmd = NULL;
+
+ if (alias) {
+ if (alias[strlen(alias)-1] == '/') sprintf(temp,"%s%s",alias, device_name);
+ else sprintf(temp,"%s",alias);
+ }
+ if (sign != '!' && operation && !strcmp(operation, "add")) { //not to create device otherwise
+
+ if (mknod(device_name, mode | type, makedev(major, minor)) && errno != EEXIST)
+ perror_msg("mknod /dev/%s failed", device_name);
+ if (CFG_MDEV_CONF) {
+ chmod(device_name, mode);
+ chown(device_name, uid, gid);
+ }
+
+ if (TT.root_maj == major && TT.root_min == minor) symlink(device_name, "root");
+
+ if (alias && (sign == '>' || sign == '=')) {
+ mkpathat(AT_FDCWD, alias, 0, 2);
+ if (rename(device_name, temp)) perror_exit("rename temp");
+ if (sign == '>') symlink(temp, device_name);
+ }
+ }
+ if (cmd) {
+ char *str = xmprintf("%s=%s", "MDEV", device_name);
+
+ putenv(str);
+ if (system(cmd) == -1) perror_msg("can't run '%s'", cmd);
+ unsetenv(str);
+ free(str);
+ }
- if (CFG_MDEV_CONF) mode=chown(temp, uid, gid);
+ if (operation && !strcmp(operation, "remove") && major >= -1) {
+ if (alias && sign == '>') unlink(temp);
+ unlink(device_name);
+ }
+ free(str2);
+ free(ln);
}
static int callback(struct dirtree *node)
// Entries in /sys/class/block aren't char devices, so skip 'em. (We'll
// get block devices out of /sys/block.)
if(!strcmp(node->name, "block")) return 0;
+ if (!dirtree_notdotdot(node)) return 0;
// Does this directory have a "dev" entry in it?
// This is path based because the hotplug callbacks are
if (S_ISDIR(node->st.st_mode) || S_ISLNK(node->st.st_mode)) {
- int len=4;
+ int len = 4;
char *dev = dirtree_path(node, &len);
strcpy(dev+len, "/dev");
- if (!access(dev, R_OK)) make_device(dev);
+ if (!access(dev, R_OK)) make_device(dev, "add");
free(dev);
}
// Circa 2.6.25 the entries more than 2 deep are all either redundant
// (mouse#, event#) or unnamed (every usb_* entry is called "device").
- return (node->parent && node->parent->parent) ? 0 : DIRTREE_RECURSE;
+ return (node->parent && node->parent->parent) ? 0 :
+ DIRTREE_RECURSE|DIRTREE_SYMFOLLOW;
}
-void mdev_main(void)
+#if 0
+static int sequence_file(char *seq)
{
- // Handle -s
+ static struct timespec tspec = { 0, 32*1000*1000 }; // time out after 2 secs
+ int time_out = 2000 / 32, fd = -1;
+ sigset_t sg;
+
+ sigemptyset(&sg);
+ sigaddset(&sg, SIGCHLD);
+ sigprocmask(SIG_BLOCK, &sg, NULL);
+
+ for (;;) {
+ ssize_t slen;
+ char buf[sizeof(int)*3 + 2];
- if (toys.optflags) {
- dirtree_read("/sys/class", callback);
- dirtree_read("/sys/block", callback);
+ if (fd < 0 && (fd = open("/dev/mdev.seq", O_RDWR)) < 0) break;
+ if ((slen = pread(fd, buf, sizeof(buf) - 1, 0)) < 0) {
+ close(fd);
+ fd = -1;
+ break;
+ }
+ buf[slen] = '\0';
+ if (buf[0] == '\n') {
+ xwrite(fd, seq, strlen(seq));
+ xlseek(fd, 0, SEEK_SET);
+ break;
+ }
+ if (!strcmp(seq, buf)) break;
+ if (sigtimedwait(&sg, NULL, &tspec) >= 0) continue;
+ if (!--time_out) break;
}
+ sigprocmask(SIG_UNBLOCK, &sg, NULL);
+ return fd;
+}
+#endif
- // hotplug support goes here
+static void firmware_load(char *fware, char *sysfs_path)
+{
+ int count, fd, lfd, dfd;
+
+ xchdir("/lib/firmware");
+ fd = open(fware, O_RDONLY);
+ // check for /sys/$DEVPATH/loading ... give 30 seconds to appear
+ xchdir(sysfs_path);
+ for (count = 0; count < 30; ++count) {
+ lfd = open("loading", O_WRONLY);
+ if (lfd >= 0) goto load;
+ sleep(1);
+ }
+ goto end;
+
+load:
+ if (fd >= 0) {
+ if (writeall(lfd, "1", 1) != 1) goto end;
+ dfd = open("data", O_WRONLY);
+ if (dfd < 0) goto end;
+ xsendfile(fd, dfd);
+ close(dfd);
+ }
+ if (fd >= 0) writeall(lfd, "0", 1);
+ else writeall(lfd, "-1", 2);
+
+end:
+ xchdir("/dev");
+ close(lfd);
+ close(fd);
+}
+
+void mdev_main(void)
+{
+
+ char buf[PATH_MAX];
+ struct dirtree *root;
+
+ umask(0);
+ xchdir("/dev");
+ if (toys.optflags & FLAG_s) {
+ struct stat st;
+
+ xstat("/", &st);
+ TT.root_maj = major(st.st_dev);
+ TT.root_min = minor(st.st_dev);
+ putenv((char*)"ACTION=add");
+
+ root = dirtree_add_node(0, "/sys/class", 1);
+ if (root) dirtree_handle_callback(root, callback);
+
+ root = dirtree_add_node(0, "/sys/block", 1);
+ if (root) dirtree_handle_callback(root, callback);
+ } else { // Hotplug handling
+ char *fware, *action, *devpath;
+ int logfd;
+
+ action = getenv("ACTION");
+ TT.devname = getenv("DEVNAME");
+ devpath = getenv("DEVPATH");
+
+ if (!action || !devpath) {
+ toys.exithelp++;
+ error_exit("env var action/devpath not found");
+ }
+ fware = getenv("FIRMWARE");
+
+ if ((logfd = open("/dev/mdev.log", O_WRONLY | O_APPEND)) >= 0) {
+ dup2(logfd, 2); // 2 -> STDERR_FILENO
+ TT.verbose = 1;
+ }
+ //TODO give sequencei file support.
+
+ // seqfd = seq_num ? sequence_file(seq_num) : -1;
+ snprintf(buf, PATH_MAX, "/sys%s/dev", devpath);
+ if (action && !strcmp("remove", action) && !fware)
+ make_device(buf, action);
+ else {
+ make_device(buf, action);
+ buf[strlen(buf) - 4] = '\0'; //remove /dev from end.
+ if (action && !strcmp("add", action) && fware)
+ firmware_load(fware, buf);
+ }
+ }
}