busybox/util-linux/mdev.c
<<
>>
Prefs
   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
  13struct globals {
  14        int root_major, root_minor;
  15        char *subsystem;
  16};
  17#define G (*(struct globals*)&bb_common_bufsiz1)
  18#define root_major (G.root_major)
  19#define root_minor (G.root_minor)
  20#define subsystem (G.subsystem)
  21
  22/* Prevent infinite loops in /sys symlinks */
  23#define MAX_SYSFS_DEPTH 3
  24
  25/* We use additional 64+ bytes in make_device() */
  26#define SCRATCH_SIZE 80
  27
  28#if ENABLE_FEATURE_MDEV_RENAME
  29/* Builds an alias path.
  30 * This function potentionally reallocates the alias parameter.
  31 */
  32static char *build_alias(char *alias, const char *device_name)
  33{
  34        char *dest;
  35
  36        /* ">bar/": rename to bar/device_name */
  37        /* ">bar[/]baz": rename to bar[/]baz */
  38        dest = strrchr(alias, '/');
  39        if (dest) { /* ">bar/[baz]" ? */
  40                *dest = '\0'; /* mkdir bar */
  41                bb_make_directory(alias, 0755, FILEUTILS_RECUR);
  42                *dest = '/';
  43                if (dest[1] == '\0') { /* ">bar/" => ">bar/device_name" */
  44                        dest = alias;
  45                        alias = concat_path_file(alias, device_name);
  46                        free(dest);
  47                }
  48        }
  49
  50        return alias;
  51}
  52#endif
  53
  54/* mknod in /dev based on a path like "/sys/block/hda/hda1" */
  55/* NB: "mdev -s" may call us many times, do not leak memory/fds! */
  56static void make_device(char *path, int delete)
  57{
  58#if ENABLE_FEATURE_MDEV_CONF
  59        parser_t *parser;
  60#endif
  61        const char *device_name;
  62        int major, minor, type, len;
  63        int mode;
  64        char *dev_maj_min = path + strlen(path);
  65
  66        /* Force the configuration file settings exactly. */
  67        umask(0);
  68
  69        /* Try to read major/minor string.  Note that the kernel puts \n after
  70         * the data, so we don't need to worry about null terminating the string
  71         * because sscanf() will stop at the first nondigit, which \n is.
  72         * We also depend on path having writeable space after it.
  73         */
  74        major = -1;
  75        if (!delete) {
  76                strcpy(dev_maj_min, "/dev");
  77                len = open_read_close(path, dev_maj_min + 1, 64);
  78                *dev_maj_min++ = '\0';
  79                if (len < 1) {
  80                        if (!ENABLE_FEATURE_MDEV_EXEC)
  81                                return;
  82                        /* no "dev" file, so just try to run script */
  83                        *dev_maj_min = '\0';
  84                } else if (sscanf(dev_maj_min, "%u:%u", &major, &minor) != 2) {
  85                        major = -1;
  86                }
  87        }
  88
  89        /* Determine device name, type, major and minor */
  90        device_name = bb_basename(path);
  91        /* http://kernel.org/doc/pending/hotplug.txt says that only
  92         * "/sys/block/..." is for block devices. "/sys/bus" etc is not.
  93         * But since 2.6.25 block devices are also in /sys/class/block.
  94         * We use strstr("/block/") to forestall future surprises. */
  95        type = S_IFCHR;
  96        if (strstr(path, "/block/"))
  97                type = S_IFBLK;
  98
  99#if !ENABLE_FEATURE_MDEV_CONF
 100        mode = 0660;
 101#else
 102        /* If we have config file, look up user settings */
 103        parser = config_open2("/etc/mdev.conf", fopen_for_read);
 104        while (1) {
 105                regmatch_t off[1 + 9*ENABLE_FEATURE_MDEV_RENAME_REGEXP];
 106                int keep_matching;
 107                char *val;
 108                struct bb_uidgid_t ugid;
 109                char *tokens[4];
 110# if ENABLE_FEATURE_MDEV_EXEC
 111                char *command = NULL;
 112# endif
 113# if ENABLE_FEATURE_MDEV_RENAME
 114                char *alias = NULL;
 115                char aliaslink = aliaslink; /* for compiler */
 116# endif
 117                /* Defaults in case we won't match any line */
 118                ugid.uid = ugid.gid = 0;
 119                keep_matching = 0;
 120                mode = 0660;
 121
 122                if (!config_read(parser, tokens, 4, 3, "# \t", PARSE_NORMAL)) {
 123                        /* End of file, create dev node with default params */
 124                        goto line_matches;
 125                }
 126
 127                val = tokens[0];
 128                keep_matching = ('-' == val[0]);
 129                val += keep_matching; /* swallow leading dash */
 130
 131                /* Fields: regex uid:gid mode [alias] [cmd] */
 132
 133                /* 1st field: @<numeric maj,min>... */
 134                if (val[0] == '@') {
 135                        /* @major,minor[-last] */
 136                        /* (useful when name is ambiguous:
 137                         * "/sys/class/usb/lp0" and
 138                         * "/sys/class/printer/lp0") */
 139                        int cmaj, cmin0, cmin1, sc;
 140                        if (major < 0)
 141                                continue; /* no dev, no match */
 142                        sc = sscanf(val, "@%u,%u-%u", &cmaj, &cmin0, &cmin1);
 143                        if (sc < 1 || major != cmaj
 144                         || (sc == 2 && minor != cmin0)
 145                         || (sc == 3 && (minor < cmin0 || minor > cmin1))
 146                        ) {
 147                                continue; /* this line doesn't match */
 148                        }
 149                } else { /* ... or regex to match device name */
 150                        regex_t match;
 151                        int result;
 152                        const char *dev_name_or_subsystem = device_name;
 153                        if ('/' == val[0] && subsystem) {
 154                                dev_name_or_subsystem = subsystem;
 155                                val++;
 156                        }
 157
 158                        /* Is this it? */
 159                        xregcomp(&match, val, REG_EXTENDED);
 160                        result = regexec(&match, dev_name_or_subsystem, ARRAY_SIZE(off), off, 0);
 161                        regfree(&match);
 162
 163                        //bb_error_msg("matches:");
 164                        //for (int i = 0; i < ARRAY_SIZE(off); i++) {
 165                        //      if (off[i].rm_so < 0) continue;
 166                        //      bb_error_msg("match %d: '%.*s'\n", i,
 167                        //              (int)(off[i].rm_eo - off[i].rm_so),
 168                        //              device_name + off[i].rm_so);
 169                        //}
 170
 171                        /* If not this device, skip rest of line */
 172                        /* (regexec returns whole pattern as "range" 0) */
 173                        if (result || off[0].rm_so
 174                         || ((int)off[0].rm_eo != (int)strlen(dev_name_or_subsystem))
 175                        ) {
 176                                continue; /* this line doesn't match */
 177                        }
 178                }
 179
 180                /* This line matches: stop parsing the file after parsing
 181                 * the rest of fields unless keep_matching == 1 */
 182
 183                /* 2nd field: uid:gid - device ownership */
 184                if (get_uidgid(&ugid, tokens[1], 1) == 0)
 185                        bb_error_msg("unknown user/group %s", tokens[1]);
 186
 187                /* 3rd field: mode - device permissions */
 188                mode = strtoul(tokens[2], NULL, 8);
 189
 190                val = tokens[3];
 191                /* 4th field (opt): >|=alias */
 192# if ENABLE_FEATURE_MDEV_RENAME
 193                if (!val)
 194                        goto line_matches;
 195                aliaslink = val[0];
 196                if (aliaslink == '>' || aliaslink == '=') {
 197                        char *a, *s, *st;
 198#  if ENABLE_FEATURE_MDEV_RENAME_REGEXP
 199                        char *p;
 200                        unsigned i, n;
 201#  endif
 202                        a = val;
 203                        s = strchrnul(val, ' ');
 204                        st = strchrnul(val, '\t');
 205                        if (st < s)
 206                                s = st;
 207                        val = (s[0] && s[1]) ? s+1 : NULL;
 208                        s[0] = '\0';
 209
 210#  if ENABLE_FEATURE_MDEV_RENAME_REGEXP
 211                        /* substitute %1..9 with off[1..9], if any */
 212                        n = 0;
 213                        s = a;
 214                        while (*s)
 215                                if (*s++ == '%')
 216                                        n++;
 217
 218                        p = alias = xzalloc(strlen(a) + n * strlen(device_name));
 219                        s = a + 1;
 220                        while (*s) {
 221                                *p = *s;
 222                                if ('%' == *s) {
 223                                        i = (s[1] - '0');
 224                                        if (i <= 9 && off[i].rm_so >= 0) {
 225                                                n = off[i].rm_eo - off[i].rm_so;
 226                                                strncpy(p, device_name + off[i].rm_so, n);
 227                                                p += n - 1;
 228                                                s++;
 229                                        }
 230                                }
 231                                p++;
 232                                s++;
 233                        }
 234#  else
 235                        alias = xstrdup(a + 1);
 236#  endif
 237                }
 238# endif /* ENABLE_FEATURE_MDEV_RENAME */
 239
 240# if ENABLE_FEATURE_MDEV_EXEC
 241                /* The rest (opt): @|$|*command */
 242                if (!val)
 243                        goto line_matches;
 244                {
 245                        const char *s = "@$*";
 246                        const char *s2 = strchr(s, val[0]);
 247
 248                        if (!s2)
 249                                bb_error_msg_and_die("bad line %u", parser->lineno);
 250
 251                        /* Correlate the position in the "@$*" with the delete
 252                         * step so that we get the proper behavior:
 253                         * @cmd: run on create
 254                         * $cmd: run on delete
 255                         * *cmd: run on both
 256                         */
 257                        if ((s2 - s + 1) /*1/2/3*/ & /*1/2*/ (1 + delete)) {
 258                                command = xstrdup(val + 1);
 259                        }
 260                }
 261# endif
 262                /* End of field parsing */
 263 line_matches:
 264#endif /* ENABLE_FEATURE_MDEV_CONF */
 265
 266                /* "Execute" the line we found */
 267
 268                if (!delete && major >= 0) {
 269                        if (ENABLE_FEATURE_MDEV_RENAME)
 270                                unlink(device_name);
 271                        if (mknod(device_name, mode | type, makedev(major, minor)) && errno != EEXIST)
 272                                bb_perror_msg_and_die("mknod %s", device_name);
 273                        if (major == root_major && minor == root_minor)
 274                                symlink(device_name, "root");
 275#if ENABLE_FEATURE_MDEV_CONF
 276                        chown(device_name, ugid.uid, ugid.gid);
 277# if ENABLE_FEATURE_MDEV_RENAME
 278                        if (alias) {
 279                                alias = build_alias(alias, device_name);
 280                                /* move the device, and optionally
 281                                 * make a symlink to moved device node */
 282                                if (rename(device_name, alias) == 0 && aliaslink == '>')
 283                                        symlink(alias, device_name);
 284                                free(alias);
 285                        }
 286# endif
 287#endif
 288                }
 289#if ENABLE_FEATURE_MDEV_EXEC
 290                if (command) {
 291                        /* setenv will leak memory, use putenv/unsetenv/free */
 292                        char *s = xasprintf("%s=%s", "MDEV", device_name);
 293                        char *s1 = xasprintf("%s=%s", "SUBSYSTEM", subsystem);
 294                        putenv(s);
 295                        putenv(s1);
 296                        if (system(command) == -1)
 297                                bb_perror_msg_and_die("can't run '%s'", command);
 298                        unsetenv("SUBSYSTEM");
 299                        free(s1);
 300                        unsetenv("MDEV");
 301                        free(s);
 302                        free(command);
 303                }
 304#endif
 305                if (delete) {
 306                        unlink(device_name);
 307                        /* At creation time, device might have been moved
 308                         * and a symlink might have been created. Undo that. */
 309#if ENABLE_FEATURE_MDEV_RENAME
 310                        if (alias) {
 311                                alias = build_alias(alias, device_name);
 312                                unlink(alias);
 313                                free(alias);
 314                        }
 315#endif
 316                }
 317
 318#if ENABLE_FEATURE_MDEV_CONF
 319                /* We found matching line.
 320                 * Stop unless it was prefixed with '-' */
 321                if (!keep_matching)
 322                        break;
 323        } /* end of "while line is read from /etc/mdev.conf" */
 324
 325        config_close(parser);
 326#endif /* ENABLE_FEATURE_MDEV_CONF */
 327}
 328
 329/* File callback for /sys/ traversal */
 330static int FAST_FUNC fileAction(const char *fileName,
 331                struct stat *statbuf UNUSED_PARAM,
 332                void *userData,
 333                int depth UNUSED_PARAM)
 334{
 335        size_t len = strlen(fileName) - 4; /* can't underflow */
 336        char *scratch = userData;
 337
 338        /* len check is for paranoid reasons */
 339        if (strcmp(fileName + len, "/dev") != 0 || len >= PATH_MAX)
 340                return FALSE;
 341
 342        strcpy(scratch, fileName);
 343        scratch[len] = '\0';
 344        make_device(scratch, 0);
 345
 346        return TRUE;
 347}
 348
 349/* Directory callback for /sys/ traversal */
 350static int FAST_FUNC dirAction(const char *fileName UNUSED_PARAM,
 351                struct stat *statbuf UNUSED_PARAM,
 352                void *userData UNUSED_PARAM,
 353                int depth)
 354{
 355        /* Extract device subsystem -- the name of the directory
 356         * under /sys/class/ */
 357        if (1 == depth) {
 358                free(subsystem);
 359                subsystem = strrchr(fileName, '/');
 360                if (subsystem)
 361                        subsystem = xstrdup(subsystem + 1);
 362        }
 363
 364        return (depth >= MAX_SYSFS_DEPTH ? SKIP : TRUE);
 365}
 366
 367/* For the full gory details, see linux/Documentation/firmware_class/README
 368 *
 369 * Firmware loading works like this:
 370 * - kernel sets FIRMWARE env var
 371 * - userspace checks /lib/firmware/$FIRMWARE
 372 * - userspace waits for /sys/$DEVPATH/loading to appear
 373 * - userspace writes "1" to /sys/$DEVPATH/loading
 374 * - userspace copies /lib/firmware/$FIRMWARE into /sys/$DEVPATH/data
 375 * - userspace writes "0" (worked) or "-1" (failed) to /sys/$DEVPATH/loading
 376 * - kernel loads firmware into device
 377 */
 378static void load_firmware(const char *const firmware, const char *const sysfs_path)
 379{
 380        int cnt;
 381        int firmware_fd, loading_fd, data_fd;
 382
 383        /* check for /lib/firmware/$FIRMWARE */
 384        xchdir("/lib/firmware");
 385        firmware_fd = xopen(firmware, O_RDONLY);
 386
 387        /* in case we goto out ... */
 388        data_fd = -1;
 389
 390        /* check for /sys/$DEVPATH/loading ... give 30 seconds to appear */
 391        xchdir(sysfs_path);
 392        for (cnt = 0; cnt < 30; ++cnt) {
 393                loading_fd = open("loading", O_WRONLY);
 394                if (loading_fd != -1)
 395                        goto loading;
 396                sleep(1);
 397        }
 398        goto out;
 399
 400 loading:
 401        /* tell kernel we're loading by "echo 1 > /sys/$DEVPATH/loading" */
 402        if (full_write(loading_fd, "1", 1) != 1)
 403                goto out;
 404
 405        /* load firmware into /sys/$DEVPATH/data */
 406        data_fd = open("data", O_WRONLY);
 407        if (data_fd == -1)
 408                goto out;
 409        cnt = bb_copyfd_eof(firmware_fd, data_fd);
 410
 411        /* tell kernel result by "echo [0|-1] > /sys/$DEVPATH/loading" */
 412        if (cnt > 0)
 413                full_write(loading_fd, "0", 1);
 414        else
 415                full_write(loading_fd, "-1", 2);
 416
 417 out:
 418        if (ENABLE_FEATURE_CLEAN_UP) {
 419                close(firmware_fd);
 420                close(loading_fd);
 421                close(data_fd);
 422        }
 423}
 424
 425int mdev_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 426int mdev_main(int argc UNUSED_PARAM, char **argv)
 427{
 428        RESERVE_CONFIG_BUFFER(temp, PATH_MAX + SCRATCH_SIZE);
 429
 430        /* We can be called as hotplug helper */
 431        /* Kernel cannot provide suitable stdio fds for us, do it ourself */
 432#if 1
 433        bb_sanitize_stdio();
 434#else
 435        /* Debug code */
 436        /* Replace LOGFILE by other file or device name if you need */
 437#define LOGFILE "/dev/console"
 438        /* Just making sure fd 0 is not closed,
 439         * we don't really intend to read from it */
 440        xmove_fd(xopen("/", O_RDONLY), STDIN_FILENO);
 441        xmove_fd(xopen(LOGFILE, O_WRONLY|O_APPEND), STDOUT_FILENO);
 442        xmove_fd(xopen(LOGFILE, O_WRONLY|O_APPEND), STDERR_FILENO);
 443#endif
 444
 445        xchdir("/dev");
 446
 447        if (argv[1] && strcmp(argv[1], "-s") == 0) {
 448                /* Scan:
 449                 * mdev -s
 450                 */
 451                struct stat st;
 452
 453                xstat("/", &st);
 454                root_major = major(st.st_dev);
 455                root_minor = minor(st.st_dev);
 456
 457                /* ACTION_FOLLOWLINKS is needed since in newer kernels
 458                 * /sys/block/loop* (for example) are symlinks to dirs,
 459                 * not real directories.
 460                 * (kernel's CONFIG_SYSFS_DEPRECATED makes them real dirs,
 461                 * but we can't enforce that on users)
 462                 */
 463                if (access("/sys/class/block", F_OK) != 0) {
 464                        /* Scan obsolete /sys/block only if /sys/class/block
 465                         * doesn't exist. Otherwise we'll have dupes.
 466                         * Also, do not complain if it doesn't exist.
 467                         * Some people configure kernel to have no blockdevs.
 468                         */
 469                        recursive_action("/sys/block",
 470                                ACTION_RECURSE | ACTION_FOLLOWLINKS | ACTION_QUIET,
 471                                fileAction, dirAction, temp, 0);
 472                }
 473                recursive_action("/sys/class",
 474                        ACTION_RECURSE | ACTION_FOLLOWLINKS,
 475                        fileAction, dirAction, temp, 0);
 476        } else {
 477                char *fw;
 478                char *seq;
 479                char *action;
 480                char *env_path;
 481
 482                /* Hotplug:
 483                 * env ACTION=... DEVPATH=... SUBSYSTEM=... [SEQNUM=...] mdev
 484                 * ACTION can be "add" or "remove"
 485                 * DEVPATH is like "/block/sda" or "/class/input/mice"
 486                 */
 487                action = getenv("ACTION");
 488                env_path = getenv("DEVPATH");
 489                subsystem = getenv("SUBSYSTEM");
 490                if (!action || !env_path /*|| !subsystem*/)
 491                        bb_show_usage();
 492                fw = getenv("FIRMWARE");
 493
 494                /* If it exists, does /dev/mdev.seq match $SEQNUM?
 495                 * If it does not match, earlier mdev is running
 496                 * in parallel, and we need to wait */
 497                seq = getenv("SEQNUM");
 498                if (seq) {
 499                        int timeout = 2000 / 32; /* 2000 msec */
 500                        do {
 501                                int seqlen;
 502                                char seqbuf[sizeof(int)*3 + 2];
 503
 504                                seqlen = open_read_close("mdev.seq", seqbuf, sizeof(seqbuf-1));
 505                                if (seqlen < 0) {
 506                                        seq = NULL;
 507                                        break;
 508                                }
 509                                seqbuf[seqlen] = '\0';
 510                                if (seqbuf[0] == '\n' /* seed file? */
 511                                 || strcmp(seq, seqbuf) == 0 /* correct idx? */
 512                                ) {
 513                                        break;
 514                                }
 515                                usleep(32*1000);
 516                        } while (--timeout);
 517                }
 518
 519                snprintf(temp, PATH_MAX, "/sys%s", env_path);
 520                if (strcmp(action, "remove") == 0) {
 521                        /* Ignoring "remove firmware". It was reported
 522                         * to happen and to cause erroneous deletion
 523                         * of device nodes. */
 524                        if (!fw)
 525                                make_device(temp, 1);
 526                }
 527                else if (strcmp(action, "add") == 0) {
 528                        make_device(temp, 0);
 529                        if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE) {
 530                                if (fw)
 531                                        load_firmware(fw, temp);
 532                        }
 533                }
 534
 535                if (seq) {
 536                        xopen_xwrite_close("mdev.seq", utoa(xatou(seq) + 1));
 537                }
 538        }
 539
 540        if (ENABLE_FEATURE_CLEAN_UP)
 541                RELEASE_CONFIG_BUFFER(temp);
 542
 543        return EXIT_SUCCESS;
 544}
 545