10f66f451Sopenharmony_ci/* mdev.c - Populate /dev directory and handle hotplug events 20f66f451Sopenharmony_ci * 30f66f451Sopenharmony_ci * Copyright 2005, 2008 Rob Landley <rob@landley.net> 40f66f451Sopenharmony_ci * Copyright 2005 Frank Sorenson <frank@tuxrocks.com> 50f66f451Sopenharmony_ci 60f66f451Sopenharmony_ciUSE_MDEV(NEWTOY(mdev, "s", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_UMASK)) 70f66f451Sopenharmony_ci 80f66f451Sopenharmony_ciconfig MDEV 90f66f451Sopenharmony_ci bool "mdev" 100f66f451Sopenharmony_ci default n 110f66f451Sopenharmony_ci help 120f66f451Sopenharmony_ci usage: mdev [-s] 130f66f451Sopenharmony_ci 140f66f451Sopenharmony_ci Create devices in /dev using information from /sys. 150f66f451Sopenharmony_ci 160f66f451Sopenharmony_ci -s Scan all entries in /sys to populate /dev 170f66f451Sopenharmony_ci 180f66f451Sopenharmony_ciconfig MDEV_CONF 190f66f451Sopenharmony_ci bool "Configuration file for mdev" 200f66f451Sopenharmony_ci default y 210f66f451Sopenharmony_ci depends on MDEV 220f66f451Sopenharmony_ci help 230f66f451Sopenharmony_ci The mdev config file (/etc/mdev.conf) contains lines that look like: 240f66f451Sopenharmony_ci hd[a-z][0-9]* 0:3 660 250f66f451Sopenharmony_ci (sd[a-z]) root:disk 660 =usb_storage 260f66f451Sopenharmony_ci 270f66f451Sopenharmony_ci Each line must contain three whitespace separated fields. The first 280f66f451Sopenharmony_ci field is a regular expression matching one or more device names, 290f66f451Sopenharmony_ci the second and third fields are uid:gid and file permissions for 300f66f451Sopenharmony_ci matching devices. Fourth field is optional. It could be used to change 310f66f451Sopenharmony_ci device name (prefix '='), path (prefix '=' and postfix '/') or create a 320f66f451Sopenharmony_ci symlink (prefix '>'). 330f66f451Sopenharmony_ci*/ 340f66f451Sopenharmony_ci 350f66f451Sopenharmony_ci#include "toys.h" 360f66f451Sopenharmony_ci#include <stdbool.h> 370f66f451Sopenharmony_ci 380f66f451Sopenharmony_ci// mknod in /dev based on a path like "/sys/block/hda/hda1" 390f66f451Sopenharmony_cistatic void make_device(char *path) 400f66f451Sopenharmony_ci{ 410f66f451Sopenharmony_ci char *device_name = 0, *custom_name = 0, *s, *temp; 420f66f451Sopenharmony_ci bool create_symlink = false; 430f66f451Sopenharmony_ci int major = 0, minor = 0, type, len, fd, mode = 0660; 440f66f451Sopenharmony_ci uid_t uid = 0; 450f66f451Sopenharmony_ci gid_t gid = 0; 460f66f451Sopenharmony_ci 470f66f451Sopenharmony_ci if (path) { 480f66f451Sopenharmony_ci 490f66f451Sopenharmony_ci // Try to read major/minor string, returning if we can't 500f66f451Sopenharmony_ci temp = strrchr(path, '/'); 510f66f451Sopenharmony_ci fd = open(path, O_RDONLY); 520f66f451Sopenharmony_ci *temp = 0; 530f66f451Sopenharmony_ci len = read(fd, toybuf, 64); 540f66f451Sopenharmony_ci close(fd); 550f66f451Sopenharmony_ci if (len<1) return; 560f66f451Sopenharmony_ci toybuf[len] = 0; 570f66f451Sopenharmony_ci 580f66f451Sopenharmony_ci // Determine device type, major and minor 590f66f451Sopenharmony_ci 600f66f451Sopenharmony_ci type = path[5]=='c' ? S_IFCHR : S_IFBLK; 610f66f451Sopenharmony_ci sscanf(toybuf, "%u:%u", &major, &minor); 620f66f451Sopenharmony_ci } else { 630f66f451Sopenharmony_ci // if (!path), do hotplug 640f66f451Sopenharmony_ci 650f66f451Sopenharmony_ci if (!(temp = getenv("MODALIAS"))) xrun((char *[]){"modprobe", temp, 0}); 660f66f451Sopenharmony_ci if (!(temp = getenv("SUBSYSTEM"))) return; 670f66f451Sopenharmony_ci type = strcmp(temp, "block") ? S_IFCHR : S_IFBLK; 680f66f451Sopenharmony_ci if (!(temp = getenv("MAJOR"))) return; 690f66f451Sopenharmony_ci sscanf(temp, "%u", &major); 700f66f451Sopenharmony_ci if (!(temp = getenv("MINOR"))) return; 710f66f451Sopenharmony_ci sscanf(temp, "%u", &minor); 720f66f451Sopenharmony_ci if (!(path = getenv("DEVPATH"))) return; 730f66f451Sopenharmony_ci device_name = getenv("DEVNAME"); 740f66f451Sopenharmony_ci } 750f66f451Sopenharmony_ci if (!device_name) 760f66f451Sopenharmony_ci device_name = strrchr(path, '/') + 1; 770f66f451Sopenharmony_ci 780f66f451Sopenharmony_ci // as in linux/drivers/base/core.c, device_get_devnode() 790f66f451Sopenharmony_ci while ((temp = strchr(device_name, '!'))) { 800f66f451Sopenharmony_ci *temp = '/'; 810f66f451Sopenharmony_ci } 820f66f451Sopenharmony_ci 830f66f451Sopenharmony_ci // If we have a config file, look up permissions for this device 840f66f451Sopenharmony_ci 850f66f451Sopenharmony_ci if (CFG_MDEV_CONF) { 860f66f451Sopenharmony_ci char *conf, *pos, *end; 870f66f451Sopenharmony_ci bool optional_field_valid = false; 880f66f451Sopenharmony_ci 890f66f451Sopenharmony_ci // mmap the config file 900f66f451Sopenharmony_ci if (-1!=(fd = open("/etc/mdev.conf", O_RDONLY))) { 910f66f451Sopenharmony_ci int line = 0; 920f66f451Sopenharmony_ci 930f66f451Sopenharmony_ci len = fdlength(fd); 940f66f451Sopenharmony_ci conf = xmmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0); 950f66f451Sopenharmony_ci 960f66f451Sopenharmony_ci // Loop through lines in mmaped file 970f66f451Sopenharmony_ci for (pos = conf; pos-conf<len;) { 980f66f451Sopenharmony_ci int field; 990f66f451Sopenharmony_ci char *end2; 1000f66f451Sopenharmony_ci 1010f66f451Sopenharmony_ci line++; 1020f66f451Sopenharmony_ci // find end of this line 1030f66f451Sopenharmony_ci for(end = pos; end-conf<len && *end!='\n'; end++); 1040f66f451Sopenharmony_ci 1050f66f451Sopenharmony_ci // Four fields (last is optional): regex, uid:gid, mode [, name|path ] 1060f66f451Sopenharmony_ci // For example: (sd[a-z])1 root:disk 660 =usb_storage_p1 1070f66f451Sopenharmony_ci for (field = 4; field; field--) { 1080f66f451Sopenharmony_ci // Skip whitespace 1090f66f451Sopenharmony_ci while (pos<end && isspace(*pos)) pos++; 1100f66f451Sopenharmony_ci if (pos==end || *pos=='#') break; 1110f66f451Sopenharmony_ci for (end2 = pos; 1120f66f451Sopenharmony_ci end2<end && !isspace(*end2) && *end2!='#'; end2++); 1130f66f451Sopenharmony_ci switch(field) { 1140f66f451Sopenharmony_ci // Regex to match this device 1150f66f451Sopenharmony_ci case 4: 1160f66f451Sopenharmony_ci { 1170f66f451Sopenharmony_ci char *regex = strndup(pos, end2-pos); 1180f66f451Sopenharmony_ci regex_t match; 1190f66f451Sopenharmony_ci regmatch_t off; 1200f66f451Sopenharmony_ci int result; 1210f66f451Sopenharmony_ci 1220f66f451Sopenharmony_ci // Is this it? 1230f66f451Sopenharmony_ci xregcomp(&match, regex, REG_EXTENDED); 1240f66f451Sopenharmony_ci result=regexec(&match, device_name, 1, &off, 0); 1250f66f451Sopenharmony_ci regfree(&match); 1260f66f451Sopenharmony_ci free(regex); 1270f66f451Sopenharmony_ci 1280f66f451Sopenharmony_ci // If not this device, skip rest of line 1290f66f451Sopenharmony_ci if (result || off.rm_so 1300f66f451Sopenharmony_ci || off.rm_eo!=strlen(device_name)) 1310f66f451Sopenharmony_ci goto end_line; 1320f66f451Sopenharmony_ci 1330f66f451Sopenharmony_ci break; 1340f66f451Sopenharmony_ci } 1350f66f451Sopenharmony_ci // uid:gid 1360f66f451Sopenharmony_ci case 3: 1370f66f451Sopenharmony_ci { 1380f66f451Sopenharmony_ci char *s2; 1390f66f451Sopenharmony_ci 1400f66f451Sopenharmony_ci // Find : 1410f66f451Sopenharmony_ci for(s = pos; s<end2 && *s!=':'; s++); 1420f66f451Sopenharmony_ci if (s==end2) goto end_line; 1430f66f451Sopenharmony_ci 1440f66f451Sopenharmony_ci // Parse UID 1450f66f451Sopenharmony_ci uid = strtoul(pos,&s2,10); 1460f66f451Sopenharmony_ci if (s!=s2) { 1470f66f451Sopenharmony_ci struct passwd *pass; 1480f66f451Sopenharmony_ci char *str = strndup(pos, s-pos); 1490f66f451Sopenharmony_ci pass = getpwnam(str); 1500f66f451Sopenharmony_ci free(str); 1510f66f451Sopenharmony_ci if (!pass) goto end_line; 1520f66f451Sopenharmony_ci uid = pass->pw_uid; 1530f66f451Sopenharmony_ci } 1540f66f451Sopenharmony_ci s++; 1550f66f451Sopenharmony_ci // parse GID 1560f66f451Sopenharmony_ci gid = strtoul(s,&s2,10); 1570f66f451Sopenharmony_ci if (end2!=s2) { 1580f66f451Sopenharmony_ci struct group *grp; 1590f66f451Sopenharmony_ci char *str = strndup(s, end2-s); 1600f66f451Sopenharmony_ci grp = getgrnam(str); 1610f66f451Sopenharmony_ci free(str); 1620f66f451Sopenharmony_ci if (!grp) goto end_line; 1630f66f451Sopenharmony_ci gid = grp->gr_gid; 1640f66f451Sopenharmony_ci } 1650f66f451Sopenharmony_ci break; 1660f66f451Sopenharmony_ci } 1670f66f451Sopenharmony_ci // mode 1680f66f451Sopenharmony_ci case 2: 1690f66f451Sopenharmony_ci { 1700f66f451Sopenharmony_ci char *beg_pos = pos; 1710f66f451Sopenharmony_ci mode = strtoul(pos, &pos, 8); 1720f66f451Sopenharmony_ci if (pos == beg_pos) { 1730f66f451Sopenharmony_ci // The line is bad because mode setting could not be 1740f66f451Sopenharmony_ci // converted to numeric value. 1750f66f451Sopenharmony_ci goto end_line; 1760f66f451Sopenharmony_ci } 1770f66f451Sopenharmony_ci break; 1780f66f451Sopenharmony_ci } 1790f66f451Sopenharmony_ci // Try to look for name or path (optional field) 1800f66f451Sopenharmony_ci case 1: 1810f66f451Sopenharmony_ci { 1820f66f451Sopenharmony_ci if(pos < end2){ 1830f66f451Sopenharmony_ci //char *name = strndup(pos, end2-pos); 1840f66f451Sopenharmony_ci char *name = malloc(end2-pos+1); 1850f66f451Sopenharmony_ci if(name){ 1860f66f451Sopenharmony_ci strncpy(name, pos, end2-pos+1); 1870f66f451Sopenharmony_ci name[end2-pos] = '\0'; 1880f66f451Sopenharmony_ci switch(name[0]){ 1890f66f451Sopenharmony_ci case '>': 1900f66f451Sopenharmony_ci create_symlink = true; 1910f66f451Sopenharmony_ci case '=': 1920f66f451Sopenharmony_ci custom_name = strdup(&name[1]); 1930f66f451Sopenharmony_ci break; 1940f66f451Sopenharmony_ci case '!': 1950f66f451Sopenharmony_ci device_name = NULL; 1960f66f451Sopenharmony_ci break; 1970f66f451Sopenharmony_ci default: 1980f66f451Sopenharmony_ci free(name); 1990f66f451Sopenharmony_ci goto end_line; 2000f66f451Sopenharmony_ci } 2010f66f451Sopenharmony_ci free(name); 2020f66f451Sopenharmony_ci optional_field_valid = true; 2030f66f451Sopenharmony_ci } 2040f66f451Sopenharmony_ci } 2050f66f451Sopenharmony_ci goto found_device; 2060f66f451Sopenharmony_ci } 2070f66f451Sopenharmony_ci } 2080f66f451Sopenharmony_ci pos=end2; 2090f66f451Sopenharmony_ci } 2100f66f451Sopenharmony_ciend_line: 2110f66f451Sopenharmony_ci // Did everything parse happily? 2120f66f451Sopenharmony_ci // Note: Last field is optional. 2130f66f451Sopenharmony_ci if ((field>1 || (field==1 && !optional_field_valid)) && field!=4) 2140f66f451Sopenharmony_ci error_exit("Bad line %d", line); 2150f66f451Sopenharmony_ci // Next line 2160f66f451Sopenharmony_ci pos = ++end; 2170f66f451Sopenharmony_ci } 2180f66f451Sopenharmony_cifound_device: 2190f66f451Sopenharmony_ci munmap(conf, len); 2200f66f451Sopenharmony_ci } 2210f66f451Sopenharmony_ci close(fd); 2220f66f451Sopenharmony_ci } 2230f66f451Sopenharmony_ci 2240f66f451Sopenharmony_ci if(device_name) { 2250f66f451Sopenharmony_ci if(custom_name) { 2260f66f451Sopenharmony_ci sprintf(toybuf, "/dev/%s", custom_name); 2270f66f451Sopenharmony_ci if(custom_name[strlen(custom_name) - 1] == '/') { 2280f66f451Sopenharmony_ci DIR *dir; 2290f66f451Sopenharmony_ci dir = opendir(toybuf); 2300f66f451Sopenharmony_ci if(dir) closedir(dir); 2310f66f451Sopenharmony_ci else mkdir(toybuf, 0755); 2320f66f451Sopenharmony_ci } 2330f66f451Sopenharmony_ci } 2340f66f451Sopenharmony_ci else 2350f66f451Sopenharmony_ci sprintf(toybuf, "/dev/%s", device_name); 2360f66f451Sopenharmony_ci 2370f66f451Sopenharmony_ci if ((temp=getenv("ACTION")) && !strcmp(temp, "remove")) { 2380f66f451Sopenharmony_ci unlink(toybuf); 2390f66f451Sopenharmony_ci return; 2400f66f451Sopenharmony_ci } 2410f66f451Sopenharmony_ci 2420f66f451Sopenharmony_ci if (strchr(device_name, '/')) mkpath(toybuf); 2430f66f451Sopenharmony_ci if (mknod(toybuf, mode | type, dev_makedev(major, minor)) && 2440f66f451Sopenharmony_ci errno != EEXIST) 2450f66f451Sopenharmony_ci perror_exit("mknod %s failed", toybuf); 2460f66f451Sopenharmony_ci if(create_symlink){ 2470f66f451Sopenharmony_ci char *symlink_name = malloc(sizeof("/dev/") + strlen(device_name) + 1); 2480f66f451Sopenharmony_ci if(symlink_name == NULL) 2490f66f451Sopenharmony_ci perror_exit("malloc failed while creating symlink to %s", toybuf); 2500f66f451Sopenharmony_ci sprintf(symlink_name, "/dev/%s", device_name); 2510f66f451Sopenharmony_ci if(symlink(toybuf, symlink_name)){ 2520f66f451Sopenharmony_ci free(symlink_name); 2530f66f451Sopenharmony_ci perror_exit("symlink creation failed for %s", toybuf); 2540f66f451Sopenharmony_ci } 2550f66f451Sopenharmony_ci free(symlink_name); 2560f66f451Sopenharmony_ci } 2570f66f451Sopenharmony_ci 2580f66f451Sopenharmony_ci if (type == S_IFBLK) close(open(toybuf, O_RDONLY)); // scan for partitions 2590f66f451Sopenharmony_ci 2600f66f451Sopenharmony_ci if (CFG_MDEV_CONF) mode=chown(toybuf, uid, gid); 2610f66f451Sopenharmony_ci } 2620f66f451Sopenharmony_ci} 2630f66f451Sopenharmony_ci 2640f66f451Sopenharmony_cistatic int callback(struct dirtree *node) 2650f66f451Sopenharmony_ci{ 2660f66f451Sopenharmony_ci // Entries in /sys/class/block aren't char devices, so skip 'em. (We'll 2670f66f451Sopenharmony_ci // get block devices out of /sys/block.) 2680f66f451Sopenharmony_ci if(!strcmp(node->name, "block")) return 0; 2690f66f451Sopenharmony_ci 2700f66f451Sopenharmony_ci // Does this directory have a "dev" entry in it? 2710f66f451Sopenharmony_ci // This is path based because the hotplug callbacks are 2720f66f451Sopenharmony_ci if (S_ISDIR(node->st.st_mode) || S_ISLNK(node->st.st_mode)) { 2730f66f451Sopenharmony_ci int len=4; 2740f66f451Sopenharmony_ci char *dev = dirtree_path(node, &len); 2750f66f451Sopenharmony_ci strcpy(dev+len, "/dev"); 2760f66f451Sopenharmony_ci if (!access(dev, R_OK)) make_device(dev); 2770f66f451Sopenharmony_ci free(dev); 2780f66f451Sopenharmony_ci } 2790f66f451Sopenharmony_ci 2800f66f451Sopenharmony_ci // Circa 2.6.25 the entries more than 2 deep are all either redundant 2810f66f451Sopenharmony_ci // (mouse#, event#) or unnamed (every usb_* entry is called "device"). 2820f66f451Sopenharmony_ci 2830f66f451Sopenharmony_ci if (node->parent && node->parent->parent) return 0; 2840f66f451Sopenharmony_ci return DIRTREE_RECURSE|DIRTREE_SYMFOLLOW; 2850f66f451Sopenharmony_ci} 2860f66f451Sopenharmony_ci 2870f66f451Sopenharmony_civoid mdev_main(void) 2880f66f451Sopenharmony_ci{ 2890f66f451Sopenharmony_ci // Handle -s 2900f66f451Sopenharmony_ci 2910f66f451Sopenharmony_ci if (toys.optflags) { 2920f66f451Sopenharmony_ci dirtree_read("/sys/class", callback); 2930f66f451Sopenharmony_ci if (!access("/sys/block", R_OK)) dirtree_read("/sys/block", callback); 2940f66f451Sopenharmony_ci } else { // hotplug support 2950f66f451Sopenharmony_ci make_device(NULL); 2960f66f451Sopenharmony_ci } 2970f66f451Sopenharmony_ci} 298