Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ AC_PLUGIN([hotplug], [yes], [Start udevd or mdev kernel event datamon])
AC_PLUGIN([rtc], [yes], [Save and restore RTC using hwclock])
AC_PLUGIN([tty], [yes], [Automatically activate new TTYs, e.g. USB-to-serial])
AC_PLUGIN([urandom], [yes], [Setup and save random seed at boot/shutdown])
AC_PLUGIN([plymouth], [no], [Plymouth boot splash integration])
AC_PLUGIN([testserv], [no], [Test plugin to start test serv daemon])

# Check for extra arguments or packages
Expand Down
8 changes: 8 additions & 0 deletions plugins/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ if BUILD_NETLINK_PLUGIN
libplug_la_SOURCES += netlink.c
endif

if BUILD_PLYMOUTH_PLUGIN
libplug_la_SOURCES += plymouth.c
endif

if BUILD_RESOLVCONF_PLUGIN
libplug_la_SOURCES += resolvconf.c
endif
Expand Down Expand Up @@ -76,6 +80,10 @@ if BUILD_NETLINK_PLUGIN
pkglib_LTLIBRARIES += netlink.la
endif

if BUILD_PLYMOUTH_PLUGIN
pkglib_LTLIBRARIES += plymouth.la
endif

if BUILD_RESOLVCONF_PLUGIN
pkglib_LTLIBRARIES += resolvconf.la
endif
Expand Down
293 changes: 293 additions & 0 deletions plugins/plymouth.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
/* Plymouth boot splash plugin for Finit
*
* Copyright (c) 2012-2026 Aaron Andersen <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.
*/

/*
* This plugin integrates the Plymouth boot splash screen with Finit.
* It manages the plymouthd lifecycle across the full boot process:
*
* - HOOK_BANNER: Start plymouthd early, before console output.
* In initramfs, starts fresh. In stage 2, reuses
* the daemon carried over from initramfs if alive.
* - HOOK_ROOTFS_UP .. HOOK_SYSTEM_UP: Display status messages.
* - HOOK_SVC_UP: Tear down plymouth once boot is complete.
* - HOOK_SWITCH_ROOT: Notify plymouth of root filesystem change so
* the daemon survives the initramfs -> rootfs
* transition.
* - HOOK_SHUTDOWN: Restart plymouthd in shutdown mode for a
* splash during poweroff/reboot.
*
* The plugin is only activated when "splash" is present on the kernel
* command line. Plymouth requires devpts for VT takeover; fs_init()
* mounts it early enough for HOOK_BANNER.
*
* NOTE: The initramfs must include /etc/initrd-release. Plymouth
* checks for this file and, when present, prefixes its argv[0] with
* '@' so that the process is not killed during switch_root. Without
* it, plymouthd will not survive the initramfs-to-rootfs transition.
*/

#include "config.h"
#include "finit.h"
#include "helpers.h"
#include "conf.h"
#include "pid.h"
#include "plugin.h"
#include "sig.h"
#include "util.h"
#include "log.h"

#ifndef PLYMOUTH_PATH
#define PLYMOUTH_PATH "/sbin/plymouth"
#endif
#ifndef PLYMOUTHD_PATH
#define PLYMOUTHD_PATH "/sbin/plymouthd"
#endif

#define PLYMOUTH_PIDFILE "/run/plymouthd.pid"

static pid_t daemon_pid;
static int switching_root;
static int in_initramfs;

static int plymouth_cmd(const char *action)
{
char cmd[256];

snprintf(cmd, sizeof(cmd), PLYMOUTH_PATH " %s", action);
return run(cmd, NULL);
}

static void plymouth_message(const char *msg)
{
pid_t pid;

pid = fork();
if (pid == 0) {
sig_unblock();
execl(PLYMOUTH_PATH, PLYMOUTH_PATH,
"display-message", "--text", msg, NULL);
_exit(EX_OSERR);
}
}

static int plymouth_alive(void)
{
return daemon_pid > 0 && pid_alive(daemon_pid);
}

/* Start plymouthd in the given mode ("boot" or "shutdown"). */
static void plymouth_start(const char *mode)
{
char cmd[256];
int rc;

if (plymouth_alive())
return;

snprintf(cmd, sizeof(cmd),
PLYMOUTHD_PATH " --attach-to-session --mode %s --pid-file %s",
mode, PLYMOUTH_PIDFILE);
rc = run(cmd, NULL);
if (rc) {
warnx("plymouthd failed to start (exit %d)", rc);
return;
}

daemon_pid = pid_file_read(PLYMOUTH_PIDFILE);
if (daemon_pid <= 0) {
warnx("plymouthd started but no PID in %s", PLYMOUTH_PIDFILE);
return;
}

rc = plymouth_cmd("show-splash");
if (rc)
warnx("plymouth show-splash failed (exit %d)", rc);
}

static void plymouth_stop(void)
{
if (!plymouth_alive())
return;

plymouth_cmd("quit");

/*
* Don't poll -- we're in a finit hook, so finit's event loop
* is blocked and can't reap children. Trust that plymouthd
* exits after receiving the quit command.
*/
daemon_pid = 0;
unlink(PLYMOUTH_PIDFILE);
}

/*
* HOOK_BANNER - earliest possible hook, before any console output.
*
* In initramfs: start plymouthd fresh.
* In stage 2: reuse plymouthd from initramfs if still alive,
* otherwise start a new instance.
*/
static void plymouth_boot(void *arg)
{
in_initramfs = fexist("/etc/initrd-release");

if (rescue)
return;

enable_progress(0);

if (!in_initramfs) {
if (plymouth_cmd("--ping") == 0) {
daemon_pid = pid_file_read(PLYMOUTH_PIDFILE);
if (daemon_pid <= 0)
daemon_pid = 1; /* alive but unknown pid */
return;
}
}

plymouth_start("boot");
}

/*
* HOOK_SVC_UP - all services launched.
*
* In initramfs: keep splash alive for switch_root.
* In stage 2: boot is done, tear down plymouth.
*/
static void plymouth_boot_done(void *arg)
{
if (in_initramfs)
return;

plymouth_stop();
enable_progress(1);
}

/* HOOK_SWITCH_ROOT - initramfs transitioning to real root. */
static void plymouth_switchroot(void *arg)
{
switching_root = 1;

plymouth_message("Switching to root filesystem...");

if (plymouth_alive())
run(PLYMOUTH_PATH " update-root-fs --new-root-dir=/sysroot", NULL);

enable_progress(1);
}

/* HOOK_SHUTDOWN - entering runlevel 0 or 6. */
static void plymouth_shutdown(void *arg)
{
if (rescue || switching_root)
return;

enable_progress(0);
plymouth_start("shutdown");
}

static void on_rootfs_up(void *arg)
{
plymouth_message("Root filesystem mounted");
}

static void on_mount_post(void *arg)
{
plymouth_message("Mounting filesystems...");
}

static void on_basefs_up(void *arg)
{
plymouth_message("All filesystems mounted");
}

static void on_network_up(void *arg)
{
plymouth_message("Network is up");
}

static void on_system_up(void *arg)
{
plymouth_message("System ready");
}

static plugin_t plugin = {
.name = "plymouth",
.hook[HOOK_BANNER] = { .cb = plymouth_boot },
.hook[HOOK_ROOTFS_UP] = { .cb = on_rootfs_up },
.hook[HOOK_MOUNT_POST] = { .cb = on_mount_post },
.hook[HOOK_BASEFS_UP] = { .cb = on_basefs_up },
.hook[HOOK_NETWORK_UP] = { .cb = on_network_up },
.hook[HOOK_SYSTEM_UP] = { .cb = on_system_up },
.hook[HOOK_SVC_UP] = { .cb = plymouth_boot_done },
.hook[HOOK_SWITCH_ROOT] = { .cb = plymouth_switchroot },
.hook[HOOK_SHUTDOWN] = { .cb = plymouth_shutdown },
};

/*
* Check kernel command line for "splash" argument. Plymouth should
* only be activated when the user explicitly requests it.
*/
static int has_splash_arg(void)
{
char line[LINE_SIZE], *tok, *saveptr;
FILE *fp;

fp = fopen("/proc/cmdline", "r");
if (!fp)
return 0;

if (!fgets(line, sizeof(line), fp)) {
fclose(fp);
return 0;
}
fclose(fp);

for (tok = strtok_r(line, " \t\n", &saveptr); tok;
tok = strtok_r(NULL, " \t\n", &saveptr)) {
if (!strcmp(tok, "splash"))
return 1;
}

return 0;
}

PLUGIN_INIT(__init)
{
if (!has_splash_arg())
return;

plugin_register(&plugin);
}

PLUGIN_EXIT(__exit)
{
plugin_unregister(&plugin);
}

/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
14 changes: 11 additions & 3 deletions src/finit.c
Original file line number Diff line number Diff line change
Expand Up @@ -370,8 +370,8 @@ static void fs_finalize(void)
fs_mount("shm", "/dev/shm", "tmpfs", flags | MS_NODEV, "mode=1777");
}

/* Modern systems use /dev/pts */
if (!fismnt("/dev/pts")) {
/* Remount devpts with proper gid/mode now that /etc/group is available */
{
char opts[32];
int gid;

Expand All @@ -383,7 +383,8 @@ static void fs_finalize(void)
snprintf(opts, sizeof(opts), "gid=%d,mode=0620,ptmxmode=0666", gid);

makedir("/dev/pts", 0755);
fs_mount("devpts", "/dev/pts", "devpts", flags, opts);
fs_mount("devpts", "/dev/pts", "devpts",
flags | MS_REMOUNT, opts);
}

/* Needed on systems like Alpine Linux */
Expand Down Expand Up @@ -529,6 +530,13 @@ static void fs_init(void)

fs_mount(fs[i].spec, fs[i].file, fs[i].type, 0, NULL);
}

/* Early devpts mount for plugins that need PTY access (e.g. plymouth) */
if (!fismnt("/dev/pts")) {
makedir("/dev/pts", 0755);
fs_mount("devpts", "/dev/pts", "devpts",
MS_NOSUID | MS_NOEXEC, "ptmxmode=0666");
}
}


Expand Down
Loading