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