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