mirror of
https://github.com/troglobit/finit.git
synced 2026-10-12 10:12:32 +00:00
Compare commits
| 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 |
@@ -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
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
AC_INIT([Finit], [4.17-rc1], [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])
|
||||
|
||||
+47
-1
@@ -3,7 +3,53 @@ Change Log
|
||||
|
||||
All relevant changes are documented in this file.
|
||||
|
||||
[4.17][] - 2026-04-xx
|
||||
[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
|
||||
|
||||
@@ -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
|
||||
|
||||
+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
|
||||
|
||||
+9
-4
@@ -379,7 +379,6 @@ void print(int rc, const char *fmt, ...)
|
||||
/* 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
|
||||
@@ -391,15 +390,21 @@ void print(int rc, const char *fmt, ...)
|
||||
* 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]) {
|
||||
delline();
|
||||
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)), status(rc));
|
||||
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, status(rc));
|
||||
cprintf("\r\e[K%s%s\r%s\n", status(3), buf, st);
|
||||
} else {
|
||||
if (progress_style == PROGRESS_CLASSIC)
|
||||
cprintf("%s\n", status(rc));
|
||||
|
||||
+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)
|
||||
{
|
||||
|
||||
+55
-12
@@ -1120,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
|
||||
*/
|
||||
@@ -1137,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);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1623,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)
|
||||
@@ -2305,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);
|
||||
|
||||
@@ -2405,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;
|
||||
}
|
||||
|
||||
@@ -2794,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();
|
||||
@@ -630,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
|
||||
|
||||
+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
|
||||
Reference in New Issue
Block a user