Skip to content

Commit a815307

Browse files
authored
Merge pull request #483 from aanderse/plymouth
Add Plymouth boot splash plugin
2 parents 50b9e46 + 1518eb9 commit a815307

4 files changed

Lines changed: 313 additions & 3 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: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
/* Plymouth boot splash plugin for Finit
2+
*
3+
* Copyright (c) 2012-2026 Aaron Andersen <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; fs_init()
41+
* mounts it early enough for HOOK_BANNER.
42+
*
43+
* NOTE: The initramfs must include /etc/initrd-release. Plymouth
44+
* checks for this file and, when present, prefixes its argv[0] with
45+
* '@' so that the process is not killed during switch_root. Without
46+
* it, plymouthd will not survive the initramfs-to-rootfs transition.
47+
*/
48+
49+
#include "config.h"
50+
#include "finit.h"
51+
#include "helpers.h"
52+
#include "conf.h"
53+
#include "pid.h"
54+
#include "plugin.h"
55+
#include "sig.h"
56+
#include "util.h"
57+
#include "log.h"
58+
59+
#ifndef PLYMOUTH_PATH
60+
#define PLYMOUTH_PATH "/sbin/plymouth"
61+
#endif
62+
#ifndef PLYMOUTHD_PATH
63+
#define PLYMOUTHD_PATH "/sbin/plymouthd"
64+
#endif
65+
66+
#define PLYMOUTH_PIDFILE "/run/plymouthd.pid"
67+
68+
static pid_t daemon_pid;
69+
static int switching_root;
70+
static int in_initramfs;
71+
72+
static int plymouth_cmd(const char *action)
73+
{
74+
char cmd[256];
75+
76+
snprintf(cmd, sizeof(cmd), PLYMOUTH_PATH " %s", action);
77+
return run(cmd, NULL);
78+
}
79+
80+
static void plymouth_message(const char *msg)
81+
{
82+
pid_t pid;
83+
84+
pid = fork();
85+
if (pid == 0) {
86+
sig_unblock();
87+
execl(PLYMOUTH_PATH, PLYMOUTH_PATH,
88+
"display-message", "--text", msg, NULL);
89+
_exit(EX_OSERR);
90+
}
91+
}
92+
93+
static int plymouth_alive(void)
94+
{
95+
return daemon_pid > 0 && pid_alive(daemon_pid);
96+
}
97+
98+
/* Start plymouthd in the given mode ("boot" or "shutdown"). */
99+
static void plymouth_start(const char *mode)
100+
{
101+
char cmd[256];
102+
int rc;
103+
104+
if (plymouth_alive())
105+
return;
106+
107+
snprintf(cmd, sizeof(cmd),
108+
PLYMOUTHD_PATH " --attach-to-session --mode %s --pid-file %s",
109+
mode, PLYMOUTH_PIDFILE);
110+
rc = run(cmd, NULL);
111+
if (rc) {
112+
warnx("plymouthd failed to start (exit %d)", rc);
113+
return;
114+
}
115+
116+
daemon_pid = pid_file_read(PLYMOUTH_PIDFILE);
117+
if (daemon_pid <= 0) {
118+
warnx("plymouthd started but no PID in %s", PLYMOUTH_PIDFILE);
119+
return;
120+
}
121+
122+
rc = plymouth_cmd("show-splash");
123+
if (rc)
124+
warnx("plymouth show-splash failed (exit %d)", rc);
125+
}
126+
127+
static void plymouth_stop(void)
128+
{
129+
if (!plymouth_alive())
130+
return;
131+
132+
plymouth_cmd("quit");
133+
134+
/*
135+
* Don't poll -- we're in a finit hook, so finit's event loop
136+
* is blocked and can't reap children. Trust that plymouthd
137+
* exits after receiving the quit command.
138+
*/
139+
daemon_pid = 0;
140+
unlink(PLYMOUTH_PIDFILE);
141+
}
142+
143+
/*
144+
* HOOK_BANNER - earliest possible hook, before any console output.
145+
*
146+
* In initramfs: start plymouthd fresh.
147+
* In stage 2: reuse plymouthd from initramfs if still alive,
148+
* otherwise start a new instance.
149+
*/
150+
static void plymouth_boot(void *arg)
151+
{
152+
in_initramfs = fexist("/etc/initrd-release");
153+
154+
if (rescue)
155+
return;
156+
157+
enable_progress(0);
158+
159+
if (!in_initramfs) {
160+
if (plymouth_cmd("--ping") == 0) {
161+
daemon_pid = pid_file_read(PLYMOUTH_PIDFILE);
162+
if (daemon_pid <= 0)
163+
daemon_pid = 1; /* alive but unknown pid */
164+
return;
165+
}
166+
}
167+
168+
plymouth_start("boot");
169+
}
170+
171+
/*
172+
* HOOK_SVC_UP - all services launched.
173+
*
174+
* In initramfs: keep splash alive for switch_root.
175+
* In stage 2: boot is done, tear down plymouth.
176+
*/
177+
static void plymouth_boot_done(void *arg)
178+
{
179+
if (in_initramfs)
180+
return;
181+
182+
plymouth_stop();
183+
enable_progress(1);
184+
}
185+
186+
/* HOOK_SWITCH_ROOT - initramfs transitioning to real root. */
187+
static void plymouth_switchroot(void *arg)
188+
{
189+
switching_root = 1;
190+
191+
plymouth_message("Switching to root filesystem...");
192+
193+
if (plymouth_alive())
194+
run(PLYMOUTH_PATH " update-root-fs --new-root-dir=/sysroot", NULL);
195+
196+
enable_progress(1);
197+
}
198+
199+
/* HOOK_SHUTDOWN - entering runlevel 0 or 6. */
200+
static void plymouth_shutdown(void *arg)
201+
{
202+
if (rescue || switching_root)
203+
return;
204+
205+
enable_progress(0);
206+
plymouth_start("shutdown");
207+
}
208+
209+
static void on_rootfs_up(void *arg)
210+
{
211+
plymouth_message("Root filesystem mounted");
212+
}
213+
214+
static void on_mount_post(void *arg)
215+
{
216+
plymouth_message("Mounting filesystems...");
217+
}
218+
219+
static void on_basefs_up(void *arg)
220+
{
221+
plymouth_message("All filesystems mounted");
222+
}
223+
224+
static void on_network_up(void *arg)
225+
{
226+
plymouth_message("Network is up");
227+
}
228+
229+
static void on_system_up(void *arg)
230+
{
231+
plymouth_message("System ready");
232+
}
233+
234+
static plugin_t plugin = {
235+
.name = "plymouth",
236+
.hook[HOOK_BANNER] = { .cb = plymouth_boot },
237+
.hook[HOOK_ROOTFS_UP] = { .cb = on_rootfs_up },
238+
.hook[HOOK_MOUNT_POST] = { .cb = on_mount_post },
239+
.hook[HOOK_BASEFS_UP] = { .cb = on_basefs_up },
240+
.hook[HOOK_NETWORK_UP] = { .cb = on_network_up },
241+
.hook[HOOK_SYSTEM_UP] = { .cb = on_system_up },
242+
.hook[HOOK_SVC_UP] = { .cb = plymouth_boot_done },
243+
.hook[HOOK_SWITCH_ROOT] = { .cb = plymouth_switchroot },
244+
.hook[HOOK_SHUTDOWN] = { .cb = plymouth_shutdown },
245+
};
246+
247+
/*
248+
* Check kernel command line for "splash" argument. Plymouth should
249+
* only be activated when the user explicitly requests it.
250+
*/
251+
static int has_splash_arg(void)
252+
{
253+
char line[LINE_SIZE], *tok, *saveptr;
254+
FILE *fp;
255+
256+
fp = fopen("/proc/cmdline", "r");
257+
if (!fp)
258+
return 0;
259+
260+
if (!fgets(line, sizeof(line), fp)) {
261+
fclose(fp);
262+
return 0;
263+
}
264+
fclose(fp);
265+
266+
for (tok = strtok_r(line, " \t\n", &saveptr); tok;
267+
tok = strtok_r(NULL, " \t\n", &saveptr)) {
268+
if (!strcmp(tok, "splash"))
269+
return 1;
270+
}
271+
272+
return 0;
273+
}
274+
275+
PLUGIN_INIT(__init)
276+
{
277+
if (!has_splash_arg())
278+
return;
279+
280+
plugin_register(&plugin);
281+
}
282+
283+
PLUGIN_EXIT(__exit)
284+
{
285+
plugin_unregister(&plugin);
286+
}
287+
288+
/**
289+
* Local Variables:
290+
* indent-tabs-mode: t
291+
* c-file-style: "linux"
292+
* End:
293+
*/

src/finit.c

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -370,8 +370,8 @@ static void fs_finalize(void)
370370
fs_mount("shm", "/dev/shm", "tmpfs", flags | MS_NODEV, "mode=1777");
371371
}
372372

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

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

385385
makedir("/dev/pts", 0755);
386-
fs_mount("devpts", "/dev/pts", "devpts", flags, opts);
386+
fs_mount("devpts", "/dev/pts", "devpts",
387+
flags | MS_REMOUNT, opts);
387388
}
388389

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

530531
fs_mount(fs[i].spec, fs[i].file, fs[i].type, 0, NULL);
531532
}
533+
534+
/* Early devpts mount for plugins that need PTY access (e.g. plymouth) */
535+
if (!fismnt("/dev/pts")) {
536+
makedir("/dev/pts", 0755);
537+
fs_mount("devpts", "/dev/pts", "devpts",
538+
MS_NOSUID | MS_NOEXEC, "ptmxmode=0666");
539+
}
532540
}
533541

534542

0 commit comments

Comments
 (0)