mirror of
https://github.com/troglobit/finit.git
synced 2026-10-13 10:14:47 +00:00
Merge pull request #463 from aanderse/switch_root
Add switch_root support for initramfs to real root transitions
This commit is contained in:
@@ -288,4 +288,27 @@ Rescue mode can be disabled at build time with `configure --without-rescue`.
|
||||
See the [Rescue Mode](config/rescue.md) section for more information.
|
||||
|
||||
|
||||
**Switch Root**
|
||||
|
||||
Finit supports switching from an initramfs to a real root filesystem using
|
||||
the built-in `initctl switch-root` command. This allows Finit to serve as
|
||||
the init system in an initramfs for early boot tasks (LUKS unlock, LVM
|
||||
activation, network boot) before transitioning to the real root.
|
||||
|
||||
```bash
|
||||
# In initramfs, after mounting the real root:
|
||||
initctl switch-root /mnt/root
|
||||
```
|
||||
|
||||
The switch-root operation:
|
||||
|
||||
1. Runs the `HOOK_SWITCH_ROOT` hook for cleanup
|
||||
2. Stops all services gracefully
|
||||
3. Moves virtual filesystems (`/dev`, `/proc`, `/sys`, `/run`) to the new root
|
||||
4. Deletes initramfs contents to free memory
|
||||
5. Pivots to the new root and execs the new init
|
||||
|
||||
See the [Switch Root](switchroot.md) section for complete documentation.
|
||||
|
||||
|
||||
[5]: https://en.wikipedia.org/wiki/Runlevel
|
||||
|
||||
@@ -39,6 +39,7 @@ Features
|
||||
* [Cgroups v2](config/cgroups.md), both configuration and monitoring in [`initctl top`](initctl.md)
|
||||
* [Plugin support](plugins.md) for customization
|
||||
* Proper [rescue mode](config/rescue.md) with bundled `sulogin` for protected maintenance shell
|
||||
* [Switch root](switchroot.md) support for initramfs-to-real-root transitions
|
||||
* Integration with [watchdogd][] for full system supervision
|
||||
* [Logging](config/logging.md) to kernel ring buffer before `syslogd` has started, see the
|
||||
recommended [sysklogd][] project for complete logging integration
|
||||
|
||||
@@ -65,6 +65,7 @@ Commands:
|
||||
halt Halt system
|
||||
poweroff Halt and power off system
|
||||
suspend Suspend system
|
||||
switch-root NEWROOT [INIT] Switch to new root filesystem (initramfs only)
|
||||
|
||||
utmp show Raw dump of UTMP/WTMP db
|
||||
```
|
||||
@@ -143,3 +144,24 @@ Apr 8 12:37:46 alpine authpriv.info dropbear[2300]: Exit (root) from <192.168.1
|
||||
Apr 8 15:02:11 alpine authpriv.info dropbear[2634]: Child connection from 192.168.121.1:48576
|
||||
Apr 8 15:02:12 alpine authpriv.notice dropbear[2634]: Password auth succeeded for 'root' from 192.168.121.1:48576
|
||||
```
|
||||
|
||||
|
||||
Switch Root
|
||||
-----------
|
||||
|
||||
The `switch-root` command is used when running Finit in an initramfs to
|
||||
transition to the real root filesystem. This is similar to the standalone
|
||||
`switch_root(8)` utility but integrated into Finit.
|
||||
|
||||
```
|
||||
initctl switch-root /mnt/root [/sbin/init]
|
||||
```
|
||||
|
||||
Requirements:
|
||||
|
||||
- Must be run during runlevel S (bootstrap) or runlevel 1
|
||||
- The new root must be a mount point (different device than /)
|
||||
- Can only be used when Finit is running as PID 1 in an initramfs
|
||||
|
||||
For complete documentation and usage examples, see the dedicated
|
||||
[Switch Root](switchroot.md) section.
|
||||
|
||||
@@ -205,6 +205,16 @@ hook points:
|
||||
new runlevel have been been stopped. When the hook has completed,
|
||||
Finit continues to start all services in the new runlevel.
|
||||
|
||||
### Switch Root Hooks
|
||||
|
||||
* `HOOK_SWITCH_ROOT`, `hook/sys/switchroot`: Called when
|
||||
`initctl switch-root` is issued, before the transition begins. Use
|
||||
this hook to save state, unmount initramfs-only filesystems, or perform
|
||||
cleanup before switching to the new root. Only runs when Finit is
|
||||
operating as PID 1 in an initramfs.
|
||||
|
||||
See the [Switch Root](switchroot.md) section for more information.
|
||||
|
||||
### Shutdown Hooks
|
||||
|
||||
* `HOOK_NETWORK_DN`, `hook/net/down`: Called right after having changed
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
Switch Root
|
||||
===========
|
||||
|
||||
Finit supports switching from an initramfs to a real root filesystem
|
||||
using the `initctl switch-root` command. This is useful for systems
|
||||
that use an initramfs for early boot (LUKS, LVM, network boot, etc.)
|
||||
and need to transition to the real root before starting services.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
```sh
|
||||
initctl switch-root NEWROOT [INIT]
|
||||
```
|
||||
|
||||
- `NEWROOT`: Path to the mounted new root filesystem (e.g., `/mnt/root`)
|
||||
- `INIT`: Optional path to init on the new root (default: `/sbin/init`)
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
1. Must be run during runlevel S (bootstrap) or runlevel 1
|
||||
2. `NEWROOT` must be a mount point (different device than /)
|
||||
3. `INIT` must exist and be executable on the new root
|
||||
4. Finit must be running as PID 1 (in initramfs)
|
||||
|
||||
|
||||
How It Works
|
||||
------------
|
||||
|
||||
1. Runs `HOOK_SWITCH_ROOT` for any cleanup scripts/plugins
|
||||
2. Runs `HOOK_SHUTDOWN` to notify plugins
|
||||
3. Stops all services and kills remaining processes
|
||||
4. Exits all plugins gracefully
|
||||
5. Moves `/dev`, `/proc`, `/sys`, `/run` to new root
|
||||
6. Deletes initramfs contents (if on tmpfs/ramfs) to free memory
|
||||
7. Moves new root mount to `/`
|
||||
8. Chroots to new root
|
||||
9. Reopens `/dev/console` for stdin/stdout/stderr
|
||||
10. Execs new init as PID 1
|
||||
|
||||
|
||||
Example: Initramfs finit.conf
|
||||
-----------------------------
|
||||
|
||||
Configuration file `/etc/finit.conf` in the initramfs:
|
||||
|
||||
```
|
||||
# /etc/finit.conf in initramfs
|
||||
|
||||
# Mount the real root filesystem
|
||||
run [S] name:mount-root /bin/mount /dev/sda1 /mnt/root -- Mounting root filesystem
|
||||
|
||||
# Switch to real root after mount completes
|
||||
run [S] name:switch-root /sbin/initctl switch-root /mnt/root -- Switching to real root
|
||||
```
|
||||
|
||||
For more complex setups (LUKS, LVM, etc.):
|
||||
|
||||
```
|
||||
# Unlock LUKS volume
|
||||
run [S] name:cryptsetup /sbin/cryptsetup open /dev/sda2 cryptroot -- Unlocking encrypted root
|
||||
|
||||
# Activate LVM
|
||||
run [S] name:lvm /sbin/lvm vgchange -ay -- Activating LVM volumes
|
||||
|
||||
# Mount root
|
||||
run [S] name:mount-root /bin/mount /dev/vg0/root /mnt/root -- Mounting root
|
||||
|
||||
# Switch root
|
||||
run [S] name:switch-root /sbin/initctl switch-root /mnt/root -- Switching to real root
|
||||
```
|
||||
|
||||
|
||||
Example: Using Runlevel 1 for Switch Root
|
||||
-----------------------------------------
|
||||
|
||||
For more complex initramfs setups where ordering of tasks becomes
|
||||
difficult in runlevel S, you can perform the switch-root in runlevel 1:
|
||||
|
||||
```
|
||||
# /etc/finit.conf in initramfs
|
||||
|
||||
# Start mdevd for device handling
|
||||
service [S] name:mdevd notify:s6 /sbin/mdevd -D %n -- Device event daemon
|
||||
run [S] name:coldplug <service/mdevd/ready> /sbin/mdevd-coldplug -- Coldplug devices
|
||||
|
||||
# Mount the real root filesystem (after devices are ready)
|
||||
run [S] name:mount-root <run/coldplug/success> /bin/mount /dev/sda1 /mnt/root -- Mounting root
|
||||
|
||||
# Transition to runlevel 1 after all S tasks complete
|
||||
# The switch-root runs cleanly in runlevel 1
|
||||
run [1] name:switch-root /sbin/initctl switch-root /mnt/root -- Switching to real root
|
||||
```
|
||||
|
||||
This approach separates the initramfs setup (runlevel S) from the
|
||||
switch-root operation (runlevel 1), making task ordering simpler.
|
||||
|
||||
|
||||
Hooks
|
||||
-----
|
||||
|
||||
The `HOOK_SWITCH_ROOT` hook runs before the switch begins. Use it for:
|
||||
|
||||
- Saving state to the new root
|
||||
- Unmounting initramfs-only mounts
|
||||
- Cleanup tasks
|
||||
|
||||
Plugins can register for `HOOK_SWITCH_ROOT` just like other hooks:
|
||||
|
||||
```c
|
||||
static void my_switch_root_hook(void *arg)
|
||||
{
|
||||
/* Cleanup before switch_root */
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
.name = "my-plugin",
|
||||
.hook[HOOK_SWITCH_ROOT] = {
|
||||
.cb = my_switch_root_hook
|
||||
}
|
||||
};
|
||||
|
||||
PLUGIN_INIT(plugin_init)
|
||||
{
|
||||
plugin_register(&plugin);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Conditions
|
||||
----------
|
||||
|
||||
After switch_root, the new finit instance starts fresh. No conditions
|
||||
or state are preserved across the switch. The new finit will:
|
||||
|
||||
1. Re-read `/etc/finit.conf` from the new root
|
||||
2. Re-initialize all conditions
|
||||
3. Start services according to the new configuration
|
||||
|
||||
|
||||
See Also
|
||||
--------
|
||||
|
||||
- [switch_root(8)](https://man7.org/linux/man-pages/man8/switch_root.8.html) - util-linux switch_root utility
|
||||
- [Kernel initramfs documentation](https://docs.kernel.org/filesystems/ramfs-rootfs-initramfs.html)
|
||||
@@ -79,6 +79,7 @@ nav:
|
||||
- Limitations: config/limitations.md
|
||||
- Usage:
|
||||
- Commands & Status: initctl.md
|
||||
- Switch Root: switchroot.md
|
||||
- Rebooting & Halting: commands.md
|
||||
- Command Line Options: cmdline.md
|
||||
- Managing Services: service.md
|
||||
|
||||
@@ -63,6 +63,7 @@ finit_SOURCES = api.c cgroup.c cgroup.h \
|
||||
exec.c finit.c finit.h \
|
||||
stty.c \
|
||||
helpers.c helpers.h \
|
||||
initramfs.c \
|
||||
iwatch.c iwatch.h \
|
||||
log.c log.h \
|
||||
mdadm.c mount.c \
|
||||
|
||||
@@ -245,6 +245,46 @@ static void bypass_shutdown(void *unused)
|
||||
do_shutdown(halt);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle switch_root API command.
|
||||
* Parses data: "newroot\0newinit\0"
|
||||
* Sends ACK before attempting switch_root since it doesn't return on success.
|
||||
* Returns: result from switch_root() on failure, doesn't return on success.
|
||||
*/
|
||||
static int do_switch_root_api(int sd, struct init_request *rq)
|
||||
{
|
||||
char *newroot, *newinit = NULL;
|
||||
char *ptr;
|
||||
int result;
|
||||
|
||||
dbg("switch-root %s", rq->data);
|
||||
strterm(rq->data, sizeof(rq->data));
|
||||
|
||||
newroot = rq->data;
|
||||
ptr = strchr(newroot, '\0');
|
||||
if (ptr && ptr < rq->data + sizeof(rq->data) - 1) {
|
||||
ptr++;
|
||||
if (*ptr)
|
||||
newinit = ptr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Send ACK first, since we won't return from
|
||||
* switch_root() on success.
|
||||
*/
|
||||
rq->cmd = INIT_CMD_ACK;
|
||||
if (write(sd, rq, sizeof(*rq)) != sizeof(*rq))
|
||||
dbg("Failed sending ACK to client");
|
||||
close(sd);
|
||||
|
||||
/* This does not return on success */
|
||||
result = switch_root(newroot, newinit);
|
||||
if (result)
|
||||
logit(LOG_ERR, "switch_root failed: %s", strerror(errno));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int do_reboot(int cmd, int timeout, char *buf, size_t len)
|
||||
{
|
||||
int rc = 1;
|
||||
@@ -403,6 +443,15 @@ static void api_cb(uev_t *w, void *arg, int events)
|
||||
rq.cmd, rq.data);
|
||||
goto leave;
|
||||
}
|
||||
break;
|
||||
|
||||
case INIT_CMD_SWITCH_ROOT:
|
||||
if (runlevel != INIT_LEVEL && runlevel != 1) {
|
||||
warnx("switch-root only allowed in runlevel S or 1");
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -503,6 +552,10 @@ static void api_cb(uev_t *w, void *arg, int events)
|
||||
result = do_reboot(rq.cmd, rq.sleeptime, rq.data, sizeof(rq.data));
|
||||
break;
|
||||
|
||||
case INIT_CMD_SWITCH_ROOT:
|
||||
do_switch_root_api(sd, &rq);
|
||||
goto leave;
|
||||
|
||||
case INIT_CMD_ACK:
|
||||
dbg("Client failed reading ACK");
|
||||
goto leave;
|
||||
|
||||
+32
-6
@@ -34,6 +34,8 @@
|
||||
#endif
|
||||
#include <sys/mount.h>
|
||||
#include <sys/sysinfo.h> /* get_nprocs_conf() */
|
||||
#include <sys/vfs.h>
|
||||
#include <linux/magic.h>
|
||||
|
||||
#include "cgroup.h"
|
||||
#include "finit.h"
|
||||
@@ -841,6 +843,7 @@ void cgroup_config(void)
|
||||
void cgroup_init(uev_ctx_t *ctx)
|
||||
{
|
||||
int opts = MS_NODEV | MS_NOEXEC | MS_NOSUID;
|
||||
int mounted = 0;
|
||||
char buf[80];
|
||||
FILE *fp;
|
||||
int fd;
|
||||
@@ -851,14 +854,36 @@ void cgroup_init(uev_ctx_t *ctx)
|
||||
#endif
|
||||
|
||||
if (mount("none", FINIT_CGPATH, "cgroup2", opts, NULL)) {
|
||||
if (errno == ENOENT)
|
||||
if (errno == EBUSY) {
|
||||
/*
|
||||
* Already mounted - this happens after switch_root
|
||||
* when cgroups were moved from the initramfs.
|
||||
* Verify it's actually cgroup2 before proceeding.
|
||||
*/
|
||||
struct statfs sfs;
|
||||
|
||||
if (statfs(FINIT_CGPATH, &sfs) || sfs.f_type != CGROUP2_SUPER_MAGIC) {
|
||||
logit(LOG_ERR, "Mount point %s busy but not cgroup2", FINIT_CGPATH);
|
||||
avail = 0;
|
||||
return;
|
||||
}
|
||||
dbg("cgroup2 already mounted at %s, reusing", FINIT_CGPATH);
|
||||
} else if (errno == ENOENT) {
|
||||
logit(LOG_INFO, "Kernel does not support cgroups v2, disabling.");
|
||||
else if (errno == EPERM) /* Probably inside an unprivileged container */
|
||||
avail = 0;
|
||||
return;
|
||||
} else if (errno == EPERM) {
|
||||
/* Probably inside an unprivileged container */
|
||||
logit(LOG_INFO, "Not allowed to mount cgroups v2, disabling.");
|
||||
else
|
||||
avail = 0;
|
||||
return;
|
||||
} else {
|
||||
err(1, "Failed mounting cgroup v2");
|
||||
avail = 0;
|
||||
return;
|
||||
avail = 0;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
mounted = 1;
|
||||
}
|
||||
avail = 1;
|
||||
|
||||
@@ -867,7 +892,8 @@ void cgroup_init(uev_ctx_t *ctx)
|
||||
if (!fp) {
|
||||
err(1, "Failed opening %s", FINIT_CGPATH "/cgroup.controllers");
|
||||
abort:
|
||||
umount(FINIT_CGPATH);
|
||||
if (mounted)
|
||||
umount(FINIT_CGPATH);
|
||||
avail = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
#define INIT_CMD_HALT 21
|
||||
#define INIT_CMD_POWEROFF 22
|
||||
#define INIT_CMD_SUSPEND 23
|
||||
#define INIT_CMD_SWITCH_ROOT 24 /* Switch to new root filesystem */
|
||||
#define INIT_CMD_WDOG_HELLO 128 /* Watchdog register and hello */
|
||||
#define INIT_CMD_SVC_ITER 129
|
||||
#define INIT_CMD_SVC_QUERY 130
|
||||
|
||||
+56
-1
@@ -646,6 +646,59 @@ int do_halt (char *arg) { return do_cmd(INIT_CMD_HALT); }
|
||||
int do_poweroff(char *arg) { return do_cmd(INIT_CMD_POWEROFF); }
|
||||
int do_suspend (char *arg) { return do_cmd(INIT_CMD_SUSPEND); }
|
||||
|
||||
/**
|
||||
* do_switch_root - Switch to a new root filesystem (initramfs only)
|
||||
* @argc: Number of arguments (1 or 2)
|
||||
* @argv: [0] = newroot path, [1] = optional init path
|
||||
*
|
||||
* This command is only valid during runlevel S (bootstrap) or 1 when
|
||||
* running from an initramfs. It stops all services, moves virtual
|
||||
* filesystems, and exec's the new init.
|
||||
*/
|
||||
int do_switch_root(int argc, char *argv[])
|
||||
{
|
||||
struct init_request rq = {
|
||||
.magic = INIT_MAGIC,
|
||||
.cmd = INIT_CMD_SWITCH_ROOT,
|
||||
};
|
||||
char *newroot, *newinit = NULL;
|
||||
size_t off;
|
||||
|
||||
if (argc < 1)
|
||||
ERRX(2, "Usage: initctl switch-root NEWROOT [INIT]");
|
||||
|
||||
newroot = argv[0];
|
||||
if (argc > 1)
|
||||
newinit = argv[1];
|
||||
|
||||
/* Verify newroot exists */
|
||||
if (access(newroot, F_OK))
|
||||
ERR(1, "Cannot access %s", newroot);
|
||||
|
||||
/*
|
||||
* Pack both strings into rq.data with null separators:
|
||||
* "newroot\0newinit\0"
|
||||
*/
|
||||
strlcpy(rq.data, newroot, sizeof(rq.data));
|
||||
off = strlen(newroot) + 1;
|
||||
|
||||
if (newinit && off < sizeof(rq.data) - 1)
|
||||
strlcpy(rq.data + off, newinit, sizeof(rq.data) - off);
|
||||
|
||||
printf("Switching root to %s", newroot);
|
||||
if (newinit)
|
||||
printf(", init %s", newinit);
|
||||
printf(" ...\n");
|
||||
|
||||
/*
|
||||
* On success, finit exec's new init and we lose connection.
|
||||
* A "failure" to read reply is actually expected on success.
|
||||
*/
|
||||
client_send(&rq, sizeof(rq));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int utmp_show(char *file)
|
||||
{
|
||||
struct utmp *ut;
|
||||
@@ -1474,7 +1527,8 @@ static int usage(int rc)
|
||||
" reboot Reboot system\n"
|
||||
" halt Halt system\n"
|
||||
" poweroff Halt and power off system\n"
|
||||
" suspend Suspend system\n");
|
||||
" suspend Suspend system\n"
|
||||
" switch-root ROOT [INIT] Switch to new root filesystem (initramfs)\n");
|
||||
|
||||
if (utmp)
|
||||
fprintf(stderr,
|
||||
@@ -1654,6 +1708,7 @@ int main(int argc, char *argv[])
|
||||
{ "halt", NULL, do_halt, NULL, NULL },
|
||||
{ "poweroff", NULL, do_poweroff, NULL, NULL },
|
||||
{ "suspend", NULL, do_suspend, NULL, NULL },
|
||||
{ "switch-root", NULL, NULL, NULL, do_switch_root },
|
||||
|
||||
{ "utmp", NULL, do_utmp, &utmp, NULL },
|
||||
{ NULL, NULL, NULL, NULL, NULL }
|
||||
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
/* 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:
|
||||
*/
|
||||
@@ -81,6 +81,9 @@
|
||||
CHOOSE(HOOK_SVC_RECONF, "nop"), \
|
||||
CHOOSE(HOOK_RUNLEVEL_CHANGE, "nop"), \
|
||||
\
|
||||
/* Switch root hook, before transitioning */ \
|
||||
CHOOSE(HOOK_SWITCH_ROOT, "hook/sys/switchroot"), \
|
||||
\
|
||||
/* Shutdown hooks, runlevel [06] */ \
|
||||
CHOOSE(HOOK_NETWORK_DN, "hook/net/down"), \
|
||||
CHOOSE(HOOK_SHUTDOWN, "hook/sys/shutdown"), \
|
||||
|
||||
@@ -59,6 +59,9 @@ void plugin_script_run(hook_point_t no);
|
||||
int plugin_init (uev_ctx_t *ctx);
|
||||
void plugin_exit (void);
|
||||
|
||||
void iterate_proc (int (*cb)(int, void *), void *data);
|
||||
int switch_root (const char *newroot, const char *newinit);
|
||||
|
||||
#endif /* FINIT_PRIVATE_H_ */
|
||||
|
||||
/**
|
||||
|
||||
@@ -77,7 +77,9 @@
|
||||
#endif
|
||||
#include <string.h> /* strerror() */
|
||||
#include <sched.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/reboot.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#ifdef _LIBITE_LITE
|
||||
# include <libite/lite.h>
|
||||
@@ -206,7 +208,7 @@ static void fs_swapoff(void)
|
||||
*
|
||||
* https://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons/
|
||||
*/
|
||||
void do_iterate_proc(int (*cb)(int, void *), void *data)
|
||||
void iterate_proc(int (*cb)(int, void *), void *data)
|
||||
{
|
||||
DIR *dirp;
|
||||
|
||||
@@ -288,7 +290,7 @@ static int do_wait(int secs)
|
||||
;
|
||||
has_proc = 0;
|
||||
iterations--;
|
||||
do_iterate_proc(status_cb, &has_proc);
|
||||
iterate_proc(status_cb, &has_proc);
|
||||
}
|
||||
while (has_proc && iterations > 0);
|
||||
|
||||
@@ -320,10 +322,10 @@ void do_shutdown(shutop_t op)
|
||||
* Tell remaining non-monitored processes to exit, give them
|
||||
* time to exit gracefully, 2 sec was customary, we go for 1.
|
||||
*/
|
||||
do_iterate_proc(kill_cb, &signo);
|
||||
iterate_proc(kill_cb, &signo);
|
||||
if (do_wait(1)) {
|
||||
signo = SIGKILL;
|
||||
do_iterate_proc(kill_cb, &signo);
|
||||
iterate_proc(kill_cb, &signo);
|
||||
}
|
||||
|
||||
/* Exit plugins gracefully */
|
||||
|
||||
Reference in New Issue
Block a user