diff --git a/doc/features.md b/doc/features.md index 51fea9e7..82e5fe78 100644 --- a/doc/features.md +++ b/doc/features.md @@ -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 diff --git a/doc/index.md b/doc/index.md index 4e4f7481..f58b22ff 100644 --- a/doc/index.md +++ b/doc/index.md @@ -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 diff --git a/doc/initctl.md b/doc/initctl.md index aff7503d..de19725c 100644 --- a/doc/initctl.md +++ b/doc/initctl.md @@ -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. diff --git a/doc/plugins.md b/doc/plugins.md index e2306310..73bdd9a3 100644 --- a/doc/plugins.md +++ b/doc/plugins.md @@ -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 diff --git a/doc/switchroot.md b/doc/switchroot.md new file mode 100644 index 00000000..89ae08b6 --- /dev/null +++ b/doc/switchroot.md @@ -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 /sbin/mdevd-coldplug -- Coldplug devices + +# Mount the real root filesystem (after devices are ready) +run [S] name:mount-root /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) diff --git a/mkdocs.yml b/mkdocs.yml index 8e937c08..8d02f735 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -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 diff --git a/src/Makefile.am b/src/Makefile.am index 346c9542..bd126600 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 \ diff --git a/src/api.c b/src/api.c index 87e29fdb..381c2197 100644 --- a/src/api.c +++ b/src/api.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; diff --git a/src/cgroup.c b/src/cgroup.c index ccb44a77..20289cf4 100644 --- a/src/cgroup.c +++ b/src/cgroup.c @@ -34,6 +34,8 @@ #endif #include #include /* get_nprocs_conf() */ +#include +#include #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; } diff --git a/src/finit.h b/src/finit.h index 3c310dcd..5bef212b 100644 --- a/src/finit.h +++ b/src/finit.h @@ -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 diff --git a/src/initctl.c b/src/initctl.c index 7096d62e..25c52cf6 100644 --- a/src/initctl.c +++ b/src/initctl.c @@ -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 } diff --git a/src/initramfs.c b/src/initramfs.c new file mode 100644 index 00000000..59fff4ac --- /dev/null +++ b/src/initramfs.c @@ -0,0 +1,293 @@ +/* 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; + + 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: + */ diff --git a/src/plugin.h b/src/plugin.h index c5febb43..87238270 100644 --- a/src/plugin.h +++ b/src/plugin.h @@ -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"), \ diff --git a/src/private.h b/src/private.h index f69ac0cd..1612803b 100644 --- a/src/private.h +++ b/src/private.h @@ -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_ */ /** diff --git a/src/sig.c b/src/sig.c index 424b67dc..e3e25b52 100644 --- a/src/sig.c +++ b/src/sig.c @@ -77,7 +77,9 @@ #endif #include /* strerror() */ #include +#include #include +#include #include #ifdef _LIBITE_LITE # include @@ -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 */