mirror of
https://github.com/troglobit/finit.git
synced 2026-10-02 14:02:52 +07:00
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.
294 lines
7.1 KiB
C
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:
|
|
*/
|