Compare commits

...
54 Commits
Author SHA1 Message Date
Joachim Wiberg 702a606d26 Hide cursor at boot and shutdown
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-17 08:19:39 +01:00
Joachim Wiberg a3d9b6e9b1 initctl: fix remaining lingering artifacts in 'top' output
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-17 08:19:38 +01:00
Joachim Wiberg 390a0f48c1 initctl: minor, simplify code
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-17 08:19:38 +01:00
Joachim Wiberg f4a13687e8 initctl: resolve hierarchical cgroup limits
Cgroups v2 limits are hierarchical - a process is constrained by the
most restrictive limit in its ancestor chain, not just its immediate
cgroup.  This patch updates cg_conf() to walk up the hierarchy and
report effective limits by comparing values at each level.

This fixes incorrect "max" (unlimited) reporting in 'initctl --json
status', 'initctl cgroup', and 'initctl top' when child cgroups have
no explicit limits but parents do.

For memory.max and cpu.max: take minimum (most restrictive)
For memory.min: take maximum (most protection)

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-17 08:19:37 +01:00
Joachim Wiberg 43ca51c3b1 initctl: add cpu/mem limits as well to json status output
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-17 08:19:37 +01:00
Joachim Wiberg 864b71af14 Follow-up to d87d298, prevent "cursor jumps"
Comment-out code that makes the cursor "jump" around at boot before
displaying: Please press Enter to activate this console.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-17 08:19:36 +01:00
Joachim Wiberg 44a928e5e6 Follow-up to e635ea8, adjust libite version dependency
libite v2.6.2 is not yet in Buildroot, so let's relax the dependency a
bit.  Load bearing functionality was in v2.6.0, any fixes on top is a
nice-to-have only.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-16 10:19:29 +01:00
Joachim Wiberg f513348963 doc: update ChangeLog for upcoming v4.15
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:38:41 +01:00
Joachim Wiberg 820b9a77c1 doc: update cgroup configuration section
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:37:09 +01:00
Joachim Wiberg 85baafe99c doc: update links to new project home and add release badge
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:37:08 +01:00
Joachim Wiberg e635ea882a Bump required libite and libuev versions
We now require readsnf() introduced in libite 2.6.0, with bug fixes
this effectively means v2.6.2.

The libuev bump is for 64-bit time_t, with bug fix => v2.4.1

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:37:08 +01:00
Joachim Wiberg f908c8d43a Fix possible out-of-bounds write, found by Coverity Scan
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:37:07 +01:00
Joachim Wiberg 308f75b209 Silence false positive from Coverity Scan
51static int is_dir_empty(const char *path)
    52{
	 1. var_decl: Declaring variable namelist without initializer.
    53        struct dirent **namelist;
    54        int num;
    55

   CID 898746: (#1 of 1): Uninitialized pointer read (UNINIT)
   2. uninit_use_in_call: Using uninitialized value namelist when calling scandir.
    56        num = scandir(path, &namelist, NULL, NULL);
    57        if (num < 0)
    58                return 0;

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:37:07 +01:00
Joachim Wiberg d87d298c85 getty: fix terminal scrollback issues after login on console
Fixes an issue where the mouse scroll wheel and Shift+PgUp/PgDn
sometimes would not work properly after login.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:37:06 +01:00
Joachim Wiberg ae7931f68e plugins: follow-up to 13a8f56, fix missing else branch
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:37:06 +01:00
Joachim Wiberg 8422d2715c initctl: fix flickering in 'top' and handle smaller screens
Drop clear screen to fix flickering in 'initctl top' output.  Also, make
sure to not garble the display if the the terminal is too small.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:04:34 +01:00
Joachim Wiberg 2eefaa5db6 initctl: fix file descriptor leak causing 'initctl top' to crash
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:04:33 +01:00
Joachim Wiberg fb0ad18d3a initctl: add CPU throttled information alongside memory usage
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:04:33 +01:00
Joachim Wiberg 2681444b07 system: use reasonable cgroup names from hotplug services
Instead of /system/10-hotplug/ we now place udevd, mdevd, and others, in
more aptly named groups using the new 'name:' syntax.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:04:32 +01:00
Joachim Wiberg dd36116a97 Add housekeeping functions to clean up unused cgroups
This should not be needed, but for some reason we don't get events when
early processes exit, so we end up with lingering cgroups.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:04:32 +01:00
Joachim Wiberg e756d6f8ac Allow forking sysv/services like podman to move between cgroups
The container monitor that podman forks off when starting a container
instance creates subgroups in the cgroup v2 hieararchy that we want to
reuse.  This patch adds cgroup_move_svc() which we call from the pidfile
plugins to relocate the conmon process.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:04:32 +01:00
Joachim Wiberg 567c695954 Start services with clone3() and place in cgroup directly
This commit activates the use of clone3() for service_fork(), to allow
Linux to create the new process directly in the correct cgroup instead
of later moving it there -- much cheaper and less error prone.

To facilitate this a few new helper cgroup functions have been added and
two new configuration directives introduced: delegate and name:leafname.
The delegate option is for running, e.g., container runtimes that want
to create their own cgroup v2 structur, and the name:leafname allows a
user to change the name of the subgroup under user/system/init.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:04:31 +01:00
Joachim Wiberg 4877aecc1e Add support for clone3() to replace fork()
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 09:32:03 +01:00
Joachim Wiberg 65644f5956 Follow-up to d9a0c2d, dead code
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-14 10:04:01 +01:00
Joachim Wiberg a02760f499 Minor, constify
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-14 10:00:39 +01:00
Joachim Wiberg 7aab3bb421 mkdocs: update project URLs and name
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-08 09:16:26 +01:00
Joachim Wiberg b5beb2b8d4 Minor, update URLs and clean up a bit
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-07 09:53:56 +01:00
Joachim Wiberg 09b4aee5bc Update ChangeLog and bump version for v4.15-rc1
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-07 09:38:14 +01:00
Joachim Wiberg d9a0c2dce9 Switch from PTY to pipe for service log redirection
The PTY approach caused isatty() to return true for services using
the log directive, triggering programs like fprintd (using glib) to
emit ANSI escape codes and other TTY-specific formatting in syslog.

Using a standard pipe ensures isatty() correctly returns false, so
programs produce plain text output suitable for logging.

For services that require line-buffered output, users can wrap the
command with `stdbuf -oL` as documented in doc/config/logging.md.

Fixes #455

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-07 09:38:13 +01:00
Joachim Wiberg 178301baf2 Change default reboot to SOC reset from WDT reset
Fixes #460

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-07 09:38:13 +01:00
Joachim Wiberg 4e18affa9b Allow skipping bootstrap wait with Ctrl-C
When setting up a new system with Finit it is very common to make small
logical mistakes that cause "hangs" at boot.  This is when Finit waits
for 180 sec. for run/tasks to complete before moving to the configured
runlevel.  This patch adds console input monitoring during bootstrap
wait that allows users to press Ctrl-C to skip waiting and proceed to
the configured runlevel.

When Ctrl-C is detected, all incomplete run/task/services are logged
to syslog for later troubleshooting after login.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-07 09:38:12 +01:00
Joachim Wiberg 21f40fb95e Allow more initctl commands in runlevels S/0/6
Relax the constraints introduced in a39ee0b, for issue #342, a bit on
when start/stop/restart/reload service can be called.  Also, allow
'initctl reload', but ignore it when the system is in runlevels S/0/6.

This makes it possible to start manual:yes type services at botostrap,
for example, which has been a common feature request.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-07 09:38:12 +01:00
Joachim Wiberg 24f0cee49a Ensure mount/unmount skips 'noauto' entries
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-07 09:38:11 +01:00
Joachim Wiberg fee38d4359 Merge pull request #458 from aanderse/master
Add support for Linux capabilities
2025-12-05 12:53:04 +01:00
Joachim Wiberg 0c85ea844a Merge pull request #459 from rical/add-mem-to-initctl-json
initctl: add memory field to JSON status output
2025-12-01 22:42:50 +01:00
Richard Alpe c3839edc40 initctl: add memory field to JSON status output
Add missing memory information to JSON output in json_status_one().
The memory field shows cgroup memory usage in bytes when cgroup
support is available and the service has a valid PID, matching
the behavior of the text status output.

Signed-off-by: Richard Alpe <richard@bit42.se>
2025-12-01 14:51:27 +01:00
Aaron Andersen be2a0ec3e7 Add support for Linux capabilities
Implement Linux capability support for services, allowing them to run
with minimal required privileges instead of running as root. This uses
the modern IAB (Inheritable, Ambient, Bounding) API from libcap.
2025-11-30 11:36:53 -05:00
Joachim Wiberg c23952cd9e Merge pull request #453 from liuming50/fix-rtc-restore-issues
plugins:rtc.c: drop some invalid condition checks

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-10-14 20:33:25 +02:00
Ming Liu 13a8f56b46 plugins:rtc.c: drop some invalid condition checks
There are some invalid condition checks in rtc.c, when rtc_file is
missing, it should not consider that as a error and return, otherwise,
it can lead to the case that time_set wont be called at all.

With this fix, when both RTC and file restore fail, it will fall back
to call time_set(NULL) and restore time from rtc_timestamp, this
ensures the OS has a valid time at the very first boot.

Signed-off-by: Ming Liu <liu.ming50@gmail.com>
2025-10-14 15:38:56 +02:00
Joachim Wiberg 927acdbd7e Increase MAX_ID_LEN to support longer service identifiers
Allow service IDs up to 64 characters to support SHA-256 hashes,
UUIDs, and other long unique identifiers. This increases memory
usage by ~98 bytes per service instance, which is negligible for
typical deployments.

The IDENT column width in initctl output adapts dynamically based
on actual ID lengths in use, so short IDs remain unaffected.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-10-14 11:49:57 +02:00
Joachim Wiberg 6f6267f3b3 When collecting a script or unregistering a service, cancel timeouts
Both service and sysv stanzas support a cleanup:script (since v4.10), with an
optional timeout.  These timers must be stopped when collecting a PID for an
svc_t, or when unregistering an svc_t, otherwise the timer callback will be
continuously called, reporting "spurious problem", and cause 100% CPU usage.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-08-29 16:49:34 +02:00
Joachim Wiberg dec89d4054 Minor, drop __func__ from dbg() statements
The dbg() macro already includes __func__ as the first text string in
the format string.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-08-29 16:48:38 +02:00
Joachim Wiberg 193e114596 Enable libsystemd replacement lib for tests also in distcheck stage
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-08-29 16:02:42 +02:00
Joachim Wiberg 1be67c434e Update ChangeLog and bump version for v4.14 release
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-08-29 15:07:45 +02:00
Joachim Wiberg 4f8dab75b2 Update test helper and all workflows to enble libsystemd build
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-08-29 14:43:25 +02:00
Joachim Wiberg 37d063fd3b Build --without-libsystemd by default
Many packages fail to detect that this replacement libsystemd does not
support the logging API (yet), so let's disable it by default.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-08-29 13:12:43 +02:00
Joachim Wiberg 97be3da126 Minor doc styling fixes
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-08-29 12:54:06 +02:00
Joachim Wiberg e7f97f8264 Fix state machine ordering issue
This patch fixes an ordering issue where the configuration generation is
stepped before all old/disabled services had been stopped.  Finit should
ensure disabled services are stopped before entering the next generation.

If the genation is stepped first, all conditions are put in "flux" state,
so, e.g, a podman container running as a sysv service may lock-up until
it is killed by Finit.  Found in the Infix OS project.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-08-29 12:47:43 +02:00
Joachim Wiberg 6415aeebac Ensure sysv 'stop' command has same env. as 'start'
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-08-29 10:17:19 +02:00
Mattias Barthel e69200ea43 Add exit status/signal info when a process dies
Signed-off-by: Mattias Barthel <mattiasbarthel@gmail.com>
2025-08-29 10:12:58 +02:00
Joachim Wiberg 26d5018224 Allow kill delay up to five minutes for debug
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-08-29 10:02:36 +02:00
Joachim Wiberg 1b7699ca93 Expand norespawn grasp to service_retry()
The norespawn hack pause Finit while a developer debugs the system.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-08-29 10:00:25 +02:00
Joachim Wiberg 8eab099f09 Minor, encase script + args in '' for debug help
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-08-29 09:58:10 +02:00
Joachim Wiberg c7c5426586 Fix #449: alsa plugin ftbfs
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-07-22 15:46:46 +02:00
50 changed files with 1866 additions and 293 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
finit is a small project, as such we have no possibility to support older versions.
The only supported version is the latest released on GitHub:
<https://github.com/troglobit/finit/releases>
<https://github.com/finit-project/finit/releases>
## Reporting a Vulnerability
+10 -2
View File
@@ -34,7 +34,15 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get -y update
sudo apt-get -y install pkg-config tree jq libuev-dev libite-dev
sudo apt-get -y install pkg-config tree jq libcap-dev
sudo apt-get -y install pkg-config
wget https://github.com/troglobit/libuev/releases/download/v2.4.1/libuev-2.4.1.tar.xz
wget https://github.com/troglobit/libite/releases/download/v2.6.2/libite-2.6.2.tar.gz
tar xf libuev-2.4.1.tar.xz
tar xf libite-2.6.2.tar.gz
(cd libuev-2.4.1 && ./configure --prefix=/usr && make && sudo make install-strip)
(cd libite-2.6.2 && ./configure --prefix=/usr && make && sudo make install-strip)
sudo ldconfig
- uses: actions/checkout@v4
- name: Static Finit
run: |
@@ -45,7 +53,7 @@ jobs:
run: |
./configure --prefix=/usr --exec-prefix= --sysconfdir=/etc --localstatedir=/var \
--enable-x11-common-plugin --enable-testserv-plugin --with-watchdog \
--with-keventd \
--with-keventd --with-libsystemd \
CFLAGS="-fsanitize=address -ggdb"
make -j3 clean
make -j3 V=1
+9 -8
View File
@@ -55,18 +55,19 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get -y update
sudo apt-get -y install pkg-config
wget https://github.com/troglobit/libuev/releases/download/v2.4.0/libuev-2.4.0.tar.xz
wget https://github.com/troglobit/libite/releases/download/v2.5.2/libite-2.5.2.tar.gz
tar xf libuev-2.4.0.tar.xz
tar xf libite-2.5.2.tar.gz
(cd libuev-2.4.0 && ./configure --prefix=/usr && make && sudo make install-strip)
(cd libite-2.5.2 && ./configure --prefix=/usr && make && sudo make install-strip)
sudo apt-get -y install pkg-config libcap-dev
wget https://github.com/troglobit/libuev/releases/download/v2.4.1/libuev-2.4.1.tar.xz
wget https://github.com/troglobit/libite/releases/download/v2.6.2/libite-2.6.2.tar.gz
tar xf libuev-2.4.1.tar.xz
tar xf libite-2.6.2.tar.gz
(cd libuev-2.4.1 && ./configure --prefix=/usr && make && sudo make install-strip)
(cd libite-2.6.2 && ./configure --prefix=/usr && make && sudo make install-strip)
sudo ldconfig
- name: Configure
run: |
./autogen.sh
./configure --prefix= --enable-x11-common-plugin --with-watchdog --with-keventd
./configure --prefix= --enable-x11-common-plugin --with-watchdog \
--with-keventd --with-libsystemd
- name: Build
run: |
export PATH=`pwd`/coverity/bin:$PATH
+11 -4
View File
@@ -20,7 +20,13 @@ jobs:
- name: Installing dependencies ...
run: |
sudo apt-get -y update
sudo apt-get -y install pkg-config jq libuev-dev libite-dev
sudo apt-get -y install pkg-config jq libcap-dev
wget https://github.com/troglobit/libuev/releases/download/v2.4.1/libuev-2.4.1.tar.xz
wget https://github.com/troglobit/libite/releases/download/v2.6.2/libite-2.6.2.tar.gz
tar xf libuev-2.4.1.tar.xz
tar xf libite-2.6.2.tar.gz
(cd libuev-2.4.1 && ./configure --prefix=/usr && make && sudo make install-strip)
(cd libite-2.6.2 && ./configure --prefix=/usr && make && sudo make install-strip)
- name: Setting release variables ...
id: build
run: |
@@ -39,9 +45,10 @@ jobs:
- name: Creating Makefiles ...
run: |
./autogen.sh
./configure --prefix=/usr --exec-prefix= --sysconfdir=/etc --localstatedir=/var \
--enable-x11-common-plugin --enable-testserv-plugin --with-watchdog \
--with-keventd
./configure --prefix=/usr --exec-prefix= --sysconfdir=/etc \
--localstatedir=/var --enable-x11-common-plugin \
--enable-testserv-plugin --with-watchdog \
--with-keventd --with-libsystemd
- name: Enable unprivileged userns (unshare)
run: |
sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0
+7 -1
View File
@@ -34,7 +34,13 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get -y update
sudo apt-get -y install pkg-config jq libuev-dev libite-dev
sudo apt-get -y install pkg-config jq libcap-dev
wget https://github.com/troglobit/libuev/releases/download/v2.4.1/libuev-2.4.1.tar.xz
wget https://github.com/troglobit/libite/releases/download/v2.6.2/libite-2.6.2.tar.gz
tar xf libuev-2.4.1.tar.xz
tar xf libite-2.6.2.tar.gz
(cd libuev-2.4.1 && ./configure --prefix=/usr && make && sudo make install-strip)
(cd libite-2.6.2 && ./configure --prefix=/usr && make && sudo make install-strip)
- uses: actions/checkout@v4
- name: Base run
run: |
+1 -1
View File
@@ -43,4 +43,4 @@ release: distcheck
DISTCHECK_CONFIGURE_FLAGS = --prefix=/usr --sysconfdir=/etc --localstatedir=/var \
--enable-testserv-plugin --enable-x11-common-plugin \
--with-watchdog --with-keventd --with-fstab=/etc/fstab \
--with-bash-completion-dir=no
--with-libsystemd --with-bash-completion-dir=no
+12 -9
View File
@@ -1,4 +1,4 @@
[![License Badge][]][License] [![GitHub Status][]][GitHub] [![Coverity Status][]][Coverity Scan]
[![License Badge][]][License] [![Release Badge][]][Release] [![GitHub Status][]][GitHub] [![Coverity Status][]][Coverity Scan]
<img align="right" src="doc/img/finit3.png" alt="Finit: Fast Init">
Finit is an alternative to [SysV init](https://en.wikipedia.org/wiki/Init)
@@ -11,7 +11,7 @@ on small and embedded Linux systems, although fully usable on server and
> — [Claudio Matsuoka][]
For detailed information, explore our extensive documentation
:books: **<https://troglobit.github.io/finit/>**
:books: **<http://finit-project.github.io/finit/>**
<div align="center">
<img src="doc/img/alpine-screenshot2.png" alt="Alpine screenshot"><br/>
@@ -20,10 +20,10 @@ For detailed information, explore our extensive documentation
For working examples, see the :rocket: [contrib/](contrib/) section or these
tutorials:
* :hammer_and_wrench: [Buildroot embedded Linux](https://troglobit.com/post/2022-12-26-buildroot-demo-of-fastinit-finit/),
* :package: [Debian GNU/Linux](contrib/debian/),
* :mountain: [Alpine Linux](contrib/alpine/), and
* :milky_way: [Void Linux](contrib/void/)
* :hammer_and_wrench: [Buildroot embedded Linux][br2demo],
* :package: [Debian GNU/Linux](contrib/debian/),
* :mountain: [Alpine Linux](contrib/alpine/), and
* :milky_way: [Void Linux](contrib/void/)
> [!NOTE]
> Support for various Linux distributions does not mean Finit installs
@@ -31,17 +31,20 @@ tutorials:
> standard installations, tested on amd64 (x86_64) systems. Custom
> setups, e.g., for embedded systems, can be found in the following
> [Buildroot][] based examples: [myLinux][], [Infix][], or the plain
> [br2-finit-demo](https://github.com/troglobit/br2-finit-demo).
> [br2-finit-demo](https://github.com/finit-project/br2-finit-demo).
[finix]: https://github.com/aanderse/finix
[Buildroot]: https://buildroot.org
[Infix]: https://kernelkit.github.io
[myLinux]: https://github.com/troglobit/myLinux/
[br2demo]: https://troglobit.com/post/2022-12-26-buildroot-demo-of-fastinit-finit/
[EeePC fastinit]: https://web.archive.org/web/20071208212450/http://wiki.eeeuser.com/boot_process:the_boot_process
[Claudio Matsuoka]: https://github.com/cmatsuoka
[License]: https://en.wikipedia.org/wiki/MIT_License
[License Badge]: https://img.shields.io/badge/License-MIT-teal.svg
[GitHub]: https://github.com/troglobit/finit/actions/workflows/build.yml/
[GitHub Status]: https://github.com/troglobit/finit/actions/workflows/build.yml/badge.svg
[Release]: https://github.com/finit-project/finit/releases
[Release Badge]: https://img.shields.io/github/v/release/finit-project/finit
[GitHub]: https://github.com/finit-project/finit/actions/workflows/build.yml/
[GitHub Status]: https://github.com/finit-project/finit/actions/workflows/build.yml/badge.svg
[Coverity Scan]: https://scan.coverity.com/projects/3545
[Coverity Status]: https://scan.coverity.com/projects/3545/badge.svg
+20 -5
View File
@@ -1,5 +1,5 @@
AC_INIT([Finit], [4.13], [https://github.com/troglobit/finit/issues],
[finit], [https://troglobit.com/projects/finit/])
AC_INIT([Finit], [4.15-rc1], [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])
AM_SILENT_RULES([yes])
@@ -48,8 +48,8 @@ AC_TYPE_UINT32_T
PKG_PROG_PKG_CONFIG
# Check for required libraries
PKG_CHECK_MODULES([uev], [libuev >= 2.2.0])
PKG_CHECK_MODULES([lite], [libite >= 2.2.0])
PKG_CHECK_MODULES([uev], [libuev >= 2.4.1])
PKG_CHECK_MODULES([lite], [libite >= 2.6.1])
# Check for configured Finit features
AC_ARG_ENABLE(auto_reload,
@@ -72,6 +72,10 @@ AC_ARG_ENABLE(cgroup,
AS_HELP_STRING([--disable-cgroup], [Disable cgroup v2 support, default: autodetect from /sys/fs/cgroup]),,[
enable_cgroup=yes])
AC_ARG_ENABLE(libcap,
AS_HELP_STRING([--disable-libcap], [Disable Linux capabilities support]),,[
enable_libcap=yes])
AC_ARG_ENABLE(redirect,
AS_HELP_STRING([--disable-redirect], [Disable redirection of service output to /dev/null]),,[
enable_redirect=yes])
@@ -171,7 +175,7 @@ AC_ARG_WITH(watchdog,
AC_ARG_WITH(libsystemd,
AS_HELP_STRING([--with-libsystemd], [Build replacement libsystemd library, default: yes]),
[with_libsystemd=$withval], [with_libsystemd=yes])
[with_libsystemd=$withval], [with_libsystemd=no])
AC_ARG_WITH(hook-scripts-path,
AS_HELP_STRING([--with-hook-scripts-path=DIR], [Base directory for hook scripts, default $libexecdir/finit/hook]),
@@ -204,6 +208,16 @@ AS_IF([test "x$enable_kernel_logging" = "xyes"], [
AS_IF([test "x$enable_cgroup" = "xyes"], [
AC_DEFINE(CGROUP2_ENABLED, 1, [Autodetect cgroup v2 support from /sys/fs/cgroup])])
AS_IF([test "x$enable_libcap" = "xyes"], [
AC_CHECK_LIB([cap], [cap_from_text], [
AC_DEFINE(HAVE_LIBCAP, 1, [Have libcap for Linux capabilities support])
LIBS="$LIBS -lcap"
], [
AC_MSG_WARN([libcap not found, capabilities support disabled])
enable_libcap=no
])
])
AS_IF([test "x$enable_fastboot" = "xyes"], [
AC_DEFINE(FAST_BOOT, 1, [Skip fsck check on filesystems listed in /etc/fstab])])
@@ -419,6 +433,7 @@ Optional features:
Built-in logrotate....: $enable_logrotate
Replacement libsystemd: $with_libsystemd
Use cgroup v2.........: $enable_cgroup
Use libcap............: $enable_libcap
Parse kernel cmdline..: $enable_kernel_cmdline
Keep kernel logging...: $enable_kernel_logging
Skip fsck check.......: $enable_fastboot
+55 -1
View File
@@ -3,6 +3,58 @@ Change Log
All relevant changes are documented in this file.
[4.15][UNRELEASED]
---------------------
### Changes
- Change default reboot to SOC reset from WDT reset, issue #460
- Bump required versions of libite (v2.6.2) and libuev (v2.4.1)
- Allow longer service identifiers (`:id`), up to 64 characters
- Allow skipping bootstrap wait (180 sec) with Ctrl-C
- Unlock `initctl` commands start/stop/restart/reload service during bootstrap,
shutdown, and reboot. Also, allow `initctl reload` in bootstrap. This makes
it possible to start manual:yes type services at botostrap, for example, which
has been a common feature request
- Add support for `clone3()` syscall with atomic cgroup assignment, from Linux
kernel 5.3+. Eliminates race conditions where forked processes (e.g., logger
processes) ended up in wrong cgroups. Falls back to `fork()` on older kernels
- Add support for cgroup delegation, useful for, e.g., container runtimes
creating sub-groups for the container instance's processes
- Add cgroup leaf name override: `cgroup.system,name:udevd` allows using
descriptive names like `/system/udevd/` instead of filename derived names
like `/system/10-hotplug/`. Syntax supports combining with other options:
`cgroup.system,name:foo,delegate,cpu.max:10000`
### Fixes
- Fix #453: RTC plugin fixes by Ming Liu, Atlas Copco
- Fix #455: ANSI escape sequences logged by tools using glib logging,
caused by use of PTY instead of pipe for stdout/stderr redirection
- getty: fix terminal scrollback issues after login on console TTY. Mouse
scroll wheel and Shift+PgUp/PgDn sometimes would not work properly
- Fix flickering and artifacts in `initctl top`
- Fix file descriptor leak in `initctl top`
- Fix incorrect "max" limit reporting in `initctl [top|cgroup|--json]` output
for child cgroups. Resolves effective limits by walking the cgroup hierarchy
- Ensure mount/unmount skips `noauto` entries
[4.14][] - 2025-08-29
---------------------
Minor bug fix release.
### Changes
- Add exit status/signal info when a process dies, by Mattias Berthel
- Extend `service kill:SEC` to support up to 300 sec.
- Extend `norespawn` debug to cover `service_retry()`
- Build *without* libsystemd replacement by default
### Fixes
- Fix #449: ALSA plugin fail-to-build-from-source
- Fix `sysv` stop command process environment, should be same as start
- Fix state machine ordering issue: enter new config generation *after*
services disabled in previous generation have been stopped
[4.13][] - 2025-07-10
---------------------
@@ -1833,7 +1885,9 @@ Major bug fix release.
* Initial release
[UNRELEASED]: https://github.com/troglobit/finit/compare/4.12...HEAD
[UNRELEASED]: https://github.com/finit-project/finit/compare/4.14...HEAD
[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
[4.13]: https://github.com/troglobit/finit/compare/4.12...4.13
[4.12]: https://github.com/troglobit/finit/compare/4.11...4.12
[4.11]: https://github.com/troglobit/finit/compare/4.10...4.11
+1 -2
View File
@@ -186,7 +186,6 @@ it only for debugging start up issues when Finit crashes.
> None of these options should be enabled on production systems since
> they can potentially give a user root access.
[1]: ftp://troglobit.com/finit/finit-4.3.tar.gz
[1]: https://github.com/finit-project/finit/releases/download/4.3/finit-4.3.tar.gz
[libuEv]: https://github.com/troglobit/libuev
[libite]: https://github.com/troglobit/libite
+176
View File
@@ -0,0 +1,176 @@
Linux Capabilities
==================
Finit supports Linux capabilities, allowing services to run with minimal
required privileges instead of running as root. This significantly improves
system security by following the principle of least privilege.
## Overview
Linux capabilities divide the traditional root privileges into distinct units
that can be independently granted to processes. For example, a web server only
needs the capability to bind to privileged ports (< 1024), not full root access.
Finit uses the modern IAB (Inheritable, Ambient, Bounding) API from libcap,
which is the same approach used by other modern service managers like dinit.
## Basic Usage
Capabilities are specified using the `caps:` directive in service configuration:
```conf
service [2345] name:nginx \
@www-data:www-data \
caps:^cap_net_bind_service \
/usr/sbin/nginx -g 'daemon off;' \
-- Web server
```
This example allows nginx to bind to privileged ports (like 80 and 443) while
running as the unprivileged `www-data` user.
## IAB Format
The capability string uses the IAB (Inheritable, Ambient, Bounding) format
with the following prefixes:
- `^` **Ambient** (and Inheritable) - **Recommended for most use cases**
- Capabilities survive across `exec()` calls
- Automatically raised to effective after exec
- Example: `^cap_net_bind_service`
- `%` **Inheritable** only
- Requires the executed binary to have matching file capabilities
- Less common, more complex setup
- Example: `%cap_net_admin`
- `!` **Bounding** - Block capability from bounding set
- Prevents the service from ever acquiring this capability
- Useful for security hardening
- Example: `!cap_sys_admin`
Multiple capabilities can be specified as a comma-separated list:
```conf
caps:^cap_net_raw,^cap_net_admin,^cap_net_bind_service
```
## Common Use Cases
### Web Server (Privileged Ports)
Allow a web server to bind to ports 80 and 443 without running as root:
```conf
service [2345] name:webserver \
@www-data:www-data \
caps:^cap_net_bind_service \
/usr/sbin/nginx -g 'daemon off;'
```
### Network Monitoring (Raw Sockets)
Allow packet capture without root privileges:
```conf
service [2345] name:tcpdump \
@tcpdump \
caps:^cap_net_raw,^cap_net_admin \
/usr/sbin/tcpdump -i eth0 -w /var/log/capture.pcap
```
### NTP Daemon (System Time)
Allow time synchronization without full root:
```conf
service [2345] name:ntpd \
@ntp \
caps:^cap_sys_time,^cap_sys_nice \
/usr/sbin/ntpd -n
```
## Available Capabilities
Common capabilities include (see `man 7 capabilities` for the complete list):
- `cap_chown` - Make arbitrary changes to file UIDs and GIDs
- `cap_dac_override` - Bypass file read, write, and execute permission checks
- `cap_dac_read_search` - Bypass file read permission checks
- `cap_fowner` - Bypass permission checks on operations that normally require filesystem UID
- `cap_kill` - Bypass permission checks for sending signals
- `cap_net_admin` - Perform various network-related operations
- `cap_net_bind_service` - Bind to privileged ports (< 1024)
- `cap_net_raw` - Use RAW and PACKET sockets
- `cap_setgid` - Make arbitrary manipulations of process GIDs
- `cap_setuid` - Make arbitrary manipulations of process UIDs
- `cap_sys_admin` - Perform system administration operations (very powerful!)
- `cap_sys_module` - Load and unload kernel modules
- `cap_sys_nice` - Raise process nice value and change scheduling
- `cap_sys_time` - Set system clock
## Security Best Practices
1. **Use the minimum required capabilities**
- Only grant what the service actually needs
- Don't grant `cap_sys_admin` unless absolutely necessary
2. **Always specify a user**
- Always use `@user` to drop to a non-root user
- Capabilities work best when combined with user separation
3. **Use ambient capabilities (`^`)**
- The `^` prefix ensures capabilities survive exec()
- Simpler than setting file capabilities on binaries
4. **Block dangerous capabilities**
- Use `!` to explicitly block capabilities you don't want
- Example: `!cap_sys_admin,!cap_sys_module`
5. **Test with `getpcaps`**
- After starting a service, verify its capabilities:
```bash
getpcaps $(pidof nginx)
```
- Should show only the capabilities you granted
## Verification
After configuring a service with capabilities, verify it works correctly:
```bash
# Start the service
initctl start webserver
# Check the process capabilities
getpcaps $(pidof nginx)
# Should show something like:
# 12345: cap_net_bind_service=eip
# Verify the user
ps -o user,pid,cmd -p $(pidof nginx)
# Should show the service running as the specified user
```
## Requirements
- Linux kernel 4.3+ (for ambient capabilities support)
- libcap library installed
- Finit built with `--enable-libcap`
## Limitations
- Capabilities are only applied when both `@user` and `caps:` are specified
- The service must drop to a non-root user for capabilities to be effective
- Some very old binaries may not work correctly with ambient capabilities
- File system capabilities are not managed by Finit (use `setcap` for that)
## See Also
- `man 7 capabilities` - Linux capabilities overview
- `man 3 cap_iab` - IAB capability API documentation
- `man 8 setcap` - Set file capabilities
- `man 8 getcap` - Query file capabilities
- `man 1 capsh` - Capability shell wrapper
+111 -7
View File
@@ -49,17 +49,121 @@ or
service [...] <...> cgroup.maint /path/to/foo args -- description
The latter form also allows per-stanza limits on the form:
The latter form also allows per-stanza limits. Two syntaxes are supported:
**New comma-separated syntax (recommended):**
service [...] <...> cgroup.maint,cpu.max:10000,mem.max:655360 /path/to/foo args -- description
**Old colon-separated syntax (legacy):**
service [...] <...> cgroup.maint:cpu.max:10000,mem.max:655360 /path/to/foo args -- description
Notice the comma separation and the `mem.` exception to the rule: every
cgroup setting maps directly to cgroup v2 syntax. I.e., `cpu.max` maps
to the file `/sys/fs/cgroup/maint/foo/cpu.max`. There is no filtering,
except for expanding the shorthand `mem.` to `memory.`, if the file is
not available, either the cgroup controller is not available in your
Linux kernel, or the name is misspelled.
Both syntaxes work identically. The new comma-separated syntax is recommended
as it's more consistent with other option parsing in Finit.
Note the `mem.` exception to the rule: every cgroup setting maps directly to
cgroup v2 syntax. I.e., `cpu.max` maps to the file `/sys/fs/cgroup/maint/foo/cpu.max`.
There is no filtering, except for expanding the shorthand `mem.` to `memory.`.
If the file is not available, either the cgroup controller is not available
in your Linux kernel, or the name is misspelled.
### Overriding Cgroup Leaf Names
By default, the cgroup leaf directory name is derived from the service
configuration filename (without the `.conf` extension). For example, a
service defined in `system/10-hotplug.conf` would create a cgroup at
`/sys/fs/cgroup/system/10-hotplug/` by default.
To use a more descriptive name (recommended for clarity), you can specify
`name:` in the cgroup directive:
service [...] <...> cgroup.system,name:udevd /lib/systemd/systemd-udevd -- Device event daemon
This creates the cgroup at `/sys/fs/cgroup/system/udevd/` instead.
The syntax supports combining the name override with other options:
service [...] <...> cgroup.system,name:udevd,cpu.max:10000 /lib/systemd/systemd-udevd -- Device event daemon
Or with delegation:
service [2345] user:podman group:podman \
cgroup.containers,name:podman,delegate,mem.max:4G \
/usr/bin/podman system service -- Podman API
A daemon using `SCHED_RR` currently need to run outside the default cgroups.
service [...] <...> cgroup.root /path/to/daemon arg -- Real-Time process
Cgroup Delegation
-----------------
For services that need to create their own child cgroups (container runtimes
like Docker, Podman, systemd-nspawn, LXC), use the `delegate` option:
service [2345] user:dockerd group:dockerd \
cgroup.system,delegate /usr/bin/dockerd -- Docker daemon
Or with the old colon syntax:
service [2345] user:dockerd group:dockerd \
cgroup.system:delegate /usr/bin/dockerd -- Docker daemon
This allows the container runtime to:
- Create child cgroups for containers
- Manage controller settings for containers
- Move processes between cgroups
When delegation is enabled, Finit:
1. Creates the service cgroup as a **domain group** (not a leaf)
2. Enables all available controllers in `cgroup.subtree_control`
3. Changes ownership of delegation files to the service user
4. Moves the service process to the cgroup root
5. Lets the container runtime manage its own subdirectories
**Requirements:**
- The service should specify `user:` and `group:` for proper ownership
- Controllers are delegated from the parent cgroup
**Example with additional config (new syntax):**
service [2345] user:podman group:podman \
cgroup.containers,delegate,mem.max:4G \
/usr/bin/podman system service -- Podman API
**Or with old syntax:**
service [2345] user:podman group:podman \
cgroup.containers:delegate,mem.max:4G \
/usr/bin/podman system service -- Podman API
Both examples delegate the cgroup while also setting a 4GB memory limit.
**Cgroup structure with delegation:**
Initially, the service process runs directly in the cgroup root:
/sys/fs/cgroup/system/container@web/
├── cgroup.procs (service PID - owned by service user)
├── cgroup.subtree_control (+cpu +memory +io - owned by service user)
└── (container children will be created here)
Once the container runtime creates child cgroups (e.g., `libpod-*/`), cgroups v2
enforces the "no internal processes" rule. When Finit detects this (`EBUSY` error),
it automatically creates an `supervisor/` subdirectory and moves service-related
processes there:
/sys/fs/cgroup/system/container@web/
├── cgroup.procs (empty)
├── cgroup.subtree_control (+cpu +memory +io)
├── supervisor/ (service processes)
│ └── cgroup.procs (conmon PIDs, etc.)
└── libpod-$HASH/ (container processes)
└── cgroup.procs (container PIDs)
This happens automatically - no configuration needed. Without delegation, the
cgroup would be a leaf and the container runtime could not create child cgroups.
+1 -1
View File
@@ -32,7 +32,7 @@ the following layout is recommended:
`- ...
Configuration files in `/etc` are provided by the user, or projects like
[finit-skel](https://github.com/troglobit/finit-skel) and extended by
[finit-skel](https://github.com/finit-project/finit-skel) and extended by
the user.
The files in `/lib/finit/system/*.conf` are system critical services and
+22
View File
@@ -39,3 +39,25 @@ Log rotation is controlled using the global `log` setting.
**Example:**
service log:prio:user.warn,tag:ntpd /sbin/ntpd pool.ntp.org -- NTP daemon
Output Buffering
----------------
When using the `log` directive, Finit redirects the service's stdout and
stderr to a pipe connected to a logger process. Programs detect this as
non-interactive output (i.e., `isatty()` returns false) and typically
switch from line-buffered to fully-buffered mode.
Most well-behaved daemons explicitly flush their output or use syslog
directly, so this is rarely an issue. However, if a service's log
messages appear delayed or batched, you can force line-buffered output
by wrapping the command with `stdbuf`:
service log /usr/bin/stdbuf -oL /path/to/command -- My service
The `-oL` option forces line-buffered output, and `-o0` forces unbuffered
output. See `stdbuf(1)` for details.
> [!NOTE]
> Using `stdbuf` is rarely necessary. Only use it if you observe actual
> buffering issues with a specific service.
+19 -1
View File
@@ -165,4 +165,22 @@ sync(2) has been called, twice.
> "On Linux, sync is only guaranteed to schedule the dirty blocks for
> writing; it can actually take a short time before all the blocks are
> finally written.
> finally written.
**Syntax:** `reboot-watchdog <on|off|true|false|1|0>`
Controls whether the system should reboot via the watchdog timer (WDT)
or directly via the SoC/kernel. When enabled, Finit will:
1. Send `SIGPWR` to the registered watchdog daemon before shutdown
2. Send `SIGTERM` to the watchdog daemon and wait up to 10 seconds
for the watchdog to trigger a hardware reset
When disabled (default), Finit skips the watchdog reboot logic and
calls the kernel's `reboot(2)` syscall directly for a clean SoC reboot.
*Default:* off (reboot via SoC)
> [!NOTE]
> This setting only affects reboots. The watchdog daemon will still
> run and monitor the system during normal operation.
+1
View File
@@ -42,6 +42,7 @@ started at any time by running `initctl start <service>`.
Other run/task/service options are:
* `caps:...` -- see the [Linux Capabilities](capabilities.md) section
* `cgroups:...` -- 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)
+1 -1
View File
@@ -134,4 +134,4 @@ and as soon as it comes back up again `ntpd` is restarted automatically.
[finit.conf(5)]: https://man.troglobit.com/man5/finit.conf.5.html
[infix]: https://kernelkit.github.io
[myLinux]: https://github.com/troglobit/myLinux/
[contrib]: https://github.com/troglobit/finit/tree/master/contrib
[contrib]: https://github.com/finit-project/finit/tree/master/contrib
+11
View File
@@ -0,0 +1,11 @@
.md-header__topic:first-child {
font-weight: 500;
font-style: italic;
}
.md-header__title {
font-size: 1.1rem;
font-style: italic;
font-weight: 500;
line-height: 2.6rem;
}
+32
View File
@@ -130,6 +130,38 @@ unnecessary overhead, which can be removed at build-time using:
configure --enable-auto-reload
**Linux Capabilities**
Finit supports Linux capabilities, allowing services to run with minimal
required privileges instead of running as root. This improves security by
following the principle of least privilege.
```conf
service [2345] name:nginx \
www-data:www-data \
caps:^cap_net_bind_service \
/usr/sbin/nginx -g 'daemon off;'
```
In this example, nginx runs as the unprivileged `www-data` user but retains
the ability to bind to privileged ports (80, 443) through the
`cap_net_bind_service` capability.
The `caps:` directive uses the IAB (Inheritable, Ambient, Bounding) format:
- `^` = Ambient (recommended) - capabilities survive exec()
- `%` = Inheritable only - requires file capabilities
- `!` = Bounding - block from acquiring capability
Multiple capabilities can be specified as comma-separated:
```conf
caps:^cap_net_raw,^cap_net_admin,!cap_sys_admin
```
See the [Linux Capabilities](config/capabilities.md) section for detailed
information, examples, and security best practices.
**Cgroups**
Finit supports cgroups v2 and comes with the following default groups in
+1 -1
View File
@@ -63,7 +63,7 @@ and proposed extensions.
[3]: https://manpages.debian.org/unstable/ifupdown2/interfaces.5.en.html
[4]: https://en.wikipedia.org/wiki/Init
[5]: https://en.wikipedia.org/wiki/Runlevel
[6]: https://github.com/troglobit/finit
[6]: https://github.com/finit-project/finit
[systemd]: https://www.freedesktop.org/wiki/Software/systemd/
[sd_notify()]: https://www.freedesktop.org/software/systemd/man/sd_notify.html
[s6 style]: https://skarnet.org/software/s6/notifywhenup.html
+14 -7
View File
@@ -10,14 +10,21 @@ many embedded systems this is crucial to ensure all circuits on the
board are properly reset for the next boot, in effect ensuring the
system works the same after both a power-on and reboot event.
> [!NOTE]
> The watchdog reboot delegation can be enabled with the `reboot-watchdog`
> configuration option in `/etc/finit.conf`. By default this is disabled
> and the system reboots directly via the SoC using the kernel's `reboot(2)`
> syscall. See [Runlevels](config/runlevels.md) for details.
The delegation is performed at the very last steps of system shutdown,
if reboot has been selected and an elected watchdog is known, first a
`SIGPWR` is sent to advise watchdogd of the pending reboot. Then, when
the necessary steps of preparing the system for shutdown (umount etc.)
are completed, Finit sends `SIGTERM` to watchdogd and puts itself in a
10 sec timeout loop waiting for the WDT to reset the board. If a reset
is not done before the timeout, Finit falls back to`reboot(RB_AUTOBOOT)`
which tells the kernel to do the reboot.
if reboot has been selected, `reboot-watchdog` is enabled, and an elected
watchdog is known. First a `SIGPWR` is sent to advise watchdogd of the
pending reboot. Then, when the necessary steps of preparing the system
for shutdown (umount etc.) are completed, Finit sends `SIGTERM` to
watchdogd and puts itself in a 10 sec timeout loop waiting for the WDT
to reset the board. If a reset is not done before the timeout, Finit
falls back to `reboot(RB_AUTOBOOT)` which tells the kernel to do the
reboot.
An external watchdog service can also be used. The more advanced cousin
[watchdogd](https://github.com/troglobit/watchdogd/) is the recommended
+8 -6
View File
@@ -1,11 +1,13 @@
site_name: doc/
site_name: Documentation
site_description: Fast Init for Linux Systems
site_url: https://troglobit.github.io/finit/
repo_url: https://github.com/troglobit/finit
repo_name: troglobit/finit
site_url: http://finit-project.github.io/finit/
repo_url: https://github.com/finit-project/finit
repo_name: Finit Project
copyright: Copyright &copy; 2008-2025 Joachim Wiberg
docs_dir: doc/
edit_uri: edit/master/doc/
extra_css:
- extra.css
exclude_docs: |
TODO.md
@@ -59,6 +61,7 @@ nav:
- Files & Layout: config/files.md
- Conditions: conditions.md
- Cgroups: config/cgroups.md
- Capabilities: config/capabilities.md
- Environment Variables: config/env.md
- One-shot Tasks: config/task-and-run.md
- Services:
@@ -107,6 +110,5 @@ plugins:
- search
- callouts
extra:
homepage: https://troglobit.com/projects/finit/
homepage: https://finit-project.github.io/
+1
View File
@@ -27,6 +27,7 @@
# include <lite/lite.h>
#endif
#include "conf.h"
#include "finit.h"
#include "helpers.h"
#include "plugin.h"
+1
View File
@@ -143,6 +143,7 @@ static void pidfile_update_conds(char *dir, char *name, uint32_t mask)
/* Complement log in service.c for non-forking services */
logit(LOG_CONSOLE | LOG_NOTICE, "Started %s[%d]", svc_ident(svc, NULL, 0), pid);
cgroup_move_svc(svc);
}
}
+20 -13
View File
@@ -170,35 +170,43 @@ static void file_save(void *arg)
static void file_restore(void *arg)
{
struct tm tm = { 0 };
int rc = 1;
char msg[120];
int rc = 0;
FILE *fp;
if (!rtc_file) {
logit(LOG_NOTICE, "System has no RTC (missing driver?), skipping restore.");
snprintf(msg, sizeof(msg), "Resetting system clock to kernel default, %s.", rtc_timestamp);
print_desc(NULL, msg);
rc = time_set(NULL);
print(rc, NULL);
return;
}
print_desc(NULL, "Restoring system clock from backup");
fp = fopen(rtc_file, "r");
if (fp) {
char buf[32];
if (fgets(buf, sizeof(buf), fp)) {
chomp(buf);
strptime(buf, RTC_FMT, &tm);
rc = time_set(&tm);
if (!strptime(buf, RTC_FMT, &tm))
rc = 1;
}
fclose(fp);
} else
logit(LOG_WARNING, "Missing %s", rtc_file);
rc = 1;
if (rc) {
time_set(NULL);
rc = 2;
print_desc(NULL, "Failed to restore system clock from restore file");
print(2, NULL);
snprintf(msg, sizeof(msg), "Resetting system clock to kernel default, %s.", rtc_timestamp);
print_desc(NULL, msg);
rc = time_set(NULL);
print(rc, NULL);
} else {
print_desc(NULL, "Restoring system clock from restore file");
rc = time_set(&tm);
print(rc, NULL);
}
print(rc, NULL);
}
static int rtc_open(void)
@@ -292,8 +300,7 @@ static void rtc_restore(void *arg)
print(2, NULL);
/* Try restoring from last save game */
if (rtc_file)
file_restore(arg);
file_restore(arg);
} else
print(0, NULL);
+1
View File
@@ -56,6 +56,7 @@ logit_LDADD = $(lite_LIBS)
finit_SOURCES = api.c cgroup.c cgroup.h \
client.c client.h \
clone3.c clone3.h \
cond.c cond-w.c cond.h \
conf.c conf.h \
devmon.c devmon.h \
+9 -8
View File
@@ -68,7 +68,8 @@ static int stop(svc_t *svc, void *user_data)
service_timeout_cancel(svc);
svc_stop(svc);
service_step(svc);
service_step_all(SVC_TYPE_ANY);
if (!IS_RESERVED_RUNLEVEL(runlevel))
service_step_all(SVC_TYPE_ANY);
return 0;
}
@@ -83,7 +84,8 @@ static int start(svc_t *svc, void *user_data)
service_timeout_cancel(svc);
svc_start(svc);
service_step(svc);
service_step_all(SVC_TYPE_ANY);
if (!IS_RESERVED_RUNLEVEL(runlevel))
service_step_all(SVC_TYPE_ANY);
return 0;
}
@@ -391,11 +393,6 @@ static void api_cb(uev_t *w, void *arg, int events)
}
switch (rq.cmd) {
case INIT_CMD_RELOAD:
case INIT_CMD_START_SVC:
case INIT_CMD_RESTART_SVC:
case INIT_CMD_STOP_SVC:
case INIT_CMD_RELOAD_SVC:
case INIT_CMD_REBOOT:
case INIT_CMD_HALT:
case INIT_CMD_POWEROFF:
@@ -453,6 +450,10 @@ static void api_cb(uev_t *w, void *arg, int events)
break;
case INIT_CMD_RELOAD: /* 'init q' and 'initctl reload' */
if (IS_RESERVED_RUNLEVEL(runlevel)) {
warnx("Ignoring reload in runlevel S and 6/0.");
goto done;
}
dbg("reload");
sm_reload();
break;
@@ -576,7 +577,7 @@ static void api_cb(uev_t *w, void *arg, int events)
dbg("Unsupported cmd: %d", rq.cmd);
break;
}
done:
if (result)
rq.cmd = INIT_CMD_NACK;
else
+387 -19
View File
@@ -21,6 +21,7 @@
* THE SOFTWARE.
*/
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
@@ -61,6 +62,160 @@ static uev_t cgw;
static int avail;
/*
* Set up inotify watch on cgroup for automatic cleanup when empty.
* Should be called after cgroup_prepare() but before process starts.
*/
int cgroup_watch(const char *group, const char *name)
{
char path[256];
int rc;
if (!avail)
return 0;
/* Special case: root cgroup has no separate events file to watch */
if (!strcmp(group, "root"))
return 0;
snprintf(path, sizeof(path), "/sys/fs/cgroup/%s/%s/cgroup.events", group, name);
rc = iwatch_add(&iw_cgroup, path, 0);
if (rc < 0) {
warn("Failed setting up inotify watch on %s", path);
return -1;
}
dbg("Watching %s for automatic cleanup", path);
return 0;
}
/*
* Derive cgroup name from service (mirrors group_name() in service.c)
* For use by plugins that need to determine the cgroup path.
*
* MUST return the same value that group_name() in service.c returns!
*/
char *cgroup_svc_name(svc_t *svc, char *buf, size_t len)
{
char *ptr;
/* Use explicit cgroup leaf name if specified */
if (svc->cgroup.leafname[0]) {
strlcpy(buf, svc->cgroup.leafname, len);
return buf;
}
if (!svc->file[0])
return svc_ident(svc, buf, len);
ptr = strrchr(svc->file, '/');
if (ptr)
ptr++;
else
ptr = svc->file;
strlcpy(buf, ptr, len);
/* Strip .conf extension - this gives us the cgroup name */
ptr = strstr(buf, ".conf");
if (ptr)
*ptr = 0;
return buf;
}
/*
* Helper function to move a PID to a service's cgroup.
* @group: Top-level cgroup (e.g., "system", "user", "maint")
* @name: Service-specific cgroup name (e.g., "dockerd", "nginx")
* @pid: Process ID to move
* @delegate: If 1, handle potential EBUSY by using supervisor/ subdirectory
*
* Useful for forking services where child PIDs need to be moved after they appear.
*
* For delegated cgroups, handles the cgroups v2 "no internal processes" rule:
* if the parent has child cgroups, tries to reuse process-named subdirectory or
* falls back to supervisor/ subdirectory.
*
* Returns: 0 on success, -1 on failure
*/
int cgroup_move_pid(const char *group, const char *name, int pid, int delegate)
{
int rc = 0, reuse = 0;
char path[512];
if (!avail)
return 0;
/* Sanity checks */
if (!group || !name || pid <= 0) {
errno = EINVAL;
return -1;
}
/* Disallow path traversal */
if (strstr(group, "..") || strstr(name, "..") ||
strchr(group, '/') || strchr(name, '/')) {
errno = EINVAL;
warn("Invalid cgroup path components: group=%s name=%s", group, name);
return -1;
}
snprintf(path, sizeof(path), FINIT_CGPATH "/%s/%s", group, name);
if (!fisdir(path)) {
warn("Cgroup %s does not exist", path);
return -1;
}
if ((rc = fnwrite(str("%d", pid), "%s/cgroup.procs", path))) {
/*
* EBUSY means the cgroup has children (cgroups v2 "no internal processes" rule).
* For delegated cgroups, fall back to subdirectory.
*/
if (errno == EBUSY && delegate) {
char comm[64];
/* Check if subdirectory matching process name, e.g., conmon, exists */
readsnf(comm, sizeof(comm), "/proc/%d/comm", pid);
strlcat(path, "/", sizeof(path));
strlcat(path, comm, sizeof(path));
if (fisdir(path))
reuse = 1;
else
snprintf(path, sizeof(path), FINIT_CGPATH "/%s/%s/supervisor", group, name);
rc = fnwrite(str("%d", pid), "%s/cgroup.procs", path);
}
if (rc) {
warn("Failed moving pid %d to cgroup %s", pid, path);
return -1;
}
}
if (reuse) {
snprintf(path, sizeof(path), FINIT_CGPATH "/%s/%s/supervisor", group, name);
rmdir(path);
}
return 0;
}
int cgroup_move_svc(svc_t *svc)
{
const char *group = "system";
char name[MAX_ARG_LEN];
if (!avail || !svc)
return 0;
if (svc->cgroup.name[0])
group = svc->cgroup.name;
return cgroup_move_pid(group, cgroup_svc_name(svc, name, sizeof(name)),
svc->pid, svc->cgroup.delegate);
}
static void cgset(const char *path, char *ctrl, char *prop)
{
char *val;
@@ -148,40 +303,99 @@ static void group_init(char *path, int leaf, const char *cfg)
}
}
static int cgroup_leaf_init(char *group, char *name, int pid, const char *cfg)
static int cgroup_create(const char *group, const char *name, const char *cfg,
int delegate, const char *username, const char *grpname,
char *pathbuf, size_t pathlen)
{
char path[256];
dbg("group %s, name %s, pid %d, cfg %s", group, name, pid, cfg ?: "NIL");
if (pid < 0 || pid == 1) {
errno = EINVAL;
return 1;
snprintf(path, sizeof(path), "/sys/fs/cgroup/%s/%s", group, name);
if (delegate) {
char initpath[PATH_MAX];
/* For delegation, create as domain group (not leaf) */
group_init(path, 0, cfg);
/* Enable controllers for delegation */
if (fnwrite(controllers, "%s/cgroup.subtree_control", path))
warn("Failed enabling controllers in %s for delegation", path);
/* Change ownership of delegation files */
if (username && username[0] && grpname && grpname[0]) {
uid_t uid = getuser(username, NULL);
gid_t gid = getgroup(grpname);
if (uid != (uid_t)-1 && gid != (gid_t)-1) {
char filepath[PATH_MAX];
char *files[] = {
"cgroup.procs",
"cgroup.subtree_control",
"cgroup.threads",
"cgroup.type",
NULL
};
for (int i = 0; files[i]; i++) {
snprintf(filepath, sizeof(filepath), "%s/%s", path, files[i]);
if (chown(filepath, uid, gid))
warn("Failed chown %s to %d:%d", filepath, uid, gid);
}
}
}
snprintf(initpath, sizeof(initpath), "%s/supervisor", path);
group_init(initpath, 1, NULL);
strlcpy(path, initpath, sizeof(path));
} else {
/* Normal leaf cgroup */
group_init(path, 1, cfg);
}
/* create and initialize new group */
snprintf(path, sizeof(path), "/sys/fs/cgroup/%s/%s", group, name);
group_init(path, 1, cfg);
if (!fisdir(path)) {
warn("Cgroup directory %s doesn't exist after creation", path);
return -1;
}
/* move process to new group */
if (fnwrite(str("%d", pid), "%s/cgroup.procs", path))
err(1, "Failed moving pid %d to group %s", pid, path);
/* Return the path to caller if they provided a buffer */
if (pathbuf && pathlen > 0)
strlcpy(pathbuf, path, pathlen);
strlcat(path, "/cgroup.events", sizeof(path));
return iwatch_add(&iw_cgroup, path, 0);
return cgroup_watch(group, name);
}
int cgroup_user(char *name, int pid)
static int cgroup_leaf_init(const char *group, const char *name, int pid, const char *cfg,
int delegate, const char *username, const char *grpname)
{
dbg("group %s, name %s, pid %d, cfg %s, delegate %d", group, name, pid, cfg ?: "NIL", delegate);
if (cgroup_create(group, name, cfg, delegate, username, grpname, NULL, 0))
return -1;
dbg("Assigning PID %d to cgroup %s/%s", pid, group, name);
/* Special case: "root" cgroup means the actual cgroup root */
if (!strcmp(group, "root"))
return fnwrite(str("%d", pid), FINIT_CGPATH "/cgroup.procs");
if (cgroup_move_pid(group, name, pid, delegate))
err(1, "Failed moving pid %d to group %s/%s", pid, group, name);
/* Set up inotify watch for cgroup cleanup */
return cgroup_watch(group, name);
}
int cgroup_user(const char *name, int pid)
{
if (!avail)
return 0;
return cgroup_leaf_init("user", name, pid, NULL);
return cgroup_leaf_init("user", name, pid, NULL, 0, NULL, NULL);
}
int cgroup_service(char *name, int pid, struct cgroup *cg)
int cgroup_service(const char *name, int pid, struct cgroup *cg, char *username, char *grpname)
{
char *group = "system";
int delegate = 0;
if (!avail)
return 0;
@@ -198,9 +412,64 @@ int cgroup_service(char *name, int pid, struct cgroup *cg)
snprintf(path, sizeof(path), "/sys/fs/cgroup/%s", cg->name);
if (fisdir(path))
group = cg->name;
delegate = cg->delegate;
}
return cgroup_leaf_init(group, name, pid, cg ? cg->cfg : NULL);
return cgroup_leaf_init(group, name, pid, cg ? cg->cfg : NULL, delegate, username, grpname);
}
/* Create cgroup for the requested type of service, return fd to cgroup for clone3() */
int cgroup_prepare(svc_t *svc, const char *name)
{
const char *group;
const char *cfg = NULL;
const char *username = NULL;
const char *grpname = NULL;
int delegate = 0;
char path[256];
int fd = -1;
if (!avail)
return -1;
if (!name) {
errno = EINVAL;
return -1;
}
if (!svc) {
/* Helper process (e.g., networking) */
group = "system";
} else if (svc_is_tty(svc)) {
/* TTY/getty services go in user cgroup */
group = "user";
} else if (svc->cgroup.name[0] && !strcmp(svc->cgroup.name, "root")) {
/* Special case: SCHED_RR processes go in root cgroup */
fd = open("/sys/fs/cgroup", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
if (fd < 0)
warn("Failed opening root cgroup");
return fd;
} else {
/* Regular service */
group = svc->cgroup.name[0] ? svc->cgroup.name : "system";
cfg = svc->cgroup.cfg;
delegate = svc->cgroup.delegate;
username = svc->username;
grpname = svc->group;
}
/* Create the cgroup and get the path back */
if (cgroup_create(group, name, cfg, delegate, username, grpname, path, sizeof(path)))
return -1;
/* Open and return fd for clone3() */
fd = open(path, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
if (fd < 0)
warn("Failed opening cgroup %s", path);
return fd;
}
static void append_ctrl(char *ctrl)
@@ -405,6 +674,79 @@ int cgroup_add(char *name, char *cfg, int is_protected)
return 0;
}
/*
* Walk a cgroup directory and remove all empty subdirectories recursively.
*/
static void cgroup_prune_recursive(const char *path)
{
struct dirent *d;
DIR *dir;
dir = opendir(path);
if (!dir)
return;
while ((d = readdir(dir)) != NULL) {
char subpath[PATH_MAX];
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
continue;
if (d->d_type != DT_DIR)
continue;
snprintf(subpath, sizeof(subpath), "%s/%s", path, d->d_name);
cgroup_prune_recursive(subpath);
}
closedir(dir);
/*
* Try to remove this directory. If it still has children or processes,
* rmdir() will fail with ENOTEMPTY/EBUSY - that's fine.
*/
if (rmdir(path) && errno != ENOTEMPTY && errno != EBUSY)
warn("Failed to prune %s", path);
}
/*
* Housekeeping: prune all empty cgroup subdirectories.
* Called after runlevel transitions to clean up finished run/task cgroups.
*/
void cgroup_prune(void)
{
struct dirent *entry;
struct cg *cg;
char path[256];
DIR *dir;
if (!avail)
return;
dbg("Pruning empty cgroup directories...");
/* Walk through each top-level cgroup (init, system, user) */
TAILQ_FOREACH(cg, &cgroups, link) {
snprintf(path, sizeof(path), FINIT_CGPATH "/%s", cg->name);
dir = opendir(path);
if (!dir)
continue;
/* Check each subdirectory */
while ((entry = readdir(dir)) != NULL) {
char subpath[512];
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
continue;
if (entry->d_type != DT_DIR)
continue;
snprintf(subpath, sizeof(subpath), "%s/%s", path, entry->d_name);
cgroup_prune_recursive(subpath);
}
closedir(dir);
}
}
/*
* Remove inactive top-level cgroup
*/
@@ -428,7 +770,8 @@ int cgroup_del(char *dir)
}
if (rmdir(dir) && errno != ENOENT) {
dbg("Failed removing %s: %s", dir, strerror(errno));
if (errno != EBUSY)
warn("Failed rmdir(%s): %s", dir, strerror(errno));
return -1;
}
@@ -442,6 +785,31 @@ int cgroup_del(char *dir)
return 0;
}
/*
* Delete cgroup for a service (convenience wrapper)
*/
int cgroup_del_svc(svc_t *svc, const char *name)
{
const char *group;
char path[256];
if (!avail)
return 0;
/* Determine group from service */
if (svc_is_tty(svc)) {
group = "user";
} else if (svc->cgroup.name[0] && !strcmp(svc->cgroup.name, "root")) {
/* Root cgroup - nothing to delete */
return 0;
} else {
group = svc->cgroup.name[0] ? svc->cgroup.name : "system";
}
snprintf(path, sizeof(path), "/sys/fs/cgroup/%s/%s", group, name);
return cgroup_del(path);
}
/* the top-level init cgroup is a leaf, that's ensured in cgroup_init() */
void cgroup_config(void)
{
+23 -8
View File
@@ -26,21 +26,36 @@
#include <uev/uev.h>
/* Forward declaration */
typedef struct svc svc_t;
struct cgroup {
char name[16];
char cfg[128];
char leafname[128];
char delegate;
};
void cgroup_mark_all(void);
void cgroup_cleanup (void);
void cgroup_mark_all(void);
void cgroup_cleanup (void);
int cgroup_add (char *name, char *cfg, int is_protected);
int cgroup_del (char *dir);
void cgroup_config (void);
int cgroup_add (char *name, char *cfg, int is_protected);
int cgroup_del (char *dir);
int cgroup_del_svc (svc_t *svc, const char *name);
void cgroup_config (void);
void cgroup_init (uev_ctx_t *ctx);
void cgroup_init (uev_ctx_t *ctx);
int cgroup_user (char *name, int pid);
int cgroup_service (char *name, int pid, struct cgroup *cg);
int cgroup_user (const char *name, int pid);
int cgroup_service (const char *name, int pid, struct cgroup *cg, char *username, char *group);
char *cgroup_svc_name(svc_t *svc, char *buf, size_t len);
int cgroup_prepare (svc_t *svc, const char *name);
int cgroup_watch (const char *group, const char *name);
int cgroup_move_pid(const char *group, const char *name, int pid, int delegate);
int cgroup_move_svc(svc_t *svc);
void cgroup_prune (void);
#endif /* FINIT_CGROUP_H_ */
+241 -58
View File
@@ -29,19 +29,15 @@
#include <stdlib.h>
#include <search.h>
#include <inttypes.h>
#ifdef _LIBITE_LITE
# include <libite/lite.h>
#else
# include <lite/lite.h>
#endif
#include <sys/sysinfo.h> /* sysinfo() */
#include "cgutil.h"
#include "initctl.h"
#include "log.h"
#define CDIM plain ? "" : "\e[2m"
#define CRST plain ? "" : "\e[0m"
#define CDIM plain ? "" : "\e[2m"
#define CRST plain ? "" : "\e[0m"
#define CLREOL plain ? "" : "\e[K" /* Clear to end of line */
#define NONE " "
#define PIPE plain ? "| " : "│ "
@@ -102,6 +98,7 @@ char *pid_cmdline(int pid)
buf[i] = ' ';
}
fclose(fp);
return buf;
}
@@ -144,7 +141,7 @@ char *pid_cgroup(int pid)
return NULL;
}
static char *cgroup_val(char *path, char *file, char *buf, size_t len)
char *cgroup_val(char *path, char *file, char *buf, size_t len)
{
char *val = NULL;
FILE *fp;
@@ -252,6 +249,42 @@ uint64_t cgroup_memory(char *group)
return cgroup_uint64(path, "memory.current");
}
int cgroup_throttle(char *group, uint64_t *throttled_usec, uint64_t *nr_throttled)
{
char path[256];
char buf[256];
FILE *fp;
int found = 0;
paste(path, sizeof(path), FINIT_CGPATH, group);
fp = fopenf("r", "%s/cpu.stat", path);
if (!fp)
return -1;
*throttled_usec = 0;
*nr_throttled = 0;
while (fgets(buf, sizeof(buf), fp)) {
chomp(buf);
if (!strncmp(buf, "throttled_usec", 14)) {
*throttled_usec = strtoull(&buf[15], NULL, 10);
found++;
} else if (!strncmp(buf, "nr_throttled", 12)) {
*nr_throttled = strtoull(&buf[13], NULL, 10);
found++;
}
if (found == 2)
break;
}
fclose(fp);
return 0;
}
static float cgroup_cpuload(struct cg *cg)
{
char fn[256];
@@ -344,18 +377,148 @@ struct cg *cg_stats(char *path)
return cg;
}
/* query config */
/*
* Compare two memory limit values, returning the more restrictive one.
* For memory.max: smaller value wins (except "max" means unlimited)
* For memory.min: larger value wins (more protection)
*/
static uint64_t cmp_mem_limit(const char *a, const char *b, int is_max)
{
uint64_t val_a, val_b;
if (!a || !a[0])
return b && b[0] ? strtoull(b, NULL, 10) : 0;
if (!b || !b[0])
return strtoull(a, NULL, 10);
/* Handle "max" (unlimited) */
if (!strcmp(a, "max"))
return !strcmp(b, "max") ? UINT64_MAX : strtoull(b, NULL, 10);
if (!strcmp(b, "max"))
return strtoull(a, NULL, 10);
val_a = strtoull(a, NULL, 10);
val_b = strtoull(b, NULL, 10);
if (is_max)
return val_a < val_b ? val_a : val_b; /* min for max limit */
else
return val_a > val_b ? val_a : val_b; /* max for min limit */
}
/*
* Compare two cpu.max values (format: "quota period")
* Returns the more restrictive quota (smallest quota/period ratio)
* Stores result in dst, up to len bytes
*/
static void cmp_cpu_max(const char *a, const char *b, char *dst, size_t len)
{
char a_copy[32], b_copy[32];
char *a_quota, *a_period, *b_quota, *b_period;
uint64_t qa, pa, qb, pb;
double ratio_a, ratio_b;
if (!a || !a[0]) {
if (b && b[0])
strlcpy(dst, b, len);
else
dst[0] = 0;
return;
}
if (!b || !b[0]) {
strlcpy(dst, a, len);
return;
}
/* Parse a */
strlcpy(a_copy, a, sizeof(a_copy));
a_quota = strtok(a_copy, " ");
a_period = strtok(NULL, " ");
/* Parse b */
strlcpy(b_copy, b, sizeof(b_copy));
b_quota = strtok(b_copy, " ");
b_period = strtok(NULL, " ");
/* Handle "max" quota (unlimited) */
if (a_quota && !strcmp(a_quota, "max")) {
strlcpy(dst, b, len);
return;
}
if (b_quota && !strcmp(b_quota, "max")) {
strlcpy(dst, a, len);
return;
}
/* Compare ratios (quota/period) - smaller ratio is more restrictive */
if (!a_quota || !a_period || !b_quota || !b_period) {
strlcpy(dst, a, len); /* fallback */
return;
}
qa = strtoull(a_quota, NULL, 10);
pa = strtoull(a_period, NULL, 10);
qb = strtoull(b_quota, NULL, 10);
pb = strtoull(b_period, NULL, 10);
if (pa == 0 || pb == 0) {
strlcpy(dst, a, len); /* fallback */
return;
}
ratio_a = (double)qa / (double)pa;
ratio_b = (double)qb / (double)pb;
strlcpy(dst, ratio_a <= ratio_b ? a : b, len);
}
/* query config with hierarchical limit resolution */
struct cg *cg_conf(char *path)
{
static struct cg cg;
char parent[512];
char tmp[32];
uint64_t mem_min_val, mem_max_val;
/* Read initial values from the leaf cgroup */
cgroup_val(path, "memory.min", cg.cg_mem.min, sizeof(cg.cg_mem.min));
cgroup_memval(path, "memory.max", cg.cg_mem.max, sizeof(cg.cg_mem.max));
cgroup_val(path, "memory.max", cg.cg_mem.max, sizeof(cg.cg_mem.max));
cgroup_val(path, "cpu.weight", cg.cg_cpu.weight, sizeof(cg.cg_cpu.weight));
cgroup_val(path, "cpu.max", cg.cg_cpu.max, sizeof(cg.cg_cpu.max));
cgroup_val(path, "cpuset.cpus.effective", cg.cg_cpu.set, sizeof(cg.cg_cpu.set));
cg.cg_vmsize = cgroup_uint64(path, "memory.current");
/* Walk up the hierarchy to find most restrictive limits */
strlcpy(parent, path, sizeof(parent));
while (strcmp(parent, FINIT_CGPATH) != 0) {
char *slash = strrchr(parent, '/');
if (!slash || slash == parent)
break;
*slash = '\0'; /* Move up one level */
/* Compare and update memory.min (take maximum) */
if (cgroup_val(parent, "memory.min", tmp, sizeof(tmp))) {
mem_min_val = cmp_mem_limit(cg.cg_mem.min, tmp, 0);
if (mem_min_val == UINT64_MAX)
strlcpy(cg.cg_mem.min, "max", sizeof(cg.cg_mem.min));
else
snprintf(cg.cg_mem.min, sizeof(cg.cg_mem.min), "%lu", mem_min_val);
}
/* Compare and update memory.max (take minimum) */
if (cgroup_val(parent, "memory.max", tmp, sizeof(tmp))) {
mem_max_val = cmp_mem_limit(cg.cg_mem.max, tmp, 1);
if (mem_max_val == UINT64_MAX)
strlcpy(cg.cg_mem.max, "max", sizeof(cg.cg_mem.max));
else
snprintf(cg.cg_mem.max, sizeof(cg.cg_mem.max), "%lu", mem_max_val);
}
/* Compare and update cpu.max (take most restrictive) */
if (cgroup_val(parent, "cpu.max", tmp, sizeof(tmp)))
cmp_cpu_max(cg.cg_cpu.max, tmp, cg.cg_cpu.max, sizeof(cg.cg_cpu.max));
}
return &cg;
}
@@ -378,32 +541,29 @@ static int cgroup_filter(const struct dirent *entry)
int cgroup_tree(char *path, char *pfx, int mode, int pos)
{
struct dirent **namelist = NULL;
char s[32], r[32], l[32];
char row[ttcols + 9]; /* + control codes */
char s[32], r[32], l[32], row[1024];
size_t rlen = sizeof(row) - 1;
size_t rplen = rlen - 9;
struct stat st;
struct cg *cg;
char buf[512];
int rc = 0;
FILE *fp;
int i, n;
int num;
if (pos >= ttrows)
return 0;
if (pos >= ttrows - 1)
return pos;
if (-1 == lstat(path, &st))
return 1;
return pos;
if ((st.st_mode & S_IFMT) != S_IFDIR) {
errno = ENOTDIR;
return -1;
return pos;
}
fp = fopenf("r", "%s/cgroup.procs", path);
if (!fp)
return -1;
return pos;
num = 0;
while (fgets(buf, sizeof(buf), fp))
num++;
@@ -413,7 +573,7 @@ int cgroup_tree(char *path, char *pfx, int mode, int pos)
switch (mode) {
case 1:
cg = cg_stats(path);
snprintf(row, rplen, "\r %6.6s %6.6s %6.6s %5.1f %5.1f %s",
snprintf(row, rlen, " %6.6s %6.6s %6.6s %5.1f %5.1f %s",
memsz(cg->cg_vmsize, s, sizeof(s)),
memsz(cg->cg_rss, r, sizeof(r)),
memsz(cg->cg_vmlib, l, sizeof(l)),
@@ -421,18 +581,20 @@ int cgroup_tree(char *path, char *pfx, int mode, int pos)
break;
case 2:
cg = cg_conf(path);
snprintf(row, rplen, "\r%6.6s [%-6.6s%6.6s] %6s [%-6.6s%6.6s] %s",
snprintf(row, rlen, "%6.6s [%-6.6s%6.6s] %6s [%-6.6s%6.6s] %s",
memsz(cg->cg_vmsize, s, sizeof(s)),
cg->cg_mem.min, cg->cg_mem.max, cg->cg_cpu.set,
cg->cg_cpu.weight, cg->cg_cpu.max, path);
break;
default:
strlcpy(row, "\r", rplen);
strlcat(row, path, rplen);
strlcpy(row, path, rlen);
break;
}
puts(row);
printf("\r%s%s\n", row, CLREOL);
pos++;
if (pos >= ttrows - 1)
goto out;
}
if (num > 0) {
@@ -450,53 +612,45 @@ int cgroup_tree(char *path, char *pfx, int mode, int pos)
/* skip kernel threads for now (no cmdline) */
cmdline = pid_cmdline(pid);
if (cmdline) {
char proc[ttcols];
char proc[512]; /* Build full command, truncate row later */
switch (mode) {
case 1:
snprintf(row, rplen, "\r%37s", " ");
snprintf(row, rlen, "%37s", " ");
break;
case 2:
snprintf(row, rplen, "\r --.-- [ ] [ ] ");
snprintf(row, rlen, " --.-- [ ] [ ] ");
break;
default:
strlcpy(row, "\r", rplen);
row[0] = '\0';
break;
}
strlcat(row, pfx, rplen);
strlcat(row, pfx, rlen);
strlcat(row, ++i == num ? END : FORK, rlen);
snprintf(proc, sizeof(proc), " %d %s", pid, cmdline);
if (plain) {
strlcat(row, proc, rplen);
strlcat(row, proc, rlen);
} else {
int len;
strlcat(row, CDIM, rlen);
strlcat(row, proc, rlen);
len = strlen(row) + strlen(CRST);
if (len > (int)rlen)
row[rlen - strlen(CRST)] = 0;
strlcat(row, CRST, sizeof(row));
}
puts(row);
printf("\r%s%s\n", row, CLREOL);
pos++;
free(cmdline);
}
if (mode == 1) {
pos += i;
if (pos >= ttrows)
if (pos >= ttrows - 1)
break;
}
}
}
out:
fclose(fp);
n = scandir(path, &namelist, cgroup_filter, alphasort);
@@ -505,12 +659,15 @@ int cgroup_tree(char *path, char *pfx, int mode, int pos)
char *nm = namelist[i]->d_name;
char prefix[80];
if (pos >= ttrows - 1)
break;
snprintf(buf, sizeof(buf), "%s/%s", path, nm);
switch (mode) {
case 1:
cg = cg_stats(buf);
snprintf(row, rplen,
"\r %6.6s %6.6s %6.6s %5.1f %5.1f ",
snprintf(row, rlen,
" %6.6s %6.6s %6.6s %5.1f %5.1f ",
memsz(cg->cg_vmsize, s, sizeof(s)),
memsz(cg->cg_rss, r, sizeof(r)),
memsz(cg->cg_vmlib, l, sizeof(l)),
@@ -518,32 +675,33 @@ int cgroup_tree(char *path, char *pfx, int mode, int pos)
break;
case 2:
cg = cg_conf(buf);
snprintf(row, rplen, "\r%6.6s [%-6.6s%6.6s] %6.6s [%-6.6s%6.6s] ",
snprintf(row, rlen, "%6.6s [%-6.6s%6.6s] %6.6s [%-6.6s%6.6s] ",
memsz(cg->cg_vmsize, s, sizeof(s)),
cg->cg_mem.min, cg->cg_mem.max,
cg->cg_cpu.set, cg->cg_cpu.weight, cg->cg_cpu.max);
break;
default:
strlcpy(row, "\r", rplen);
row[0] = '\0';
break;
}
strlcat(row, pfx, rplen);
strlcat(row, pfx, rlen);
if (i + 1 == n) {
strlcat(row, END, rplen);
strlcat(row, END, rlen);
snprintf(prefix, sizeof(prefix), "%s ", pfx);
} else {
strlcat(row, FORK, rplen);
strlcat(row, FORK, rlen);
snprintf(prefix, sizeof(prefix), "%s%s ", pfx, PIPE);
}
strlcat(row, " ", rplen);
strlcat(row, " ", rlen);
strlcat(row, nm, rplen);
strlcat(row, "/ ", rplen);
strlcat(row, nm, rlen);
strlcat(row, "/ ", rlen);
puts(row);
printf("\r%s%s\n", row, CLREOL);
pos++;
rc += cgroup_tree(buf, prefix, mode, pos + i);
pos = cgroup_tree(buf, prefix, mode, pos);
free(namelist[i]);
}
@@ -551,7 +709,7 @@ int cgroup_tree(char *path, char *pfx, int mode, int pos)
free(namelist);
}
return rc;
return pos;
}
int show_cgps(char *arg)
@@ -570,17 +728,39 @@ int show_cgps(char *arg)
static void cgtop(uev_t *w, void *arg, int events)
{
static int first = 1;
int lines;
(void)w;
(void)events;
fputs("\e[2J\e[1;1H", stdout);
if (heading)
/* Re-probe screen size in case terminal was resized */
ttinit(1);
/* Clear screen on first run to remove any artifacts */
if (first) {
fputs("\e[?7l\e[2J\e[H", stdout); /* Disable line wrap, clear, home */
first = 0;
} else {
/* Move cursor to home instead of clearing screen to avoid flicker */
fputs("\e[H", stdout);
}
lines = 0;
if (heading) {
print_header(" VmSIZE RSS VmLIB %%MEM %%CPU GROUP");
cgroup_tree(arg, NULL, 1, 0);
lines = 1;
}
cgroup_tree(arg, NULL, 1, lines);
/* Clear from cursor to end of screen to remove leftover lines */
fputs("\e[J", stdout);
fflush(stdout);
}
static void cleanup(void)
{
fputs("\e[?7h", stdout); /* Re-enable line wrap */
ttcooked();
showcursor();
puts("");
@@ -631,6 +811,9 @@ int show_cgtop(char *arg)
if (!hcreate(ttrows + 25))
ERR(70, "failed creating hash table");
/* Ensure we have correct terminal size before starting */
ttinit(1);
sysinfo(&si);
total_ram = si.totalram * si.mem_unit;
+2
View File
@@ -57,7 +57,9 @@ int cgroup_avail(void);
char *pid_cmdline (int pid);
char *pid_cgroup (int pid);
char *cgroup_val(char *path, char *file, char *buf, size_t len);
uint64_t cgroup_memory(char *group);
int cgroup_throttle(char *group, uint64_t *throttled_usec, uint64_t *nr_throttled);
struct cg *cg_stats(char *path);
struct cg *cg_conf (char *path);
+95
View File
@@ -0,0 +1,95 @@
/* Portable clone3() wrapper with fallback to fork()
*
* Copyright (c) 2025 Joachim Wiberg <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.
*/
#include "config.h"
#include <errno.h>
#include <sched.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "clone3.h"
#include "finit.h"
#include "log.h"
/* Clone3 flags from linux/sched.h */
#ifndef CLONE_INTO_CGROUP
#define CLONE_INTO_CGROUP 0x200000000ULL
#endif
/* Linux kernel struct clone_args for clone3() syscall */
struct clone3_args {
uint64_t flags; /* Flags bit mask */
uint64_t pidfd; /* File descriptor for PID */
uint64_t child_tid; /* Child TID */
uint64_t parent_tid; /* Parent TID */
uint64_t exit_signal; /* Signal to deliver on exit */
uint64_t stack; /* Stack address */
uint64_t stack_size; /* Stack size */
uint64_t tls; /* TLS descriptor */
uint64_t set_tid; /* Set PID */
uint64_t set_tid_size; /* Number of set_tid entries */
uint64_t cgroup; /* Cgroup file descriptor */
} __attribute__((aligned(8)));
#ifdef __NR_clone3
static int use_clone3 = 1;
#else
static int use_clone3 = 0;
#endif
int has_clone3(void)
{
return use_clone3 == 1;
}
pid_t call_clone3(uint64_t flags, int cgroup_fd)
{
struct clone3_args cl = {
.flags = flags,
.exit_signal = SIGCHLD,
};
pid_t pid;
if (!use_clone3)
goto fallback;
if (cgroup_fd >= 0) {
cl.flags |= CLONE_INTO_CGROUP;
cl.cgroup = cgroup_fd;
}
pid = syscall(__NR_clone3, &cl, sizeof(cl));
if (pid != -1)
return pid;
logit(LOG_WARNING, "clone3() failed, falling back to fork(): %s", strerror(errno));
use_clone3 = 0;
fallback:
return fork();
}
+33
View File
@@ -0,0 +1,33 @@
/* Portable clone3() wrapper with fallback to fork()
*
* Copyright (c) 2025 Joachim Wiberg <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.
*/
#ifndef FINIT_CLONE3_H_
#define FINIT_CLONE3_H_
#include <stdint.h>
#include <sys/types.h>
pid_t call_clone3 (uint64_t flags, int cgroup_fd);
int has_clone3 (void);
#endif /* FINIT_CLONE3_H_ */
+41 -2
View File
@@ -60,6 +60,7 @@ int single = 0; /* single user mode from kernel cmdline */
int bootstrap = 1; /* set while bootstrapping (for TTYs) */
int kerndebug = 0; /* set if /proc/sys/kernel/printk > 7 */
int syncsec = 0; /* reboot delay */
int wdtreboot = 0; /* reboot via watchdog, default: SOC */
int readiness = SVC_NOTIFY_PID;
char *finit_conf= NULL;
char *finit_rcsd= NULL;
@@ -105,7 +106,9 @@ char *runparts = NULL;
int runparts_progress;
int runparts_sysv;
char cgroup_current[16]; /* cgroup.NAME sets current cgroup for a set of services */
char cgroup_current[16]; /* cgroup.NAME sets current cgroup for a set of services */
char cgroup_settings_current[128]; /* cgroup.system,cpu.weight:500 - cgroup settings */
int cgroup_delegate_current; /* cgroup.system,delegate - delegation flag */
struct conf_change {
TAILQ_ENTRY(conf_change) link;
@@ -1034,6 +1037,11 @@ static int parse_static(char *line, int is_rcsd)
return 0;
}
if (MATCH_CMD(line, "reboot-watchdog ", x)) {
wdtreboot = get_bool(strip_line(x), 0);
return 0;
}
/*
* Periodic check and instability index leveler, seconds
*/
@@ -1099,7 +1107,38 @@ static int parse_dynamic(char *line, struct rlimit rlimit[], char *file)
/* Set current cgroup for the following services/run/tasks */
if (MATCH_CMD(line, "cgroup.", x)) {
strlcpy(cgroup_current, x, sizeof(cgroup_current));
char *group, *token, *saveptr;
char settings[128] = {0};
/* Reset cgroup state */
cgroup_settings_current[0] = '\0';
cgroup_delegate_current = 0;
/* First token is the group name (system/user/init) */
group = strtok_r(x, ",", &saveptr);
if (group)
strlcpy(cgroup_current, group, sizeof(cgroup_current));
else
strlcpy(cgroup_current, x, sizeof(cgroup_current));
/* Parse any remaining comma-separated options */
while ((token = strtok_r(NULL, ",", &saveptr)) != NULL) {
if (strncmp(token, "name:", 5) == 0) {
/* Cgroup leaf name override - only valid in per-service directives */
warnx("cgroup %s: name: option not valid in global directive!", group);
} else if (strcmp(token, "delegate") == 0) {
cgroup_delegate_current = 1;
} else {
/* Other settings (cpu.weight:500, memory.max:1G, etc.) */
if (settings[0])
strlcat(settings, ",", sizeof(settings));
strlcat(settings, token, sizeof(settings));
}
}
if (settings[0])
strlcpy(cgroup_settings_current, settings, sizeof(cgroup_settings_current));
return 0;
}
+3
View File
@@ -37,6 +37,7 @@ extern int single;
extern int bootstrap;
extern int kerndebug;
extern int syncsec;
extern int wdtreboot;
extern int readiness;
extern char *fstab;
extern char *sdown;
@@ -50,6 +51,8 @@ extern int logfile_count_max;
extern struct rlimit global_rlimit[];
extern char cgroup_current[];
extern char cgroup_settings_current[];
extern int cgroup_delegate_current;
int str2rlim(char *str);
char *rlim2str(int rlim);
+10
View File
@@ -212,6 +212,11 @@ static int fsck(int pass)
if (mnt->mnt_passno == 0 || mnt->mnt_passno != pass)
continue;
if (hasmntopt(mnt, "noauto")) {
dbg("Skipping fsck of %s, marked noauto", mnt->mnt_fsname);
continue;
}
/* Device to maybe fsck, */
dev = mnt->mnt_fsname;
@@ -425,6 +430,11 @@ static void fs_swapon(char *cmd, size_t len)
if (strcmp(mnt->mnt_type, MNTTYPE_SWAP))
continue;
if (hasmntopt(mnt, "noauto")) {
dbg("Skipping swap %s, marked noauto", mnt->mnt_fsname);
continue;
}
snprintf(cmd, len, "swapon %s", mnt->mnt_fsname);
run_interactive(cmd, "Enabling swap %s", mnt->mnt_fsname);
}
+4 -1
View File
@@ -89,6 +89,9 @@ void console_init(void)
/* Reset attributes, background and foreground color */
dprint(STDOUT_FILENO, "\033[49m\033[39m\e[2J", 14);
/* Hide cursor while we boot up. */
dprint(STDOUT_FILENO, "\033[?25l", 6);
log_init();
}
@@ -463,7 +466,7 @@ void networking(int updown)
_exit(EX_OSERR);
_exit(WEXITSTATUS(rc));
}
cgroup_service("network", pid, NULL);
cgroup_service("network", pid, NULL, NULL, NULL);
print(pid > 0 ? 0 : 1, "%s network interfaces ...",
updown ? "Bringing up" : "Taking down");
+122 -8
View File
@@ -33,11 +33,6 @@
#include <time.h>
#include <utmp.h>
#include <arpa/inet.h>
#ifdef _LIBITE_LITE
# include <libite/lite.h>
#else
# include <lite/lite.h>
#endif
#include "initctl.h"
#include "client.h"
@@ -45,7 +40,6 @@
#include "serv.h"
#include "service.h"
#include "cgutil.h"
#include "util.h"
#include "utmp-api.h"
struct cmd {
@@ -124,7 +118,7 @@ void print_header(const char *fmt, ...)
fputc('=', stdout);
fputs("\n", stdout);
} else {
char buf[ttcols];
char buf[ttcols - 9];
vsnprintf(buf, sizeof(buf), fmt, ap);
printheader(stdout, buf, 0);
@@ -1015,6 +1009,7 @@ static int json_status_one(FILE *fp, svc_t *svc, char *indent, int prev)
indent, svc_typestr(svc),
indent, svc->forking ? "true" : "false",
indent, svc_status(svc));
if (svc->state != SVC_RUNNING_STATE) {
int rc, sig;
@@ -1030,24 +1025,134 @@ static int json_status_one(FILE *fp, svc_t *svc, char *indent, int prev)
"%s \"exit\": { \"%s\": %d },\n",
indent, "signal", sig);
}
fprintf(fp,
"%s \"origin\": \"%s\",\n"
"%s \"command\": \"%s\",\n",
indent, svc->file[0] ? svc->file : "built-in",
indent, svc_command(svc, buf, sizeof(buf), 0));
svc_environ(svc, buf, sizeof(buf), 0);
if (buf[0])
fprintf(fp,
"%s \"environment\": \"%s\",\n", indent, buf);
svc_cond(svc, buf, sizeof(buf), 0);
if (buf[0])
fprintf(fp,
"%s \"condition\": %s,\n", indent, buf);
if (svc->manual)
fprintf(fp,
"%s \"starts\": %d,\n", indent, svc->once);
fprintf(fp,
"%s \"restarts\": %d,\n", indent, svc->restart_tot); /* XXX: add restart_cnt and restart_max */
/* Add memory and CPU information if cgroup support is available */
if (cgrp && svc->pid > 1) {
char *group = pid_cgroup(svc->pid);
if (group) {
uint64_t throttled_usec = 0;
uint64_t nr_throttled = 0;
char path[256];
struct cg *cg;
fprintf(fp, "%s \"cgroup\": \"%s\",\n", indent, group);
fprintf(fp, "%s \"memory\": %lu,\n", indent, cgroup_memory(group));
if (cgroup_throttle(group, &throttled_usec, &nr_throttled) == 0) {
fprintf(fp, "%s \"cpu\": {\n", indent);
fprintf(fp, "%s \"throttled_usec\": %lu,\n", indent, throttled_usec);
fprintf(fp, "%s \"nr_throttled\": %lu\n", indent, nr_throttled);
fprintf(fp, "%s },\n", indent);
}
/* Add limits structure using existing cg_conf() */
snprintf(path, sizeof(path), "%s/%s", FINIT_CGPATH, group);
cg = cg_conf(path);
if (cg && (cg->cg_mem.min[0] || cg->cg_mem.max[0] ||
cg->cg_cpu.weight[0] || cg->cg_cpu.max[0])) {
int has_memory = cg->cg_mem.min[0] || cg->cg_mem.max[0];
int has_cpu = cg->cg_cpu.weight[0] || cg->cg_cpu.max[0];
fprintf(fp, "%s \"limits\": {\n", indent);
if (has_memory) {
fprintf(fp, "%s \"memory\": {\n", indent);
if (cg->cg_mem.min[0]) {
if (!strcmp(cg->cg_mem.min, "max"))
fprintf(fp, "%s \"min\": \"%s\"", indent, cg->cg_mem.min);
else
fprintf(fp, "%s \"min\": %s", indent, cg->cg_mem.min);
if (cg->cg_mem.max[0])
fprintf(fp, ",\n");
else
fprintf(fp, "\n");
}
if (cg->cg_mem.max[0]) {
if (!strcmp(cg->cg_mem.max, "max"))
fprintf(fp, "%s \"max\": \"%s\"\n", indent, cg->cg_mem.max);
else
fprintf(fp, "%s \"max\": %s\n", indent, cg->cg_mem.max);
}
fprintf(fp, "%s }", indent);
if (has_cpu)
fprintf(fp, ",\n");
else
fprintf(fp, "\n");
}
if (has_cpu) {
char cpu_burst[32];
char *quota, *period;
char max_copy[32];
fprintf(fp, "%s \"cpu\": {\n", indent);
if (cg->cg_cpu.weight[0])
fprintf(fp, "%s \"weight\": %s,\n", indent, cg->cg_cpu.weight);
if (cg->cg_cpu.max[0]) {
/* Split cpu.max into quota and period */
strlcpy(max_copy, cg->cg_cpu.max, sizeof(max_copy));
quota = strtok(max_copy, " ");
period = strtok(NULL, " ");
fprintf(fp, "%s \"max\": {\n", indent);
if (quota) {
if (!strcmp(quota, "max"))
fprintf(fp, "%s \"quota\": \"%s\"", indent, quota);
else
fprintf(fp, "%s \"quota\": %s", indent, quota);
if (period)
fprintf(fp, ",\n");
else
fprintf(fp, "\n");
}
if (period)
fprintf(fp, "%s \"period\": %s\n", indent, period);
fprintf(fp, "%s }", indent);
/* Check for cpu.max.burst (kernel >= 5.14) */
if (cgroup_val(path, "cpu.max.burst", cpu_burst, sizeof(cpu_burst)))
fprintf(fp, ",\n%s \"max_burst\": %s\n", indent, cpu_burst);
else
fprintf(fp, "\n");
}
fprintf(fp, "%s }\n", indent);
}
fprintf(fp, "%s },\n", indent);
}
free(group);
}
}
fprintf(fp,
"%s \"pidfile\": \"%s\",\n"
"%s \"pid\": %d,\n"
@@ -1134,8 +1239,11 @@ static int show_status(char *arg)
printf(" Starts : %d\n", svc->once);
printf(" Restarts : %d (%d/%d)\n", svc->restart_tot, svc->restart_cnt, svc->restart_max);
printf(" Runlevels : %s\n", runlevel_string(runlevel, svc->runlevels));
if (cgrp && svc->pid > 1) {
const struct cg *cg;
uint64_t throttled_usec = 0;
uint64_t nr_throttled = 0;
char path[256];
char *group;
@@ -1147,6 +1255,12 @@ static int show_status(char *arg)
cg = cg_conf(path);
printf(" Memory : %s\n", memsz(cgroup_memory(group), uptm, sizeof(uptm)));
if (cgroup_throttle(group, &throttled_usec, &nr_throttled) == 0) {
printf("CPU Throttle : %lu usec (%lu times)\n",
throttled_usec, nr_throttled);
}
printf(" CGroup : %s cpu %s [%s, %s] mem [%s, %s]\n",
group, cg->cg_cpu.set, cg->cg_cpu.weight, cg->cg_cpu.max,
cg->cg_mem.min, cg->cg_mem.max);
@@ -1610,7 +1724,7 @@ int main(int argc, char *argv[])
}
if (interactive)
ttinit();
ttinit(0);
return cmd_parse(argc - optind, &argv[optind], command);
}
+1 -1
View File
@@ -95,7 +95,7 @@ static void run_env(const char *env[])
int run_parts(char *dir, char *cmd, const char *env[], int progress, int sysv)
{
size_t cmdlen = cmd ? strlen(cmd) : strlen("start");
struct dirent **d;
struct dirent **d = NULL;
int i, num;
int rc = 0;
+197 -84
View File
@@ -39,8 +39,12 @@
# include <lite/lite.h>
#endif
#include <wordexp.h>
#ifdef HAVE_LIBCAP
# include <sys/capability.h>
#endif
#include "cgroup.h"
#include "clone3.h"
#include "client.h"
#include "conf.h"
#include "cond.h"
@@ -88,7 +92,7 @@ static void service_timeout_cb(uev_t *w, void *arg, int events)
svc_t *svc = arg;
if (UEV_ERROR == events) {
dbg("%s(): spurious problem, svc %s.", __func__, svc_ident(svc, NULL, 0));
dbg("%s: spurious problem", svc_ident(svc, NULL, 0));
uev_timer_start(w);
return;
}
@@ -271,23 +275,18 @@ static int lredirect(svc_t *svc)
{
static int have_sysklogd = -1;
pid_t svc_pid = getpid();
int pipefd[2];
pid_t pid;
int fd;
/*
* Open PTY to connect to logger. A pty isn't buffered
* like a pipe, and it eats newlines so they aren't logged
* Use a pipe to connect to logger. This ensures isatty()
* returns false for the service, preventing programs from
* emitting ANSI escape codes and other TTY-specific output.
*/
fd = posix_openpt(O_RDWR);
if (fd == -1) {
dbg("Failed posix_openpt(), errno %d: %s", errno, strerror(errno));
svc->log.enabled = 0;
return -1;
}
if (grantpt(fd) == -1 || unlockpt(fd) == -1) {
dbg("Failed grantpt()|unlockpt(), errno %d: %s", errno, strerror(errno));
close(fd);
if (pipe(pipefd) == -1) {
dbg("Failed pipe(), errno %d: %s", errno, strerror(errno));
svc->log.enabled = 0;
return -1;
}
@@ -319,17 +318,12 @@ static int lredirect(svc_t *svc)
char *prio = "daemon.info";
char buf[MAX_IDENT_LEN];
char *tag;
int fds;
sched_yield();
fds = open(ptsname(fd), O_RDONLY);
close(fd);
if (fds == -1) {
logit(LOG_WARNING, "failed open() ptsname(%d), errno %d", fd, errno);
_exit(0);
}
dup2(fds, STDIN_FILENO);
close(pipefd[1]); /* Close write end in logger */
dup2(pipefd[0], STDIN_FILENO);
close(pipefd[0]);
/* Reset signals */
sig_unblock();
@@ -385,10 +379,11 @@ static int lredirect(svc_t *svc)
_exit(1);
}
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
close(pipefd[0]); /* Close read end in service */
dup2(pipefd[1], STDOUT_FILENO);
dup2(pipefd[1], STDERR_FILENO);
return close(fd);
return close(pipefd[1]);
}
/*
@@ -483,30 +478,6 @@ static int is_norespawn(void)
fexist("/tmp/norespawn");
}
/* used for process group name, derived from originating filename,
* so to group multiple services, place them in the same .conf
*/
static char *group_name(svc_t *svc, char *buf, size_t len)
{
char *ptr;
if (!svc->file[0])
return svc_ident(svc, buf, len);
ptr = strrchr(svc->file, '/');
if (ptr)
ptr++;
else
ptr = svc->file;
strlcpy(buf, ptr, len);
ptr = strstr(buf, ".conf");
if (ptr)
*ptr = 0;
return buf;
}
static void compose_cmdline(svc_t *svc, char *buf, size_t len)
{
size_t i;
@@ -521,11 +492,55 @@ static void compose_cmdline(svc_t *svc, char *buf, size_t len)
}
}
static void set_uid(uid_t uid, svc_t *svc)
{
#ifdef HAVE_LIBCAP
if (cap_setuid(uid)) {
err(1, "%s: failed cap_setuid(%d)", svc_ident(svc, NULL, 0), uid);
return;
}
/* After dropping privileges, set the specific capabilities we need */
if (svc->capabilities[0]) {
cap_iab_t cap_iab = cap_iab_from_text(svc->capabilities);
if (!cap_iab) {
err(1, "%s: failed parsing capabilities '%s'",
svc_ident(svc, NULL, 0), svc->capabilities);
return;
}
if (cap_iab_set_proc(cap_iab) != 0) {
cap_free(cap_iab);
err(1, "%s: failed setting capabilities",
svc_ident(svc, NULL, 0));
}
cap_free(cap_iab);
}
#else
if (setuid(uid))
err(1, "%s: failed setuid(%d)", svc_ident(svc, NULL, 0), uid);
#endif
}
static pid_t service_fork(svc_t *svc)
{
const char *cgnm;
char grnam[128];
int cgfd = -1;
pid_t pid;
pid = fork();
cgnm = cgroup_svc_name(svc, grnam, sizeof(grnam));
cgfd = cgroup_prepare(svc, cgnm);
pid = call_clone3(0, cgfd);
if (cgfd >= 0)
close(cgfd);
if (pid < 0) {
cgroup_del_svc(svc, cgnm);
return pid;
}
if (pid == 0) {
char *home = NULL;
#ifdef ENABLE_STATIC
@@ -552,8 +567,7 @@ static pid_t service_fork(svc_t *svc)
}
if (uid >= 0) {
if (setuid(uid))
err(1, "%s: failed setuid(%d)", svc_ident(svc, NULL, 0), uid);
set_uid(uid, svc);
/* Set default path for regular users */
if (uid > 0)
@@ -571,13 +585,11 @@ static pid_t service_fork(svc_t *svc)
source_env(svc);
}
if (pid > 1) {
char grnam[80];
if (pid > 0 && !has_clone3()) {
if (svc_is_tty(svc))
cgroup_user("getty", pid);
else
cgroup_service(group_name(svc, grnam, sizeof(grnam)), pid, &svc->cgroup);
cgroup_service(cgnm, pid, &svc->cgroup, svc->username, svc->group);
}
return pid;
@@ -735,7 +747,7 @@ static int service_start(svc_t *svc)
}
} else { /* pid == 0 */
char str[strlen(NOTIFY_PATH) + 32];
char *args[MAX_NUM_SVC_ARGS + 1];
char *args[MAX_NUM_SVC_ARGS + 3];
int status;
if (!svc_is_tty(svc))
@@ -1134,12 +1146,12 @@ int service_stop(svc_t *svc)
svc_set_state(svc, SVC_HALTED_STATE);
}
} else {
char *args[MAX_NUM_SVC_ARGS + 1];
char *args[MAX_NUM_SVC_ARGS + 3];
size_t i = 0, j;
args[i++] = svc->cmd;
/* this handles, e.g., bridge-stop br0 stop */
for (j = 0; j < MAX_NUM_SVC_ARGS - 2; j++) {
for (j = 0; j < MAX_NUM_SVC_ARGS; j++) {
if (!strlen(svc->args[j]))
break;
args[i++] = svc->args[j];
@@ -1147,10 +1159,11 @@ int service_stop(svc_t *svc)
args[i++] = "stop";
args[i] = NULL;
switch (fork()) {
switch (service_fork(svc)) {
case 0:
setsid();
redirect(svc);
setsid();
sig_unblock();
exec_runtask(args[0], &args[1]);
_exit(0);
break;
@@ -1372,36 +1385,68 @@ static void parse_env(svc_t *svc, char *env)
}
/*
* the @cgroup argument can be, e.g., .system:mem.max:1234 or just the
* the @cgroup argument can be, e.g., .system,mem.max:1234 or just the
* default group with some cfg, e.g., :mem.max:1234 as a side effect,
* cgroupinit also work, selecting group init.
*/
static void parse_cgroup(svc_t *svc, char *cgroup)
{
char *ptr = NULL;
if (!cgroup)
return;
/* Per-service cgroup directive completely overrides global settings */
svc->cgroup.delegate = 0;
svc->cgroup.leafname[0] = 0;
svc->cgroup.cfg[0] = 0;
/* Detect syntax: old colon-based vs new comma-separated */
if (cgroup[0] == '.') {
ptr = strchr(cgroup, ':');
if (ptr)
*ptr++ = 0;
group:
strlcpy(svc->cgroup.name, &cgroup[1], sizeof(svc->cgroup.name));
if (!ptr)
return;
char *token, *ptr, *group;
char settings[128] = {0};
char cgcopy[256];
/* New comma-separated syntax: cgroup.system,name:udevd,delegate,cpu.max:10000 */
strlcpy(cgcopy, cgroup, sizeof(cgcopy));
group = strtok_r(cgcopy, ",", &ptr);
if (group && group[0] == '.')
strlcpy(svc->cgroup.name, &group[1], sizeof(svc->cgroup.name));
/* Parse remaining comma-separated options */
while ((token = strtok_r(NULL, ",", &ptr)) != NULL) {
if (strncmp(token, "name:", 5) == 0) {
/* Cgroup leaf name override */
strlcpy(svc->cgroup.leafname, token + 5, sizeof(svc->cgroup.leafname));
} else if (strcmp(token, "delegate") == 0) {
svc->cgroup.delegate = 1;
} else {
/* Other settings (cpu.weight:500, memory.max:1G, etc.) */
if (settings[0])
strlcat(settings, ",", sizeof(settings));
strlcat(settings, token, sizeof(settings));
}
}
if (settings[0])
strlcpy(svc->cgroup.cfg, settings, sizeof(svc->cgroup.cfg));
dbg("%s: cgroup name:%s leafname:%s cfg:%s delegate:%s", svc_ident(svc, NULL, 0),
svc->cgroup.name, svc->cgroup.leafname, svc->cgroup.cfg,
svc->cgroup.delegate ? "yes" : "no");
} else if (cgroup[0] == ':') {
char *ptr;
/* Old syntax: cgroup:settings (keeps current group) */
ptr = &cgroup[1];
} else
goto group;
if (strlen(ptr) >= sizeof(svc->cgroup)) {
errx(1, "%s: cgroup settings too long (>%zu chars)", svc_ident(svc, NULL, 0), sizeof(svc->cgroup));
return;
if (strlen(ptr) >= sizeof(svc->cgroup.cfg)) {
errx(1, "%s: cgroup settings too long (>%zu chars)", svc_ident(svc, NULL, 0),
sizeof(svc->cgroup.cfg));
return;
}
strlcpy(svc->cgroup.cfg, ptr, sizeof(svc->cgroup.cfg));
} else {
/* Just a group name without dot or colon */
strlcpy(svc->cgroup.name, cgroup, sizeof(svc->cgroup.name));
}
strlcpy(svc->cgroup.cfg, ptr, sizeof(svc->cgroup.cfg));
}
static void parse_sighalt(svc_t *svc, char *arg)
@@ -1420,9 +1465,9 @@ static void parse_killdelay(svc_t *svc, char *delay)
const char *errstr;
long long sec;
sec = strtonum(delay, 1, 60, &errstr);
sec = strtonum(delay, 1, 300, &errstr);
if (errstr) {
errx(1, "%s: killdelay %s is %s (1-60)", svc_ident(svc, NULL, 0), delay, errstr);
errx(1, "%s: killdelay %s is %s (1-300)", svc_ident(svc, NULL, 0), delay, errstr);
return;
}
@@ -1464,7 +1509,7 @@ static void parse_script(svc_t *svc, char *type, char *script, int *tmo, char *b
found = which(path);
if (!found) {
logit(LOG_WARNING, "%s:%s is missing or not executable, skipping.", type, path);
logit(LOG_WARNING, "%s:'%s' is missing or not executable, skipping.", type, path);
goto err;
}
free(found);
@@ -1665,6 +1710,7 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
char *ready_script = NULL, *conflict = NULL;
char *reload_script = NULL, *stop_script = NULL;
char *cleanup_script = NULL;
char *caps = NULL;
char ident[MAX_IDENT_LEN];
char *ifstmt = NULL;
char *notify = NULL;
@@ -1780,6 +1826,8 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
stop_script = arg;
else if (MATCH_CMD(cmd, "env:", arg))
env = arg;
else if (MATCH_CMD(cmd, "caps:", arg))
caps = arg;
/* catch both cgroup: and cgroup. handled in parse_cgroup() */
else if (MATCH_CMD(cmd, "cgroup", arg))
cgroup = arg;
@@ -2003,6 +2051,10 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
parse_env(svc, env);
else
memset(svc->env, 0, sizeof(svc->env));
if (caps)
strlcpy(svc->capabilities, caps, sizeof(svc->capabilities));
else
memset(svc->capabilities, 0, sizeof(svc->capabilities));
if (file)
strlcpy(svc->file, file, sizeof(svc->file));
else
@@ -2054,6 +2106,14 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
/* Seed with currently active group, may be empty */
strlcpy(svc->cgroup.name, cgroup_current, sizeof(svc->cgroup.name));
/* Apply cgroup settings if specified */
if (cgroup_settings_current[0])
strlcpy(svc->cgroup.cfg, cgroup_settings_current, sizeof(svc->cgroup.cfg));
/* Apply delegation flag */
svc->cgroup.delegate = cgroup_delegate_current;
if (cgroup)
parse_cgroup(svc, cgroup);
@@ -2097,6 +2157,7 @@ void service_unregister(svc_t *svc)
return;
service_stop(svc);
service_timeout_cancel(svc);
for (c = strtok(svc->cond, ","); c; c = strtok(NULL, ","))
devmon_del_cond(c);
@@ -2136,6 +2197,10 @@ void service_monitor(pid_t lost, int status)
: (svc->state == SVC_CLEANUP_STATE
? svc->cleanup_script
: svc->pre_script)), lost, ok, sig, rc);
/* Prevent: spurious problem from timeout callback */
service_timeout_cancel(svc);
/* Kill all children in the same proess group, e.g. logit */
dbg("Killing lingering children in same process group ...");
kill(-svc->pid, SIGKILL);
@@ -2467,6 +2532,10 @@ static void service_retry(svc_t *svc)
int timeout;
service_timeout_cancel(svc);
if (is_norespawn())
return;
if (svc->respawn) {
dbg("%s crashed/exited, respawning ...", svc_ident(svc, NULL, 0));
svc_unblock(svc);
@@ -2518,8 +2587,13 @@ static void service_retry(svc_t *svc)
timeout = ((*restart_cnt) <= (svc->restart_max / 2)) ? 2000 : 5000;
/* If a longer timeout was specified in the conf, use that instead. */
svc->restart_tmo = max(svc->restart_tmo, timeout);
logit(LOG_CONSOLE|LOG_WARNING, "Service %s[%d] died, restarting (retry in %d msec) (attempt: %d/%d)",
svc_ident(svc, NULL, 0), svc->oldpid, svc->restart_tmo, *restart_cnt, svc->restart_max);
logit(LOG_CONSOLE|LOG_WARNING, "Service %s[%d] died (%s%d), restarting (retry in %d msec) (attempt: %d/%d)",
svc_ident(svc, NULL, 0), svc->oldpid,
WIFEXITED(svc->status) ? "with exit status: " : "by signal: ",
WIFEXITED(svc->status) ? WEXITSTATUS(svc->status) : WTERMSIG(svc->status),
svc->restart_tmo,
*restart_cnt,
svc->restart_max);
svc_unblock(svc);
service_step(svc);
@@ -2771,6 +2845,9 @@ restart:
if (sm_in_reload())
break;
if (is_norespawn())
break;
/* Don't start if it conflicts with something else already started */
if (svc_conflicts(svc)) {
logit(svc->nowarn ? LOG_DEBUG : LOG_INFO,
@@ -3035,6 +3112,42 @@ int service_completed(svc_t **svcp)
return 1;
}
/*
* service_log_incomplete - Log all incomplete run/task in current runlevel
*
* Iterates through all run/task services and logs those that have not
* yet completed. Useful for diagnosing bootstrap delays or reporting
* what was skipped when user interrupts bootstrap wait.
*/
void service_log_incomplete(void)
{
svc_t *svc, *iter = NULL;
int count = 0;
for (svc = svc_iterator(&iter, 1); svc; svc = svc_iterator(&iter, 0)) {
if (!svc_is_runtask(svc))
continue;
if (!svc_enabled(svc))
continue;
if (svc_conflicts(svc))
continue;
if (strstr(svc->cond, plugin_hook_str(HOOK_SVC_UP)) ||
strstr(svc->cond, plugin_hook_str(HOOK_SYSTEM_UP)))
continue;
if (!svc->once) {
logit(LOG_WARNING, "Incomplete: %s", svc_ident(svc, NULL, 0));
count++;
}
}
if (count == 0)
logit(LOG_WARNING, "No incomplete run/task services found");
}
/*
* Called in SM_RELOAD_WAIT_STATE to update unmodified non-native
* services' READY condition to the current generation. Similar
@@ -3141,7 +3254,7 @@ static void service_interval_cb(uev_t *w, void *arg, int events)
(void)arg;
if (UEV_ERROR == events) {
dbg("%s(): spurious problem, restarting.", __func__);
dbg("spurious problem, restarting.");
uev_timer_start(w);
return;
}
+1
View File
@@ -48,6 +48,7 @@ void service_step_all (int types);
void service_worker (void *unused);
int service_completed (svc_t **svc);
void service_log_incomplete (void);
void service_notify_reconf (void);
void service_init (uev_ctx_t *ctx);
+9 -4
View File
@@ -187,6 +187,11 @@ static void fs_swapoff(void)
if (strcmp(mnt->mnt_type, MNTTYPE_SWAP))
continue;
if (hasmntopt(mnt, "noauto")) {
dbg("Skipping swap %s, marked noauto", mnt->mnt_fsname);
continue;
}
snprintf(cmd, sizeof(cmd), "swapoff %s", mnt->mnt_fsname);
run_interactive(cmd, "Disabling swap %s", mnt->mnt_fsname);
}
@@ -353,7 +358,7 @@ void do_shutdown(shutop_t op)
return;
}
if (wdog) {
if (wdtreboot && wdog) {
print(kill(wdog->pid, SIGPWR) == 1, "Advising watchdog, system going down");
do_sleep(2);
}
@@ -400,17 +405,17 @@ void do_shutdown(shutop_t op)
/* Reboot via watchdog or kernel, or shutdown? */
if (op == SHUT_REBOOT) {
print(0, "Rebooting ...");
if (wdog && wdog->pid > 1) {
if (wdtreboot && wdog && wdog->pid > 1) {
int timeout = 10;
/* Wait here until the WDT reboots, or timeout with fallback */
print(0, "Rebooting using WDT, please wait ...");
print(kill(wdog->pid, SIGTERM) == 1, "Pending watchdog reboot");
while (timeout--)
do_sleep(1);
}
dbg("Rebooting ...");
print(0, "Rebooting ...");
reboot(RB_AUTOBOOT);
} else if (op == SHUT_OFF) {
print(0, "Powering down ...");
+104 -4
View File
@@ -23,10 +23,13 @@
#include "config.h" /* Generated by configure script */
#include <fcntl.h>
#include <paths.h>
#include <sys/types.h>
#include <termios.h>
#include "finit.h"
#include "cgroup.h"
#include "cond.h"
#include "conf.h"
#include "log.h"
@@ -56,8 +59,80 @@ static struct {
int newlevel; /* Set to new runlevel on change, -1 otherwise */
int reload; /* Set on reload event, else 0 */
int in_reload; /* Set when waiting for all processes to be halted */
int skip_bootstrap; /* Set to skip bootstrap waiting */
} sm;
static uev_t console_watcher;
static int console_fd = -1;
/*
* Console input callback - handles Ctrl-C during bootstrap wait
*/
static void console_input_cb(uev_t *w, void *arg, int events)
{
ssize_t len;
char ch;
(void)arg;
if (UEV_ERROR == events) {
warnx("Error in console watcher");
return;
}
/* Read and check for Ctrl-C (ASCII 3) */
len = read(w->fd, &ch, 1);
if (len == 1 && ch == 3) { /* Ctrl-C */
sm.skip_bootstrap = 1;
print(2, "Bootstrap wait interrupted by Ctrl-C, resuming ...");
}
}
/*
* Stop and cleanup the console watcher
*/
static void console_watcher_stop(void)
{
if (console_fd >= 0) {
uev_io_stop(&console_watcher);
close(console_fd);
console_fd = -1;
}
}
/*
* Setup console watcher for Ctrl-C during bootstrap
*/
static void console_watcher_start(uev_ctx_t *ctx)
{
struct termios tio;
/* Open console in non-blocking mode */
console_fd = open(console(), O_RDONLY | O_NONBLOCK);
if (console_fd < 0) {
dbg("Failed opening console for input monitoring: %s", strerror(errno));
return;
}
/* Get current terminal settings */
if (tcgetattr(console_fd, &tio) == 0) {
/* Put terminal in raw mode to catch Ctrl-C */
tio.c_lflag &= ~(ICANON | ECHO | ISIG);
tio.c_cc[VMIN] = 0;
tio.c_cc[VTIME] = 0;
tcsetattr(console_fd, TCSANOW, &tio);
}
/* Register I/O watcher */
if (uev_io_init(ctx, &console_watcher, console_input_cb, NULL, console_fd, UEV_READ)) {
dbg("Failed setting up console watcher");
close(console_fd);
console_fd = -1;
} else {
dbg("Console watcher active, press Ctrl-C to skip bootstrap wait");
}
}
/*
* Wait for system bootstrap to complete, all SVC_TYPE_RUNTASK must be
@@ -76,13 +151,19 @@ static void sm_check_bootstrap(void *work)
service_step_all(SVC_TYPE_ANY);
bootstrap_done = service_completed(&svc);
if (timeout-- > 0 && !bootstrap_done) {
if (timeout-- > 0 && !bootstrap_done && !sm.skip_bootstrap) {
dbg("Not all bootstrap run/tasks have completed yet ... %d", timeout);
schedule_work(work);
return;
}
if (timeout > 0) {
/* Stop console watcher before proceeding */
console_watcher_stop();
if (sm.skip_bootstrap) {
logit(LOG_WARNING, "Ctrl-C received, skipped waiting for the following:");
service_log_incomplete();
} else if (timeout > 0) {
dbg("All run/task have completed, resuming bootstrap.");
} else {
dbg("Timeout, resuming bootstrap.");
@@ -111,6 +192,10 @@ static void sm_check_bootstrap(void *work)
dbg("Clean up all bootstrap-only tasks/services ...");
svc_prune_bootstrap();
/* Clean up empty cgroups from bootstrap tasks (e.g., udevadm, coldplug) */
dbg("Pruning empty cgroups from bootstrap ...");
cgroup_prune();
/* All services/tasks/etc. in configure runlevel have started */
dbg("Running svc up hooks ...");
plugin_run_hooks(HOOK_SVC_UP);
@@ -184,6 +269,7 @@ void sm_init(void)
sm.newlevel = -1;
sm.reload = 0;
sm.in_reload = 0;
sm.skip_bootstrap = 0;
dbg("Starting bootstrap finalize timer ...");
schedule_work(&work);
@@ -206,6 +292,7 @@ restart:
service_step_all(SVC_TYPE_RUNTASK | SVC_TYPE_SERVICE | SVC_TYPE_SYSV);
sm.state = SM_BOOTSTRAP_WAIT_STATE;
console_watcher_start(ctx);
break;
/*
@@ -229,6 +316,9 @@ restart:
if (sm.newlevel == -1)
break;
/* Ensure console watcher is stopped before proceeding */
console_watcher_stop();
/* Hooks that should run at the very end */
dbg("Calling all system up hooks ...");
plugin_run_hooks(HOOK_SYSTEM_UP);
@@ -271,6 +361,9 @@ restart:
/* Restore terse mode and run hooks before shutdown */
if (runlevel == 0 || runlevel == 6) {
/* Hide cursor, we're going down ... */
dprint(STDOUT_FILENO, "\033[?25l", 14);
api_exit();
log_exit();
plugin_run_hooks(HOOK_SHUTDOWN);
@@ -317,6 +410,10 @@ restart:
dbg("All services have been stopped, calling runlevel change hooks ...");
plugin_run_hooks(HOOK_RUNLEVEL_CHANGE); /* Reconfigure HW/VLANs/etc here */
/* Clean up empty cgroups from previous runlevel */
dbg("Pruning empty cgroups from previous runlevel ...");
cgroup_prune();
dbg("Starting services new to this runlevel ...");
sm.in_reload = 0;
service_step_all(SVC_TYPE_ANY);
@@ -359,6 +456,8 @@ restart:
break;
case SM_RELOAD_CHANGE_STATE:
sm.in_reload = 1;
/* First reload all *.conf in /etc/finit.d/ */
conf_reload();
@@ -367,10 +466,11 @@ restart:
* let all affected services move to WAITING/HALTED
*/
dbg("Stopping services not allowed after reconf ...");
sm.in_reload = 1;
cond_reload();
service_step_all(SVC_TYPE_ANY);
/* Step the generation ... */
cond_reload();
sm.state = SM_RELOAD_WAIT_STATE;
break;
+12 -5
View File
@@ -128,11 +128,18 @@ void stty(int fd, speed_t speed)
term.c_cc[VERASE] = CERASE;
tcsetattr(fd, TCSAFLUSH, &term);
/* Show cursor again, if it was hidden previously */
dprint(fd, "\033[?25h", 6);
/* Enable line wrap, if disabled previously */
dprint(fd, "\033[?7h", 5);
/* Reset terminal to sane state - disable modes that break scrollback */
dprint(fd, "\033[m", 3); /* Reset SGR attributes */
dprint(fd, "\033[?1000l", 8); /* Disable X11 mouse tracking */
dprint(fd, "\033[?1002l", 8); /* Disable button event tracking */
dprint(fd, "\033[?1003l", 8); /* Disable all mouse tracking */
dprint(fd, "\033[?1006l", 8); /* Disable SGR mouse mode */
dprint(fd, "\033[?1015l", 8); /* Disable urxvt mouse mode */
// dprint(fd, "\033[?1049l", 8); /* Exit alternate screen buffer */
dprint(fd, "\033[?1l", 6); /* Exit application cursor keys mode */
dprint(fd, "\033[?2004l", 8); /* Disable bracketed paste mode */
dprint(fd, "\033[?25h", 6); /* Show cursor */
dprint(fd, "\033[?7h", 5); /* Enable line wrap */
}
/**
+2 -1
View File
@@ -94,7 +94,7 @@ typedef enum {
SVC_NOTIFY_S6,
} svc_notify_t;
#define MAX_ID_LEN 16
#define MAX_ID_LEN 65
#define MAX_ARG_LEN 64
#define MAX_CMD_LEN 256
#define MAX_IDENT_LEN (MAX_ARG_LEN + MAX_ID_LEN + 1)
@@ -191,6 +191,7 @@ typedef struct svc {
/* Identity */
char username[MAX_USER_LEN];
char group[MAX_USER_LEN];
char capabilities[MAX_CMD_LEN];
/* Command, arguments and service description */
char cmd[MAX_CMD_LEN];
+1 -1
View File
@@ -50,7 +50,7 @@ int r_flag = 0;
static int is_dir_empty(const char *path)
{
struct dirent **namelist;
struct dirent **namelist = NULL;
int num;
num = scandir(path, &namelist, NULL, NULL);
+6 -3
View File
@@ -180,7 +180,7 @@ char *str(char *fmt, ...)
return buf;
}
int getuser(char *username, char **home)
int getuser(const char *username, char **home)
{
#ifdef ENABLE_STATIC
if (home)
@@ -198,7 +198,7 @@ int getuser(char *username, char **home)
#endif
}
int getgroup(char *group)
int getgroup(const char *group)
{
#ifdef ENABLE_STATIC
return fgetint("/etc/group", "x:\n", group);
@@ -687,7 +687,7 @@ int fistmpfs(char *dir)
* Called by initctl, and by finit at boot and shutdown, to
* (re)initialize the screen size for print() et al.
*/
int ttinit(void)
int ttinit(int probe_size)
{
struct pollfd fd = { STDIN_FILENO, POLLIN, 0 };
struct winsize ws = { 0 };
@@ -707,6 +707,9 @@ int ttinit(void)
tcsetattr(STDERR_FILENO, TCSANOW, &tc);
}
if (probe_size)
goto fallback;
/* 1. Try TIOCWINSZ to query window size from kernel */
if (!ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws)) {
ttrows = ws.ws_row;
+4 -4
View File
@@ -62,8 +62,8 @@ const char *basenm (const char *path);
char *str (char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
int getuser (char *username, char **home);
int getgroup (char *group);
int getuser (const char *username, char **home);
int getgroup (const char *group);
int getcuser (char *buf, size_t len);
int getcgroup (char *buf, size_t len);
@@ -95,11 +95,11 @@ int fismnt (char *dir);
int fistmpfs (char *dir);
#ifdef HAVE_TERMIOS_H
int ttinit (void);
int ttinit (int probe_size);
int ttraw (void);
int ttcooked (void);
#else
#define ttinit() ttcols
#define ttinit(v) ttcols
#define ttraw() 0
#define ttcooked() 0
#endif
+10 -10
View File
@@ -18,33 +18,33 @@
# change the contents any way you like, it can even be empty.
# Check for systemd-udevd and eudev, if we find both, we opt for the latter.
service nowarn cgroup.system name:udevd notify:none env:-/etc/default/udevd pid:udevd log \
service nowarn cgroup.system,name:udevd name:udevd notify:none env:-/etc/default/udevd pid:udevd log \
[S12345789] /lib/systemd/systemd-udevd $UDEVD_ARGS -- Device event daemon (udev)
service nowarn cgroup.system name:udevd notify:none env:-/etc/default/udevd pid:udevd log \
service nowarn cgroup.system,name:udevd name:udevd notify:none env:-/etc/default/udevd pid:udevd log \
[S12345789] udevd $UDEVD_ARGS -- Device event daemon (udev)
# Wait for udevd to start, then trigger coldplug events and module loading.
# The last 'settle' call waits for it to finalize processing all uevents.
run nowarn if:udevd cgroup.init :1 [S] <service/udevd/ready> log udevadm settle -t 0 --
run nowarn if:udevd cgroup.init :2 [S] <service/udevd/ready> log udevadm control --reload --
run nowarn if:udevd cgroup.init :3 [S] <service/udevd/ready> log udevadm trigger -c add -t devices -- Requesting device events
run nowarn if:udevd cgroup.init :4 [S] <service/udevd/ready> log udevadm trigger -c add -t subsystems -- Requesting subsystem events
run nowarn if:udevd cgroup.init :5 [S] <service/udevd/ready> log udevadm settle -t 30 -- Waiting for udev to finish
run nowarn if:udevd cgroup.init,name:udevadm :1 [S] <service/udevd/ready> log udevadm settle -t 0 --
run nowarn if:udevd cgroup.init,name:udevadm :2 [S] <service/udevd/ready> log udevadm control --reload --
run nowarn if:udevd cgroup.init,name:udevadm :3 [S] <service/udevd/ready> log udevadm trigger -c add -t devices -- Requesting device events
run nowarn if:udevd cgroup.init,name:udevadm :4 [S] <service/udevd/ready> log udevadm trigger -c add -t subsystems -- Requesting subsystem events
run nowarn if:udevd cgroup.init,name:udevadm :5 [S] <service/udevd/ready> log udevadm settle -t 30 -- Waiting for udev to finish
# If udevd is not available, check for mdevd. Unlike plain 'mdev -df', the
# mdevd-coldplug process exits when mdevd has completed. So the condition
# <run/coldplug/success> (same as for plain mdev below) can be relied on as a
# barrier for services that, e.g., need to probe for switchcore ports or PHYs
# that require firmware loading before they show up.
service nowarn conflict:udevd,mdev cgroup.init notify:none if:!udevd \
service nowarn conflict:udevd,mdev cgroup.init,name:mdevd notify:none if:!udevd \
[S12345789] notify:s6 mdevd -O 4 -D %n -- Device event daemon (mdevd)
run nowarn conflict:udevd,mdev cgroup.init name:coldplug <service/mdevd/ready> \
run nowarn conflict:udevd,mdev cgroup.init,name:coldplug <service/mdevd/ready> \
[S] mdevd-coldplug -- Cold plugging system
# If neither udevd or mdevd are available, fall back to mdev. The new daemon
# mode calls `mdev -s` internally but does not create a PID file when that is
# done, so synchronization with coldplug is fragile.
service nowarn conflict:udevd,mdevd cgroup.init notify:none if:!udevd,!mdevd \
service nowarn conflict:udevd,mdevd cgroup.init,name:mdev notify:none if:!udevd,!mdevd \
[S12345789] mdev -df -S -- Device event daemon (mdev)
# Trigger a replay of all uevents from boot, before userspace was up and the
+2 -1
View File
@@ -25,7 +25,8 @@ if [ "$run_make" -eq 1 ]; then
fi
./configure --prefix=/usr --exec-prefix= --sysconfdir=/etc --localstatedir=/var \
--enable-x11-common-plugin --with-watchdog --with-keventd \
--enable-x11-common-plugin --enable-testserv-plugin --with-watchdog \
--with-keventd --with-libsystemd \
CFLAGS='-fsanitize=address -ggdb'
if [ "$run_make" -eq 1 ]; then