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 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 (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" 39 static 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 } 210 end_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 } 218 found_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 264 static 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 if (node->parent && node->parent->parent) return 0; 284 return DIRTREE_RECURSE|DIRTREE_SYMFOLLOW; 285 } 286 287 void mdev_main(void) 288 { 289 // Handle -s 290 291 if (toys.optflags) { 292 dirtree_read("/sys/class", callback); 293 if (!access("/sys/block", R_OK)) dirtree_read("/sys/block", callback); 294 } else { // hotplug support 295 make_device(NULL); 296 } 297 } 298