1/* vi: set sw=4 ts=4: */ 2/* 3 * 4 * mdev - Mini udev for busybox 5 * 6 * Copyright 2005 Rob Landley <rob@landley.net> 7 * Copyright 2005 Frank Sorenson <frank@tuxrocks.com> 8 * 9 * Licensed under GPL version 2, see file LICENSE in this tarball for details. 10 */ 11 12#include "libbb.h" 13#include "xregex.h" 14 15struct globals { 16 int root_major, root_minor; 17}; 18#define G (*(struct globals*)&bb_common_bufsiz1) 19#define root_major (G.root_major) 20#define root_minor (G.root_minor) 21 22#define MAX_SYSFS_DEPTH 3 /* prevent infinite loops in /sys symlinks */ 23 24/* mknod in /dev based on a path like "/sys/block/hda/hda1" */ 25static void make_device(char *path, int delete) 26{ 27 const char *device_name; 28 int major, minor, type, len; 29 int mode = 0660; 30 uid_t uid = 0; 31 gid_t gid = 0; 32 char *temp = path + strlen(path); 33 char *command = NULL; 34 35 /* Try to read major/minor string. Note that the kernel puts \n after 36 * the data, so we don't need to worry about null terminating the string 37 * because sscanf() will stop at the first nondigit, which \n is. We 38 * also depend on path having writeable space after it. */ 39 40 if (!delete) { 41 strcat(path, "/dev"); 42 len = open_read_close(path, temp + 1, 64); 43 *temp++ = 0; 44 if (len < 1) return; 45 } 46 47 /* Determine device name, type, major and minor */ 48 49 device_name = bb_basename(path); 50 type = path[5]=='c' ? S_IFCHR : S_IFBLK; 51 52 /* If we have a config file, look up permissions for this device */ 53 54 if (ENABLE_FEATURE_MDEV_CONF) { 55 char *conf, *pos, *end; 56 int line, fd; 57 58 /* mmap the config file */ 59 fd = open("/etc/mdev.conf", O_RDONLY); 60 if (fd < 0) 61 goto end_parse; 62 len = xlseek(fd, 0, SEEK_END); 63 conf = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0); 64 close(fd); 65 if (!conf) 66 goto end_parse; 67 68 line = 0; 69 /* Loop through lines in mmaped file*/ 70 for (pos=conf; pos-conf<len;) { 71 int field; 72 char *end2; 73 74 line++; 75 /* find end of this line */ 76 for (end=pos; end-conf<len && *end!='\n'; end++) 77 ; 78 79 /* Three fields: regex, uid:gid, mode */ 80 for (field=0; field < (3 + ENABLE_FEATURE_MDEV_EXEC); 81 field++) 82 { 83 /* Skip whitespace */ 84 while (pos<end && isspace(*pos)) pos++; 85 if (pos==end || *pos=='#') break; 86 for (end2=pos; 87 end2<end && !isspace(*end2) && *end2!='#'; end2++) 88 ; 89 90 if (field == 0) { 91 /* Regex to match this device */ 92 93 char *regex = xstrndup(pos, end2-pos); 94 regex_t match; 95 regmatch_t off; 96 int result; 97 98 /* Is this it? */ 99 xregcomp(&match,regex, REG_EXTENDED); 100 result = regexec(&match, device_name, 1, &off, 0); 101 regfree(&match); 102 free(regex); 103 104 /* If not this device, skip rest of line */ 105 if (result || off.rm_so 106 || off.rm_eo != strlen(device_name)) 107 break; 108 } 109 if (field == 1) { 110 /* uid:gid */ 111 112 char *s, *s2; 113 114 /* Find : */ 115 for (s=pos; s<end2 && *s!=':'; s++) 116 ; 117 if (s == end2) break; 118 119 /* Parse UID */ 120 uid = strtoul(pos, &s2, 10); 121 if (s != s2) { 122 struct passwd *pass; 123 char *_unam = xstrndup(pos, s-pos); 124 pass = getpwnam(_unam); 125 free(_unam); 126 if (!pass) break; 127 uid = pass->pw_uid; 128 } 129 s++; 130 /* parse GID */ 131 gid = strtoul(s, &s2, 10); 132 if (end2 != s2) { 133 struct group *grp; 134 char *_grnam = xstrndup(s, end2-s); 135 grp = getgrnam(_grnam); 136 free(_grnam); 137 if (!grp) break; 138 gid = grp->gr_gid; 139 } 140 } 141 if (field == 2) { 142 /* mode */ 143 144 mode = strtoul(pos, &pos, 8); 145 if (pos != end2) break; 146 } 147 if (ENABLE_FEATURE_MDEV_EXEC && field == 3) { 148 // Command to run 149 const char *s = "@$*"; 150 const char *s2; 151 s2 = strchr(s, *pos++); 152 if (!s2) { 153 // Force error 154 field = 1; 155 break; 156 } 157 if ((s2-s+1) & (1<<delete)) 158 command = xstrndup(pos, end-pos); 159 } 160 161 pos = end2; 162 } 163 164 /* Did everything parse happily? */ 165 166 if (field > 2) break; 167 if (field) bb_error_msg_and_die("bad line %d",line); 168 169 /* Next line */ 170 pos = ++end; 171 } 172 munmap(conf, len); 173 end_parse: /* nothing */ ; 174 } 175 176 umask(0); 177 if (!delete) { 178 if (sscanf(temp, "%d:%d", &major, &minor) != 2) return; 179 if (mknod(device_name, mode | type, makedev(major, minor)) && errno != EEXIST) 180 bb_perror_msg_and_die("mknod %s", device_name); 181 182 if (major == root_major && minor == root_minor) 183 symlink(device_name, "root"); 184 185 if (ENABLE_FEATURE_MDEV_CONF) chown(device_name, uid, gid); 186 } 187 if (command) { 188 /* setenv will leak memory, so use putenv */ 189 char *s = xasprintf("MDEV=%s", device_name); 190 putenv(s); 191 if (system(command) == -1) 192 bb_perror_msg_and_die("cannot run %s", command); 193 s[4] = '\0'; 194 unsetenv(s); 195 free(s); 196 free(command); 197 } 198 if (delete) unlink(device_name); 199} 200 201/* File callback for /sys/ traversal */ 202static int fileAction(const char *fileName, struct stat *statbuf, 203 void *userData, int depth) 204{ 205 size_t len = strlen(fileName) - 4; 206 char *scratch = userData; 207 208 if (strcmp(fileName + len, "/dev")) 209 return FALSE; 210 211 strcpy(scratch, fileName); 212 scratch[len] = 0; 213 make_device(scratch, 0); 214 215 return TRUE; 216} 217 218/* Directory callback for /sys/ traversal */ 219static int dirAction(const char *fileName, struct stat *statbuf, 220 void *userData, int depth) 221{ 222 return (depth >= MAX_SYSFS_DEPTH ? SKIP : TRUE); 223} 224 225/* For the full gory details, see linux/Documentation/firmware_class/README 226 * 227 * Firmware loading works like this: 228 * - kernel sets FIRMWARE env var 229 * - userspace checks /lib/firmware/$FIRMWARE 230 * - userspace waits for /sys/$DEVPATH/loading to appear 231 * - userspace writes "1" to /sys/$DEVPATH/loading 232 * - userspace copies /lib/firmware/$FIRMWARE into /sys/$DEVPATH/data 233 * - userspace writes "0" (worked) or "-1" (failed) to /sys/$DEVPATH/loading 234 * - kernel loads firmware into device 235 */ 236static void load_firmware(const char *const firmware, const char *const sysfs_path) 237{ 238 int cnt; 239 int firmware_fd, loading_fd, data_fd; 240 241 /* check for $FIRMWARE from kernel */ 242 243 /* check for /lib/firmware/$FIRMWARE */ 244 xchdir("/lib/firmware"); 245 firmware_fd = xopen(firmware, O_RDONLY); 246 247 /* in case we goto out ... */ 248 data_fd = -1; 249 250 /* check for /sys/$DEVPATH/loading ... give 30 seconds to appear */ 251 xchdir(sysfs_path); 252 for (cnt = 0; cnt < 30; ++cnt) { 253 loading_fd = open("loading", O_WRONLY); 254 if (loading_fd == -1) 255 sleep(1); 256 else 257 break; 258 } 259 if (loading_fd == -1) 260 goto out; 261 262 /* tell kernel we're loading by `echo 1 > /sys/$DEVPATH/loading` */ 263 if (write(loading_fd, "1", 1) != 1) 264 goto out; 265 266 /* load firmware by `cat /lib/firmware/$FIRMWARE > /sys/$DEVPATH/data */ 267 data_fd = open("data", O_WRONLY); 268 if (data_fd == -1) 269 goto out; 270 cnt = bb_copyfd_eof(firmware_fd, data_fd); 271 272 /* tell kernel result by `echo [0|-1] > /sys/$DEVPATH/loading` */ 273 if (cnt > 0) 274 write(loading_fd, "0", 1); 275 else 276 write(loading_fd, "-1", 2); 277 278 out: 279 if (ENABLE_FEATURE_CLEAN_UP) { 280 close(firmware_fd); 281 close(loading_fd); 282 close(data_fd); 283 } 284} 285 286int mdev_main(int argc, char **argv); 287int mdev_main(int argc, char **argv) 288{ 289 char *action; 290 char *env_path; 291 RESERVE_CONFIG_BUFFER(temp,PATH_MAX); 292 293 xchdir("/dev"); 294 295 /* Scan */ 296 297 if (argc == 2 && !strcmp(argv[1],"-s")) { 298 struct stat st; 299 300 xstat("/", &st); 301 root_major = major(st.st_dev); 302 root_minor = minor(st.st_dev); 303 304 recursive_action("/sys/block", 305 ACTION_RECURSE | ACTION_FOLLOWLINKS, 306 fileAction, dirAction, temp, 0); 307 308 recursive_action("/sys/class", 309 ACTION_RECURSE | ACTION_FOLLOWLINKS, 310 fileAction, dirAction, temp, 0); 311 312 /* Hotplug */ 313 314 } else { 315 action = getenv("ACTION"); 316 env_path = getenv("DEVPATH"); 317 if (!action || !env_path) 318 bb_show_usage(); 319 320 sprintf(temp, "/sys%s", env_path); 321 if (!strcmp(action, "remove")) 322 make_device(temp, 1); 323 else if (!strcmp(action, "add")) { 324 make_device(temp, 0); 325 326 if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE) 327 load_firmware(getenv("FIRMWARE"), temp); 328 } 329 } 330 331 if (ENABLE_FEATURE_CLEAN_UP) RELEASE_CONFIG_BUFFER(temp); 332 return 0; 333} 334