@@ -674,6 +674,11 @@ _stash_test_status() {
esac
}
+# Can we run in a private pid/mount namespace?
+HAVE_PRIVATENS=
+./tools/run_seq_pidns bash -c "exit 77"
+test $? -eq 77 && HAVE_PRIVATENS=yes
+
# Can we run systemd scopes?
HAVE_SYSTEMD_SCOPES=
systemctl reset-failed "fstests-check" &>/dev/null
@@ -691,22 +696,29 @@ _adjust_oom_score -500
# the system runs out of memory it'll be the test that gets killed and not the
# test framework. The test is run in a separate process without any of our
# functions, so we open-code adjusting the OOM score.
-#
-# If systemd is available, run the entire test script in a scope so that we can
-# kill all subprocesses of the test if it fails to clean up after itself. This
-# is essential for ensuring that the post-test unmount succeeds. Note that
-# systemd doesn't automatically remove transient scopes that fail to terminate
-# when systemd tells them to terminate (e.g. programs stuck in D state when
-# systemd sends SIGKILL), so we use reset-failed to tear down the scope.
-#
-# Use setsid to run the test program with a separate session id so that we
-# can pkill only the processes started by this test.
_run_seq() {
local res
unset CHILDPID
unset FSTESTS_ISOL # set by tools/run_seq_*
- if [ -n "${HAVE_SYSTEMD_SCOPES}" ]; then
+ if [ -n "${HAVE_PRIVATENS}" ]; then
+ # If pid and mount namespaces are available, run the whole test
+ # inside them so that the test cannot access any process or
+ # /tmp contents that it does not itself create. The ./$seq
+ # process is considered the "init" process of the pid
+ # namespace, so all subprocesses will be sent SIGKILL when it
+ # terminates.
+ ./tools/run_seq_pidns "./$seq"
+ res=$?
+ elif [ -n "${HAVE_SYSTEMD_SCOPES}" ]; then
+ # If systemd is available, run the entire test script in a
+ # scope so that we can kill all subprocesses of the test if it
+ # fails to clean up after itself. This is essential for
+ # ensuring that the post-test unmount succeeds. Note that
+ # systemd doesn't automatically remove transient scopes that
+ # fail to terminate when systemd tells them to terminate (e.g.
+ # programs stuck in D state when systemd sends SIGKILL), so we
+ # use reset-failed to tear down the scope.
local unit="$(systemd-escape "fs$seq").scope"
systemctl reset-failed "${unit}" &> /dev/null
systemd-run --quiet --unit "${unit}" --scope \
@@ -33,7 +33,11 @@ _test_sync()
# Kill only the processes started by this test.
_pkill()
{
- pkill --session 0 "$@"
+ if [ "$FSTESTS_ISOL" = "setsid" ]; then
+ pkill --session 0 "$@"
+ else
+ pkill "$@"
+ fi
}
# Common execution handling for fsstress invocation.
@@ -2736,7 +2740,11 @@ _require_user_exists()
# not, passing $SHELL in this manner works both for "su" and "su -c cmd".
_su()
{
- su --session-command $SHELL "$@"
+ if [ "$FSTESTS_ISOL" = "setsid" ]; then
+ su --session-command $SHELL "$@"
+ else
+ su "$@"
+ fi
}
# check if a user exists and is able to execute commands.
@@ -54,6 +54,7 @@ usage(char *pname)
fpe(" If -M or -G is specified, -U is required\n");
fpe("-s Set uid/gid to 0 in the new user namespace\n");
fpe("-v Display verbose messages\n");
+ fpe("-z Return child's return value\n");
fpe("\n");
fpe("Map strings for -M and -G consist of records of the form:\n");
fpe("\n");
@@ -144,6 +145,8 @@ int
main(int argc, char *argv[])
{
int flags, opt;
+ int return_child_status = 0;
+ int child_status = 0;
pid_t child_pid;
struct child_args args;
char *uid_map, *gid_map;
@@ -161,7 +164,7 @@ main(int argc, char *argv[])
setid = 0;
gid_map = NULL;
uid_map = NULL;
- while ((opt = getopt(argc, argv, "+imnpuUM:G:vs")) != -1) {
+ while ((opt = getopt(argc, argv, "+imnpuUM:G:vsz")) != -1) {
switch (opt) {
case 'i': flags |= CLONE_NEWIPC; break;
case 'm': flags |= CLONE_NEWNS; break;
@@ -173,6 +176,7 @@ main(int argc, char *argv[])
case 'G': gid_map = optarg; break;
case 'U': flags |= CLONE_NEWUSER; break;
case 's': setid = 1; break;
+ case 'z': return_child_status = 1; break;
default: usage(argv[0]);
}
}
@@ -229,11 +233,19 @@ main(int argc, char *argv[])
close(args.pipe_fd[1]);
- if (waitpid(child_pid, NULL, 0) == -1) /* Wait for child */
+ if (waitpid(child_pid, &child_status, 0) == -1) /* Wait for child */
errExit("waitpid");
if (verbose)
- printf("%s: terminating\n", argv[0]);
+ printf("%s: terminating %d\n", argv[0], WEXITSTATUS(child_status));
+
+ if (return_child_status) {
+ if (WIFEXITED(child_status))
+ exit(WEXITSTATUS(child_status));
+ if (WIFSIGNALED(child_status))
+ exit(WTERMSIG(child_status) + 128); /* like sh */
+ exit(EXIT_FAILURE);
+ }
exit(EXIT_SUCCESS);
}
@@ -18,7 +18,7 @@ _cleanup()
{
exec {test_fd}<&-
cd /
- rm -f $tmp.*
+ rm -r -f $tmp.*
}
# Import common functions.
@@ -35,13 +35,24 @@ echo inode $tf_inode >> $seqres.full
# Create new fd by exec
exec {test_fd}> $testfile
-# flock locks the fd then exits, we should see the lock info even the owner is dead
+# flock locks the fd then exits, we should see the lock info even the owner is
+# dead. If we're using pid namespace isolation we have to move /proc so that
+# we can access the /proc/locks from the init_pid_ns.
+if [ "$FSTESTS_ISOL" = "privatens" ]; then
+ move_proc="$tmp.procdir"
+ mkdir -p "$move_proc"
+ mount --move /proc "$move_proc"
+fi
flock -x $test_fd
cat /proc/locks >> $seqres.full
# Checking
grep -q ":$tf_inode " /proc/locks || echo "lock info not found"
+if [ -n "$move_proc" ]; then
+ mount --move "$move_proc" /proc
+fi
+
# success, all done
status=0
echo "Silence is golden"
new file mode 100755
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 Oracle. All Rights Reserved.
+#
+# Try starting things in a private pid/mount namespace with a private /tmp
+# and /proc so that child process trees cannot interfere with each other.
+
+if [ -n "${FSTESTS_ISOL}" ]; then
+ for path in /proc /tmp; do
+ mount --make-private "$path"
+ done
+ mount -t proc proc /proc
+ mount -t tmpfs tmpfs /tmp
+
+ # Allow the test to become a target of the oom killer
+ oom_knob="/proc/self/oom_score_adj"
+ test -w "${oom_knob}" && echo 250 > "${oom_knob}"
+
+ exec "$@"
+fi
+
+if [ -z "$1" ] || [ "$1" = "--help" ]; then
+ echo "Usage: $0 command [args...]"
+ exit 1
+fi
+
+FSTESTS_ISOL=privatens exec "$(dirname "$0")/../src/nsexec" -z -m -p "$0" "$@"