• Home
  • History
  • Annotate
  • 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, 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