1/* vi: set sw=4 ts=4: */ 2/* 3 * mdev - Mini udev for busybox 4 * 5 * Copyright 2005 Rob Landley <rob@landley.net> 6 * Copyright 2005 Frank Sorenson <frank@tuxrocks.com> 7 * 8 * Licensed under GPL version 2, see file LICENSE in this tarball for details. 9 */ 10#include "libbb.h" 11#include "xregex.h" 12 13/* "mdev -s" scans /sys/class/xxx, looking for directories which have dev 14 * file (it is of the form "M:m\n"). Example: /sys/class/tty/tty0/dev 15 * contains "4:0\n". Directory name is taken as device name, path component 16 * directly after /sys/class/ as subsystem. In this example, "tty0" and "tty". 17 * Then mdev creates the /dev/device_name node. 18 * If /sys/class/.../dev file does not exist, mdev still may act 19 * on this device: see "@|$|*command args..." parameter in config file. 20 * 21 * mdev w/o parameters is called as hotplug helper. It takes device 22 * and subsystem names from $DEVPATH and $SUBSYSTEM, extracts 23 * maj,min from "/sys/$DEVPATH/dev" and also examines 24 * $ACTION ("add"/"delete") and $FIRMWARE. 25 * 26 * If action is "add", mdev creates /dev/device_name similarly to mdev -s. 27 * (todo: explain "delete" and $FIRMWARE) 28 * 29 * If /etc/mdev.conf exists, it may modify /dev/device_name's properties. 30 * /etc/mdev.conf file format: 31 * 32 * [-][subsystem/]device user:grp mode [>|=path] [@|$|*command args...] 33 * [-]@maj,min[-min2] user:grp mode [>|=path] [@|$|*command args...] 34 * [-]$envvar=val user:grp mode [>|=path] [@|$|*command args...] 35 * 36 * Leading minus in 1st field means "don't stop on this line", otherwise 37 * search is stopped after the matching line is encountered. 38 * 39 * The device name or "subsystem/device" combo is matched against 1st field 40 * (which is a regex), or maj,min is matched against 1st field, 41 * or specified environment variable (as regex) is matched against 1st field. 42 * 43 * $envvar=val format is useful for loading modules for hot-plugged devices 44 * which do not have driver loaded yet. In this case /sys/class/.../dev 45 * does not exist, but $MODALIAS is set to needed module's name 46 * (actually, an alias to it) by kernel. This rule instructs mdev 47 * to load the module and exit: 48 * $MODALIAS=.* 0:0 660 @modprobe "$MODALIAS" 49 * The kernel will generate another hotplug event when /sys/class/.../dev 50 * file appears. 51 * 52 * When line matches, the device node is created, chmod'ed and chown'ed, 53 * moved to path, and if >path, a symlink to moved node is created, 54 * all this if /sys/class/.../dev exists. 55 * Examples: 56 * =loop/ - moves to /dev/loop 57 * >disk/sda%1 - moves to /dev/disk/sdaN, makes /dev/sdaN a symlink 58 * 59 * Then "command args..." is executed (via sh -c 'command args...'). 60 * @:execute on creation, $:on deletion, *:on both. 61 * This happens regardless of /sys/class/.../dev existence. 62 */ 63 64struct globals { 65 int root_major, root_minor; 66 char *subsystem; 67} FIX_ALIASING; 68#define G (*(struct globals*)&bb_common_bufsiz1) 69 70/* Prevent infinite loops in /sys symlinks */ 71#define MAX_SYSFS_DEPTH 3 72 73/* We use additional 64+ bytes in make_device() */ 74#define SCRATCH_SIZE 80 75 76static void mkdir_recursive(char *name) 77{ 78 /* if name has many levels ("dir1/dir2"), 79 * bb_make_directory() will create dir1 according to umask, 80 * not according to its "mode" parameter. 81 * Since we run with umask=0, need to temporarily switch it. 82 */ 83 umask(022); /* "dir1" (if any) will be 0755 too */ 84 bb_make_directory(name, 0755, FILEUTILS_RECUR); 85 umask(0); 86} 87 88/* Builds an alias path. 89 * This function potentionally reallocates the alias parameter. 90 * Only used for ENABLE_FEATURE_MDEV_RENAME 91 */ 92static char *build_alias(char *alias, const char *device_name) 93{ 94 char *dest; 95 96 /* ">bar/": rename to bar/device_name */ 97 /* ">bar[/]baz": rename to bar[/]baz */ 98 dest = strrchr(alias, '/'); 99 if (dest) { /* ">bar/[baz]" ? */ 100 *dest = '\0'; /* mkdir bar */ 101 mkdir_recursive(alias); 102 *dest = '/'; 103 if (dest[1] == '\0') { /* ">bar/" => ">bar/device_name" */ 104 dest = alias; 105 alias = concat_path_file(alias, device_name); 106 free(dest); 107 } 108 } 109 110 return alias; 111} 112 113/* mknod in /dev based on a path like "/sys/block/hda/hda1" 114 * NB1: path parameter needs to have SCRATCH_SIZE scratch bytes 115 * after NUL, but we promise to not mangle (IOW: to restore if needed) 116 * path string. 117 * NB2: "mdev -s" may call us many times, do not leak memory/fds! 118 */ 119static void make_device(char *path, int delete) 120{ 121 char *device_name, *subsystem_slash_devname; 122 int major, minor, type, len; 123 mode_t mode; 124 parser_t *parser; 125 126 /* Try to read major/minor string. Note that the kernel puts \n after 127 * the data, so we don't need to worry about null terminating the string 128 * because sscanf() will stop at the first nondigit, which \n is. 129 * We also depend on path having writeable space after it. 130 */ 131 major = -1; 132 if (!delete) { 133 char *dev_maj_min = path + strlen(path); 134 135 strcpy(dev_maj_min, "/dev"); 136 len = open_read_close(path, dev_maj_min + 1, 64); 137 *dev_maj_min = '\0'; 138 if (len < 1) { 139 if (!ENABLE_FEATURE_MDEV_EXEC) 140 return; 141 /* no "dev" file, but we can still run scripts 142 * based on device name */ 143 } else if (sscanf(++dev_maj_min, "%u:%u", &major, &minor) != 2) { 144 major = -1; 145 } 146 } 147 /* else: for delete, -1 still deletes the node, but < -1 suppresses that */ 148 149 /* Determine device name, type, major and minor */ 150 device_name = (char*) bb_basename(path); 151 /* http://kernel.org/doc/pending/hotplug.txt says that only 152 * "/sys/block/..." is for block devices. "/sys/bus" etc is not. 153 * But since 2.6.25 block devices are also in /sys/class/block. 154 * We use strstr("/block/") to forestall future surprises. */ 155 type = S_IFCHR; 156 if (strstr(path, "/block/") || (G.subsystem && strncmp(G.subsystem, "block", 5) == 0)) 157 type = S_IFBLK; 158 159 /* Make path point to "subsystem/device_name" */ 160 subsystem_slash_devname = NULL; 161 /* Check for coldplug invocations first */ 162 if (strncmp(path, "/sys/block/", 11) == 0) /* legacy case */ 163 path += sizeof("/sys/") - 1; 164 else if (strncmp(path, "/sys/class/", 11) == 0) 165 path += sizeof("/sys/class/") - 1; 166 else { 167 /* Example of a hotplug invocation: 168 * SUBSYSTEM="block" 169 * DEVPATH="/sys" + "/devices/virtual/mtd/mtd3/mtdblock3" 170 * ("/sys" is added by mdev_main) 171 * - path does not contain subsystem 172 */ 173 subsystem_slash_devname = concat_path_file(G.subsystem, device_name); 174 path = subsystem_slash_devname; 175 } 176 177 /* If we have config file, look up user settings */ 178 if (ENABLE_FEATURE_MDEV_CONF) 179 parser = config_open2("/etc/mdev.conf", fopen_for_read); 180 181 do { 182 int keep_matching; 183 struct bb_uidgid_t ugid; 184 char *tokens[4]; 185 char *command = NULL; 186 char *alias = NULL; 187 char aliaslink = aliaslink; /* for compiler */ 188 189 /* Defaults in case we won't match any line */ 190 ugid.uid = ugid.gid = 0; 191 keep_matching = 0; 192 mode = 0660; 193 194 if (ENABLE_FEATURE_MDEV_CONF 195 && config_read(parser, tokens, 4, 3, "# \t", PARSE_NORMAL) 196 ) { 197 char *val; 198 char *str_to_match; 199 regmatch_t off[1 + 9 * ENABLE_FEATURE_MDEV_RENAME_REGEXP]; 200 201 val = tokens[0]; 202 keep_matching = ('-' == val[0]); 203 val += keep_matching; /* swallow leading dash */ 204 205 /* Match against either "subsystem/device_name" 206 * or "device_name" alone */ 207 str_to_match = strchr(val, '/') ? path : device_name; 208 209 /* Fields: regex uid:gid mode [alias] [cmd] */ 210 211 if (val[0] == '@') { 212 /* @major,minor[-minor2] */ 213 /* (useful when name is ambiguous: 214 * "/sys/class/usb/lp0" and 215 * "/sys/class/printer/lp0") */ 216 int cmaj, cmin0, cmin1, sc; 217 if (major < 0) 218 continue; /* no dev, no match */ 219 sc = sscanf(val, "@%u,%u-%u", &cmaj, &cmin0, &cmin1); 220 if (sc < 1 221 || major != cmaj 222 || (sc == 2 && minor != cmin0) 223 || (sc == 3 && (minor < cmin0 || minor > cmin1)) 224 ) { 225 continue; /* this line doesn't match */ 226 } 227 goto line_matches; 228 } 229 if (val[0] == '$') { 230 /* regex to match an environment variable */ 231 char *eq = strchr(++val, '='); 232 if (!eq) 233 continue; 234 *eq = '\0'; 235 str_to_match = getenv(val); 236 if (!str_to_match) 237 continue; 238 str_to_match -= strlen(val) + 1; 239 *eq = '='; 240 } 241 /* else: regex to match [subsystem/]device_name */ 242 243 { 244 regex_t match; 245 int result; 246 247 xregcomp(&match, val, REG_EXTENDED); 248 result = regexec(&match, str_to_match, ARRAY_SIZE(off), off, 0); 249 regfree(&match); 250 //bb_error_msg("matches:"); 251 //for (int i = 0; i < ARRAY_SIZE(off); i++) { 252 // if (off[i].rm_so < 0) continue; 253 // bb_error_msg("match %d: '%.*s'\n", i, 254 // (int)(off[i].rm_eo - off[i].rm_so), 255 // device_name + off[i].rm_so); 256 //} 257 258 /* If no match, skip rest of line */ 259 /* (regexec returns whole pattern as "range" 0) */ 260 if (result 261 || off[0].rm_so 262 || ((int)off[0].rm_eo != (int)strlen(str_to_match)) 263 ) { 264 continue; /* this line doesn't match */ 265 } 266 } 267 line_matches: 268 /* This line matches. Stop parsing after parsing 269 * the rest the line unless keep_matching == 1 */ 270 271 /* 2nd field: uid:gid - device ownership */ 272 if (get_uidgid(&ugid, tokens[1], 1) == 0) 273 bb_error_msg("unknown user/group %s on line %d", tokens[1], parser->lineno); 274 275 /* 3rd field: mode - device permissions */ 276 bb_parse_mode(tokens[2], &mode); 277 278 val = tokens[3]; 279 /* 4th field (opt): ">|=alias" or "!" to not create the node */ 280 281 if (ENABLE_FEATURE_MDEV_RENAME && val) { 282 char *a, *s, *st; 283 284 a = val; 285 s = strchrnul(val, ' '); 286 st = strchrnul(val, '\t'); 287 if (st < s) 288 s = st; 289 st = (s[0] && s[1]) ? s+1 : NULL; 290 291 aliaslink = a[0]; 292 if (aliaslink == '!' && s == a+1) { 293 val = st; 294 /* "!": suppress node creation/deletion */ 295 major = -2; 296 } 297 else if (aliaslink == '>' || aliaslink == '=') { 298 val = st; 299 s[0] = '\0'; 300 if (ENABLE_FEATURE_MDEV_RENAME_REGEXP) { 301 char *p; 302 unsigned i, n; 303 304 /* substitute %1..9 with off[1..9], if any */ 305 n = 0; 306 s = a; 307 while (*s) 308 if (*s++ == '%') 309 n++; 310 311 p = alias = xzalloc(strlen(a) + n * strlen(str_to_match)); 312 s = a + 1; 313 while (*s) { 314 *p = *s; 315 if ('%' == *s) { 316 i = (s[1] - '0'); 317 if (i <= 9 && off[i].rm_so >= 0) { 318 n = off[i].rm_eo - off[i].rm_so; 319 strncpy(p, str_to_match + off[i].rm_so, n); 320 p += n - 1; 321 s++; 322 } 323 } 324 p++; 325 s++; 326 } 327 } else { 328 alias = xstrdup(a + 1); 329 } 330 } 331 } 332 333 if (ENABLE_FEATURE_MDEV_EXEC && val) { 334 const char *s = "$@*"; 335 const char *s2 = strchr(s, val[0]); 336 337 if (!s2) { 338 bb_error_msg("bad line %u", parser->lineno); 339 if (ENABLE_FEATURE_MDEV_RENAME) 340 free(alias); 341 continue; 342 } 343 344 /* Are we running this command now? 345 * Run $cmd on delete, @cmd on create, *cmd on both 346 */ 347 if (s2 - s != delete) { 348 /* We are here if: '*', 349 * or: '@' and delete = 0, 350 * or: '$' and delete = 1 351 */ 352 command = xstrdup(val + 1); 353 } 354 } 355 } 356 357 /* End of field parsing */ 358 359 /* "Execute" the line we found */ 360 { 361 const char *node_name; 362 363 node_name = device_name; 364 if (ENABLE_FEATURE_MDEV_RENAME && alias) 365 node_name = alias = build_alias(alias, device_name); 366 367 if (!delete && major >= 0) { 368 if (mknod(node_name, mode | type, makedev(major, minor)) && errno != EEXIST) 369 bb_perror_msg("can't create '%s'", node_name); 370 if (major == G.root_major && minor == G.root_minor) 371 symlink(node_name, "root"); 372 if (ENABLE_FEATURE_MDEV_CONF) { 373 chmod(node_name, mode); 374 chown(node_name, ugid.uid, ugid.gid); 375 } 376 if (ENABLE_FEATURE_MDEV_RENAME && alias) { 377 if (aliaslink == '>') 378 symlink(node_name, device_name); 379 } 380 } 381 382 if (ENABLE_FEATURE_MDEV_EXEC && command) { 383 /* setenv will leak memory, use putenv/unsetenv/free */ 384 char *s = xasprintf("%s=%s", "MDEV", node_name); 385 char *s1 = xasprintf("%s=%s", "SUBSYSTEM", G.subsystem); 386 putenv(s); 387 putenv(s1); 388 if (system(command) == -1) 389 bb_perror_msg("can't run '%s'", command); 390 bb_unsetenv_and_free(s1); 391 bb_unsetenv_and_free(s); 392 free(command); 393 } 394 395 if (delete && major >= -1) { 396 if (ENABLE_FEATURE_MDEV_RENAME && alias) { 397 if (aliaslink == '>') 398 unlink(device_name); 399 } 400 unlink(node_name); 401 } 402 403 if (ENABLE_FEATURE_MDEV_RENAME) 404 free(alias); 405 } 406 407 /* We found matching line. 408 * Stop unless it was prefixed with '-' */ 409 if (ENABLE_FEATURE_MDEV_CONF && !keep_matching) 410 break; 411 412 /* end of "while line is read from /etc/mdev.conf" */ 413 } while (ENABLE_FEATURE_MDEV_CONF); 414 415 if (ENABLE_FEATURE_MDEV_CONF) 416 config_close(parser); 417 free(subsystem_slash_devname); 418} 419 420/* File callback for /sys/ traversal */ 421static int FAST_FUNC fileAction(const char *fileName, 422 struct stat *statbuf UNUSED_PARAM, 423 void *userData, 424 int depth UNUSED_PARAM) 425{ 426 size_t len = strlen(fileName) - 4; /* can't underflow */ 427 char *scratch = userData; 428 429 /* len check is for paranoid reasons */ 430 if (strcmp(fileName + len, "/dev") != 0 || len >= PATH_MAX) 431 return FALSE; 432 433 strcpy(scratch, fileName); 434 scratch[len] = '\0'; 435 make_device(scratch, /*delete:*/ 0); 436 437 return TRUE; 438} 439 440/* Directory callback for /sys/ traversal */ 441static int FAST_FUNC dirAction(const char *fileName UNUSED_PARAM, 442 struct stat *statbuf UNUSED_PARAM, 443 void *userData UNUSED_PARAM, 444 int depth) 445{ 446 /* Extract device subsystem -- the name of the directory 447 * under /sys/class/ */ 448 if (1 == depth) { 449 free(G.subsystem); 450 G.subsystem = strrchr(fileName, '/'); 451 if (G.subsystem) 452 G.subsystem = xstrdup(G.subsystem + 1); 453 } 454 455 return (depth >= MAX_SYSFS_DEPTH ? SKIP : TRUE); 456} 457 458/* For the full gory details, see linux/Documentation/firmware_class/README 459 * 460 * Firmware loading works like this: 461 * - kernel sets FIRMWARE env var 462 * - userspace checks /lib/firmware/$FIRMWARE 463 * - userspace waits for /sys/$DEVPATH/loading to appear 464 * - userspace writes "1" to /sys/$DEVPATH/loading 465 * - userspace copies /lib/firmware/$FIRMWARE into /sys/$DEVPATH/data 466 * - userspace writes "0" (worked) or "-1" (failed) to /sys/$DEVPATH/loading 467 * - kernel loads firmware into device 468 */ 469static void load_firmware(const char *firmware, const char *sysfs_path) 470{ 471 int cnt; 472 int firmware_fd, loading_fd, data_fd; 473 474 /* check for /lib/firmware/$FIRMWARE */ 475 xchdir("/lib/firmware"); 476 firmware_fd = xopen(firmware, O_RDONLY); 477 478 /* in case we goto out ... */ 479 data_fd = -1; 480 481 /* check for /sys/$DEVPATH/loading ... give 30 seconds to appear */ 482 xchdir(sysfs_path); 483 for (cnt = 0; cnt < 30; ++cnt) { 484 loading_fd = open("loading", O_WRONLY); 485 if (loading_fd != -1) 486 goto loading; 487 sleep(1); 488 } 489 goto out; 490 491 loading: 492 /* tell kernel we're loading by "echo 1 > /sys/$DEVPATH/loading" */ 493 if (full_write(loading_fd, "1", 1) != 1) 494 goto out; 495 496 /* load firmware into /sys/$DEVPATH/data */ 497 data_fd = open("data", O_WRONLY); 498 if (data_fd == -1) 499 goto out; 500 cnt = bb_copyfd_eof(firmware_fd, data_fd); 501 502 /* tell kernel result by "echo [0|-1] > /sys/$DEVPATH/loading" */ 503 if (cnt > 0) 504 full_write(loading_fd, "0", 1); 505 else 506 full_write(loading_fd, "-1", 2); 507 508 out: 509 if (ENABLE_FEATURE_CLEAN_UP) { 510 close(firmware_fd); 511 close(loading_fd); 512 close(data_fd); 513 } 514} 515 516int mdev_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 517int mdev_main(int argc UNUSED_PARAM, char **argv) 518{ 519 RESERVE_CONFIG_BUFFER(temp, PATH_MAX + SCRATCH_SIZE); 520 521 /* We can be called as hotplug helper */ 522 /* Kernel cannot provide suitable stdio fds for us, do it ourself */ 523 bb_sanitize_stdio(); 524 525 /* Force the configuration file settings exactly */ 526 umask(0); 527 528 xchdir("/dev"); 529 530 if (argv[1] && strcmp(argv[1], "-s") == 0) { 531 /* Scan: 532 * mdev -s 533 */ 534 struct stat st; 535 536 xstat("/", &st); 537 G.root_major = major(st.st_dev); 538 G.root_minor = minor(st.st_dev); 539 540 /* ACTION_FOLLOWLINKS is needed since in newer kernels 541 * /sys/block/loop* (for example) are symlinks to dirs, 542 * not real directories. 543 * (kernel's CONFIG_SYSFS_DEPRECATED makes them real dirs, 544 * but we can't enforce that on users) 545 */ 546 if (access("/sys/class/block", F_OK) != 0) { 547 /* Scan obsolete /sys/block only if /sys/class/block 548 * doesn't exist. Otherwise we'll have dupes. 549 * Also, do not complain if it doesn't exist. 550 * Some people configure kernel to have no blockdevs. 551 */ 552 recursive_action("/sys/block", 553 ACTION_RECURSE | ACTION_FOLLOWLINKS | ACTION_QUIET, 554 fileAction, dirAction, temp, 0); 555 } 556 recursive_action("/sys/class", 557 ACTION_RECURSE | ACTION_FOLLOWLINKS, 558 fileAction, dirAction, temp, 0); 559 } else { 560 char *fw; 561 char *seq; 562 char *action; 563 char *env_path; 564 static const char keywords[] ALIGN1 = "remove\0add\0"; 565 enum { OP_remove = 0, OP_add }; 566 smalluint op; 567 568 /* Hotplug: 569 * env ACTION=... DEVPATH=... SUBSYSTEM=... [SEQNUM=...] mdev 570 * ACTION can be "add" or "remove" 571 * DEVPATH is like "/block/sda" or "/class/input/mice" 572 */ 573 action = getenv("ACTION"); 574 env_path = getenv("DEVPATH"); 575 G.subsystem = getenv("SUBSYSTEM"); 576 if (!action || !env_path /*|| !G.subsystem*/) 577 bb_show_usage(); 578 fw = getenv("FIRMWARE"); 579 op = index_in_strings(keywords, action); 580 /* If it exists, does /dev/mdev.seq match $SEQNUM? 581 * If it does not match, earlier mdev is running 582 * in parallel, and we need to wait */ 583 seq = getenv("SEQNUM"); 584 if (seq) { 585 int timeout = 2000 / 32; /* 2000 msec */ 586 do { 587 int seqlen; 588 char seqbuf[sizeof(int)*3 + 2]; 589 590 seqlen = open_read_close("mdev.seq", seqbuf, sizeof(seqbuf-1)); 591 if (seqlen < 0) { 592 seq = NULL; 593 break; 594 } 595 seqbuf[seqlen] = '\0'; 596 if (seqbuf[0] == '\n' /* seed file? */ 597 || strcmp(seq, seqbuf) == 0 /* correct idx? */ 598 ) { 599 break; 600 } 601 usleep(32*1000); 602 } while (--timeout); 603 } 604 605 snprintf(temp, PATH_MAX, "/sys%s", env_path); 606 if (op == OP_remove) { 607 /* Ignoring "remove firmware". It was reported 608 * to happen and to cause erroneous deletion 609 * of device nodes. */ 610 if (!fw) 611 make_device(temp, /*delete:*/ 1); 612 } 613 else if (op == OP_add) { 614 make_device(temp, /*delete:*/ 0); 615 if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE) { 616 if (fw) 617 load_firmware(fw, temp); 618 } 619 } 620 621 if (seq) { 622 xopen_xwrite_close("mdev.seq", utoa(xatou(seq) + 1)); 623 } 624 } 625 626 if (ENABLE_FEATURE_CLEAN_UP) 627 RELEASE_CONFIG_BUFFER(temp); 628 629 return EXIT_SUCCESS; 630} 631