Compare commits

...
15 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
16 changed files with 575 additions and 24 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
+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:
*/
+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 */
+66
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
+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 - - -