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 
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"
make_device(char * path)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     // Try to read major/minor string
44 
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       len = fdlength(fd);
86       conf = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
87       if (conf) {
88         int line = 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 
184   sprintf(toybuf, "/dev/%s", device_name);
185 
186   if ((temp=getenv("ACTION")) && !strcmp(temp, "remove")) {
187     unlink(toybuf);
188     return;
189   }
190 
191   if (strchr(device_name, '/'))
192     mkpathat(AT_FDCWD, toybuf, 0, 2);
193   if (mknod(toybuf, mode | type, makedev(major, minor)) && errno != EEXIST)
194     perror_exit("mknod %s failed", toybuf);
195 
196 
197   if (type == S_IFBLK) close(open(toybuf, O_RDONLY)); // scan for partitions
198 
199   if (CFG_MDEV_CONF) mode=chown(toybuf, uid, gid);
200 }
201 
callback(struct dirtree * node)202 static int callback(struct dirtree *node)
203 {
204   // Entries in /sys/class/block aren't char devices, so skip 'em.  (We'll
205   // get block devices out of /sys/block.)
206   if(!strcmp(node->name, "block")) return 0;
207 
208   // Does this directory have a "dev" entry in it?
209   // This is path based because the hotplug callbacks are
210   if (S_ISDIR(node->st.st_mode) || S_ISLNK(node->st.st_mode)) {
211     int len=4;
212     char *dev = dirtree_path(node, &len);
213     strcpy(dev+len, "/dev");
214     if (!access(dev, R_OK)) make_device(dev);
215     free(dev);
216   }
217 
218   // Circa 2.6.25 the entries more than 2 deep are all either redundant
219   // (mouse#, event#) or unnamed (every usb_* entry is called "device").
220 
221   return (node->parent && node->parent->parent) ? 0 : DIRTREE_RECURSE;
222 }
223 
mdev_main(void)224 void mdev_main(void)
225 {
226   // Handle -s
227 
228   if (toys.optflags) {
229     dirtree_read("/sys/class", callback);
230     dirtree_read("/sys/block", callback);
231   } else { // hotplug support
232     make_device(NULL);
233   }
234 }
235