Skip to content

Commit 726e529

Browse files
committed
Add Plymouth boot splash plugin
Manage the plymouthd lifecycle across boot, switch_root, and shutdown. Activated by the "splash" kernel command line argument.
1 parent 50b9e46 commit 726e529

3 files changed

Lines changed: 319 additions & 0 deletions

File tree

configure.ac

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ AC_PLUGIN([hotplug], [yes], [Start udevd or mdev kernel event datamon])
116116
AC_PLUGIN([rtc], [yes], [Save and restore RTC using hwclock])
117117
AC_PLUGIN([tty], [yes], [Automatically activate new TTYs, e.g. USB-to-serial])
118118
AC_PLUGIN([urandom], [yes], [Setup and save random seed at boot/shutdown])
119+
AC_PLUGIN([plymouth], [no], [Plymouth boot splash integration])
119120
AC_PLUGIN([testserv], [no], [Test plugin to start test serv daemon])
120121

121122
# Check for extra arguments or packages

plugins/Makefile.am

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ if BUILD_NETLINK_PLUGIN
2929
libplug_la_SOURCES += netlink.c
3030
endif
3131

32+
if BUILD_PLYMOUTH_PLUGIN
33+
libplug_la_SOURCES += plymouth.c
34+
endif
35+
3236
if BUILD_RESOLVCONF_PLUGIN
3337
libplug_la_SOURCES += resolvconf.c
3438
endif
@@ -76,6 +80,10 @@ if BUILD_NETLINK_PLUGIN
7680
pkglib_LTLIBRARIES += netlink.la
7781
endif
7882

83+
if BUILD_PLYMOUTH_PLUGIN
84+
pkglib_LTLIBRARIES += plymouth.la
85+
endif
86+
7987
if BUILD_RESOLVCONF_PLUGIN
8088
pkglib_LTLIBRARIES += resolvconf.la
8189
endif

plugins/plymouth.c

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
/* Plymouth boot splash plugin for Finit
2+
*
3+
* Copyright (c) 2012-2026 Joachim Wiberg <troglobit@gmail.com>
4+
*
5+
* Permission is hereby granted, free of charge, to any person obtaining a copy
6+
* of this software and associated documentation files (the "Software"), to deal
7+
* in the Software without restriction, including without limitation the rights
8+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
* copies of the Software, and to permit persons to whom the Software is
10+
* furnished to do so, subject to the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be included in
13+
* all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
* THE SOFTWARE.
22+
*/
23+
24+
/*
25+
* This plugin integrates the Plymouth boot splash screen with Finit.
26+
* It manages the plymouthd lifecycle across the full boot process:
27+
*
28+
* - HOOK_BANNER: Start plymouthd early, before console output.
29+
* In initramfs, starts fresh. In stage 2, reuses
30+
* the daemon carried over from initramfs if alive.
31+
* - HOOK_ROOTFS_UP .. HOOK_SYSTEM_UP: Display status messages.
32+
* - HOOK_SVC_UP: Tear down plymouth once boot is complete.
33+
* - HOOK_SWITCH_ROOT: Notify plymouth of root filesystem change so
34+
* the daemon survives the initramfs -> rootfs
35+
* transition.
36+
* - HOOK_SHUTDOWN: Restart plymouthd in shutdown mode for a
37+
* splash during poweroff/reboot.
38+
*
39+
* The plugin is only activated when "splash" is present on the kernel
40+
* command line. Plymouth requires devpts for VT takeover, which may
41+
* not be mounted yet at HOOK_BANNER time (before fs_finalize), so the
42+
* plugin mounts it if needed.
43+
*
44+
* NOTE: The initramfs must include /etc/initrd-release. Plymouth
45+
* checks for this file and, when present, prefixes its argv[0] with
46+
* '@' so that the process is not killed during switch_root. Without
47+
* it, plymouthd will not survive the initramfs-to-rootfs transition.
48+
*/
49+
50+
#include <sys/mount.h>
51+
#include <sys/stat.h>
52+
53+
#include "config.h"
54+
#include "finit.h"
55+
#include "helpers.h"
56+
#include "conf.h"
57+
#include "pid.h"
58+
#include "plugin.h"
59+
#include "sig.h"
60+
#include "util.h"
61+
#include "log.h"
62+
63+
#ifndef PLYMOUTH_PATH
64+
#define PLYMOUTH_PATH "/sbin/plymouth"
65+
#endif
66+
#ifndef PLYMOUTHD_PATH
67+
#define PLYMOUTHD_PATH "/sbin/plymouthd"
68+
#endif
69+
70+
#define PLYMOUTH_PIDFILE "/run/plymouthd.pid"
71+
72+
static pid_t daemon_pid;
73+
static int switching_root;
74+
static int in_initramfs;
75+
76+
static int plymouth_cmd(const char *action)
77+
{
78+
char cmd[256];
79+
80+
snprintf(cmd, sizeof(cmd), PLYMOUTH_PATH " %s", action);
81+
return run(cmd, NULL);
82+
}
83+
84+
static void plymouth_message(const char *msg)
85+
{
86+
pid_t pid;
87+
88+
pid = fork();
89+
if (pid == 0) {
90+
sig_unblock();
91+
execl(PLYMOUTH_PATH, PLYMOUTH_PATH,
92+
"display-message", "--text", msg, NULL);
93+
_exit(EX_OSERR);
94+
}
95+
}
96+
97+
static int plymouth_alive(void)
98+
{
99+
return daemon_pid > 0 && pid_alive(daemon_pid);
100+
}
101+
102+
/*
103+
* Start plymouthd in the given mode ("boot" or "shutdown").
104+
*
105+
* Mounts devpts if needed -- HOOK_BANNER fires before fs_finalize(),
106+
* so /dev/pts may not exist yet. Plymouth needs it for --attach-to-session
107+
* which gives it control of the VT for rendering.
108+
*/
109+
static void plymouth_start(const char *mode)
110+
{
111+
char cmd[256];
112+
int rc;
113+
114+
if (plymouth_alive())
115+
return;
116+
117+
/* Mount devpts if not already mounted (needed for --attach-to-session) */
118+
if (!fismnt("/dev/pts")) {
119+
mkdir("/dev/pts", 0755);
120+
mount("devpts", "/dev/pts", "devpts",
121+
MS_NOSUID | MS_NOEXEC, "ptmxmode=0666,mode=0620");
122+
}
123+
124+
snprintf(cmd, sizeof(cmd),
125+
PLYMOUTHD_PATH " --attach-to-session --mode %s --pid-file %s",
126+
mode, PLYMOUTH_PIDFILE);
127+
rc = run(cmd, NULL);
128+
if (rc) {
129+
warnx("plymouthd failed to start (exit %d)", rc);
130+
return;
131+
}
132+
133+
daemon_pid = pid_file_read(PLYMOUTH_PIDFILE);
134+
if (daemon_pid <= 0) {
135+
warnx("plymouthd started but no PID in %s", PLYMOUTH_PIDFILE);
136+
return;
137+
}
138+
139+
rc = plymouth_cmd("show-splash");
140+
if (rc)
141+
warnx("plymouth show-splash failed (exit %d)", rc);
142+
}
143+
144+
static void plymouth_stop(void)
145+
{
146+
if (!plymouth_alive())
147+
return;
148+
149+
plymouth_cmd("quit");
150+
151+
/*
152+
* Don't poll -- we're in a finit hook, so finit's event loop
153+
* is blocked and can't reap children. Trust that plymouthd
154+
* exits after receiving the quit command.
155+
*/
156+
daemon_pid = 0;
157+
unlink(PLYMOUTH_PIDFILE);
158+
}
159+
160+
/*
161+
* HOOK_BANNER - earliest possible hook, before any console output.
162+
*
163+
* In initramfs: start plymouthd fresh.
164+
* In stage 2: reuse plymouthd from initramfs if still alive,
165+
* otherwise start a new instance.
166+
*/
167+
static void plymouth_boot(void *arg)
168+
{
169+
in_initramfs = fexist("/etc/initrd-release");
170+
171+
if (rescue)
172+
return;
173+
174+
enable_progress(0);
175+
176+
if (!in_initramfs) {
177+
if (plymouth_cmd("--ping") == 0) {
178+
daemon_pid = pid_file_read(PLYMOUTH_PIDFILE);
179+
if (daemon_pid <= 0)
180+
daemon_pid = 1; /* alive but unknown pid */
181+
return;
182+
}
183+
}
184+
185+
plymouth_start("boot");
186+
}
187+
188+
/*
189+
* HOOK_SVC_UP - all services launched.
190+
*
191+
* In initramfs: keep splash alive for switch_root.
192+
* In stage 2: boot is done, tear down plymouth.
193+
*/
194+
static void plymouth_boot_done(void *arg)
195+
{
196+
if (in_initramfs)
197+
return;
198+
199+
plymouth_stop();
200+
enable_progress(1);
201+
}
202+
203+
/* HOOK_SWITCH_ROOT - initramfs transitioning to real root. */
204+
static void plymouth_switchroot(void *arg)
205+
{
206+
switching_root = 1;
207+
208+
plymouth_message("Switching to root filesystem...");
209+
210+
if (plymouth_alive())
211+
run(PLYMOUTH_PATH " update-root-fs --new-root-dir=/sysroot", NULL);
212+
213+
enable_progress(1);
214+
}
215+
216+
/* HOOK_SHUTDOWN - entering runlevel 0 or 6. */
217+
static void plymouth_shutdown(void *arg)
218+
{
219+
if (rescue || switching_root)
220+
return;
221+
222+
enable_progress(0);
223+
plymouth_start("shutdown");
224+
}
225+
226+
static void on_rootfs_up(void *arg)
227+
{
228+
plymouth_message("Root filesystem mounted");
229+
}
230+
231+
static void on_mount_post(void *arg)
232+
{
233+
plymouth_message("Mounting filesystems...");
234+
}
235+
236+
static void on_basefs_up(void *arg)
237+
{
238+
plymouth_message("All filesystems mounted");
239+
}
240+
241+
static void on_network_up(void *arg)
242+
{
243+
plymouth_message("Network is up");
244+
}
245+
246+
static void on_system_up(void *arg)
247+
{
248+
plymouth_message("System ready");
249+
}
250+
251+
static plugin_t plugin = {
252+
.name = "plymouth",
253+
.hook[HOOK_BANNER] = { .cb = plymouth_boot },
254+
.hook[HOOK_ROOTFS_UP] = { .cb = on_rootfs_up },
255+
.hook[HOOK_MOUNT_POST] = { .cb = on_mount_post },
256+
.hook[HOOK_BASEFS_UP] = { .cb = on_basefs_up },
257+
.hook[HOOK_NETWORK_UP] = { .cb = on_network_up },
258+
.hook[HOOK_SYSTEM_UP] = { .cb = on_system_up },
259+
.hook[HOOK_SVC_UP] = { .cb = plymouth_boot_done },
260+
.hook[HOOK_SWITCH_ROOT] = { .cb = plymouth_switchroot },
261+
.hook[HOOK_SHUTDOWN] = { .cb = plymouth_shutdown },
262+
};
263+
264+
/*
265+
* Check kernel command line for "splash" argument. Plymouth should
266+
* only be activated when the user explicitly requests it.
267+
*/
268+
static int has_splash_arg(void)
269+
{
270+
char line[LINE_SIZE], *tok, *saveptr;
271+
FILE *fp;
272+
273+
fp = fopen("/proc/cmdline", "r");
274+
if (!fp)
275+
return 0;
276+
277+
if (!fgets(line, sizeof(line), fp)) {
278+
fclose(fp);
279+
return 0;
280+
}
281+
fclose(fp);
282+
283+
for (tok = strtok_r(line, " \t\n", &saveptr); tok;
284+
tok = strtok_r(NULL, " \t\n", &saveptr)) {
285+
if (!strcmp(tok, "splash"))
286+
return 1;
287+
}
288+
289+
return 0;
290+
}
291+
292+
PLUGIN_INIT(__init)
293+
{
294+
if (!has_splash_arg())
295+
return;
296+
297+
plugin_register(&plugin);
298+
}
299+
300+
PLUGIN_EXIT(__exit)
301+
{
302+
plugin_unregister(&plugin);
303+
}
304+
305+
/**
306+
* Local Variables:
307+
* indent-tabs-mode: t
308+
* c-file-style: "linux"
309+
* End:
310+
*/

0 commit comments

Comments
 (0)