mirror of
https://github.com/troglobit/finit.git
synced 2026-10-12 10:12:32 +00:00
Compare commits
36
Commits
rdep-stop-cond
...
4.x
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70a44d3860 | ||
|
|
f29a3db86c | ||
|
|
f6b394e0a2 | ||
|
|
ccaa4caf48 | ||
|
|
77d199f1a4 | ||
|
|
0448151c0a | ||
|
|
75739fbeef | ||
|
|
3a72d1263c | ||
|
|
7e2b154f9b | ||
|
|
547e59b2ad | ||
|
|
7f37d0c4e9 | ||
|
|
dd77f63ad3 | ||
|
|
e0f9df1f27 | ||
|
|
b56ffc155e | ||
|
|
4596e27eb0 | ||
|
|
a3eac3a85e | ||
|
|
7d09e34b80 | ||
|
|
3febc7d513 | ||
|
|
309ad1bea7 | ||
|
|
30f2ca3b2e | ||
|
|
4a53f610cd | ||
|
|
e74dff96ba | ||
|
|
9ed4407b20 | ||
|
|
eb913e8bf2 | ||
|
|
17d04779fb | ||
|
|
ab661423ba | ||
|
|
8e77ea68b2 | ||
|
|
b0e672bc9a | ||
|
|
ad76e15949 | ||
|
|
b8735cc893 | ||
|
|
c058b3f9ce | ||
|
|
8f6c61606a | ||
|
|
96f944b816 | ||
|
|
a81530754b | ||
|
|
1518eb9466 | ||
|
|
50b9e46e19 |
@@ -1,12 +1,13 @@
|
||||
name: Bob the Builder
|
||||
|
||||
# Run on all branches, including all pull requests, except the 'dev'
|
||||
# branch since that's where we run Coverity Scan (limited tokens/day)
|
||||
# Run on push to release branches (master and maintenance N.x), and
|
||||
# on all pull requests. The 'dev' branch is where we run Coverity
|
||||
# Scan (limited tokens/day)
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
- '!dev'
|
||||
- master
|
||||
- '[0-9]+.x'
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, labeled]
|
||||
|
||||
@@ -22,8 +23,6 @@ jobs:
|
||||
# Verify we can build on latest Ubuntu with both gcc and clang
|
||||
name: ${{ matrix.compiler }}
|
||||
runs-on: ubuntu-latest
|
||||
# Skip redundant builds for PRs - prefer PR builds over push builds
|
||||
if: github.event_name != 'push' || github.ref == 'refs/heads/master'
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: [gcc, clang]
|
||||
@@ -43,7 +42,7 @@ jobs:
|
||||
(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
|
||||
- uses: actions/checkout@v7
|
||||
- name: Static Finit
|
||||
run: |
|
||||
./autogen.sh
|
||||
@@ -81,7 +80,7 @@ jobs:
|
||||
make -j1 check || (cat test/test-suite.log; false)
|
||||
- name: Upload Test Results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: finit-test-${{ matrix.compiler }}
|
||||
path: test/*.log
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
coverity:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
- name: Fetch latest Coverity Scan MD5
|
||||
id: var
|
||||
env:
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
--post-data "token=$TOKEN&project=${COVERITY_PROJ}&md5=1" \
|
||||
-O coverity-latest.tar.gz.md5
|
||||
echo "md5=$(cat coverity-latest.tar.gz.md5)" | tee -a $GITHUB_OUTPUT
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
id: cache
|
||||
with:
|
||||
path: coverity-latest.tar.gz
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
--form description="${PROJECT_NAME} $(git rev-parse HEAD)" \
|
||||
https://scan.coverity.com/builds?project=${COVERITY_PROJ}
|
||||
- name: Upload build.log
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: coverity-build.log
|
||||
path: cov-int/build-log.txt
|
||||
|
||||
@@ -4,10 +4,12 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- '[0-9]+.x'
|
||||
paths:
|
||||
- 'doc/**'
|
||||
- 'README.md'
|
||||
- 'mkdocs.yml'
|
||||
- 'configure.ac'
|
||||
- '.github/workflows/docs.yml'
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, labeled]
|
||||
@@ -15,6 +17,7 @@ on:
|
||||
- 'doc/**'
|
||||
- 'README.md'
|
||||
- 'mkdocs.yml'
|
||||
- 'configure.ac'
|
||||
- '.github/workflows/docs.yml'
|
||||
|
||||
permissions:
|
||||
@@ -23,14 +26,23 @@ permissions:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0 # Needed for git-revision-date-localized plugin
|
||||
|
||||
- name: Determine docs version
|
||||
id: version
|
||||
run: |
|
||||
version=$(sed -n 's/^AC_INIT(\[Finit\], *\[\([0-9]*\)\..*/\1.x/p' configure.ac)
|
||||
test -n "$version"
|
||||
echo "version=$version" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
@@ -46,25 +58,27 @@ jobs:
|
||||
run: mkdocs build --clean
|
||||
|
||||
- name: Upload site artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: site
|
||||
path: site/
|
||||
|
||||
deploy:
|
||||
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
|
||||
if: github.event_name == 'push'
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
VERSION: ${{ needs.build.outputs.version }}
|
||||
|
||||
steps:
|
||||
- name: Download site artifact
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: site
|
||||
path: site/
|
||||
|
||||
- name: Checkout pages repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
repository: finit-project/finit-project.github.io
|
||||
path: pages
|
||||
@@ -79,7 +93,56 @@ jobs:
|
||||
|
||||
- name: Sync site to pages repo
|
||||
run: |
|
||||
rsync -a --delete --exclude .git site/ pages/
|
||||
rsync -a --delete site/ "pages/$VERSION/"
|
||||
|
||||
- name: Update version index and redirects
|
||||
run: |
|
||||
cd pages/
|
||||
|
||||
# The root holds only version directories and the files
|
||||
# generated below, drop anything else (pre-versioned site)
|
||||
find . -maxdepth 1 ! -name . ! -name .git ! -name CNAME \
|
||||
! -name '[0-9]*.x' -exec rm -rf {} +
|
||||
|
||||
latest=$(ls -d [0-9]*.x | sort -rV | head -1)
|
||||
|
||||
sep=
|
||||
printf '[' > versions.json
|
||||
for v in $(ls -d [0-9]*.x | sort -rV); do
|
||||
printf '%s{"version": "%s", "title": "%s", "aliases": []}' \
|
||||
"$sep" "$v" "$v" >> versions.json
|
||||
sep=', '
|
||||
done
|
||||
printf ']\n' >> versions.json
|
||||
|
||||
cat > index.html <<EOF
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="refresh" content="0; url=$latest/">
|
||||
<link rel="canonical" href="https://finit-project.github.io/$latest/">
|
||||
</head>
|
||||
</html>
|
||||
EOF
|
||||
|
||||
cat > 404.html <<EOF
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Finit — page not found</title>
|
||||
<script>
|
||||
var path = window.location.pathname;
|
||||
if (!/^\/[0-9]+\.x(\/|$)/.test(path))
|
||||
window.location.replace("/$latest" + path);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<p>Page not found. Try the <a href="/$latest/">latest User Guide</a>.</p>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
|
||||
- name: Commit and push
|
||||
run: |
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
- name: Installing dependencies ...
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
@@ -31,12 +31,17 @@ jobs:
|
||||
run: |
|
||||
ver=${GITHUB_REF#refs/tags/}
|
||||
echo "ver=${ver}" >> $GITHUB_OUTPUT
|
||||
if echo $ver | grep -qE '^[0-9]+\.[0-9]+(\.[0-9]+)?(-alpha|-beta|-rc)[0-9]*$'; then
|
||||
stable='[0-9]+\.[0-9]+(\.[0-9]+)?'
|
||||
if echo $ver | grep -qE "^${stable}(-alpha|-beta|-rc)[0-9]*$"; then
|
||||
echo "pre=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "pre=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
if echo $ver | grep -qE '^[0-9.]+\.[0-9.]+(\.[0-9]+)?$'; then
|
||||
# Mark as latest release only if this is the highest stable
|
||||
# tag, so a 4.x maintenance release doesn't outrank 5.x
|
||||
newest=$(git ls-remote --tags --refs origin | awk -F/ '{print $NF}' \
|
||||
| grep -E "^${stable}$" | sort -V | tail -1)
|
||||
if [ "$ver" = "$newest" ]; then
|
||||
echo "latest=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "latest=false" >> $GITHUB_OUTPUT
|
||||
@@ -64,7 +69,7 @@ jobs:
|
||||
mv ../*.tar.* artifacts/
|
||||
- name: Upload Test Results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: finit-release-test-logs
|
||||
path: finit-*/_build/sub/test/*.log
|
||||
|
||||
@@ -14,22 +14,31 @@ jobs:
|
||||
schedule:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
run: ${{ steps.schedule.outputs.run }}
|
||||
branches: ${{ steps.schedule.outputs.branches }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check for any commits since last week
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Check for branches with no commits since last week
|
||||
id: schedule
|
||||
run: |
|
||||
commits="$(git log --format=%H --since='1 week ago')"
|
||||
if [ -z "$commits" ]; then
|
||||
echo "run=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "run=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
branches=
|
||||
maint=$(git branch -r --format='%(refname:lstrip=3)' | grep -E '^[0-9]+\.x$')
|
||||
for branch in master $maint; do
|
||||
commits="$(git log --format=%H --since='1 week ago' origin/$branch)"
|
||||
if [ -z "$commits" ]; then
|
||||
branches="$branches\"$branch\","
|
||||
fi
|
||||
done
|
||||
echo "branches=[${branches%,}]" >> $GITHUB_OUTPUT
|
||||
distcheck:
|
||||
needs: schedule
|
||||
if: ${{ needs.schedule.outputs.run == 'true' }}
|
||||
if: ${{ needs.schedule.outputs.branches != '[]' }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
branch: ${{ fromJSON(needs.schedule.outputs.branches) }}
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
@@ -42,7 +51,9 @@ jobs:
|
||||
(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
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ matrix.branch }}
|
||||
- name: Base run
|
||||
run: |
|
||||
./autogen.sh
|
||||
@@ -61,7 +72,7 @@ jobs:
|
||||
make -j1 distcheck || (cat $dir/test/test-suite.log; false)
|
||||
- name: Upload Test Results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: finit-weekly-test-logs
|
||||
name: finit-weekly-test-logs-${{ matrix.branch }}
|
||||
path: finit-*/_build/sub/test/*.log
|
||||
|
||||
+2
-1
@@ -1,4 +1,4 @@
|
||||
AC_INIT([Finit], [4.16], [https://github.com/finit-project/finit/issues],
|
||||
AC_INIT([Finit], [4.18-pre], [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
|
||||
|
||||
+83
-1
@@ -3,6 +3,87 @@ Change Log
|
||||
|
||||
All relevant changes are documented in this file.
|
||||
|
||||
[Unreleased]
|
||||
------------
|
||||
|
||||
### Changes
|
||||
|
||||
- Restart log now spells out the signal name and flags core dumps,
|
||||
e.g. `killed by SIGKILL` or `killed by SIGSEGV, core dumped`, in
|
||||
place of the bare numeric `by signal: N`. Gives operators a much
|
||||
stronger breadcrumb when a daemon dies unexpectedly
|
||||
- Service `user:` and `group:` names, including supplementary groups,
|
||||
can now be up to 63 characters, up from 15. Longer names were
|
||||
silently truncated before the lookup, so the service never started.
|
||||
By Anees Rehman
|
||||
- Shutdown now stops after the first successful remount of `/` read
|
||||
only. The plain `mount -n -o remount,ro /`, which reads
|
||||
`/etc/fstab`, is only used when both device-agnostic forms fail,
|
||||
the same order as Debian's `umountroot`. By Anees Rehman
|
||||
|
||||
### Fixes
|
||||
|
||||
- Remove stale daemon-owned (`pid:!`) pidfiles after unclean exits.
|
||||
When a daemon dies via SIGKILL/OOM/segfault, or exits early during
|
||||
startup, its pidfile lingers and can prevent the next instance from
|
||||
starting -- `dbus-daemon` for example refuses to start when its
|
||||
pidfile already exists, so Finit's restart loop gives up. Finit now
|
||||
drops the file when it still names the just-reaped PID and that PID
|
||||
is no longer alive (the liveness check guards against PID reuse).
|
||||
This is a controlled exception to the long-standing rule that Finit
|
||||
does not touch service-owned pidfiles
|
||||
- Fix misspelled `SIGUNKOWN` returned by `sig_name()` for unknown
|
||||
signal numbers, now spelled correctly as `SIGUNKNOWN`. Surfaced by
|
||||
the new restart log above
|
||||
- Fix mode of existing `d`/`D` directories in tmpfiles, the chmod branch
|
||||
never ran because the directory creation swallowed `EEXIST`. Mode and
|
||||
owner of `d`/`D`, `e`, and `f`/`F` entries, and of the directories
|
||||
`mksubsys()` creates for the dbus plugin, are now set through an open fd
|
||||
and only when they differ, so an immutable directory like `/var/empty`
|
||||
no longer warns on every boot. By Sam Brkopac
|
||||
- Fix crash at config load for a `stop:` or `reload:` script written
|
||||
with a timeout, e.g. `stop:5,/bin/true`, which took PID 1 down
|
||||
- Fix a lost reload for a service paused mid-reload by a condition in
|
||||
flux. A second reload arriving meanwhile re-parsed the unchanged .conf
|
||||
and cleared the pending mark, so the service resumed without ever being
|
||||
reloaded. Seen on Infix, where sshd kept its old listen addresses after
|
||||
a change that landed as two reloads in a row
|
||||
|
||||
[4.17][] - 2026-04-28
|
||||
---------------------
|
||||
|
||||
### Changes
|
||||
|
||||
- Add Plymouth boot splash plugin to manage the `plymouthd` lifecycle across boot,
|
||||
`switch_root`, and shutdown. Activated by the `splash` kernel command line
|
||||
argument. By Aaron Andersen
|
||||
- Add `tty:` option for run/task/service stanzas to acquire a controlling
|
||||
terminal. The device is opened as stdin, set as the session's controlling TTY
|
||||
via `TIOCSCTTY`, and `TERM` is set automatically — `linux` for virtual terminals
|
||||
and `vt102` for serial lines. By Ollie Gutierrez
|
||||
- Improve console output atomicity at boot and shutdown: description and status
|
||||
(`[ OK ]` / `[FAIL]`) are emitted in a single write so kernel messages cannot
|
||||
split them. If output has been scrolled away, the description is re-printed
|
||||
before the final status line. Console buffer is drained with `tcdrain(2)` and
|
||||
ANSI attributes are reset before handing control back to the kernel at
|
||||
shutdown/reboot
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fix reverse-dependency condition handling on reload: when a service without
|
||||
SIGHUP support is reloaded, its condition is now cleared immediately so
|
||||
dependents transition to `STOPPING` before `SIGTERM` is sent to the parent,
|
||||
preventing them from crashing with an incremented restart counter
|
||||
- Fix `/var/tmp` mode in `tmpfiles.d/var.conf` from `0777` to `1777` (sticky bit)
|
||||
to match `/tmp`, by Aaron Andersen
|
||||
- Fix potential memory corruption in config parser: stale pointer captures of
|
||||
`PATH` and `SHELL` at startup could dangle after environment modification;
|
||||
`conf_reset_env()` now reads from the live environment instead, by Vasily Zubko
|
||||
- Fix plugin name registration: replace `__FILE__` macro with explicit string
|
||||
literals in all plugin structs and debug messages, preventing subtle build-
|
||||
system–dependent macro expansion issues, by Vasily Zubko
|
||||
|
||||
|
||||
[4.16][] - 2026-02-27
|
||||
---------------------
|
||||
|
||||
@@ -1968,7 +2049,8 @@ Major bug fix release.
|
||||
|
||||
* Initial release
|
||||
|
||||
[UNRELEASED]: https://github.com/finit-project/finit/compare/4.15...HEAD
|
||||
[UNRELEASED]: https://github.com/finit-project/finit/compare/4.17...HEAD
|
||||
[4.17]: https://github.com/finit-project/finit/compare/4.16...4.17
|
||||
[4.16]: https://github.com/finit-project/finit/compare/4.15...4.16
|
||||
[4.15]: https://github.com/finit-project/finit/compare/4.14...4.15
|
||||
[4.14]: https://github.com/finit-project/finit/compare/4.13...4.14
|
||||
|
||||
@@ -46,6 +46,7 @@ Other run/task/service options are:
|
||||
* `cgroup.NAME[,opts]` or `cgroup:opts` -- see the [Cgroups](cgroups.md) section
|
||||
* `env:[-]/path/to/env` -- see the [Service Environment](service-env.md) section
|
||||
* `log:...` -- see [Redirecting Output](logging.md#redirecting-output)
|
||||
* `tty:<dev>` -- see [Controlling TTY](tty.md#controlling-tty)
|
||||
* `nowarn` -- see [Conditional Loading](services.md#conditional-loading)
|
||||
* `notify:...` -- see [Service Synchronization](service-sync.md)
|
||||
* `if:...` -- see [Conditional Execution](services.md#conditional-execution)
|
||||
|
||||
@@ -31,6 +31,14 @@ basename of the binary to guess the PID file to watch for the PID:
|
||||
`/var/run/ntpd.pid`. If Finit guesses wrong, you have to submit the
|
||||
full `pid:!/path/to/file.pid`.
|
||||
|
||||
With `pid:!/path`, the file belongs to the service: Finit reads it
|
||||
but does not create or remove it. The one exception is *stale*
|
||||
cleanup — if the service dies without removing its own pidfile
|
||||
(SIGKILL, OOM, segfault), and the file still names the just-reaped
|
||||
PID, Finit removes it before the next retry. This prevents daemons
|
||||
that refuse to start on an existing pidfile (e.g. `dbus-daemon`)
|
||||
from getting stuck in a crash-restart loop.
|
||||
|
||||
**Example:**
|
||||
|
||||
In the case of `ospfd` (below), we omit the `-d` flag (daemonize) to
|
||||
|
||||
+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
|
||||
+2
-1
@@ -61,7 +61,8 @@ For more complex setups (LUKS, LVM, etc.):
|
||||
|
||||
```
|
||||
# Unlock LUKS volume
|
||||
run [S] name:cryptsetup /sbin/cryptsetup open /dev/sda2 cryptroot -- Unlocking encrypted root
|
||||
# The tty:@console stanza is required so cryptsetup can prompt for a passphrase
|
||||
run [S] name:cryptsetup tty:@console /sbin/cryptsetup open /dev/sda2 cryptroot -- Unlocking encrypted root
|
||||
|
||||
# Activate LVM
|
||||
run [S] name:lvm /sbin/lvm vgchange -ay -- Activating LVM volumes
|
||||
|
||||
@@ -744,6 +744,37 @@ is
|
||||
and the default
|
||||
.Cm tag
|
||||
identity is the basename of the service or run/task command.
|
||||
.It Cm tty:\&<dev>
|
||||
Give the
|
||||
.Cm run , task ,
|
||||
or
|
||||
.Cm service
|
||||
a controlling terminal on
|
||||
.Cm <dev> .
|
||||
The device is opened, set as the controlling terminal for the session
|
||||
(after
|
||||
.Fn setsid ) ,
|
||||
and connected to stdin, stdout, and stderr.
|
||||
A default
|
||||
.Ev TERM
|
||||
variable is set based on the device type:
|
||||
.Cm vt102
|
||||
for serial lines and
|
||||
.Cm linux
|
||||
for virtual terminals.
|
||||
.Cm <dev>
|
||||
may be a device node like
|
||||
.Pa /dev/ttyS0 ,
|
||||
or the special keyword
|
||||
.Cm @console
|
||||
(see TTYs and Consoles above).
|
||||
When combined with
|
||||
.Cm log: ,
|
||||
stdout and stderr are redirected to the log sink instead, but stdin
|
||||
remains connected to the TTY device.
|
||||
.Bd -unfilled -offset indent
|
||||
service [2345] tty:/dev/ttyS0 /usr/sbin/foo -- Foo on serial console
|
||||
.Ed
|
||||
.El
|
||||
.Sh RESCUE MODE
|
||||
Finit supports a rescue mode which is activated by the
|
||||
|
||||
+71
-40
@@ -1,9 +1,9 @@
|
||||
site_name: ""
|
||||
site_name: User's Guide
|
||||
site_description: Fast Init for Linux Systems
|
||||
site_url: http://finit-project.github.io
|
||||
site_url: https://finit-project.github.io
|
||||
repo_url: https://github.com/finit-project/finit
|
||||
repo_name: Finit Project
|
||||
copyright: Copyright © 2008-2025 Joachim Wiberg
|
||||
copyright: Copyright © 2008-2026 Joachim Wiberg
|
||||
docs_dir: doc/
|
||||
edit_uri: edit/master/doc/
|
||||
extra_css:
|
||||
@@ -13,42 +13,6 @@ exclude_docs: |
|
||||
TODO.md
|
||||
ChangeLog.md
|
||||
|
||||
theme:
|
||||
logo: img/logo.png
|
||||
name: material
|
||||
features:
|
||||
- toc.follow
|
||||
- toc.integrate
|
||||
- navigation.path
|
||||
- navigation.instant
|
||||
- navigation.instant.progress
|
||||
- navigation.tracking
|
||||
- navigation.tabs
|
||||
# - navigation.sections
|
||||
# - navigation.expand
|
||||
# - navigation.top
|
||||
- navigation.footer
|
||||
- search.highlight
|
||||
- search.share
|
||||
# - content.action.edit
|
||||
- content.code.copy
|
||||
- content.code.annotate
|
||||
palette:
|
||||
- media: "(prefers-color-scheme: light)"
|
||||
scheme: default
|
||||
primary: orange
|
||||
accent: orange
|
||||
toggle:
|
||||
icon: material/weather-night
|
||||
name: Switch to dark mode
|
||||
- media: "(prefers-color-scheme: dark)"
|
||||
scheme: slate
|
||||
primary: black
|
||||
accent: black
|
||||
toggle:
|
||||
icon: material/weather-sunny
|
||||
name: Switch to light mode
|
||||
|
||||
nav:
|
||||
- Getting Started:
|
||||
- Introduction: index.md
|
||||
@@ -94,14 +58,62 @@ nav:
|
||||
- Distributions: distro.md
|
||||
- Requirements: requirements.md
|
||||
|
||||
theme:
|
||||
logo: img/logo.png
|
||||
name: material
|
||||
features:
|
||||
- toc.follow
|
||||
# - toc.integrate
|
||||
- navigation.path
|
||||
- navigation.instant
|
||||
- navigation.instant.progress
|
||||
- navigation.tracking
|
||||
- navigation.indexes
|
||||
# - navigation.tabs
|
||||
# - navigation.sections
|
||||
# - navigation.expand
|
||||
# - navigation.top
|
||||
# - navigation.footer
|
||||
- search.highlight
|
||||
- search.share
|
||||
# - content.action.edit
|
||||
- content.code.copy
|
||||
- content.code.annotate
|
||||
- content.footnote.tooltips
|
||||
palette:
|
||||
- media: "(prefers-color-scheme: light)"
|
||||
scheme: default
|
||||
primary: orange
|
||||
accent: orange
|
||||
toggle:
|
||||
icon: material/weather-night
|
||||
name: Switch to dark mode
|
||||
- media: "(prefers-color-scheme: dark)"
|
||||
scheme: slate
|
||||
primary: black
|
||||
accent: black
|
||||
toggle:
|
||||
icon: material/weather-sunny
|
||||
name: Switch to light mode
|
||||
|
||||
markdown_extensions:
|
||||
- admonition
|
||||
- attr_list
|
||||
- footnotes
|
||||
- pymdownx.blocks.caption
|
||||
- pymdownx.critic
|
||||
- pymdownx.caret
|
||||
- pymdownx.keys
|
||||
- pymdownx.mark
|
||||
- pymdownx.tilde
|
||||
- pymdownx.details
|
||||
- pymdownx.superfences
|
||||
- pymdownx.highlight:
|
||||
anchor_linenums: true
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.emoji:
|
||||
emoji_index: !!python/name:material.extensions.emoji.twemoji
|
||||
emoji_generator: !!python/name:material.extensions.emoji.to_svg
|
||||
- pymdownx.snippets
|
||||
- pymdownx.tabbed:
|
||||
alternate_style: true
|
||||
@@ -112,10 +124,29 @@ markdown_extensions:
|
||||
plugins:
|
||||
- search
|
||||
- callouts
|
||||
- glightbox
|
||||
- glightbox:
|
||||
touchNavigation: true
|
||||
loop: false
|
||||
effect: zoom
|
||||
slide_effect: slide
|
||||
width: 100%
|
||||
height: auto
|
||||
zoomable: true
|
||||
draggable: true
|
||||
skip_classes:
|
||||
- custom-skip-class-name
|
||||
auto_themed: true
|
||||
auto_caption: false
|
||||
caption_position: bottom
|
||||
background: black
|
||||
shadow: false
|
||||
manual: false
|
||||
|
||||
extra:
|
||||
generator: false
|
||||
homepage: https://finit-project.github.io/
|
||||
version:
|
||||
provider: mike
|
||||
social:
|
||||
- icon: fontawesome/brands/github
|
||||
link: https://github.com/finit-project/finit
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
/* Plymouth boot splash plugin for Finit
|
||||
*
|
||||
* Copyright (c) 2012-2026 Aaron Andersen <aaron@fosslib.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This plugin integrates the Plymouth boot splash screen with Finit.
|
||||
* It manages the plymouthd lifecycle across the full boot process:
|
||||
*
|
||||
* - HOOK_BANNER: Start plymouthd early, before console output.
|
||||
* In initramfs, starts fresh. In stage 2, reuses
|
||||
* the daemon carried over from initramfs if alive.
|
||||
* - HOOK_ROOTFS_UP .. HOOK_SYSTEM_UP: Display status messages.
|
||||
* - HOOK_SVC_UP: Tear down plymouth once boot is complete.
|
||||
* - HOOK_SWITCH_ROOT: Notify plymouth of root filesystem change so
|
||||
* the daemon survives the initramfs -> rootfs
|
||||
* transition.
|
||||
* - HOOK_SHUTDOWN: Restart plymouthd in shutdown mode for a
|
||||
* splash during poweroff/reboot.
|
||||
*
|
||||
* The plugin is only activated when "splash" is present on the kernel
|
||||
* command line. Plymouth requires devpts for VT takeover; fs_init()
|
||||
* mounts it early enough for HOOK_BANNER.
|
||||
*
|
||||
* NOTE: The initramfs must include /etc/initrd-release. Plymouth
|
||||
* checks for this file and, when present, prefixes its argv[0] with
|
||||
* '@' so that the process is not killed during switch_root. Without
|
||||
* it, plymouthd will not survive the initramfs-to-rootfs transition.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "finit.h"
|
||||
#include "helpers.h"
|
||||
#include "conf.h"
|
||||
#include "pid.h"
|
||||
#include "plugin.h"
|
||||
#include "sig.h"
|
||||
#include "util.h"
|
||||
#include "log.h"
|
||||
|
||||
#ifndef PLYMOUTH_PATH
|
||||
#define PLYMOUTH_PATH "/sbin/plymouth"
|
||||
#endif
|
||||
#ifndef PLYMOUTHD_PATH
|
||||
#define PLYMOUTHD_PATH "/sbin/plymouthd"
|
||||
#endif
|
||||
|
||||
#define PLYMOUTH_PIDFILE "/run/plymouthd.pid"
|
||||
|
||||
static pid_t daemon_pid;
|
||||
static int switching_root;
|
||||
static int in_initramfs;
|
||||
|
||||
static int plymouth_cmd(const char *action)
|
||||
{
|
||||
char cmd[256];
|
||||
|
||||
snprintf(cmd, sizeof(cmd), PLYMOUTH_PATH " %s", action);
|
||||
return run(cmd, NULL);
|
||||
}
|
||||
|
||||
static void plymouth_message(const char *msg)
|
||||
{
|
||||
pid_t pid;
|
||||
|
||||
pid = fork();
|
||||
if (pid == 0) {
|
||||
sig_unblock();
|
||||
execl(PLYMOUTH_PATH, PLYMOUTH_PATH,
|
||||
"display-message", "--text", msg, NULL);
|
||||
_exit(EX_OSERR);
|
||||
}
|
||||
}
|
||||
|
||||
static int plymouth_alive(void)
|
||||
{
|
||||
return daemon_pid > 0 && pid_alive(daemon_pid);
|
||||
}
|
||||
|
||||
/* Start plymouthd in the given mode ("boot" or "shutdown"). */
|
||||
static void plymouth_start(const char *mode)
|
||||
{
|
||||
char cmd[256];
|
||||
int rc;
|
||||
|
||||
if (plymouth_alive())
|
||||
return;
|
||||
|
||||
snprintf(cmd, sizeof(cmd),
|
||||
PLYMOUTHD_PATH " --attach-to-session --mode %s --pid-file %s",
|
||||
mode, PLYMOUTH_PIDFILE);
|
||||
rc = run(cmd, NULL);
|
||||
if (rc) {
|
||||
warnx("plymouthd failed to start (exit %d)", rc);
|
||||
return;
|
||||
}
|
||||
|
||||
daemon_pid = pid_file_read(PLYMOUTH_PIDFILE);
|
||||
if (daemon_pid <= 0) {
|
||||
warnx("plymouthd started but no PID in %s", PLYMOUTH_PIDFILE);
|
||||
return;
|
||||
}
|
||||
|
||||
rc = plymouth_cmd("show-splash");
|
||||
if (rc)
|
||||
warnx("plymouth show-splash failed (exit %d)", rc);
|
||||
}
|
||||
|
||||
static void plymouth_stop(void)
|
||||
{
|
||||
if (!plymouth_alive())
|
||||
return;
|
||||
|
||||
plymouth_cmd("quit");
|
||||
|
||||
/*
|
||||
* Don't poll -- we're in a finit hook, so finit's event loop
|
||||
* is blocked and can't reap children. Trust that plymouthd
|
||||
* exits after receiving the quit command.
|
||||
*/
|
||||
daemon_pid = 0;
|
||||
unlink(PLYMOUTH_PIDFILE);
|
||||
}
|
||||
|
||||
/*
|
||||
* HOOK_BANNER - earliest possible hook, before any console output.
|
||||
*
|
||||
* In initramfs: start plymouthd fresh.
|
||||
* In stage 2: reuse plymouthd from initramfs if still alive,
|
||||
* otherwise start a new instance.
|
||||
*/
|
||||
static void plymouth_boot(void *arg)
|
||||
{
|
||||
in_initramfs = fexist("/etc/initrd-release");
|
||||
|
||||
if (rescue)
|
||||
return;
|
||||
|
||||
enable_progress(0);
|
||||
|
||||
if (!in_initramfs) {
|
||||
if (plymouth_cmd("--ping") == 0) {
|
||||
daemon_pid = pid_file_read(PLYMOUTH_PIDFILE);
|
||||
if (daemon_pid <= 0)
|
||||
daemon_pid = 1; /* alive but unknown pid */
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
plymouth_start("boot");
|
||||
}
|
||||
|
||||
/*
|
||||
* HOOK_SVC_UP - all services launched.
|
||||
*
|
||||
* In initramfs: keep splash alive for switch_root.
|
||||
* In stage 2: boot is done, tear down plymouth.
|
||||
*/
|
||||
static void plymouth_boot_done(void *arg)
|
||||
{
|
||||
if (in_initramfs)
|
||||
return;
|
||||
|
||||
plymouth_stop();
|
||||
enable_progress(1);
|
||||
}
|
||||
|
||||
/* HOOK_SWITCH_ROOT - initramfs transitioning to real root. */
|
||||
static void plymouth_switchroot(void *arg)
|
||||
{
|
||||
switching_root = 1;
|
||||
|
||||
plymouth_message("Switching to root filesystem...");
|
||||
|
||||
if (plymouth_alive())
|
||||
run(PLYMOUTH_PATH " update-root-fs --new-root-dir=/sysroot", NULL);
|
||||
|
||||
enable_progress(1);
|
||||
}
|
||||
|
||||
/* HOOK_SHUTDOWN - entering runlevel 0 or 6. */
|
||||
static void plymouth_shutdown(void *arg)
|
||||
{
|
||||
if (rescue || switching_root)
|
||||
return;
|
||||
|
||||
enable_progress(0);
|
||||
plymouth_start("shutdown");
|
||||
}
|
||||
|
||||
static void on_rootfs_up(void *arg)
|
||||
{
|
||||
plymouth_message("Root filesystem mounted");
|
||||
}
|
||||
|
||||
static void on_mount_post(void *arg)
|
||||
{
|
||||
plymouth_message("Mounting filesystems...");
|
||||
}
|
||||
|
||||
static void on_basefs_up(void *arg)
|
||||
{
|
||||
plymouth_message("All filesystems mounted");
|
||||
}
|
||||
|
||||
static void on_network_up(void *arg)
|
||||
{
|
||||
plymouth_message("Network is up");
|
||||
}
|
||||
|
||||
static void on_system_up(void *arg)
|
||||
{
|
||||
plymouth_message("System ready");
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
.name = "plymouth",
|
||||
.hook[HOOK_BANNER] = { .cb = plymouth_boot },
|
||||
.hook[HOOK_ROOTFS_UP] = { .cb = on_rootfs_up },
|
||||
.hook[HOOK_MOUNT_POST] = { .cb = on_mount_post },
|
||||
.hook[HOOK_BASEFS_UP] = { .cb = on_basefs_up },
|
||||
.hook[HOOK_NETWORK_UP] = { .cb = on_network_up },
|
||||
.hook[HOOK_SYSTEM_UP] = { .cb = on_system_up },
|
||||
.hook[HOOK_SVC_UP] = { .cb = plymouth_boot_done },
|
||||
.hook[HOOK_SWITCH_ROOT] = { .cb = plymouth_switchroot },
|
||||
.hook[HOOK_SHUTDOWN] = { .cb = plymouth_shutdown },
|
||||
};
|
||||
|
||||
/*
|
||||
* Check kernel command line for "splash" argument. Plymouth should
|
||||
* only be activated when the user explicitly requests it.
|
||||
*/
|
||||
static int has_splash_arg(void)
|
||||
{
|
||||
char line[LINE_SIZE], *tok, *saveptr;
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen("/proc/cmdline", "r");
|
||||
if (!fp)
|
||||
return 0;
|
||||
|
||||
if (!fgets(line, sizeof(line), fp)) {
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
for (tok = strtok_r(line, " \t\n", &saveptr); tok;
|
||||
tok = strtok_r(NULL, " \t\n", &saveptr)) {
|
||||
if (!strcmp(tok, "splash"))
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
PLUGIN_INIT(__init)
|
||||
{
|
||||
if (!has_splash_arg())
|
||||
return;
|
||||
|
||||
plugin_register(&plugin);
|
||||
}
|
||||
|
||||
PLUGIN_EXIT(__exit)
|
||||
{
|
||||
plugin_unregister(&plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Local Variables:
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "linux"
|
||||
* End:
|
||||
*/
|
||||
+11
-3
@@ -370,8 +370,8 @@ static void fs_finalize(void)
|
||||
fs_mount("shm", "/dev/shm", "tmpfs", flags | MS_NODEV, "mode=1777");
|
||||
}
|
||||
|
||||
/* Modern systems use /dev/pts */
|
||||
if (!fismnt("/dev/pts")) {
|
||||
/* Remount devpts with proper gid/mode now that /etc/group is available */
|
||||
{
|
||||
char opts[32];
|
||||
int gid;
|
||||
|
||||
@@ -383,7 +383,8 @@ static void fs_finalize(void)
|
||||
snprintf(opts, sizeof(opts), "gid=%d,mode=0620,ptmxmode=0666", gid);
|
||||
|
||||
makedir("/dev/pts", 0755);
|
||||
fs_mount("devpts", "/dev/pts", "devpts", flags, opts);
|
||||
fs_mount("devpts", "/dev/pts", "devpts",
|
||||
flags | MS_REMOUNT, opts);
|
||||
}
|
||||
|
||||
/* Needed on systems like Alpine Linux */
|
||||
@@ -529,6 +530,13 @@ static void fs_init(void)
|
||||
|
||||
fs_mount(fs[i].spec, fs[i].file, fs[i].type, 0, NULL);
|
||||
}
|
||||
|
||||
/* Early devpts mount for plugins that need PTY access (e.g. plymouth) */
|
||||
if (!fismnt("/dev/pts")) {
|
||||
makedir("/dev/pts", 0755);
|
||||
fs_mount("devpts", "/dev/pts", "devpts",
|
||||
MS_NOSUID | MS_NOEXEC, "ptmxmode=0666");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+67
-6
@@ -50,6 +50,7 @@
|
||||
|
||||
static pstyle_t progress_onoff = PROGRESS_DEFAULT;
|
||||
static pstyle_t progress_style = PROGRESS_DEFAULT;
|
||||
static char last_desc[512]; /* saved description for re-print after interruption */
|
||||
|
||||
#ifndef HOSTNAME_PATH
|
||||
#define HOSTNAME_PATH "/etc/hostname"
|
||||
@@ -336,6 +337,8 @@ void printv(const char *fmt, va_list ap)
|
||||
len = print_timestamp(buf, sizeof(buf));
|
||||
vsnprintf(&buf[len], sizeof(buf) - len, fmt, ap);
|
||||
|
||||
strlcpy(last_desc, &buf[len], sizeof(last_desc)); /* save for re-print */
|
||||
|
||||
if (progress_style == PROGRESS_CLASSIC)
|
||||
cprintf("\r%s ", pad(buf, sizeof(buf), ".", sizeof(buf)));
|
||||
else
|
||||
@@ -344,24 +347,70 @@ void printv(const char *fmt, va_list ap)
|
||||
|
||||
void print(int rc, const char *fmt, ...)
|
||||
{
|
||||
char buf[ttcols];
|
||||
size_t len;
|
||||
|
||||
if (progress_style == PROGRESS_SILENT)
|
||||
return;
|
||||
|
||||
buf[0] = 0;
|
||||
len = print_timestamp(buf, sizeof(buf));
|
||||
|
||||
if (fmt) {
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
printv(fmt, ap);
|
||||
vsnprintf(&buf[len], sizeof(buf) - len, fmt, ap);
|
||||
va_end(ap);
|
||||
strlcpy(last_desc, &buf[len], sizeof(last_desc));
|
||||
} else if (rc >= 0 && last_desc[0]) {
|
||||
/*
|
||||
* No new description, but we have one saved from an earlier
|
||||
* print(-1, ...) call. Re-print it so the final status is not
|
||||
* left stranded if command output or a kernel message scrolled
|
||||
* away the original description line.
|
||||
*/
|
||||
strlcpy(&buf[len], last_desc, sizeof(buf) - len);
|
||||
} else {
|
||||
buf[0] = 0;
|
||||
}
|
||||
|
||||
if (rc < 0)
|
||||
if (rc < 0) {
|
||||
/* Pending state: show description with spinner, no final status yet */
|
||||
if (!buf[0])
|
||||
return;
|
||||
if (progress_style == PROGRESS_CLASSIC)
|
||||
cprintf("\r%s ", pad(buf, sizeof(buf), ".", sizeof(buf)));
|
||||
else
|
||||
cprintf("\r\e[K%s%s", status(3), buf);
|
||||
return;
|
||||
}
|
||||
|
||||
if (progress_style == PROGRESS_CLASSIC)
|
||||
cprintf("%s\n", status(rc));
|
||||
else
|
||||
cprintf("\r%s\n", status(rc));
|
||||
/*
|
||||
* Final status. Emit description + status in a single cprintf() so
|
||||
* both reach the console in one write(), preventing kernel messages or
|
||||
* command output from splitting the description from its [ OK ]/[FAIL].
|
||||
*
|
||||
* status() returns a pointer to a static buffer, so we must copy the
|
||||
* result for rc before calling status(3) — the second call overwrites
|
||||
* the same buffer, corrupting the first result.
|
||||
*/
|
||||
last_desc[0] = 0;
|
||||
|
||||
if (buf[0]) {
|
||||
char st[64];
|
||||
|
||||
strlcpy(st, status(rc), sizeof(st));
|
||||
if (progress_style == PROGRESS_CLASSIC)
|
||||
cprintf("\r%s %s\n", pad(buf, sizeof(buf), ".", sizeof(buf)), st);
|
||||
else
|
||||
cprintf("\r\e[K%s%s\r%s\n", status(3), buf, st);
|
||||
} else {
|
||||
if (progress_style == PROGRESS_CLASSIC)
|
||||
cprintf("%s\n", status(rc));
|
||||
else
|
||||
cprintf("\r%s\n", status(rc));
|
||||
}
|
||||
}
|
||||
|
||||
void print_desc(char *action, char *desc)
|
||||
@@ -375,6 +424,18 @@ int print_result(int fail)
|
||||
return fail;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reset console state and drain the output buffer before kernel takeover.
|
||||
* Called just before reboot()/halt() to prevent ANSI escape codes from
|
||||
* leaking into bootloader or early-kernel output.
|
||||
*/
|
||||
void print_exit(void)
|
||||
{
|
||||
tcdrain(STDERR_FILENO);
|
||||
dprint(STDERR_FILENO, "\e[0m\e[?25h", 10); /* reset SGR, show cursor */
|
||||
tcdrain(STDERR_FILENO);
|
||||
}
|
||||
|
||||
void set_hostname(char **hostname)
|
||||
{
|
||||
FILE *fp;
|
||||
|
||||
@@ -68,6 +68,7 @@ void printv (const char *fmt, va_list ap);
|
||||
void print (int action, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
|
||||
void print_desc (char *action, char *desc);
|
||||
int print_result (int fail);
|
||||
void print_exit (void);
|
||||
|
||||
void set_hostname (char **hostname);
|
||||
void networking (int updown);
|
||||
|
||||
+22
-10
@@ -73,7 +73,8 @@ static int delete_cb(const char *fpath, const struct stat *sb, int tflag, struct
|
||||
if (sb->st_dev != delete_rootdev)
|
||||
return 0;
|
||||
|
||||
remove(fpath);
|
||||
if (remove(fpath))
|
||||
dbg("Failed to remove %s: %s", fpath, strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -146,6 +147,7 @@ int switch_root(const char *newroot, const char *newinit)
|
||||
struct stat newroot_st, oldroot_st;
|
||||
char init_path[PATH_MAX];
|
||||
int console_fd;
|
||||
int fd;
|
||||
dev_t rootdev;
|
||||
int signo;
|
||||
|
||||
@@ -166,17 +168,31 @@ int switch_root(const char *newroot, const char *newinit)
|
||||
}
|
||||
|
||||
/* Verify newroot exists and is a directory */
|
||||
if (stat(newroot, &newroot_st) || !S_ISDIR(newroot_st.st_mode)) {
|
||||
fd = open(newroot, O_RDONLY | O_DIRECTORY);
|
||||
if (fd < 0) {
|
||||
logit(LOG_ERR, "switch_root: %s is not a directory", newroot);
|
||||
errno = ENOTDIR;
|
||||
return -1;
|
||||
}
|
||||
if (fstat(fd, &newroot_st)) {
|
||||
close(fd);
|
||||
logit(LOG_ERR, "switch_root: cannot stat %s", newroot);
|
||||
return -1;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
/* Verify newroot is a mount point (different device than parent) */
|
||||
if (stat("/", &oldroot_st)) {
|
||||
fd = open("/", O_RDONLY | O_DIRECTORY);
|
||||
if (fd < 0) {
|
||||
logit(LOG_ERR, "switch_root: cannot open /");
|
||||
return -1;
|
||||
}
|
||||
if (fstat(fd, &oldroot_st)) {
|
||||
close(fd);
|
||||
logit(LOG_ERR, "switch_root: cannot stat /");
|
||||
return -1;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
if (newroot_st.st_dev == oldroot_st.st_dev) {
|
||||
logit(LOG_ERR, "switch_root: %s is not a mount point", newroot);
|
||||
@@ -259,18 +275,14 @@ int switch_root(const char *newroot, const char *newinit)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Reopen console */
|
||||
close(STDIN_FILENO);
|
||||
close(STDOUT_FILENO);
|
||||
close(STDERR_FILENO);
|
||||
|
||||
/* Reopen console in new root. dup2() closes the old fds itself,
|
||||
* so open() returns a fd > STDERR_FILENO that we can always close. */
|
||||
console_fd = open("/dev/console", O_RDWR);
|
||||
if (console_fd >= 0) {
|
||||
dup2(console_fd, STDIN_FILENO);
|
||||
dup2(console_fd, STDOUT_FILENO);
|
||||
dup2(console_fd, STDERR_FILENO);
|
||||
if (console_fd > STDERR_FILENO)
|
||||
close(console_fd);
|
||||
close(console_fd);
|
||||
}
|
||||
|
||||
/* Reset signals to default */
|
||||
|
||||
+6
-2
@@ -121,15 +121,19 @@ void unmount_tmpfs(void)
|
||||
}
|
||||
}
|
||||
|
||||
void unmount_regular(void)
|
||||
int unmount_regular(void)
|
||||
{
|
||||
const struct mntent *mnt;
|
||||
FILE *fp = NULL;
|
||||
int rc = 0;
|
||||
|
||||
while ((mnt = iterator("/proc/mounts", &fp))) {
|
||||
if (!unmount(mnt->mnt_dir))
|
||||
rc = unmount(mnt->mnt_dir);
|
||||
if (!rc)
|
||||
iterator_end(&fp); /* Restart iteration */
|
||||
}
|
||||
|
||||
return !!rc;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -201,7 +201,10 @@ int pid_file_set(svc_t *svc, char *file, int not)
|
||||
* pid:!foo --> !/run/foo.pid
|
||||
* pid:!/run/foo.pid --> !/run/foo.pid
|
||||
*
|
||||
* Note, nothing is created or removed by Finit in this latter form.
|
||||
* Nothing is created or removed by Finit in this latter form, with one
|
||||
* exception: a verifiably stale pidfile (still names the just-reaped
|
||||
* PID, and that PID is no longer alive) is removed so the next instance
|
||||
* can start. See service_clean_pidfile() in src/service.c.
|
||||
*/
|
||||
int pid_file_parse(svc_t *svc, char *arg)
|
||||
{
|
||||
|
||||
+121
-12
@@ -29,10 +29,12 @@
|
||||
#include <sched.h> /* sched_yield() */
|
||||
#include <string.h>
|
||||
#include <sys/reboot.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/wait.h>
|
||||
#include <linux/vt.h>
|
||||
#include <net/if.h>
|
||||
#ifdef _LIBITE_LITE
|
||||
# include <libite/lite.h>
|
||||
@@ -387,6 +389,43 @@ static int lredirect(svc_t *svc)
|
||||
return close(pipefd[1]);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set up a controlling TTY for run/task/service.
|
||||
* Opens the TTY device, dups to 0/1/2, resets termios to
|
||||
* sane defaults, sets TERM env, and updates procname.
|
||||
*/
|
||||
static void svc_prepare_ctty(const char *tty, const char *procname, int log)
|
||||
{
|
||||
int fd, dummy;
|
||||
|
||||
fd = open(tty, O_RDWR);
|
||||
if (fd < 0) {
|
||||
logit(LOG_ERR, "Failed opening %s: %s", tty, strerror(errno));
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
/* Acquire as controlling terminal for the session */
|
||||
ioctl(fd, TIOCSCTTY, 0);
|
||||
|
||||
dup2(fd, STDIN_FILENO);
|
||||
if (!log) {
|
||||
dup2(fd, STDOUT_FILENO);
|
||||
dup2(fd, STDERR_FILENO);
|
||||
}
|
||||
|
||||
/* Set default TERM */
|
||||
if (ioctl(fd, VT_OPENQRY, &dummy) == -1)
|
||||
setenv("TERM", "vt102", 1); /* likely a serial line */
|
||||
else
|
||||
setenv("TERM", "linux", 1);
|
||||
|
||||
/* Reset to sane defaults */
|
||||
stty(fd, B0);
|
||||
close(fd);
|
||||
|
||||
prctl(PR_SET_NAME, procname, 0, 0, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle redirection of process output, if enabled
|
||||
*/
|
||||
@@ -946,6 +985,14 @@ static int service_start(svc_t *svc)
|
||||
|
||||
sig_unblock();
|
||||
|
||||
/*
|
||||
* If the stanza declared tty:<dev>, set up a controlling
|
||||
* terminal on that device now that we're a session leader.
|
||||
* Must run after setsid() to acquire the controlling TTY.
|
||||
*/
|
||||
if (!svc_is_tty(svc) && svc_has_ctty(svc))
|
||||
svc_prepare_ctty(svc->log.ctty, svc->cmd, !!svc->log.enabled);
|
||||
|
||||
if (svc_is_runtask(svc))
|
||||
status = exec_runtask(args[0], &args[1]);
|
||||
else if (svc_is_tty(svc))
|
||||
@@ -1073,6 +1120,35 @@ static void service_notify_stop(svc_t *svc)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Drop a daemon-owned (pid:!) pidfile if it still names the just-reaped
|
||||
* PID and that PID is gone. The liveness check guards against reuse.
|
||||
*/
|
||||
static void service_clean_pidfile(svc_t *svc, pid_t reaped)
|
||||
{
|
||||
pid_t pid;
|
||||
char *fn;
|
||||
|
||||
if (reaped <= 1)
|
||||
return;
|
||||
|
||||
fn = pid_file(svc);
|
||||
if (!fn)
|
||||
return;
|
||||
|
||||
pid = pid_file_read(fn);
|
||||
if (pid != reaped || pid_alive(pid))
|
||||
return;
|
||||
|
||||
if (remove(fn) && errno != ENOENT) {
|
||||
logit(LOG_CRIT, "Failed removing stale service %s pidfile %s",
|
||||
svc_ident(svc, NULL, 0), fn);
|
||||
return;
|
||||
}
|
||||
|
||||
dbg("Removed stale service %s pidfile %s", svc_ident(svc, NULL, 0), fn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Clean up any lingering state from dead/killed services
|
||||
*/
|
||||
@@ -1090,6 +1166,8 @@ static void service_cleanup(svc_t *svc)
|
||||
if (remove(fn) && errno != ENOENT)
|
||||
logit(LOG_CRIT, "Failed removing service %s pidfile %s",
|
||||
svc_ident(svc, NULL, 0), fn);
|
||||
} else if (svc->pidfile[0] == '!') {
|
||||
service_clean_pidfile(svc, svc->pid);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1576,7 +1654,8 @@ static void parse_script(svc_t *svc, char *type, char *script, int *tmo, char *b
|
||||
script, errstr);
|
||||
goto err;
|
||||
}
|
||||
*tmo = (int)(sec * 1000);
|
||||
if (tmo)
|
||||
*tmo = (int)(sec * 1000);
|
||||
} else {
|
||||
path = script;
|
||||
if (tmo)
|
||||
@@ -1795,6 +1874,7 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
|
||||
char ident[MAX_IDENT_LEN];
|
||||
char *ifstmt = NULL;
|
||||
char *notify = NULL;
|
||||
char *ctty = NULL;
|
||||
struct tty tty = { 0 };
|
||||
char *dev = NULL;
|
||||
int respawn = 0;
|
||||
@@ -1911,6 +1991,8 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
|
||||
env = arg;
|
||||
else if (MATCH_CMD(cmd, "caps:", arg))
|
||||
caps = arg;
|
||||
else if (MATCH_CMD(cmd, "tty:", arg))
|
||||
ctty = arg;
|
||||
/* catch both cgroup: and cgroup. handled in parse_cgroup() */
|
||||
else if (MATCH_CMD(cmd, "cgroup", arg))
|
||||
cgroup = arg;
|
||||
@@ -2160,6 +2242,22 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
|
||||
parse_caps(svc, caps);
|
||||
else
|
||||
memset(svc->capabilities, 0, sizeof(svc->capabilities));
|
||||
|
||||
if (!svc_is_tty(svc) && ctty) {
|
||||
char *dev = ctty;
|
||||
|
||||
/* NOTE: @console expands only to first console, not all */
|
||||
if (tty_isatcon(ctty))
|
||||
dev = tty_atcon();
|
||||
dev = tty_canonicalize(dev);
|
||||
if (dev)
|
||||
strlcpy(svc->log.ctty, dev, sizeof(svc->log.ctty));
|
||||
else
|
||||
logit(LOG_WARNING, "%s: tty: %s not found, skipping ctty", svc_ident(svc, NULL, 0), ctty);
|
||||
} else if (!svc_is_tty(svc)) {
|
||||
memset(svc->log.ctty, 0, sizeof(svc->log.ctty));
|
||||
}
|
||||
|
||||
if (file)
|
||||
strlcpy(svc->file, file, sizeof(svc->file));
|
||||
else
|
||||
@@ -2239,11 +2337,14 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
|
||||
if (cgroup)
|
||||
parse_cgroup(svc, cgroup);
|
||||
|
||||
/* New, recently modified or unchanged ... used on reload. */
|
||||
/*
|
||||
* New or modified since the last reload. The mark is cleared when
|
||||
* the change has been applied, on start or reload, so one that is
|
||||
* still set here is a change that has not been applied yet, e.g.
|
||||
* the service is paused waiting for a condition. Leave it.
|
||||
*/
|
||||
if ((file && conf_changed(file)) || conf_changed(svc_getenv(svc)) || svc->args_dirty)
|
||||
svc_mark_dirty(svc);
|
||||
else
|
||||
svc_mark_clean(svc);
|
||||
|
||||
svc_enable(svc);
|
||||
|
||||
@@ -2339,7 +2440,10 @@ void service_monitor(pid_t lost, int status)
|
||||
if (svc_is_forking(svc)) {
|
||||
/* Likely start script exiting */
|
||||
if (svc_is_starting(svc)) {
|
||||
svc->pid = 0; /* Expect no more activity from this one */
|
||||
/* Daemon died before clearing 'starting'; drop any stale pidfile. */
|
||||
service_clean_pidfile(svc, lost);
|
||||
svc->oldpid = lost; /* So service_retry() logs the real PID */
|
||||
svc->pid = 0; /* Expect no more activity from this one */
|
||||
goto cont;
|
||||
}
|
||||
|
||||
@@ -2728,13 +2832,18 @@ static void service_retry(svc_t *svc)
|
||||
timeout = ((*restart_cnt) <= (svc->restart_max / 2)) ? 2000 : 5000;
|
||||
/* If a longer timeout was specified in the conf, use that instead. */
|
||||
svc->restart_tmo = max(svc->restart_tmo, timeout);
|
||||
logit(LOG_CONSOLE|LOG_WARNING, "Service %s[%d] died (%s%d), restarting (retry in %d msec) (attempt: %d/%d)",
|
||||
svc_ident(svc, NULL, 0), svc->oldpid,
|
||||
WIFEXITED(svc->status) ? "with exit status: " : "by signal: ",
|
||||
WIFEXITED(svc->status) ? WEXITSTATUS(svc->status) : WTERMSIG(svc->status),
|
||||
svc->restart_tmo,
|
||||
*restart_cnt,
|
||||
svc->restart_max);
|
||||
if (WIFEXITED(svc->status))
|
||||
logit(LOG_CONSOLE|LOG_WARNING,
|
||||
"Service %s[%d] died (exit status: %d), restarting (retry in %d msec) (attempt: %d/%d)",
|
||||
svc_ident(svc, NULL, 0), svc->oldpid, WEXITSTATUS(svc->status),
|
||||
svc->restart_tmo, *restart_cnt, svc->restart_max);
|
||||
else
|
||||
logit(LOG_CONSOLE|LOG_WARNING,
|
||||
"Service %s[%d] died (killed by %s%s), restarting (retry in %d msec) (attempt: %d/%d)",
|
||||
svc_ident(svc, NULL, 0), svc->oldpid,
|
||||
sig_name(WTERMSIG(svc->status)),
|
||||
WCOREDUMP(svc->status) ? ", core dumped" : "",
|
||||
svc->restart_tmo, *restart_cnt, svc->restart_max);
|
||||
|
||||
svc_unblock(svc);
|
||||
service_step(svc);
|
||||
|
||||
@@ -170,7 +170,7 @@ static struct sigmap {
|
||||
|
||||
void mdadm_wait(void);
|
||||
void unmount_tmpfs(void);
|
||||
void unmount_regular(void);
|
||||
int unmount_regular(void);
|
||||
|
||||
static void fs_swapoff(void)
|
||||
{
|
||||
@@ -302,6 +302,7 @@ void do_shutdown(shutop_t op)
|
||||
struct sched_param sched_param = { .sched_priority = 99 };
|
||||
int in_cont = in_container();
|
||||
int signo = SIGTERM;
|
||||
int rc;
|
||||
|
||||
if (SHUTDOWN_DEBUG) cprintf("do_shutdown: entered, op=%d\n", op);
|
||||
|
||||
@@ -372,12 +373,13 @@ void do_shutdown(shutop_t op)
|
||||
}
|
||||
|
||||
/* Unmount any tmpfs before unmounting swap ... */
|
||||
print(0, "Unmounting filesystems ...");
|
||||
print(-1, "Unmounting filesystems ...");
|
||||
unmount_tmpfs();
|
||||
fs_swapoff();
|
||||
|
||||
/* ... unmount remaining regular file systems. */
|
||||
unmount_regular();
|
||||
rc = unmount_regular();
|
||||
print(rc, NULL);
|
||||
|
||||
/*
|
||||
* We sit on / so we must remount it ro, try all the things!
|
||||
@@ -386,9 +388,9 @@ void do_shutdown(shutop_t op)
|
||||
* - https://bugs.launchpad.net/ubuntu/+source/util-linux/+bug/29187
|
||||
*/
|
||||
sync();
|
||||
run("mount -n -o remount,ro -t dummytype dummydev /", NULL);
|
||||
run("mount -n -o remount,ro dummydev /", NULL);
|
||||
run("mount -n -o remount,ro /", "mount");
|
||||
if (run("mount -n -o remount,ro -t dummytype dummydev /", NULL) &&
|
||||
run("mount -n -o remount,ro dummydev /", NULL))
|
||||
run("mount -n -o remount,ro /", "mount");
|
||||
|
||||
/* Call mdadm to mark any RAID array(s) as clean before halting. */
|
||||
mdadm_wait();
|
||||
@@ -424,14 +426,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);
|
||||
}
|
||||
|
||||
@@ -627,7 +632,7 @@ const char *sig_name(int signo)
|
||||
return signames[i].name;
|
||||
}
|
||||
|
||||
return "SIGUNKOWN";
|
||||
return "SIGUNKNOWN";
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -100,7 +100,7 @@ typedef enum {
|
||||
#define MAX_IDENT_LEN (MAX_ARG_LEN + MAX_ID_LEN + 1)
|
||||
#define MAX_STR_LEN 64
|
||||
#define MAX_COND_LEN (MAX_ARG_LEN * 3)
|
||||
#define MAX_USER_LEN 16
|
||||
#define MAX_USER_LEN MAX_ARG_LEN
|
||||
#define MAX_NUM_SUPGROUPS 4
|
||||
#define MAX_NUM_FDS 64 /* Max number of I/O plugins */
|
||||
#define MAX_NUM_SVC_ARGS 64
|
||||
@@ -176,6 +176,7 @@ typedef struct svc {
|
||||
char file[64];
|
||||
char prio[20];
|
||||
char ident[20];
|
||||
char ctty[32];
|
||||
} log;
|
||||
|
||||
/* Only for TTY type services */
|
||||
@@ -291,6 +292,7 @@ static inline int svc_in_runlevel (svc_t *svc, int runlevel) { return svc && IS
|
||||
static inline int svc_has_pidfile (svc_t *svc) { return svc_is_daemon(svc) && svc->pidfile[0] != 0 && svc->pidfile[0] != '!'; }
|
||||
static inline int svc_has_pre (svc_t *svc) { return svc->pre_script[0]; }
|
||||
static inline int svc_has_post (svc_t *svc) { return svc->post_script[0]; }
|
||||
static inline int svc_has_ctty (svc_t *svc) { return svc && svc->log.ctty[0]; }
|
||||
static inline int svc_has_ready (svc_t *svc) { return svc->ready_script[0];}
|
||||
static inline int svc_has_cleanup (svc_t *svc) { return svc->cleanup_script[0]; }
|
||||
|
||||
|
||||
+50
-42
@@ -24,6 +24,7 @@
|
||||
#include "config.h" /* Generated by configure script */
|
||||
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <ftw.h>
|
||||
#include <getopt.h>
|
||||
#include <glob.h>
|
||||
@@ -586,67 +587,74 @@ static void tmpfiles(char *line)
|
||||
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);
|
||||
if (!mode)
|
||||
mode = 0755;
|
||||
rc = makedir(path, mode);
|
||||
if (!rc && dirperm(path, mode, uid, gid))
|
||||
warn("Failed setting mode/owner on %s", path);
|
||||
}
|
||||
umask(omask);
|
||||
break;
|
||||
}
|
||||
case 'e':
|
||||
case 'e': {
|
||||
int uid, gid;
|
||||
|
||||
if (glob(path, GLOB_NOESCAPE, NULL, &gl))
|
||||
break;
|
||||
|
||||
uid = parse_uid(user);
|
||||
gid = parse_gid(group);
|
||||
if (gid < 0)
|
||||
gid = 0;
|
||||
|
||||
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);
|
||||
/* skip non-directories */
|
||||
if (dirperm(p, mode, uid, gid) && errno != ENOTDIR)
|
||||
warn("Failed setting mode/owner on %s", p);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'f':
|
||||
case 'F':
|
||||
mkparent(path, 0755);
|
||||
case 'F': {
|
||||
int flags = O_WRONLY | O_CREAT | O_NOFOLLOW | O_CLOEXEC;
|
||||
int fd, uid, gid;
|
||||
|
||||
if (type[1] == '+' || type[0] == 'F') {
|
||||
/* f+/F will create or truncate the file */
|
||||
fp = fopen(path, "w+");
|
||||
flags |= O_TRUNC; /* create or truncate */
|
||||
} else {
|
||||
/* f will create the file if it doesn't exist */
|
||||
if (strc)
|
||||
fp = fopen(path, "w");
|
||||
if (!strc)
|
||||
break; /* create only if missing */
|
||||
flags |= O_EXCL;
|
||||
}
|
||||
|
||||
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);
|
||||
mkparent(path, 0755);
|
||||
fd = open(path, flags, 0666);
|
||||
if (fd < 0) {
|
||||
if (errno != EEXIST)
|
||||
warn("Failed creating %s", path);
|
||||
break;
|
||||
}
|
||||
|
||||
fp = fdopen(fd, "w");
|
||||
if (!fp) {
|
||||
close(fd);
|
||||
break;
|
||||
}
|
||||
|
||||
write_arg(fp, arg);
|
||||
|
||||
uid = parse_uid(user);
|
||||
gid = parse_gid(group);
|
||||
if (gid < 0)
|
||||
gid = 0;
|
||||
if (fdperm(fd, mode, uid, gid))
|
||||
warn("Failed setting mode/owner on %s", path);
|
||||
|
||||
rc = fclose(fp);
|
||||
break;
|
||||
}
|
||||
case 'l': /* Finit extension, like 'L' but only if target exists */
|
||||
if (!arg) {
|
||||
paste(buf, sizeof(buf), "/usr/share/factory", path);
|
||||
|
||||
+40
-4
@@ -25,6 +25,7 @@
|
||||
|
||||
#include <ctype.h> /* isprint() */
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <grp.h>
|
||||
#include <pwd.h>
|
||||
#ifdef HAVE_MNTENT_H
|
||||
@@ -280,6 +281,43 @@ int getcgroup(char *buf, size_t len)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set mode and owner on an open fd, only what differs is touched, the
|
||||
* file may be immutable. mode 0 skips the chmod, uid -1 the chown.
|
||||
*/
|
||||
int fdperm(int fd, mode_t mode, uid_t uid, gid_t gid)
|
||||
{
|
||||
struct stat st;
|
||||
int rc;
|
||||
|
||||
rc = fstat(fd, &st);
|
||||
if (!rc && mode && (st.st_mode & 07777) != mode)
|
||||
rc = fchmod(fd, mode);
|
||||
if (!rc && uid != (uid_t)-1 && (st.st_uid != uid || st.st_gid != gid))
|
||||
rc = fchown(fd, uid, gid);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Same for a directory that may already exist. Opened with
|
||||
* O_NOFOLLOW | O_DIRECTORY, so the change lands on the directory
|
||||
* itself and not on whatever a link at @path points to.
|
||||
*/
|
||||
int dirperm(const char *path, mode_t mode, uid_t uid, gid_t gid)
|
||||
{
|
||||
int fd, rc;
|
||||
|
||||
fd = open(path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
|
||||
rc = fdperm(fd, mode, uid, gid);
|
||||
close(fd);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int mksubsys(const char *dir, mode_t mode, char *user, char *group)
|
||||
{
|
||||
mode_t omask;
|
||||
@@ -295,10 +333,8 @@ int mksubsys(const char *dir, mode_t mode, char *user, char *group)
|
||||
gid = 0;
|
||||
|
||||
rc = makedir(dir, mode);
|
||||
if (rc && errno == EEXIST)
|
||||
rc = chmod(dir, mode);
|
||||
if (chown(dir, uid, gid))
|
||||
err(1, "Failed chown(%s, %d, %d)", dir, uid, gid);
|
||||
if (!rc && dirperm(dir, mode, uid, gid))
|
||||
warn("Failed setting mode/owner on %s", dir);
|
||||
}
|
||||
|
||||
umask(omask);
|
||||
|
||||
@@ -68,6 +68,8 @@ int getgroup (const char *group);
|
||||
int getcuser (char *buf, size_t len);
|
||||
int getcgroup (char *buf, size_t len);
|
||||
|
||||
int fdperm (int fd, mode_t mode, uid_t uid, gid_t gid);
|
||||
int dirperm (const char *path, mode_t mode, uid_t uid, gid_t gid);
|
||||
int mksubsys (const char *dir, mode_t mode, char *user, char *group);
|
||||
|
||||
int fnread (char *buf, size_t len, char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
|
||||
|
||||
+6
-1
@@ -30,9 +30,10 @@ EXTRA_DIST += setup-sysroot.sh
|
||||
EXTRA_DIST += add-remove-dynamic-service.sh
|
||||
EXTRA_DIST += add-remove-dynamic-service-sub-config.sh
|
||||
EXTRA_DIST += bootstrap-crash.sh
|
||||
EXTRA_DIST += cond-start-task.sh
|
||||
EXTRA_DIST += script-timeout.sh
|
||||
EXTRA_DIST += crashing.sh
|
||||
EXTRA_DIST += dep-chain-reload.sh
|
||||
EXTRA_DIST += reload-while-paused.sh
|
||||
EXTRA_DIST += depserv.sh
|
||||
EXTRA_DIST += devmon.sh
|
||||
EXTRA_DIST += failing-sysv.sh
|
||||
@@ -41,6 +42,7 @@ EXTRA_DIST += global-envs.sh
|
||||
EXTRA_DIST += initctl-status-subset.sh
|
||||
EXTRA_DIST += notify.sh
|
||||
EXTRA_DIST += pidfile.sh
|
||||
EXTRA_DIST += stale-pidfile.sh
|
||||
EXTRA_DIST += pre-post-serv.sh
|
||||
EXTRA_DIST += pre-fail.sh
|
||||
EXTRA_DIST += process-depends.sh
|
||||
@@ -74,8 +76,10 @@ TESTS += add-remove-dynamic-service.sh
|
||||
TESTS += add-remove-dynamic-service-sub-config.sh
|
||||
TESTS += bootstrap-crash.sh
|
||||
TESTS += cond-start-task.sh
|
||||
TESTS += script-timeout.sh
|
||||
TESTS += crashing.sh
|
||||
TESTS += dep-chain-reload.sh
|
||||
TESTS += reload-while-paused.sh
|
||||
TESTS += depserv.sh
|
||||
TESTS += devmon.sh
|
||||
TESTS += failing-sysv.sh
|
||||
@@ -84,6 +88,7 @@ TESTS += global-envs.sh
|
||||
TESTS += initctl-status-subset.sh
|
||||
TESTS += notify.sh
|
||||
TESTS += pidfile.sh
|
||||
TESTS += stale-pidfile.sh
|
||||
TESTS += pre-post-serv.sh
|
||||
TESTS += pre-fail.sh
|
||||
TESTS += process-depends.sh
|
||||
|
||||
+17
-1
@@ -255,8 +255,12 @@ wdstart()
|
||||
|
||||
wdkill()
|
||||
{
|
||||
[ -n "${wdpid:-}" ] || return 0
|
||||
|
||||
say "Stopping test watchdog, pid $wdpid"
|
||||
kill -KILL $wdpid
|
||||
# Reap the sleep first, killing its subshell orphans it to PID 1.
|
||||
pkill -KILL -P "$wdpid" 2>/dev/null || true
|
||||
kill -KILL "$wdpid" 2>/dev/null || true
|
||||
}
|
||||
|
||||
teardown()
|
||||
@@ -305,6 +309,18 @@ trap teardown EXIT
|
||||
SYSROOT="${SYSROOT:-$(pwd)/${TEST_DIR}/sysroot}"
|
||||
export SYSROOT
|
||||
|
||||
# 'make check' refreshes the sysroot through the setup-chroot rule, but
|
||||
# running a test script by hand does not, and then the test silently
|
||||
# exercises whatever finit was installed last. Compare and refuse.
|
||||
top_builddir="${top_builddir:-$TEST_DIR/..}"
|
||||
sysroot_finit="$SYSROOT/sbin/finit"
|
||||
built_finit="$top_builddir/src/finit"
|
||||
[ -x "$top_builddir/src/.libs/finit" ] && built_finit="$top_builddir/src/.libs/finit"
|
||||
|
||||
if [ -x "$built_finit" ] && [ -e "$sysroot_finit" ] && ! cmp -s "$built_finit" "$sysroot_finit"; then
|
||||
fail "Stale $sysroot_finit, run 'make -C test setup-chroot' or use 'make check'"
|
||||
fi
|
||||
|
||||
TEST_TIMEOUT=300
|
||||
|
||||
# shellcheck source=/dev/null
|
||||
|
||||
Executable
+76
@@ -0,0 +1,76 @@
|
||||
#!/bin/sh
|
||||
# A reload must not be lost on a service paused by a condition
|
||||
#
|
||||
# svc_s writes its PID file one second after a SIGHUP, so a service
|
||||
# with <pid/svc_s> is paused for that second whenever svc_s reloads.
|
||||
# svc_x counts the SIGHUPs it gets. Both .conf files are touched and
|
||||
# two reloads are requested back to back, so the second one arrives
|
||||
# while svc_x is paused with its own reload still pending. svc_x must
|
||||
# get exactly one SIGHUP and keep its PID.
|
||||
|
||||
set -eu
|
||||
|
||||
TEST_DIR=$(dirname "$0")
|
||||
|
||||
test_teardown()
|
||||
{
|
||||
say "Running test teardown."
|
||||
run "rm -f $FINIT_RCSD/svc_s.conf $FINIT_RCSD/svc_x.conf /tmp/hup.sh /tmp/hup.log"
|
||||
}
|
||||
|
||||
pidof()
|
||||
{
|
||||
texec initctl -j status "$1" | jq .pid
|
||||
}
|
||||
|
||||
test_setup()
|
||||
{
|
||||
run "cat > /tmp/hup.sh" <<EOF
|
||||
#!/bin/sh
|
||||
trap 'echo HUP >> /tmp/hup.log' HUP
|
||||
trap 'exit 0' TERM
|
||||
while true; do
|
||||
sleep 1
|
||||
done
|
||||
EOF
|
||||
run "cat > $FINIT_RCSD/svc_s.conf" <<EOF
|
||||
service log:stdout notify:pid pid:!/run/service.pid name:svc_s service.sh -- Slow to reassert
|
||||
EOF
|
||||
run "cat > $FINIT_RCSD/svc_x.conf" <<EOF
|
||||
service log:stdout <pid/svc_s> name:svc_x /bin/sh /tmp/hup.sh -- Paused while svc_s reloads
|
||||
EOF
|
||||
}
|
||||
|
||||
# shellcheck source=/dev/null
|
||||
. "$TEST_DIR/lib/setup.sh"
|
||||
|
||||
sep "Configuration"
|
||||
run "cat $FINIT_RCSD/svc_s.conf"
|
||||
run "cat $FINIT_RCSD/svc_x.conf"
|
||||
|
||||
say "Reload Finit to start both services"
|
||||
run "initctl reload"
|
||||
retry 'assert_status "svc_x" "running"' 10 1
|
||||
|
||||
pid_x=$(pidof svc_x)
|
||||
say "svc_x PID before: $pid_x"
|
||||
|
||||
sep "Touch both, reload twice"
|
||||
run "rm -f /tmp/hup.log"
|
||||
run "initctl touch svc_s.conf"
|
||||
run "initctl touch svc_x.conf"
|
||||
run "initctl reload"
|
||||
run "initctl reload"
|
||||
|
||||
say "Wait for services to settle"
|
||||
retry 'assert_status "svc_x" "running"' 15 1
|
||||
run "initctl status"
|
||||
|
||||
# shellcheck disable=SC2016
|
||||
retry 'assert "svc_x got its SIGHUP" "$(texec sh -c "cat /tmp/hup.log 2>/dev/null | wc -l")" -eq 1' 10 1
|
||||
|
||||
new_pid_x=$(pidof svc_x)
|
||||
# shellcheck disable=SC2086
|
||||
assert "svc_x was not restarted" $new_pid_x -eq $pid_x
|
||||
|
||||
return 0
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/bin/sh
|
||||
# A timeout on a stop: or reload: script must not take PID 1 with it.
|
||||
# Those two hooks passed a NULL timeout pointer to parse_script(),
|
||||
# which wrote through it whenever the script was prefixed with a
|
||||
# valid number. Only a valid number reached the store, so
|
||||
# 'stop:abc,/bin/true' was harmless while 'stop:5,/bin/true' was not.
|
||||
set -eu
|
||||
|
||||
TEST_DIR=$(dirname "$0")
|
||||
|
||||
test_teardown()
|
||||
{
|
||||
say "Running test teardown."
|
||||
run "rm -f $FINIT_CONF"
|
||||
}
|
||||
|
||||
# shellcheck source=/dev/null
|
||||
. "$TEST_DIR/lib/setup.sh"
|
||||
|
||||
# shellcheck disable=SC2154
|
||||
assert_alive()
|
||||
{
|
||||
assert "Finit survived $1" "$(kill -0 "$finit_pid" 2>/dev/null && echo yes)" = "yes"
|
||||
}
|
||||
|
||||
for hook in stop reload post pre; do
|
||||
say "Timeout on a $hook: script"
|
||||
run "echo 'service $hook:5,/bin/true service.sh -- Timeout test' > $FINIT_CONF"
|
||||
run "initctl reload" || true
|
||||
assert_alive "$hook:5,/bin/true"
|
||||
done
|
||||
|
||||
say 'The service still runs afterwards'
|
||||
run "echo 'service stop:5,/bin/true service.sh -- Timeout test' > $FINIT_CONF"
|
||||
run "initctl reload"
|
||||
retry 'assert_num_children 1 service.sh'
|
||||
+16
-3
@@ -34,6 +34,7 @@ volatile sig_atomic_t reloading = 1;
|
||||
volatile sig_atomic_t running = 1;
|
||||
static char *ident = PROGNM;
|
||||
static char fn[80];
|
||||
static int exclusive = 0; /* -x: refuse to start if pidfile exists (dbus-style) */
|
||||
|
||||
static void verify_env(char *arg)
|
||||
{
|
||||
@@ -122,7 +123,7 @@ static void writefn(char *fn, int val)
|
||||
|
||||
static int checkfn(char *fn)
|
||||
{
|
||||
return !access(fn, R_OK);
|
||||
return !access(fn, F_OK);
|
||||
}
|
||||
|
||||
static void mine(char *fn)
|
||||
@@ -133,12 +134,18 @@ static void mine(char *fn)
|
||||
|
||||
static void pidfile(char *pidfn)
|
||||
{
|
||||
static int once = 0;
|
||||
|
||||
if (!pidfn) {
|
||||
if (fn[0] == 0)
|
||||
snprintf(fn, sizeof(fn), "%s%s.pid", _PATH_VARRUN, ident);
|
||||
pidfn = fn;
|
||||
}
|
||||
|
||||
if (exclusive && !once && checkfn(pidfn))
|
||||
errx(EX_SOFTWARE, "PID file %s exists, refusing to start", pidfn);
|
||||
once = 1;
|
||||
|
||||
if (!checkfn(pidfn)) {
|
||||
pid_t pid;
|
||||
|
||||
@@ -148,7 +155,7 @@ static void pidfile(char *pidfn)
|
||||
atexit(cleanup);
|
||||
} else {
|
||||
inf("Touching PID file %s", pidfn);
|
||||
utimensat(0, fn, NULL, 0);
|
||||
utimensat(AT_FDCWD, pidfn, NULL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,6 +189,7 @@ static int usage(int rc)
|
||||
" -p Create PID file despite running in foreground\n"
|
||||
" -P FILE Create PID file using FILE\n"
|
||||
" -r SVC Call initctl to restart service SVC (self)\n"
|
||||
" -x Refuse to start if PID file already exists (dbus-style)\n"
|
||||
"\n"
|
||||
"By default this program daemonizes itself to the background, and,\n"
|
||||
"when it's done setting up its signal handler(s), creates a PID file\n"
|
||||
@@ -212,7 +220,7 @@ int main(int argc, char *argv[])
|
||||
char cmd[80];
|
||||
int c;
|
||||
|
||||
while ((c = getopt(argc, argv, "cCe:E:f:F:hi:nN:pP:r:")) != EOF) {
|
||||
while ((c = getopt(argc, argv, "cCe:E:f:F:hi:nN:pP:r:x")) != EOF) {
|
||||
switch (c) {
|
||||
case 'c':
|
||||
do_crash = 1;
|
||||
@@ -249,12 +257,17 @@ int main(int argc, char *argv[])
|
||||
break;
|
||||
case 'P':
|
||||
pidfn = optarg;
|
||||
if ((size_t)snprintf(fn, sizeof(fn), "%s", optarg) >= sizeof(fn))
|
||||
errx(EX_USAGE, "-P path too long (max %zu)", sizeof(fn) - 1);
|
||||
do_pidfile++;
|
||||
break;
|
||||
case 'r':
|
||||
snprintf(cmd, sizeof(cmd), "initctl restart %s", optarg);
|
||||
do_restart = 1;
|
||||
break;
|
||||
case 'x':
|
||||
exclusive = 1;
|
||||
break;
|
||||
default:
|
||||
return usage(1);
|
||||
}
|
||||
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/bin/sh
|
||||
# Regression: Finit must remove a daemon-owned (pid:!) stale pidfile
|
||||
# left behind by an unclean exit (SIGKILL), so the next instance can
|
||||
# start. Simulates the dbus-daemon pattern: 'serv -x' refuses to
|
||||
# start if its pidfile already exists.
|
||||
|
||||
set -eu
|
||||
|
||||
TEST_DIR=$(dirname "$0")
|
||||
PIDFN="/run/serv.pid"
|
||||
|
||||
test_teardown()
|
||||
{
|
||||
say "Running test teardown."
|
||||
run "rm -f $FINIT_CONF $PIDFN"
|
||||
}
|
||||
|
||||
# shellcheck source=/dev/null
|
||||
. "$TEST_DIR/lib/setup.sh"
|
||||
|
||||
say "Add service stanza '$FINIT_CONF' with pid:!$PIDFN"
|
||||
run "echo 'service pid:!$PIDFN serv -np -P $PIDFN -x' > $FINIT_CONF"
|
||||
run "initctl reload"
|
||||
|
||||
retry "assert_num_children 1 serv"
|
||||
assert_is_pidfile "serv" "$PIDFN"
|
||||
|
||||
PID=$(texec cat "$PIDFN")
|
||||
say "SIGKILL serv ($PID) -- leaves stale pidfile"
|
||||
run "kill -9 $PID"
|
||||
|
||||
# Without the fix, 'serv -x' refuses to start on each retry until
|
||||
# Finit hits restart_max and marks the service crashed. With the fix
|
||||
# Finit removes the stale pidfile and the next instance comes up.
|
||||
retry "assert_pidiff serv $PID"
|
||||
retry "assert_num_children 1 serv"
|
||||
|
||||
sep
|
||||
+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