smdev.c - smdev - suckless mdev
(HTM) git clone git://git.suckless.org/smdev
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
smdev.c (8663B)
---
1 /* See LICENSE file for copyright and license details. */
2 #include <sys/ioctl.h>
3 #include <sys/stat.h>
4 #include <sys/types.h>
5
6 #include <linux/sockios.h>
7 #include <linux/if_packet.h>
8 #include <net/if.h>
9 #include <netinet/in.h>
10 #include <ifaddrs.h>
11
12 #include <errno.h>
13 #include <fcntl.h>
14 #include <grp.h>
15 #include <libgen.h>
16 #include <limits.h>
17 #include <pwd.h>
18 #include <regex.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <unistd.h>
23
24 #include "config.h"
25 #include "mkpath.h"
26 #include "util.h"
27
28 enum action {
29 ADD_ACTION,
30 REMOVE_ACTION,
31 UNKNOWN_ACTION
32 };
33
34 struct event {
35 int minor;
36 int major;
37 enum action action;
38 char *devpath;
39 char *devname;
40 struct rule *rule;
41 };
42
43 /* Simple cache for regcomp() results */
44 static struct pregentry {
45 regex_t preg;
46 int cached;
47 } pregcache[LEN(rules)];
48
49 /* The expanded/parsed path components of a rule */
50 struct rulepath {
51 char path[PATH_MAX];
52 char name[PATH_MAX];
53 };
54
55 static int dohotplug(void);
56 static int matchrule(int, char *);
57 static void runrulecmd(struct rule *);
58 static void parsepath(struct rule *, struct rulepath *, const char *);
59 static int removedev(struct event *);
60 static int createdev(struct event *);
61 static int doevent(struct event *);
62 static int craftev(char *);
63 static void populatedev(const char *);
64 static int ifrename(void);
65
66 static void
67 usage(void)
68 {
69 eprintf("usage: %s [-s]\n", argv0);
70 }
71
72 int
73 main(int argc, char *argv[])
74 {
75 int sflag = 0;
76 int i;
77
78 ARGBEGIN {
79 case 's':
80 sflag = 1;
81 break;
82 default:
83 usage();
84 } ARGEND;
85
86 umask(0);
87 if (sflag) {
88 recurse("/sys/devices", populatedev);
89 } else {
90 if (dohotplug() < 0)
91 eprintf("Environment not set up correctly for hotplugging\n");
92 }
93
94 for (i = 0; i < LEN(pregcache); i++)
95 if (pregcache[i].cached)
96 regfree(&pregcache[i].preg);
97
98 if (ifrename() < 0)
99 return EXIT_FAILURE;
100
101 return EXIT_SUCCESS;
102 }
103
104 static enum action
105 mapaction(const char *action)
106 {
107 if (strcmp(action, "add") == 0)
108 return ADD_ACTION;
109 if (strcmp(action, "remove") == 0)
110 return REMOVE_ACTION;
111 return UNKNOWN_ACTION;
112 }
113
114 /* Handle hotplugging events */
115 static int
116 dohotplug(void)
117 {
118 char *minor, *major;
119 char *action;
120 char *devpath;
121 char *devname;
122 struct event ev;
123
124 minor = getenv("MINOR");
125 major = getenv("MAJOR");
126 action = getenv("ACTION");
127 devpath = getenv("DEVPATH");
128 devname = getenv("DEVNAME");
129 if (!minor || !major || !action || !devpath || !devname)
130 return -1;
131
132 ev.minor = estrtol(minor, 10);
133 ev.major = estrtol(major, 10);
134 ev.action = mapaction(action);
135 ev.devpath = devpath;
136 ev.devname = devname;
137 return doevent(&ev);
138 }
139
140 /*
141 * `ruleidx' indexes into the rules[] table. We assume
142 * pregcache[] is mapped 1-1 with the rules[] table.
143 */
144 static int
145 matchrule(int ruleidx, char *devname)
146 {
147 struct rule *rule = &rules[ruleidx];
148 regex_t *preg;
149 regmatch_t pmatch;
150 int ret;
151
152 if (!pregcache[ruleidx].cached) {
153 ret = regcomp(&pregcache[ruleidx].preg,
154 rule->devregex, REG_EXTENDED);
155 if (ret < 0)
156 eprintf("regcomp:");
157 pregcache[ruleidx].cached = 1;
158 }
159 preg = &pregcache[ruleidx].preg;
160
161 ret = regexec(preg, devname, 1, &pmatch, 0);
162 if (ret == REG_NOMATCH || pmatch.rm_so ||
163 pmatch.rm_eo != strlen(devname))
164 return -1;
165 return 0;
166 }
167
168 static void
169 runrulecmd(struct rule *rule)
170 {
171 if (rule->cmd)
172 system(&rule->cmd[1]);
173 }
174
175 static void
176 parsepath(struct rule *rule, struct rulepath *rpath,
177 const char *devname)
178 {
179 char buf[PATH_MAX], *path, *dirc;
180 const char *basedevname;
181
182 if(!(basedevname = strrchr(devname, '/')))
183 basedevname = devname;
184
185 if (!rule->path) {
186 strlcpy(rpath->name, basedevname, sizeof(rpath->name));
187 snprintf(rpath->path, sizeof(rpath->path), "/dev/%s",
188 rpath->name);
189 return;
190 }
191
192 if (rule->path[0] != '=' && rule->path[0] != '>')
193 eprintf("Invalid path '%s'\n", rule->path);
194
195 path = strdup(&rule->path[1]);
196 if (!path)
197 eprintf("strdup:");
198
199 /* No need to rename the device node */
200 if (rule->path[strlen(rule->path) - 1] == '/') {
201 snprintf(rpath->path, sizeof(rpath->path), "/dev/%s%s",
202 path, basedevname);
203 strlcpy(rpath->name, basedevname, sizeof(rpath->name));
204 free(path);
205 return;
206 }
207
208 if (strchr(path, '/')) {
209 if (!(dirc = strdup(path)))
210 eprintf("strdup:");
211 snprintf(buf, sizeof(buf), "/dev/%s", dirname(dirc));
212 strlcpy(rpath->name, basename(path), sizeof(rpath->name));
213 snprintf(rpath->path, sizeof(rpath->path), "%s/%s", buf,
214 rpath->name);
215 free(dirc);
216 } else {
217 strlcpy(rpath->name, path, sizeof(rpath->name));
218 snprintf(rpath->path, sizeof(rpath->path), "/dev/%s",
219 rpath->name);
220 }
221
222 free(path);
223 }
224
225 static int
226 removedev(struct event *ev)
227 {
228 struct rule *rule;
229 struct rulepath rpath;
230 char *ocwd;
231 char buf[PATH_MAX];
232
233 rule = ev->rule;
234 ocwd = agetcwd();
235
236 parsepath(rule, &rpath, ev->devname);
237
238 if(rule->cmd) {
239 if (chdir("/dev") < 0)
240 eprintf("chdir /dev:");
241 runrulecmd(rule);
242 }
243
244 if (chdir(ocwd) < 0)
245 eprintf("chdir %s:", ocwd);
246
247 free(ocwd);
248
249 if (rule->path && rule->path[0] == '!')
250 return 0;
251
252 /* Delete device node */
253 unlink(rpath.path);
254 /* Delete symlink */
255 if (rule->path && rule->path[0] == '>') {
256 snprintf(buf, sizeof(buf), "/dev/%s", ev->devname);
257 unlink(buf);
258 }
259 return 0;
260 }
261
262 static int
263 createdev(struct event *ev)
264 {
265 struct rule *rule;
266 struct rulepath rpath;
267 struct passwd *pw;
268 struct group *gr;
269 char *dirc, *ocwd;
270 char buf[BUFSIZ];
271 int type;
272
273 rule = ev->rule;
274 ocwd = agetcwd();
275
276 if (rule->path && rule->path[0] == '!')
277 goto runrule;
278
279 snprintf(buf, sizeof(buf), "%d:%d", ev->major, ev->minor);
280 if ((type = devtype(buf)) < 0)
281 return -1;
282
283 /* Parse path and create the directory tree */
284 parsepath(rule, &rpath, ev->devname);
285 if (!(dirc = strdup(rpath.path)))
286 eprintf("strdup:");
287 strlcpy(buf, dirname(dirc), sizeof(buf));
288 free(dirc);
289 umask(022);
290 if (mkpath(buf, 0755) < 0)
291 eprintf("mkdir %s:", buf);
292 umask(0);
293
294 if (mknod(rpath.path, rule->mode | type,
295 makedev(ev->major, ev->minor)) < 0 &&
296 errno != EEXIST)
297 eprintf("mknod %s:", rpath.path);
298
299 errno = 0;
300 pw = getpwnam(rule->user);
301 if (!pw) {
302 if (errno)
303 eprintf("getpwnam %s:", rule->user);
304 else
305 eprintf("getpwnam %s: no such user\n",
306 rule->user);
307 }
308
309 errno = 0;
310 gr = getgrnam(rule->group);
311 if (!gr) {
312 if (errno)
313 eprintf("getgrnam %s:", rule->group);
314 else
315 eprintf("getgrnam %s: no such group\n",
316 rule->group);
317 }
318
319 if (chown(rpath.path, pw->pw_uid, gr->gr_gid) < 0)
320 eprintf("chown %s:", rpath.path);
321
322 if (chmod(rpath.path, rule->mode) < 0)
323 eprintf("chmod %s:", rpath.path);
324
325 if (rule->path && rule->path[0] == '>') {
326 /* ev->devname is the original device name */
327 snprintf(buf, sizeof(buf), "/dev/%s", ev->devname);
328 symlink(rpath.path, buf);
329 }
330
331 runrule:
332 if(rule->cmd) {
333 if (chdir("/dev") < 0)
334 eprintf("chdir /dev:");
335 runrulecmd(rule);
336 }
337
338 if (chdir(ocwd) < 0)
339 eprintf("chdir %s:", ocwd);
340
341 free(ocwd);
342
343 return 0;
344 }
345
346 /* Event dispatcher */
347 static int
348 doevent(struct event *ev)
349 {
350 int i;
351
352 for (i = 0; i < LEN(rules); i++) {
353 if (matchrule(i, ev->devname) < 0)
354 continue;
355 ev->rule = &rules[i];
356 switch (ev->action) {
357 case ADD_ACTION:
358 return createdev(ev);
359 case REMOVE_ACTION:
360 return removedev(ev);
361 default:
362 return 0;
363 }
364 }
365 return 0;
366 }
367
368 /* Craft a fake event so the rest of the code can cope */
369 static int
370 craftev(char *sysfspath)
371 {
372 char path[PATH_MAX];
373 char *devpath;
374
375 devpath = sysfspath + strlen("/sys");
376 snprintf(path, sizeof(path), "/sys%s/uevent", devpath);
377
378 clearenv();
379 setenv("ACTION", "add", 1);
380 setenv("DEVPATH", devpath, 1);
381 if(readuevent(path) < 0)
382 return -1;
383 return 0;
384 }
385
386 static void
387 populatedev(const char *path)
388 {
389 char *cwd;
390
391 recurse(path, populatedev);
392 if (strcmp(path, "dev") == 0) {
393 cwd = agetcwd();
394 if (!craftev(cwd))
395 dohotplug();
396 free(cwd);
397 }
398 }
399
400 static int
401 ifrename(void)
402 {
403 struct sockaddr_ll *sa;
404 struct ifaddrs *ifas, *ifa;
405 struct ifreq ifr;
406 int sd;
407 int i;
408 int r;
409 int ok = 0;
410
411 sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
412 if (sd < 0)
413 eprintf("socket:");
414 r = getifaddrs(&ifas);
415 if (r < 0)
416 eprintf("getifaddrs:");
417 for (ifa = ifas; ifa; ifa = ifa->ifa_next) {
418 if (ifa->ifa_flags & IFF_LOOPBACK)
419 continue;
420 if (ifa->ifa_addr->sa_family != AF_PACKET)
421 continue;
422 sa = (struct sockaddr_ll *)ifa->ifa_addr;
423 for (i = 0; mac2names[i].name; i++) {
424 if (memcmp(mac2names[i].mac, sa->sll_addr, 6) != 0)
425 continue;
426 memset(&ifr, 0, sizeof(ifr));
427 strlcpy(ifr.ifr_name,
428 ifa->ifa_name, sizeof(ifr.ifr_name));
429 strlcpy(ifr.ifr_newname,
430 mac2names[i].name, sizeof(ifr.ifr_newname));
431 r = ioctl(sd, SIOCSIFNAME, &ifr);
432 if (r < 0) {
433 weprintf("SIOCSIFNAME:");
434 ok = -1;
435 }
436 }
437 }
438 freeifaddrs(ifas);
439 close(sd);
440 return ok;
441 }