selftests/namespace: first threaded active reference count test

Test that namespace becomes inactive after thread exits. This verifies
active reference counting works with threads, not just processes.

Link: https://patch.msgid.link/20251029-work-namespace-nstree-listns-v4-67-2e6f823ebdc0@kernel.org
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Christian Brauner <brauner@kernel.org>
This commit is contained in:
Christian Brauner
2025-10-29 13:21:20 +01:00
parent c89d100f6a
commit 29f083c499

View File

@@ -12,9 +12,12 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <pthread.h>
#include "../kselftest_harness.h"
#include "../filesystems/utils.h"
#include "wrappers.h"
#ifndef FD_NSFS_ROOT
#define FD_NSFS_ROOT -10003 /* Root of the nsfs filesystem */
@@ -2113,4 +2116,139 @@ TEST(ns_mixed_types_same_owner)
ASSERT_LT(u_fd, 0);
}
/* Thread test helpers and structures */
struct thread_ns_info {
__u64 ns_id;
int pipefd;
int syncfd_read;
int syncfd_write;
int exit_code;
};
static void *thread_create_namespace(void *arg)
{
struct thread_ns_info *info = (struct thread_ns_info *)arg;
int ret;
/* Create new network namespace */
ret = unshare(CLONE_NEWNET);
if (ret < 0) {
info->exit_code = 1;
return NULL;
}
/* Get namespace ID */
int fd = open("/proc/thread-self/ns/net", O_RDONLY);
if (fd < 0) {
info->exit_code = 2;
return NULL;
}
ret = ioctl(fd, NS_GET_ID, &info->ns_id);
close(fd);
if (ret < 0) {
info->exit_code = 3;
return NULL;
}
/* Send namespace ID to main thread */
if (write(info->pipefd, &info->ns_id, sizeof(info->ns_id)) != sizeof(info->ns_id)) {
info->exit_code = 4;
return NULL;
}
/* Wait for signal to exit */
char sync_byte;
if (read(info->syncfd_read, &sync_byte, 1) != 1) {
info->exit_code = 5;
return NULL;
}
info->exit_code = 0;
return NULL;
}
/*
* Test that namespace becomes inactive after thread exits.
* This verifies active reference counting works with threads, not just processes.
*/
TEST(thread_ns_inactive_after_exit)
{
pthread_t thread;
struct thread_ns_info info;
struct file_handle *handle;
int pipefd[2];
int syncpipe[2];
int ret;
char sync_byte;
char buf[sizeof(*handle) + MAX_HANDLE_SZ];
ASSERT_EQ(pipe(pipefd), 0);
ASSERT_EQ(pipe(syncpipe), 0);
info.pipefd = pipefd[1];
info.syncfd_read = syncpipe[0];
info.syncfd_write = -1;
info.exit_code = -1;
/* Create thread that will create a namespace */
ret = pthread_create(&thread, NULL, thread_create_namespace, &info);
ASSERT_EQ(ret, 0);
/* Read namespace ID from thread */
__u64 ns_id;
ret = read(pipefd[0], &ns_id, sizeof(ns_id));
if (ret != sizeof(ns_id)) {
sync_byte = 'X';
write(syncpipe[1], &sync_byte, 1);
pthread_join(thread, NULL);
close(pipefd[0]);
close(pipefd[1]);
close(syncpipe[0]);
close(syncpipe[1]);
SKIP(return, "Failed to read namespace ID from thread");
}
TH_LOG("Thread created namespace with ID %llu", (unsigned long long)ns_id);
/* Construct file handle */
handle = (struct file_handle *)buf;
handle->handle_bytes = sizeof(struct nsfs_file_handle);
handle->handle_type = FILEID_NSFS;
struct nsfs_file_handle *fh = (struct nsfs_file_handle *)handle->f_handle;
fh->ns_id = ns_id;
fh->ns_type = 0;
fh->ns_inum = 0;
/* Namespace should be active while thread is alive */
TH_LOG("Attempting to open namespace while thread is alive (should succeed)");
int nsfd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
ASSERT_GE(nsfd, 0);
close(nsfd);
/* Signal thread to exit */
TH_LOG("Signaling thread to exit");
sync_byte = 'X';
ASSERT_EQ(write(syncpipe[1], &sync_byte, 1), 1);
close(syncpipe[1]);
/* Wait for thread to exit */
ASSERT_EQ(pthread_join(thread, NULL), 0);
close(pipefd[0]);
close(pipefd[1]);
close(syncpipe[0]);
if (info.exit_code != 0)
SKIP(return, "Thread failed to create namespace");
TH_LOG("Thread exited, namespace should be inactive");
/* Namespace should now be inactive */
nsfd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
ASSERT_LT(nsfd, 0);
/* Should fail with ENOENT (inactive) or ESTALE (gone) */
TH_LOG("Namespace inactive as expected: %s (errno=%d)", strerror(errno), errno);
ASSERT_TRUE(errno == ENOENT || errno == ESTALE);
}
TEST_HARNESS_MAIN