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