Files
finit/src/initramfs.c
T
Aaron Andersen 8e7d1b7bb5 Refactor: drop do_ prefix from iterate_proc() and switch_root()
The do_ prefix is conventionally reserved for local helper functions.
Move switch_root() declaration to private.h alongside iterate_proc()
and remove the now-empty initramfs.h header.
2026-01-02 18:13:00 -05:00

294 lines
7.1 KiB
C

/* Initramfs switch_root support
*
* Copyright (c) 2008-2025 Joachim Wiberg <troglobit@gmail.com>
*
* 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 <errno.h>
#include <fcntl.h>
#include <ftw.h>
#include <limits.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/vfs.h>
#include <sys/wait.h>
#include <unistd.h>
#include <linux/magic.h>
#ifdef _LIBITE_LITE
# include <libite/lite.h>
#else
# include <lite/lite.h>
#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;
remove(fpath);
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;
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 */
if (stat(newroot, &newroot_st) || !S_ISDIR(newroot_st.st_mode)) {
logit(LOG_ERR, "switch_root: %s is not a directory", newroot);
errno = ENOTDIR;
return -1;
}
/* Verify newroot is a mount point (different device than parent) */
if (stat("/", &oldroot_st)) {
logit(LOG_ERR, "switch_root: cannot stat /");
return -1;
}
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 */
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
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);
if (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:
*/