diff --git a/modules.d/99base/init b/modules.d/99base/init index 3297ac0d..e4d15868 100755 --- a/modules.d/99base/init +++ b/modules.d/99base/init @@ -96,7 +96,7 @@ source_all pre-pivot getarg break && emergency_shell kill $(pidof udevd) echo "Switching to real root filesystem $root" -exec switch_root "$NEWROOT" "$INIT" $CMDLINE || { +exec switch_root -n "$NEWROOT" || { # davej doesn't like initrd bugs echo "Something went very badly wrong in the initrd. Please " echo "file a bug against mkinitrd." diff --git a/switch_root.c b/switch_root.c index b3efadd0..2c10396b 100644 --- a/switch_root.c +++ b/switch_root.c @@ -1,10 +1,7 @@ /* - * switch_root.c + * switchroot.c - switch to new root directory and start init. * - * Code to switch from initramfs to system root. - * Based on nash.c from mkinitrd - * - * Copyright 2002-2009 Red Hat, Inc. All rights reserved. + * Copyright 2002-2008 Red Hat, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,538 +16,311 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . * - * Author(s): Erik Troan - * Jeremy Katz - * Peter Jones - * Harald Hoyer + * Authors: + * Peter Jones + * Jeremy Katz */ #define _GNU_SOURCE 1 + #include #include #include -#include +#include #include -#include -#include +#include +#include #include #include -#include -#include #include -#include -#include -#include -#ifndef MNT_FORCE -#define MNT_FORCE 0x1 +#ifndef MS_MOVE +#define MS_MOVE 8192 #endif #ifndef MNT_DETACH #define MNT_DETACH 0x2 #endif +enum { + ok, + err_no_directory, + err_usage, +}; -#define asprintfa(str, fmt, ...) ({ \ - char *_tmp = NULL; \ - int _rc; \ - _rc = asprintf((str), (fmt), __VA_ARGS__); \ - if (_rc != -1) { \ - _tmp = strdupa(*(str)); \ - if (!_tmp) { \ - _rc = -1; \ - } else { \ - free(*(str)); \ - *(str) = _tmp; \ - } \ - } \ - _rc; \ - }) - - - -static inline int -setFdCoe(int fd, int enable) +static int readFD(int fd, char **buf) { - int rc; - long flags = 0; + char *p; + size_t size = 16384; + int s = 0, filesize = 0; - rc = fcntl(fd, F_GETFD, &flags); - if (rc < 0) - return rc; + if (!(*buf = calloc (16384, sizeof (char)))) + return -1; - if (enable) - flags |= FD_CLOEXEC; - else - flags &= ~FD_CLOEXEC; + do { + p = *buf + filesize; + s = read(fd, p, 16384 - s); + if (s < 0) + break; + filesize += s; + /* only exit for empty reads */ + if (s == 0) + break; + else if (s == 16384) { + *buf = realloc(*buf, size + 16384); + memset(*buf + size, '\0', 16384); + size += s; + s = 0; + } else { + size += s; + } + } while (1); - rc = fcntl(fd, F_SETFD, flags); - return rc; + *buf = realloc(*buf, filesize+1); + (*buf)[filesize] = '\0'; + + return *buf ? filesize : -1; } -static char * -getArg(char * cmd, char * end, char ** arg) +static char *getKernelCmdLine(void) { - char quote = '\0'; + static char *cmdline = NULL; + int fd = -1; + int errnum; - if (!cmd || cmd >= end) - return NULL; + fd = open("/proc/cmdline", O_RDONLY); + if (fd < 0) { + errnum = errno; + fprintf(stderr, "Error: Could not open /proc/cmdline: %m\n"); + errno = errnum; + return NULL; + } + + if (readFD(fd, &cmdline) < 0) { + errnum = errno; + fprintf(stderr, "Error: could not read /proc/cmdline: %m\n"); + close(fd); + errno = errnum; + return NULL; + } + close(fd); - while (isspace(*cmd) && cmd < end) - cmd++; - if (cmd >= end) - return NULL; - - if (*cmd == '"') - cmd++, quote = '"'; - else if (*cmd == '\'') - cmd++, quote = '\''; - - if (quote) { - *arg = cmd; - - /* This doesn't support \ escapes */ - while (cmd < end && *cmd != quote) - cmd++; - - if (cmd == end) { - printf("error: quote mismatch for %s\n", *arg); - return NULL; - } - - *cmd = '\0'; - cmd++; - } else { - *arg = cmd; - while (!isspace(*cmd) && cmd < end) - cmd++; - *cmd = '\0'; - if (**arg == '$') - *arg = getenv(*arg+1); - if (*arg == NULL) - *arg = ""; - } - - cmd++; - - while (isspace(*cmd)) - cmd++; - - return cmd; + return cmdline; } -static int -mountCommand(char * cmd, char * end) +/* get the start of a kernel arg "arg". returns everything after it + * (useful for things like getting the args to init=). so if you only + * want one arg, you need to terminate it at the n */ +static char *getKernelArg(char *arg) { - char * fsType = NULL; - char * device, *spec; - char * mntPoint; - char * opts = NULL; - int rc = 0; - int flags = MS_MGC_VAL; - char * newOpts; + char *start; + char *cmdline; + int len; - if (!(cmd = getArg(cmd, end, &spec))) { - printf( - "usage: mount [--ro] [-o ] -t \n"); - return 1; - } + cmdline = start = getKernelCmdLine(); + if (start == NULL) + return NULL; - while (cmd && *spec == '-') { - if (!strcmp(spec, "--ro")) { - flags |= MS_RDONLY; - } else if (!strcmp(spec, "--bind")) { - flags = MS_BIND; - fsType = "none"; - } else if (!strcmp(spec, "--move")) { - flags = MS_MOVE; - fsType = "none"; - } else if (!strcmp(spec, "-o")) { - cmd = getArg(cmd, end, &opts); - if (!cmd) { - printf("mount: -o requires arguments\n"); - return 1; - } - } else if (!strcmp(spec, "-t")) { - if (!(cmd = getArg(cmd, end, &fsType))) { - printf("mount: missing filesystem type\n"); - return 1; - } - } + while (*start) { + if (isspace(*start)) { + start++; + continue; + } - cmd = getArg(cmd, end, &spec); - } + len = strlen(arg); + /* don't return if it's a different argument that merely starts + * like this one. */ + if (strncmp(start, arg, len) == 0) { + if (start[len] == '=') + return start + len + 1; + if (!start[len] || isspace(start[len])) + return start + len; + } + while (*++start && !isspace(*start)) + ; + } - if (!cmd) { - printf("mount: missing device or mountpoint\n"); - return 1; - } - - if (!(cmd = getArg(cmd, end, &mntPoint))) { - struct mntent *mnt; - FILE *fstab; - - fstab = fopen("/etc/fstab", "r"); - if (!fstab) { - printf("mount: missing mount point\n"); - return 1; - } - do { - if (!(mnt = getmntent(fstab))) { - printf("mount: missing mount point\n"); - fclose(fstab); - return 1; - } - if (!strcmp(mnt->mnt_dir, spec)) { - spec = mnt->mnt_fsname; - mntPoint = mnt->mnt_dir; - - if (!strcmp(mnt->mnt_type, "bind")) { - flags |= MS_BIND; - fsType = "none"; - } else - fsType = mnt->mnt_type; - - opts = mnt->mnt_opts; - break; - } - } while(1); - - fclose(fstab); - } - - if (!fsType) { - printf("mount: filesystem type expected\n"); - return 1; - } - - if (cmd && cmd < end) { - printf("mount: unexpected arguments\n"); - return 1; - } - - /* need to deal with options */ - if (opts) { - char * end; - char * start = opts; - - newOpts = alloca(strlen(opts) + 1); - *newOpts = '\0'; - - while (*start) { - end = strchr(start, ','); - if (!end) { - end = start + strlen(start); - } else { - *end = '\0'; - end++; - } - - if (!strcmp(start, "ro")) - flags |= MS_RDONLY; - else if (!strcmp(start, "rw")) - flags &= ~MS_RDONLY; - else if (!strcmp(start, "nosuid")) - flags |= MS_NOSUID; - else if (!strcmp(start, "suid")) - flags &= ~MS_NOSUID; - else if (!strcmp(start, "nodev")) - flags |= MS_NODEV; - else if (!strcmp(start, "dev")) - flags &= ~MS_NODEV; - else if (!strcmp(start, "noexec")) - flags |= MS_NOEXEC; - else if (!strcmp(start, "exec")) - flags &= ~MS_NOEXEC; - else if (!strcmp(start, "sync")) - flags |= MS_SYNCHRONOUS; - else if (!strcmp(start, "async")) - flags &= ~MS_SYNCHRONOUS; - else if (!strcmp(start, "nodiratime")) - flags |= MS_NODIRATIME; - else if (!strcmp(start, "diratime")) - flags &= ~MS_NODIRATIME; - else if (!strcmp(start, "noatime")) - flags |= MS_NOATIME; - else if (!strcmp(start, "atime")) - flags &= ~MS_NOATIME; - else if (!strcmp(start, "relatime")) - flags |= MS_RELATIME; - else if (!strcmp(start, "norelatime")) - flags &= ~MS_RELATIME; - else if (!strcmp(start, "remount")) - flags |= MS_REMOUNT; - else if (!strcmp(start, "bind")) - flags |= MS_BIND; - else if (!strcmp(start, "defaults")) - ; - else { - if (*newOpts) - strcat(newOpts, ","); - strcat(newOpts, start); - } - - start = end; - } - - opts = newOpts; - } - - device = strdupa(spec); - - if (!device) { - printf("mount: could not find filesystem '%s'\n", spec); - return 1; - } - - { - char *mount_opts = NULL; - mount_opts = opts; - if (mount(device, mntPoint, fsType, flags, mount_opts) < 0) { - printf("mount: error mounting %s on %s as %s: %m\n", - device, mntPoint, fsType); - rc = 1; - } - } - - return rc; + return NULL; } -/* remove all files/directories below dirName -- don't cross mountpoints */ -static int -recursiveRemove(char * dirName) +#define MAX_INIT_ARGS 32 +static int build_init_args(char **init, char ***initargs_out) { - struct stat sb,rb; - DIR * dir; - struct dirent * d; - char * strBuf = alloca(strlen(dirName) + 1024); + const char *initprogs[] = { "/sbin/init", "/etc/init", + "/bin/init", "/bin/sh", NULL }; + const char *ignoreargs[] = { "console=", "BOOT_IMAGE=", NULL }; + char *cmdline = NULL; + char **initargs; - if (!(dir = opendir(dirName))) { - printf("error opening %s: %m\n", dirName); - return 0; - } + int i = 0; - if (fstat(dirfd(dir),&rb)) { - printf("unable to stat %s: %m\n", dirName); - closedir(dir); - return 0; - } + *init = getKernelArg("init"); - errno = 0; - while ((d = readdir(dir))) { - errno = 0; + if (*init == NULL) { + int j; + cmdline = getKernelCmdLine(); + if (cmdline == NULL) + return -1; - if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) { - errno = 0; - continue; - } + for (j = 0; initprogs[j] != NULL; j++) { + if (!access(initprogs[j], X_OK)) { + *init = strdup(initprogs[j]); + break; + } + } + } - strcpy(strBuf, dirName); - strcat(strBuf, "/"); - strcat(strBuf, d->d_name); + initargs = (char **)calloc(MAX_INIT_ARGS+1, sizeof (char *)); + if (initargs == NULL) + return -1; - if (lstat(strBuf, &sb)) { - printf("failed to stat %s: %m\n", strBuf); - errno = 0; - continue; - } + if (cmdline && *init) { + initargs[i++] = *init; + } else { + cmdline = *init; + initargs[0] = NULL; + } - /* only descend into subdirectories if device is same as dir */ - if (S_ISDIR(sb.st_mode)) { - if (sb.st_dev == rb.st_dev) { - recursiveRemove(strBuf); - if (rmdir(strBuf)) - printf("failed to rmdir %s: %m\n", strBuf); - } - errno = 0; - continue; - } + if (cmdline) { + char quote = '\0'; + char *chptr; + char *start; - if (unlink(strBuf)) { - printf("failed to remove %s: %m\n", strBuf); - errno = 0; - continue; - } - } + start = chptr = cmdline; + for (; (i < MAX_INIT_ARGS) && (*start != '\0'); i++) { + while (*chptr && (*chptr != quote)) { + if (isspace(*chptr) && quote == '\0') + break; + if (*chptr == '"' || *chptr == '\'') + quote = *chptr; + chptr++; + } - if (errno) { - closedir(dir); - printf("error reading from %s: %m\n", dirName); - return 1; - } + if (quote == '"' || quote == '\'') + chptr++; + if (*chptr != '\0') + *(chptr++) = '\0'; - closedir(dir); + /* There are some magic parameters added *after* + * everything you pass, including a console= from the + * x86_64 kernel and BOOT_IMAGE= by syslinux. Bash + * doesn't know what they mean, so it then exits, init + * gets killed, desaster ensues. *sigh*. + */ + int j; + for (j = 0; ignoreargs[j] != NULL; j++) { + if (cmdline == *init && !strncmp(start, ignoreargs[j], strlen(ignoreargs[j]))) { + if (!*chptr) + initargs[i] = NULL; + else + i--; + start = chptr; + break; + } + } + if (start == chptr) + continue; - return 0; + if (start[0] == '\0') + i--; + else + initargs[i] = strdup(start); + start = chptr; + } + } + + if (initargs[i-1] != NULL) + initargs[i] = NULL; + + *initargs_out = initargs; + + + return 0; } -static void -mountMntEnt(const struct mntent *mnt) +static void switchroot(const char *newroot) { - char *start = NULL, *end; - char *target = NULL; - struct stat sb; - - printf("mounting %s\n", mnt->mnt_dir); - if (asprintfa(&target, ".%s", mnt->mnt_dir) < 0) { - printf("setuproot: out of memory while mounting %s\n", - mnt->mnt_dir); - return; - } - - if (stat(target, &sb) < 0) - return; - - if (asprintf(&start, "-o %s -t %s %s .%s\n", - mnt->mnt_opts, mnt->mnt_type, mnt->mnt_fsname, - mnt->mnt_dir) < 0) { - printf("setuproot: out of memory while mounting %s\n", - mnt->mnt_dir); - return; - } - - end = start + 1; - while (*end && (*end != '\n')) - end++; - /* end points to the \n at the end of the command */ - - if (mountCommand(start, end) != 0) - printf("setuproot: mount returned error\n"); + /* Don't try to unmount the old "/", there's no way to do it. */ + const char *umounts[] = { "/dev", "/proc", "/sys", NULL }; + char *init, **initargs; + int errnum; + int rc; + int i; + + rc = build_init_args(&init, &initargs); + if (rc < 0) + return; + + for (i = 0; umounts[i] != NULL; i++) { + if (umount2(umounts[i], MNT_DETACH) < 0) { + fprintf(stderr, "Error unmounting old %s: %m\n", + umounts[i]); + fprintf(stderr, "Forcing unmount of %s\n", umounts[i]); + umount2(umounts[i], MNT_FORCE); + } + } + + chdir(newroot); + + if (mount(newroot, "/", NULL, MS_MOVE, NULL) < 0) { + errnum = errno; + fprintf(stderr, "switchroot: mount failed: %m\n"); + errno = errnum; + return; + } + + if (chroot(".")) { + errnum = errno; + fprintf(stderr, "switchroot: chroot failed: %m\n"); + errno = errnum; + return; + } + + if (access(initargs[0], X_OK)) + fprintf(stderr, "WARNING: can't access %s\n", initargs[0]); + + execv(initargs[0], initargs); + return; } -static int -setuprootCommand(char *new) +static void usage(FILE *output) { - FILE *fp; - - printf("Setting up new root fs\n"); - - if (chdir(new)) { - printf("setuproot: chdir(%s) failed: %m\n", new); - return 1; - } - - if (mount("/dev", "./dev", NULL, MS_BIND, NULL) < 0) - printf("setuproot: moving /dev failed: %m\n"); - - fp = setmntent("./etc/fstab.sys", "r"); - if (fp) - printf("using fstab.sys from mounted FS\n"); - else { - fp = setmntent("/etc/fstab.sys", "r"); - if (fp) - printf("using fstab.sys from initrd\n"); - } - if (fp) { - struct mntent *mnt; - - while((mnt = getmntent(fp))) - mountMntEnt(mnt); - endmntent(fp); - } else { - struct { - char *source; - char *target; - char *type; - int flags; - void *data; - int raise; - } fstab[] = { - { "/proc", "./proc", "proc", 0, NULL }, - { "/sys", "./sys", "sysfs", 0, NULL }, -#if 0 - { "/dev/pts", "./dev/pts", "devpts", 0, "gid=5,mode=620" }, - { "/dev/shm", "./dev/shm", "tmpfs", 0, NULL }, - { "/selinux", "/selinux", "selinuxfs", 0, NULL }, -#endif - { NULL, } - }; - int i = 0; - - printf("no fstab.sys, mounting internal defaults\n"); - for (; fstab[i].source != NULL; i++) { - if (mount(fstab[i].source, fstab[i].target, fstab[i].type, - fstab[i].flags, fstab[i].data) < 0) - printf("setuproot: error mounting %s: %m\n", - fstab[i].source); - } - } - - chdir("/"); - return 0; + fprintf(output, "usage: switchroot {-n|--newroot} \n"); + if (output == stderr) + exit(err_usage); + exit(ok); } -int main(int argc, char **argv) +int main(int argc, char *argv[]) { - /* Don't try to unmount the old "/", there's no way to do it. */ - const char *umounts[] = { "/dev", "/proc", "/sys", NULL }; - char *new = NULL; - int fd, i = 0; + int i; + char *newroot = NULL; - argv++; - new = argv[0]; - argv++; - printf("Switching to root: %s\n", new); + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "--help") + || !strcmp(argv[i], "-h") + || !strcmp(argv[i], "--usage")) { + usage(stdout); + } else if (!strcmp(argv[i], "-n") + || !strcmp(argv[i], "--newroot")) { + newroot = argv[++i]; + } else if (!strncmp(argv[i], "--newroot=", 10)) { + newroot = argv[i] + 10; + } else { + usage(stderr); + } + } - setuprootCommand(new); + if (newroot == NULL || newroot[0] == '\0') { + usage(stderr); + } - fd = open("/", O_RDONLY); - for (; umounts[i] != NULL; i++) { - printf("unmounting old %s\n", umounts[i]); - if (umount2(umounts[i], MNT_DETACH) < 0) { - printf("ERROR unmounting old %s: %m\n",umounts[i]); - printf("forcing unmount of %s\n", umounts[i]); - umount2(umounts[i], MNT_FORCE); - } - } - i=0; + switchroot(newroot); - chdir(new); - - recursiveRemove("/"); - - if (mount(new, "/", NULL, MS_MOVE, NULL) < 0) { - printf("switchroot: mount failed: %m\n"); - close(fd); - return 1; - } - - if (chroot(".")) { - printf("switchroot: chroot() failed: %m\n"); - close(fd); - return 1; - } - - /* release the old "/" */ - close(fd); - - close(3); - if ((fd = open("/dev/console", O_RDWR)) < 0) { - printf("ERROR opening /dev/console: %m\n"); - printf("Trying to use fd 0 instead.\n"); - fd = dup2(0, 3); - } else { - setFdCoe(fd, 0); - if (fd != 3) { - dup2(fd, 3); - close(fd); - fd = 3; - } - } - close(0); - dup2(fd, 0); - close(1); - dup2(fd, 1); - close(2); - dup2(fd, 2); - close(fd); - - if (access(argv[0], X_OK)) { - printf("WARNING: can't access %s\n", argv[0]); - } - - execv(argv[0], argv); - - printf("exec of init (%s) failed!!!: %m\n", argv[0]); - return 1; + fprintf(stderr, "switchroot has failed. Sorry.\n"); + return 1; } + +/* + * vim:noet:ts=8:sw=8:sts=8 + */