Home | History | Annotate | Download | only in pending
      1 /* mdev.c - Populate /dev directory and handle hotplug events
      2  *
      3  * Copyright 2005, 2008 Rob Landley <rob (at) landley.net>
      4  * Copyright 2005 Frank Sorenson <frank (at) tuxrocks.com>
      5 
      6 USE_MDEV(NEWTOY(mdev, "s", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_UMASK))
      7 
      8 config MDEV
      9   bool "mdev"
     10   default n
     11   help
     12     usage: mdev [-s]
     13 
     14     Create devices in /dev using information from /sys.
     15 
     16     -s	Scan all entries in /sys to populate /dev
     17 
     18 config MDEV_CONF
     19   bool "Configuration file for mdev"
     20   default y
     21   depends on MDEV
     22   help
     23     The mdev config file (/etc/mdev.conf) contains lines that look like:
     24     hd[a-z][0-9]* 0:3 660
     25 
     26     Each line must contain three whitespace separated fields. The first
     27     field is a regular expression matching one or more device names,
     28     the second and third fields are uid:gid and file permissions for
     29     matching devies.
     30 */
     31 
     32 #include "toys.h"
     33 
     34 // mknod in /dev based on a path like "/sys/block/hda/hda1"
     35 static void make_device(char *path)
     36 {
     37   char *device_name = 0, *s, *temp;
     38   int major = 0, minor = 0, type, len, fd, mode = 0660;
     39   uid_t uid = 0;
     40   gid_t gid = 0;
     41 
     42   if (path) {
     43 
     44     // Try to read major/minor string, returning if we can't
     45     temp = strrchr(path, '/');
     46     fd = open(path, O_RDONLY);
     47     *temp = 0;
     48     len = read(fd, toybuf, 64);
     49     close(fd);
     50     if (len<1) return;
     51     toybuf[len] = 0;
     52 
     53     // Determine device type, major and minor
     54 
     55     type = path[5]=='c' ? S_IFCHR : S_IFBLK;
     56     sscanf(toybuf, "%u:%u", &major, &minor);
     57   } else {
     58     // if (!path), do hotplug
     59 
     60     if (!(temp = getenv("MODALIAS"))) xrun((char *[]){"modprobe", temp, 0});
     61     if (!(temp = getenv("SUBSYSTEM"))) return;
     62     type = strcmp(temp, "block") ? S_IFCHR : S_IFBLK;
     63     if (!(temp = getenv("MAJOR"))) return;
     64     sscanf(temp, "%u", &major);
     65     if (!(temp = getenv("MINOR"))) return;
     66     sscanf(temp, "%u", &minor);
     67     if (!(path = getenv("DEVPATH"))) return;
     68     device_name = getenv("DEVNAME");
     69   }
     70   if (!device_name)
     71     device_name = strrchr(path, '/') + 1;
     72 
     73   // as in linux/drivers/base/core.c, device_get_devnode()
     74   while ((temp = strchr(device_name, '!'))) {
     75     *temp = '/';
     76   }
     77 
     78   // If we have a config file, look up permissions for this device
     79 
     80   if (CFG_MDEV_CONF) {
     81     char *conf, *pos, *end;
     82 
     83     // mmap the config file
     84     if (-1!=(fd = open("/etc/mdev.conf", O_RDONLY))) {
     85       int line = 0;
     86 
     87       len = fdlength(fd);
     88       conf = xmmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
     89 
     90       // Loop through lines in mmaped file
     91       for (pos = conf; pos-conf<len;) {
     92         int field;
     93         char *end2;
     94 
     95         line++;
     96         // find end of this line
     97         for(end = pos; end-conf<len && *end!='\n'; end++);
     98 
     99         // Three fields: regex, uid:gid, mode
    100         for (field = 3; field; field--) {
    101           // Skip whitespace
    102           while (pos<end && isspace(*pos)) pos++;
    103           if (pos==end || *pos=='#') break;
    104           for (end2 = pos;
    105             end2<end && !isspace(*end2) && *end2!='#'; end2++);
    106           switch(field) {
    107             // Regex to match this device
    108             case 3:
    109             {
    110               char *regex = strndup(pos, end2-pos);
    111               regex_t match;
    112               regmatch_t off;
    113               int result;
    114 
    115               // Is this it?
    116               xregcomp(&match, regex, REG_EXTENDED);
    117               result=regexec(&match, device_name, 1, &off, 0);
    118               regfree(&match);
    119               free(regex);
    120 
    121               // If not this device, skip rest of line
    122               if (result || off.rm_so
    123                 || off.rm_eo!=strlen(device_name))
    124                   goto end_line;
    125 
    126               break;
    127             }
    128             // uid:gid
    129             case 2:
    130             {
    131               char *s2;
    132 
    133               // Find :
    134               for(s = pos; s<end2 && *s!=':'; s++);
    135               if (s==end2) goto end_line;
    136 
    137               // Parse UID
    138               uid = strtoul(pos,&s2,10);
    139               if (s!=s2) {
    140                 struct passwd *pass;
    141                 char *str = strndup(pos, s-pos);
    142                 pass = getpwnam(str);
    143                 free(str);
    144                 if (!pass) goto end_line;
    145                 uid = pass->pw_uid;
    146               }
    147               s++;
    148               // parse GID
    149               gid = strtoul(s,&s2,10);
    150               if (end2!=s2) {
    151                 struct group *grp;
    152                 char *str = strndup(s, end2-s);
    153                 grp = getgrnam(str);
    154                 free(str);
    155                 if (!grp) goto end_line;
    156                 gid = grp->gr_gid;
    157               }
    158               break;
    159             }
    160             // mode
    161             case 1:
    162             {
    163               mode = strtoul(pos, &pos, 8);
    164               if (pos!=end2) goto end_line;
    165               goto found_device;
    166             }
    167           }
    168           pos=end2;
    169         }
    170 end_line:
    171         // Did everything parse happily?
    172         if (field && field!=3) error_exit("Bad line %d", line);
    173 
    174         // Next line
    175         pos = ++end;
    176       }
    177 found_device:
    178       munmap(conf, len);
    179     }
    180     close(fd);
    181   }
    182 
    183   sprintf(toybuf, "/dev/%s", device_name);
    184 
    185   if ((temp=getenv("ACTION")) && !strcmp(temp, "remove")) {
    186     unlink(toybuf);
    187     return;
    188   }
    189 
    190   if (strchr(device_name, '/'))
    191     mkpathat(AT_FDCWD, toybuf, 0, 2);
    192   if (mknod(toybuf, mode | type, dev_makedev(major, minor)) && errno != EEXIST)
    193     perror_exit("mknod %s failed", toybuf);
    194 
    195 
    196   if (type == S_IFBLK) close(open(toybuf, O_RDONLY)); // scan for partitions
    197 
    198   if (CFG_MDEV_CONF) mode=chown(toybuf, uid, gid);
    199 }
    200 
    201 static int callback(struct dirtree *node)
    202 {
    203   // Entries in /sys/class/block aren't char devices, so skip 'em.  (We'll
    204   // get block devices out of /sys/block.)
    205   if(!strcmp(node->name, "block")) return 0;
    206 
    207   // Does this directory have a "dev" entry in it?
    208   // This is path based because the hotplug callbacks are
    209   if (S_ISDIR(node->st.st_mode) || S_ISLNK(node->st.st_mode)) {
    210     int len=4;
    211     char *dev = dirtree_path(node, &len);
    212     strcpy(dev+len, "/dev");
    213     if (!access(dev, R_OK)) make_device(dev);
    214     free(dev);
    215   }
    216 
    217   // Circa 2.6.25 the entries more than 2 deep are all either redundant
    218   // (mouse#, event#) or unnamed (every usb_* entry is called "device").
    219 
    220   return (node->parent && node->parent->parent) ? 0 : DIRTREE_RECURSE;
    221 }
    222 
    223 void mdev_main(void)
    224 {
    225   // Handle -s
    226 
    227   if (toys.optflags) {
    228     dirtree_read("/sys/class", callback);
    229     if (!access("/sys/block", R_OK)) dirtree_read("/sys/block", callback);
    230   } else { // hotplug support
    231     make_device(NULL);
    232   }
    233 }
    234