Compare commits

..
20 Commits
Author SHA1 Message Date
Joachim Wiberg e74dff96ba Update ChangeLog and bump version for v4.17 GA
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-04-28 10:09:16 +02:00
Joachim Wiberg 9ed4407b20 Follow-up to 96f944b: fix regression in status output at boot
status() returns a pointer to a single static buffer, so calling it twice
in the same cprintf() argument list — status(3) and status(rc) — causes
one to overwrite the other before the format string is rendered.  When
status(3) wins, the line shows [ ⋯  ] instead of [ OK ].  Fix by copying
status(rc) into a local buffer before calling status(3).

Also drop the delline() calls added to print() — that macro writes \033[2K
to buffered stdout while cprintf() writes unbuffered to stderr, so the
erase sequences can arrive out of order.  The \r\e[K already present in
the cprintf format strings makes them redundant anyway.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-04-28 10:02:21 +02:00
Joachim Wiberg eb913e8bf2 initramfs: fix Coverity defects
Check return value of remove() in delete_cb() and log failures via
dbg(), CID 909395

Replace stat() calls with open(O_DIRECTORY)+ fstat() for newroot and "/"
checks.  Eliminates the check-then-use race and lets O_DIRECTORY do the
isdir validation atomically, CID 909394

Drop the explicit close(0/1/2) before opening /dev/console.  dup2()
closes the old targets itself, so open() returns a fd > STDERR_FILENO
that can always be closed unconditionally, removing the conditional
guard, CID 909393

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-04-26 22:11:19 +02:00
Joachim Wiberg 17d04779fb Update ChangeLog and bump version for v4.17-rc1
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-04-26 21:54:49 +02:00
Joachim Wiberg ab661423ba Merge pull request #488 from Ollie-Gutierrez/ctty-support
service: add optional controlling TTY via tty: stanza
2026-04-23 19:41:02 +02:00
Ollie Gutierrez 8e77ea68b2 service: add optional controlling TTY via tty: stanza 2026-04-23 15:08:57 +00:00
Joachim Wiberg b0e672bc9a Merge pull request #487 from aanderse/patch-1
tmpfiles.d: update /var/tmp mode
2026-04-18 07:34:37 +02:00
Aaron Andersen ad76e15949 tmpfiles.d: update /var/tmp mode
`/var/tmp` mode should match `/tmp`
2026-04-18 09:17:10 +09:00
Joachim Wiberg b8735cc893 Merge pull request #486 from aanderse/patch-1 2026-04-14 04:57:00 +02:00
Aaron Andersen c058b3f9ce plugins/plymouth.c: fixup email address 2026-04-14 09:03:19 +09:00
Joachim Wiberg 8f6c61606a Merge pull request #485 from finit-project/console-output-fixes
Atomic print and re-print desc on status, flush before reboot
2026-04-08 17:11:57 +02:00
Joachim Wiberg 96f944b816 Atomic print and re-print desc on status, flush before reboot
Refactor print() to emit description + final status in a single call to
cprintf(), preventing kernel messages from splitting the two parts.

For two-phase print(-1,...) + print_result() sequences used by, e.g.,
run_interactive, save the last description and re-print it before the
[ OK ] / [FAIL] output so the status is never left stranded on a blank
line when command output or kernel messages have scrolled away the
original description.

Finally, add print_exit() which drains the console output buffer with
tcdrain(2) and resets ANSI SGR attributes + cursor visibility before the
kernel takes back the console on reboot/halt, preventing escape code
leakage into bootloader or early-kernel output.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-04-07 11:36:19 +02:00
Joachim Wiberg a81530754b Merge pull request #483 from aanderse/plymouth
Add Plymouth boot splash plugin
2026-03-20 14:35:53 +01:00
Aaron Andersen 1518eb9466 Add Plymouth boot splash plugin
Manage the plymouthd lifecycle across boot, switch_root, and shutdown.
Activated by the "splash" kernel command line argument.
2026-03-20 09:18:18 -04:00
Joachim Wiberg 50b9e46e19 Merge pull request #484 from finit-project/rdep-stop-cond
Clear condition before stopping rdeps on reload
2026-03-19 06:53:40 +01:00
Joachim Wiberg c01faef99b service: clear condition before stopping rdeps on reload
When a service without SIGHUP reload support (noreload) is touched and
'initctl reload' is called, service_update_rdeps() correctly identifies
its reverse dependencies but only marks them dirty.  It does not clear
the service's condition, so when service_step_all() runs:

 - rdeps supporting SIGHUP hit the sm_in_reload() guard and break early,
   left running while their dependency is being killed.
 - rdeps without SIGHUP support may receive SIGTERM too late, after the
   dependency has already died and broken their connection, causing them
   to exit from RUNNING state and have their restart counter incremented.

Fix by calling cond_clear() on the service's condition immediately in
service_update_rdeps(), before service_step_all() runs.  cond_clear()
calls cond_update() which calls service_step() inline on all affected
services, which see COND_OFF and transition to STOPPING_STATE — all
before SIGTERM is ever sent to the dependency itself.

This mirrors the pattern already used in api.c:do_reload() for direct
'initctl reload <svc>' calls.

Fixes: avahi/mdns stop causing mdns-alias restart counter increment

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-03-19 06:37:50 +01:00
Joachim Wiberg 4867b7268b Merge pull request #482 from vazub/plugfix
Use explicit plugin names to prevent subtle macro processing bugs
2026-03-16 07:31:34 +01:00
bazub 662293e194 Use explicit plugin names to prevent subtle macro processing bugs 2026-03-06 20:36:23 +00:00
Joachim Wiberg 4ce810cc1f Merge pull request #481 from vazub/pathfix
Remove redundant global path var and fix memory corruption
2026-03-04 15:05:36 +01:00
bazub 184c079c08 Remove redundant global path var and fix memory corruption 2026-03-03 20:31:33 +00:00
33 changed files with 617 additions and 60 deletions
+2 -1
View File
@@ -1,4 +1,4 @@
AC_INIT([Finit], [4.16], [https://github.com/finit-project/finit/issues],
AC_INIT([Finit], [4.17], [https://github.com/finit-project/finit/issues],
[finit], [https://http://finit-project.github.io/])
AC_CONFIG_AUX_DIR(aux)
AM_INIT_AUTOMAKE([1.11 foreign subdir-objects])
@@ -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
+37 -1
View File
@@ -3,6 +3,41 @@ Change Log
All relevant changes are documented in this file.
[4.17][] - 2026-04-28
---------------------
### Changes
- Add Plymouth boot splash plugin to manage the `plymouthd` lifecycle across boot,
`switch_root`, and shutdown. Activated by the `splash` kernel command line
argument. By Aaron Andersen
- Add `tty:` option for run/task/service stanzas to acquire a controlling
terminal. The device is opened as stdin, set as the session's controlling TTY
via `TIOCSCTTY`, and `TERM` is set automatically — `linux` for virtual terminals
and `vt102` for serial lines. By Ollie Gutierrez
- Improve console output atomicity at boot and shutdown: description and status
(`[ OK ]` / `[FAIL]`) are emitted in a single write so kernel messages cannot
split them. If output has been scrolled away, the description is re-printed
before the final status line. Console buffer is drained with `tcdrain(2)` and
ANSI attributes are reset before handing control back to the kernel at
shutdown/reboot
### Fixes
- Fix reverse-dependency condition handling on reload: when a service without
SIGHUP support is reloaded, its condition is now cleared immediately so
dependents transition to `STOPPING` before `SIGTERM` is sent to the parent,
preventing them from crashing with an incremented restart counter
- Fix `/var/tmp` mode in `tmpfiles.d/var.conf` from `0777` to `1777` (sticky bit)
to match `/tmp`, by Aaron Andersen
- Fix potential memory corruption in config parser: stale pointer captures of
`PATH` and `SHELL` at startup could dangle after environment modification;
`conf_reset_env()` now reads from the live environment instead, by Vasily Zubko
- Fix plugin name registration: replace `__FILE__` macro with explicit string
literals in all plugin structs and debug messages, preventing subtle build-
system–dependent macro expansion issues, by Vasily Zubko
[4.16][] - 2026-02-27
---------------------
@@ -1968,7 +2003,8 @@ Major bug fix release.
* Initial release
[UNRELEASED]: https://github.com/finit-project/finit/compare/4.15...HEAD
[UNRELEASED]: https://github.com/finit-project/finit/compare/4.17...HEAD
[4.17]: https://github.com/finit-project/finit/compare/4.16...4.17
[4.16]: https://github.com/finit-project/finit/compare/4.15...4.16
[4.15]: https://github.com/finit-project/finit/compare/4.14...4.15
[4.14]: https://github.com/finit-project/finit/compare/4.13...4.14
+1
View File
@@ -46,6 +46,7 @@ Other run/task/service options are:
* `cgroup.NAME[,opts]` or `cgroup:opts` -- see the [Cgroups](cgroups.md) section
* `env:[-]/path/to/env` -- see the [Service Environment](service-env.md) section
* `log:...` -- see [Redirecting Output](logging.md#redirecting-output)
* `tty:<dev>` -- see [Controlling TTY](tty.md#controlling-tty)
* `nowarn` -- see [Conditional Loading](services.md#conditional-loading)
* `notify:...` -- see [Service Synchronization](service-sync.md)
* `if:...` -- see [Conditional Execution](services.md#conditional-execution)
+28 -1
View File
@@ -83,4 +83,31 @@ board bringup and system debugging it can come in handy.
One can also use the `service` stanza to start a stand-alone shell:
service [12345] /bin/sh -l
service [12345] /bin/sh -l
Controlling TTY for Services
----------------------------
The `tty:<dev>` option gives a `run`, `task`, or `service` a controlling
terminal on the given device. The device is opened, set as the
controlling terminal for the session (after `setsid()`), and connected to
the process's stdin, stdout, and stderr. A default `TERM` environment
variable is set based on the device type: `vt102` for serial lines and
`linux` for virtual terminals.
`<dev>` may be a device node like `/dev/ttyS0`, or the special keyword
`@console` (see above). Note that `@console` expands only to the
first console, not all.
When `tty:` is combined with `log:`, stdout and stderr are redirected
to the log sink instead of the TTY, but stdin remains connected to the
TTY device.
> The `tty:<dev>` option is for `run`, `task`, and `service` stanzas only.
> The `tty` directive itself (for getty/login) has its own syntax, see
> above.
**Example:**
service [2345] tty:/dev/ttyS0 /usr/sbin/foo -- Foo on serial console
task [S] tty:@console my-setup-script -- Board bringup on console
+2 -1
View File
@@ -61,7 +61,8 @@ For more complex setups (LUKS, LVM, etc.):
```
# Unlock LUKS volume
run [S] name:cryptsetup /sbin/cryptsetup open /dev/sda2 cryptroot -- Unlocking encrypted root
# The tty:@console stanza is required so cryptsetup can prompt for a passphrase
run [S] name:cryptsetup tty:@console /sbin/cryptsetup open /dev/sda2 cryptroot -- Unlocking encrypted root
# Activate LVM
run [S] name:lvm /sbin/lvm vgchange -ay -- Activating LVM volumes
+31
View File
@@ -744,6 +744,37 @@ is
and the default
.Cm tag
identity is the basename of the service or run/task command.
.It Cm tty:\&<dev>
Give the
.Cm run , task ,
or
.Cm service
a controlling terminal on
.Cm <dev> .
The device is opened, set as the controlling terminal for the session
(after
.Fn setsid ) ,
and connected to stdin, stdout, and stderr.
A default
.Ev TERM
variable is set based on the device type:
.Cm vt102
for serial lines and
.Cm linux
for virtual terminals.
.Cm <dev>
may be a device node like
.Pa /dev/ttyS0 ,
or the special keyword
.Cm @console
(see TTYs and Consoles above).
When combined with
.Cm log: ,
stdout and stderr are redirected to the log sink instead, but stdin
remains connected to the TTY device.
.Bd -unfilled -offset indent
service [2345] tty:/dev/ttyS0 /usr/sbin/foo -- Foo on serial console
.Ed
.El
.Sh RESCUE MODE
Finit supports a rescue mode which is activated by the
+8
View File
@@ -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
@@ -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
+3 -3
View File
@@ -38,7 +38,7 @@
static void save(void *arg)
{
if (rescue) {
dbg("Skipping %s plugin in rescue mode.", __FILE__);
dbg("Skipping %s plugin in rescue mode.", "alsa-utils");
return;
}
@@ -51,7 +51,7 @@ static void save(void *arg)
static void restore(void *arg)
{
if (rescue) {
dbg("Skipping %s plugin in rescue mode.", __FILE__);
dbg("Skipping %s plugin in rescue mode.", "alsa-utils");
return;
}
@@ -62,7 +62,7 @@ static void restore(void *arg)
}
static plugin_t plugin = {
.name = __FILE__,
.name = "alsa-utils",
.hook[HOOK_BASEFS_UP] = { .cb = restore },
.hook[HOOK_SHUTDOWN] = { .cb = save }
};
+1 -1
View File
@@ -172,7 +172,7 @@ static void setup(void *arg)
}
static plugin_t plugin = {
.name = __FILE__,
.name = "bootmisc",
.hook[HOOK_MOUNT_POST] = { .cb = clean },
.hook[HOOK_BASEFS_UP] = { .cb = setup },
.depends = { "pidfile" },
+2 -2
View File
@@ -106,7 +106,7 @@ static void setup(void *arg)
char *cmd;
if (rescue) {
dbg("Skipping %s plugin in rescue mode.", __FILE__);
dbg("Skipping %s plugin in rescue mode.", "dbus");
return;
}
@@ -164,7 +164,7 @@ static void setup(void *arg)
}
static plugin_t plugin = {
.name = __FILE__,
.name = "dbus",
.hook[HOOK_SVC_PLUGIN] = { .cb = setup },
};
+1 -1
View File
@@ -79,7 +79,7 @@ static void hscript_shutdown(void *arg)
}
static plugin_t plugin = {
.name = __FILE__,
.name = "hook-scripts",
.hook[HOOK_BANNER] = { .cb = hscript_banner },
.hook[HOOK_ROOTFS_UP] = { .cb = hscript_rootfs_up },
.hook[HOOK_MOUNT_ERROR] = { .cb = hscript_mount_error },
+1 -1
View File
@@ -228,7 +228,7 @@ static void coldplug(void *arg)
}
static plugin_t plugin = {
.name = __FILE__,
.name = "modprobe",
.hook[HOOK_BASEFS_UP] = { .cb = coldplug },
.depends = { "bootmisc", }
};
+1 -1
View File
@@ -213,7 +213,7 @@ static void load(void *arg)
}
static plugin_t plugin = {
.name = __FILE__,
.name = "modules-load",
.hook[HOOK_SVC_PLUGIN] = { .cb = load },
};
+1 -1
View File
@@ -426,7 +426,7 @@ static void nl_enumerate(void *arg)
}
static plugin_t plugin = {
.name = __FILE__,
.name = "netlink",
.hook[HOOK_SVC_RECONF] = { .cb = nl_reconf },
.hook[HOOK_SVC_PLUGIN] = { .cb = nl_enumerate },
.io = {
+1 -1
View File
@@ -335,7 +335,7 @@ static void pidfile_init(void *arg)
* SIGSTP:ed (in state PAUSED) waiting for <net/iface/lo>.
*/
static plugin_t plugin = {
.name = __FILE__,
.name = "pidfile",
.hook[HOOK_BASEFS_UP] = { .cb = pidfile_init },
.hook[HOOK_SVC_RECONF] = { .cb = pidfile_reconf },
.depends = { "netlink" }, /* bootmisc depends on us */
+293
View File
@@ -0,0 +1,293 @@
/* Plymouth boot splash plugin for Finit
*
* Copyright (c) 2012-2026 Aaron Andersen <aaron@fosslib.net>
*
* 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:
*/
+2 -2
View File
@@ -43,7 +43,7 @@ static void setup(void *arg)
glob_t gl;
if (rescue) {
dbg("Skipping %s plugin in rescue mode.", __FILE__);
dbg("Skipping %s plugin in rescue mode.", "procps");
return;
}
@@ -69,7 +69,7 @@ static void setup(void *arg)
}
static plugin_t plugin = {
.name = __FILE__,
.name = "procps",
.hook[HOOK_BASEFS_UP] = {
.cb = setup
},
+1 -1
View File
@@ -51,7 +51,7 @@ static void setup(void *arg)
}
static plugin_t plugin = {
.name = __FILE__,
.name = "resolvconf",
.hook[HOOK_BASEFS_UP] = {
.cb = setup
},
+3 -3
View File
@@ -236,7 +236,7 @@ static void rtc_save(void *arg)
int fd, rc = 0;
if (rescue) {
dbg("Skipping %s plugin in rescue mode.", __FILE__);
dbg("Skipping %s plugin in rescue mode.", "rtc");
return;
}
@@ -266,7 +266,7 @@ static void rtc_restore(void *arg)
int fd, rc = 0;
if (rescue) {
dbg("Skipping %s plugin in rescue mode.", __FILE__);
dbg("Skipping %s plugin in rescue mode.", "rtc");
return;
}
@@ -321,7 +321,7 @@ static void update(uev_t *w, void *arg, int events)
static plugin_t plugin = {
.name = __FILE__,
.name = "rtc",
.hook[HOOK_BASEFS_UP] = {
.cb = rtc_restore
},
+1 -1
View File
@@ -180,7 +180,7 @@ static void sys_init(void *arg)
}
static plugin_t plugin = {
.name = __FILE__,
.name = "sys",
.hook[HOOK_BASEFS_UP] = { .cb = sys_init },
.depends = { "bootmisc", },
};
+1
View File
@@ -43,6 +43,7 @@
static void tty_watcher(void *arg, int fd, int events);
static plugin_t plugin = {
.name = "tty",
.io = {
.cb = tty_watcher,
.flags = PLUGIN_IO_READ,
+3 -3
View File
@@ -87,7 +87,7 @@ static void setup(void *arg)
int fd, err;
if (rescue) {
dbg("Skipping %s plugin in rescue mode.", __FILE__);
dbg("Skipping %s plugin in rescue mode.", "urandom");
return;
}
@@ -188,7 +188,7 @@ static void save(void *arg)
mode_t prev;
if (rescue) {
dbg("Skipping %s plugin in rescue mode.", __FILE__);
dbg("Skipping %s plugin in rescue mode.", "urandom");
return;
}
@@ -202,7 +202,7 @@ static void save(void *arg)
}
static plugin_t plugin = {
.name = __FILE__,
.name = "urandom",
.hook[HOOK_BASEFS_UP] = { .cb = setup },
.hook[HOOK_SHUTDOWN] = { .cb = save },
.depends = { "bootmisc", }
+1 -1
View File
@@ -104,7 +104,7 @@ static void usr_init(void *arg)
}
static plugin_t plugin = {
.name = __FILE__,
.name = "usr",
.hook[HOOK_BASEFS_UP] = { .cb = usr_init },
.depends = { "bootmisc", },
};
+2 -2
View File
@@ -39,7 +39,7 @@
static void setup(void *arg)
{
if (rescue) {
dbg("Skipping %s plugin in rescue mode.", __FILE__);
dbg("Skipping %s plugin in rescue mode.", "x11-common");
return;
}
@@ -48,7 +48,7 @@ static void setup(void *arg)
}
static plugin_t plugin = {
.name = __FILE__,
.name = "x11-common",
.hook[HOOK_SVC_PLUGIN] = { .cb = setup },
};
+2 -11
View File
@@ -121,9 +121,6 @@ static uev_t etcw;
static TAILQ_HEAD(, conf_change) conf_change_list = TAILQ_HEAD_INITIALIZER(conf_change_list);
static char *path;
static char *shell;
static int parse_conf(char *file, int is_rcsd);
static void drop_changes(void);
@@ -377,8 +374,6 @@ void conf_parse_cmdline(int argc, char *argv[])
fstab = strdup(ptr);
finit_conf = strdup(FINIT_CONF);
finit_rcsd = strdup(FINIT_RCSD);
path = getenv("PATH");
shell = getenv("SHELL");
for (int i = 1; i < argc; i++)
parse_arg(argv[i]);
@@ -404,13 +399,9 @@ void conf_reset_env(void)
free(node);
}
if (path)
setenv("PATH", path, 1);
else
if (!getenv("PATH"))
setenv("PATH", _PATH_STDPATH, 1);
if (shell)
setenv("SHELL", shell, 1);
else
if (!getenv("SHELL"))
setenv("SHELL", _PATH_BSHELL, 1);
setenv("LOGNAME", "root", 1);
setenv("USER", "root", 1);
+11 -3
View File
@@ -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;
@@ -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 */
@@ -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");
}
}
+67 -6
View File
@@ -50,6 +50,7 @@
static pstyle_t progress_onoff = PROGRESS_DEFAULT;
static pstyle_t progress_style = PROGRESS_DEFAULT;
static char last_desc[512]; /* saved description for re-print after interruption */
#ifndef HOSTNAME_PATH
#define HOSTNAME_PATH "/etc/hostname"
@@ -336,6 +337,8 @@ void printv(const char *fmt, va_list ap)
len = print_timestamp(buf, sizeof(buf));
vsnprintf(&buf[len], sizeof(buf) - len, fmt, ap);
strlcpy(last_desc, &buf[len], sizeof(last_desc)); /* save for re-print */
if (progress_style == PROGRESS_CLASSIC)
cprintf("\r%s ", pad(buf, sizeof(buf), ".", sizeof(buf)));
else
@@ -344,24 +347,70 @@ void printv(const char *fmt, va_list ap)
void print(int rc, const char *fmt, ...)
{
char buf[ttcols];
size_t len;
if (progress_style == PROGRESS_SILENT)
return;
buf[0] = 0;
len = print_timestamp(buf, sizeof(buf));
if (fmt) {
va_list ap;
va_start(ap, fmt);
printv(fmt, ap);
vsnprintf(&buf[len], sizeof(buf) - len, fmt, ap);
va_end(ap);
strlcpy(last_desc, &buf[len], sizeof(last_desc));
} else if (rc >= 0 && last_desc[0]) {
/*
* No new description, but we have one saved from an earlier
* print(-1, ...) call. Re-print it so the final status is not
* left stranded if command output or a kernel message scrolled
* away the original description line.
*/
strlcpy(&buf[len], last_desc, sizeof(buf) - len);
} else {
buf[0] = 0;
}
if (rc < 0)
if (rc < 0) {
/* Pending state: show description with spinner, no final status yet */
if (!buf[0])
return;
if (progress_style == PROGRESS_CLASSIC)
cprintf("\r%s ", pad(buf, sizeof(buf), ".", sizeof(buf)));
else
cprintf("\r\e[K%s%s", status(3), buf);
return;
}
if (progress_style == PROGRESS_CLASSIC)
cprintf("%s\n", status(rc));
else
cprintf("\r%s\n", status(rc));
/*
* Final status. Emit description + status in a single cprintf() so
* both reach the console in one write(), preventing kernel messages or
* command output from splitting the description from its [ OK ]/[FAIL].
*
* status() returns a pointer to a static buffer, so we must copy the
* result for rc before calling status(3) — the second call overwrites
* the same buffer, corrupting the first result.
*/
last_desc[0] = 0;
if (buf[0]) {
char st[64];
strlcpy(st, status(rc), sizeof(st));
if (progress_style == PROGRESS_CLASSIC)
cprintf("\r%s %s\n", pad(buf, sizeof(buf), ".", sizeof(buf)), st);
else
cprintf("\r\e[K%s%s\r%s\n", status(3), buf, st);
} else {
if (progress_style == PROGRESS_CLASSIC)
cprintf("%s\n", status(rc));
else
cprintf("\r%s\n", status(rc));
}
}
void print_desc(char *action, char *desc)
@@ -375,6 +424,18 @@ int print_result(int fail)
return fail;
}
/*
* Reset console state and drain the output buffer before kernel takeover.
* Called just before reboot()/halt() to prevent ANSI escape codes from
* leaking into bootloader or early-kernel output.
*/
void print_exit(void)
{
tcdrain(STDERR_FILENO);
dprint(STDERR_FILENO, "\e[0m\e[?25h", 10); /* reset SGR, show cursor */
tcdrain(STDERR_FILENO);
}
void set_hostname(char **hostname)
{
FILE *fp;
+1
View File
@@ -68,6 +68,7 @@ void printv (const char *fmt, va_list ap);
void print (int action, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
void print_desc (char *action, char *desc);
int print_result (int fail);
void print_exit (void);
void set_hostname (char **hostname);
void networking (int updown);
+22 -10
View File
@@ -73,7 +73,8 @@ static int delete_cb(const char *fpath, const struct stat *sb, int tflag, struct
if (sb->st_dev != delete_rootdev)
return 0;
remove(fpath);
if (remove(fpath))
dbg("Failed to remove %s: %s", fpath, strerror(errno));
return 0;
}
@@ -146,6 +147,7 @@ int switch_root(const char *newroot, const char *newinit)
struct stat newroot_st, oldroot_st;
char init_path[PATH_MAX];
int console_fd;
int fd;
dev_t rootdev;
int signo;
@@ -166,17 +168,31 @@ int switch_root(const char *newroot, const char *newinit)
}
/* Verify newroot exists and is a directory */
if (stat(newroot, &newroot_st) || !S_ISDIR(newroot_st.st_mode)) {
fd = open(newroot, O_RDONLY | O_DIRECTORY);
if (fd < 0) {
logit(LOG_ERR, "switch_root: %s is not a directory", newroot);
errno = ENOTDIR;
return -1;
}
if (fstat(fd, &newroot_st)) {
close(fd);
logit(LOG_ERR, "switch_root: cannot stat %s", newroot);
return -1;
}
close(fd);
/* Verify newroot is a mount point (different device than parent) */
if (stat("/", &oldroot_st)) {
fd = open("/", O_RDONLY | O_DIRECTORY);
if (fd < 0) {
logit(LOG_ERR, "switch_root: cannot open /");
return -1;
}
if (fstat(fd, &oldroot_st)) {
close(fd);
logit(LOG_ERR, "switch_root: cannot stat /");
return -1;
}
close(fd);
if (newroot_st.st_dev == oldroot_st.st_dev) {
logit(LOG_ERR, "switch_root: %s is not a mount point", newroot);
@@ -259,18 +275,14 @@ int switch_root(const char *newroot, const char *newinit)
return -1;
}
/* Reopen console */
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
/* Reopen console in new root. dup2() closes the old fds itself,
* so open() returns a fd > STDERR_FILENO that we can always close. */
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);
close(console_fd);
}
/* Reset signals to default */
+81 -1
View File
@@ -29,10 +29,12 @@
#include <sched.h> /* sched_yield() */
#include <string.h>
#include <sys/reboot.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <linux/vt.h>
#include <net/if.h>
#ifdef _LIBITE_LITE
# include <libite/lite.h>
@@ -387,6 +389,43 @@ static int lredirect(svc_t *svc)
return close(pipefd[1]);
}
/*
* Set up a controlling TTY for run/task/service.
* Opens the TTY device, dups to 0/1/2, resets termios to
* sane defaults, sets TERM env, and updates procname.
*/
static void svc_prepare_ctty(const char *tty, const char *procname, int log)
{
int fd, dummy;
fd = open(tty, O_RDWR);
if (fd < 0) {
logit(LOG_ERR, "Failed opening %s: %s", tty, strerror(errno));
_exit(1);
}
/* Acquire as controlling terminal for the session */
ioctl(fd, TIOCSCTTY, 0);
dup2(fd, STDIN_FILENO);
if (!log) {
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
}
/* Set default TERM */
if (ioctl(fd, VT_OPENQRY, &dummy) == -1)
setenv("TERM", "vt102", 1); /* likely a serial line */
else
setenv("TERM", "linux", 1);
/* Reset to sane defaults */
stty(fd, B0);
close(fd);
prctl(PR_SET_NAME, procname, 0, 0, 0);
}
/*
* Handle redirection of process output, if enabled
*/
@@ -946,6 +985,14 @@ static int service_start(svc_t *svc)
sig_unblock();
/*
* If the stanza declared tty:<dev>, set up a controlling
* terminal on that device now that we're a session leader.
* Must run after setsid() to acquire the controlling TTY.
*/
if (!svc_is_tty(svc) && svc_has_ctty(svc))
svc_prepare_ctty(svc->log.ctty, svc->cmd, !!svc->log.enabled);
if (svc_is_runtask(svc))
status = exec_runtask(args[0], &args[1]);
else if (svc_is_tty(svc))
@@ -1795,6 +1842,7 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
char ident[MAX_IDENT_LEN];
char *ifstmt = NULL;
char *notify = NULL;
char *ctty = NULL;
struct tty tty = { 0 };
char *dev = NULL;
int respawn = 0;
@@ -1911,6 +1959,8 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
env = arg;
else if (MATCH_CMD(cmd, "caps:", arg))
caps = arg;
else if (MATCH_CMD(cmd, "tty:", arg))
ctty = arg;
/* catch both cgroup: and cgroup. handled in parse_cgroup() */
else if (MATCH_CMD(cmd, "cgroup", arg))
cgroup = arg;
@@ -2160,6 +2210,22 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
parse_caps(svc, caps);
else
memset(svc->capabilities, 0, sizeof(svc->capabilities));
if (!svc_is_tty(svc) && ctty) {
char *dev = ctty;
/* NOTE: @console expands only to first console, not all */
if (tty_isatcon(ctty))
dev = tty_atcon();
dev = tty_canonicalize(dev);
if (dev)
strlcpy(svc->log.ctty, dev, sizeof(svc->log.ctty));
else
logit(LOG_WARNING, "%s: tty: %s not found, skipping ctty", svc_ident(svc, NULL, 0), ctty);
} else if (!svc_is_tty(svc)) {
memset(svc->log.ctty, 0, sizeof(svc->log.ctty));
}
if (file)
strlcpy(svc->file, file, sizeof(svc->file));
else
@@ -2418,7 +2484,21 @@ void service_update_rdeps(void)
if (!svc_is_noreload(svc))
continue; /* Yup, no need to stop start rdeps */
svc_mark_affected(mkcond(svc, cond, sizeof(cond)));
/*
* Clear the condition immediately, before service_step_all()
* runs. cond_clear() calls cond_update() which calls
* service_step() on all affected services right now. Those
* services see COND_OFF and get service_stop() called,
* transitioning to STOPPING_STATE before we ever send SIGTERM
* to this service. Without this, the condition is only cleared
* after the service dies, by which time reverse-dependencies
* may have already crashed due to the lost connection.
* See also: api.c do_reload() which does the same for direct
* 'initctl reload <svc>' calls.
*/
mkcond(svc, cond, sizeof(cond));
cond_clear(cond);
svc_mark_affected(cond);
}
}
+3
View File
@@ -424,14 +424,17 @@ void do_shutdown(shutop_t op)
}
print(0, "Rebooting ...");
print_exit();
reboot(RB_AUTOBOOT);
} else if (op == SHUT_OFF) {
print(0, "Powering down ...");
print_exit();
reboot(RB_POWER_OFF);
}
/* Also fallback if any of the other two fails */
print(0, "Halting ...");
print_exit();
reboot(RB_HALT_SYSTEM);
}
+2
View File
@@ -176,6 +176,7 @@ typedef struct svc {
char file[64];
char prio[20];
char ident[20];
char ctty[32];
} log;
/* Only for TTY type services */
@@ -291,6 +292,7 @@ static inline int svc_in_runlevel (svc_t *svc, int runlevel) { return svc && IS
static inline int svc_has_pidfile (svc_t *svc) { return svc_is_daemon(svc) && svc->pidfile[0] != 0 && svc->pidfile[0] != '!'; }
static inline int svc_has_pre (svc_t *svc) { return svc->pre_script[0]; }
static inline int svc_has_post (svc_t *svc) { return svc->post_script[0]; }
static inline int svc_has_ctty (svc_t *svc) { return svc && svc->log.ctty[0]; }
static inline int svc_has_ready (svc_t *svc) { return svc->ready_script[0];}
static inline int svc_has_cleanup (svc_t *svc) { return svc->cleanup_script[0]; }
+1 -1
View File
@@ -8,7 +8,7 @@ d /var/lib/alarm 0755 - - -
d /var/lib/misc 0755 - - -
d /var/lib/urandom 0755 - - -
d /var/spool 0755 - - -
d /var/tmp 0777 - - -
d /var/tmp 1777 - - -
# Used by pam_console(8)
d /run/console 1777 - - -