Unverified Commit 59a42b0e authored by Christian Brauner's avatar Christian Brauner
Browse files

selftests/pidfd: add pidfs file handle selftests

parent 8ce35281
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -6,3 +6,4 @@ pidfd_wait
pidfd_fdinfo_test
pidfd_getfd_test
pidfd_setns_test
pidfd_file_handle_test
+2 −1
Original line number Diff line number Diff line
@@ -2,7 +2,8 @@
CFLAGS += -g $(KHDR_INCLUDES) -pthread -Wall

TEST_GEN_PROGS := pidfd_test pidfd_fdinfo_test pidfd_open_test \
	pidfd_poll_test pidfd_wait pidfd_getfd_test pidfd_setns_test
	pidfd_poll_test pidfd_wait pidfd_getfd_test pidfd_setns_test \
	pidfd_file_handle_test

include ../lib.mk
+39 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
#include <sys/wait.h>

#include "../kselftest.h"
#include "../clone3/clone3_selftests.h"

#ifndef P_PIDFD
#define P_PIDFD 3
@@ -68,6 +69,11 @@
#define PIDFD_SKIP 3
#define PIDFD_XFAIL 4

static inline int sys_waitid(int which, pid_t pid, siginfo_t *info, int options)
{
	return syscall(__NR_waitid, which, pid, info, options, NULL);
}

static inline int wait_for_pid(pid_t pid)
{
	int status, ret;
@@ -114,4 +120,37 @@ static inline int sys_memfd_create(const char *name, unsigned int flags)
	return syscall(__NR_memfd_create, name, flags);
}

static inline pid_t create_child(int *pidfd, unsigned flags)
{
	struct __clone_args args = {
		.flags		= CLONE_PIDFD | flags,
		.exit_signal	= SIGCHLD,
		.pidfd		= ptr_to_u64(pidfd),
	};

	return sys_clone3(&args, sizeof(struct __clone_args));
}

static inline ssize_t read_nointr(int fd, void *buf, size_t count)
{
	ssize_t ret;

	do {
		ret = read(fd, buf, count);
	} while (ret < 0 && errno == EINTR);

	return ret;
}

static inline ssize_t write_nointr(int fd, const void *buf, size_t count)
{
	ssize_t ret;

	do {
		ret = write(fd, buf, count);
	} while (ret < 0 && errno == EINTR);

	return ret;
}

#endif /* __PIDFD_H */
+503 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0

#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <linux/types.h>
#include <poll.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syscall.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/socket.h>
#include <linux/kcmp.h>
#include <sys/stat.h>

#include "pidfd.h"
#include "../kselftest_harness.h"

FIXTURE(file_handle)
{
	pid_t pid;
	int pidfd;

	pid_t child_pid1;
	int child_pidfd1;

	pid_t child_pid2;
	int child_pidfd2;

	pid_t child_pid3;
	int child_pidfd3;
};

FIXTURE_SETUP(file_handle)
{
	int ret;
	int ipc_sockets[2];
	char c;

	self->pid = getpid();
	self->pidfd = sys_pidfd_open(self->pid, 0);
	ASSERT_GE(self->pidfd, 0);

	ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
	EXPECT_EQ(ret, 0);

	self->child_pid1 = create_child(&self->child_pidfd1, CLONE_NEWUSER);
	EXPECT_GE(self->child_pid1, 0);

	if (self->child_pid1 == 0) {
		close(ipc_sockets[0]);

		if (write_nointr(ipc_sockets[1], "1", 1) < 0)
			_exit(EXIT_FAILURE);

		close(ipc_sockets[1]);

		pause();
		_exit(EXIT_SUCCESS);
	}

	close(ipc_sockets[1]);
	ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
	close(ipc_sockets[0]);

	ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
	EXPECT_EQ(ret, 0);

	self->child_pid2 = create_child(&self->child_pidfd2, CLONE_NEWUSER | CLONE_NEWPID);
	EXPECT_GE(self->child_pid2, 0);

	if (self->child_pid2 == 0) {
		close(ipc_sockets[0]);

		if (write_nointr(ipc_sockets[1], "1", 1) < 0)
			_exit(EXIT_FAILURE);

		close(ipc_sockets[1]);

		pause();
		_exit(EXIT_SUCCESS);
	}

	close(ipc_sockets[1]);
	ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
	close(ipc_sockets[0]);

	ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
	EXPECT_EQ(ret, 0);

	self->child_pid3 = create_child(&self->child_pidfd3, CLONE_NEWUSER | CLONE_NEWPID);
	EXPECT_GE(self->child_pid3, 0);

	if (self->child_pid3 == 0) {
		close(ipc_sockets[0]);

		if (write_nointr(ipc_sockets[1], "1", 1) < 0)
			_exit(EXIT_FAILURE);

		close(ipc_sockets[1]);

		pause();
		_exit(EXIT_SUCCESS);
	}

	close(ipc_sockets[1]);
	ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
	close(ipc_sockets[0]);
}

FIXTURE_TEARDOWN(file_handle)
{
	EXPECT_EQ(close(self->pidfd), 0);

	EXPECT_EQ(sys_pidfd_send_signal(self->child_pidfd1, SIGKILL, NULL, 0), 0);
	if (self->child_pidfd1 >= 0)
		EXPECT_EQ(0, close(self->child_pidfd1));

	EXPECT_EQ(sys_waitid(P_PID, self->child_pid1, NULL, WEXITED), 0);

	EXPECT_EQ(sys_pidfd_send_signal(self->child_pidfd2, SIGKILL, NULL, 0), 0);
	if (self->child_pidfd2 >= 0)
		EXPECT_EQ(0, close(self->child_pidfd2));

	EXPECT_EQ(sys_waitid(P_PID, self->child_pid2, NULL, WEXITED), 0);

	if (self->child_pidfd3 >= 0) {
		EXPECT_EQ(sys_pidfd_send_signal(self->child_pidfd3, SIGKILL, NULL, 0), 0);
		EXPECT_EQ(0, close(self->child_pidfd3));
		EXPECT_EQ(sys_waitid(P_PID, self->child_pid3, NULL, WEXITED), 0);
	}
}

/*
 * Test that we can decode a pidfs file handle in the same pid
 * namespace.
 */
TEST_F(file_handle, file_handle_same_pidns)
{
	int mnt_id;
	struct file_handle *fh;
	int pidfd = -EBADF;
	struct stat st1, st2;

	fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ);
	ASSERT_NE(fh, NULL);
	memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ);
	fh->handle_bytes = MAX_HANDLE_SZ;

	ASSERT_EQ(name_to_handle_at(self->child_pidfd1, "", fh, &mnt_id, AT_EMPTY_PATH), 0);

	ASSERT_EQ(fstat(self->child_pidfd1, &st1), 0);

	pidfd = open_by_handle_at(self->pidfd, fh, 0);
	ASSERT_GE(pidfd, 0);

	ASSERT_EQ(fstat(pidfd, &st2), 0);
	ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);

	ASSERT_EQ(close(pidfd), 0);

	pidfd = open_by_handle_at(self->pidfd, fh, O_CLOEXEC);
	ASSERT_GE(pidfd, 0);

	ASSERT_EQ(fstat(pidfd, &st2), 0);
	ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);

	ASSERT_EQ(close(pidfd), 0);

	pidfd = open_by_handle_at(self->pidfd, fh, O_NONBLOCK);
	ASSERT_GE(pidfd, 0);

	ASSERT_EQ(fstat(pidfd, &st2), 0);
	ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);

	ASSERT_EQ(close(pidfd), 0);

	free(fh);
}

/*
 * Test that we can decode a pidfs file handle from a child pid
 * namespace.
 */
TEST_F(file_handle, file_handle_child_pidns)
{
	int mnt_id;
	struct file_handle *fh;
	int pidfd = -EBADF;
	struct stat st1, st2;

	fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ);
	ASSERT_NE(fh, NULL);
	memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ);
	fh->handle_bytes = MAX_HANDLE_SZ;

	ASSERT_EQ(name_to_handle_at(self->child_pidfd2, "", fh, &mnt_id, AT_EMPTY_PATH), 0);

	ASSERT_EQ(fstat(self->child_pidfd2, &st1), 0);

	pidfd = open_by_handle_at(self->pidfd, fh, 0);
	ASSERT_GE(pidfd, 0);

	ASSERT_EQ(fstat(pidfd, &st2), 0);
	ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);

	ASSERT_EQ(close(pidfd), 0);

	pidfd = open_by_handle_at(self->pidfd, fh, O_CLOEXEC);
	ASSERT_GE(pidfd, 0);

	ASSERT_EQ(fstat(pidfd, &st2), 0);
	ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);

	ASSERT_EQ(close(pidfd), 0);

	pidfd = open_by_handle_at(self->pidfd, fh, O_NONBLOCK);
	ASSERT_GE(pidfd, 0);

	ASSERT_EQ(fstat(pidfd, &st2), 0);
	ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);

	ASSERT_EQ(close(pidfd), 0);

	free(fh);
}

/*
 * Test that we fail to decode a pidfs file handle from an ancestor
 * child pid namespace.
 */
TEST_F(file_handle, file_handle_foreign_pidns)
{
	int mnt_id;
	struct file_handle *fh;
	pid_t pid;

	fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ);
	ASSERT_NE(fh, NULL);
	memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ);
	fh->handle_bytes = MAX_HANDLE_SZ;

	ASSERT_EQ(name_to_handle_at(self->pidfd, "", fh, &mnt_id, AT_EMPTY_PATH), 0);

	ASSERT_EQ(setns(self->child_pidfd2, CLONE_NEWUSER | CLONE_NEWPID), 0);

	pid = fork();
	ASSERT_GE(pid, 0);

	if (pid == 0) {
		int pidfd = open_by_handle_at(self->pidfd, fh, 0);
		if (pidfd >= 0) {
			TH_LOG("Managed to open pidfd outside of the caller's pid namespace hierarchy");
			_exit(1);
		}
		_exit(0);
	}

	ASSERT_EQ(wait_for_pid(pid), 0);

	free(fh);
}

/*
 * Test that we can decode a pidfs file handle of a process that has
 * exited but not been reaped.
 */
TEST_F(file_handle, pid_has_exited)
{
	int mnt_id, pidfd, child_pidfd3;
	struct file_handle *fh;
	struct stat st1, st2;

	fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ);
	ASSERT_NE(fh, NULL);
	memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ);
	fh->handle_bytes = MAX_HANDLE_SZ;

	ASSERT_EQ(name_to_handle_at(self->child_pidfd3, "", fh, &mnt_id, AT_EMPTY_PATH), 0);

	ASSERT_EQ(fstat(self->child_pidfd3, &st1), 0);

	pidfd = open_by_handle_at(self->pidfd, fh, 0);
	ASSERT_GE(pidfd, 0);

	ASSERT_EQ(fstat(pidfd, &st2), 0);
	ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);

	ASSERT_EQ(close(pidfd), 0);

	child_pidfd3 = self->child_pidfd3;
	self->child_pidfd3 = -EBADF;
	EXPECT_EQ(sys_pidfd_send_signal(child_pidfd3, SIGKILL, NULL, 0), 0);
	EXPECT_EQ(close(child_pidfd3), 0);
	EXPECT_EQ(sys_waitid(P_PID, self->child_pid3, NULL, WEXITED | WNOWAIT), 0);

	pidfd = open_by_handle_at(self->pidfd, fh, 0);
	ASSERT_GE(pidfd, 0);

	EXPECT_EQ(sys_waitid(P_PID, self->child_pid3, NULL, WEXITED), 0);
}

/*
 * Test that we fail to decode a pidfs file handle of a process that has
 * already been reaped.
 */
TEST_F(file_handle, pid_has_been_reaped)
{
	int mnt_id, pidfd, child_pidfd3;
	struct file_handle *fh;
	struct stat st1, st2;

	fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ);
	ASSERT_NE(fh, NULL);
	memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ);
	fh->handle_bytes = MAX_HANDLE_SZ;

	ASSERT_EQ(name_to_handle_at(self->child_pidfd3, "", fh, &mnt_id, AT_EMPTY_PATH), 0);

	ASSERT_EQ(fstat(self->child_pidfd3, &st1), 0);

	pidfd = open_by_handle_at(self->pidfd, fh, 0);
	ASSERT_GE(pidfd, 0);

	ASSERT_EQ(fstat(pidfd, &st2), 0);
	ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);

	ASSERT_EQ(close(pidfd), 0);

	child_pidfd3 = self->child_pidfd3;
	self->child_pidfd3 = -EBADF;
	EXPECT_EQ(sys_pidfd_send_signal(child_pidfd3, SIGKILL, NULL, 0), 0);
	EXPECT_EQ(close(child_pidfd3), 0);
	EXPECT_EQ(sys_waitid(P_PID, self->child_pid3, NULL, WEXITED), 0);

	pidfd = open_by_handle_at(self->pidfd, fh, 0);
	ASSERT_LT(pidfd, 0);
}

/*
 * Test valid flags to open a pidfd file handle. Note, that
 * PIDFD_NONBLOCK is defined as O_NONBLOCK and O_NONBLOCK is an alias to
 * O_NDELAY. Also note that PIDFD_THREAD is an alias for O_EXCL.
 */
TEST_F(file_handle, open_by_handle_at_valid_flags)
{
	int mnt_id;
	struct file_handle *fh;
	int pidfd = -EBADF;
	struct stat st1, st2;

	fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ);
	ASSERT_NE(fh, NULL);
	memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ);
	fh->handle_bytes = MAX_HANDLE_SZ;

	ASSERT_EQ(name_to_handle_at(self->child_pidfd2, "", fh, &mnt_id, AT_EMPTY_PATH), 0);

	ASSERT_EQ(fstat(self->child_pidfd2, &st1), 0);

	pidfd = open_by_handle_at(self->pidfd, fh,
				  O_RDONLY |
				  O_WRONLY |
				  O_RDWR |
				  O_NONBLOCK |
				  O_NDELAY |
				  O_CLOEXEC |
				  O_EXCL);
	ASSERT_GE(pidfd, 0);

	ASSERT_EQ(fstat(pidfd, &st2), 0);
	ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);

	ASSERT_EQ(close(pidfd), 0);
}

/*
 * Test that invalid flags passed to open a pidfd file handle are
 * rejected.
 */
TEST_F(file_handle, open_by_handle_at_invalid_flags)
{
	int mnt_id;
	struct file_handle *fh;
	int pidfd = -EBADF;
	static const struct invalid_pidfs_file_handle_flags {
		int oflag;
		const char *oflag_name;
	}  invalid_pidfs_file_handle_flags[] = {
		{ FASYNC,	"FASYNC"	},
		{ O_CREAT,	"O_CREAT"	},
		{ O_NOCTTY,	"O_NOCTTY"	},
		{ O_CREAT,	"O_CREAT"	},
		{ O_TRUNC,	"O_TRUNC"	},
		{ O_APPEND,	"O_APPEND"	},
		{ O_SYNC,	"O_SYNC"	},
		{ O_DSYNC,	"O_DSYNC"	},
		{ O_DIRECT,	"O_DIRECT"	},
		{ O_DIRECTORY,	"O_DIRECTORY"	},
		{ O_NOFOLLOW,	"O_NOFOLLOW"	},
		{ O_NOATIME,	"O_NOATIME"	},
		{ O_PATH,	"O_PATH"	},
		{ O_TMPFILE,	"O_TMPFILE"	},
		/*
		 * O_LARGEFILE is added implicitly by
		 * open_by_handle_at() so pidfs simply masks it off.
		 */
	};

	fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ);
	ASSERT_NE(fh, NULL);
	memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ);
	fh->handle_bytes = MAX_HANDLE_SZ;

	ASSERT_EQ(name_to_handle_at(self->child_pidfd2, "", fh, &mnt_id, AT_EMPTY_PATH), 0);

	for (int i = 0; i < ARRAY_SIZE(invalid_pidfs_file_handle_flags); i++) {
		pidfd = open_by_handle_at(self->pidfd, fh, invalid_pidfs_file_handle_flags[i].oflag);
		ASSERT_LT(pidfd, 0) {
			TH_LOG("open_by_handle_at() succeeded with invalid flags: %s", invalid_pidfs_file_handle_flags[i].oflag_name);
		}
	}
}

/* Test that lookup fails. */
TEST_F(file_handle, lookup_must_fail)
{
	int mnt_id;
	struct file_handle *fh;

	fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ);
	ASSERT_NE(fh, NULL);
	memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ);
	fh->handle_bytes = MAX_HANDLE_SZ;

	ASSERT_NE(name_to_handle_at(self->child_pidfd2, "lookup-is-not-possible-with-pidfs", fh, &mnt_id, AT_EMPTY_PATH), 0);
	ASSERT_EQ(errno, ENOTDIR);
	ASSERT_NE(name_to_handle_at(self->child_pidfd2, "lookup-is-not-possible-with-pidfs", fh, &mnt_id, 0), 0);
	ASSERT_EQ(errno, ENOTDIR);
}

#ifndef AT_HANDLE_CONNECTABLE
#define AT_HANDLE_CONNECTABLE 0x002
#endif

/*
 * Test that AT_HANDLE_CONNECTABLE is rejected. Connectable file handles
 * don't make sense for pidfs. Note that currently AT_HANDLE_CONNECTABLE
 * is rejected because it is incompatible with AT_EMPTY_PATH which is
 * required with pidfds as we don't support lookup.
 */
TEST_F(file_handle, invalid_name_to_handle_at_flags)
{
	int mnt_id;
	struct file_handle *fh;

	fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ);
	ASSERT_NE(fh, NULL);
	memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ);
	fh->handle_bytes = MAX_HANDLE_SZ;

	ASSERT_NE(name_to_handle_at(self->child_pidfd2, "", fh, &mnt_id, AT_EMPTY_PATH | AT_HANDLE_CONNECTABLE), 0);
}

#ifndef AT_HANDLE_FID
#define AT_HANDLE_FID 0x200
#endif

/*
 * Test that a request with AT_HANDLE_FID always leads to decodable file
 * handle as pidfs always provides export operations.
 */
TEST_F(file_handle, valid_name_to_handle_at_flags)
{
	int mnt_id, pidfd;
	struct file_handle *fh;
	struct stat st1, st2;

	fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ);
	ASSERT_NE(fh, NULL);
	memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ);
	fh->handle_bytes = MAX_HANDLE_SZ;

	ASSERT_EQ(name_to_handle_at(self->child_pidfd2, "", fh, &mnt_id, AT_EMPTY_PATH | AT_HANDLE_FID), 0);

	ASSERT_EQ(fstat(self->child_pidfd2, &st1), 0);

	pidfd = open_by_handle_at(self->pidfd, fh, 0);
	ASSERT_GE(pidfd, 0);

	ASSERT_EQ(fstat(pidfd, &st2), 0);
	ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);

	ASSERT_EQ(close(pidfd), 0);
}

TEST_HARNESS_MAIN
+4 −43
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@
#include <linux/ioctl.h>

#include "pidfd.h"
#include "../clone3/clone3_selftests.h"
#include "../kselftest_harness.h"

#ifndef PIDFS_IOCTL_MAGIC
@@ -118,22 +117,6 @@ FIXTURE(current_nsset)
	int child_pidfd_derived_nsfds2[PIDFD_NS_MAX];
};

static int sys_waitid(int which, pid_t pid, int options)
{
	return syscall(__NR_waitid, which, pid, NULL, options, NULL);
}

pid_t create_child(int *pidfd, unsigned flags)
{
	struct __clone_args args = {
		.flags		= CLONE_PIDFD | flags,
		.exit_signal	= SIGCHLD,
		.pidfd		= ptr_to_u64(pidfd),
	};

	return sys_clone3(&args, sizeof(struct clone_args));
}

static bool switch_timens(void)
{
	int fd, ret;
@@ -150,28 +133,6 @@ static bool switch_timens(void)
	return ret == 0;
}

static ssize_t read_nointr(int fd, void *buf, size_t count)
{
	ssize_t ret;

	do {
		ret = read(fd, buf, count);
	} while (ret < 0 && errno == EINTR);

	return ret;
}

static ssize_t write_nointr(int fd, const void *buf, size_t count)
{
	ssize_t ret;

	do {
		ret = write(fd, buf, count);
	} while (ret < 0 && errno == EINTR);

	return ret;
}

FIXTURE_SETUP(current_nsset)
{
	int i, proc_fd, ret;
@@ -229,7 +190,7 @@ FIXTURE_SETUP(current_nsset)
		_exit(EXIT_SUCCESS);
	}

	ASSERT_EQ(sys_waitid(P_PID, self->child_pid_exited, WEXITED | WNOWAIT), 0);
	ASSERT_EQ(sys_waitid(P_PID, self->child_pid_exited, NULL, WEXITED | WNOWAIT), 0);

	self->pidfd = sys_pidfd_open(self->pid, 0);
	EXPECT_GE(self->pidfd, 0) {
@@ -432,9 +393,9 @@ FIXTURE_TEARDOWN(current_nsset)
		EXPECT_EQ(0, close(self->child_pidfd1));
	if (self->child_pidfd2 >= 0)
		EXPECT_EQ(0, close(self->child_pidfd2));
	ASSERT_EQ(sys_waitid(P_PID, self->child_pid_exited, WEXITED), 0);
	ASSERT_EQ(sys_waitid(P_PID, self->child_pid1, WEXITED), 0);
	ASSERT_EQ(sys_waitid(P_PID, self->child_pid2, WEXITED), 0);
	ASSERT_EQ(sys_waitid(P_PID, self->child_pid_exited, NULL, WEXITED), 0);
	ASSERT_EQ(sys_waitid(P_PID, self->child_pid1, NULL, WEXITED), 0);
	ASSERT_EQ(sys_waitid(P_PID, self->child_pid2, NULL, WEXITED), 0);
}

static int preserve_ns(const int pid, const char *ns)
Loading