• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
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 
callback(struct dirtree * node)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 
mdev_main(void)223 void mdev_main(void)
224 {
225   // Handle -s
226 
227   if (toys.optflags) {
228     dirtree_read("/sys/class", callback);
229     dirtree_read("/sys/block", callback);
230   } else { // hotplug support
231     make_device(NULL);
232   }
233 }
234