/* Initramfs switch_root support * * Copyright (c) 2008-2025 Joachim Wiberg * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "config.h" /* Generated by configure script */ #include #include #include #include #include #include #include #include #include #include #include #ifdef _LIBITE_LITE # include #else # include #endif #include "finit.h" #include "cond.h" #include "helpers.h" #include "log.h" #include "plugin.h" #include "private.h" #include "sig.h" #include "util.h" /* State for delete_cb(), nftw() doesn't support passing user data */ static const char *delete_skip; static dev_t delete_rootdev; /* * nftw() callback to delete initramfs contents before switch_root. * Skips the new root mount point and stays on the same filesystem. */ static int delete_cb(const char *fpath, const struct stat *sb, int tflag, struct FTW *ftw) { (void)tflag; /* Skip root directory itself */ if (ftw->level == 0) return 0; /* Skip the new root mount point */ if (delete_skip && !strncmp(fpath, delete_skip, strlen(delete_skip))) return 0; /* Stay on the same filesystem (initramfs) */ if (sb->st_dev != delete_rootdev) return 0; if (remove(fpath)) dbg("Failed to remove %s: %s", fpath, strerror(errno)); return 0; } /* * Recursively delete initramfs contents to free memory before switch_root. */ static void delete_initramfs_contents(dev_t rootdev, const char *skip) { delete_rootdev = rootdev; delete_skip = skip; nftw("/", delete_cb, 20, FTW_DEPTH | FTW_PHYS | FTW_MOUNT); } /* * Check if we're running on a tmpfs/ramfs (initramfs) */ static int is_initramfs(void) { struct statfs sfs; if (statfs("/", &sfs)) return 0; return sfs.f_type == RAMFS_MAGIC || sfs.f_type == TMPFS_MAGIC; } /* * Move a mount point from oldpath to newpath under newroot */ static int do_move_mount(const char *oldpath, const char *newroot) { char newpath[PATH_MAX]; struct stat st; if (stat(oldpath, &st)) return 0; /* Not mounted, skip */ snprintf(newpath, sizeof(newpath), "%s%s", newroot, oldpath); /* Create target directory if needed */ makedir(newpath, 0755); if (mount(oldpath, newpath, NULL, MS_MOVE, NULL)) { dbg("Failed to move %s to %s: %s", oldpath, newpath, strerror(errno)); return -1; } dbg("Moved %s to %s", oldpath, newpath); return 0; } static int kill_cb(int pid, void *data) { int signo = *(int *)data; kill(pid, signo); return 0; } /* * Perform switch_root to a new root filesystem * * This function does not return on success - it exec's the new init. * On failure, it returns -1 and sets errno. */ int switch_root(const char *newroot, const char *newinit) { struct stat newroot_st, oldroot_st; char init_path[PATH_MAX]; int console_fd; int fd; dev_t rootdev; int signo; if (!newroot || !newroot[0]) { errno = EINVAL; return -1; } /* Default to /sbin/init if not specified */ if (!newinit || !newinit[0]) newinit = "/sbin/init"; /* Verify we're PID 1 */ if (getpid() != 1) { logit(LOG_ERR, "switch_root must be run as PID 1"); errno = EPERM; return -1; } /* Verify newroot exists and is a directory */ fd = open(newroot, O_RDONLY | O_DIRECTORY); if (fd < 0) { logit(LOG_ERR, "switch_root: %s is not a directory", newroot); errno = ENOTDIR; return -1; } if (fstat(fd, &newroot_st)) { close(fd); logit(LOG_ERR, "switch_root: cannot stat %s", newroot); return -1; } close(fd); /* Verify newroot is a mount point (different device than parent) */ fd = open("/", O_RDONLY | O_DIRECTORY); if (fd < 0) { logit(LOG_ERR, "switch_root: cannot open /"); return -1; } if (fstat(fd, &oldroot_st)) { close(fd); logit(LOG_ERR, "switch_root: cannot stat /"); return -1; } close(fd); if (newroot_st.st_dev == oldroot_st.st_dev) { logit(LOG_ERR, "switch_root: %s is not a mount point", newroot); errno = EINVAL; return -1; } /* Verify init exists in new root */ snprintf(init_path, sizeof(init_path), "%s%s", newroot, newinit); if (access(init_path, X_OK)) { logit(LOG_ERR, "switch_root: %s not found or not executable", init_path); errno = ENOENT; return -1; } logit(LOG_NOTICE, "Performing switch_root to %s, init %s", newroot, newinit); /* Run switch_root hook before we start tearing things down */ plugin_run_hooks(HOOK_SWITCH_ROOT); /* Stop all services gracefully */ dbg("Stopping all services..."); halt = SHUT_OFF; /* Prevent actual shutdown */ /* Use existing shutdown logic to stop services */ api_exit(); log_exit(); plugin_run_hooks(HOOK_SHUTDOWN); /* Kill remaining processes (except kernel threads and ourselves) */ signo = SIGTERM; iterate_proc(kill_cb, &signo); do_usleep(500000); /* Give them 500ms */ signo = SIGKILL; iterate_proc(kill_cb, &signo); /* Reap zombies */ while (waitpid(-1, NULL, WNOHANG) > 0) ; /* Exit plugins */ plugin_exit(); cond_exit(); /* Move virtual filesystems to new root */ dbg("Moving virtual filesystems..."); do_move_mount("/dev", newroot); do_move_mount("/proc", newroot); do_move_mount("/sys", newroot); do_move_mount("/run", newroot); /* Change to new root directory */ if (chdir(newroot)) { err(1, "Failed to chdir to %s", newroot); return -1; } /* Delete contents of old root if we're on initramfs */ rootdev = oldroot_st.st_dev; if (is_initramfs()) { dbg("Deleting initramfs contents..."); delete_initramfs_contents(rootdev, newroot); } /* Mount --move newroot to / */ if (mount(newroot, "/", NULL, MS_MOVE, NULL)) { err(1, "Failed to move %s to /", newroot); return -1; } /* chroot to new root */ if (chroot(".")) { err(1, "Failed to chroot to new root"); return -1; } if (chdir("/")) { err(1, "Failed to chdir to /"); return -1; } /* Reopen console in new root. dup2() closes the old fds itself, * so open() returns a fd > STDERR_FILENO that we can always close. */ console_fd = open("/dev/console", O_RDWR); if (console_fd >= 0) { dup2(console_fd, STDIN_FILENO); dup2(console_fd, STDOUT_FILENO); dup2(console_fd, STDERR_FILENO); close(console_fd); } /* Reset signals to default */ sig_unblock(); /* Exec the new init - this does not return on success */ dbg("Executing %s...", newinit); execl(newinit, newinit, NULL); /* If we get here, exec failed */ err(1, "Failed to exec %s", newinit); return -1; } /** * Local Variables: * indent-tabs-mode: t * c-file-style: "linux" * End: */