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