mirror of
https://github.com/troglobit/finit.git
synced 2026-10-12 10:12:32 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb913e8bf2 | ||
|
|
17d04779fb | ||
|
|
ab661423ba | ||
|
|
8e77ea68b2 | ||
|
|
b0e672bc9a | ||
|
|
ad76e15949 | ||
|
|
b8735cc893 | ||
|
|
c058b3f9ce | ||
|
|
8f6c61606a | ||
|
|
96f944b816 | ||
|
|
a81530754b | ||
|
|
1518eb9466 | ||
|
|
50b9e46e19 | ||
|
|
c01faef99b | ||
|
|
4867b7268b | ||
|
|
662293e194 | ||
|
|
4ce810cc1f | ||
|
|
184c079c08 | ||
|
|
17c6791c70 | ||
|
|
42f24f5af5 | ||
|
|
cef5049bf0 | ||
|
|
ec251f1ce1 | ||
|
|
98ec30d308 | ||
|
|
e1fe870aaf | ||
|
|
f04fce544d | ||
|
|
7aa9ed5982 | ||
|
|
a1a92a04bd | ||
|
|
804f655d87 | ||
|
|
2c78e7429a | ||
|
|
d77f0127b4 | ||
|
|
4253d0de25 | ||
|
|
d47181a4a7 | ||
|
|
8642007d0e | ||
|
|
96c74ed48b | ||
|
|
03a14bcaa5 | ||
|
|
4bb2c33859 | ||
|
|
ab81272083 | ||
|
|
1fbf03bc1e | ||
|
|
92a2861b1c | ||
|
|
922714780a | ||
|
|
b206c3d655 | ||
|
|
109d8826bd | ||
|
|
60478106eb | ||
|
|
0de9cfb020 | ||
|
|
0b182c063e | ||
|
|
f0914f6d32 | ||
|
|
7090321ff3 | ||
|
|
266f1132a4 | ||
|
|
5f7e8457af | ||
|
|
a215747355 | ||
|
|
dacda5ab5d | ||
|
|
39044adcf8 | ||
|
|
9396cd1d26 | ||
|
|
c9fd4418e7 | ||
|
|
7206f745a2 | ||
|
|
25bcde12d4 | ||
|
|
7459ede806 | ||
|
|
6bf35513c3 | ||
|
|
0b8d39329a | ||
|
|
84098dd8b7 | ||
|
|
c4463ec64f | ||
|
|
6f1808248e | ||
|
|
29029bb78f | ||
|
|
bbe588ac16 | ||
|
|
34bf9a7776 | ||
|
|
bb111f98fc | ||
|
|
c7faf4f9e2 | ||
|
|
18774539c0 | ||
|
|
abaad560f0 | ||
|
|
0ef325d86c | ||
|
|
2c94f4e45f | ||
|
|
da184e7922 | ||
|
|
1e93fc1671 | ||
|
|
8e7d1b7bb5 | ||
|
|
373738f3d1 | ||
|
|
e6d3eb2526 | ||
|
|
d7fd5bc902 | ||
|
|
3f98220d5d | ||
|
|
0ca509a42e | ||
|
|
d17144d16f | ||
|
|
a608c5a5c9 | ||
|
|
9b44b99480 | ||
|
|
ad225156f1 | ||
|
|
eb92a915d3 | ||
|
|
69a4f2c115 | ||
|
|
9ce95fe8c0 | ||
|
|
06889cc014 | ||
|
|
329f11b4da | ||
|
|
ce40e2b9d2 | ||
|
|
d0176612c9 | ||
|
|
21753e26dd | ||
|
|
0dc2513b32 | ||
|
|
d16bfa789b | ||
|
|
0b27778c96 | ||
|
|
3e25297e3c | ||
|
|
6560968c8b | ||
|
|
c9a3c5c885 | ||
|
|
a2d45e8f5e | ||
|
|
f6c69eb108 | ||
|
|
f0032ab6b7 | ||
|
|
53c5d0e55a | ||
|
|
7c8ab7915f | ||
|
|
12f7855a78 | ||
|
|
bcc3360b53 | ||
|
|
d3408d14a8 | ||
|
|
804060444a | ||
|
|
de544a5b36 | ||
|
|
0d48d1df49 | ||
|
|
b46592e818 | ||
|
|
22bc218c84 |
@@ -40,23 +40,23 @@ jobs:
|
||||
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)
|
||||
(cd libuev-2.4.1 && ./configure && make -j9 && sudo make install-strip)
|
||||
(cd libite-2.6.2 && ./configure && make -j9 && sudo make install-strip)
|
||||
sudo ldconfig
|
||||
- uses: actions/checkout@v4
|
||||
- name: Static Finit
|
||||
run: |
|
||||
./autogen.sh
|
||||
./configure --prefix= --enable-static
|
||||
make -j3 V=1
|
||||
make -j9 V=1
|
||||
- name: Regular Finit
|
||||
run: |
|
||||
./configure --prefix=/usr --exec-prefix= --sysconfdir=/etc --localstatedir=/var \
|
||||
--enable-x11-common-plugin --enable-testserv-plugin --with-watchdog \
|
||||
--with-keventd --with-libsystemd \
|
||||
CFLAGS="-fsanitize=address -ggdb"
|
||||
make -j3 clean
|
||||
make -j3 V=1
|
||||
make -j9 clean
|
||||
make -j9 V=1
|
||||
- name: Install to /tmp
|
||||
run: |
|
||||
DESTDIR=/tmp make install-strip
|
||||
@@ -78,8 +78,9 @@ jobs:
|
||||
sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0
|
||||
- name: Run Unit Tests
|
||||
run: |
|
||||
make check || (cat test/test-suite.log; false)
|
||||
make -j1 check || (cat test/test-suite.log; false)
|
||||
- name: Upload Test Results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: finit-test-${{ matrix.compiler }}
|
||||
|
||||
@@ -60,8 +60,8 @@ jobs:
|
||||
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)
|
||||
(cd libuev-2.4.1 && ./configure && make -j9 && sudo make install-strip)
|
||||
(cd libite-2.6.2 && ./configure && make -j9 && sudo make install-strip)
|
||||
sudo ldconfig
|
||||
- name: Configure
|
||||
run: |
|
||||
|
||||
+47
-15
@@ -19,12 +19,6 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -46,23 +40,61 @@ jobs:
|
||||
pipx inject mkdocs mkdocs-material
|
||||
pipx inject mkdocs pymdown-extensions
|
||||
pipx inject mkdocs mkdocs-callouts
|
||||
pipx inject mkdocs mkdocs-glightbox
|
||||
|
||||
- name: Build documentation
|
||||
run: mkdocs build
|
||||
run: mkdocs build --clean
|
||||
|
||||
- name: Upload Pages artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
- name: Upload site artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: site
|
||||
path: site/
|
||||
|
||||
deploy:
|
||||
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
- name: Download site artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: site
|
||||
path: site/
|
||||
|
||||
- name: Checkout pages repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: finit-project/finit-project.github.io
|
||||
path: pages
|
||||
ssh-key: ${{ secrets.DOCS_DEPLOY_KEY }}
|
||||
persist-credentials: true
|
||||
|
||||
- name: Setup SSH for push
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
ssh-keyscan github.com >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Sync site to pages repo
|
||||
run: |
|
||||
rsync -a --delete --exclude .git site/ pages/
|
||||
|
||||
- name: Commit and push
|
||||
run: |
|
||||
cd pages/
|
||||
if [ -z "$(git status --porcelain)" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SRC_REPO="${GITHUB_REPOSITORY}"
|
||||
SRC_REF="${GITHUB_SHA::7}"
|
||||
SRC_URL="https://github.com/${SRC_REPO}/commit/${SRC_REF}"
|
||||
|
||||
git config user.name "GitHub Actions"
|
||||
git config user.email "actions@github.com"
|
||||
git add -A
|
||||
git commit -m "docs: update from ${SRC_REPO}@${SRC_REF}" \
|
||||
-m "For details, see ${SRC_URL}"
|
||||
git push
|
||||
|
||||
@@ -13,8 +13,6 @@ jobs:
|
||||
name: Build and upload release tarball
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
MAKEFLAGS: -j3
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Installing dependencies ...
|
||||
@@ -25,8 +23,9 @@ jobs:
|
||||
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)
|
||||
(cd libuev-2.4.1 && ./configure && make -j9 && sudo make install-strip)
|
||||
(cd libite-2.6.2 && ./configure && make -j9 && sudo make install-strip)
|
||||
sudo ldconfig
|
||||
- name: Setting release variables ...
|
||||
id: build
|
||||
run: |
|
||||
@@ -54,10 +53,21 @@ jobs:
|
||||
sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0
|
||||
- name: Build release ...
|
||||
run: |
|
||||
make release || (cat test/test-suite.log; false)
|
||||
# Get version for finding distcheck build directory
|
||||
./configure --version | head -1
|
||||
ver=$(./configure --version | head -1 | awk '{print $NF}')
|
||||
dir=finit-$ver/_build/sub
|
||||
|
||||
make -j1 release || (cat $dir/test/test-suite.log; false)
|
||||
ls -lF ../
|
||||
mkdir -p artifacts/
|
||||
mv ../*.tar.* artifacts/
|
||||
- name: Upload Test Results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: finit-release-test-logs
|
||||
path: finit-*/_build/sub/test/*.log
|
||||
- name: Extract ChangeLog entry ...
|
||||
run: |
|
||||
awk '/-----*/{if (x == 1) exit; x=1;next}x' doc/ChangeLog.md \
|
||||
|
||||
@@ -14,14 +14,14 @@ jobs:
|
||||
schedule:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
run: ${{ steps.check.outputs.run }}
|
||||
run: ${{ steps.schedule.outputs.run }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check for any commits since last week
|
||||
id: schedule
|
||||
run: |
|
||||
commits="$(git log --format=%H --since='1 week ago')"
|
||||
if [ -n "$commits" ]; then
|
||||
if [ -z "$commits" ]; then
|
||||
echo "run=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "run=false" >> $GITHUB_OUTPUT
|
||||
@@ -39,8 +39,9 @@ jobs:
|
||||
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)
|
||||
(cd libuev-2.4.1 && ./configure && make -j9 && sudo make install-strip)
|
||||
(cd libite-2.6.2 && ./configure && make -j9 && sudo make install-strip)
|
||||
sudo ldconfig
|
||||
- uses: actions/checkout@v4
|
||||
- name: Base run
|
||||
run: |
|
||||
@@ -58,3 +59,9 @@ jobs:
|
||||
ver=$(/tmp/sbin/initctl -V | awk '/Finit.*/ {print $2;}')
|
||||
dir=finit-$ver/_build/sub
|
||||
make -j1 distcheck || (cat $dir/test/test-suite.log; false)
|
||||
- name: Upload Test Results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: finit-weekly-test-logs
|
||||
path: finit-*/_build/sub/test/*.log
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
[![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)
|
||||
and [systemd](https://www.freedesktop.org/wiki/Software/systemd/), focused
|
||||
on small and embedded Linux systems, although fully usable on server and
|
||||
[desktop systems][finix].
|
||||
Finit is a fast, simple alternative to [SysV init](https://en.wikipedia.org/wiki/Init)
|
||||
and [systemd](https://systemd.io), designed for small and embedded Linux systems.
|
||||
It can also run on desktop and server systems, like [finix][].
|
||||
|
||||
> Reverse engineered from the [EeePC fastinit][]
|
||||
> "gaps filled with frog DNA …"
|
||||
> — [Claudio Matsuoka][]
|
||||
|
||||
For detailed information, explore our extensive documentation
|
||||
:books: **<http://finit-project.github.io/finit/>**
|
||||
:books: **<http://finit-project.github.io>**
|
||||
|
||||
<div align="center">
|
||||
<img src="doc/img/alpine-screenshot2.png" alt="Alpine screenshot"><br/>
|
||||
@@ -26,14 +25,14 @@ tutorials:
|
||||
* :milky_way: [Void Linux](contrib/void/)
|
||||
|
||||
> [!NOTE]
|
||||
> Support for various Linux distributions does not mean Finit installs
|
||||
> easily on all architectures. Bundled install scripts are examples for
|
||||
> 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/finit-project/br2-finit-demo).
|
||||
> Finit can run on various Linux distributions, but the bundled install
|
||||
> scripts are examples only. They have been tested on amd64 (x86_64) systems
|
||||
> with standard configurations.
|
||||
>
|
||||
> For embedded systems, see these [Buildroot][]-based examples:
|
||||
> [myLinux][], [Infix][], or [br2-finit-demo][].
|
||||
|
||||
[finix]: https://github.com/aanderse/finix
|
||||
[finix]: https://github.com/finix-community/finix
|
||||
[Buildroot]: https://buildroot.org
|
||||
[Infix]: https://kernelkit.github.io
|
||||
[myLinux]: https://github.com/troglobit/myLinux/
|
||||
@@ -48,3 +47,4 @@ tutorials:
|
||||
[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
|
||||
[br2-finit-demo]: https://github.com/finit-project/br2-finit-demo
|
||||
|
||||
+2
-1
@@ -1,4 +1,4 @@
|
||||
AC_INIT([Finit], [4.15-rc1], [https://github.com/finit-project/finit/issues],
|
||||
AC_INIT([Finit], [4.17-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])
|
||||
@@ -116,6 +116,7 @@ AC_PLUGIN([hotplug], [yes], [Start udevd or mdev kernel event datamon])
|
||||
AC_PLUGIN([rtc], [yes], [Save and restore RTC using hwclock])
|
||||
AC_PLUGIN([tty], [yes], [Automatically activate new TTYs, e.g. USB-to-serial])
|
||||
AC_PLUGIN([urandom], [yes], [Setup and save random seed at boot/shutdown])
|
||||
AC_PLUGIN([plymouth], [no], [Plymouth boot splash integration])
|
||||
AC_PLUGIN([testserv], [no], [Test plugin to start test serv daemon])
|
||||
|
||||
# Check for extra arguments or packages
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# On some systems, bootstrap can be really quick and cause elogind
|
||||
# to not start before a user has managed to log in. To prevent
|
||||
# this, add <pid/elogind> as condition to your TTYs in getty.conf
|
||||
service [2345] <pid/syslogd> /lib/elogind/elogind -- Login manager
|
||||
service [2345] <pid/syslogd> /usr/libexec/elogind -- Login manager
|
||||
|
||||
+122
-2
@@ -3,9 +3,101 @@ Change Log
|
||||
|
||||
All relevant changes are documented in this file.
|
||||
|
||||
[4.15][UNRELEASED]
|
||||
[4.17][] - 2026-04-xx
|
||||
---------------------
|
||||
|
||||
### Changes
|
||||
|
||||
- Add Plymouth boot splash plugin to manage the `plymouthd` lifecycle across boot,
|
||||
`switch_root`, and shutdown. Activated by the `splash` kernel command line
|
||||
argument. By Aaron Andersen
|
||||
- Add `tty:` option for run/task/service stanzas to acquire a controlling
|
||||
terminal. The device is opened as stdin, set as the session's controlling TTY
|
||||
via `TIOCSCTTY`, and `TERM` is set automatically — `linux` for virtual terminals
|
||||
and `vt102` for serial lines. By Ollie Gutierrez
|
||||
- Improve console output atomicity at boot and shutdown: description and status
|
||||
(`[ OK ]` / `[FAIL]`) are emitted in a single write so kernel messages cannot
|
||||
split them. If output has been scrolled away, the description is re-printed
|
||||
before the final status line. Console buffer is drained with `tcdrain(2)` and
|
||||
ANSI attributes are reset before handing control back to the kernel at
|
||||
shutdown/reboot
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fix reverse-dependency condition handling on reload: when a service without
|
||||
SIGHUP support is reloaded, its condition is now cleared immediately so
|
||||
dependents transition to `STOPPING` before `SIGTERM` is sent to the parent,
|
||||
preventing them from crashing with an incremented restart counter
|
||||
- Fix `/var/tmp` mode in `tmpfiles.d/var.conf` from `0777` to `1777` (sticky bit)
|
||||
to match `/tmp`, by Aaron Andersen
|
||||
- Fix potential memory corruption in config parser: stale pointer captures of
|
||||
`PATH` and `SHELL` at startup could dangle after environment modification;
|
||||
`conf_reset_env()` now reads from the live environment instead, by Vasily Zubko
|
||||
- Fix plugin name registration: replace `__FILE__` macro with explicit string
|
||||
literals in all plugin structs and debug messages, preventing subtle build-
|
||||
system–dependent macro expansion issues, by Vasily Zubko
|
||||
|
||||
|
||||
[4.16][] - 2026-02-27
|
||||
---------------------
|
||||
|
||||
### Changes
|
||||
|
||||
- Add `initctl switch-root` command and plugin hook point. Useful for
|
||||
systems requiring early boot tasks like LUKS unlock, LVM activation,
|
||||
or network boot before mounting the real root, by Aaron Andersen
|
||||
- Set USER and LOGNAME environment variables when dropping privileges.
|
||||
Fixes issues with software like rootless Podman that determines user
|
||||
identity from environment variables, by Aaron Andersen
|
||||
- Add `remain:yes` option for run/task oneshot commands, similar to the
|
||||
systemd `RemainAfterExit=yes`, by Aaron Andersen
|
||||
- Clear service conditions on `initctl reload NAME` to ensure dependent
|
||||
services are properly updated
|
||||
- Run service `stop:` and `reload:` scripts as non-blocking processes,
|
||||
preventing Finit from stalling on long-running helper scripts
|
||||
- Guard shutdown with timer watchdog to detect and debug shutdown hangs
|
||||
- Add `~` condition prefix to propagate reload from a dependency to the
|
||||
dependent service. E.g., `<!~pid/netd>` means not only a regular
|
||||
condition, but when `netd` reloads, restart this service too. Similar
|
||||
to systemd's directive `PropagatesReloadTo=`, but declared on the
|
||||
consumer side. Issue #416
|
||||
- `tmpfiles`: add support for `-E` and `--exclude-prefix` flags
|
||||
contributed by Aaron Andersen
|
||||
|
||||
### Fixes
|
||||
- Fix #464: invalid user:group examples in cgroups.md
|
||||
- Fix #466: elogind path for Debian-based distros, by Jackie Liu
|
||||
- Fix #467: TTY services stuck in restart state after non-zero exit.
|
||||
Throttling logic introduced in v4.15 had duplicate checks causing
|
||||
infinite timer loop, and TTYs lacked default restart timeout
|
||||
- Fix #475: clear pid condition on service collection to fix stale
|
||||
deps. When a service crashes (SIGKILL), the RUNNING → HALTED path
|
||||
bypasses STOPPING where `cond_clear()` is normally called, leaving
|
||||
dependents stuck
|
||||
- Fix #476: dependents not restarted after SIGHUP reload of service in
|
||||
dependency chain. Add `service_step_all()` at end of reload cycle to
|
||||
guarantee convergence after conditions are reasserted. See also the
|
||||
new `~` condition prefix (above) to propagate reload to dependents
|
||||
- Fix #478: fix `<dev/foo>` conditions at boot, conditions that exist
|
||||
before inotify was set up. Found and fixed by Mattias Walström
|
||||
- Fix reload of SIGHUP-capable services incorrectly disrupting their
|
||||
dependents. E.g., `initctl reload syslogd` would stop dbus, dnsmasq,
|
||||
etc. even though syslogd handles SIGHUP gracefully and its PID persists
|
||||
- Only remove managed pidfiles in service cleanup. For SysV services
|
||||
with `pid:!/path`, the pidfile belongs to the service itself and Finit
|
||||
should not delete it
|
||||
- Silence spurious cgroup warnings for short-lived processes where the
|
||||
kernel reaps the child before cgroup assignment completes
|
||||
- Fix handling of already-mounted cgroups in `cgroup_init()`, can occur
|
||||
after switch_root or in container environments
|
||||
- Improve cgroups documentation clarity, grammar, and examples
|
||||
|
||||
|
||||
[4.15][] - 2026-01-01
|
||||
---------------------
|
||||
|
||||
The Finit project now has a new home page: <https://finit-project.github.io>
|
||||
|
||||
### 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)
|
||||
@@ -24,17 +116,43 @@ All relevant changes are documented in this file.
|
||||
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`
|
||||
- Add support for Linux capabilities, allowing services to run with minimal
|
||||
required privileges instead of running as root. Uses the modern IAB API
|
||||
(Inheritable, Ambient, Bounding) from libcap. By Aaron Andersen
|
||||
- Add support for supplementary groups for services. Uses the syntax
|
||||
`@user:group,sup1,sup2` to explicitly specify supplementary groups, in
|
||||
addition to reading group membership from `/etc/group`. By Aaron Andersen
|
||||
- Add CPU throttled information to `initctl` output, showing when a service
|
||||
has been throttled due to reaching its CPU quota
|
||||
- Add memory field to `initctl --json` status output for cgroup-enabled
|
||||
services
|
||||
- Default to user and group `root` (UID 0) for services
|
||||
- Always check that service user and group resolve to valid UID/GID pairs,
|
||||
otherwise log error and refuse to start service
|
||||
- Hide cursor at boot and shutdown for cleaner visual experience
|
||||
- Rename `tty` services early from `init` -> `getty` even though no getty
|
||||
process has been started yet. This to aid in boot timing detection using
|
||||
tools like `bootchartd` (BusyBox version or bootchart2 project) when Finit
|
||||
only shows *"Please press Enter to activate this console."*
|
||||
- Services with `respawn` option (including TTYs) now delay retry on non-zero
|
||||
exit codes instead of immediate respawn. Prevents busy-loop when service
|
||||
fails repeatedly, which on some systems may have a crippling effect
|
||||
|
||||
### 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
|
||||
- Fix #462: `/dev/pts` mounted with wrong mode (400 instead of 620), causing
|
||||
permission issues with pseudo-terminals
|
||||
- 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
|
||||
- Fix issue with running Finit under `bootchartd` (bootchart2 project), always
|
||||
set `PATH` and `SHELL` envs early, before attempting to mount `/proc` et al
|
||||
- Always reset ownership and permissions on TTY device nodes before launching getty
|
||||
- Ensure mount/unmount skips `noauto` entries
|
||||
|
||||
|
||||
@@ -1885,7 +2003,9 @@ Major bug fix release.
|
||||
|
||||
* Initial release
|
||||
|
||||
[UNRELEASED]: https://github.com/finit-project/finit/compare/4.14...HEAD
|
||||
[UNRELEASED]: https://github.com/finit-project/finit/compare/4.17...HEAD
|
||||
[4.17]: https://github.com/finit-project/finit/compare/4.16...4.17
|
||||
[4.16]: https://github.com/finit-project/finit/compare/4.15...4.16
|
||||
[4.15]: https://github.com/finit-project/finit/compare/4.14...4.15
|
||||
[4.14]: https://github.com/finit-project/finit/compare/4.13...4.14
|
||||
[4.13]: https://github.com/troglobit/finit/compare/4.12...4.13
|
||||
|
||||
+37
-4
@@ -14,11 +14,11 @@ specified separated by comma. Multiple conditions are logically AND'ed
|
||||
during evaluation, i.e. all conditions must be satisfied in order for a
|
||||
service to run.
|
||||
|
||||
A special syntax, using a leading `!` in run/task/service conditions,
|
||||
denote if a:
|
||||
Two special prefixes can be used inside the angle brackets:
|
||||
|
||||
- service does not support `SIGHUP`
|
||||
- run/task should not block runlevel changes (i.e., bootstrap)
|
||||
- `!` -- service does not support `SIGHUP` (noreload), or run/task
|
||||
should not block runlevel changes (i.e., bootstrap)
|
||||
- `~` -- propagate reload from this dependency, see below
|
||||
|
||||
Finit guarantees by default that all run/tasks run (at least) once
|
||||
per runlevel. For most tasks this is a good default, for example
|
||||
@@ -53,6 +53,30 @@ moving to the next runlevel after bootstrap, so we set `<!>`:
|
||||
task [S0123456789] <!sys/pwr/fail> name:pwrfail initctl poweroff -- Power failure, shutting down
|
||||
|
||||
|
||||
Propagating Reload in Dependencies
|
||||
-----------------------------------
|
||||
|
||||
By default, when a service reloads during `initctl reload`, dependent
|
||||
services are paused (`SIGSTOP`) and simply resumed (`SIGCONT`) when the
|
||||
condition is reasserted. This is correct for barrier-style dependencies
|
||||
like `<pid/syslogd>`, where dependents just need syslogd running and do
|
||||
not care if it reloads its config.
|
||||
|
||||
For services that need to react when their upstream reloads, the `~`
|
||||
prefix propagates the reload from the dependency:
|
||||
|
||||
service <pid/svc_a> name:svc_b /sbin/svc_b -- Needs A (barrier)
|
||||
service <!~pid/svc_b> name:svc_c /sbin/svc_c -- Propagate reload from B
|
||||
|
||||
Here, `<~pid/svc_b>` means: propagate a reload of `svc_b` to `svc_c`.
|
||||
When `svc_b` reloads, `svc_c` will be restarted (because of `!`,
|
||||
noreload) instead of merely resumed. If `svc_c` supported `SIGHUP`
|
||||
(no `!` prefix), it would be sent `SIGHUP` instead.
|
||||
|
||||
This is similar to systemd's `PropagatesReloadTo=` directive, but
|
||||
declared on the consumer side rather than the provider side.
|
||||
|
||||
|
||||
Triggering
|
||||
----------
|
||||
|
||||
@@ -194,6 +218,10 @@ When Finit configuration files are changed and the `initctl reload`
|
||||
command is called, it is expected of services to touch their PID files
|
||||
for Finit to reassert their conditions.
|
||||
|
||||
Similarly, when a single service is reloaded with `initctl reload NAME`,
|
||||
its conditions are cleared and reasserted, ensuring dependent services
|
||||
are properly updated.
|
||||
|
||||
Daemons that don't create PID files, or fail to touch them on reload,
|
||||
can be worked around by using the `pid:/path/to/file.pid` syntax in
|
||||
the service stanza for the daemon. It is far from optimal since any
|
||||
@@ -277,6 +305,11 @@ This STOP/CONT handling minimizes the number of unnecessary service
|
||||
restarts that would otherwise occur because a depending service was sent
|
||||
`SIGHUP` for example.
|
||||
|
||||
Services with the `~` prefix are an exception to this rule: when their
|
||||
conditions return to `on` after being in `flux`, the reload is propagated
|
||||
-- the service is reloaded (SIGHUP) or restarted (noreload `!`) instead
|
||||
of simply being resumed.
|
||||
|
||||
Therefore, any plugin that supplies Finit with conditions must ensure
|
||||
that their state is updated after each reconfiguration. This can be
|
||||
done by binding to the `HOOK_SVC_RECONF` hook. For an example of how
|
||||
|
||||
+20
-10
@@ -115,9 +115,10 @@ Common capabilities include (see `man 7 capabilities` for the complete list):
|
||||
- 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
|
||||
2. **Specify a user (preferably non-root)**
|
||||
- The `@user` directive is **required** for `caps:` to take effect
|
||||
- For ambient capabilities (`^`), use a non-root user (not `@root`)
|
||||
- Example: `@www-data`, `@nginx`, `@tcpdump`
|
||||
|
||||
3. **Use ambient capabilities (`^`)**
|
||||
- The `^` prefix ensures capabilities survive exec()
|
||||
@@ -162,15 +163,24 @@ ps -o user,pid,cmd -p $(pidof nginx)
|
||||
|
||||
## 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
|
||||
- The `caps:` directive requires `@user` to be specified for it to take effect
|
||||
- Without `@user`, the service runs as root with full capabilities and
|
||||
the `caps:` configuration is silently ignored
|
||||
- You can use `@root` with `caps:`, but see below about ambient capabilities
|
||||
- For ambient capabilities (`^`, recommended), the user **must be non-root**
|
||||
- Using `@root` with `caps:^...` will not work effectively, as ambient
|
||||
capabilities are only added to the effective set when euid ≠ 0
|
||||
- Use inheritable (`%`) or bounding (`!`) capabilities with `@root` if needed
|
||||
- Services without `caps:` use standard privilege dropping:
|
||||
- Services with `@user` (non-root) have no special capabilities
|
||||
- Services without `@user` run as root with full capabilities
|
||||
- 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
|
||||
- [capabilities(7)](https://man7.org/linux/man-pages/man7/capabilities.7.html) - Linux capabilities overview
|
||||
- [cap_iab(3)](https://man7.org/linux/man-pages/man3/cap_iab.3.html) - IAB capability API documentation
|
||||
- [setcap(8)](https://man7.org/linux/man-pages/man8/setcap.8.html) - Set file capabilities
|
||||
- [getcap(8)](https://man7.org/linux/man-pages/man8/getcap.8.html) - Query file capabilities
|
||||
- [capsh(1)](https://man7.org/linux/man-pages/man1/capsh.1.html) - Capability shell wrapper
|
||||
|
||||
+67
-21
@@ -1,16 +1,29 @@
|
||||
There are three major cgroup configuration directives:
|
||||
Finit provides three different cgroup directives for controlling resource allocation:
|
||||
|
||||
1. Global top-level group: `init`, `system`, `user`, or a custom group
|
||||
2. Selecting a top-level group for a set of run/task/services
|
||||
3. Per run/task/service limits
|
||||
1. **Top-level cgroup definition**: `cgroup NAME settings`
|
||||
- Defines a top-level cgroup (e.g., `init`, `system`, `user`) with default settings
|
||||
- Space-separated syntax
|
||||
- Example: `cgroup system cpu.weight:9700`
|
||||
|
||||
2. **Global cgroup selector**: `cgroup.NAME[,options]` (standalone directive)
|
||||
- Sets the default cgroup for subsequent services in a `.conf` file
|
||||
- Dot-separated with optional comma-separated options
|
||||
- Example: `cgroup.maint` or `cgroup.system,delegate`
|
||||
|
||||
3. **Per-service cgroup option**: `cgroup.NAME[,options]` or `cgroup:options`
|
||||
- Overrides the cgroup for a specific service
|
||||
- Part of the service directive line
|
||||
- Example: `service [...] cgroup.maint,mem.max:1G /path/to/cmd`
|
||||
|
||||
> [!NOTE]
|
||||
> Linux cgroups and details surrounding values are not explained in the
|
||||
> Finit documentation. The Linux admin-guide cover this well:
|
||||
> Finit documentation. The Linux admin-guide covers this well:
|
||||
> <https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html>
|
||||
|
||||
Top-level Group Configuration
|
||||
-----------------------------
|
||||
Top-level Cgroup Definition
|
||||
----------------------------
|
||||
|
||||
**Syntax:** `cgroup NAME settings`
|
||||
|
||||
# Top-level cgroups and their default settings. All groups mandatory
|
||||
# but more can be added, max 8 groups in total currently. The cgroup
|
||||
@@ -34,22 +47,37 @@ By default, the `system/` cgroup is selected for almost everything. The
|
||||
`init/` cgroup is reserved for PID 1 itself and its closest relatives.
|
||||
The `user/` cgroup is for local TTY logins spawned by getty.
|
||||
|
||||
Selecting Cgroups
|
||||
------------------
|
||||
Global Cgroup Selector
|
||||
----------------------
|
||||
|
||||
To select a different top-level cgroup, e.g. `maint/`, one can either
|
||||
define it for a group of run/task/service directives in a `.conf` or per
|
||||
each stanza:
|
||||
**Syntax:** `cgroup.NAME[,options]` (standalone directive)
|
||||
|
||||
To select a different top-level cgroup, e.g. `maint/`, for a group of
|
||||
run/task/service directives in a `.conf` file, use the `cgroup.NAME`
|
||||
directive as a standalone line:
|
||||
|
||||
cgroup.maint
|
||||
service [...] <...> /path/to/foo args -- description
|
||||
service [...] <...> /path/to/bar args -- description
|
||||
|
||||
or
|
||||
Both services will run in the `maint/` cgroup.
|
||||
|
||||
You can also include options with the global selector:
|
||||
|
||||
cgroup.system,delegate
|
||||
service [...] <...> /path/to/foo args -- description
|
||||
|
||||
Per-Service Cgroup Option
|
||||
--------------------------
|
||||
|
||||
**Syntax:** `cgroup.NAME[,options]` or `cgroup:options` (service option)
|
||||
|
||||
To override the cgroup for a specific service, use the `cgroup.NAME`
|
||||
option within the service directive:
|
||||
|
||||
service [...] <...> cgroup.maint /path/to/foo args -- description
|
||||
|
||||
The latter form also allows per-stanza limits. Two syntaxes are supported:
|
||||
This form also allows per-service limits. Two syntaxes are supported:
|
||||
|
||||
**New comma-separated syntax (recommended):**
|
||||
|
||||
@@ -59,6 +87,11 @@ The latter form also allows per-stanza limits. Two syntaxes are supported:
|
||||
|
||||
service [...] <...> cgroup.maint:cpu.max:10000,mem.max:655360 /path/to/foo args -- description
|
||||
|
||||
You can also apply options to the current default cgroup (without changing it)
|
||||
using the `cgroup:options` syntax:
|
||||
|
||||
service [...] <...> cgroup:cpu.max:10000,mem.max:655360 /path/to/foo args -- description
|
||||
|
||||
Both syntaxes work identically. The new comma-separated syntax is recommended
|
||||
as it's more consistent with other option parsing in Finit.
|
||||
|
||||
@@ -88,11 +121,11 @@ The syntax supports combining the name override with other options:
|
||||
|
||||
Or with delegation:
|
||||
|
||||
service [2345] user:podman group:podman \
|
||||
service [2345] @podman: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.
|
||||
A daemon using `SCHED_RR` currently needs to run outside the default cgroups.
|
||||
|
||||
service [...] <...> cgroup.root /path/to/daemon arg -- Real-Time process
|
||||
|
||||
@@ -102,12 +135,12 @@ 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 \
|
||||
service [2345] @dockerd:dockerd \
|
||||
cgroup.system,delegate /usr/bin/dockerd -- Docker daemon
|
||||
|
||||
Or with the old colon syntax:
|
||||
|
||||
service [2345] user:dockerd group:dockerd \
|
||||
service [2345] @dockerd:dockerd \
|
||||
cgroup.system:delegate /usr/bin/dockerd -- Docker daemon
|
||||
|
||||
This allows the container runtime to:
|
||||
@@ -126,23 +159,36 @@ When delegation is enabled, Finit:
|
||||
|
||||
**Requirements:**
|
||||
|
||||
- The service should specify `user:` and `group:` for proper ownership
|
||||
- The service should specify `@user:group` for proper ownership
|
||||
- Controllers are delegated from the parent cgroup
|
||||
|
||||
**Example with additional config (new syntax):**
|
||||
|
||||
service [2345] user:podman group:podman \
|
||||
service [2345] @podman:podman \
|
||||
cgroup.containers,delegate,mem.max:4G \
|
||||
/usr/bin/podman system service -- Podman API
|
||||
|
||||
**Or with old syntax:**
|
||||
|
||||
service [2345] user:podman group:podman \
|
||||
service [2345] @podman: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.
|
||||
|
||||
**Container template example:**
|
||||
|
||||
Here's a real-world example from [Infix OS](https://github.com/kernelkit/infix)
|
||||
for running rootful podman container instances using delegation:
|
||||
|
||||
sysv log:prio:local1,tag:%i kill:30 pid:!/run/container:%i.pid \
|
||||
pre:0,/usr/sbin/container cleanup:0,/usr/sbin/container \
|
||||
cgroup.system,delegate \
|
||||
[2345] <!> :%i container -n %i -- container %i
|
||||
|
||||
This template uses `sysv` type with delegation, demonstrating that cgroup
|
||||
delegation works with different service types, not just `service`.
|
||||
|
||||
**Cgroup structure with delegation:**
|
||||
|
||||
Initially, the service process runs directly in the cgroup root:
|
||||
|
||||
@@ -127,11 +127,12 @@ Resource Limits
|
||||
Set the hard or soft limit for a resource, or both if that argument is
|
||||
omitted. `RESOURCE` is the lower-case `RLIMIT_` string constants from
|
||||
`setrlimit(2)`, without prefix. E.g. to set `RLIMIT_CPU`, use `cpu`.
|
||||
|
||||
|
||||
LIMIT is an integer that depends on the resource being modified, see
|
||||
the man page, or the kernel `/proc/PID/limits` file, for details.
|
||||
Finit versions before v3.1 used `infinity` for `unlimited`, which is
|
||||
still supported, albeit deprecated.
|
||||
[setrlimit(2)](https://man7.org/linux/man-pages/man2/setrlimit.2.html),
|
||||
or the kernel `/proc/PID/limits` file, for details. Finit versions
|
||||
before v3.1 used `infinity` for `unlimited`, which is still supported,
|
||||
albeit deprecated.
|
||||
|
||||
# No process is allowed more than 8MB of address space
|
||||
rlimit hard as 8388608
|
||||
|
||||
@@ -43,9 +43,10 @@ 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
|
||||
* `cgroup.NAME[,opts]` or `cgroup:opts` -- see the [Cgroups](cgroups.md) section
|
||||
* `env:[-]/path/to/env` -- see the [Service Environment](service-env.md) section
|
||||
* `log:...` -- see [Redirecting Output](logging.md#redirecting-output)
|
||||
* `tty:<dev>` -- see [Controlling TTY](tty.md#controlling-tty)
|
||||
* `nowarn` -- see [Conditional Loading](services.md#conditional-loading)
|
||||
* `notify:...` -- see [Service Synchronization](service-sync.md)
|
||||
* `if:...` -- see [Conditional Execution](services.md#conditional-execution)
|
||||
@@ -69,6 +70,11 @@ confines of that the following options are available:
|
||||
* `respawn` -- bypasses the `restart` mechanism completely, allows
|
||||
endless restarts. Useful in many use-cases, but not what `service`
|
||||
was originally designed for so not the default behavior
|
||||
* `remain:yes` -- for `run` and `task` only. Prevents the task from
|
||||
re-running on runlevel re-entry and ensures the `post:` script runs
|
||||
when the task is explicitly stopped or leaves its valid runlevels.
|
||||
Similar to systemd's `RemainAfterExit=yes`. See [Task and Run](task-and-run.md)
|
||||
for more details
|
||||
* `oncrash:reboot` -- when all retries have failed, and the service
|
||||
has *crashed*, if this option is set the system is rebooted
|
||||
* `oncrash:script` -- similarly, but instead of rebooting, call the
|
||||
|
||||
+24
-1
@@ -47,6 +47,14 @@ waits for another service, `zebra`, to have created its PID file in
|
||||
*all* files in `/var/run`, for each file named `*.pid`, or `*/pid`,
|
||||
Finit opens it and find the matching `NAME:ID` using the PID.
|
||||
|
||||
The condition can be prefixed with `!` and/or `~`:
|
||||
|
||||
- `<!pid/zebra>` -- `ospfd` does not support `SIGHUP` (noreload)
|
||||
- `<~pid/zebra>` -- propagate reload from `zebra` to `ospfd`
|
||||
- `<!~pid/zebra>` -- both: noreload and propagate reload
|
||||
|
||||
For details, see the [Finit Conditions](../conditions.md) document.
|
||||
|
||||
Some services do not maintain a PID file and rather than patching each
|
||||
application Finit provides a workaround. A `pid` keyword can be set
|
||||
to have Finit automatically create (when starting) and later remove
|
||||
@@ -116,10 +124,25 @@ Non-privileged Services
|
||||
|
||||
Every `run`, `task`, or `service` can also list the privileges the
|
||||
`/path/to/cmd` should be executed with. Prefix the command with
|
||||
`@USR[:GRP]`, group is optional, like this:
|
||||
`@USR[:GRP[,SUPP,...]]`, where group and supplementary groups are
|
||||
optional, like this:
|
||||
|
||||
run [2345] @joe:users logger "Hello world"
|
||||
|
||||
Finit reads the user's supplementary group membership from `/etc/group`
|
||||
automatically. Any groups the user belongs to will be inherited by
|
||||
the service.
|
||||
|
||||
To specify additional supplementary groups beyond those in `/etc/group`,
|
||||
append them after the primary group, separated by commas:
|
||||
|
||||
service @caddy:caddy,ssl-cert /usr/bin/caddy run
|
||||
|
||||
This runs the `caddy` service as user `caddy`, with primary group
|
||||
`caddy`, inheriting any groups `caddy` is a member of in `/etc/group`,
|
||||
plus the additional `ssl-cert` group. This is useful when a service
|
||||
needs access to resources owned by groups not listed in `/etc/group`.
|
||||
|
||||
For multiple instances of the same command, e.g. a DHCP client or
|
||||
multiple web servers, add `:ID` somewhere between the `run`, `task`,
|
||||
`service` keyword and the command, like this:
|
||||
|
||||
@@ -44,3 +44,44 @@ redirects can be used:
|
||||
|
||||
Please note, `;`, `&&`, `||`, and similar are *not supported*. Any
|
||||
non-trivial constructs are better placed in a separate shell script.
|
||||
|
||||
|
||||
remain:yes
|
||||
----------
|
||||
|
||||
By default, a `run` or `task` will re-run each time its runlevel is
|
||||
entered, and its `post:` script does not run on completion.
|
||||
|
||||
With `remain:yes`, the task runs once and does not re-run on runlevel
|
||||
re-entry:
|
||||
|
||||
task [2345] remain:yes /usr/sbin/setup-firewall -- Firewall setup
|
||||
|
||||
This has the following effects:
|
||||
|
||||
* The task does not re-run on runlevel re-entry
|
||||
* The `post:` script runs when:
|
||||
- The task is explicitly stopped (`initctl stop NAME`)
|
||||
- The task leaves its valid runlevels (e.g., runlevel change)
|
||||
|
||||
This is useful for tasks that set up persistent state where:
|
||||
|
||||
* Cleanup should only happen on explicit stop or when leaving valid runlevels
|
||||
* The setup should not be re-run on every runlevel entry
|
||||
|
||||
**Example:** Setting up firewall rules with cleanup on shutdown:
|
||||
|
||||
```
|
||||
task [2345] remain:yes \
|
||||
post:/usr/sbin/teardown-firewall \
|
||||
/usr/sbin/setup-firewall -- Firewall setup
|
||||
```
|
||||
|
||||
The firewall rules are created once. The `post:` script runs when
|
||||
entering runlevel 0 (halt) or 6 (reboot), or on explicit stop.
|
||||
|
||||
> [!NOTE]
|
||||
> The `remain:yes` option is not supported for bootstrap-only tasks
|
||||
> (tasks with only runlevel S). Bootstrap tasks are deleted immediately
|
||||
> after completion, and their `post:` scripts never run. A warning is
|
||||
> logged if `remain:yes` is used on such tasks.
|
||||
|
||||
+28
-1
@@ -83,4 +83,31 @@ board bringup and system debugging it can come in handy.
|
||||
|
||||
One can also use the `service` stanza to start a stand-alone shell:
|
||||
|
||||
service [12345] /bin/sh -l
|
||||
service [12345] /bin/sh -l
|
||||
|
||||
Controlling TTY for Services
|
||||
----------------------------
|
||||
|
||||
The `tty:<dev>` option gives a `run`, `task`, or `service` a controlling
|
||||
terminal on the given device. The device is opened, set as the
|
||||
controlling terminal for the session (after `setsid()`), and connected to
|
||||
the process's stdin, stdout, and stderr. A default `TERM` environment
|
||||
variable is set based on the device type: `vt102` for serial lines and
|
||||
`linux` for virtual terminals.
|
||||
|
||||
`<dev>` may be a device node like `/dev/ttyS0`, or the special keyword
|
||||
`@console` (see above). Note that `@console` expands only to the
|
||||
first console, not all.
|
||||
|
||||
When `tty:` is combined with `log:`, stdout and stderr are redirected
|
||||
to the log sink instead of the TTY, but stdin remains connected to the
|
||||
TTY device.
|
||||
|
||||
> The `tty:<dev>` option is for `run`, `task`, and `service` stanzas only.
|
||||
> The `tty` directive itself (for getty/login) has its own syntax, see
|
||||
> above.
|
||||
|
||||
**Example:**
|
||||
|
||||
service [2345] tty:/dev/ttyS0 /usr/sbin/foo -- Foo on serial console
|
||||
task [S] tty:@console my-setup-script -- Board bringup on console
|
||||
+105
-46
@@ -1,80 +1,139 @@
|
||||
Distro Recommendations
|
||||
======================
|
||||
|
||||
By default Finit uses the following directories for configuration files:
|
||||
Finit supports two directory layouts for managing service configurations:
|
||||
|
||||
1. **Simple layout** - all configuration files directly in `/etc/finit.d/`
|
||||
2. **Distro layout** - separate `available/` and `enabled/` directories
|
||||
|
||||
The distro layout is recommended for distributions that need clear
|
||||
separation between installed and enabled services, and want to use the
|
||||
`initctl enable/disable` commands for service management.
|
||||
|
||||
## Simple Layout
|
||||
|
||||
Finit supports using a flat directory structure:
|
||||
|
||||
```
|
||||
/etc/
|
||||
|-- finit.d/ -- Regular (enabled) services
|
||||
|-- finit.d/ -- All enabled services
|
||||
| |-- lighttpd.conf
|
||||
| `- *.conf
|
||||
|-- finit.conf -- Bootstrap tasks and services
|
||||
:
|
||||
```
|
||||
|
||||
To enable a service one simply drops a small configuration file in the
|
||||
`/etc/finit.d/` directory. This practice works with systems that
|
||||
keep disabled services elsewhere, or generates them as needed from some
|
||||
other tool.
|
||||
In this layout, you enable a service by placing its `.conf` file in
|
||||
`/etc/finit.d/` and disable it by removing the file. This works well for
|
||||
embedded systems or custom setups where services are managed by other tools
|
||||
or generated dynamically.
|
||||
|
||||
Distributions, however, may want a clearer separation of enabled and
|
||||
available (installed but not enabled) services. They may even want to
|
||||
customize the directories used, for brand labeling or uniformity.
|
||||
> [!NOTE] No tooling support
|
||||
> The `initctl enable/disable` commands **do not work** with this layout.
|
||||
> You must manually manage files in `/etc/finit.d/`.
|
||||
|
||||
To that end Finit allows for a sub-directory `/etc/finit.d/available/`
|
||||
where installed but disabled services can reside. Adding a symlink to a
|
||||
configuration in this sub-directory enables the service, but it will not
|
||||
be started.
|
||||
## Distro Layout
|
||||
|
||||
To change the default configuration directory and configuration file
|
||||
names the Finit `configure` script offers the following two options at
|
||||
build time:
|
||||
Distributions typically want clearer separation between available (installed)
|
||||
and enabled services. Finit supports this through the `available/` and
|
||||
`enabled/` subdirectories:
|
||||
|
||||
```
|
||||
/etc/
|
||||
|-- finit.d/
|
||||
| |-- available/ -- Installed but disabled services
|
||||
| | |-- httpd.conf
|
||||
| | |-- ntpd.conf
|
||||
| | `-- sshd.conf
|
||||
| |-- enabled/ -- Enabled services (symlinks)
|
||||
| | `- sshd.conf -- Symlink to ../available/sshd.conf
|
||||
|-- finit.conf -- Bootstrap tasks and services
|
||||
:
|
||||
```
|
||||
|
||||
In this layout:
|
||||
- **available/** contains all installed service configurations
|
||||
- **enabled/** contains symlinks to configurations that should start at boot
|
||||
- Services are enabled/disabled by creating/removing symlinks
|
||||
|
||||
> [!IMPORTANT] Recommended
|
||||
> This is the recommended layout. In fact, the `initctl enable/disable`
|
||||
> commands **require** this layout with both `available/` and `enabled/`
|
||||
> directories and will not work without it.
|
||||
|
||||
## Managing Services with initctl
|
||||
|
||||
When using the distro layout, the `initctl` tool provides convenient commands
|
||||
for managing service configurations:
|
||||
|
||||
```
|
||||
list List all .conf in /etc/finit.d/
|
||||
enable <CONF> Enable .conf by creating symlink in enabled/
|
||||
disable <CONF> Disable .conf by removing symlink from enabled/
|
||||
reload Reload *.conf in /etc/finit.d/ (activate changes)
|
||||
```
|
||||
|
||||
Example usage:
|
||||
|
||||
```bash
|
||||
# Enable sshd (creates enabled/sshd.conf -> ../available/sshd.conf)
|
||||
initctl enable sshd
|
||||
|
||||
# Disable httpd (removes enabled/httpd.conf symlink)
|
||||
initctl disable httpd
|
||||
|
||||
# Apply changes (reload configuration and start/stop services)
|
||||
initctl reload
|
||||
```
|
||||
|
||||
The `.conf` suffix is optional - `initctl` adds it automatically if missing.
|
||||
|
||||
> [!NOTE] Remember `initctl reload`
|
||||
> Changes made with `enable`/`disable` take effect only after running
|
||||
> `initctl reload`, unless Finit was built with `--enable-auto-reload`
|
||||
> which automatically detects and applies configuration changes.
|
||||
|
||||
### Service Overrides
|
||||
|
||||
The `initctl` tool only operates on symlinks in the `enabled/` directory.
|
||||
If you place a regular (non-symlink) `.conf` file directly in the parent
|
||||
directory (e.g., `/etc/finit.d/`), `initctl` will ignore it. This allows
|
||||
you to create system-level overrides that cannot be accidentally disabled
|
||||
by package management tools.
|
||||
|
||||
## Customizing Directory Paths
|
||||
|
||||
Distributions can customize the directory names and locations at build time
|
||||
using the Finit `configure` script:
|
||||
|
||||
```sh
|
||||
./configure --with-rcsd=/etc/init.d --with-config=/etc/init.d/init.conf
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Remember `--prefix` et al as well, the default is likely *not* what
|
||||
> you want. See the [build docs][1] for details.
|
||||
This changes the default `/etc/finit.d/` to `/etc/init.d/` and moves the
|
||||
bootstrap configuration file accordingly.
|
||||
|
||||
The resulting directory structure is depicted below. Please notice how
|
||||
`/etc/finit.conf` now resides in the same sub-directory as a non-symlink
|
||||
`/etc/init.d/init.conf`:
|
||||
> [!IMPORTANT] Remember to set `--prefix`
|
||||
> Remember to set `--prefix` and related options appropriately. The default
|
||||
> prefix is `/usr/local`, which is likely not what you want for a system
|
||||
> init. See the [build documentation][1] for details.
|
||||
|
||||
Example with custom paths:
|
||||
|
||||
```
|
||||
/etc/
|
||||
|-- init.d/
|
||||
| |-- available/ -- Regular (disabled) services
|
||||
| |-- available/ -- Installed services
|
||||
| | |-- httpd.conf
|
||||
| | |-- ntpd.conf
|
||||
| | `-- sshd.conf
|
||||
| |-- sshd.conf -- Symlink to available/sshd.conf
|
||||
| |-- enabled/ -- Enabled services (symlinks)
|
||||
| | `- sshd.conf -- Symlink to ../available/sshd.conf
|
||||
| `- init.conf -- Bootstrap tasks and services
|
||||
:
|
||||
```
|
||||
|
||||
To facilitate the task of managing configurations, be it a service,
|
||||
task, run, or other stanza, the `initctl` tool has a a few built-in
|
||||
commands:
|
||||
|
||||
```
|
||||
list List all .conf in /etc/finit.d/
|
||||
enable <CONF> Enable .conf in /etc/finit.d/available/
|
||||
disable <CONF> Disable .conf in /etc/finit.d/[enabled/]
|
||||
reload Reload *.conf in /etc/finit.d/ (activates changes)
|
||||
```
|
||||
|
||||
To enable a service like `sshd.conf`, above, use
|
||||
|
||||
initctl enable sshd
|
||||
|
||||
The `.conf` suffix is not needed, `initctl` adds it implicitly if it is
|
||||
missing. The `disable` command works in a similar fashion.
|
||||
|
||||
Note, however, that `initctl` only operates on symlinks and it always
|
||||
requires the `available/` sub-directory. Any non-symlink in the parent
|
||||
directory, here `/etc/init.d/`, will be considered a system override by
|
||||
`initctl`.
|
||||
Notice how both the service directory and bootstrap configuration file now
|
||||
use the custom `/etc/init.d/` path specified at build time.
|
||||
|
||||
[1]: build.md#configure
|
||||
|
||||
+104
@@ -1,6 +1,10 @@
|
||||
Finit Features
|
||||
==============
|
||||
|
||||
This page highlights some of Finit's key features with examples and usage
|
||||
scenarios. For complete documentation, configuration syntax, and advanced
|
||||
options, see the [Configuration](config/index.md) section.
|
||||
|
||||
**Process Supervision**
|
||||
|
||||
Start, monitor and restart services should they fail.
|
||||
@@ -162,6 +166,28 @@ See the [Linux Capabilities](config/capabilities.md) section for detailed
|
||||
information, examples, and security best practices.
|
||||
|
||||
|
||||
**Supplementary Groups**
|
||||
|
||||
Finit supports supplementary groups for services, allowing them to access
|
||||
resources owned by multiple groups without running as root. This complements
|
||||
capabilities for fine-grained privilege control.
|
||||
|
||||
```conf
|
||||
service @caddy:caddy,ssl-cert /usr/bin/caddy run
|
||||
```
|
||||
|
||||
In this example, the Caddy web server runs as user `caddy` with primary group
|
||||
`caddy`, but also has access to resources owned by the `ssl-cert` group (such
|
||||
as TLS certificates).
|
||||
|
||||
Finit automatically reads the user's supplementary group membership from
|
||||
`/etc/group`. Additional groups can be specified explicitly using the syntax
|
||||
`@user:group,sup1,sup2,...`.
|
||||
|
||||
See the [Non-privileged Services](config/services.md#non-privileged-services)
|
||||
section for more information.
|
||||
|
||||
|
||||
**Cgroups**
|
||||
|
||||
Finit supports cgroups v2 and comes with the following default groups in
|
||||
@@ -207,4 +233,82 @@ commands for details.
|
||||
> disabled early at boot.
|
||||
|
||||
|
||||
**Service Management**
|
||||
|
||||
Finit includes the `initctl` tool for managing services and system state at
|
||||
runtime. Key capabilities include:
|
||||
|
||||
- **Enable/Disable services**: Manage which services start at boot by moving
|
||||
configuration files between `/etc/finit.d/available` and
|
||||
`/etc/finit.d/enabled`
|
||||
- **Start/Stop/Restart**: Control individual services without requiring a
|
||||
full system reboot
|
||||
- **Status monitoring**: View service state, PID, uptime, and resource usage
|
||||
- **Condition management**: Set and clear user-defined conditions to control
|
||||
service dependencies
|
||||
- **Cgroup monitoring**: Real-time process and resource monitoring with
|
||||
`initctl top`, similar to the traditional `top` command but cgroup-aware
|
||||
|
||||
Example commands:
|
||||
|
||||
```bash
|
||||
initctl enable myservice # Enable service for next boot
|
||||
initctl start myservice # Start service now
|
||||
initctl status # Show all services
|
||||
initctl top # Interactive resource monitor
|
||||
initctl cond set usr/custom # Set custom condition
|
||||
```
|
||||
|
||||
See the [Commands & Status](initctl.md) section for complete documentation.
|
||||
|
||||
|
||||
**Rescue Mode**
|
||||
|
||||
Finit provides a built-in rescue mode for system recovery and maintenance.
|
||||
When booting with the `rescue` kernel parameter, the system enters a
|
||||
protected maintenance shell.
|
||||
|
||||
If the bundled `sulogin` program is available (from Finit, util-linux, or
|
||||
BusyBox), you'll be prompted for the root password before accessing the
|
||||
maintenance shell. This provides secure access for system recovery.
|
||||
|
||||
If `sulogin` is not available, Finit falls back to reading
|
||||
`/lib/finit/rescue.conf` and boots the system in a limited maintenance mode.
|
||||
|
||||
```
|
||||
# Kernel command line
|
||||
linux /vmlinuz root=/dev/sda1 rescue
|
||||
```
|
||||
|
||||
In rescue mode, `initctl` will not work. After fixing the problem, use
|
||||
`reboot -f` to force reboot.
|
||||
|
||||
Rescue mode can be disabled at build time with `configure --without-rescue`.
|
||||
|
||||
See the [Rescue Mode](config/rescue.md) section for more information.
|
||||
|
||||
|
||||
**Switch Root**
|
||||
|
||||
Finit supports switching from an initramfs to a real root filesystem using
|
||||
the built-in `initctl switch-root` command. This allows Finit to serve as
|
||||
the init system in an initramfs for early boot tasks (LUKS unlock, LVM
|
||||
activation, network boot) before transitioning to the real root.
|
||||
|
||||
```bash
|
||||
# In initramfs, after mounting the real root:
|
||||
initctl switch-root /mnt/root
|
||||
```
|
||||
|
||||
The switch-root operation:
|
||||
|
||||
1. Runs the `HOOK_SWITCH_ROOT` hook for cleanup
|
||||
2. Stops all services gracefully
|
||||
3. Moves virtual filesystems (`/dev`, `/proc`, `/sys`, `/run`) to the new root
|
||||
4. Deletes initramfs contents to free memory
|
||||
5. Pivots to the new root and execs the new init
|
||||
|
||||
See the [Switch Root](switchroot.md) section for complete documentation.
|
||||
|
||||
|
||||
[5]: https://en.wikipedia.org/wiki/Runlevel
|
||||
|
||||
+20
-14
@@ -1,7 +1,7 @@
|
||||
Introduction
|
||||
============
|
||||
|
||||
{ align=right }
|
||||
{ align=right width=40% }
|
||||
|
||||
> Reverse engineered from the [EeePC fastinit][]
|
||||
> "gaps filled with frog DNA …"
|
||||
@@ -17,26 +17,31 @@ PID file monitoring, or [conditions](conditions.md).
|
||||
Features
|
||||
--------
|
||||
|
||||
* [Runlevels][5], defined per service
|
||||
* One-shot tasks, services (daemons), or [SysV init][4] start/stop scripts
|
||||
* Runparts and `/etc/rc.local` support
|
||||
* [Runlevels](config/runlevels.md), defined per service
|
||||
* [One-shot tasks](config/task-and-run.md), [services](config/services.md) (daemons), or [SysV init][4] start/stop scripts
|
||||
* [Runparts](config/runparts.md) and `/etc/rc.local` support
|
||||
* Process supervision similar to [systemd][]
|
||||
* Sourcing environment files
|
||||
* Conditions for network/process/custom dependencies
|
||||
* Readiness notification; PID files (native) for synchronizing system
|
||||
* Fine-grained privilege control:
|
||||
- [Linux capabilities](config/capabilities.md) for minimal required privileges
|
||||
- [Supplementary groups](config/services.md#non-privileged-services) for multi-group resource access
|
||||
* [Sourcing environment files](config/service-env.md)
|
||||
* [Conditions](conditions.md) for network/process/custom dependencies
|
||||
* [Readiness notification](config/service-sync.md); PID files (native) for synchronizing system
|
||||
startup, support for systemd [sd_notify()][], or [s6 style][] too
|
||||
* Limited support for [tmpfiles.d(5)][] (no aging, attributes, or subvolumes)
|
||||
* Pre/Post script actions
|
||||
* [Pre/Post script actions](config/service-wrappers.md)
|
||||
* Rudimentary [templating support](config/templating.md)
|
||||
* Tooling to enable/disable services
|
||||
* Built-in getty
|
||||
* [Tooling](initctl.md) to enable/disable services
|
||||
* [Automatic reload](features.md#automatic-reload) of modified configuration files (optional)
|
||||
* [Built-in getty](config/tty.md)
|
||||
* Built-in watchdog, with support for hand-over to [watchdogd][]
|
||||
* Built-in support for Debian/BusyBox [`/etc/network/interfaces`][3]
|
||||
* Cgroups v2, both configuration and monitoring in `initctl top`
|
||||
* Plugin support for customization
|
||||
* Proper rescue mode with bundled `sulogin` for protected maintenance shell
|
||||
* [Cgroups v2](config/cgroups.md), both configuration and monitoring in [`initctl top`](initctl.md)
|
||||
* [Plugin support](plugins.md) for customization
|
||||
* Proper [rescue mode](config/rescue.md) with bundled `sulogin` for protected maintenance shell
|
||||
* [Switch root](switchroot.md) support for initramfs-to-real-root transitions
|
||||
* Integration with [watchdogd][] for full system supervision
|
||||
* Logging to kernel ring buffer before `syslogd` has started, see the
|
||||
* [Logging](config/logging.md) to kernel ring buffer before `syslogd` has started, see the
|
||||
recommended [sysklogd][] project for complete logging integration
|
||||
and how to log to the kernel ring buffer from scripts using `logger`
|
||||
|
||||
@@ -64,6 +69,7 @@ and proposed extensions.
|
||||
[4]: https://en.wikipedia.org/wiki/Init
|
||||
[5]: https://en.wikipedia.org/wiki/Runlevel
|
||||
[6]: https://github.com/finit-project/finit
|
||||
[dinit]: https://davmac.org/projects/dinit/
|
||||
[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
|
||||
|
||||
@@ -65,6 +65,7 @@ Commands:
|
||||
halt Halt system
|
||||
poweroff Halt and power off system
|
||||
suspend Suspend system
|
||||
switch-root NEWROOT [INIT] Switch to new root filesystem (initramfs only)
|
||||
|
||||
utmp show Raw dump of UTMP/WTMP db
|
||||
```
|
||||
@@ -143,3 +144,24 @@ Apr 8 12:37:46 alpine authpriv.info dropbear[2300]: Exit (root) from <192.168.1
|
||||
Apr 8 15:02:11 alpine authpriv.info dropbear[2634]: Child connection from 192.168.121.1:48576
|
||||
Apr 8 15:02:12 alpine authpriv.notice dropbear[2634]: Password auth succeeded for 'root' from 192.168.121.1:48576
|
||||
```
|
||||
|
||||
|
||||
Switch Root
|
||||
-----------
|
||||
|
||||
The `switch-root` command is used when running Finit in an initramfs to
|
||||
transition to the real root filesystem. This is similar to the standalone
|
||||
`switch_root(8)` utility but integrated into Finit.
|
||||
|
||||
```
|
||||
initctl switch-root /mnt/root [/sbin/init]
|
||||
```
|
||||
|
||||
Requirements:
|
||||
|
||||
- Must be run during runlevel S (bootstrap) or runlevel 1
|
||||
- The new root must be a mount point (different device than /)
|
||||
- Can only be used when Finit is running as PID 1 in an initramfs
|
||||
|
||||
For complete documentation and usage examples, see the dedicated
|
||||
[Switch Root](switchroot.md) section.
|
||||
|
||||
@@ -205,6 +205,16 @@ hook points:
|
||||
new runlevel have been been stopped. When the hook has completed,
|
||||
Finit continues to start all services in the new runlevel.
|
||||
|
||||
### Switch Root Hooks
|
||||
|
||||
* `HOOK_SWITCH_ROOT`, `hook/sys/switchroot`: Called when
|
||||
`initctl switch-root` is issued, before the transition begins. Use
|
||||
this hook to save state, unmount initramfs-only filesystems, or perform
|
||||
cleanup before switching to the new root. Only runs when Finit is
|
||||
operating as PID 1 in an initramfs.
|
||||
|
||||
See the [Switch Root](switchroot.md) section for more information.
|
||||
|
||||
### Shutdown Hooks
|
||||
|
||||
* `HOOK_NETWORK_DN`, `hook/net/down`: Called right after having changed
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
Switch Root
|
||||
===========
|
||||
|
||||
Finit supports switching from an initramfs to a real root filesystem
|
||||
using the `initctl switch-root` command. This is useful for systems
|
||||
that use an initramfs for early boot (LUKS, LVM, network boot, etc.)
|
||||
and need to transition to the real root before starting services.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
```sh
|
||||
initctl switch-root NEWROOT [INIT]
|
||||
```
|
||||
|
||||
- `NEWROOT`: Path to the mounted new root filesystem (e.g., `/mnt/root`)
|
||||
- `INIT`: Optional path to init on the new root (default: `/sbin/init`)
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
1. Must be run during runlevel S (bootstrap) or runlevel 1
|
||||
2. `NEWROOT` must be a mount point (different device than /)
|
||||
3. `INIT` must exist and be executable on the new root
|
||||
4. Finit must be running as PID 1 (in initramfs)
|
||||
|
||||
|
||||
How It Works
|
||||
------------
|
||||
|
||||
1. Runs `HOOK_SWITCH_ROOT` for any cleanup scripts/plugins
|
||||
2. Runs `HOOK_SHUTDOWN` to notify plugins
|
||||
3. Stops all services and kills remaining processes
|
||||
4. Exits all plugins gracefully
|
||||
5. Moves `/dev`, `/proc`, `/sys`, `/run` to new root
|
||||
6. Deletes initramfs contents (if on tmpfs/ramfs) to free memory
|
||||
7. Moves new root mount to `/`
|
||||
8. Chroots to new root
|
||||
9. Reopens `/dev/console` for stdin/stdout/stderr
|
||||
10. Execs new init as PID 1
|
||||
|
||||
|
||||
Example: Initramfs finit.conf
|
||||
-----------------------------
|
||||
|
||||
Configuration file `/etc/finit.conf` in the initramfs:
|
||||
|
||||
```
|
||||
# /etc/finit.conf in initramfs
|
||||
|
||||
# Mount the real root filesystem
|
||||
run [S] name:mount-root /bin/mount /dev/sda1 /mnt/root -- Mounting root filesystem
|
||||
|
||||
# Switch to real root after mount completes
|
||||
run [S] name:switch-root /sbin/initctl switch-root /mnt/root -- Switching to real root
|
||||
```
|
||||
|
||||
For more complex setups (LUKS, LVM, etc.):
|
||||
|
||||
```
|
||||
# Unlock LUKS volume
|
||||
# The tty:@console stanza is required so cryptsetup can prompt for a passphrase
|
||||
run [S] name:cryptsetup tty:@console /sbin/cryptsetup open /dev/sda2 cryptroot -- Unlocking encrypted root
|
||||
|
||||
# Activate LVM
|
||||
run [S] name:lvm /sbin/lvm vgchange -ay -- Activating LVM volumes
|
||||
|
||||
# Mount root
|
||||
run [S] name:mount-root /bin/mount /dev/vg0/root /mnt/root -- Mounting root
|
||||
|
||||
# Switch root
|
||||
run [S] name:switch-root /sbin/initctl switch-root /mnt/root -- Switching to real root
|
||||
```
|
||||
|
||||
|
||||
Example: Using Runlevel 1 for Switch Root
|
||||
-----------------------------------------
|
||||
|
||||
For more complex initramfs setups where ordering of tasks becomes
|
||||
difficult in runlevel S, you can perform the switch-root in runlevel 1:
|
||||
|
||||
```
|
||||
# /etc/finit.conf in initramfs
|
||||
|
||||
# Start mdevd for device handling
|
||||
service [S] name:mdevd notify:s6 /sbin/mdevd -D %n -- Device event daemon
|
||||
run [S] name:coldplug <service/mdevd/ready> /sbin/mdevd-coldplug -- Coldplug devices
|
||||
|
||||
# Mount the real root filesystem (after devices are ready)
|
||||
run [S] name:mount-root <run/coldplug/success> /bin/mount /dev/sda1 /mnt/root -- Mounting root
|
||||
|
||||
# Transition to runlevel 1 after all S tasks complete
|
||||
# The switch-root runs cleanly in runlevel 1
|
||||
run [1] name:switch-root /sbin/initctl switch-root /mnt/root -- Switching to real root
|
||||
```
|
||||
|
||||
This approach separates the initramfs setup (runlevel S) from the
|
||||
switch-root operation (runlevel 1), making task ordering simpler.
|
||||
|
||||
|
||||
Hooks
|
||||
-----
|
||||
|
||||
The `HOOK_SWITCH_ROOT` hook runs before the switch begins. Use it for:
|
||||
|
||||
- Saving state to the new root
|
||||
- Unmounting initramfs-only mounts
|
||||
- Cleanup tasks
|
||||
|
||||
Plugins can register for `HOOK_SWITCH_ROOT` just like other hooks:
|
||||
|
||||
```c
|
||||
static void my_switch_root_hook(void *arg)
|
||||
{
|
||||
/* Cleanup before switch_root */
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
.name = "my-plugin",
|
||||
.hook[HOOK_SWITCH_ROOT] = {
|
||||
.cb = my_switch_root_hook
|
||||
}
|
||||
};
|
||||
|
||||
PLUGIN_INIT(plugin_init)
|
||||
{
|
||||
plugin_register(&plugin);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Conditions
|
||||
----------
|
||||
|
||||
After switch_root, the new finit instance starts fresh. No conditions
|
||||
or state are preserved across the switch. The new finit will:
|
||||
|
||||
1. Re-read `/etc/finit.conf` from the new root
|
||||
2. Re-initialize all conditions
|
||||
3. Start services according to the new configuration
|
||||
|
||||
|
||||
See Also
|
||||
--------
|
||||
|
||||
- [switch_root(8)](https://man7.org/linux/man-pages/man8/switch_root.8.html) - util-linux switch_root utility
|
||||
- [Kernel initramfs documentation](https://docs.kernel.org/filesystems/ramfs-rootfs-initramfs.html)
|
||||
@@ -398,6 +398,20 @@ mechanism completely, allows endless restarts. Useful in many
|
||||
use-cases, but not what
|
||||
.Cm service
|
||||
was originally designed for so not the default behavior.
|
||||
.It Cm remain:yes
|
||||
for
|
||||
.Cm run
|
||||
and
|
||||
.Cm task
|
||||
only. Prevents the task from re-running on runlevel re-entry and
|
||||
ensures the
|
||||
.Cm post:
|
||||
script runs when the task is explicitly stopped or leaves its valid
|
||||
runlevels. Similar to systemd's
|
||||
.Cm RemainAfterExit=yes .
|
||||
.Pp
|
||||
.Sy Note:
|
||||
not supported for bootstrap-only tasks (runlevels [S] only).
|
||||
.It Cm oncrash:reboot
|
||||
when all retries have failed, and the service
|
||||
has
|
||||
@@ -730,6 +744,37 @@ is
|
||||
and the default
|
||||
.Cm tag
|
||||
identity is the basename of the service or run/task command.
|
||||
.It Cm tty:\&<dev>
|
||||
Give the
|
||||
.Cm run , task ,
|
||||
or
|
||||
.Cm service
|
||||
a controlling terminal on
|
||||
.Cm <dev> .
|
||||
The device is opened, set as the controlling terminal for the session
|
||||
(after
|
||||
.Fn setsid ) ,
|
||||
and connected to stdin, stdout, and stderr.
|
||||
A default
|
||||
.Ev TERM
|
||||
variable is set based on the device type:
|
||||
.Cm vt102
|
||||
for serial lines and
|
||||
.Cm linux
|
||||
for virtual terminals.
|
||||
.Cm <dev>
|
||||
may be a device node like
|
||||
.Pa /dev/ttyS0 ,
|
||||
or the special keyword
|
||||
.Cm @console
|
||||
(see TTYs and Consoles above).
|
||||
When combined with
|
||||
.Cm log: ,
|
||||
stdout and stderr are redirected to the log sink instead, but stdin
|
||||
remains connected to the TTY device.
|
||||
.Bd -unfilled -offset indent
|
||||
service [2345] tty:/dev/ttyS0 /usr/sbin/foo -- Foo on serial console
|
||||
.Ed
|
||||
.El
|
||||
.Sh RESCUE MODE
|
||||
Finit supports a rescue mode which is activated by the
|
||||
|
||||
+29
-21
@@ -1,6 +1,6 @@
|
||||
site_name: Documentation
|
||||
site_name: ""
|
||||
site_description: Fast Init for Linux Systems
|
||||
site_url: http://finit-project.github.io/finit/
|
||||
site_url: http://finit-project.github.io
|
||||
repo_url: https://github.com/finit-project/finit
|
||||
repo_name: Finit Project
|
||||
copyright: Copyright © 2008-2025 Joachim Wiberg
|
||||
@@ -11,6 +11,7 @@ extra_css:
|
||||
|
||||
exclude_docs: |
|
||||
TODO.md
|
||||
ChangeLog.md
|
||||
|
||||
theme:
|
||||
logo: img/logo.png
|
||||
@@ -22,7 +23,7 @@ theme:
|
||||
- navigation.instant
|
||||
- navigation.instant.progress
|
||||
- navigation.tracking
|
||||
# - navigation.tabs
|
||||
- navigation.tabs
|
||||
# - navigation.sections
|
||||
# - navigation.expand
|
||||
# - navigation.top
|
||||
@@ -49,20 +50,16 @@ theme:
|
||||
name: Switch to light mode
|
||||
|
||||
nav:
|
||||
- Introduction: index.md
|
||||
- Example: example.md
|
||||
- Features: features.md
|
||||
- Commands & Status: initctl.md
|
||||
- Rebooting & Halting: commands.md
|
||||
- Runlevels: runlevels.md
|
||||
- Runparts & /etc/rc.local: runparts.md
|
||||
- Getting Started:
|
||||
- Introduction: index.md
|
||||
- Features: features.md
|
||||
- Quick Example: example.md
|
||||
- Building: build.md
|
||||
- Configuration:
|
||||
- Overview: config/index.md
|
||||
- Files & Layout: config/files.md
|
||||
- Conditions: conditions.md
|
||||
- Cgroups: config/cgroups.md
|
||||
- Capabilities: config/capabilities.md
|
||||
- Environment Variables: config/env.md
|
||||
- Runlevels: config/runlevels.md
|
||||
- One-shot Tasks: config/task-and-run.md
|
||||
- Services:
|
||||
- Syntax: config/services.md
|
||||
@@ -71,24 +68,30 @@ nav:
|
||||
- Synchronization: config/service-sync.md
|
||||
- Wrapper Scripts: config/service-wrappers.md
|
||||
- Run-parts Scripts: config/runparts.md
|
||||
- Runlevels: config/runlevels.md
|
||||
- TTY & Console: config/tty.md
|
||||
- Templating: config/templating.md
|
||||
- Environment Variables: config/env.md
|
||||
- Logging: config/logging.md
|
||||
- Cgroups: config/cgroups.md
|
||||
- Capabilities: config/capabilities.md
|
||||
- Templating: config/templating.md
|
||||
- SysV Compatibility: config/sysv.md
|
||||
- Rescue Mode: config/rescue.md
|
||||
- Limitations: config/limitations.md
|
||||
- Usage:
|
||||
- Building: build.md
|
||||
- Command Line: cmdline.md
|
||||
- Plugins: plugins.md
|
||||
- Services: service.md
|
||||
- Commands & Status: initctl.md
|
||||
- Switch Root: switchroot.md
|
||||
- Rebooting & Halting: commands.md
|
||||
- Command Line Options: cmdline.md
|
||||
- Managing Services: service.md
|
||||
- Signals: signals.md
|
||||
- Reference:
|
||||
- Distributions: distro.md
|
||||
- keventd: keventd.md
|
||||
- Runlevels Overview: runlevels.md
|
||||
- Runparts & rc.local: runparts.md
|
||||
- Plugins: plugins.md
|
||||
- Watchdog: watchdog.md
|
||||
- keventd: keventd.md
|
||||
- Service State Machine: state-machine.md
|
||||
- Distributions: distro.md
|
||||
- Requirements: requirements.md
|
||||
|
||||
markdown_extensions:
|
||||
@@ -109,6 +112,11 @@ markdown_extensions:
|
||||
plugins:
|
||||
- search
|
||||
- callouts
|
||||
- glightbox
|
||||
|
||||
extra:
|
||||
homepage: https://finit-project.github.io/
|
||||
social:
|
||||
- icon: fontawesome/brands/github
|
||||
link: https://github.com/finit-project/finit
|
||||
name: Finit on GitHub
|
||||
|
||||
@@ -29,6 +29,10 @@ if BUILD_NETLINK_PLUGIN
|
||||
libplug_la_SOURCES += netlink.c
|
||||
endif
|
||||
|
||||
if BUILD_PLYMOUTH_PLUGIN
|
||||
libplug_la_SOURCES += plymouth.c
|
||||
endif
|
||||
|
||||
if BUILD_RESOLVCONF_PLUGIN
|
||||
libplug_la_SOURCES += resolvconf.c
|
||||
endif
|
||||
@@ -76,6 +80,10 @@ if BUILD_NETLINK_PLUGIN
|
||||
pkglib_LTLIBRARIES += netlink.la
|
||||
endif
|
||||
|
||||
if BUILD_PLYMOUTH_PLUGIN
|
||||
pkglib_LTLIBRARIES += plymouth.la
|
||||
endif
|
||||
|
||||
if BUILD_RESOLVCONF_PLUGIN
|
||||
pkglib_LTLIBRARIES += resolvconf.la
|
||||
endif
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
static void save(void *arg)
|
||||
{
|
||||
if (rescue) {
|
||||
dbg("Skipping %s plugin in rescue mode.", __FILE__);
|
||||
dbg("Skipping %s plugin in rescue mode.", "alsa-utils");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ static void save(void *arg)
|
||||
static void restore(void *arg)
|
||||
{
|
||||
if (rescue) {
|
||||
dbg("Skipping %s plugin in rescue mode.", __FILE__);
|
||||
dbg("Skipping %s plugin in rescue mode.", "alsa-utils");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ static void restore(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
.name = __FILE__,
|
||||
.name = "alsa-utils",
|
||||
.hook[HOOK_BASEFS_UP] = { .cb = restore },
|
||||
.hook[HOOK_SHUTDOWN] = { .cb = save }
|
||||
};
|
||||
|
||||
+1
-1
@@ -172,7 +172,7 @@ static void setup(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
.name = __FILE__,
|
||||
.name = "bootmisc",
|
||||
.hook[HOOK_MOUNT_POST] = { .cb = clean },
|
||||
.hook[HOOK_BASEFS_UP] = { .cb = setup },
|
||||
.depends = { "pidfile" },
|
||||
|
||||
+2
-2
@@ -106,7 +106,7 @@ static void setup(void *arg)
|
||||
char *cmd;
|
||||
|
||||
if (rescue) {
|
||||
dbg("Skipping %s plugin in rescue mode.", __FILE__);
|
||||
dbg("Skipping %s plugin in rescue mode.", "dbus");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ static void setup(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
.name = __FILE__,
|
||||
.name = "dbus",
|
||||
.hook[HOOK_SVC_PLUGIN] = { .cb = setup },
|
||||
};
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ static void hscript_shutdown(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
.name = __FILE__,
|
||||
.name = "hook-scripts",
|
||||
.hook[HOOK_BANNER] = { .cb = hscript_banner },
|
||||
.hook[HOOK_ROOTFS_UP] = { .cb = hscript_rootfs_up },
|
||||
.hook[HOOK_MOUNT_ERROR] = { .cb = hscript_mount_error },
|
||||
|
||||
+1
-1
@@ -228,7 +228,7 @@ static void coldplug(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
.name = __FILE__,
|
||||
.name = "modprobe",
|
||||
.hook[HOOK_BASEFS_UP] = { .cb = coldplug },
|
||||
.depends = { "bootmisc", }
|
||||
};
|
||||
|
||||
@@ -213,7 +213,7 @@ static void load(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
.name = __FILE__,
|
||||
.name = "modules-load",
|
||||
.hook[HOOK_SVC_PLUGIN] = { .cb = load },
|
||||
};
|
||||
|
||||
|
||||
+22
-2
@@ -406,9 +406,29 @@ static void nl_reconf(void *arg)
|
||||
cond_reassert("net/");
|
||||
}
|
||||
|
||||
/*
|
||||
* Initial enumeration of existing interfaces and routes. Called from
|
||||
* HOOK_SVC_PLUGIN which runs after the condition system is initialized.
|
||||
* Interfaces that exist before finit starts (e.g., virtio-net in QEMU)
|
||||
* won't generate RTM_NEWLINK events, so we must query for them.
|
||||
*/
|
||||
static void nl_enumerate(void *arg)
|
||||
{
|
||||
int sd;
|
||||
|
||||
sd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
|
||||
if (sd < 0)
|
||||
return;
|
||||
|
||||
nl_resync_ifaces(sd, 0);
|
||||
nl_resync_routes(sd, 1);
|
||||
close(sd);
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
.name = __FILE__,
|
||||
.hook[HOOK_SVC_RECONF] = { .cb = nl_reconf },
|
||||
.name = "netlink",
|
||||
.hook[HOOK_SVC_RECONF] = { .cb = nl_reconf },
|
||||
.hook[HOOK_SVC_PLUGIN] = { .cb = nl_enumerate },
|
||||
.io = {
|
||||
.cb = nl_callback,
|
||||
.flags = PLUGIN_IO_READ,
|
||||
|
||||
+3
-2
@@ -59,7 +59,7 @@ static int pidfile_add_path(struct iwatch *iw, char *path)
|
||||
}
|
||||
}
|
||||
|
||||
return iwatch_add(iw, path, IN_ONLYDIR | IN_CLOSE_WRITE);
|
||||
return iwatch_add(iw, path, IN_ONLYDIR | IN_CLOSE_WRITE | IN_ATTRIB);
|
||||
}
|
||||
|
||||
static void pidfile_update_conds(char *dir, char *name, uint32_t mask)
|
||||
@@ -276,6 +276,7 @@ static void pidfile_reconf(void *arg)
|
||||
if (cond_get(cond) == COND_ON)
|
||||
continue;
|
||||
|
||||
dbg("Reassert condition %s", cond);
|
||||
cond_set_path(cond_path(cond), COND_ON);
|
||||
}
|
||||
|
||||
@@ -334,7 +335,7 @@ static void pidfile_init(void *arg)
|
||||
* SIGSTP:ed (in state PAUSED) waiting for <net/iface/lo>.
|
||||
*/
|
||||
static plugin_t plugin = {
|
||||
.name = __FILE__,
|
||||
.name = "pidfile",
|
||||
.hook[HOOK_BASEFS_UP] = { .cb = pidfile_init },
|
||||
.hook[HOOK_SVC_RECONF] = { .cb = pidfile_reconf },
|
||||
.depends = { "netlink" }, /* bootmisc depends on us */
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
/* Plymouth boot splash plugin for Finit
|
||||
*
|
||||
* Copyright (c) 2012-2026 Aaron Andersen <aaron@fosslib.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This plugin integrates the Plymouth boot splash screen with Finit.
|
||||
* It manages the plymouthd lifecycle across the full boot process:
|
||||
*
|
||||
* - HOOK_BANNER: Start plymouthd early, before console output.
|
||||
* In initramfs, starts fresh. In stage 2, reuses
|
||||
* the daemon carried over from initramfs if alive.
|
||||
* - HOOK_ROOTFS_UP .. HOOK_SYSTEM_UP: Display status messages.
|
||||
* - HOOK_SVC_UP: Tear down plymouth once boot is complete.
|
||||
* - HOOK_SWITCH_ROOT: Notify plymouth of root filesystem change so
|
||||
* the daemon survives the initramfs -> rootfs
|
||||
* transition.
|
||||
* - HOOK_SHUTDOWN: Restart plymouthd in shutdown mode for a
|
||||
* splash during poweroff/reboot.
|
||||
*
|
||||
* The plugin is only activated when "splash" is present on the kernel
|
||||
* command line. Plymouth requires devpts for VT takeover; fs_init()
|
||||
* mounts it early enough for HOOK_BANNER.
|
||||
*
|
||||
* NOTE: The initramfs must include /etc/initrd-release. Plymouth
|
||||
* checks for this file and, when present, prefixes its argv[0] with
|
||||
* '@' so that the process is not killed during switch_root. Without
|
||||
* it, plymouthd will not survive the initramfs-to-rootfs transition.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "finit.h"
|
||||
#include "helpers.h"
|
||||
#include "conf.h"
|
||||
#include "pid.h"
|
||||
#include "plugin.h"
|
||||
#include "sig.h"
|
||||
#include "util.h"
|
||||
#include "log.h"
|
||||
|
||||
#ifndef PLYMOUTH_PATH
|
||||
#define PLYMOUTH_PATH "/sbin/plymouth"
|
||||
#endif
|
||||
#ifndef PLYMOUTHD_PATH
|
||||
#define PLYMOUTHD_PATH "/sbin/plymouthd"
|
||||
#endif
|
||||
|
||||
#define PLYMOUTH_PIDFILE "/run/plymouthd.pid"
|
||||
|
||||
static pid_t daemon_pid;
|
||||
static int switching_root;
|
||||
static int in_initramfs;
|
||||
|
||||
static int plymouth_cmd(const char *action)
|
||||
{
|
||||
char cmd[256];
|
||||
|
||||
snprintf(cmd, sizeof(cmd), PLYMOUTH_PATH " %s", action);
|
||||
return run(cmd, NULL);
|
||||
}
|
||||
|
||||
static void plymouth_message(const char *msg)
|
||||
{
|
||||
pid_t pid;
|
||||
|
||||
pid = fork();
|
||||
if (pid == 0) {
|
||||
sig_unblock();
|
||||
execl(PLYMOUTH_PATH, PLYMOUTH_PATH,
|
||||
"display-message", "--text", msg, NULL);
|
||||
_exit(EX_OSERR);
|
||||
}
|
||||
}
|
||||
|
||||
static int plymouth_alive(void)
|
||||
{
|
||||
return daemon_pid > 0 && pid_alive(daemon_pid);
|
||||
}
|
||||
|
||||
/* Start plymouthd in the given mode ("boot" or "shutdown"). */
|
||||
static void plymouth_start(const char *mode)
|
||||
{
|
||||
char cmd[256];
|
||||
int rc;
|
||||
|
||||
if (plymouth_alive())
|
||||
return;
|
||||
|
||||
snprintf(cmd, sizeof(cmd),
|
||||
PLYMOUTHD_PATH " --attach-to-session --mode %s --pid-file %s",
|
||||
mode, PLYMOUTH_PIDFILE);
|
||||
rc = run(cmd, NULL);
|
||||
if (rc) {
|
||||
warnx("plymouthd failed to start (exit %d)", rc);
|
||||
return;
|
||||
}
|
||||
|
||||
daemon_pid = pid_file_read(PLYMOUTH_PIDFILE);
|
||||
if (daemon_pid <= 0) {
|
||||
warnx("plymouthd started but no PID in %s", PLYMOUTH_PIDFILE);
|
||||
return;
|
||||
}
|
||||
|
||||
rc = plymouth_cmd("show-splash");
|
||||
if (rc)
|
||||
warnx("plymouth show-splash failed (exit %d)", rc);
|
||||
}
|
||||
|
||||
static void plymouth_stop(void)
|
||||
{
|
||||
if (!plymouth_alive())
|
||||
return;
|
||||
|
||||
plymouth_cmd("quit");
|
||||
|
||||
/*
|
||||
* Don't poll -- we're in a finit hook, so finit's event loop
|
||||
* is blocked and can't reap children. Trust that plymouthd
|
||||
* exits after receiving the quit command.
|
||||
*/
|
||||
daemon_pid = 0;
|
||||
unlink(PLYMOUTH_PIDFILE);
|
||||
}
|
||||
|
||||
/*
|
||||
* HOOK_BANNER - earliest possible hook, before any console output.
|
||||
*
|
||||
* In initramfs: start plymouthd fresh.
|
||||
* In stage 2: reuse plymouthd from initramfs if still alive,
|
||||
* otherwise start a new instance.
|
||||
*/
|
||||
static void plymouth_boot(void *arg)
|
||||
{
|
||||
in_initramfs = fexist("/etc/initrd-release");
|
||||
|
||||
if (rescue)
|
||||
return;
|
||||
|
||||
enable_progress(0);
|
||||
|
||||
if (!in_initramfs) {
|
||||
if (plymouth_cmd("--ping") == 0) {
|
||||
daemon_pid = pid_file_read(PLYMOUTH_PIDFILE);
|
||||
if (daemon_pid <= 0)
|
||||
daemon_pid = 1; /* alive but unknown pid */
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
plymouth_start("boot");
|
||||
}
|
||||
|
||||
/*
|
||||
* HOOK_SVC_UP - all services launched.
|
||||
*
|
||||
* In initramfs: keep splash alive for switch_root.
|
||||
* In stage 2: boot is done, tear down plymouth.
|
||||
*/
|
||||
static void plymouth_boot_done(void *arg)
|
||||
{
|
||||
if (in_initramfs)
|
||||
return;
|
||||
|
||||
plymouth_stop();
|
||||
enable_progress(1);
|
||||
}
|
||||
|
||||
/* HOOK_SWITCH_ROOT - initramfs transitioning to real root. */
|
||||
static void plymouth_switchroot(void *arg)
|
||||
{
|
||||
switching_root = 1;
|
||||
|
||||
plymouth_message("Switching to root filesystem...");
|
||||
|
||||
if (plymouth_alive())
|
||||
run(PLYMOUTH_PATH " update-root-fs --new-root-dir=/sysroot", NULL);
|
||||
|
||||
enable_progress(1);
|
||||
}
|
||||
|
||||
/* HOOK_SHUTDOWN - entering runlevel 0 or 6. */
|
||||
static void plymouth_shutdown(void *arg)
|
||||
{
|
||||
if (rescue || switching_root)
|
||||
return;
|
||||
|
||||
enable_progress(0);
|
||||
plymouth_start("shutdown");
|
||||
}
|
||||
|
||||
static void on_rootfs_up(void *arg)
|
||||
{
|
||||
plymouth_message("Root filesystem mounted");
|
||||
}
|
||||
|
||||
static void on_mount_post(void *arg)
|
||||
{
|
||||
plymouth_message("Mounting filesystems...");
|
||||
}
|
||||
|
||||
static void on_basefs_up(void *arg)
|
||||
{
|
||||
plymouth_message("All filesystems mounted");
|
||||
}
|
||||
|
||||
static void on_network_up(void *arg)
|
||||
{
|
||||
plymouth_message("Network is up");
|
||||
}
|
||||
|
||||
static void on_system_up(void *arg)
|
||||
{
|
||||
plymouth_message("System ready");
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
.name = "plymouth",
|
||||
.hook[HOOK_BANNER] = { .cb = plymouth_boot },
|
||||
.hook[HOOK_ROOTFS_UP] = { .cb = on_rootfs_up },
|
||||
.hook[HOOK_MOUNT_POST] = { .cb = on_mount_post },
|
||||
.hook[HOOK_BASEFS_UP] = { .cb = on_basefs_up },
|
||||
.hook[HOOK_NETWORK_UP] = { .cb = on_network_up },
|
||||
.hook[HOOK_SYSTEM_UP] = { .cb = on_system_up },
|
||||
.hook[HOOK_SVC_UP] = { .cb = plymouth_boot_done },
|
||||
.hook[HOOK_SWITCH_ROOT] = { .cb = plymouth_switchroot },
|
||||
.hook[HOOK_SHUTDOWN] = { .cb = plymouth_shutdown },
|
||||
};
|
||||
|
||||
/*
|
||||
* Check kernel command line for "splash" argument. Plymouth should
|
||||
* only be activated when the user explicitly requests it.
|
||||
*/
|
||||
static int has_splash_arg(void)
|
||||
{
|
||||
char line[LINE_SIZE], *tok, *saveptr;
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen("/proc/cmdline", "r");
|
||||
if (!fp)
|
||||
return 0;
|
||||
|
||||
if (!fgets(line, sizeof(line), fp)) {
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
for (tok = strtok_r(line, " \t\n", &saveptr); tok;
|
||||
tok = strtok_r(NULL, " \t\n", &saveptr)) {
|
||||
if (!strcmp(tok, "splash"))
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
PLUGIN_INIT(__init)
|
||||
{
|
||||
if (!has_splash_arg())
|
||||
return;
|
||||
|
||||
plugin_register(&plugin);
|
||||
}
|
||||
|
||||
PLUGIN_EXIT(__exit)
|
||||
{
|
||||
plugin_unregister(&plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Local Variables:
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "linux"
|
||||
* End:
|
||||
*/
|
||||
+2
-2
@@ -43,7 +43,7 @@ static void setup(void *arg)
|
||||
glob_t gl;
|
||||
|
||||
if (rescue) {
|
||||
dbg("Skipping %s plugin in rescue mode.", __FILE__);
|
||||
dbg("Skipping %s plugin in rescue mode.", "procps");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ static void setup(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
.name = __FILE__,
|
||||
.name = "procps",
|
||||
.hook[HOOK_BASEFS_UP] = {
|
||||
.cb = setup
|
||||
},
|
||||
|
||||
@@ -51,7 +51,7 @@ static void setup(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
.name = __FILE__,
|
||||
.name = "resolvconf",
|
||||
.hook[HOOK_BASEFS_UP] = {
|
||||
.cb = setup
|
||||
},
|
||||
|
||||
+3
-3
@@ -236,7 +236,7 @@ static void rtc_save(void *arg)
|
||||
int fd, rc = 0;
|
||||
|
||||
if (rescue) {
|
||||
dbg("Skipping %s plugin in rescue mode.", __FILE__);
|
||||
dbg("Skipping %s plugin in rescue mode.", "rtc");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -266,7 +266,7 @@ static void rtc_restore(void *arg)
|
||||
int fd, rc = 0;
|
||||
|
||||
if (rescue) {
|
||||
dbg("Skipping %s plugin in rescue mode.", __FILE__);
|
||||
dbg("Skipping %s plugin in rescue mode.", "rtc");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -321,7 +321,7 @@ static void update(uev_t *w, void *arg, int events)
|
||||
|
||||
|
||||
static plugin_t plugin = {
|
||||
.name = __FILE__,
|
||||
.name = "rtc",
|
||||
.hook[HOOK_BASEFS_UP] = {
|
||||
.cb = rtc_restore
|
||||
},
|
||||
|
||||
+1
-1
@@ -180,7 +180,7 @@ static void sys_init(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
.name = __FILE__,
|
||||
.name = "sys",
|
||||
.hook[HOOK_BASEFS_UP] = { .cb = sys_init },
|
||||
.depends = { "bootmisc", },
|
||||
};
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
static void tty_watcher(void *arg, int fd, int events);
|
||||
|
||||
static plugin_t plugin = {
|
||||
.name = "tty",
|
||||
.io = {
|
||||
.cb = tty_watcher,
|
||||
.flags = PLUGIN_IO_READ,
|
||||
|
||||
+3
-3
@@ -87,7 +87,7 @@ static void setup(void *arg)
|
||||
int fd, err;
|
||||
|
||||
if (rescue) {
|
||||
dbg("Skipping %s plugin in rescue mode.", __FILE__);
|
||||
dbg("Skipping %s plugin in rescue mode.", "urandom");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ static void save(void *arg)
|
||||
mode_t prev;
|
||||
|
||||
if (rescue) {
|
||||
dbg("Skipping %s plugin in rescue mode.", __FILE__);
|
||||
dbg("Skipping %s plugin in rescue mode.", "urandom");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ static void save(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
.name = __FILE__,
|
||||
.name = "urandom",
|
||||
.hook[HOOK_BASEFS_UP] = { .cb = setup },
|
||||
.hook[HOOK_SHUTDOWN] = { .cb = save },
|
||||
.depends = { "bootmisc", }
|
||||
|
||||
+1
-1
@@ -104,7 +104,7 @@ static void usr_init(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
.name = __FILE__,
|
||||
.name = "usr",
|
||||
.hook[HOOK_BASEFS_UP] = { .cb = usr_init },
|
||||
.depends = { "bootmisc", },
|
||||
};
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
static void setup(void *arg)
|
||||
{
|
||||
if (rescue) {
|
||||
dbg("Skipping %s plugin in rescue mode.", __FILE__);
|
||||
dbg("Skipping %s plugin in rescue mode.", "x11-common");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ static void setup(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
.name = __FILE__,
|
||||
.name = "x11-common",
|
||||
.hook[HOOK_SVC_PLUGIN] = { .cb = setup },
|
||||
};
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ finit_SOURCES = api.c cgroup.c cgroup.h \
|
||||
exec.c finit.c finit.h \
|
||||
stty.c \
|
||||
helpers.c helpers.h \
|
||||
initramfs.c \
|
||||
iwatch.c iwatch.h \
|
||||
log.c log.h \
|
||||
mdadm.c mount.c \
|
||||
|
||||
@@ -112,6 +112,8 @@ static int restart(svc_t *svc, void *user_data)
|
||||
|
||||
static int reload(svc_t *svc, void *user_data)
|
||||
{
|
||||
char cond[MAX_COND_LEN];
|
||||
|
||||
(void)user_data;
|
||||
|
||||
if (!svc)
|
||||
@@ -122,6 +124,25 @@ static int reload(svc_t *svc, void *user_data)
|
||||
else
|
||||
service_timeout_cancel(svc);
|
||||
|
||||
/*
|
||||
* Clear conditions before reload to ensure dependent services
|
||||
* are properly updated. Only needed when the service does NOT
|
||||
* support SIGHUP (noreload), because then it will be stopped
|
||||
* and restarted, so conditions genuinely go away. When the
|
||||
* service handles SIGHUP, its PID and pidfile persist, so the
|
||||
* condition stays valid and dependents should not be disrupted.
|
||||
*
|
||||
* Note: only clear 'ready' for services where the pidfile
|
||||
* inotify handler reasserts it (pid/none). For s6/systemd
|
||||
* services readiness relies on their respective notification
|
||||
* mechanism which may not re-trigger on SIGHUP.
|
||||
*/
|
||||
if (svc_is_noreload(svc)) {
|
||||
cond_clear(mkcond(svc, cond, sizeof(cond)));
|
||||
if (svc->notify == SVC_NOTIFY_PID || svc->notify == SVC_NOTIFY_NONE)
|
||||
service_ready(svc, 0);
|
||||
}
|
||||
|
||||
svc_mark_dirty(svc);
|
||||
service_step(svc);
|
||||
|
||||
@@ -245,6 +266,46 @@ static void bypass_shutdown(void *unused)
|
||||
do_shutdown(halt);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle switch_root API command.
|
||||
* Parses data: "newroot\0newinit\0"
|
||||
* Sends ACK before attempting switch_root since it doesn't return on success.
|
||||
* Returns: result from switch_root() on failure, doesn't return on success.
|
||||
*/
|
||||
static int do_switch_root_api(int sd, struct init_request *rq)
|
||||
{
|
||||
char *newroot, *newinit = NULL;
|
||||
char *ptr;
|
||||
int result;
|
||||
|
||||
dbg("switch-root %s", rq->data);
|
||||
strterm(rq->data, sizeof(rq->data));
|
||||
|
||||
newroot = rq->data;
|
||||
ptr = strchr(newroot, '\0');
|
||||
if (ptr && ptr < rq->data + sizeof(rq->data) - 1) {
|
||||
ptr++;
|
||||
if (*ptr)
|
||||
newinit = ptr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Send ACK first, since we won't return from
|
||||
* switch_root() on success.
|
||||
*/
|
||||
rq->cmd = INIT_CMD_ACK;
|
||||
if (write(sd, rq, sizeof(*rq)) != sizeof(*rq))
|
||||
dbg("Failed sending ACK to client");
|
||||
close(sd);
|
||||
|
||||
/* This does not return on success */
|
||||
result = switch_root(newroot, newinit);
|
||||
if (result)
|
||||
logit(LOG_ERR, "switch_root failed: %s", strerror(errno));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int do_reboot(int cmd, int timeout, char *buf, size_t len)
|
||||
{
|
||||
int rc = 1;
|
||||
@@ -403,6 +464,15 @@ static void api_cb(uev_t *w, void *arg, int events)
|
||||
rq.cmd, rq.data);
|
||||
goto leave;
|
||||
}
|
||||
break;
|
||||
|
||||
case INIT_CMD_SWITCH_ROOT:
|
||||
if (runlevel != INIT_LEVEL && runlevel != 1) {
|
||||
warnx("switch-root only allowed in runlevel S or 1");
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -503,6 +573,10 @@ static void api_cb(uev_t *w, void *arg, int events)
|
||||
result = do_reboot(rq.cmd, rq.sleeptime, rq.data, sizeof(rq.data));
|
||||
break;
|
||||
|
||||
case INIT_CMD_SWITCH_ROOT:
|
||||
do_switch_root_api(sd, &rq);
|
||||
goto leave;
|
||||
|
||||
case INIT_CMD_ACK:
|
||||
dbg("Client failed reading ACK");
|
||||
goto leave;
|
||||
|
||||
+36
-8
@@ -34,6 +34,8 @@
|
||||
#endif
|
||||
#include <sys/mount.h>
|
||||
#include <sys/sysinfo.h> /* get_nprocs_conf() */
|
||||
#include <sys/vfs.h>
|
||||
#include <linux/magic.h>
|
||||
|
||||
#include "cgroup.h"
|
||||
#include "finit.h"
|
||||
@@ -188,7 +190,8 @@ int cgroup_move_pid(const char *group, const char *name, int pid, int delegate)
|
||||
}
|
||||
|
||||
if (rc) {
|
||||
warn("Failed moving pid %d to cgroup %s", pid, path);
|
||||
if (errno != ESRCH)
|
||||
warn("Failed moving pid %d to cgroup %s", pid, path);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -377,7 +380,8 @@ static int cgroup_leaf_init(const char *group, const char *name, int pid, const
|
||||
if (!strcmp(group, "root"))
|
||||
return fnwrite(str("%d", pid), FINIT_CGPATH "/cgroup.procs");
|
||||
|
||||
if (cgroup_move_pid(group, name, pid, delegate))
|
||||
/* Error if error is not "No such process" */
|
||||
if (cgroup_move_pid(group, name, pid, delegate) && errno != ESRCH)
|
||||
err(1, "Failed moving pid %d to group %s/%s", pid, group, name);
|
||||
|
||||
/* Set up inotify watch for cgroup cleanup */
|
||||
@@ -841,6 +845,7 @@ void cgroup_config(void)
|
||||
void cgroup_init(uev_ctx_t *ctx)
|
||||
{
|
||||
int opts = MS_NODEV | MS_NOEXEC | MS_NOSUID;
|
||||
int mounted = 0;
|
||||
char buf[80];
|
||||
FILE *fp;
|
||||
int fd;
|
||||
@@ -851,14 +856,36 @@ void cgroup_init(uev_ctx_t *ctx)
|
||||
#endif
|
||||
|
||||
if (mount("none", FINIT_CGPATH, "cgroup2", opts, NULL)) {
|
||||
if (errno == ENOENT)
|
||||
if (errno == EBUSY) {
|
||||
/*
|
||||
* Already mounted - this happens after switch_root
|
||||
* when cgroups were moved from the initramfs.
|
||||
* Verify it's actually cgroup2 before proceeding.
|
||||
*/
|
||||
struct statfs sfs;
|
||||
|
||||
if (statfs(FINIT_CGPATH, &sfs) || sfs.f_type != CGROUP2_SUPER_MAGIC) {
|
||||
logit(LOG_ERR, "Mount point %s busy but not cgroup2", FINIT_CGPATH);
|
||||
avail = 0;
|
||||
return;
|
||||
}
|
||||
dbg("cgroup2 already mounted at %s, reusing", FINIT_CGPATH);
|
||||
} else if (errno == ENOENT) {
|
||||
logit(LOG_INFO, "Kernel does not support cgroups v2, disabling.");
|
||||
else if (errno == EPERM) /* Probably inside an unprivileged container */
|
||||
avail = 0;
|
||||
return;
|
||||
} else if (errno == EPERM) {
|
||||
/* Probably inside an unprivileged container */
|
||||
logit(LOG_INFO, "Not allowed to mount cgroups v2, disabling.");
|
||||
else
|
||||
avail = 0;
|
||||
return;
|
||||
} else {
|
||||
err(1, "Failed mounting cgroup v2");
|
||||
avail = 0;
|
||||
return;
|
||||
avail = 0;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
mounted = 1;
|
||||
}
|
||||
avail = 1;
|
||||
|
||||
@@ -867,7 +894,8 @@ void cgroup_init(uev_ctx_t *ctx)
|
||||
if (!fp) {
|
||||
err(1, "Failed opening %s", FINIT_CGPATH "/cgroup.controllers");
|
||||
abort:
|
||||
umount(FINIT_CGPATH);
|
||||
if (mounted)
|
||||
umount(FINIT_CGPATH);
|
||||
avail = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
+4
-9
@@ -37,7 +37,7 @@
|
||||
|
||||
#define CDIM plain ? "" : "\e[2m"
|
||||
#define CRST plain ? "" : "\e[0m"
|
||||
#define CLREOL plain ? "" : "\e[K" /* Clear to end of line */
|
||||
#define CLREOL plain ? "" : "\e[K" /* Clear to end of line */
|
||||
|
||||
#define NONE " "
|
||||
#define PIPE plain ? "| " : "│ "
|
||||
@@ -630,14 +630,9 @@ int cgroup_tree(char *path, char *pfx, int mode, int pos)
|
||||
strlcat(row, ++i == num ? END : FORK, rlen);
|
||||
|
||||
snprintf(proc, sizeof(proc), " %d %s", pid, cmdline);
|
||||
|
||||
if (plain) {
|
||||
strlcat(row, proc, rlen);
|
||||
} else {
|
||||
strlcat(row, CDIM, rlen);
|
||||
strlcat(row, proc, rlen);
|
||||
strlcat(row, CRST, sizeof(row));
|
||||
}
|
||||
strlcat(row, CDIM, rlen);
|
||||
strlcat(row, proc, rlen);
|
||||
strlcat(row, CRST, sizeof(row));
|
||||
|
||||
printf("\r%s%s\n", row, CLREOL);
|
||||
pos++;
|
||||
|
||||
+5
-4
@@ -37,6 +37,11 @@
|
||||
#include "finit.h"
|
||||
#include "log.h"
|
||||
|
||||
/* clone3() was added in Linux 5.3, syscall number 435 on all archs */
|
||||
#ifndef __NR_clone3
|
||||
#define __NR_clone3 435
|
||||
#endif
|
||||
|
||||
/* Clone3 flags from linux/sched.h */
|
||||
#ifndef CLONE_INTO_CGROUP
|
||||
#define CLONE_INTO_CGROUP 0x200000000ULL
|
||||
@@ -57,11 +62,7 @@ struct clone3_args {
|
||||
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)
|
||||
{
|
||||
|
||||
+15
-11
@@ -121,9 +121,6 @@ static uev_t etcw;
|
||||
|
||||
static TAILQ_HEAD(, conf_change) conf_change_list = TAILQ_HEAD_INITIALIZER(conf_change_list);
|
||||
|
||||
static char *path;
|
||||
static char *shell;
|
||||
|
||||
static int parse_conf(char *file, int is_rcsd);
|
||||
static void drop_changes(void);
|
||||
|
||||
@@ -377,8 +374,6 @@ void conf_parse_cmdline(int argc, char *argv[])
|
||||
fstab = strdup(ptr);
|
||||
finit_conf = strdup(FINIT_CONF);
|
||||
finit_rcsd = strdup(FINIT_RCSD);
|
||||
path = getenv("PATH");
|
||||
shell = getenv("SHELL");
|
||||
|
||||
for (int i = 1; i < argc; i++)
|
||||
parse_arg(argv[i]);
|
||||
@@ -404,13 +399,9 @@ void conf_reset_env(void)
|
||||
free(node);
|
||||
}
|
||||
|
||||
if (path)
|
||||
setenv("PATH", path, 1);
|
||||
else
|
||||
if (!getenv("PATH"))
|
||||
setenv("PATH", _PATH_STDPATH, 1);
|
||||
if (shell)
|
||||
setenv("SHELL", shell, 1);
|
||||
else
|
||||
if (!getenv("SHELL"))
|
||||
setenv("SHELL", _PATH_BSHELL, 1);
|
||||
setenv("LOGNAME", "root", 1);
|
||||
setenv("USER", "root", 1);
|
||||
@@ -745,8 +736,21 @@ void conf_parse_cond(svc_t *svc, char *cond)
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* The '~' prefix means a reload of the upstream service is
|
||||
* propagated to this service -- it will be reloaded (SIGHUP)
|
||||
* or restarted (noreload), not just paused and resumed.
|
||||
* Syntax: <!~pid/foo,~pid/bar> or <~pid/foo>
|
||||
*
|
||||
* Strip '~' from each condition and set the service-level
|
||||
* flag. This allows '~' on any condition in the list.
|
||||
*/
|
||||
svc->cond[0] = 0;
|
||||
for (i = 0, c = strtok(ptr, ","); c; c = strtok(NULL, ","), i++) {
|
||||
if (c[0] == '~') {
|
||||
c++;
|
||||
svc->flux_reload = 1;
|
||||
}
|
||||
devmon_add_cond(c);
|
||||
if (i)
|
||||
strlcat(svc->cond, ",", sizeof(svc->cond));
|
||||
|
||||
+20
-1
@@ -75,6 +75,7 @@ static void drop_node(struct dev_node *node)
|
||||
void devmon_add_cond(const char *cond)
|
||||
{
|
||||
struct dev_node *node;
|
||||
char path[PATH_MAX];
|
||||
|
||||
if (!cond || strncmp(cond, "dev/", 4)) {
|
||||
// dbg("no match %s", cond ?: "<NIL>");
|
||||
@@ -103,16 +104,34 @@ void devmon_add_cond(const char *cond)
|
||||
node->refcnt = 1;
|
||||
|
||||
TAILQ_INSERT_TAIL(&dev_node_list, node, link);
|
||||
|
||||
snprintf(path, sizeof(path), "/%s", cond);
|
||||
if (fexist(path))
|
||||
cond_set(cond);
|
||||
}
|
||||
|
||||
void devmon_del_cond(const char *cond)
|
||||
{
|
||||
if (!cond || strcmp(cond, "dev/"))
|
||||
if (!cond || strncmp(cond, COND_DEV, strlen(COND_DEV)))
|
||||
return;
|
||||
|
||||
drop_node(find_node(cond));
|
||||
}
|
||||
|
||||
void devmon_reconf(void)
|
||||
{
|
||||
struct dev_node *node, *tmp;
|
||||
char path[PATH_MAX];
|
||||
|
||||
TAILQ_FOREACH_SAFE(node, &dev_node_list, link, tmp) {
|
||||
snprintf(path, sizeof(path), "/%s", node->name);
|
||||
if (fexist(path))
|
||||
cond_set(node->name);
|
||||
else
|
||||
cond_clear(node->name);
|
||||
}
|
||||
}
|
||||
|
||||
static int devmon_add_path(struct iwatch *iw, char *path)
|
||||
{
|
||||
char *ptr;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
void devmon_add_cond(const char *cond);
|
||||
void devmon_del_cond(const char *cond);
|
||||
|
||||
void devmon_reconf(void);
|
||||
void devmon_init(uev_ctx_t *ctx);
|
||||
|
||||
#endif /* FINIT_DEVMON_H_ */
|
||||
|
||||
@@ -286,6 +286,12 @@ static void prepare_tty(char *tty, speed_t speed, const char *procname, struct r
|
||||
char name[80];
|
||||
int fd, dummy;
|
||||
|
||||
/* Set proper protections and ownership */
|
||||
if (chown(tty, 0, 0))
|
||||
logit(LOG_ERR, "failed chowning 0:0 %s: %s", tty, strerror(errno));
|
||||
if (chmod(tty, 0620))
|
||||
logit(LOG_ERR, "failed chmoding 620 %s: %s", tty, strerror(errno));
|
||||
|
||||
fd = open(tty, O_RDWR);
|
||||
if (fd < 0) {
|
||||
logit(LOG_ERR, "Failed opening %s: %s", tty, strerror(errno));
|
||||
|
||||
+32
-8
@@ -39,6 +39,7 @@
|
||||
#include <sys/sysmacros.h>
|
||||
#endif
|
||||
#include <sys/prctl.h>
|
||||
#include <paths.h>
|
||||
#ifdef _LIBITE_LITE
|
||||
# include <libite/lite.h>
|
||||
#else
|
||||
@@ -60,8 +61,9 @@
|
||||
#include "util.h"
|
||||
#include "utmp-api.h"
|
||||
|
||||
uev_ctx_t *ctx = NULL; /* Main loop context */
|
||||
svc_t *wdog = NULL; /* No watchdog by default */
|
||||
uev_ctx_t *ctx; /* Main loop context */
|
||||
svc_t *wdog; /* No watchdog by default */
|
||||
char *arg0; /* Saved for setprocnm() */
|
||||
|
||||
|
||||
/*
|
||||
@@ -368,10 +370,9 @@ static void fs_finalize(void)
|
||||
fs_mount("shm", "/dev/shm", "tmpfs", flags | MS_NODEV, "mode=1777");
|
||||
}
|
||||
|
||||
/* Modern systems use /dev/pts */
|
||||
if (!fismnt("/dev/pts")) {
|
||||
/* Remount devpts with proper gid/mode now that /etc/group is available */
|
||||
{
|
||||
char opts[32];
|
||||
int mode;
|
||||
int gid;
|
||||
|
||||
gid = getgroup("tty");
|
||||
@@ -379,11 +380,11 @@ static void fs_finalize(void)
|
||||
gid = 0;
|
||||
|
||||
/* 0600 is default on Debian, use 0620 to get mesg y by default */
|
||||
mode = 0620;
|
||||
snprintf(opts, sizeof(opts), "gid=%d,mode=%d,ptmxmode=0666", gid, mode);
|
||||
snprintf(opts, sizeof(opts), "gid=%d,mode=0620,ptmxmode=0666", gid);
|
||||
|
||||
makedir("/dev/pts", 0755);
|
||||
fs_mount("devpts", "/dev/pts", "devpts", flags, opts);
|
||||
fs_mount("devpts", "/dev/pts", "devpts",
|
||||
flags | MS_REMOUNT, opts);
|
||||
}
|
||||
|
||||
/* Needed on systems like Alpine Linux */
|
||||
@@ -523,8 +524,19 @@ static void fs_init(void)
|
||||
if (fismnt(fs[i].file))
|
||||
continue;
|
||||
|
||||
/* Create mount point if it doesn't exist */
|
||||
if (mkdir(fs[i].file, 0755) && errno != EEXIST)
|
||||
warn("Failed creating mountpoint %s: %s", fs[i].file, strerror(errno));
|
||||
|
||||
fs_mount(fs[i].spec, fs[i].file, fs[i].type, 0, NULL);
|
||||
}
|
||||
|
||||
/* Early devpts mount for plugins that need PTY access (e.g. plymouth) */
|
||||
if (!fismnt("/dev/pts")) {
|
||||
makedir("/dev/pts", 0755);
|
||||
fs_mount("devpts", "/dev/pts", "devpts",
|
||||
MS_NOSUID | MS_NOEXEC, "ptmxmode=0666");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -610,6 +622,9 @@ int main(int argc, char *argv[])
|
||||
{
|
||||
uev_ctx_t loop;
|
||||
|
||||
/* Save argv for setprocnm() in child processes */
|
||||
arg0 = argv[0];
|
||||
|
||||
/* user calling telinit or init */
|
||||
if (getpid() != 1) {
|
||||
if (geteuid() == 0)
|
||||
@@ -624,6 +639,15 @@ int main(int argc, char *argv[])
|
||||
return EX_NOPERM;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sanity check critical environment variables. We need PATH
|
||||
* and SHELL for early commands like mount in fs_init().
|
||||
*/
|
||||
if (!checkenv("PATH"))
|
||||
setenv("PATH", _PATH_STDPATH, 1);
|
||||
if (!checkenv("SHELL"))
|
||||
setenv("SHELL", _PATH_BSHELL, 1);
|
||||
|
||||
/*
|
||||
* Need /dev, /proc, and /sys for console=, remount and cgroups
|
||||
*/
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
#define INIT_CMD_HALT 21
|
||||
#define INIT_CMD_POWEROFF 22
|
||||
#define INIT_CMD_SUSPEND 23
|
||||
#define INIT_CMD_SWITCH_ROOT 24 /* Switch to new root filesystem */
|
||||
#define INIT_CMD_WDOG_HELLO 128 /* Watchdog register and hello */
|
||||
#define INIT_CMD_SVC_ITER 129
|
||||
#define INIT_CMD_SVC_QUERY 130
|
||||
|
||||
+86
-6
@@ -32,6 +32,7 @@
|
||||
#include <stdarg.h>
|
||||
#include <sysexits.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/prctl.h>
|
||||
#ifdef _LIBITE_LITE
|
||||
# include <libite/lite.h>
|
||||
#else
|
||||
@@ -49,6 +50,7 @@
|
||||
|
||||
static pstyle_t progress_onoff = PROGRESS_DEFAULT;
|
||||
static pstyle_t progress_style = PROGRESS_DEFAULT;
|
||||
static char last_desc[512]; /* saved description for re-print after interruption */
|
||||
|
||||
#ifndef HOSTNAME_PATH
|
||||
#define HOSTNAME_PATH "/etc/hostname"
|
||||
@@ -335,6 +337,8 @@ void printv(const char *fmt, va_list ap)
|
||||
len = print_timestamp(buf, sizeof(buf));
|
||||
vsnprintf(&buf[len], sizeof(buf) - len, fmt, ap);
|
||||
|
||||
strlcpy(last_desc, &buf[len], sizeof(last_desc)); /* save for re-print */
|
||||
|
||||
if (progress_style == PROGRESS_CLASSIC)
|
||||
cprintf("\r%s ", pad(buf, sizeof(buf), ".", sizeof(buf)));
|
||||
else
|
||||
@@ -343,24 +347,65 @@ void printv(const char *fmt, va_list ap)
|
||||
|
||||
void print(int rc, const char *fmt, ...)
|
||||
{
|
||||
char buf[ttcols];
|
||||
size_t len;
|
||||
|
||||
if (progress_style == PROGRESS_SILENT)
|
||||
return;
|
||||
|
||||
buf[0] = 0;
|
||||
len = print_timestamp(buf, sizeof(buf));
|
||||
|
||||
if (fmt) {
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
printv(fmt, ap);
|
||||
vsnprintf(&buf[len], sizeof(buf) - len, fmt, ap);
|
||||
va_end(ap);
|
||||
strlcpy(last_desc, &buf[len], sizeof(last_desc));
|
||||
} else if (rc >= 0 && last_desc[0]) {
|
||||
/*
|
||||
* No new description, but we have one saved from an earlier
|
||||
* print(-1, ...) call. Re-print it so the final status is not
|
||||
* left stranded if command output or a kernel message scrolled
|
||||
* away the original description line.
|
||||
*/
|
||||
strlcpy(&buf[len], last_desc, sizeof(buf) - len);
|
||||
} else {
|
||||
buf[0] = 0;
|
||||
}
|
||||
|
||||
if (rc < 0)
|
||||
if (rc < 0) {
|
||||
/* Pending state: show description with spinner, no final status yet */
|
||||
if (!buf[0])
|
||||
return;
|
||||
delline();
|
||||
if (progress_style == PROGRESS_CLASSIC)
|
||||
cprintf("\r%s ", pad(buf, sizeof(buf), ".", sizeof(buf)));
|
||||
else
|
||||
cprintf("\r\e[K%s%s", status(3), buf);
|
||||
return;
|
||||
}
|
||||
|
||||
if (progress_style == PROGRESS_CLASSIC)
|
||||
cprintf("%s\n", status(rc));
|
||||
else
|
||||
cprintf("\r%s\n", status(rc));
|
||||
/*
|
||||
* Final status. Emit description + status in a single cprintf() so
|
||||
* both reach the console in one write(), preventing kernel messages or
|
||||
* command output from splitting the description from its [ OK ]/[FAIL].
|
||||
*/
|
||||
last_desc[0] = 0;
|
||||
|
||||
if (buf[0]) {
|
||||
delline();
|
||||
if (progress_style == PROGRESS_CLASSIC)
|
||||
cprintf("\r%s %s\n", pad(buf, sizeof(buf), ".", sizeof(buf)), status(rc));
|
||||
else
|
||||
cprintf("\r\e[K%s%s\r%s\n", status(3), buf, status(rc));
|
||||
} else {
|
||||
if (progress_style == PROGRESS_CLASSIC)
|
||||
cprintf("%s\n", status(rc));
|
||||
else
|
||||
cprintf("\r%s\n", status(rc));
|
||||
}
|
||||
}
|
||||
|
||||
void print_desc(char *action, char *desc)
|
||||
@@ -374,6 +419,18 @@ int print_result(int fail)
|
||||
return fail;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reset console state and drain the output buffer before kernel takeover.
|
||||
* Called just before reboot()/halt() to prevent ANSI escape codes from
|
||||
* leaking into bootloader or early-kernel output.
|
||||
*/
|
||||
void print_exit(void)
|
||||
{
|
||||
tcdrain(STDERR_FILENO);
|
||||
dprint(STDERR_FILENO, "\e[0m\e[?25h", 10); /* reset SGR, show cursor */
|
||||
tcdrain(STDERR_FILENO);
|
||||
}
|
||||
|
||||
void set_hostname(char **hostname)
|
||||
{
|
||||
FILE *fp;
|
||||
@@ -527,6 +584,29 @@ int in_container(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* setprocnm - Set process name in argv[0] for ps/pidof
|
||||
* @name: New process name to set
|
||||
*
|
||||
* Changes the process name by modifying argv[0]. This is particularly
|
||||
* useful for compatibility with BusyBox's pidof which reads cmdline
|
||||
* from /proc/pid/cmdline rather than /proc/pid/comm.
|
||||
*/
|
||||
void setprocnm(const char *name)
|
||||
{
|
||||
extern char *arg0;
|
||||
size_t len;
|
||||
|
||||
if (!arg0 || !name)
|
||||
return;
|
||||
|
||||
prctl(PR_SET_NAME, name, 0, 0, 0);
|
||||
|
||||
len = strlen(arg0);
|
||||
memset(arg0, 0, len);
|
||||
strlcpy(arg0, name, len + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Local Variables:
|
||||
* indent-tabs-mode: t
|
||||
|
||||
@@ -68,10 +68,12 @@ void printv (const char *fmt, va_list ap);
|
||||
void print (int action, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
|
||||
void print_desc (char *action, char *desc);
|
||||
int print_result (int fail);
|
||||
void print_exit (void);
|
||||
|
||||
void set_hostname (char **hostname);
|
||||
void networking (int updown);
|
||||
int in_container (void);
|
||||
void setprocnm (const char *name);
|
||||
|
||||
int complete (char *cmd, int pid);
|
||||
int run (char *cmd, char *log);
|
||||
@@ -82,6 +84,17 @@ pid_t run_sh (char *tty, int noclear, int nowait, struct rlimit rlimi
|
||||
pid_t run_bg (char *cmd, char *args[]);
|
||||
int run_parts (char *dir, char *cmd, const char *env[], int progress, int sysv);
|
||||
|
||||
|
||||
static inline int checkenv(const char *env)
|
||||
{
|
||||
const char *val = getenv(env);
|
||||
|
||||
if (!val || !val[0])
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline int dprint(int fd, const char *s, size_t len)
|
||||
{
|
||||
size_t loop = 3;
|
||||
|
||||
+125
-10
@@ -646,6 +646,59 @@ int do_halt (char *arg) { return do_cmd(INIT_CMD_HALT); }
|
||||
int do_poweroff(char *arg) { return do_cmd(INIT_CMD_POWEROFF); }
|
||||
int do_suspend (char *arg) { return do_cmd(INIT_CMD_SUSPEND); }
|
||||
|
||||
/**
|
||||
* do_switch_root - Switch to a new root filesystem (initramfs only)
|
||||
* @argc: Number of arguments (1 or 2)
|
||||
* @argv: [0] = newroot path, [1] = optional init path
|
||||
*
|
||||
* This command is only valid during runlevel S (bootstrap) or 1 when
|
||||
* running from an initramfs. It stops all services, moves virtual
|
||||
* filesystems, and exec's the new init.
|
||||
*/
|
||||
int do_switch_root(int argc, char *argv[])
|
||||
{
|
||||
struct init_request rq = {
|
||||
.magic = INIT_MAGIC,
|
||||
.cmd = INIT_CMD_SWITCH_ROOT,
|
||||
};
|
||||
char *newroot, *newinit = NULL;
|
||||
size_t off;
|
||||
|
||||
if (argc < 1)
|
||||
ERRX(2, "Usage: initctl switch-root NEWROOT [INIT]");
|
||||
|
||||
newroot = argv[0];
|
||||
if (argc > 1)
|
||||
newinit = argv[1];
|
||||
|
||||
/* Verify newroot exists */
|
||||
if (access(newroot, F_OK))
|
||||
ERR(1, "Cannot access %s", newroot);
|
||||
|
||||
/*
|
||||
* Pack both strings into rq.data with null separators:
|
||||
* "newroot\0newinit\0"
|
||||
*/
|
||||
strlcpy(rq.data, newroot, sizeof(rq.data));
|
||||
off = strlen(newroot) + 1;
|
||||
|
||||
if (newinit && off < sizeof(rq.data) - 1)
|
||||
strlcpy(rq.data + off, newinit, sizeof(rq.data) - off);
|
||||
|
||||
printf("Switching root to %s", newroot);
|
||||
if (newinit)
|
||||
printf(", init %s", newinit);
|
||||
printf(" ...\n");
|
||||
|
||||
/*
|
||||
* On success, finit exec's new init and we lose connection.
|
||||
* A "failure" to read reply is actually expected on success.
|
||||
*/
|
||||
client_send(&rq, sizeof(rq));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int utmp_show(char *file)
|
||||
{
|
||||
struct utmp *ut;
|
||||
@@ -983,6 +1036,62 @@ static int svc_compare(svc_t *svc, char *arg)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Escape a string for safe JSON output. Handles quotes, backslashes,
|
||||
* and control characters. Returns pointer to static buffer.
|
||||
*/
|
||||
static char *json_escape(const char *str)
|
||||
{
|
||||
static char buf[1024];
|
||||
char *ptr = buf;
|
||||
size_t left = sizeof(buf) - 1;
|
||||
|
||||
if (!str)
|
||||
return "";
|
||||
|
||||
while (*str && left > 1) {
|
||||
char c = *str++;
|
||||
|
||||
switch (c) {
|
||||
case '"':
|
||||
case '\\':
|
||||
*ptr++ = '\\';
|
||||
*ptr++ = c;
|
||||
left -= 2;
|
||||
break;
|
||||
case '\n':
|
||||
*ptr++ = '\\';
|
||||
*ptr++ = 'n';
|
||||
left -= 2;
|
||||
break;
|
||||
case '\r':
|
||||
*ptr++ = '\\';
|
||||
*ptr++ = 'r';
|
||||
left -= 2;
|
||||
break;
|
||||
case '\t':
|
||||
*ptr++ = '\\';
|
||||
*ptr++ = 't';
|
||||
left -= 2;
|
||||
break;
|
||||
default:
|
||||
if ((unsigned char)c < 0x20) {
|
||||
/* Other control chars: use \uXXXX */
|
||||
int n = snprintf(ptr, left, "\\u%04x", (unsigned char)c);
|
||||
ptr += n;
|
||||
left -= n;
|
||||
} else {
|
||||
*ptr++ = c;
|
||||
left--;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
*ptr = '\0';
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int json_status_one(FILE *fp, svc_t *svc, char *indent, int prev)
|
||||
{
|
||||
long now = jiffies();
|
||||
@@ -998,14 +1107,16 @@ static int json_status_one(FILE *fp, svc_t *svc, char *indent, int prev)
|
||||
fprintf(fp,
|
||||
"%s"
|
||||
"%s{\n"
|
||||
"%s \"identity\": \"%s\",\n"
|
||||
"%s \"description\": \"%s\",\n"
|
||||
"%s \"identity\": \"%s\",\n",
|
||||
prev ? ",\n" : indent, prev ? indent : "",
|
||||
indent, svc_ident(svc, NULL, 0));
|
||||
fprintf(fp,
|
||||
"%s \"description\": \"%s\",\n",
|
||||
indent, json_escape(svc->desc));
|
||||
fprintf(fp,
|
||||
"%s \"type\": \"%s\",\n"
|
||||
"%s \"forking\": %s,\n"
|
||||
"%s \"status\": \"%s\",\n",
|
||||
prev ? ",\n" : indent, prev ? indent : "",
|
||||
indent, svc_ident(svc, NULL, 0),
|
||||
indent, svc->desc,
|
||||
indent, svc_typestr(svc),
|
||||
indent, svc->forking ? "true" : "false",
|
||||
indent, svc_status(svc));
|
||||
@@ -1027,15 +1138,17 @@ static int json_status_one(FILE *fp, svc_t *svc, char *indent, int prev)
|
||||
}
|
||||
|
||||
fprintf(fp,
|
||||
"%s \"origin\": \"%s\",\n"
|
||||
"%s \"origin\": \"%s\",\n",
|
||||
indent, svc->file[0] ? svc->file : "built-in");
|
||||
svc_command(svc, buf, sizeof(buf), 0);
|
||||
fprintf(fp,
|
||||
"%s \"command\": \"%s\",\n",
|
||||
indent, svc->file[0] ? svc->file : "built-in",
|
||||
indent, svc_command(svc, buf, sizeof(buf), 0));
|
||||
indent, json_escape(buf));
|
||||
|
||||
svc_environ(svc, buf, sizeof(buf), 0);
|
||||
if (buf[0])
|
||||
fprintf(fp,
|
||||
"%s \"environment\": \"%s\",\n", indent, buf);
|
||||
"%s \"environment\": \"%s\",\n", indent, json_escape(buf));
|
||||
|
||||
svc_cond(svc, buf, sizeof(buf), 0);
|
||||
if (buf[0])
|
||||
@@ -1474,7 +1587,8 @@ static int usage(int rc)
|
||||
" reboot Reboot system\n"
|
||||
" halt Halt system\n"
|
||||
" poweroff Halt and power off system\n"
|
||||
" suspend Suspend system\n");
|
||||
" suspend Suspend system\n"
|
||||
" switch-root ROOT [INIT] Switch to new root filesystem (initramfs)\n");
|
||||
|
||||
if (utmp)
|
||||
fprintf(stderr,
|
||||
@@ -1654,6 +1768,7 @@ int main(int argc, char *argv[])
|
||||
{ "halt", NULL, do_halt, NULL, NULL },
|
||||
{ "poweroff", NULL, do_poweroff, NULL, NULL },
|
||||
{ "suspend", NULL, do_suspend, NULL, NULL },
|
||||
{ "switch-root", NULL, NULL, NULL, do_switch_root },
|
||||
|
||||
{ "utmp", NULL, do_utmp, &utmp, NULL },
|
||||
{ NULL, NULL, NULL, NULL, NULL }
|
||||
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
/* Initramfs switch_root support
|
||||
*
|
||||
* Copyright (c) 2008-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" /* Generated by configure script */
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <ftw.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/vfs.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <linux/magic.h>
|
||||
#ifdef _LIBITE_LITE
|
||||
# include <libite/lite.h>
|
||||
#else
|
||||
# include <lite/lite.h>
|
||||
#endif
|
||||
|
||||
#include "finit.h"
|
||||
#include "cond.h"
|
||||
#include "helpers.h"
|
||||
#include "log.h"
|
||||
#include "plugin.h"
|
||||
#include "private.h"
|
||||
#include "sig.h"
|
||||
#include "util.h"
|
||||
|
||||
/* State for delete_cb(), nftw() doesn't support passing user data */
|
||||
static const char *delete_skip;
|
||||
static dev_t delete_rootdev;
|
||||
|
||||
/*
|
||||
* nftw() callback to delete initramfs contents before switch_root.
|
||||
* Skips the new root mount point and stays on the same filesystem.
|
||||
*/
|
||||
static int delete_cb(const char *fpath, const struct stat *sb, int tflag, struct FTW *ftw)
|
||||
{
|
||||
(void)tflag;
|
||||
|
||||
/* Skip root directory itself */
|
||||
if (ftw->level == 0)
|
||||
return 0;
|
||||
|
||||
/* Skip the new root mount point */
|
||||
if (delete_skip && !strncmp(fpath, delete_skip, strlen(delete_skip)))
|
||||
return 0;
|
||||
|
||||
/* Stay on the same filesystem (initramfs) */
|
||||
if (sb->st_dev != delete_rootdev)
|
||||
return 0;
|
||||
|
||||
if (remove(fpath))
|
||||
dbg("Failed to remove %s: %s", fpath, strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Recursively delete initramfs contents to free memory before switch_root.
|
||||
*/
|
||||
static void delete_initramfs_contents(dev_t rootdev, const char *skip)
|
||||
{
|
||||
delete_rootdev = rootdev;
|
||||
delete_skip = skip;
|
||||
|
||||
nftw("/", delete_cb, 20, FTW_DEPTH | FTW_PHYS | FTW_MOUNT);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if we're running on a tmpfs/ramfs (initramfs)
|
||||
*/
|
||||
static int is_initramfs(void)
|
||||
{
|
||||
struct statfs sfs;
|
||||
|
||||
if (statfs("/", &sfs))
|
||||
return 0;
|
||||
|
||||
return sfs.f_type == RAMFS_MAGIC || sfs.f_type == TMPFS_MAGIC;
|
||||
}
|
||||
|
||||
/*
|
||||
* Move a mount point from oldpath to newpath under newroot
|
||||
*/
|
||||
static int do_move_mount(const char *oldpath, const char *newroot)
|
||||
{
|
||||
char newpath[PATH_MAX];
|
||||
struct stat st;
|
||||
|
||||
if (stat(oldpath, &st))
|
||||
return 0; /* Not mounted, skip */
|
||||
|
||||
snprintf(newpath, sizeof(newpath), "%s%s", newroot, oldpath);
|
||||
|
||||
/* Create target directory if needed */
|
||||
makedir(newpath, 0755);
|
||||
|
||||
if (mount(oldpath, newpath, NULL, MS_MOVE, NULL)) {
|
||||
dbg("Failed to move %s to %s: %s", oldpath, newpath, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
dbg("Moved %s to %s", oldpath, newpath);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kill_cb(int pid, void *data)
|
||||
{
|
||||
int signo = *(int *)data;
|
||||
|
||||
kill(pid, signo);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform switch_root to a new root filesystem
|
||||
*
|
||||
* This function does not return on success - it exec's the new init.
|
||||
* On failure, it returns -1 and sets errno.
|
||||
*/
|
||||
int switch_root(const char *newroot, const char *newinit)
|
||||
{
|
||||
struct stat newroot_st, oldroot_st;
|
||||
char init_path[PATH_MAX];
|
||||
int console_fd;
|
||||
int fd;
|
||||
dev_t rootdev;
|
||||
int signo;
|
||||
|
||||
if (!newroot || !newroot[0]) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Default to /sbin/init if not specified */
|
||||
if (!newinit || !newinit[0])
|
||||
newinit = "/sbin/init";
|
||||
|
||||
/* Verify we're PID 1 */
|
||||
if (getpid() != 1) {
|
||||
logit(LOG_ERR, "switch_root must be run as PID 1");
|
||||
errno = EPERM;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Verify newroot exists and is a directory */
|
||||
fd = open(newroot, O_RDONLY | O_DIRECTORY);
|
||||
if (fd < 0) {
|
||||
logit(LOG_ERR, "switch_root: %s is not a directory", newroot);
|
||||
errno = ENOTDIR;
|
||||
return -1;
|
||||
}
|
||||
if (fstat(fd, &newroot_st)) {
|
||||
close(fd);
|
||||
logit(LOG_ERR, "switch_root: cannot stat %s", newroot);
|
||||
return -1;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
/* Verify newroot is a mount point (different device than parent) */
|
||||
fd = open("/", O_RDONLY | O_DIRECTORY);
|
||||
if (fd < 0) {
|
||||
logit(LOG_ERR, "switch_root: cannot open /");
|
||||
return -1;
|
||||
}
|
||||
if (fstat(fd, &oldroot_st)) {
|
||||
close(fd);
|
||||
logit(LOG_ERR, "switch_root: cannot stat /");
|
||||
return -1;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
if (newroot_st.st_dev == oldroot_st.st_dev) {
|
||||
logit(LOG_ERR, "switch_root: %s is not a mount point", newroot);
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Verify init exists in new root */
|
||||
snprintf(init_path, sizeof(init_path), "%s%s", newroot, newinit);
|
||||
if (access(init_path, X_OK)) {
|
||||
logit(LOG_ERR, "switch_root: %s not found or not executable", init_path);
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
logit(LOG_NOTICE, "Performing switch_root to %s, init %s", newroot, newinit);
|
||||
|
||||
/* Run switch_root hook before we start tearing things down */
|
||||
plugin_run_hooks(HOOK_SWITCH_ROOT);
|
||||
|
||||
/* Stop all services gracefully */
|
||||
dbg("Stopping all services...");
|
||||
halt = SHUT_OFF; /* Prevent actual shutdown */
|
||||
|
||||
/* Use existing shutdown logic to stop services */
|
||||
api_exit();
|
||||
log_exit();
|
||||
plugin_run_hooks(HOOK_SHUTDOWN);
|
||||
|
||||
/* Kill remaining processes (except kernel threads and ourselves) */
|
||||
signo = SIGTERM;
|
||||
iterate_proc(kill_cb, &signo);
|
||||
do_usleep(500000); /* Give them 500ms */
|
||||
|
||||
signo = SIGKILL;
|
||||
iterate_proc(kill_cb, &signo);
|
||||
|
||||
/* Reap zombies */
|
||||
while (waitpid(-1, NULL, WNOHANG) > 0)
|
||||
;
|
||||
|
||||
/* Exit plugins */
|
||||
plugin_exit();
|
||||
cond_exit();
|
||||
|
||||
/* Move virtual filesystems to new root */
|
||||
dbg("Moving virtual filesystems...");
|
||||
do_move_mount("/dev", newroot);
|
||||
do_move_mount("/proc", newroot);
|
||||
do_move_mount("/sys", newroot);
|
||||
do_move_mount("/run", newroot);
|
||||
|
||||
/* Change to new root directory */
|
||||
if (chdir(newroot)) {
|
||||
err(1, "Failed to chdir to %s", newroot);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Delete contents of old root if we're on initramfs */
|
||||
rootdev = oldroot_st.st_dev;
|
||||
if (is_initramfs()) {
|
||||
dbg("Deleting initramfs contents...");
|
||||
delete_initramfs_contents(rootdev, newroot);
|
||||
}
|
||||
|
||||
/* Mount --move newroot to / */
|
||||
if (mount(newroot, "/", NULL, MS_MOVE, NULL)) {
|
||||
err(1, "Failed to move %s to /", newroot);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* chroot to new root */
|
||||
if (chroot(".")) {
|
||||
err(1, "Failed to chroot to new root");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (chdir("/")) {
|
||||
err(1, "Failed to chdir to /");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Reopen console in new root. dup2() closes the old fds itself,
|
||||
* so open() returns a fd > STDERR_FILENO that we can always close. */
|
||||
console_fd = open("/dev/console", O_RDWR);
|
||||
if (console_fd >= 0) {
|
||||
dup2(console_fd, STDIN_FILENO);
|
||||
dup2(console_fd, STDOUT_FILENO);
|
||||
dup2(console_fd, STDERR_FILENO);
|
||||
close(console_fd);
|
||||
}
|
||||
|
||||
/* Reset signals to default */
|
||||
sig_unblock();
|
||||
|
||||
/* Exec the new init - this does not return on success */
|
||||
dbg("Executing %s...", newinit);
|
||||
execl(newinit, newinit, NULL);
|
||||
|
||||
/* If we get here, exec failed */
|
||||
err(1, "Failed to exec %s", newinit);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Local Variables:
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "linux"
|
||||
* End:
|
||||
*/
|
||||
@@ -81,6 +81,9 @@
|
||||
CHOOSE(HOOK_SVC_RECONF, "nop"), \
|
||||
CHOOSE(HOOK_RUNLEVEL_CHANGE, "nop"), \
|
||||
\
|
||||
/* Switch root hook, before transitioning */ \
|
||||
CHOOSE(HOOK_SWITCH_ROOT, "hook/sys/switchroot"), \
|
||||
\
|
||||
/* Shutdown hooks, runlevel [06] */ \
|
||||
CHOOSE(HOOK_NETWORK_DN, "hook/net/down"), \
|
||||
CHOOSE(HOOK_SHUTDOWN, "hook/sys/shutdown"), \
|
||||
|
||||
@@ -59,6 +59,9 @@ void plugin_script_run(hook_point_t no);
|
||||
int plugin_init (uev_ctx_t *ctx);
|
||||
void plugin_exit (void);
|
||||
|
||||
void iterate_proc (int (*cb)(int, void *), void *data);
|
||||
int switch_root (const char *newroot, const char *newinit);
|
||||
|
||||
#endif /* FINIT_PRIVATE_H_ */
|
||||
|
||||
/**
|
||||
|
||||
+321
-53
@@ -25,13 +25,16 @@
|
||||
#include "config.h" /* Generated by configure script */
|
||||
|
||||
#include <ctype.h> /* isblank() */
|
||||
#include <grp.h> /* setgroups() */
|
||||
#include <sched.h> /* sched_yield() */
|
||||
#include <string.h>
|
||||
#include <sys/reboot.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/wait.h>
|
||||
#include <linux/vt.h>
|
||||
#include <net/if.h>
|
||||
#ifdef _LIBITE_LITE
|
||||
# include <libite/lite.h>
|
||||
@@ -386,6 +389,43 @@ static int lredirect(svc_t *svc)
|
||||
return close(pipefd[1]);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set up a controlling TTY for run/task/service.
|
||||
* Opens the TTY device, dups to 0/1/2, resets termios to
|
||||
* sane defaults, sets TERM env, and updates procname.
|
||||
*/
|
||||
static void svc_prepare_ctty(const char *tty, const char *procname, int log)
|
||||
{
|
||||
int fd, dummy;
|
||||
|
||||
fd = open(tty, O_RDWR);
|
||||
if (fd < 0) {
|
||||
logit(LOG_ERR, "Failed opening %s: %s", tty, strerror(errno));
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
/* Acquire as controlling terminal for the session */
|
||||
ioctl(fd, TIOCSCTTY, 0);
|
||||
|
||||
dup2(fd, STDIN_FILENO);
|
||||
if (!log) {
|
||||
dup2(fd, STDOUT_FILENO);
|
||||
dup2(fd, STDERR_FILENO);
|
||||
}
|
||||
|
||||
/* Set default TERM */
|
||||
if (ioctl(fd, VT_OPENQRY, &dummy) == -1)
|
||||
setenv("TERM", "vt102", 1); /* likely a serial line */
|
||||
else
|
||||
setenv("TERM", "linux", 1);
|
||||
|
||||
/* Reset to sane defaults */
|
||||
stty(fd, B0);
|
||||
close(fd);
|
||||
|
||||
prctl(PR_SET_NAME, procname, 0, 0, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle redirection of process output, if enabled
|
||||
*/
|
||||
@@ -495,14 +535,21 @@ 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 */
|
||||
/*
|
||||
* Only use cap_setuid() if capabilities are configured,
|
||||
* otherwise it would (by design) drop all capabilities,
|
||||
* breaking root services
|
||||
*/
|
||||
if (svc->capabilities[0]) {
|
||||
cap_iab_t cap_iab = cap_iab_from_text(svc->capabilities);
|
||||
cap_iab_t cap_iab;
|
||||
|
||||
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 */
|
||||
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);
|
||||
@@ -515,11 +562,10 @@ static void set_uid(uid_t uid, svc_t *svc)
|
||||
svc_ident(svc, NULL, 0));
|
||||
}
|
||||
cap_free(cap_iab);
|
||||
}
|
||||
#else
|
||||
} else
|
||||
#endif
|
||||
if (setuid(uid))
|
||||
err(1, "%s: failed setuid(%d)", svc_ident(svc, NULL, 0), uid);
|
||||
#endif
|
||||
}
|
||||
|
||||
static pid_t service_fork(svc_t *svc)
|
||||
@@ -549,7 +595,21 @@ static pid_t service_fork(svc_t *svc)
|
||||
#else
|
||||
int uid = getuser(svc->username, &home);
|
||||
int gid = getgroup(svc->group);
|
||||
|
||||
/* Check for invalid user/group - fail rather than silent fallback */
|
||||
if (uid < 0) {
|
||||
err(1, "%s: user '%s' not found, cannot start service",
|
||||
svc_ident(svc, NULL, 0), svc->username);
|
||||
return -1;
|
||||
}
|
||||
if (gid < 0) {
|
||||
err(1, "%s: group '%s' not found, cannot start service",
|
||||
svc_ident(svc, NULL, 0), svc->group);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
if (svc_is_tty(svc))
|
||||
setprocnm("getty");
|
||||
|
||||
sched_yield();
|
||||
|
||||
@@ -560,6 +620,42 @@ static pid_t service_fork(svc_t *svc)
|
||||
svc_ident(svc, NULL, 0), rlim2str(i));
|
||||
}
|
||||
|
||||
#ifndef ENABLE_STATIC
|
||||
/* Set supplementary groups from /etc/group and config */
|
||||
{
|
||||
gid_t supgids[NGROUPS_MAX];
|
||||
int ngroups = NGROUPS_MAX;
|
||||
int i, j, n = 0;
|
||||
|
||||
/* Get user's supplementary groups from /etc/group */
|
||||
if (getgrouplist(svc->username, gid, supgids, &ngroups) >= 0)
|
||||
n = ngroups;
|
||||
|
||||
/* Add explicitly configured supplementary groups */
|
||||
for (i = 0; i < svc->num_supgroups && n < NGROUPS_MAX; i++) {
|
||||
int g = getgroup(svc->supgroups[i]);
|
||||
int found = 0;
|
||||
|
||||
if (g < 0) {
|
||||
warn("%s: unknown supplementary group '%s'",
|
||||
svc_ident(svc, NULL, 0), svc->supgroups[i]);
|
||||
continue;
|
||||
}
|
||||
/* Skip if already in list from /etc/group */
|
||||
for (j = 0; j < n; j++) {
|
||||
if (supgids[j] == (gid_t)g) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
supgids[n++] = g;
|
||||
}
|
||||
if (n > 0 && setgroups(n, supgids))
|
||||
err(1, "%s: failed setgroups()", svc_ident(svc, NULL, 0));
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Set desired user+group */
|
||||
if (gid >= 0) {
|
||||
if (setgid(gid))
|
||||
@@ -570,8 +666,11 @@ static pid_t service_fork(svc_t *svc)
|
||||
set_uid(uid, svc);
|
||||
|
||||
/* Set default path for regular users */
|
||||
if (uid > 0)
|
||||
if (uid > 0) {
|
||||
setenv("PATH", _PATH_DEFPATH, 1);
|
||||
setenv("USER", svc->username, 1);
|
||||
setenv("LOGNAME", svc->username, 1);
|
||||
}
|
||||
if (home) {
|
||||
setenv("HOME", home, 1);
|
||||
if (chdir(home)) {
|
||||
@@ -886,6 +985,14 @@ static int service_start(svc_t *svc)
|
||||
|
||||
sig_unblock();
|
||||
|
||||
/*
|
||||
* If the stanza declared tty:<dev>, set up a controlling
|
||||
* terminal on that device now that we're a session leader.
|
||||
* Must run after setsid() to acquire the controlling TTY.
|
||||
*/
|
||||
if (!svc_is_tty(svc) && svc_has_ctty(svc))
|
||||
svc_prepare_ctty(svc->log.ctty, svc->cmd, !!svc->log.enabled);
|
||||
|
||||
if (svc_is_runtask(svc))
|
||||
status = exec_runtask(args[0], &args[1]);
|
||||
else if (svc_is_tty(svc))
|
||||
@@ -971,7 +1078,6 @@ static int service_run_script(svc_t *svc, char *script)
|
||||
{
|
||||
const char *id = svc_ident(svc, NULL, 0);
|
||||
pid_t pid = service_fork(svc);
|
||||
int status, rc;
|
||||
|
||||
if (pid < 0) {
|
||||
err(1, "%s: failed forking off script %s", id, script);
|
||||
@@ -987,6 +1093,8 @@ static int service_run_script(svc_t *svc, char *script)
|
||||
};
|
||||
char pidbuf[16];
|
||||
|
||||
redirect(svc);
|
||||
|
||||
snprintf(pidbuf, sizeof(pidbuf), "%d", svc->pid);
|
||||
setenv("MAINPID", pidbuf, 1);
|
||||
|
||||
@@ -996,23 +1104,7 @@ static int service_run_script(svc_t *svc, char *script)
|
||||
}
|
||||
|
||||
dbg("%s: script '%s' started as PID %d", id, script, pid);
|
||||
if (waitpid(pid, &status, 0) == -1) {
|
||||
warn("%s: failed calling script %s", id, script);
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = WEXITSTATUS(status);
|
||||
if (WIFEXITED(status)) {
|
||||
dbg("%s: script '%s' exited without signal, status: %d", id, script, rc);
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
dbg("%s: script '%s' terminated by signal %d", id, script, WTERMSIG(status));
|
||||
if (!rc)
|
||||
rc = 1;
|
||||
} else {
|
||||
dbg("%s: script '%s' exited with status: %d", id, script, rc);
|
||||
}
|
||||
|
||||
return rc;
|
||||
return service_script_add(svc, pid, svc->killdelay);
|
||||
}
|
||||
|
||||
/* Ensure we don't have any notify socket lingering */
|
||||
@@ -1033,15 +1125,31 @@ static void service_notify_stop(svc_t *svc)
|
||||
*/
|
||||
static void service_cleanup(svc_t *svc)
|
||||
{
|
||||
char *fn;
|
||||
char cond[MAX_COND_LEN];
|
||||
|
||||
/* PID collected, cancel any pending SIGKILL */
|
||||
service_timeout_cancel(svc);
|
||||
|
||||
fn = pid_file(svc);
|
||||
if (fn && remove(fn) && errno != ENOENT)
|
||||
logit(LOG_CRIT, "Failed removing service %s pidfile %s",
|
||||
svc_ident(svc, NULL, 0), fn);
|
||||
/* Only clean up process' pidfile if managed by us */
|
||||
if (svc_has_pidfile(svc)) {
|
||||
char *fn = pid_file(svc);
|
||||
|
||||
if (remove(fn) && errno != ENOENT)
|
||||
logit(LOG_CRIT, "Failed removing service %s pidfile %s",
|
||||
svc_ident(svc, NULL, 0), fn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Invalidate the pid/ condition for this service to ensure
|
||||
* dependent services are properly stopped and restarted.
|
||||
* Without this, the condition is only cleared asynchronously
|
||||
* via inotify on pidfile removal, which may not trigger when
|
||||
* the daemon fails to clean up its own pidfile, or when the
|
||||
* service dies during a reload cycle and goes directly from
|
||||
* RUNNING to HALTED (skipping STOPPING where cond_clear()
|
||||
* is normally called).
|
||||
*/
|
||||
cond_clear(mkcond(svc, cond, sizeof(cond)));
|
||||
|
||||
service_notify_stop(svc);
|
||||
|
||||
@@ -1080,7 +1188,7 @@ int service_stop(svc_t *svc)
|
||||
service_timeout_cancel(svc);
|
||||
|
||||
if (svc->stop_script[0]) {
|
||||
logit(LOG_CONSOLE | LOG_NOTICE, "%s[%d], calling stop:%s ...", id, svc->pid, svc->stop_script);
|
||||
logit(LOG_CONSOLE | LOG_NOTICE, "Stopping %s[%d], calling stop:%s ...", id, svc->pid, svc->stop_script);
|
||||
} else if (!svc_is_sysv(svc)) {
|
||||
char *nm = pid_get_name(svc->pid, NULL, 0);
|
||||
const char *sig = sig_name(svc->sighalt);
|
||||
@@ -1095,10 +1203,10 @@ int service_stop(svc_t *svc)
|
||||
}
|
||||
|
||||
dbg("Sending %s to pid:%d name:%s(%s)", sig, svc->pid, id, nm);
|
||||
logit(LOG_CONSOLE | LOG_NOTICE, "%s[%d], stopping, sending %s ...", id, svc->pid, sig);
|
||||
logit(LOG_CONSOLE | LOG_NOTICE, "Stopping %s[%d], sending %s ...", id, svc->pid, sig);
|
||||
} else {
|
||||
compose_cmdline(svc, cmdline, sizeof(cmdline));
|
||||
logit(LOG_CONSOLE | LOG_NOTICE, "%s[%d], calling '%s stop' ...", id, svc->pid, cmdline);
|
||||
logit(LOG_CONSOLE | LOG_NOTICE, "Stopping %s[%d], calling '%s stop' ...", id, svc->pid, cmdline);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1218,7 +1326,7 @@ static int service_reload(svc_t *svc)
|
||||
print_desc("Restarting ", svc->desc);
|
||||
|
||||
if (svc->reload_script[0]) {
|
||||
logit(LOG_CONSOLE | LOG_NOTICE, "%s[%d], calling reload:%s ...", id, svc->pid, svc->reload_script);
|
||||
logit(LOG_CONSOLE | LOG_NOTICE, "Reloading %s[%d], calling reload:%s ...", id, svc->pid, svc->reload_script);
|
||||
rc = service_run_script(svc, svc->reload_script);
|
||||
} else if (svc->sighup) {
|
||||
if (svc->pid <= 1) {
|
||||
@@ -1226,8 +1334,8 @@ static int service_reload(svc_t *svc)
|
||||
svc->start_time = svc->pid = 0;
|
||||
goto done;
|
||||
}
|
||||
dbg("%s[%d], sending SIGHUP", id, svc->pid);
|
||||
logit(LOG_CONSOLE | LOG_NOTICE, "%s[%d], sending SIGHUP ...", id, svc->pid);
|
||||
dbg("Reloading %s[%d], sending SIGHUP", id, svc->pid);
|
||||
logit(LOG_CONSOLE | LOG_NOTICE, "Reloading %s[%d], sending SIGHUP ...", id, svc->pid);
|
||||
rc = kill(svc->pid, SIGHUP);
|
||||
if (rc == -1 && (errno == ESRCH || errno == ENOENT)) {
|
||||
/* nobody home, reset internal state machine */
|
||||
@@ -1384,6 +1492,26 @@ static void parse_env(svc_t *svc, char *env)
|
||||
strlcpy(svc->env, env, sizeof(svc->env));
|
||||
}
|
||||
|
||||
static void parse_caps(svc_t *svc, char *caps)
|
||||
{
|
||||
#ifdef HAVE_LIBCAP
|
||||
cap_iab_t cap_iab;
|
||||
|
||||
cap_iab = cap_iab_from_text(caps);
|
||||
if (!cap_iab) {
|
||||
err(1, "%s: failed parsing capabilities '%s'", svc_ident(svc, NULL, 0), caps);
|
||||
memset(svc->capabilities, 0, sizeof(svc->capabilities));
|
||||
return;
|
||||
}
|
||||
|
||||
cap_free(cap_iab);
|
||||
strlcpy(svc->capabilities, caps, sizeof(svc->capabilities));
|
||||
#else
|
||||
(void)svc;
|
||||
(void)caps;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* 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,
|
||||
@@ -1714,11 +1842,12 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
|
||||
char ident[MAX_IDENT_LEN];
|
||||
char *ifstmt = NULL;
|
||||
char *notify = NULL;
|
||||
char *ctty = NULL;
|
||||
struct tty tty = { 0 };
|
||||
char *dev = NULL;
|
||||
int respawn = 0;
|
||||
int levels = 0;
|
||||
int forking = 0, manual = 0, nowarn = 0;
|
||||
int forking = 0, manual = 0, remain = 0, nowarn = 0;
|
||||
int restart_max = SVC_RESPAWN_MAX;
|
||||
int restart_tmo = 0;
|
||||
unsigned oncrash_action = SVC_ONCRASH_IGNORE;
|
||||
@@ -1786,6 +1915,8 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
|
||||
forking = 1;
|
||||
else if (MATCH_CMD(cmd, "manual:yes", arg))
|
||||
manual = 1;
|
||||
else if (MATCH_CMD(cmd, "remain:yes", arg))
|
||||
remain = 1;
|
||||
else if (MATCH_CMD(cmd, "restart:", arg)) {
|
||||
if (MATCH_CMD(arg, "always", arg))
|
||||
restart_max = -1;
|
||||
@@ -1828,6 +1959,8 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
|
||||
env = arg;
|
||||
else if (MATCH_CMD(cmd, "caps:", arg))
|
||||
caps = arg;
|
||||
else if (MATCH_CMD(cmd, "tty:", arg))
|
||||
ctty = arg;
|
||||
/* catch both cgroup: and cgroup. handled in parse_cgroup() */
|
||||
else if (MATCH_CMD(cmd, "cgroup", arg))
|
||||
cgroup = arg;
|
||||
@@ -1957,7 +2090,29 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
|
||||
char *ptr = strchr(username, ':');
|
||||
|
||||
if (ptr) {
|
||||
char *sup;
|
||||
|
||||
*ptr++ = 0;
|
||||
/* Check for supplementary groups: group,sup1,sup2,... */
|
||||
sup = strchr(ptr, ',');
|
||||
if (sup) {
|
||||
*sup++ = 0;
|
||||
svc->num_supgroups = 0;
|
||||
while (sup) {
|
||||
char *next = strchr(sup, ',');
|
||||
if (next)
|
||||
*next++ = 0;
|
||||
if (svc->num_supgroups >= MAX_NUM_SUPGROUPS) {
|
||||
warn("%s: too many supplementary groups, max %d",
|
||||
svc->cmd, MAX_NUM_SUPGROUPS);
|
||||
break;
|
||||
}
|
||||
strlcpy(svc->supgroups[svc->num_supgroups], sup,
|
||||
sizeof(svc->supgroups[0]));
|
||||
svc->num_supgroups++;
|
||||
sup = next;
|
||||
}
|
||||
}
|
||||
strlcpy(svc->group, ptr, sizeof(svc->group));
|
||||
}
|
||||
strlcpy(svc->username, username, sizeof(svc->username));
|
||||
@@ -2052,9 +2207,25 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
|
||||
else
|
||||
memset(svc->env, 0, sizeof(svc->env));
|
||||
if (caps)
|
||||
strlcpy(svc->capabilities, caps, sizeof(svc->capabilities));
|
||||
parse_caps(svc, caps);
|
||||
else
|
||||
memset(svc->capabilities, 0, sizeof(svc->capabilities));
|
||||
|
||||
if (!svc_is_tty(svc) && ctty) {
|
||||
char *dev = ctty;
|
||||
|
||||
/* NOTE: @console expands only to first console, not all */
|
||||
if (tty_isatcon(ctty))
|
||||
dev = tty_atcon();
|
||||
dev = tty_canonicalize(dev);
|
||||
if (dev)
|
||||
strlcpy(svc->log.ctty, dev, sizeof(svc->log.ctty));
|
||||
else
|
||||
logit(LOG_WARNING, "%s: tty: %s not found, skipping ctty", svc_ident(svc, NULL, 0), ctty);
|
||||
} else if (!svc_is_tty(svc)) {
|
||||
memset(svc->log.ctty, 0, sizeof(svc->log.ctty));
|
||||
}
|
||||
|
||||
if (file)
|
||||
strlcpy(svc->file, file, sizeof(svc->file));
|
||||
else
|
||||
@@ -2069,6 +2240,18 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
|
||||
memset(svc->ifstmt, 0, sizeof(svc->ifstmt));
|
||||
svc->manual = manual;
|
||||
svc->nowarn = nowarn;
|
||||
|
||||
/*
|
||||
* remain:yes is not supported for bootstrap-only tasks. These
|
||||
* tasks are deleted immediately after completion and their post:
|
||||
* scripts never run. This is by design.
|
||||
*/
|
||||
if (remain && svc_is_runtask(svc) && !ISOTHER(levels, INIT_LEVEL)) {
|
||||
logit(LOG_WARNING, "%s: remain:yes ignored for bootstrap-only tasks",
|
||||
svc_ident(svc, NULL, 0));
|
||||
remain = 0;
|
||||
}
|
||||
svc->remain = remain;
|
||||
svc->respawn = respawn;
|
||||
svc->forking = forking;
|
||||
svc->restart_max = restart_max;
|
||||
@@ -2092,15 +2275,20 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
|
||||
/* only set forking based on pidfile if user supplied pid: option */
|
||||
if (pid && svc->pidfile[0] == '!')
|
||||
svc->forking = 1;
|
||||
|
||||
if (svc->restart_tmo == 0) {
|
||||
if (svc_is_forking(svc))
|
||||
svc->restart_tmo = 2000;
|
||||
else
|
||||
svc->restart_tmo = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set default restart_tmo for services and TTYs that can restart */
|
||||
if (svc_is_daemon(svc) && svc->restart_tmo == 0) {
|
||||
if (svc_is_forking(svc))
|
||||
svc->restart_tmo = 2000;
|
||||
else
|
||||
svc->restart_tmo = 1;
|
||||
}
|
||||
|
||||
/* TTYs need a longer default to throttle errors (e.g., missing device) */
|
||||
if (svc_is_tty(svc) && svc->restart_tmo == 0)
|
||||
svc->restart_tmo = 2000;
|
||||
|
||||
/* Set configured limits */
|
||||
memcpy(svc->rlimit, rlimit, sizeof(svc->rlimit));
|
||||
|
||||
@@ -2296,7 +2484,21 @@ void service_update_rdeps(void)
|
||||
if (!svc_is_noreload(svc))
|
||||
continue; /* Yup, no need to stop start rdeps */
|
||||
|
||||
svc_mark_affected(mkcond(svc, cond, sizeof(cond)));
|
||||
/*
|
||||
* Clear the condition immediately, before service_step_all()
|
||||
* runs. cond_clear() calls cond_update() which calls
|
||||
* service_step() on all affected services right now. Those
|
||||
* services see COND_OFF and get service_stop() called,
|
||||
* transitioning to STOPPING_STATE before we ever send SIGTERM
|
||||
* to this service. Without this, the condition is only cleared
|
||||
* after the service dies, by which time reverse-dependencies
|
||||
* may have already crashed due to the lost connection.
|
||||
* See also: api.c do_reload() which does the same for direct
|
||||
* 'initctl reload <svc>' calls.
|
||||
*/
|
||||
mkcond(svc, cond, sizeof(cond));
|
||||
cond_clear(cond);
|
||||
svc_mark_affected(cond);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2537,6 +2739,11 @@ static void service_retry(svc_t *svc)
|
||||
return;
|
||||
|
||||
if (svc->respawn) {
|
||||
/*
|
||||
* Respawn services (TTYs) that exited with non-zero status
|
||||
* have already been delayed in the SVC_RUNNING_STATE handler.
|
||||
* Just restart now.
|
||||
*/
|
||||
dbg("%s crashed/exited, respawning ...", svc_ident(svc, NULL, 0));
|
||||
svc_unblock(svc);
|
||||
service_step(svc);
|
||||
@@ -2747,8 +2954,12 @@ restart:
|
||||
if (svc_is_removed(svc) && svc_has_cleanup(svc)) {
|
||||
svc_set_state(svc, SVC_CLEANUP_STATE);
|
||||
service_cleanup_script(svc);
|
||||
} else
|
||||
} else {
|
||||
/* Unblock remain tasks after post script so they can restart */
|
||||
if (svc_is_remain(svc))
|
||||
svc_unblock(svc);
|
||||
svc_set_state(svc, SVC_HALTED_STATE);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -2764,9 +2975,16 @@ restart:
|
||||
break;
|
||||
|
||||
case SVC_DONE_STATE:
|
||||
if (svc_is_changed(svc))
|
||||
/* Remain tasks: always run post script when stopped, even if config changed */
|
||||
if (svc_is_runtask(svc) && svc_is_remain(svc) && svc_is_stopped(svc)) {
|
||||
if (svc_has_post(svc)) {
|
||||
svc_set_state(svc, SVC_TEARDOWN_STATE);
|
||||
service_post_script(svc);
|
||||
} else
|
||||
svc_set_state(svc, SVC_HALTED_STATE);
|
||||
} else if (svc_is_changed(svc))
|
||||
svc_set_state(svc, SVC_HALTED_STATE);
|
||||
if (svc_is_runtask(svc) && svc_is_manual(svc) && enabled)
|
||||
else if (svc_is_runtask(svc) && svc_is_manual(svc) && enabled)
|
||||
svc_set_state(svc, SVC_WAITING_STATE);
|
||||
break;
|
||||
|
||||
@@ -2899,6 +3117,8 @@ restart:
|
||||
|
||||
if (!svc->pid) {
|
||||
if (svc_is_daemon(svc) || svc_is_tty(svc)) {
|
||||
int rc = WEXITSTATUS(svc->status);
|
||||
|
||||
svc_restarting(svc); /* BLOCK_RESTARTING */
|
||||
svc_set_state(svc, SVC_HALTED_STATE);
|
||||
|
||||
@@ -2913,6 +3133,18 @@ restart:
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*
|
||||
* Non-zero exit for respawn services (TTYs): delay retry
|
||||
* to prevent busy-loop and rate-limit retries
|
||||
*/
|
||||
if (WIFEXITED(svc->status) &&
|
||||
rc != 0) {
|
||||
dbg("%s exited with error %d, delaying respawn ...",
|
||||
svc_ident(svc, NULL, 0), rc);
|
||||
service_timeout_after(svc, svc->restart_tmo, service_retry);
|
||||
goto done;
|
||||
}
|
||||
|
||||
dbg("respawning %s", svc_ident(svc, NULL, 0));
|
||||
svc_unblock(svc);
|
||||
break;
|
||||
@@ -2983,6 +3215,19 @@ restart:
|
||||
case COND_ON:
|
||||
kill(svc->pid, SIGCONT);
|
||||
svc_set_state(svc, SVC_RUNNING_STATE);
|
||||
|
||||
/*
|
||||
* Propagate reload (~): upstream reloaded, so
|
||||
* we must also reload/restart, not just resume.
|
||||
*/
|
||||
if (svc->flux_reload && !svc_is_changed(svc)) {
|
||||
if (svc_is_noreload(svc))
|
||||
service_stop(svc);
|
||||
else
|
||||
service_reload(svc);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Reassert condition if we go from waiting and no change */
|
||||
if (!svc_is_changed(svc)) {
|
||||
if (svc->notify == SVC_NOTIFY_PID) {
|
||||
@@ -3054,6 +3299,29 @@ void service_runtask_clean(void)
|
||||
if (!svc_is_runtask(svc))
|
||||
continue;
|
||||
|
||||
/* Remain tasks stay in DONE state if still valid in new runlevel */
|
||||
if (svc_is_remain(svc) && svc->state == SVC_DONE_STATE) {
|
||||
if (svc_in_runlevel(svc, runlevel) && !svc_is_changed(svc))
|
||||
continue; /* Keep once flag, stay in DONE */
|
||||
|
||||
/* Config changed or leaving runlevel: stop, run post, restart */
|
||||
svc->once = 0;
|
||||
svc_stop(svc);
|
||||
service_step(svc);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* On reload (SIGHUP), only remain tasks (handled above) should
|
||||
* have their once flag cleared and be restarted. Regular run/task
|
||||
* that already ran in this runlevel must not run again.
|
||||
*
|
||||
* On runlevel change, continue below to reset once flag so tasks
|
||||
* can run again in the new runlevel.
|
||||
*/
|
||||
if (sm_in_reload())
|
||||
continue;
|
||||
|
||||
/* run/task declared with <!> */
|
||||
if (svc->sighup)
|
||||
svc->once = 1;
|
||||
|
||||
@@ -77,7 +77,9 @@
|
||||
#endif
|
||||
#include <string.h> /* strerror() */
|
||||
#include <sched.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/reboot.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#ifdef _LIBITE_LITE
|
||||
# include <libite/lite.h>
|
||||
@@ -206,7 +208,7 @@ static void fs_swapoff(void)
|
||||
*
|
||||
* https://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons/
|
||||
*/
|
||||
void do_iterate_proc(int (*cb)(int, void *), void *data)
|
||||
void iterate_proc(int (*cb)(int, void *), void *data)
|
||||
{
|
||||
DIR *dirp;
|
||||
|
||||
@@ -288,7 +290,7 @@ static int do_wait(int secs)
|
||||
;
|
||||
has_proc = 0;
|
||||
iterations--;
|
||||
do_iterate_proc(status_cb, &has_proc);
|
||||
iterate_proc(status_cb, &has_proc);
|
||||
}
|
||||
while (has_proc && iterations > 0);
|
||||
|
||||
@@ -301,6 +303,8 @@ void do_shutdown(shutop_t op)
|
||||
int in_cont = in_container();
|
||||
int signo = SIGTERM;
|
||||
|
||||
if (SHUTDOWN_DEBUG) cprintf("do_shutdown: entered, op=%d\n", op);
|
||||
|
||||
if (!in_cont) {
|
||||
/*
|
||||
* On a PREEMPT-RT system, Finit must run as the highest prioritized
|
||||
@@ -320,13 +324,15 @@ void do_shutdown(shutop_t op)
|
||||
* Tell remaining non-monitored processes to exit, give them
|
||||
* time to exit gracefully, 2 sec was customary, we go for 1.
|
||||
*/
|
||||
do_iterate_proc(kill_cb, &signo);
|
||||
if (SHUTDOWN_DEBUG) cprintf("do_shutdown: killing remaining processes ...\n");
|
||||
iterate_proc(kill_cb, &signo);
|
||||
if (do_wait(1)) {
|
||||
signo = SIGKILL;
|
||||
do_iterate_proc(kill_cb, &signo);
|
||||
iterate_proc(kill_cb, &signo);
|
||||
}
|
||||
|
||||
/* Exit plugins gracefully */
|
||||
if (SHUTDOWN_DEBUG) cprintf("do_shutdown: plugin_exit() + cond_exit() ...\n");
|
||||
plugin_exit();
|
||||
|
||||
/* We can no longer act on conditions, activate script runner */
|
||||
@@ -349,12 +355,14 @@ void do_shutdown(shutop_t op)
|
||||
for (int fd = 3; fd < 128; fd++)
|
||||
close(fd);
|
||||
|
||||
if (SHUTDOWN_DEBUG) cprintf("do_shutdown: vfork, child will reboot ...\n");
|
||||
if (vfork()) {
|
||||
/*
|
||||
* Put PID 1 aside and let child perform reboot/halt
|
||||
* kernel may exit child and we don't want to exit PID 1
|
||||
* ... causing "aiii killing init" during reboot ...
|
||||
*/
|
||||
if (SHUTDOWN_DEBUG) cprintf("do_shutdown: PID 1 returning from vfork\n");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -416,14 +424,17 @@ void do_shutdown(shutop_t op)
|
||||
}
|
||||
|
||||
print(0, "Rebooting ...");
|
||||
print_exit();
|
||||
reboot(RB_AUTOBOOT);
|
||||
} else if (op == SHUT_OFF) {
|
||||
print(0, "Powering down ...");
|
||||
print_exit();
|
||||
reboot(RB_POWER_OFF);
|
||||
}
|
||||
|
||||
/* Also fallback if any of the other two fails */
|
||||
print(0, "Halting ...");
|
||||
print_exit();
|
||||
reboot(RB_HALT_SYSTEM);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "cgroup.h"
|
||||
#include "cond.h"
|
||||
#include "conf.h"
|
||||
#include "devmon.h"
|
||||
#include "log.h"
|
||||
#include "helpers.h"
|
||||
#include "private.h"
|
||||
@@ -62,9 +63,37 @@ static struct {
|
||||
int skip_bootstrap; /* Set to skip bootstrap waiting */
|
||||
} sm;
|
||||
|
||||
/* Shutdown trace: set SHUTDOWN_DEBUG to 1 in sm.h to enable */
|
||||
#define shutdbg(fmt, args...) do { \
|
||||
if (SHUTDOWN_DEBUG && (runlevel == 0 || runlevel == 6)) \
|
||||
cprintf("shutdown[%c]: " fmt "\n", \
|
||||
sm_rl2ch(runlevel), ##args); \
|
||||
} while (0)
|
||||
|
||||
static uev_t console_watcher;
|
||||
static int console_fd = -1;
|
||||
|
||||
/*
|
||||
* Shutdown watchdog: if graceful service stop takes too long,
|
||||
* force do_shutdown() directly from the event loop callback.
|
||||
* Default timeout is 30 seconds, adjustable at build time.
|
||||
*/
|
||||
#ifndef SHUTDOWN_WDT_TIMEOUT
|
||||
#define SHUTDOWN_WDT_TIMEOUT 30
|
||||
#endif
|
||||
static uev_t shutdown_wdt;
|
||||
|
||||
static void shutdown_wdt_cb(uev_t *w, void *arg, int events)
|
||||
{
|
||||
(void)w;
|
||||
(void)arg;
|
||||
(void)events;
|
||||
|
||||
logit(LOG_CONSOLE | LOG_CRIT, "Timed out (%d sec) waiting for services to stop, forcing shutdown.",
|
||||
SHUTDOWN_WDT_TIMEOUT);
|
||||
do_shutdown(halt);
|
||||
}
|
||||
|
||||
/*
|
||||
* Console input callback - handles Ctrl-C during bootstrap wait
|
||||
*/
|
||||
@@ -349,6 +378,7 @@ restart:
|
||||
|
||||
/* reload ? */
|
||||
if (sm.reload) {
|
||||
shutdbg("RUNNING got reload request!");
|
||||
sm.reload = 0;
|
||||
sm.state = SM_RELOAD_CHANGE_STATE;
|
||||
}
|
||||
@@ -361,12 +391,15 @@ 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);
|
||||
|
||||
shutdbg("entering CHANGE state, starting %d sec watchdog",
|
||||
SHUTDOWN_WDT_TIMEOUT);
|
||||
uev_timer_init(ctx, &shutdown_wdt, shutdown_wdt_cb,
|
||||
NULL, SHUTDOWN_WDT_TIMEOUT * 1000, 0);
|
||||
api_exit();
|
||||
log_exit();
|
||||
shutdbg("running HOOK_SHUTDOWN ...");
|
||||
plugin_run_hooks(HOOK_SHUTDOWN);
|
||||
shutdbg("HOOK_SHUTDOWN done");
|
||||
}
|
||||
|
||||
dbg("Setting new runlevel --> %c <-- previous %c", sm_rl2ch(runlevel), sm_rl2ch(prevlevel));
|
||||
@@ -386,12 +419,14 @@ restart:
|
||||
}
|
||||
|
||||
/* Reset once flag of runtasks */
|
||||
shutdbg("runtask_clean + step_all ...");
|
||||
service_runtask_clean();
|
||||
|
||||
dbg("Stopping services not allowed in new runlevel ...");
|
||||
sm.in_reload = 1;
|
||||
service_step_all(SVC_TYPE_ANY);
|
||||
|
||||
shutdbg("CHANGE done, entering WAIT");
|
||||
sm.state = SM_RUNLEVEL_WAIT_STATE;
|
||||
break;
|
||||
|
||||
@@ -402,10 +437,13 @@ restart:
|
||||
*/
|
||||
svc = svc_stop_completed();
|
||||
if (svc) {
|
||||
shutdbg("WAIT, collecting %s[%d] ...", svc_ident(svc, NULL, 0), svc->pid);
|
||||
dbg("Waiting to collect %s, cmd %s(%d) ...", svc_ident(svc, NULL, 0), svc->cmd, svc->pid);
|
||||
break;
|
||||
}
|
||||
|
||||
shutdbg("WAIT done, all services stopped");
|
||||
|
||||
/* Prev runlevel services stopped, call hooks before starting new runlevel ... */
|
||||
dbg("All services have been stopped, calling runlevel change hooks ...");
|
||||
plugin_run_hooks(HOOK_RUNLEVEL_CHANGE); /* Reconfigure HW/VLANs/etc here */
|
||||
@@ -418,6 +456,7 @@ restart:
|
||||
sm.in_reload = 0;
|
||||
service_step_all(SVC_TYPE_ANY);
|
||||
|
||||
shutdbg("entering CLEAN");
|
||||
sm.state = SM_RUNLEVEL_CLEAN_STATE;
|
||||
break;
|
||||
|
||||
@@ -429,11 +468,15 @@ restart:
|
||||
*/
|
||||
svc = svc_clean_completed();
|
||||
if (svc) {
|
||||
shutdbg("CLEAN, waiting for %s[%d] (%s) ...",
|
||||
svc_ident(svc, NULL, 0), svc->pid, svc_status(svc));
|
||||
dbg("Waiting to collect post/cleanup script for %s, cmd %s(%d) ...",
|
||||
svc_ident(svc, NULL, 0), svc->cmd, svc->pid);
|
||||
break;
|
||||
}
|
||||
|
||||
shutdbg("CLEAN done, all post/cleanup scripts collected");
|
||||
|
||||
/* Cleanup stale services */
|
||||
svc_clean_dynamic(service_unregister);
|
||||
|
||||
@@ -449,9 +492,15 @@ restart:
|
||||
* Tannhäuser Gate. All those .. moments .. will be lost in time, like
|
||||
* tears ... in ... rain."
|
||||
*/
|
||||
if (runlevel == 0 || runlevel == 6)
|
||||
if (runlevel == 0 || runlevel == 6) {
|
||||
shutdbg("calling do_shutdown()");
|
||||
uev_timer_stop(&shutdown_wdt);
|
||||
do_shutdown(halt);
|
||||
shutdbg("do_shutdown() returned to SM!");
|
||||
}
|
||||
|
||||
shutdbg("entering RUNNING (reload=%d, newlevel=%d)",
|
||||
sm.reload, sm.newlevel);
|
||||
sm.state = SM_RUNNING_STATE;
|
||||
break;
|
||||
|
||||
@@ -461,6 +510,9 @@ restart:
|
||||
/* First reload all *.conf in /etc/finit.d/ */
|
||||
conf_reload();
|
||||
|
||||
/* Handle remain tasks that need to run post script before restart */
|
||||
service_runtask_clean();
|
||||
|
||||
/*
|
||||
* Then, mark all affected service conditions as in-flux and
|
||||
* let all affected services move to WAITING/HALTED
|
||||
@@ -509,12 +561,27 @@ restart:
|
||||
/* Cleanup stale services */
|
||||
svc_clean_dynamic(service_unregister);
|
||||
|
||||
dbg("Step all services waiting for reasserted conditions ...");
|
||||
service_step_all(SVC_TYPE_ANY);
|
||||
|
||||
dbg("Calling reconf hooks ...");
|
||||
plugin_run_hooks(HOOK_SVC_RECONF);
|
||||
|
||||
dbg("Step all services waiting for reasserted conditions ...");
|
||||
service_step_all(SVC_TYPE_ANY);
|
||||
|
||||
dbg("Update configuration generation of device conditions ...");
|
||||
devmon_reconf();
|
||||
|
||||
dbg("Step all services waiting for reasserted conditions ...");
|
||||
service_step_all(SVC_TYPE_ANY);
|
||||
|
||||
dbg("Update configuration generation of unmodified non-native services ...");
|
||||
service_notify_reconf();
|
||||
|
||||
dbg("Step all services waiting for reasserted conditions ...");
|
||||
service_step_all(SVC_TYPE_ANY);
|
||||
|
||||
dbg("Reconfiguration done");
|
||||
sm.state = SM_RUNNING_STATE;
|
||||
break;
|
||||
|
||||
@@ -24,6 +24,11 @@
|
||||
#ifndef FINIT_SM_H_
|
||||
#define FINIT_SM_H_
|
||||
|
||||
/* Set to 1 to trace shutdown (independent of 'initctl debug') */
|
||||
#ifndef SHUTDOWN_DEBUG
|
||||
#define SHUTDOWN_DEBUG 0
|
||||
#endif
|
||||
|
||||
void sm_init (void);
|
||||
void sm_step (void);
|
||||
|
||||
|
||||
@@ -344,6 +344,10 @@ svc_t *svc_stop_completed(void)
|
||||
for (svc = svc_iterator(&iter, 1); svc; svc = svc_iterator(&iter, 0)) {
|
||||
if (svc->state == SVC_STOPPING_STATE && svc->pid > 1)
|
||||
return svc;
|
||||
|
||||
/* Also wait for remain tasks running their post script */
|
||||
if (svc_is_remain(svc) && svc->state == SVC_TEARDOWN_STATE && svc->pid > 1)
|
||||
return svc;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
|
||||
@@ -101,6 +101,7 @@ typedef enum {
|
||||
#define MAX_STR_LEN 64
|
||||
#define MAX_COND_LEN (MAX_ARG_LEN * 3)
|
||||
#define MAX_USER_LEN 16
|
||||
#define MAX_NUM_SUPGROUPS 4
|
||||
#define MAX_NUM_FDS 64 /* Max number of I/O plugins */
|
||||
#define MAX_NUM_SVC_ARGS 64
|
||||
|
||||
@@ -138,12 +139,14 @@ typedef struct svc {
|
||||
svc_type_t type; /* Service, run, task, ... */
|
||||
char protect; /* Services like dbus-daemon & udev by Finit */
|
||||
char manual; /* run/task that require `initctl start foo` */
|
||||
char remain; /* run/task: stay in DONE state, run post: on stop */
|
||||
char nowarn; /* Skip or log warning if cmd missing or conflicts */
|
||||
const int dirty; /* 0: unmodified, 1: modified */
|
||||
const int removed;
|
||||
int starting; /* ... waiting for pidfile to be re-asserted */
|
||||
int runlevels;
|
||||
int sighup; /* This service supports SIGHUP :) */
|
||||
int flux_reload; /* Propagate reload from dependency, '~' prefix */
|
||||
int forking; /* This is a service/sysv daemon that forks, wait for it ... */
|
||||
svc_block_t block; /* Reason that this service is currently stopped */
|
||||
char cond[MAX_COND_LEN];
|
||||
@@ -173,6 +176,7 @@ typedef struct svc {
|
||||
char file[64];
|
||||
char prio[20];
|
||||
char ident[20];
|
||||
char ctty[32];
|
||||
} log;
|
||||
|
||||
/* Only for TTY type services */
|
||||
@@ -191,6 +195,8 @@ typedef struct svc {
|
||||
/* Identity */
|
||||
char username[MAX_USER_LEN];
|
||||
char group[MAX_USER_LEN];
|
||||
char supgroups[MAX_NUM_SUPGROUPS][MAX_USER_LEN];
|
||||
int num_supgroups;
|
||||
char capabilities[MAX_CMD_LEN];
|
||||
|
||||
/* Command, arguments and service description */
|
||||
@@ -279,12 +285,14 @@ static inline int svc_is_tty (svc_t *svc) { return svc && SVC_TYPE_TTY
|
||||
static inline int svc_is_runtask (svc_t *svc) { return svc && (SVC_TYPE_RUNTASK & svc->type);}
|
||||
static inline int svc_is_forking (svc_t *svc) { return svc && svc->forking; }
|
||||
static inline int svc_is_manual (svc_t *svc) { return svc && svc->manual; }
|
||||
static inline int svc_is_remain (svc_t *svc) { return svc && svc->remain; }
|
||||
static inline int svc_is_noreload (svc_t *svc) { return svc && (0 == svc->sighup && 0 == svc->reload_script[0]); }
|
||||
|
||||
static inline int svc_in_runlevel (svc_t *svc, int runlevel) { return svc && ISSET(svc->runlevels, runlevel); }
|
||||
static inline int svc_has_pidfile (svc_t *svc) { return svc_is_daemon(svc) && svc->pidfile[0] != 0 && svc->pidfile[0] != '!'; }
|
||||
static inline int svc_has_pre (svc_t *svc) { return svc->pre_script[0]; }
|
||||
static inline int svc_has_post (svc_t *svc) { return svc->post_script[0]; }
|
||||
static inline int svc_has_ctty (svc_t *svc) { return svc && svc->log.ctty[0]; }
|
||||
static inline int svc_has_ready (svc_t *svc) { return svc->ready_script[0];}
|
||||
static inline int svc_has_cleanup (svc_t *svc) { return svc->cleanup_script[0]; }
|
||||
|
||||
|
||||
+299
-45
@@ -34,6 +34,7 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#ifdef _LIBITE_LITE
|
||||
# include <libite/lite.h>
|
||||
#else
|
||||
@@ -45,8 +46,29 @@
|
||||
|
||||
int debug;
|
||||
|
||||
int c_flag = 0;
|
||||
int r_flag = 0;
|
||||
int create_flag = 0;
|
||||
int clean_flag = 0;
|
||||
int remove_flag = 0;
|
||||
|
||||
#define MAX_EXCLUDES 32
|
||||
static const char *exclude_prefixes[MAX_EXCLUDES];
|
||||
static int num_excludes;
|
||||
|
||||
static void add_exclude_prefix(const char *prefix)
|
||||
{
|
||||
if (num_excludes < MAX_EXCLUDES)
|
||||
exclude_prefixes[num_excludes++] = prefix;
|
||||
}
|
||||
|
||||
static int excluded(const char *path)
|
||||
{
|
||||
for (int i = 0; i < num_excludes; i++) {
|
||||
if (!strncmp(path, exclude_prefixes[i], strlen(exclude_prefixes[i])))
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int is_dir_empty(const char *path)
|
||||
{
|
||||
@@ -261,6 +283,122 @@ static void write_arg(FILE *fp, char *arg)
|
||||
fputs(arg, fp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse age string and return age in seconds.
|
||||
* Returns 0 on invalid/empty age ("-" or NULL).
|
||||
* Examples: "10d" = 864000, "1w" = 604800, "2h" = 7200
|
||||
*/
|
||||
static time_t parse_age(const char *age)
|
||||
{
|
||||
time_t val;
|
||||
char *end;
|
||||
|
||||
if (!age || !strcmp(age, "-") || !strcmp(age, "0"))
|
||||
return 0;
|
||||
|
||||
errno = 0;
|
||||
val = strtoul(age, &end, 10);
|
||||
if (errno || end == age)
|
||||
return 0;
|
||||
|
||||
switch (*end) {
|
||||
case 'w':
|
||||
val *= 7;
|
||||
/* fallthrough */
|
||||
case 'd':
|
||||
val *= 24;
|
||||
/* fallthrough */
|
||||
case 'h':
|
||||
val *= 60;
|
||||
/* fallthrough */
|
||||
case 'm':
|
||||
val *= 60;
|
||||
/* fallthrough */
|
||||
case 's':
|
||||
case '\0':
|
||||
break;
|
||||
default:
|
||||
return 0; /* unknown suffix */
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse user string - supports both names and numeric UIDs.
|
||||
* Returns UID on success, -1 on failure.
|
||||
*/
|
||||
static int parse_uid(const char *user)
|
||||
{
|
||||
long val;
|
||||
|
||||
if (!user || !user[0])
|
||||
return 0;
|
||||
|
||||
/* Check if it's a numeric UID */
|
||||
val = atonum(user);
|
||||
if (val >= 0)
|
||||
return val;
|
||||
|
||||
/* Not numeric, look up by name */
|
||||
return getuser(user, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse group string - supports both names and numeric GIDs.
|
||||
* Returns GID on success, -1 on failure.
|
||||
*/
|
||||
static int parse_gid(const char *group)
|
||||
{
|
||||
long val;
|
||||
|
||||
if (!group || !group[0])
|
||||
return 0;
|
||||
|
||||
/* Check if it's a numeric GID */
|
||||
val = atonum(group);
|
||||
if (val >= 0)
|
||||
return val;
|
||||
|
||||
/* Not numeric, look up by name */
|
||||
return getgroup(group);
|
||||
}
|
||||
|
||||
/* Globals for do_clean() callback - nftw doesn't support user data */
|
||||
static time_t clean_age;
|
||||
static time_t clean_now;
|
||||
|
||||
static int do_clean(const char *fpath, const struct stat *sb, int tflag, struct FTW *ftw)
|
||||
{
|
||||
int is_dir;
|
||||
|
||||
/* Skip the root directory itself */
|
||||
if (ftw->level == 0)
|
||||
return 0;
|
||||
|
||||
is_dir = (tflag == FTW_D || tflag == FTW_DP);
|
||||
|
||||
/*
|
||||
* Conservative cleanup matching systemd-tmpfiles behavior:
|
||||
* - Files: keep if ANY of atime, ctime, mtime is recent
|
||||
* - Directories: keep if ANY of atime, mtime is recent (ctime
|
||||
* excluded because cleanup itself updates directory ctime)
|
||||
*/
|
||||
if (clean_now - sb->st_mtime < clean_age)
|
||||
return 0; /* mtime is too new, keep it */
|
||||
|
||||
if (clean_now - sb->st_atime < clean_age)
|
||||
return 0; /* atime is too new, keep it */
|
||||
|
||||
if (!is_dir && clean_now - sb->st_ctime < clean_age)
|
||||
return 0; /* ctime is too new, keep it (files only) */
|
||||
|
||||
if (remove(fpath) && errno != ENOENT && errno != EBUSY)
|
||||
warn("Failed cleaning %s", fpath);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The configuration format is one line per path, containing type, path,
|
||||
* mode, ownership, age, and argument fields. The lines are separated by
|
||||
@@ -295,6 +433,8 @@ static void tmpfiles(char *line)
|
||||
errx(1, "Path name specifiers unsupported, skipping.");
|
||||
return;
|
||||
}
|
||||
if (excluded(path))
|
||||
return;
|
||||
|
||||
token = strtok(NULL, "\t ");
|
||||
if (token) {
|
||||
@@ -312,13 +452,12 @@ static void tmpfiles(char *line)
|
||||
group = "root";
|
||||
|
||||
age = strtok(NULL, "\t ");
|
||||
(void)age; /* unused atm. */
|
||||
arg = strtok(NULL, "\n");
|
||||
|
||||
strc = stat(path, &st);
|
||||
|
||||
// file and directory removal logic
|
||||
if (r_flag) {
|
||||
if (remove_flag) {
|
||||
switch (type[0]) {
|
||||
case 'b':
|
||||
case 'c':
|
||||
@@ -349,7 +488,7 @@ static void tmpfiles(char *line)
|
||||
break;
|
||||
case 'X':
|
||||
case 'x':
|
||||
dbg("Unsupported x/X command, ignoring %s, no support for clean at runtime.", path);
|
||||
/* handled by clean_flag (--clean) */
|
||||
break;
|
||||
case 'Z':
|
||||
case 'z':
|
||||
@@ -360,8 +499,34 @@ static void tmpfiles(char *line)
|
||||
}
|
||||
}
|
||||
|
||||
/* age-based cleanup logic */
|
||||
if (clean_flag) {
|
||||
time_t max_age = parse_age(age);
|
||||
|
||||
/* Only process if age is specified */
|
||||
if (max_age > 0) {
|
||||
switch (type[0]) {
|
||||
case 'd':
|
||||
case 'D':
|
||||
case 'e':
|
||||
if (!fisdir(path))
|
||||
break;
|
||||
clean_age = max_age;
|
||||
clean_now = time(NULL);
|
||||
nftw(path, do_clean, 20, FTW_DEPTH | FTW_PHYS);
|
||||
break;
|
||||
case 'x':
|
||||
case 'X':
|
||||
/* exclusion patterns - not yet implemented */
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// file & directory creation logic
|
||||
if (c_flag) {
|
||||
if (create_flag) {
|
||||
switch (type[0]) {
|
||||
case 'b':
|
||||
rc = parse_mm(arg, &major, &minor);
|
||||
@@ -409,26 +574,55 @@ static void tmpfiles(char *line)
|
||||
rc = 0;
|
||||
break;
|
||||
case 'd':
|
||||
case 'D':
|
||||
case 'D': {
|
||||
int uid, gid;
|
||||
mode_t omask;
|
||||
|
||||
mkparent(path, 0755);
|
||||
rc = mksubsys(path, mode ?: 0755, user, group);
|
||||
omask = umask(0);
|
||||
uid = parse_uid(user);
|
||||
if (uid >= 0) {
|
||||
gid = parse_gid(group);
|
||||
if (gid < 0)
|
||||
gid = 0;
|
||||
|
||||
rc = makedir(path, mode ?: 0755);
|
||||
if (rc && errno == EEXIST)
|
||||
rc = chmod(path, mode ?: 0755);
|
||||
if (chown(path, uid, gid))
|
||||
warn("Failed chown(%s, %d, %d)", path, uid, gid);
|
||||
}
|
||||
umask(omask);
|
||||
break;
|
||||
}
|
||||
case 'e':
|
||||
if (glob(path, GLOB_NOESCAPE, NULL, &gl))
|
||||
break;
|
||||
|
||||
for (size_t i = 0; i < gl.gl_pathc; i++)
|
||||
rc += mksubsys(gl.gl_pathv[i], mode ?: 0755, user, group);
|
||||
for (size_t i = 0; i < gl.gl_pathc; i++) {
|
||||
char *p = gl.gl_pathv[i];
|
||||
int uid, gid;
|
||||
|
||||
/* e only adjusts existing directories */
|
||||
if (!fisdir(p))
|
||||
continue;
|
||||
|
||||
uid = parse_uid(user);
|
||||
gid = parse_gid(group);
|
||||
if (gid < 0)
|
||||
gid = 0;
|
||||
|
||||
if (mode)
|
||||
chmod(p, mode);
|
||||
if (uid >= 0 && chown(p, uid, gid))
|
||||
warn("Failed chown(%s, %d, %d)", p, uid, gid);
|
||||
}
|
||||
break;
|
||||
case 'f':
|
||||
case 'F':
|
||||
mkparent(path, 0755);
|
||||
if (type[1] == '+' || type[0] == 'F') {
|
||||
/* f+/F will create or truncate the file */
|
||||
if (!arg) {
|
||||
rc = create(path, mode ?: 0644, user, group);
|
||||
break;
|
||||
}
|
||||
fp = fopen(path, "w+");
|
||||
} else {
|
||||
/* f will create the file if it doesn't exist */
|
||||
@@ -437,8 +631,20 @@ static void tmpfiles(char *line)
|
||||
}
|
||||
|
||||
if (fp) {
|
||||
int uid, gid;
|
||||
|
||||
write_arg(fp, arg);
|
||||
rc = fclose(fp);
|
||||
|
||||
/* Apply mode and ownership */
|
||||
if (mode)
|
||||
chmod(path, mode);
|
||||
uid = parse_uid(user);
|
||||
gid = parse_gid(group);
|
||||
if (gid < 0)
|
||||
gid = 0;
|
||||
if (uid >= 0 && chown(path, uid, gid))
|
||||
warn("Failed chown(%s, %d, %d)", path, uid, gid);
|
||||
}
|
||||
break;
|
||||
case 'l': /* Finit extension, like 'L' but only if target exists */
|
||||
@@ -465,7 +671,10 @@ static void tmpfiles(char *line)
|
||||
if (!strc) {
|
||||
if (type[1] != '+')
|
||||
break;
|
||||
rmrf(path);
|
||||
if (fisdir(path))
|
||||
rmrf(path);
|
||||
else
|
||||
erase(path);
|
||||
}
|
||||
mkparent(path, 0755);
|
||||
if (!arg) {
|
||||
@@ -505,7 +714,7 @@ static void tmpfiles(char *line)
|
||||
break;
|
||||
case 'X':
|
||||
case 'x':
|
||||
dbg("Unsupported x/X command, ignoring %s, no support for clean at runtime.", path);
|
||||
/* handled by clean_flag (--clean) */
|
||||
break;
|
||||
case 'Z':
|
||||
opts = "-R";
|
||||
@@ -532,45 +741,96 @@ static void tmpfiles(char *line)
|
||||
warn("Failed %s operation on path %s", type, path);
|
||||
}
|
||||
|
||||
static void process_file(const char *fn)
|
||||
{
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen(fn, "r");
|
||||
if (!fp) {
|
||||
warn("Failed to open %s", fn);
|
||||
return;
|
||||
}
|
||||
|
||||
while (!feof(fp)) {
|
||||
char *line;
|
||||
|
||||
line = fparseln(fp, NULL, NULL, NULL, FPARSELN_UNESCCOMM);
|
||||
if (!line)
|
||||
continue;
|
||||
|
||||
tmpfiles(line);
|
||||
free(line);
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
static int usage(int rc)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"Usage: tmpfiles [COMMAND...]\n"
|
||||
"Usage: tmpfiles [OPTIONS] [CONFIGFILE...]\n"
|
||||
"\n"
|
||||
"Commands:\n"
|
||||
"Options:\n"
|
||||
" -C, --clean Clean files and directories based on age\n"
|
||||
" -c, --create Create files and directories\n"
|
||||
" -d, --debug Show developer debug messages\n"
|
||||
" -E Exclude /dev, /proc, /run, and /sys\n"
|
||||
" --exclude-prefix=PFX Ignore rules for paths starting with PFX\n"
|
||||
" -r, --remove Remove files and directories marked for removal\n"
|
||||
" -h, --help This help text\n"
|
||||
"\n"
|
||||
"If no CONFIGFILE is specified, all *.conf files in the standard\n"
|
||||
"tmpfiles.d directories are processed.\n"
|
||||
"\n");
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
enum {
|
||||
OPT_EXCLUDE_PREFIX = 256,
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct option long_options[] = {
|
||||
{ "create", 0, NULL, 'c' },
|
||||
{ "debug", 0, NULL, 'd' },
|
||||
{ "remove", 0, NULL, 'r' },
|
||||
{ "help", 0, NULL, 'h' },
|
||||
{ "clean", 0, NULL, 'C' },
|
||||
{ "create", 0, NULL, 'c' },
|
||||
{ "debug", 0, NULL, 'd' },
|
||||
{ "exclude-prefix", 1, NULL, OPT_EXCLUDE_PREFIX },
|
||||
{ "remove", 0, NULL, 'r' },
|
||||
{ "help", 0, NULL, 'h' },
|
||||
{ NULL, 0, NULL, 0 }
|
||||
};
|
||||
|
||||
int c;
|
||||
|
||||
while ((c = getopt_long(argc, argv, "cdrh?", long_options, NULL)) != EOF) {
|
||||
while ((c = getopt_long(argc, argv, "CcdrEh?", long_options, NULL)) != EOF) {
|
||||
switch(c) {
|
||||
case 'C':
|
||||
clean_flag = 1;
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
c_flag = 1;
|
||||
create_flag = 1;
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
debug = 1;
|
||||
break;
|
||||
|
||||
case 'E':
|
||||
add_exclude_prefix("/dev");
|
||||
add_exclude_prefix("/proc");
|
||||
add_exclude_prefix("/run");
|
||||
add_exclude_prefix("/sys");
|
||||
break;
|
||||
|
||||
case OPT_EXCLUDE_PREFIX:
|
||||
add_exclude_prefix(optarg);
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
r_flag = 1;
|
||||
remove_flag = 1;
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
@@ -582,12 +842,21 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
}
|
||||
|
||||
if (c_flag + r_flag == 0) {
|
||||
fprintf(stderr, "You need to specify at least one of --create or --remove.\n");
|
||||
if (create_flag + clean_flag + remove_flag == 0) {
|
||||
fprintf(stderr, "You need to specify at least one of --clean, --create, or --remove.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* If config files specified on command line, process only those */
|
||||
if (optind < argc) {
|
||||
for (int i = optind; i < argc; i++)
|
||||
process_file(argv[i]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* No config files specified, process standard tmpfiles.d directories.
|
||||
*
|
||||
* Only the three last tmpfiles.d/ directories are defined in
|
||||
* tmpfiles.d(5) as system search paths. Finit adds two more
|
||||
* before that to have Finit specific ones sorted first, and
|
||||
@@ -614,7 +883,6 @@ int main(int argc, char *argv[])
|
||||
for (i = 0; i < gl.gl_pathc; i++) {
|
||||
char *fn = gl.gl_pathv[i];
|
||||
size_t j;
|
||||
FILE *fp;
|
||||
|
||||
/* check for overrides */
|
||||
for (j = i + 1; j < gl.gl_pathc; j++) {
|
||||
@@ -627,26 +895,12 @@ int main(int argc, char *argv[])
|
||||
if (!fn)
|
||||
continue; /* skip, override exists */
|
||||
|
||||
fp = fopen(fn, "r");
|
||||
if (!fp)
|
||||
continue;
|
||||
|
||||
// info("Parsing %s ...", fn);
|
||||
while (!feof(fp)) {
|
||||
char *line;
|
||||
|
||||
line = fparseln(fp, NULL, NULL, NULL, FPARSELN_UNESCCOMM);
|
||||
if (!line)
|
||||
continue;
|
||||
|
||||
tmpfiles(line);
|
||||
free(line);
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
process_file(fn);
|
||||
}
|
||||
|
||||
globfree(&gl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+38
-4
@@ -180,16 +180,34 @@ char *str(char *fmt, ...)
|
||||
return buf;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return UID for the given user, with fallback to 'root' if user is unset.
|
||||
* Fails with -1 only if user (incl. fallback) does not exist.
|
||||
*/
|
||||
int getuser(const char *username, char **home)
|
||||
{
|
||||
#ifdef ENABLE_STATIC
|
||||
if (!username || !username[0])
|
||||
username = "root";
|
||||
|
||||
if (home)
|
||||
*home = "/"; /* XXX: Fixme */
|
||||
return fgetint("/etc/passwd", "x:\n", username);
|
||||
#else
|
||||
struct passwd *usr;
|
||||
const struct passwd *usr;
|
||||
|
||||
if (!username || (usr = getpwnam(username)) == NULL)
|
||||
if (!username || !username[0]) {
|
||||
usr = getpwuid(0);
|
||||
if (!usr)
|
||||
return -1;
|
||||
|
||||
if (home)
|
||||
*home = usr->pw_dir;
|
||||
return usr->pw_uid;
|
||||
}
|
||||
|
||||
usr = getpwnam(username);
|
||||
if (!usr)
|
||||
return -1;
|
||||
|
||||
if (home)
|
||||
@@ -198,14 +216,30 @@ int getuser(const char *username, char **home)
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Return GID for the given group, with fallback to 'root' if group is unset.
|
||||
* Fails with -1 only if user (incl. fallback) does not exist.
|
||||
*/
|
||||
int getgroup(const char *group)
|
||||
{
|
||||
#ifdef ENABLE_STATIC
|
||||
if (!group || !group[0])
|
||||
group = "root";
|
||||
|
||||
return fgetint("/etc/group", "x:\n", group);
|
||||
#else
|
||||
struct group *grp;
|
||||
const struct group *grp;
|
||||
|
||||
if ((grp = getgrnam(group)) == NULL)
|
||||
if (!group || !group[0]) {
|
||||
grp = getgrgid(0);
|
||||
if (!grp)
|
||||
return -1;
|
||||
|
||||
return grp->gr_gid;
|
||||
}
|
||||
|
||||
grp = getgrnam(group);
|
||||
if (!grp)
|
||||
return -1;
|
||||
|
||||
return grp->gr_gid;
|
||||
|
||||
@@ -10,6 +10,7 @@ EXTRA_DIST = skel/bin/busybox-x86_64.sha256 skel/sbin/service.sh skel/etc/env
|
||||
skel/etc/rcS.d/S01abc.sh skel/etc/rcS.d/S02def.sh \
|
||||
skel/cdrom/.empty skel/dev/shm/.empty skel/dev/pts/.empty \
|
||||
skel/etc/inittab skel/etc/hostname skel/etc/fstab \
|
||||
skel/etc/passwd skel/etc/group skel/etc/ld.so.conf \
|
||||
skel/etc/init.d/rcS skel/etc/init.d/rcK skel/tmp/.empty \
|
||||
skel/etc/finit.d/.empty skel/etc/finit.d/available/.empty \
|
||||
skel/etc/finit.d/enabled/.empty \
|
||||
@@ -31,6 +32,7 @@ EXTRA_DIST += add-remove-dynamic-service-sub-config.sh
|
||||
EXTRA_DIST += bootstrap-crash.sh
|
||||
EXTRA_DIST += cond-start-task.sh
|
||||
EXTRA_DIST += crashing.sh
|
||||
EXTRA_DIST += dep-chain-reload.sh
|
||||
EXTRA_DIST += depserv.sh
|
||||
EXTRA_DIST += devmon.sh
|
||||
EXTRA_DIST += failing-sysv.sh
|
||||
@@ -73,6 +75,7 @@ TESTS += add-remove-dynamic-service-sub-config.sh
|
||||
TESTS += bootstrap-crash.sh
|
||||
TESTS += cond-start-task.sh
|
||||
TESTS += crashing.sh
|
||||
TESTS += dep-chain-reload.sh
|
||||
TESTS += depserv.sh
|
||||
TESTS += devmon.sh
|
||||
TESTS += failing-sysv.sh
|
||||
|
||||
Executable
+181
@@ -0,0 +1,181 @@
|
||||
#!/bin/sh
|
||||
# Verify transitive dependency chain during reload and crash
|
||||
#
|
||||
# Four services in a chain: A → B → C → D, all using notify:pid.
|
||||
# B and C are placed in separate sub-config files so we can use
|
||||
# 'initctl touch' on them independently. B supports SIGHUP (reload),
|
||||
# while C and D use '!' (noreload) and '~' (propagate reload),
|
||||
# matching a common pattern in real-world setups (e.g., FRR routing
|
||||
# daemons on Infix OS).
|
||||
#
|
||||
# Chain: A ← B <pid/A> ← C <!~pid/B> ← D <!~pid/C>
|
||||
#
|
||||
# Test 1 - touch C + reload (the "Infix" scenario):
|
||||
# C is in the middle of the chain. After 'initctl touch svc_c.conf'
|
||||
# + 'initctl reload':
|
||||
# - A and B should be unaffected (same PID)
|
||||
# - C is stopped/started (config was touched, noreload)
|
||||
# - D must be restarted (reload propagated from C)
|
||||
#
|
||||
# Test 2 - crash (kill -9):
|
||||
# When B is killed with SIGKILL the crash path (RUNNING → HALTED)
|
||||
# bypasses STOPPING. Without cond_clear() in service_cleanup(),
|
||||
# pid/B is never invalidated and C, D are never restarted.
|
||||
#
|
||||
# Test 3 - touch B + reload:
|
||||
# B supports SIGHUP (no '!' prefix). After 'initctl touch
|
||||
# svc_b.conf' + 'initctl reload':
|
||||
# - A should be unaffected (same PID)
|
||||
# - B is SIGHUP'd (same PID, config was touched)
|
||||
# - C and D must be restarted (reload propagated, '~' prefix)
|
||||
|
||||
set -eu
|
||||
|
||||
TEST_DIR=$(dirname "$0")
|
||||
|
||||
test_teardown()
|
||||
{
|
||||
say "Running test teardown."
|
||||
run "rm -f $FINIT_RCSD/svc_b.conf $FINIT_RCSD/svc_c.conf"
|
||||
}
|
||||
|
||||
pidof()
|
||||
{
|
||||
texec initctl -j status "$1" | jq .pid
|
||||
}
|
||||
|
||||
test_setup()
|
||||
{
|
||||
run "cat >> $FINIT_CONF" <<EOF
|
||||
service log:stdout notify:pid name:svc_a serv -np -i svc_a -- Chain root
|
||||
service log:stdout notify:pid <!~pid/svc_c> name:svc_d serv -np -i svc_d -- Needs C
|
||||
EOF
|
||||
run "cat >> $FINIT_RCSD/svc_b.conf" <<EOF
|
||||
service log:stdout notify:pid <pid/svc_a> name:svc_b serv -np -i svc_b -- Needs A
|
||||
EOF
|
||||
run "cat >> $FINIT_RCSD/svc_c.conf" <<EOF
|
||||
service log:stdout notify:pid <!~pid/svc_b> name:svc_c serv -np -i svc_c -- Needs B
|
||||
EOF
|
||||
}
|
||||
|
||||
# shellcheck source=/dev/null
|
||||
. "$TEST_DIR/lib/setup.sh"
|
||||
|
||||
sep "Configuration"
|
||||
run "cat $FINIT_CONF"
|
||||
run "cat $FINIT_RCSD/svc_b.conf"
|
||||
run "cat $FINIT_RCSD/svc_c.conf"
|
||||
|
||||
say "Reload Finit to start all services"
|
||||
run "initctl reload"
|
||||
|
||||
say "Wait for full chain to start"
|
||||
retry 'assert_status "svc_d" "running"' 10 1
|
||||
|
||||
run "initctl status"
|
||||
run "initctl cond dump"
|
||||
|
||||
# ――――――――――――――――――――――――――――――――――――――――――――――――――――――
|
||||
# Test 1: touch C (middle of chain, noreload) + reload
|
||||
# ――――――――――――――――――――――――――――――――――――――――――――――――――――――
|
||||
sep "Test 1: Touch C (middle) and global reload"
|
||||
|
||||
pid_a=$(pidof svc_a)
|
||||
pid_b=$(pidof svc_b)
|
||||
pid_c=$(pidof svc_c)
|
||||
pid_d=$(pidof svc_d)
|
||||
say "PIDs before: A=$pid_a B=$pid_b C=$pid_c D=$pid_d"
|
||||
|
||||
run "initctl touch svc_c.conf"
|
||||
run "initctl reload"
|
||||
|
||||
say "Wait for chain to settle"
|
||||
retry 'assert_status "svc_d" "running"' 15 1
|
||||
|
||||
run "initctl status"
|
||||
run "initctl cond dump"
|
||||
|
||||
new_pid_a=$(pidof svc_a)
|
||||
new_pid_b=$(pidof svc_b)
|
||||
new_pid_c=$(pidof svc_c)
|
||||
new_pid_d=$(pidof svc_d)
|
||||
say "PIDs after: A=$new_pid_a B=$new_pid_b C=$new_pid_c D=$new_pid_d"
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
assert "A was not restarted" $new_pid_a -eq $pid_a
|
||||
# shellcheck disable=SC2086
|
||||
assert "B was not restarted" $new_pid_b -eq $pid_b
|
||||
# shellcheck disable=SC2086
|
||||
assert "C was restarted (touched)" $new_pid_c -ne $pid_c
|
||||
# shellcheck disable=SC2086
|
||||
assert "D was restarted (transitive dep)" $new_pid_d -ne $pid_d
|
||||
|
||||
# ――――――――――――――――――――――――――――――――――――――――――――――――――――――
|
||||
# Test 2: crash (kill -9), bypasses STOPPING
|
||||
# ――――――――――――――――――――――――――――――――――――――――――――――――――――――
|
||||
sep "Test 2: Kill B with SIGKILL (bypasses STOPPING)"
|
||||
|
||||
pid_b=$(pidof svc_b)
|
||||
pid_c=$(pidof svc_c)
|
||||
pid_d=$(pidof svc_d)
|
||||
say "PIDs before: B=$pid_b C=$pid_c D=$pid_d"
|
||||
|
||||
run "kill -9 $pid_b"
|
||||
|
||||
say "Wait for B to respawn and chain to settle"
|
||||
retry 'assert_status "svc_d" "running"' 15 1
|
||||
|
||||
run "initctl status"
|
||||
run "initctl cond dump"
|
||||
|
||||
new_pid_b=$(pidof svc_b)
|
||||
new_pid_c=$(pidof svc_c)
|
||||
new_pid_d=$(pidof svc_d)
|
||||
say "PIDs after: B=$new_pid_b C=$new_pid_c D=$new_pid_d"
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
assert "B was restarted (crashed+respawn)" $new_pid_b -ne $pid_b
|
||||
# shellcheck disable=SC2086
|
||||
assert "C was restarted (transitive dep)" $new_pid_c -ne $pid_c
|
||||
# shellcheck disable=SC2086
|
||||
assert "D was restarted (transitive dep)" $new_pid_d -ne $pid_d
|
||||
|
||||
# ――――――――――――――――――――――――――――――――――――――――――――――――――――――
|
||||
# Test 3: touch B (supports SIGHUP) + reload
|
||||
# ――――――――――――――――――――――――――――――――――――――――――――――――――――――
|
||||
sep "Test 3: Touch B (SIGHUP) and global reload"
|
||||
|
||||
pid_a=$(pidof svc_a)
|
||||
pid_b=$(pidof svc_b)
|
||||
pid_c=$(pidof svc_c)
|
||||
pid_d=$(pidof svc_d)
|
||||
say "PIDs before: A=$pid_a B=$pid_b C=$pid_c D=$pid_d"
|
||||
|
||||
run "initctl debug"
|
||||
run "initctl touch svc_b.conf"
|
||||
run "initctl reload"
|
||||
sleep 2
|
||||
run "initctl status"
|
||||
|
||||
say "Wait for chain to settle"
|
||||
retry 'assert_status "svc_d" "running"' 15 1
|
||||
|
||||
run "initctl status"
|
||||
run "initctl cond dump"
|
||||
|
||||
new_pid_a=$(pidof svc_a)
|
||||
new_pid_b=$(pidof svc_b)
|
||||
new_pid_c=$(pidof svc_c)
|
||||
new_pid_d=$(pidof svc_d)
|
||||
say "PIDs after: A=$new_pid_a B=$new_pid_b C=$new_pid_c D=$new_pid_d"
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
assert "A was not restarted" $new_pid_a -eq $pid_a
|
||||
# shellcheck disable=SC2086
|
||||
assert "B was not restarted (SIGHUP)" $new_pid_b -eq $pid_b
|
||||
# shellcheck disable=SC2086
|
||||
assert "C was restarted (transitive dep)" $new_pid_c -ne $pid_c
|
||||
# shellcheck disable=SC2086
|
||||
assert "D was restarted (transitive dep)" $new_pid_d -ne $pid_d
|
||||
|
||||
return 0
|
||||
+12
-3
@@ -5,6 +5,7 @@
|
||||
# - bar must not start until foo is ready
|
||||
# - bar must be stopped when foo goes down
|
||||
# - bar must be restarted when foo is restarted
|
||||
# - bar must not be restarted when foo is reloaded (per-service reload)
|
||||
#
|
||||
# Regression test bug #314, that bar is not in a restart loop when foo
|
||||
# is stopped. Also, verify that changing between runlevels, where foo
|
||||
@@ -49,8 +50,15 @@ test_one()
|
||||
run "initctl status"
|
||||
assert "bar is restarted" "$(texec initctl |grep bar | awk '{print $1;}')" != "$pid"
|
||||
|
||||
say "Verify bar is NOT restarted when foo is reloaded (per-service) ..."
|
||||
retry 'assert_status "bar" "running"' 5 1
|
||||
pid=$(texec initctl |grep bar | awk '{print $1;}')
|
||||
run "initctl reload foo"
|
||||
retry 'assert_status "bar" "running"' 5 1
|
||||
assert "bar is NOT restarted on reload" "$(texec initctl |grep bar | awk '{print $1;}')" == "$pid"
|
||||
|
||||
# Wait for spice to be stolen by the Harkonnen
|
||||
sleep 3
|
||||
sleep 5
|
||||
# bar should now have detected the loss of spice and be in restart
|
||||
run "initctl status"
|
||||
assert_status "bar" "restart"
|
||||
@@ -61,8 +69,9 @@ test_one()
|
||||
assert_status "bar" "waiting"
|
||||
}
|
||||
|
||||
run "initctl debug"
|
||||
sep
|
||||
test_one "pid/foo"
|
||||
sep
|
||||
run "initctl debug"
|
||||
test_one "service/foo/running"
|
||||
#run "initctl debug"
|
||||
test_one "service/foo/ready"
|
||||
|
||||
@@ -42,6 +42,11 @@ test_one()
|
||||
|
||||
mkdev "$node"
|
||||
assert_status "serv" "running"
|
||||
assert_cond "$cond"
|
||||
|
||||
run "initctl reload"
|
||||
assert_status "serv" "running"
|
||||
assert_cond "$cond"
|
||||
|
||||
rmdev "$node"
|
||||
assert_status "serv" "waiting"
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
set -eu
|
||||
|
||||
echo "=== Finit Test Sysroot Setup ==="
|
||||
echo "Date: $(date)"
|
||||
echo "SYSROOT: $SYSROOT"
|
||||
echo "top_builddir: $top_builddir"
|
||||
echo "srcdir: $srcdir"
|
||||
echo "================================"
|
||||
echo
|
||||
|
||||
# shellcheck disable=SC2154
|
||||
make -C "$top_builddir" DESTDIR="$SYSROOT" install
|
||||
|
||||
@@ -21,3 +29,13 @@ done
|
||||
for conf in 10-hotplug.conf; do
|
||||
find "$SYSROOT" -name $conf -delete
|
||||
done
|
||||
|
||||
# Update dynamic linker cache for /usr/local/lib libraries
|
||||
echo "Running ldconfig in sysroot: $SYSROOT"
|
||||
echo "Contents of $SYSROOT/etc/ld.so.conf:"
|
||||
cat "$SYSROOT/etc/ld.so.conf" || echo "Warning: ld.so.conf not found"
|
||||
echo "Libraries in $SYSROOT/usr/local/lib:"
|
||||
ls -la "$SYSROOT/usr/local/lib/" 2>/dev/null || echo "Warning: /usr/local/lib not found in sysroot"
|
||||
ldconfig -v -r "$SYSROOT" || echo "Warning: ldconfig failed with exit code $?"
|
||||
echo "Verifying ldconfig cache was created:"
|
||||
ls -la "$SYSROOT/etc/ld.so.cache" || echo "Warning: ld.so.cache not created"
|
||||
|
||||
+1
-1
@@ -52,4 +52,4 @@ done
|
||||
echo "$pid" > /tmp/oldpid
|
||||
|
||||
#echo "PID $pid, kill -9 ..."
|
||||
kill -9 "$pid"
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
root:x:0:
|
||||
daemon:x:1:
|
||||
bin:x:2:
|
||||
sys:x:3:
|
||||
adm:x:4:
|
||||
tty:x:5:
|
||||
disk:x:6:
|
||||
dialout:x:20:
|
||||
nogroup:x:65534:
|
||||
@@ -0,0 +1,3 @@
|
||||
/usr/local/lib
|
||||
/usr/lib
|
||||
/lib
|
||||
@@ -0,0 +1,5 @@
|
||||
root:x:0:0:root:/root:/bin/sh
|
||||
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
|
||||
bin:x:2:2:bin:/bin:/usr/sbin/nologin
|
||||
sys:x:3:3:sys:/dev:/usr/sbin/nologin
|
||||
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
|
||||
@@ -325,6 +325,12 @@ int main(int argc, char *argv[])
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (melange) {
|
||||
mine(melange);
|
||||
vanish = -2;
|
||||
}
|
||||
|
||||
if (do_pidfile > 0)
|
||||
pidfile(NULL);
|
||||
reloading = 0;
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ d /var/lib/alarm 0755 - - -
|
||||
d /var/lib/misc 0755 - - -
|
||||
d /var/lib/urandom 0755 - - -
|
||||
d /var/spool 0755 - - -
|
||||
d /var/tmp 0777 - - -
|
||||
d /var/tmp 1777 - - -
|
||||
|
||||
# Used by pam_console(8)
|
||||
d /run/console 1777 - - -
|
||||
|
||||
Reference in New Issue
Block a user