mirror of
https://github.com/troglobit/finit.git
synced 2026-10-12 10:12:32 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
702a606d26 | ||
|
|
a3d9b6e9b1 | ||
|
|
390a0f48c1 | ||
|
|
f4a13687e8 | ||
|
|
43ca51c3b1 | ||
|
|
864b71af14 | ||
|
|
44a928e5e6 | ||
|
|
f513348963 | ||
|
|
820b9a77c1 | ||
|
|
85baafe99c | ||
|
|
e635ea882a | ||
|
|
f908c8d43a | ||
|
|
308f75b209 | ||
|
|
d87d298c85 | ||
|
|
ae7931f68e | ||
|
|
8422d2715c | ||
|
|
2eefaa5db6 | ||
|
|
fb0ad18d3a | ||
|
|
2681444b07 | ||
|
|
dd36116a97 | ||
|
|
e756d6f8ac | ||
|
|
567c695954 | ||
|
|
4877aecc1e | ||
|
|
65644f5956 | ||
|
|
a02760f499 | ||
|
|
7aab3bb421 | ||
|
|
b5beb2b8d4 | ||
|
|
09b4aee5bc | ||
|
|
d9a0c2dce9 | ||
|
|
178301baf2 | ||
|
|
4e18affa9b | ||
|
|
21f40fb95e | ||
|
|
24f0cee49a | ||
|
|
fee38d4359 | ||
|
|
0c85ea844a | ||
|
|
c3839edc40 | ||
|
|
be2a0ec3e7 | ||
|
|
c23952cd9e | ||
|
|
13a8f56b46 | ||
|
|
927acdbd7e | ||
|
|
6f6267f3b3 | ||
|
|
dec89d4054 | ||
|
|
193e114596 | ||
|
|
1be67c434e | ||
|
|
4f8dab75b2 | ||
|
|
37d063fd3b | ||
|
|
97be3da126 | ||
|
|
e7f97f8264 | ||
|
|
6415aeebac | ||
|
|
e69200ea43 | ||
|
|
26d5018224 | ||
|
|
1b7699ca93 | ||
|
|
8eab099f09 | ||
|
|
c7c5426586 |
+1
-1
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 © 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/
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
# include <lite/lite.h>
|
||||
#endif
|
||||
|
||||
#include "conf.h"
|
||||
#include "finit.h"
|
||||
#include "helpers.h"
|
||||
#include "plugin.h"
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 ...");
|
||||
|
||||
@@ -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
@@ -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 */
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user