toybox/toys/pending/mdev.c
<<
>>
Prefs
   1/* mdev.c - Populate /dev directory and handle hotplug events
   2 *
   3 * Copyright 2005, 2008 Rob Landley <rob@landley.net>
   4 * Copyright 2005 Frank Sorenson <frank@tuxrocks.com>
   5
   6USE_MDEV(NEWTOY(mdev, "s", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_UMASK))
   7
   8config 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
  18config 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    (sd[a-z]) root:disk 660 =usb_storage
  26
  27    Each line must contain three whitespace separated fields. The first
  28    field is a regular expression matching one or more device names,
  29    the second and third fields are uid:gid and file permissions for
  30    matching devices. Fourth field is optional. It could be used to change
  31    device name (prefix '='), path (prefix '=' and postfix '/') or create a
  32    symlink (prefix '>').
  33*/
  34
  35#include "toys.h"
  36#include <stdbool.h>
  37
  38// mknod in /dev based on a path like "/sys/block/hda/hda1"
  39static void make_device(char *path)
  40{
  41  char *device_name = 0, *custom_name = 0, *s, *temp;
  42  bool create_symlink = false;
  43  int major = 0, minor = 0, type, len, fd, mode = 0660;
  44  uid_t uid = 0;
  45  gid_t gid = 0;
  46
  47  if (path) {
  48
  49    // Try to read major/minor string, returning if we can't
  50    temp = strrchr(path, '/');
  51    fd = open(path, O_RDONLY);
  52    *temp = 0;
  53    len = read(fd, toybuf, 64);
  54    close(fd);
  55    if (len<1) return;
  56    toybuf[len] = 0;
  57
  58    // Determine device type, major and minor
  59
  60    type = path[5]=='c' ? S_IFCHR : S_IFBLK;
  61    sscanf(toybuf, "%u:%u", &major, &minor);
  62  } else {
  63    // if (!path), do hotplug
  64
  65    if (!(temp = getenv("MODALIAS"))) xrun((char *[]){"modprobe", temp, 0});
  66    if (!(temp = getenv("SUBSYSTEM"))) return;
  67    type = strcmp(temp, "block") ? S_IFCHR : S_IFBLK;
  68    if (!(temp = getenv("MAJOR"))) return;
  69    sscanf(temp, "%u", &major);
  70    if (!(temp = getenv("MINOR"))) return;
  71    sscanf(temp, "%u", &minor);
  72    if (!(path = getenv("DEVPATH"))) return;
  73    device_name = getenv("DEVNAME");
  74  }
  75  if (!device_name)
  76    device_name = strrchr(path, '/') + 1;
  77
  78  // as in linux/drivers/base/core.c, device_get_devnode()
  79  while ((temp = strchr(device_name, '!'))) {
  80    *temp = '/';
  81  }
  82
  83  // If we have a config file, look up permissions for this device
  84
  85  if (CFG_MDEV_CONF) {
  86    char *conf, *pos, *end;
  87    bool optional_field_valid = false;
  88
  89    // mmap the config file
  90    if (-1!=(fd = open("/etc/mdev.conf", O_RDONLY))) {
  91      int line = 0;
  92
  93      len = fdlength(fd);
  94      conf = xmmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
  95
  96      // Loop through lines in mmaped file
  97      for (pos = conf; pos-conf<len;) {
  98        int field;
  99        char *end2;
 100
 101        line++;
 102        // find end of this line
 103        for(end = pos; end-conf<len && *end!='\n'; end++);
 104
 105        // Four fields (last is optional): regex, uid:gid, mode [, name|path ]
 106        // For example: (sd[a-z])1  root:disk 660 =usb_storage_p1
 107        for (field = 4; field; field--) {
 108          // Skip whitespace
 109          while (pos<end && isspace(*pos)) pos++;
 110          if (pos==end || *pos=='#') break;
 111          for (end2 = pos;
 112            end2<end && !isspace(*end2) && *end2!='#'; end2++);
 113          switch(field) {
 114            // Regex to match this device
 115            case 4:
 116            {
 117              char *regex = strndup(pos, end2-pos);
 118              regex_t match;
 119              regmatch_t off;
 120              int result;
 121
 122              // Is this it?
 123              xregcomp(&match, regex, REG_EXTENDED);
 124              result=regexec(&match, device_name, 1, &off, 0);
 125              regfree(&match);
 126              free(regex);
 127
 128              // If not this device, skip rest of line
 129              if (result || off.rm_so
 130                || off.rm_eo!=strlen(device_name))
 131                  goto end_line;
 132
 133              break;
 134            }
 135            // uid:gid
 136            case 3:
 137            {
 138              char *s2;
 139
 140              // Find :
 141              for(s = pos; s<end2 && *s!=':'; s++);
 142              if (s==end2) goto end_line;
 143
 144              // Parse UID
 145              uid = strtoul(pos,&s2,10);
 146              if (s!=s2) {
 147                struct passwd *pass;
 148                char *str = strndup(pos, s-pos);
 149                pass = getpwnam(str);
 150                free(str);
 151                if (!pass) goto end_line;
 152                uid = pass->pw_uid;
 153              }
 154              s++;
 155              // parse GID
 156              gid = strtoul(s,&s2,10);
 157              if (end2!=s2) {
 158                struct group *grp;
 159                char *str = strndup(s, end2-s);
 160                grp = getgrnam(str);
 161                free(str);
 162                if (!grp) goto end_line;
 163                gid = grp->gr_gid;
 164              }
 165              break;
 166            }
 167            // mode
 168            case 2:
 169            {
 170              char *beg_pos = pos;
 171              mode = strtoul(pos, &pos, 8);
 172              if(pos == beg_pos) {
 173                // The line is bad because mode setting could not be
 174                // converted to numeric value.
 175                goto end_line;
 176              }
 177              break;
 178            }
 179            // Try to look for name or path (optional field)
 180            case 1:
 181            {
 182              if(pos < end2){
 183                //char *name = strndup(pos, end2-pos);
 184                char *name = malloc(end2-pos+1);
 185                if(name){
 186                  strncpy(name, pos, end2-pos+1);
 187                  name[end2-pos] = '\0';
 188                  switch(name[0]){
 189                    case '>':
 190                      create_symlink = true;
 191                    case '=':
 192                      custom_name = strdup(&name[1]);
 193                      break;
 194                    case '!':
 195                      device_name = NULL;
 196                      break;
 197                    default:
 198                      free(name);
 199                      goto end_line;
 200                  }
 201                  free(name);
 202                  optional_field_valid = true;
 203                }
 204              }
 205              goto found_device;
 206            }
 207          }
 208          pos=end2;
 209        }
 210end_line:
 211        // Did everything parse happily?
 212        // Note: Last field is optional.
 213        if ((field>1 || (field==1 && !optional_field_valid)) && field!=4)
 214          error_exit("Bad line %d", line);
 215        // Next line
 216        pos = ++end;
 217      }
 218found_device:
 219      munmap(conf, len);
 220    }
 221    close(fd);
 222  }
 223
 224  if(device_name) {
 225    if(custom_name) {
 226      sprintf(toybuf, "/dev/%s", custom_name);
 227      if(custom_name[strlen(custom_name) - 1] == '/') {
 228        DIR *dir;
 229        dir = opendir(toybuf);
 230        if(dir) closedir(dir);
 231        else mkdir(toybuf, 0755);
 232      }
 233    }
 234    else
 235      sprintf(toybuf, "/dev/%s", device_name);
 236
 237      if ((temp=getenv("ACTION")) && !strcmp(temp, "remove")) {
 238        unlink(toybuf);
 239        return;
 240      }
 241
 242      if (strchr(device_name, '/')) mkpath(toybuf);
 243      if (mknod(toybuf, mode | type, dev_makedev(major, minor)) &&
 244          errno != EEXIST)
 245        perror_exit("mknod %s failed", toybuf);
 246      if(create_symlink){
 247        char *symlink_name = malloc(sizeof("/dev/") + strlen(device_name) + 1);
 248        if(symlink_name == NULL)
 249          perror_exit("malloc failed while creating symlink to %s", toybuf);
 250        sprintf(symlink_name, "/dev/%s", device_name);
 251        if(symlink(toybuf, symlink_name)){
 252          free(symlink_name);
 253          perror_exit("symlink creation failed for %s", toybuf);
 254        }
 255        free(symlink_name);
 256      }
 257
 258      if (type == S_IFBLK) close(open(toybuf, O_RDONLY)); // scan for partitions
 259
 260      if (CFG_MDEV_CONF) mode=chown(toybuf, uid, gid);
 261  }
 262}
 263
 264static int callback(struct dirtree *node)
 265{
 266  // Entries in /sys/class/block aren't char devices, so skip 'em.  (We'll
 267  // get block devices out of /sys/block.)
 268  if(!strcmp(node->name, "block")) return 0;
 269
 270  // Does this directory have a "dev" entry in it?
 271  // This is path based because the hotplug callbacks are
 272  if (S_ISDIR(node->st.st_mode) || S_ISLNK(node->st.st_mode)) {
 273    int len=4;
 274    char *dev = dirtree_path(node, &len);
 275    strcpy(dev+len, "/dev");
 276    if (!access(dev, R_OK)) make_device(dev);
 277    free(dev);
 278  }
 279
 280  // Circa 2.6.25 the entries more than 2 deep are all either redundant
 281  // (mouse#, event#) or unnamed (every usb_* entry is called "device").
 282
 283  return (node->parent && node->parent->parent) ? 0 : DIRTREE_RECURSE;
 284}
 285
 286void mdev_main(void)
 287{
 288  // Handle -s
 289
 290  if (toys.optflags) {
 291    dirtree_read("/sys/class", callback);
 292    if (!access("/sys/block", R_OK)) dirtree_read("/sys/block", callback);
 293  } else { // hotplug support
 294    make_device(NULL);
 295  }
 296}
 297