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, and
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 = NULL, *s, *temp;
38 int major, minor, type, len, fd;
39 int mode = 0660;
40 uid_t uid = 0;
41 gid_t gid = 0;
42
43 if (path) {
44 // Try to read major/minor string
45
46 temp = strrchr(path, '/');
47 fd = open(path, O_RDONLY);
48 *temp=0;
49 temp = toybuf;
50 len = read(fd, temp, 64);
51 close(fd);
52 if (len<1) return;
53 temp[len] = 0;
54
55 // Determine device type, major and minor
56
57 type = path[5]=='c' ? S_IFCHR : S_IFBLK;
58 major = minor = 0;
59 sscanf(temp, "%u:%u", &major, &minor);
60 } else {
61 // if (!path), do hotplug
62
63 if (!(temp = getenv("SUBSYSTEM")))
64 return;
65 type = strcmp(temp, "block") ? S_IFCHR : S_IFBLK;
66 major = minor = 0;
67 if (!(temp = getenv("MAJOR")))
68 return;
69 sscanf(temp, "%u", &major);
70 if (!(temp = getenv("MINOR")))
71 return;
72 sscanf(temp, "%u", &minor);
73 path = getenv("DEVPATH");
74 device_name = getenv("DEVNAME");
75 if (!path)
76 return;
77 temp = toybuf;
78 }
79 if (!device_name)
80 device_name = strrchr(path, '/') + 1;
81
82 // If we have a config file, look up permissions for this device
83
84 if (CFG_MDEV_CONF) {
85 char *conf, *pos, *end;
86
87 // mmap the config file
88 if (-1!=(fd = open("/etc/mdev.conf", O_RDONLY))) {
89 len = fdlength(fd);
90 conf = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
91 if (conf) {
92 int line = 0;
93
94 // Loop through lines in mmaped file
95 for (pos = conf; pos-conf<len;) {
96 int field;
97 char *end2;
98
99 line++;
100 // find end of this line
101 for(end = pos; end-conf<len && *end!='\n'; end++);
102
103 // Three fields: regex, uid:gid, mode
104 for (field = 3; field; field--) {
105 // Skip whitespace
106 while (pos<end && isspace(*pos)) pos++;
107 if (pos==end || *pos=='#') break;
108 for (end2 = pos;
109 end2<end && !isspace(*end2) && *end2!='#'; end2++);
110 switch(field) {
111 // Regex to match this device
112 case 3:
113 {
114 char *regex = strndup(pos, end2-pos);
115 regex_t match;
116 regmatch_t off;
117 int result;
118
119 // Is this it?
120 xregcomp(&match, regex, REG_EXTENDED);
121 result=regexec(&match, device_name, 1, &off, 0);
122 regfree(&match);
123 free(regex);
124
125 // If not this device, skip rest of line
126 if (result || off.rm_so
127 || off.rm_eo!=strlen(device_name))
128 goto end_line;
129
130 break;
131 }
132 // uid:gid
133 case 2:
134 {
135 char *s2;
136
137 // Find :
138 for(s = pos; s<end2 && *s!=':'; s++);
139 if (s==end2) goto end_line;
140
141 // Parse UID
142 uid = strtoul(pos,&s2,10);
143 if (s!=s2) {
144 struct passwd *pass;
145 char *str = strndup(pos, s-pos);
146 pass = getpwnam(str);
147 free(str);
148 if (!pass) goto end_line;
149 uid = pass->pw_uid;
150 }
151 s++;
152 // parse GID
153 gid = strtoul(s,&s2,10);
154 if (end2!=s2) {
155 struct group *grp;
156 char *str = strndup(s, end2-s);
157 grp = getgrnam(str);
158 free(str);
159 if (!grp) goto end_line;
160 gid = grp->gr_gid;
161 }
162 break;
163 }
164 // mode
165 case 1:
166 {
167 mode = strtoul(pos, &pos, 8);
168 if (pos!=end2) goto end_line;
169 goto found_device;
170 }
171 }
172 pos=end2;
173 }
174 end_line:
175 // Did everything parse happily?
176 if (field && field!=3) error_exit("Bad line %d", line);
177
178 // Next line
179 pos = ++end;
180 }
181 found_device:
182 munmap(conf, len);
183 }
184 close(fd);
185 }
186 }
187
188 sprintf(temp, "/dev/%s", device_name);
189
190 if (getenv("ACTION") && !strcmp(getenv("ACTION"), "remove")) {
191 unlink(temp);
192 return;
193 }
194
195 if (strchr(device_name, '/'))
196 mkpathat(AT_FDCWD, temp, 0, 2);
197 if (mknod(temp, mode | type, makedev(major, minor)) && errno != EEXIST)
198 perror_exit("mknod %s failed", temp);
199
200
201 if (type == S_IFBLK) close(open(temp, O_RDONLY)); // scan for partitions
202
203 if (CFG_MDEV_CONF) mode=chown(temp, uid, gid);
204 }
205
callback(struct dirtree * node)206 static int callback(struct dirtree *node)
207 {
208 // Entries in /sys/class/block aren't char devices, so skip 'em. (We'll
209 // get block devices out of /sys/block.)
210 if(!strcmp(node->name, "block")) return 0;
211
212 // Does this directory have a "dev" entry in it?
213 // This is path based because the hotplug callbacks are
214 if (S_ISDIR(node->st.st_mode) || S_ISLNK(node->st.st_mode)) {
215 int len=4;
216 char *dev = dirtree_path(node, &len);
217 strcpy(dev+len, "/dev");
218 if (!access(dev, R_OK)) make_device(dev);
219 free(dev);
220 }
221
222 // Circa 2.6.25 the entries more than 2 deep are all either redundant
223 // (mouse#, event#) or unnamed (every usb_* entry is called "device").
224
225 return (node->parent && node->parent->parent) ? 0 : DIRTREE_RECURSE;
226 }
227
mdev_main(void)228 void mdev_main(void)
229 {
230 // Handle -s
231
232 if (toys.optflags) {
233 dirtree_read("/sys/class", callback);
234 dirtree_read("/sys/block", callback);
235 } else { // hotplug support
236 make_device(NULL);
237 }
238 }
239