Compare commits

...
13 Commits
Author SHA1 Message Date
Anees Rehman 70a44d3860 mount: report unmount failures
Return a failure status from unmount_regular() when filesystems remain
mounted, and use it to report the final shutdown unmount result.

Signed-off-by: Anees Rehman <anees.rehman@atlascopco.com>
2026-10-12 09:39:03 +02:00
Joachim Wiberg f29a3db86c doc: ChangeLog entry for the lost reload fix
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-09-27 18:56:00 +02:00
Joachim Wiberg f6b394e0a2 service: keep a pending reload across a second conf reload
A service whose condition goes into flux during a reload is paused
with its reload still pending.  If another reload was requested in
the meantime, re-parsing its unchanged .conf file cleared the pending
mark, so the service was resumed without ever being reloaded.  Seen
with sshd <pid/syslogd> on Infix, where a configuration change that
touched both landed as two reloads in a row and sshd kept its old
listen addresses.

The mark is only ever cleared once the change has been applied, so a
mark that is still set when the file is parsed again means exactly
that: not applied yet.  Leave it alone.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-09-27 18:40:44 +02:00
Joachim Wiberg ccaa4caf48 Bump version and update ChangeLog for 4.18
Covers the longer user and group names, the tmpfiles mode and owner
work, the shutdown remount order, and the script timeout crash.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-09-27 18:03:24 +02:00
Anees Rehman 77d199f1a4 sig: avoid reading fstab during shutdown
Only fall back to the regular mount invocation when both
fstab-independent root remount attempts fail.
This avoids reading an encrypted fstab during a normal shutdown.

Signed-off-by: Anees Rehman <anees.rehman@atlascopco.com>
(cherry picked from commit 263ea929fd)
2026-09-27 17:59:15 +02:00
Anees Rehman 0448151c0a svc: allow longer user and group names
Service user and group names are stored in 16-byte buffers, limiting
them to 15 characters. This silently truncates longer names before
getpwnam() and getgrnam(), preventing services configured with valid
longer account names from starting.

Increase the buffers to match the existing maximum configuration
argument length.

Signed-off-by: Anees Rehman <anees.rehman@atlascopco.com>
2026-09-27 17:43:11 +02:00
Joachim Wiberg 75739fbeef tmpfiles: apply mode and owner of e and f/F entries through an fd
Same treatment as d/D got: e went through fisdir() and a path based
chmod/chown, f/F did the chmod/chown by path after closing the file.
Both now work on the open fd, so the mode and owner end up on the
directory or file that was just checked or written.  f/F use open(2)
directly, which lets a plain f rely on O_EXCL for create-if-missing
instead of the earlier stat().

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-09-27 17:29:51 +02:00
Sam Brkopac 3a72d1263c tmpfiles: set mode and owner of d/D directories through an fd
makedir() swallows EEXIST, so the chmod branch for directories that
already existed never ran, while chown() ran every time.  On finix
/var/empty is immutable, which gave a warning on every boot even
though the owner was already correct.

mksubsys() is the original of this code and has the same shape, the
dbus plugin uses it for /tmp/dbus.  Both now go through dirperm(),
which opens the directory and uses fstat/fchmod/fchown on the fd,
changing only what differs.  A failed chown in mksubsys() is a warning
now rather than err(1), PID 1 should not exit over a directory it
cannot adjust.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-09-27 17:25:50 +02:00
Joachim Wiberg 7e2b154f9b .github: update actions to Node.js 24
All workflow runs now warn:

    Node.js 20 is deprecated. The following actions target Node.js 20
    but are being forced to run on Node.js 24.

Update the actions/* dependencies to their current major versions,
all of which target Node.js 24: checkout v7, upload-artifact v7,
download-artifact v8, setup-python v7, and cache v6.  The release
action tracks the floating v1 tag and updates on its own.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:09:36 +02:00
Joachim Wiberg 547e59b2ad .github: allow building and releasing from maintenance branches
With 5.0 on master, maintenance releases move to a 4.x branch: run
push builds, docs deploy, and weekly distcheck for any N.x branch,
and mark only the highest stable tag as the latest release.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 14:45:11 +02:00
Joachim Wiberg 7f37d0c4e9 test: refuse to run against a stale sysroot binary
'make check' refreshes the sysroot through the setup-chroot rule, but
running a test script by hand does not, so the test exercises whichever
finit was installed last and reports on code that is no longer there.
Both a passing and a failing run are then meaningless, and nothing says
so.

Compare the built binary against the installed one at startup and fail
with the command that fixes it.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 14:17:04 +02:00
Joachim Wiberg dd77f63ad3 service: do not let a script timeout take PID 1 with it
A stop: or reload: script written with a timeout killed Finit at
config load:

    service stop:5,/bin/true service.sh -- Boom

parse_script() takes the timeout as a pointer and the caller decides
whether it wants one.  However, both stop: and reload: scripts so far
have no timeout, i.e., NULL.  Guard the branch that reads a leading
number.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 14:16:50 +02:00
Joachim Wiberg e0f9df1f27 test: reap the watchdog's sleep in wdkill()
Every test left a stray `sleep 300` behind, reparented to PID 1, where
it lingered for up to five minutes after the test had finished.

wdstart() runs the watchdog in a subshell, so $! is the pid of the
subshell, not of the sleep it forks.  wdkill() killed the subshell and
orphaned the sleep.

Kill the child first, killing the subshell puts the sleep beyond the
reach of pkill -P.  Neither kill is sure to match, and wdkill() runs
from the EXIT trap under set -e, so both must tolerate failure.  Also
return early when wdpid is unset, for failures before wdstart() runs.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 14:15:22 +02:00
18 changed files with 319 additions and 96 deletions
+7 -8
View File
@@ -1,12 +1,13 @@
name: Bob the Builder name: Bob the Builder
# Run on all branches, including all pull requests, except the 'dev' # Run on push to release branches (master and maintenance N.x), and
# branch since that's where we run Coverity Scan (limited tokens/day) # on all pull requests. The 'dev' branch is where we run Coverity
# Scan (limited tokens/day)
on: on:
push: push:
branches: branches:
- '**' - master
- '!dev' - '[0-9]+.x'
pull_request: pull_request:
types: [opened, synchronize, reopened, labeled] types: [opened, synchronize, reopened, labeled]
@@ -22,8 +23,6 @@ jobs:
# Verify we can build on latest Ubuntu with both gcc and clang # Verify we can build on latest Ubuntu with both gcc and clang
name: ${{ matrix.compiler }} name: ${{ matrix.compiler }}
runs-on: ubuntu-latest 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: strategy:
matrix: matrix:
compiler: [gcc, clang] compiler: [gcc, clang]
@@ -43,7 +42,7 @@ jobs:
(cd libuev-2.4.1 && ./configure && make -j9 && sudo make install-strip) (cd libuev-2.4.1 && ./configure && make -j9 && sudo make install-strip)
(cd libite-2.6.2 && ./configure && make -j9 && sudo make install-strip) (cd libite-2.6.2 && ./configure && make -j9 && sudo make install-strip)
sudo ldconfig sudo ldconfig
- uses: actions/checkout@v4 - uses: actions/checkout@v7
- name: Static Finit - name: Static Finit
run: | run: |
./autogen.sh ./autogen.sh
@@ -81,7 +80,7 @@ jobs:
make -j1 check || (cat test/test-suite.log; false) make -j1 check || (cat test/test-suite.log; false)
- name: Upload Test Results - name: Upload Test Results
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: finit-test-${{ matrix.compiler }} name: finit-test-${{ matrix.compiler }}
path: test/*.log path: test/*.log
+3 -3
View File
@@ -19,7 +19,7 @@ jobs:
coverity: coverity:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v7
- name: Fetch latest Coverity Scan MD5 - name: Fetch latest Coverity Scan MD5
id: var id: var
env: env:
@@ -29,7 +29,7 @@ jobs:
--post-data "token=$TOKEN&project=${COVERITY_PROJ}&md5=1" \ --post-data "token=$TOKEN&project=${COVERITY_PROJ}&md5=1" \
-O coverity-latest.tar.gz.md5 -O coverity-latest.tar.gz.md5
echo "md5=$(cat coverity-latest.tar.gz.md5)" | tee -a $GITHUB_OUTPUT echo "md5=$(cat coverity-latest.tar.gz.md5)" | tee -a $GITHUB_OUTPUT
- uses: actions/cache@v4 - uses: actions/cache@v6
id: cache id: cache
with: with:
path: coverity-latest.tar.gz path: coverity-latest.tar.gz
@@ -86,7 +86,7 @@ jobs:
--form description="${PROJECT_NAME} $(git rev-parse HEAD)" \ --form description="${PROJECT_NAME} $(git rev-parse HEAD)" \
https://scan.coverity.com/builds?project=${COVERITY_PROJ} https://scan.coverity.com/builds?project=${COVERITY_PROJ}
- name: Upload build.log - name: Upload build.log
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: coverity-build.log name: coverity-build.log
path: cov-int/build-log.txt path: cov-int/build-log.txt
+6 -6
View File
@@ -4,7 +4,7 @@ on:
push: push:
branches: branches:
- master - master
- 4.x - '[0-9]+.x'
paths: paths:
- 'doc/**' - 'doc/**'
- 'README.md' - 'README.md'
@@ -30,7 +30,7 @@ jobs:
version: ${{ steps.version.outputs.version }} version: ${{ steps.version.outputs.version }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v7
with: with:
fetch-depth: 0 # Needed for git-revision-date-localized plugin fetch-depth: 0 # Needed for git-revision-date-localized plugin
@@ -42,7 +42,7 @@ jobs:
echo "version=$version" >> $GITHUB_OUTPUT echo "version=$version" >> $GITHUB_OUTPUT
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v4 uses: actions/setup-python@v7
with: with:
python-version: '3.x' python-version: '3.x'
@@ -58,7 +58,7 @@ jobs:
run: mkdocs build --clean run: mkdocs build --clean
- name: Upload site artifact - name: Upload site artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: site name: site
path: site/ path: site/
@@ -72,13 +72,13 @@ jobs:
steps: steps:
- name: Download site artifact - name: Download site artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v8
with: with:
name: site name: site
path: site/ path: site/
- name: Checkout pages repo - name: Checkout pages repo
uses: actions/checkout@v4 uses: actions/checkout@v7
with: with:
repository: finit-project/finit-project.github.io repository: finit-project/finit-project.github.io
path: pages path: pages
+9 -4
View File
@@ -14,7 +14,7 @@ jobs:
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v7
- name: Installing dependencies ... - name: Installing dependencies ...
run: | run: |
sudo apt-get -y update sudo apt-get -y update
@@ -31,12 +31,17 @@ jobs:
run: | run: |
ver=${GITHUB_REF#refs/tags/} ver=${GITHUB_REF#refs/tags/}
echo "ver=${ver}" >> $GITHUB_OUTPUT 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 echo "pre=true" >> $GITHUB_OUTPUT
else else
echo "pre=false" >> $GITHUB_OUTPUT echo "pre=false" >> $GITHUB_OUTPUT
fi 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 echo "latest=true" >> $GITHUB_OUTPUT
else else
echo "latest=false" >> $GITHUB_OUTPUT echo "latest=false" >> $GITHUB_OUTPUT
@@ -64,7 +69,7 @@ jobs:
mv ../*.tar.* artifacts/ mv ../*.tar.* artifacts/
- name: Upload Test Results - name: Upload Test Results
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: finit-release-test-logs name: finit-release-test-logs
path: finit-*/_build/sub/test/*.log path: finit-*/_build/sub/test/*.log
+22 -11
View File
@@ -14,22 +14,31 @@ jobs:
schedule: schedule:
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
run: ${{ steps.schedule.outputs.run }} branches: ${{ steps.schedule.outputs.branches }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v7
- name: Check for any commits since last week with:
fetch-depth: 0
- name: Check for branches with no commits since last week
id: schedule id: schedule
run: | run: |
commits="$(git log --format=%H --since='1 week ago')" 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 if [ -z "$commits" ]; then
echo "run=true" >> $GITHUB_OUTPUT branches="$branches\"$branch\","
else
echo "run=false" >> $GITHUB_OUTPUT
fi fi
done
echo "branches=[${branches%,}]" >> $GITHUB_OUTPUT
distcheck: distcheck:
needs: schedule needs: schedule
if: ${{ needs.schedule.outputs.run == 'true' }} if: ${{ needs.schedule.outputs.branches != '[]' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
branch: ${{ fromJSON(needs.schedule.outputs.branches) }}
fail-fast: false
steps: steps:
- name: Install dependencies - name: Install dependencies
run: | run: |
@@ -42,7 +51,9 @@ jobs:
(cd libuev-2.4.1 && ./configure && make -j9 && sudo make install-strip) (cd libuev-2.4.1 && ./configure && make -j9 && sudo make install-strip)
(cd libite-2.6.2 && ./configure && make -j9 && sudo make install-strip) (cd libite-2.6.2 && ./configure && make -j9 && sudo make install-strip)
sudo ldconfig sudo ldconfig
- uses: actions/checkout@v4 - uses: actions/checkout@v7
with:
ref: ${{ matrix.branch }}
- name: Base run - name: Base run
run: | run: |
./autogen.sh ./autogen.sh
@@ -61,7 +72,7 @@ jobs:
make -j1 distcheck || (cat $dir/test/test-suite.log; false) make -j1 distcheck || (cat $dir/test/test-suite.log; false)
- name: Upload Test Results - name: Upload Test Results
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: finit-weekly-test-logs name: finit-weekly-test-logs-${{ matrix.branch }}
path: finit-*/_build/sub/test/*.log path: finit-*/_build/sub/test/*.log
+1 -1
View File
@@ -1,4 +1,4 @@
AC_INIT([Finit], [4.17], [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/]) [finit], [https://http://finit-project.github.io/])
AC_CONFIG_AUX_DIR(aux) AC_CONFIG_AUX_DIR(aux)
AM_INIT_AUTOMAKE([1.11 foreign subdir-objects]) AM_INIT_AUTOMAKE([1.11 foreign subdir-objects])
+21
View File
@@ -12,6 +12,14 @@ All relevant changes are documented in this file.
e.g. `killed by SIGKILL` or `killed by SIGSEGV, core dumped`, in e.g. `killed by SIGKILL` or `killed by SIGSEGV, core dumped`, in
place of the bare numeric `by signal: N`. Gives operators a much place of the bare numeric `by signal: N`. Gives operators a much
stronger breadcrumb when a daemon dies unexpectedly 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 ### Fixes
@@ -27,6 +35,19 @@ All relevant changes are documented in this file.
- Fix misspelled `SIGUNKOWN` returned by `sig_name()` for unknown - Fix misspelled `SIGUNKOWN` returned by `sig_name()` for unknown
signal numbers, now spelled correctly as `SIGUNKNOWN`. Surfaced by signal numbers, now spelled correctly as `SIGUNKNOWN`. Surfaced by
the new restart log above 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 [4.17][] - 2026-04-28
--------------------- ---------------------
+6 -2
View File
@@ -121,15 +121,19 @@ void unmount_tmpfs(void)
} }
} }
void unmount_regular(void) int unmount_regular(void)
{ {
const struct mntent *mnt; const struct mntent *mnt;
FILE *fp = NULL; FILE *fp = NULL;
int rc = 0;
while ((mnt = iterator("/proc/mounts", &fp))) { while ((mnt = iterator("/proc/mounts", &fp))) {
if (!unmount(mnt->mnt_dir)) rc = unmount(mnt->mnt_dir);
if (!rc)
iterator_end(&fp); /* Restart iteration */ iterator_end(&fp); /* Restart iteration */
} }
return !!rc;
} }
/** /**
+7 -3
View File
@@ -1654,6 +1654,7 @@ static void parse_script(svc_t *svc, char *type, char *script, int *tmo, char *b
script, errstr); script, errstr);
goto err; goto err;
} }
if (tmo)
*tmo = (int)(sec * 1000); *tmo = (int)(sec * 1000);
} else { } else {
path = script; path = script;
@@ -2336,11 +2337,14 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
if (cgroup) if (cgroup)
parse_cgroup(svc, 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) if ((file && conf_changed(file)) || conf_changed(svc_getenv(svc)) || svc->args_dirty)
svc_mark_dirty(svc); svc_mark_dirty(svc);
else
svc_mark_clean(svc);
svc_enable(svc); svc_enable(svc);
+7 -5
View File
@@ -170,7 +170,7 @@ static struct sigmap {
void mdadm_wait(void); void mdadm_wait(void);
void unmount_tmpfs(void); void unmount_tmpfs(void);
void unmount_regular(void); int unmount_regular(void);
static void fs_swapoff(void) static void fs_swapoff(void)
{ {
@@ -302,6 +302,7 @@ void do_shutdown(shutop_t op)
struct sched_param sched_param = { .sched_priority = 99 }; struct sched_param sched_param = { .sched_priority = 99 };
int in_cont = in_container(); int in_cont = in_container();
int signo = SIGTERM; int signo = SIGTERM;
int rc;
if (SHUTDOWN_DEBUG) cprintf("do_shutdown: entered, op=%d\n", op); 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 ... */ /* Unmount any tmpfs before unmounting swap ... */
print(0, "Unmounting filesystems ..."); print(-1, "Unmounting filesystems ...");
unmount_tmpfs(); unmount_tmpfs();
fs_swapoff(); fs_swapoff();
/* ... unmount remaining regular file systems. */ /* ... 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! * We sit on / so we must remount it ro, try all the things!
@@ -386,8 +388,8 @@ void do_shutdown(shutop_t op)
* - https://bugs.launchpad.net/ubuntu/+source/util-linux/+bug/29187 * - https://bugs.launchpad.net/ubuntu/+source/util-linux/+bug/29187
*/ */
sync(); sync();
run("mount -n -o remount,ro -t dummytype dummydev /", NULL); 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 dummydev /", NULL))
run("mount -n -o remount,ro /", "mount"); run("mount -n -o remount,ro /", "mount");
/* Call mdadm to mark any RAID array(s) as clean before halting. */ /* Call mdadm to mark any RAID array(s) as clean before halting. */
+1 -1
View File
@@ -100,7 +100,7 @@ typedef enum {
#define MAX_IDENT_LEN (MAX_ARG_LEN + MAX_ID_LEN + 1) #define MAX_IDENT_LEN (MAX_ARG_LEN + MAX_ID_LEN + 1)
#define MAX_STR_LEN 64 #define MAX_STR_LEN 64
#define MAX_COND_LEN (MAX_ARG_LEN * 3) #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_SUPGROUPS 4
#define MAX_NUM_FDS 64 /* Max number of I/O plugins */ #define MAX_NUM_FDS 64 /* Max number of I/O plugins */
#define MAX_NUM_SVC_ARGS 64 #define MAX_NUM_SVC_ARGS 64
+44 -36
View File
@@ -24,6 +24,7 @@
#include "config.h" /* Generated by configure script */ #include "config.h" /* Generated by configure script */
#include <dirent.h> #include <dirent.h>
#include <fcntl.h>
#include <ftw.h> #include <ftw.h>
#include <getopt.h> #include <getopt.h>
#include <glob.h> #include <glob.h>
@@ -586,67 +587,74 @@ static void tmpfiles(char *line)
if (gid < 0) if (gid < 0)
gid = 0; gid = 0;
rc = makedir(path, mode ?: 0755); if (!mode)
if (rc && errno == EEXIST) mode = 0755;
rc = chmod(path, mode ?: 0755); rc = makedir(path, mode);
if (chown(path, uid, gid)) if (!rc && dirperm(path, mode, uid, gid))
warn("Failed chown(%s, %d, %d)", path, uid, gid); warn("Failed setting mode/owner on %s", path);
} }
umask(omask); umask(omask);
break; break;
} }
case 'e': case 'e': {
int uid, gid;
if (glob(path, GLOB_NOESCAPE, NULL, &gl)) if (glob(path, GLOB_NOESCAPE, NULL, &gl))
break; break;
uid = parse_uid(user);
gid = parse_gid(group);
if (gid < 0)
gid = 0;
for (size_t i = 0; i < gl.gl_pathc; i++) { for (size_t i = 0; i < gl.gl_pathc; i++) {
char *p = gl.gl_pathv[i]; char *p = gl.gl_pathv[i];
int uid, gid;
/* e only adjusts existing directories */ /* skip non-directories */
if (!fisdir(p)) if (dirperm(p, mode, uid, gid) && errno != ENOTDIR)
continue; warn("Failed setting mode/owner on %s", p);
uid = parse_uid(user);
gid = parse_gid(group);
if (gid < 0)
gid = 0;
if (mode)
chmod(p, mode);
if (uid >= 0 && chown(p, uid, gid))
warn("Failed chown(%s, %d, %d)", p, uid, gid);
} }
break; break;
}
case 'f': case 'f':
case 'F': case 'F': {
mkparent(path, 0755); int flags = O_WRONLY | O_CREAT | O_NOFOLLOW | O_CLOEXEC;
int fd, uid, gid;
if (type[1] == '+' || type[0] == 'F') { if (type[1] == '+' || type[0] == 'F') {
/* f+/F will create or truncate the file */ flags |= O_TRUNC; /* create or truncate */
fp = fopen(path, "w+");
} else { } else {
/* f will create the file if it doesn't exist */ if (!strc)
if (strc) break; /* create only if missing */
fp = fopen(path, "w"); flags |= O_EXCL;
} }
if (fp) { mkparent(path, 0755);
int uid, gid; 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); write_arg(fp, arg);
rc = fclose(fp);
/* Apply mode and ownership */
if (mode)
chmod(path, mode);
uid = parse_uid(user); uid = parse_uid(user);
gid = parse_gid(group); gid = parse_gid(group);
if (gid < 0) if (gid < 0)
gid = 0; gid = 0;
if (uid >= 0 && chown(path, uid, gid)) if (fdperm(fd, mode, uid, gid))
warn("Failed chown(%s, %d, %d)", path, uid, gid); warn("Failed setting mode/owner on %s", path);
}
rc = fclose(fp);
break; break;
}
case 'l': /* Finit extension, like 'L' but only if target exists */ case 'l': /* Finit extension, like 'L' but only if target exists */
if (!arg) { if (!arg) {
paste(buf, sizeof(buf), "/usr/share/factory", path); paste(buf, sizeof(buf), "/usr/share/factory", path);
+40 -4
View File
@@ -25,6 +25,7 @@
#include <ctype.h> /* isprint() */ #include <ctype.h> /* isprint() */
#include <errno.h> #include <errno.h>
#include <fcntl.h>
#include <grp.h> #include <grp.h>
#include <pwd.h> #include <pwd.h>
#ifdef HAVE_MNTENT_H #ifdef HAVE_MNTENT_H
@@ -280,6 +281,43 @@ int getcgroup(char *buf, size_t len)
return 0; 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) int mksubsys(const char *dir, mode_t mode, char *user, char *group)
{ {
mode_t omask; mode_t omask;
@@ -295,10 +333,8 @@ int mksubsys(const char *dir, mode_t mode, char *user, char *group)
gid = 0; gid = 0;
rc = makedir(dir, mode); rc = makedir(dir, mode);
if (rc && errno == EEXIST) if (!rc && dirperm(dir, mode, uid, gid))
rc = chmod(dir, mode); warn("Failed setting mode/owner on %s", dir);
if (chown(dir, uid, gid))
err(1, "Failed chown(%s, %d, %d)", dir, uid, gid);
} }
umask(omask); umask(omask);
+2
View File
@@ -68,6 +68,8 @@ int getgroup (const char *group);
int getcuser (char *buf, size_t len); int getcuser (char *buf, size_t len);
int getcgroup (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 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))); int fnread (char *buf, size_t len, char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
+4 -1
View File
@@ -30,9 +30,10 @@ EXTRA_DIST += setup-sysroot.sh
EXTRA_DIST += add-remove-dynamic-service.sh EXTRA_DIST += add-remove-dynamic-service.sh
EXTRA_DIST += add-remove-dynamic-service-sub-config.sh EXTRA_DIST += add-remove-dynamic-service-sub-config.sh
EXTRA_DIST += bootstrap-crash.sh EXTRA_DIST += bootstrap-crash.sh
EXTRA_DIST += cond-start-task.sh EXTRA_DIST += script-timeout.sh
EXTRA_DIST += crashing.sh EXTRA_DIST += crashing.sh
EXTRA_DIST += dep-chain-reload.sh EXTRA_DIST += dep-chain-reload.sh
EXTRA_DIST += reload-while-paused.sh
EXTRA_DIST += depserv.sh EXTRA_DIST += depserv.sh
EXTRA_DIST += devmon.sh EXTRA_DIST += devmon.sh
EXTRA_DIST += failing-sysv.sh EXTRA_DIST += failing-sysv.sh
@@ -75,8 +76,10 @@ TESTS += add-remove-dynamic-service.sh
TESTS += add-remove-dynamic-service-sub-config.sh TESTS += add-remove-dynamic-service-sub-config.sh
TESTS += bootstrap-crash.sh TESTS += bootstrap-crash.sh
TESTS += cond-start-task.sh TESTS += cond-start-task.sh
TESTS += script-timeout.sh
TESTS += crashing.sh TESTS += crashing.sh
TESTS += dep-chain-reload.sh TESTS += dep-chain-reload.sh
TESTS += reload-while-paused.sh
TESTS += depserv.sh TESTS += depserv.sh
TESTS += devmon.sh TESTS += devmon.sh
TESTS += failing-sysv.sh TESTS += failing-sysv.sh
+17 -1
View File
@@ -255,8 +255,12 @@ wdstart()
wdkill() wdkill()
{ {
[ -n "${wdpid:-}" ] || return 0
say "Stopping test watchdog, pid $wdpid" 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() teardown()
@@ -305,6 +309,18 @@ trap teardown EXIT
SYSROOT="${SYSROOT:-$(pwd)/${TEST_DIR}/sysroot}" SYSROOT="${SYSROOT:-$(pwd)/${TEST_DIR}/sysroot}"
export 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 TEST_TIMEOUT=300
# shellcheck source=/dev/null # shellcheck source=/dev/null
+76
View File
@@ -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
+36
View File
@@ -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'