blob: cc729e72a943b158b47ad88b27ca4969fd45d8c9 [file] [log] [blame]
"Robert P. J. Day"63fc1a92006-07-02 19:47:05 +00001/* vi: set sw=4 ts=4: */
2/*
Tim Rikerc1ef7bd2006-01-25 00:08:53 +00003 *
Rob Landley70f7ef72005-12-13 08:21:33 +00004 * mdev - Mini udev for busybox
Tim Rikerc1ef7bd2006-01-25 00:08:53 +00005 *
Rob Landley70f7ef72005-12-13 08:21:33 +00006 * Copyright 2005 Rob Landley <rob@landley.net>
7 * Copyright 2005 Frank Sorenson <frank@tuxrocks.com>
8 *
Rob Landleye9a7a622006-09-22 02:52:41 +00009 * Licensed under GPL version 2, see file LICENSE in this tarball for details.
Rob Landley70f7ef72005-12-13 08:21:33 +000010 */
11
Denis Vlasenkob6adbf12007-05-26 19:00:18 +000012#include "libbb.h"
Rob Landleyb56c2852005-12-17 10:52:30 +000013#include "xregex.h"
Rob Landley70f7ef72005-12-13 08:21:33 +000014
Denis Vlasenko4e5f82c2007-06-03 22:30:22 +000015struct globals {
Rob Landley5cd1ccd2006-05-21 18:33:27 +000016 int root_major, root_minor;
Denis Vlasenko4e5f82c2007-06-03 22:30:22 +000017};
18#define G (*(struct globals*)&bb_common_bufsiz1)
19#define root_major (G.root_major)
20#define root_minor (G.root_minor)
Rob Landleya7e3d052006-02-21 06:11:13 +000021
Rob Landley70f7ef72005-12-13 08:21:33 +000022/* mknod in /dev based on a path like "/sys/block/hda/hda1" */
Rob Landleyef10d522006-06-26 14:11:33 +000023static void make_device(char *path, int delete)
Rob Landley70f7ef72005-12-13 08:21:33 +000024{
Rob Landleyef10d522006-06-26 14:11:33 +000025 char *device_name;
Denis Vlasenkof46be092006-10-16 19:39:37 +000026 int major, minor, type, len;
Mike Frysingera421ba82006-02-03 00:25:37 +000027 int mode = 0660;
28 uid_t uid = 0;
29 gid_t gid = 0;
Rob Landley15fe2e12006-05-08 02:53:23 +000030 char *temp = path + strlen(path);
Rob Landleyef10d522006-06-26 14:11:33 +000031 char *command = NULL;
Rob Landley70f7ef72005-12-13 08:21:33 +000032
Rob Landley15fe2e12006-05-08 02:53:23 +000033 /* Try to read major/minor string. Note that the kernel puts \n after
34 * the data, so we don't need to worry about null terminating the string
35 * because sscanf() will stop at the first nondigit, which \n is. We
36 * also depend on path having writeable space after it. */
Rob Landley70f7ef72005-12-13 08:21:33 +000037
Rob Landley10b36f92006-08-10 01:09:37 +000038 if (!delete) {
39 strcat(path, "/dev");
Denis Vlasenkoea620772006-10-14 02:23:43 +000040 len = open_read_close(path, temp + 1, 64);
Rob Landley10b36f92006-08-10 01:09:37 +000041 *temp++ = 0;
Rob Landley10b36f92006-08-10 01:09:37 +000042 if (len < 1) return;
43 }
Tim Rikerc1ef7bd2006-01-25 00:08:53 +000044
Rob Landley70f7ef72005-12-13 08:21:33 +000045 /* Determine device name, type, major and minor */
Tim Rikerc1ef7bd2006-01-25 00:08:53 +000046
Rob Landley70f7ef72005-12-13 08:21:33 +000047 device_name = strrchr(path, '/') + 1;
Rob Landley15fe2e12006-05-08 02:53:23 +000048 type = path[5]=='c' ? S_IFCHR : S_IFBLK;
Rob Landley70f7ef72005-12-13 08:21:33 +000049
Rob Landleyb56c2852005-12-17 10:52:30 +000050 /* If we have a config file, look up permissions for this device */
Tim Rikerc1ef7bd2006-01-25 00:08:53 +000051
Rob Landleyb56c2852005-12-17 10:52:30 +000052 if (ENABLE_FEATURE_MDEV_CONF) {
Mike Frysingera421ba82006-02-03 00:25:37 +000053 char *conf, *pos, *end;
Denis Vlasenkof46be092006-10-16 19:39:37 +000054 int line, fd;
Rob Landleyb56c2852005-12-17 10:52:30 +000055
56 /* mmap the config file */
Denis Vlasenkof46be092006-10-16 19:39:37 +000057 fd = open("/etc/mdev.conf", O_RDONLY);
58 if (fd < 0)
59 goto end_parse;
60 len = xlseek(fd, 0, SEEK_END);
61 conf = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
62 close(fd);
63 if (!conf)
64 goto end_parse;
Rob Landleyb56c2852005-12-17 10:52:30 +000065
Denis Vlasenkof46be092006-10-16 19:39:37 +000066 line = 0;
67 /* Loop through lines in mmaped file*/
68 for (pos=conf; pos-conf<len;) {
69 int field;
70 char *end2;
Tim Rikerc1ef7bd2006-01-25 00:08:53 +000071
Denis Vlasenkof46be092006-10-16 19:39:37 +000072 line++;
73 /* find end of this line */
Denis Vlasenkocf749bc2006-11-26 15:45:17 +000074 for (end=pos; end-conf<len && *end!='\n'; end++)
Denis Vlasenkof46be092006-10-16 19:39:37 +000075 ;
Rob Landleyb56c2852005-12-17 10:52:30 +000076
Denis Vlasenkof46be092006-10-16 19:39:37 +000077 /* Three fields: regex, uid:gid, mode */
78 for (field=0; field < (3 + ENABLE_FEATURE_MDEV_EXEC);
79 field++)
80 {
81 /* Skip whitespace */
82 while (pos<end && isspace(*pos)) pos++;
83 if (pos==end || *pos=='#') break;
84 for (end2=pos;
85 end2<end && !isspace(*end2) && *end2!='#'; end2++)
86 ;
Mike Frysingera421ba82006-02-03 00:25:37 +000087
Denis Vlasenkof46be092006-10-16 19:39:37 +000088 if (field == 0) {
89 /* Regex to match this device */
Rob Landleyb56c2852005-12-17 10:52:30 +000090
Denis Vlasenkof46be092006-10-16 19:39:37 +000091 char *regex = strndupa(pos, end2-pos);
92 regex_t match;
93 regmatch_t off;
94 int result;
Tim Rikerc1ef7bd2006-01-25 00:08:53 +000095
Denis Vlasenkof46be092006-10-16 19:39:37 +000096 /* Is this it? */
97 xregcomp(&match,regex, REG_EXTENDED);
98 result = regexec(&match, device_name, 1, &off, 0);
99 regfree(&match);
Rob Landleyb56c2852005-12-17 10:52:30 +0000100
Denis Vlasenkof46be092006-10-16 19:39:37 +0000101 /* If not this device, skip rest of line */
102 if (result || off.rm_so
103 || off.rm_eo != strlen(device_name))
104 break;
Rob Landleyb56c2852005-12-17 10:52:30 +0000105 }
Denis Vlasenkof46be092006-10-16 19:39:37 +0000106 if (field == 1) {
107 /* uid:gid */
108
109 char *s, *s2;
110
111 /* Find : */
Denis Vlasenkocf749bc2006-11-26 15:45:17 +0000112 for (s=pos; s<end2 && *s!=':'; s++)
Denis Vlasenkof46be092006-10-16 19:39:37 +0000113 ;
114 if (s == end2) break;
115
116 /* Parse UID */
117 uid = strtoul(pos, &s2, 10);
118 if (s != s2) {
119 struct passwd *pass;
120 pass = getpwnam(strndupa(pos, s-pos));
121 if (!pass) break;
122 uid = pass->pw_uid;
123 }
124 s++;
125 /* parse GID */
126 gid = strtoul(s, &s2, 10);
127 if (end2 != s2) {
128 struct group *grp;
129 grp = getgrnam(strndupa(s, end2-s));
130 if (!grp) break;
131 gid = grp->gr_gid;
132 }
133 }
134 if (field == 2) {
135 /* mode */
136
137 mode = strtoul(pos, &pos, 8);
138 if (pos != end2) break;
139 }
140 if (ENABLE_FEATURE_MDEV_EXEC && field == 3) {
141 // Command to run
Denis Vlasenkob6aae0f2007-01-29 22:51:25 +0000142 const char *s = "@$*";
143 const char *s2;
Denis Vlasenkof46be092006-10-16 19:39:37 +0000144 s2 = strchr(s, *pos++);
145 if (!s2) {
146 // Force error
147 field = 1;
148 break;
149 }
150 if ((s2-s+1) & (1<<delete))
151 command = xstrndup(pos, end-pos);
152 }
153
154 pos = end2;
Rob Landleyb56c2852005-12-17 10:52:30 +0000155 }
Denis Vlasenkof46be092006-10-16 19:39:37 +0000156
157 /* Did everything parse happily? */
158
159 if (field > 2) break;
160 if (field) bb_error_msg_and_die("bad line %d",line);
161
162 /* Next line */
163 pos = ++end;
Rob Landleyb56c2852005-12-17 10:52:30 +0000164 }
Denis Vlasenkof46be092006-10-16 19:39:37 +0000165 munmap(conf, len);
166 end_parse: /* nothing */ ;
Rob Landleyb56c2852005-12-17 10:52:30 +0000167 }
Rob Landley70f7ef72005-12-13 08:21:33 +0000168
Rob Landleyb56c2852005-12-17 10:52:30 +0000169 umask(0);
Rob Landleyef10d522006-06-26 14:11:33 +0000170 if (!delete) {
Rob Landley10b36f92006-08-10 01:09:37 +0000171 if (sscanf(temp, "%d:%d", &major, &minor) != 2) return;
Rob Landleyef10d522006-06-26 14:11:33 +0000172 if (mknod(device_name, mode | type, makedev(major, minor)) && errno != EEXIST)
Denis Vlasenkof46be092006-10-16 19:39:37 +0000173 bb_perror_msg_and_die("mknod %s", device_name);
Rob Landley70f7ef72005-12-13 08:21:33 +0000174
Denis Vlasenko4e5f82c2007-06-03 22:30:22 +0000175 if (major == root_major && minor == root_minor)
Rob Landleyef10d522006-06-26 14:11:33 +0000176 symlink(device_name, "root");
Denis Vlasenko9213a9e2006-09-17 16:28:10 +0000177
Rob Landleyef10d522006-06-26 14:11:33 +0000178 if (ENABLE_FEATURE_MDEV_CONF) chown(device_name, uid, gid);
179 }
180 if (command) {
181 int rc;
182 char *s;
Denis Vlasenko9213a9e2006-09-17 16:28:10 +0000183
Denis Vlasenkof46be092006-10-16 19:39:37 +0000184 s = xasprintf("MDEV=%s", device_name);
Rob Landleyef10d522006-06-26 14:11:33 +0000185 putenv(s);
186 rc = system(command);
Denis Vlasenkof46be092006-10-16 19:39:37 +0000187 s[4] = 0;
Rob Landleyef10d522006-06-26 14:11:33 +0000188 putenv(s);
189 free(s);
190 free(command);
Denis Vlasenkoa9595882006-09-29 21:30:43 +0000191 if (rc == -1) bb_perror_msg_and_die("cannot run %s", command);
Rob Landleyef10d522006-06-26 14:11:33 +0000192 }
193 if (delete) unlink(device_name);
Rob Landley70f7ef72005-12-13 08:21:33 +0000194}
195
196/* Recursive search of /sys/block or /sys/class. path must be a writeable
197 * buffer of size PATH_MAX containing the directory string to start at. */
198
Rob Landleyb56c2852005-12-17 10:52:30 +0000199static void find_dev(char *path)
Rob Landley70f7ef72005-12-13 08:21:33 +0000200{
201 DIR *dir;
Mike Frysingera421ba82006-02-03 00:25:37 +0000202 size_t len = strlen(path);
Mike Frysinger248d2222006-02-03 00:19:42 +0000203 struct dirent *entry;
Rob Landley70f7ef72005-12-13 08:21:33 +0000204
Denis Vlasenkof46be092006-10-16 19:39:37 +0000205 dir = opendir(path);
206 if (dir == NULL)
Mike Frysinger248d2222006-02-03 00:19:42 +0000207 return;
Rob Landley70f7ef72005-12-13 08:21:33 +0000208
Mike Frysinger248d2222006-02-03 00:19:42 +0000209 while ((entry = readdir(dir)) != NULL) {
Rob Landley0960ca72006-06-13 18:27:16 +0000210 struct stat st;
Rob Landley70f7ef72005-12-13 08:21:33 +0000211
212 /* Skip "." and ".." (also skips hidden files, which is ok) */
213
Mike Frysingera421ba82006-02-03 00:25:37 +0000214 if (entry->d_name[0] == '.')
215 continue;
Rob Landley70f7ef72005-12-13 08:21:33 +0000216
Rob Landley0960ca72006-06-13 18:27:16 +0000217 // uClibc doesn't fill out entry->d_type reliably. so we use lstat().
218
219 snprintf(path+len, PATH_MAX-len, "/%s", entry->d_name);
220 if (!lstat(path, &st) && S_ISDIR(st.st_mode)) find_dev(path);
221 path[len] = 0;
Tim Rikerc1ef7bd2006-01-25 00:08:53 +0000222
Rob Landley70f7ef72005-12-13 08:21:33 +0000223 /* If there's a dev entry, mknod it */
Tim Rikerc1ef7bd2006-01-25 00:08:53 +0000224
Rob Landleyef10d522006-06-26 14:11:33 +0000225 if (!strcmp(entry->d_name, "dev")) make_device(path, 0);
Rob Landley70f7ef72005-12-13 08:21:33 +0000226 }
Tim Rikerc1ef7bd2006-01-25 00:08:53 +0000227
Rob Landley70f7ef72005-12-13 08:21:33 +0000228 closedir(dir);
229}
230
Bernhard Reutner-Fischerfebe3c42007-04-04 20:52:03 +0000231int mdev_main(int argc, char **argv);
232int mdev_main(int argc, char **argv)
Rob Landley70f7ef72005-12-13 08:21:33 +0000233{
Rob Landley29e08ff2006-01-12 06:13:50 +0000234 char *action;
235 char *env_path;
236 RESERVE_CONFIG_BUFFER(temp,PATH_MAX);
237
Denis Vlasenko4e5f82c2007-06-03 22:30:22 +0000238 xchdir("/dev");
Rob Landley15fe2e12006-05-08 02:53:23 +0000239
Rob Landley29e08ff2006-01-12 06:13:50 +0000240 /* Scan */
Tim Rikerc1ef7bd2006-01-25 00:08:53 +0000241
Rob Landley29e08ff2006-01-12 06:13:50 +0000242 if (argc == 2 && !strcmp(argv[1],"-s")) {
Rob Landleya7e3d052006-02-21 06:11:13 +0000243 struct stat st;
244
Denis Vlasenkof46be092006-10-16 19:39:37 +0000245 xstat("/", &st);
Denis Vlasenko4e5f82c2007-06-03 22:30:22 +0000246 root_major = major(st.st_dev);
247 root_minor = minor(st.st_dev);
Rob Landley29e08ff2006-01-12 06:13:50 +0000248 strcpy(temp,"/sys/block");
249 find_dev(temp);
250 strcpy(temp,"/sys/class");
251 find_dev(temp);
252
253 /* Hotplug */
254
255 } else {
256 action = getenv("ACTION");
257 env_path = getenv("DEVPATH");
Denis Vlasenko92758142006-10-03 19:56:34 +0000258 if (!action || !env_path)
Mike Frysingera421ba82006-02-03 00:25:37 +0000259 bb_show_usage();
Tim Rikerc1ef7bd2006-01-25 00:08:53 +0000260
Rob Landleyef10d522006-06-26 14:11:33 +0000261 sprintf(temp, "/sys%s", env_path);
262 if (!strcmp(action, "add")) make_device(temp,0);
263 else if (!strcmp(action, "remove")) make_device(temp,1);
Rob Landley29e08ff2006-01-12 06:13:50 +0000264 }
265
Mike Frysingera421ba82006-02-03 00:25:37 +0000266 if (ENABLE_FEATURE_CLEAN_UP) RELEASE_CONFIG_BUFFER(temp);
Rob Landley70f7ef72005-12-13 08:21:33 +0000267 return 0;
268}