diff options
Diffstat (limited to 'lib/libc/tests/sys/cpuset_test.c')
-rw-r--r-- | lib/libc/tests/sys/cpuset_test.c | 691 |
1 files changed, 691 insertions, 0 deletions
diff --git a/lib/libc/tests/sys/cpuset_test.c b/lib/libc/tests/sys/cpuset_test.c new file mode 100644 index 000000000000..53d6a8215bbc --- /dev/null +++ b/lib/libc/tests/sys/cpuset_test.c @@ -0,0 +1,691 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2020-2021 Kyle Evans <kevans@FreeBSD.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/cpuset.h> +#include <sys/jail.h> +#include <sys/procdesc.h> +#include <sys/select.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <sys/wait.h> + +#include <errno.h> +#include <stdio.h> +#include <unistd.h> + +#include <atf-c.h> + +#define SP_PARENT 0 +#define SP_CHILD 1 + +struct jail_test_info { + cpuset_t jail_tidmask; + cpusetid_t jail_cpuset; + cpusetid_t jail_child_cpuset; +}; + +struct jail_test_cb_params { + struct jail_test_info info; + cpuset_t mask; + cpusetid_t rootid; + cpusetid_t setid; +}; + +typedef void (*jail_test_cb)(struct jail_test_cb_params *); + +#define FAILURE_JAIL 42 +#define FAILURE_MASK 43 +#define FAILURE_JAILSET 44 +#define FAILURE_PIDSET 45 +#define FAILURE_SEND 46 +#define FAILURE_DEADLK 47 +#define FAILURE_ATTACH 48 +#define FAILURE_BADAFFIN 49 +#define FAILURE_SUCCESS 50 + +static const char * +do_jail_errstr(int error) +{ + + switch (error) { + case FAILURE_JAIL: + return ("jail_set(2) failed"); + case FAILURE_MASK: + return ("Failed to get the thread cpuset mask"); + case FAILURE_JAILSET: + return ("Failed to get the jail setid"); + case FAILURE_PIDSET: + return ("Failed to get the pid setid"); + case FAILURE_SEND: + return ("Failed to send(2) cpuset information"); + case FAILURE_DEADLK: + return ("Deadlock hit trying to attach to jail"); + case FAILURE_ATTACH: + return ("jail_attach(2) failed"); + case FAILURE_BADAFFIN: + return ("Unexpected post-attach affinity"); + case FAILURE_SUCCESS: + return ("jail_attach(2) succeeded, but should have failed."); + default: + return (NULL); + } +} + +static void +skip_ltncpu(int ncpu, cpuset_t *mask) +{ + + CPU_ZERO(mask); + ATF_REQUIRE_EQ(0, cpuset_getaffinity(CPU_LEVEL_CPUSET, CPU_WHICH_PID, + -1, sizeof(*mask), mask)); + if (CPU_COUNT(mask) < ncpu) + atf_tc_skip("Test requires %d or more cores.", ncpu); +} + +ATF_TC(newset); +ATF_TC_HEAD(newset, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test cpuset(2)"); +} +ATF_TC_BODY(newset, tc) +{ + cpusetid_t nsetid, setid, qsetid; + + /* Obtain our initial set id. */ + ATF_REQUIRE_EQ(0, cpuset_getid(CPU_LEVEL_CPUSET, CPU_WHICH_TID, -1, + &setid)); + + /* Create a new one. */ + ATF_REQUIRE_EQ(0, cpuset(&nsetid)); + ATF_CHECK(nsetid != setid); + + /* Query id again, make sure it's equal to the one we just got. */ + ATF_REQUIRE_EQ(0, cpuset_getid(CPU_LEVEL_CPUSET, CPU_WHICH_TID, -1, + &qsetid)); + ATF_CHECK_EQ(nsetid, qsetid); +} + +ATF_TC(transient); +ATF_TC_HEAD(transient, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test that transient cpusets are freed."); +} +ATF_TC_BODY(transient, tc) +{ + cpusetid_t isetid, scratch, setid; + + ATF_REQUIRE_EQ(0, cpuset_getid(CPU_LEVEL_CPUSET, CPU_WHICH_PID, -1, + &isetid)); + + ATF_REQUIRE_EQ(0, cpuset(&setid)); + ATF_REQUIRE_EQ(0, cpuset_getid(CPU_LEVEL_CPUSET, CPU_WHICH_CPUSET, + setid, &scratch)); + + /* + * Return back to our initial cpuset; the kernel should free the cpuset + * we just created. + */ + ATF_REQUIRE_EQ(0, cpuset_setid(CPU_WHICH_PID, -1, isetid)); + ATF_REQUIRE_EQ(-1, cpuset_getid(CPU_LEVEL_CPUSET, CPU_WHICH_CPUSET, + setid, &scratch)); + ATF_CHECK_EQ(ESRCH, errno); +} + +ATF_TC(deadlk); +ATF_TC_HEAD(deadlk, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test against disjoint cpusets."); + atf_tc_set_md_var(tc, "require.user", "root"); +} +ATF_TC_BODY(deadlk, tc) +{ + cpusetid_t setid; + cpuset_t dismask, mask, omask; + int fcpu, i, found, ncpu, second; + + /* Make sure we have 3 cpus, so we test partial overlap. */ + skip_ltncpu(3, &omask); + + ATF_REQUIRE_EQ(0, cpuset(&setid)); + CPU_ZERO(&mask); + CPU_ZERO(&dismask); + CPU_COPY(&omask, &mask); + CPU_COPY(&omask, &dismask); + fcpu = CPU_FFS(&mask); + ncpu = CPU_COUNT(&mask); + + /* + * Turn off all but the first two for mask, turn off the first for + * dismask and turn them all off for both after the third. + */ + for (i = fcpu - 1, found = 0; i < CPU_MAXSIZE && found != ncpu; i++) { + if (CPU_ISSET(i, &omask)) { + found++; + if (found == 1) { + CPU_CLR(i, &dismask); + } else if (found == 2) { + second = i; + } else if (found >= 3) { + CPU_CLR(i, &mask); + if (found > 3) + CPU_CLR(i, &dismask); + } + } + } + + ATF_REQUIRE_EQ(0, cpuset_setaffinity(CPU_LEVEL_CPUSET, CPU_WHICH_PID, + -1, sizeof(mask), &mask)); + + /* Must be a strict subset! */ + ATF_REQUIRE_EQ(-1, cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, + -1, sizeof(dismask), &dismask)); + ATF_REQUIRE_EQ(EINVAL, errno); + + /* + * We'll set our anonymous set to the 0,1 set that currently matches + * the process. If we then set the process to the 1,2 set that's in + * dismask, we should then personally be restricted down to the single + * overlapping CPOU. + */ + ATF_REQUIRE_EQ(0, cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, + -1, sizeof(mask), &mask)); + ATF_REQUIRE_EQ(0, cpuset_setaffinity(CPU_LEVEL_CPUSET, CPU_WHICH_PID, + -1, sizeof(dismask), &dismask)); + ATF_REQUIRE_EQ(0, cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, + -1, sizeof(mask), &mask)); + ATF_REQUIRE_EQ(1, CPU_COUNT(&mask)); + ATF_REQUIRE(CPU_ISSET(second, &mask)); + + /* + * Finally, clearing the overlap and attempting to set the process + * cpuset to a completely disjoint mask should fail, because this + * process will then not have anything to run on. + */ + CPU_CLR(second, &dismask); + ATF_REQUIRE_EQ(-1, cpuset_setaffinity(CPU_LEVEL_CPUSET, CPU_WHICH_PID, + -1, sizeof(dismask), &dismask)); + ATF_REQUIRE_EQ(EDEADLK, errno); +} + +static int +do_jail(int sock) +{ + struct jail_test_info info; + struct iovec iov[2]; + char *name; + int error; + + if (asprintf(&name, "cpuset_%d", getpid()) == -1) + _exit(42); + + iov[0].iov_base = "name"; + iov[0].iov_len = 5; + + iov[1].iov_base = name; + iov[1].iov_len = strlen(name) + 1; + + if (jail_set(iov, 2, JAIL_CREATE | JAIL_ATTACH) < 0) + return (FAILURE_JAIL); + + /* Record parameters, kick them over, then make a swift exit. */ + CPU_ZERO(&info.jail_tidmask); + error = cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, + -1, sizeof(info.jail_tidmask), &info.jail_tidmask); + if (error != 0) + return (FAILURE_MASK); + + error = cpuset_getid(CPU_LEVEL_ROOT, CPU_WHICH_TID, -1, + &info.jail_cpuset); + if (error != 0) + return (FAILURE_JAILSET); + error = cpuset_getid(CPU_LEVEL_CPUSET, CPU_WHICH_TID, -1, + &info.jail_child_cpuset); + if (error != 0) + return (FAILURE_PIDSET); + if (send(sock, &info, sizeof(info), 0) != sizeof(info)) + return (FAILURE_SEND); + return (0); +} + +static void +do_jail_test(int ncpu, bool newset, jail_test_cb prologue, + jail_test_cb epilogue) +{ + struct jail_test_cb_params cbp; + const char *errstr; + pid_t pid; + int error, sock, sockpair[2], status; + + memset(&cbp.info, '\0', sizeof(cbp.info)); + + skip_ltncpu(ncpu, &cbp.mask); + + ATF_REQUIRE_EQ(0, cpuset_getid(CPU_LEVEL_ROOT, CPU_WHICH_PID, -1, + &cbp.rootid)); + if (newset) + ATF_REQUIRE_EQ(0, cpuset(&cbp.setid)); + else + ATF_REQUIRE_EQ(0, cpuset_getid(CPU_LEVEL_CPUSET, CPU_WHICH_PID, + -1, &cbp.setid)); + /* Special hack for prison0; it uses cpuset 1 as the root. */ + if (cbp.rootid == 0) + cbp.rootid = 1; + + /* Not every test needs early setup. */ + if (prologue != NULL) + (*prologue)(&cbp); + + ATF_REQUIRE_EQ(0, socketpair(PF_UNIX, SOCK_STREAM, 0, sockpair)); + ATF_REQUIRE((pid = fork()) != -1); + + if (pid == 0) { + /* Child */ + close(sockpair[SP_PARENT]); + sock = sockpair[SP_CHILD]; + + _exit(do_jail(sock)); + } else { + /* Parent */ + sock = sockpair[SP_PARENT]; + close(sockpair[SP_CHILD]); + + while ((error = waitpid(pid, &status, 0)) == -1 && + errno == EINTR) { + } + + ATF_REQUIRE_EQ(sizeof(cbp.info), recv(sock, &cbp.info, + sizeof(cbp.info), 0)); + + /* Sanity check the exit info. */ + ATF_REQUIRE_EQ(pid, error); + ATF_REQUIRE(WIFEXITED(status)); + if (WEXITSTATUS(status) != 0) { + errstr = do_jail_errstr(WEXITSTATUS(status)); + if (errstr != NULL) + atf_tc_fail("%s", errstr); + else + atf_tc_fail("Unknown error '%d'", + WEXITSTATUS(status)); + } + + epilogue(&cbp); + } +} + +static void +jail_attach_mutate_pro(struct jail_test_cb_params *cbp) +{ + cpuset_t *mask; + int count; + + mask = &cbp->mask; + + /* Knock out the first cpu. */ + count = CPU_COUNT(mask); + CPU_CLR(CPU_FFS(mask) - 1, mask); + ATF_REQUIRE_EQ(count - 1, CPU_COUNT(mask)); + ATF_REQUIRE_EQ(0, cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, + -1, sizeof(*mask), mask)); +} + +static void +jail_attach_newbase_epi(struct jail_test_cb_params *cbp) +{ + struct jail_test_info *info; + cpuset_t *mask; + + info = &cbp->info; + mask = &cbp->mask; + + /* + * The rootid test has been thrown in because a bug was discovered + * where any newly derived cpuset during attach would be parented to + * the wrong cpuset. Otherwise, we should observe that a new cpuset + * has been created for this process. + */ + ATF_REQUIRE(info->jail_cpuset != cbp->rootid); + ATF_REQUIRE(info->jail_cpuset != cbp->setid); + ATF_REQUIRE(info->jail_cpuset != info->jail_child_cpuset); + ATF_REQUIRE_EQ(0, CPU_CMP(mask, &info->jail_tidmask)); +} + +ATF_TC(jail_attach_newbase); +ATF_TC_HEAD(jail_attach_newbase, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test jail attachment effect on affinity with a new base cpuset."); + atf_tc_set_md_var(tc, "require.user", "root"); +} +ATF_TC_BODY(jail_attach_newbase, tc) +{ + + /* Need >= 2 cpus to test restriction. */ + do_jail_test(2, true, &jail_attach_mutate_pro, + &jail_attach_newbase_epi); +} + +ATF_TC(jail_attach_newbase_plain); +ATF_TC_HEAD(jail_attach_newbase_plain, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test jail attachment effect on affinity with a new, unmodified base cpuset."); + atf_tc_set_md_var(tc, "require.user", "root"); +} +ATF_TC_BODY(jail_attach_newbase_plain, tc) +{ + + do_jail_test(2, true, NULL, &jail_attach_newbase_epi); +} + +/* + * Generic epilogue for tests that are expecting to use the jail's root cpuset + * with their own mask, whether that's been modified or not. + */ +static void +jail_attach_jset_epi(struct jail_test_cb_params *cbp) +{ + struct jail_test_info *info; + cpuset_t *mask; + + info = &cbp->info; + mask = &cbp->mask; + + ATF_REQUIRE(info->jail_cpuset != cbp->setid); + ATF_REQUIRE_EQ(info->jail_cpuset, info->jail_child_cpuset); + ATF_REQUIRE_EQ(0, CPU_CMP(mask, &info->jail_tidmask)); +} + +ATF_TC(jail_attach_prevbase); +ATF_TC_HEAD(jail_attach_prevbase, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test jail attachment effect on affinity without a new base."); + atf_tc_set_md_var(tc, "require.user", "root"); +} +ATF_TC_BODY(jail_attach_prevbase, tc) +{ + + do_jail_test(2, false, &jail_attach_mutate_pro, &jail_attach_jset_epi); +} + +static void +jail_attach_plain_pro(struct jail_test_cb_params *cbp) +{ + + if (cbp->setid != cbp->rootid) + atf_tc_skip("Must be running with the root cpuset."); +} + +ATF_TC(jail_attach_plain); +ATF_TC_HEAD(jail_attach_plain, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test jail attachment effect on affinity without specialization."); + atf_tc_set_md_var(tc, "require.user", "root"); +} +ATF_TC_BODY(jail_attach_plain, tc) +{ + + do_jail_test(1, false, &jail_attach_plain_pro, &jail_attach_jset_epi); +} + +static int +jail_attach_disjoint_newjail(int fd) +{ + struct iovec iov[2]; + char *name; + int jid; + + if (asprintf(&name, "cpuset_%d", getpid()) == -1) + _exit(42); + + iov[0].iov_base = "name"; + iov[0].iov_len = sizeof("name"); + + iov[1].iov_base = name; + iov[1].iov_len = strlen(name) + 1; + + if ((jid = jail_set(iov, 2, JAIL_CREATE | JAIL_ATTACH)) < 0) + return (FAILURE_JAIL); + + /* Signal that we're ready. */ + write(fd, &jid, sizeof(jid)); + for (;;) { + /* Spin */ + } +} + +static int +wait_jail(int fd, int pfd) +{ + fd_set lset; + struct timeval tv; + int error, jid, maxfd; + + FD_ZERO(&lset); + FD_SET(fd, &lset); + FD_SET(pfd, &lset); + + maxfd = MAX(fd, pfd); + + tv.tv_sec = 5; + tv.tv_usec = 0; + + /* Wait for jid to be written. */ + do { + error = select(maxfd + 1, &lset, NULL, NULL, &tv); + } while (error == -1 && errno == EINTR); + + if (error == 0) { + atf_tc_fail("Jail creator did not respond in time."); + } + + ATF_REQUIRE_MSG(error > 0, "Unexpected error %d from select()", errno); + + if (FD_ISSET(pfd, &lset)) { + /* Process died */ + atf_tc_fail("Jail creator died unexpectedly."); + } + + ATF_REQUIRE(FD_ISSET(fd, &lset)); + ATF_REQUIRE_EQ(sizeof(jid), recv(fd, &jid, sizeof(jid), 0)); + + return (jid); +} + +static int +try_attach_child(int jid, cpuset_t *expected_mask) +{ + cpuset_t mask; + + if (jail_attach(jid) == -1) { + if (errno == EDEADLK) + return (FAILURE_DEADLK); + return (FAILURE_ATTACH); + } + + if (expected_mask == NULL) + return (FAILURE_SUCCESS); + + /* If we had an expected mask, check it against the new process mask. */ + CPU_ZERO(&mask); + if (cpuset_getaffinity(CPU_LEVEL_CPUSET, CPU_WHICH_PID, + -1, sizeof(mask), &mask) != 0) { + return (FAILURE_MASK); + } + + if (CPU_CMP(expected_mask, &mask) != 0) + return (FAILURE_BADAFFIN); + + return (0); +} + +static void +try_attach(int jid, cpuset_t *expected_mask) +{ + const char *errstr; + pid_t pid; + int error, fail, status; + + ATF_REQUIRE(expected_mask != NULL); + ATF_REQUIRE((pid = fork()) != -1); + if (pid == 0) + _exit(try_attach_child(jid, expected_mask)); + + while ((error = waitpid(pid, &status, 0)) == -1 && errno == EINTR) { + /* Try again. */ + } + + /* Sanity check the exit info. */ + ATF_REQUIRE_EQ(pid, error); + ATF_REQUIRE(WIFEXITED(status)); + if ((fail = WEXITSTATUS(status)) != 0) { + errstr = do_jail_errstr(fail); + if (errstr != NULL) + atf_tc_fail("%s", errstr); + else + atf_tc_fail("Unknown error '%d'", WEXITSTATUS(status)); + } +} + +ATF_TC(jail_attach_disjoint); +ATF_TC_HEAD(jail_attach_disjoint, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test root attachment into completely disjoint jail cpuset."); + atf_tc_set_md_var(tc, "require.user", "root"); +} +ATF_TC_BODY(jail_attach_disjoint, tc) +{ + cpuset_t smask, jmask; + int sockpair[2]; + cpusetid_t setid; + pid_t pid; + int fcpu, jid, pfd, sock, scpu; + + ATF_REQUIRE_EQ(0, cpuset(&setid)); + + skip_ltncpu(2, &jmask); + fcpu = CPU_FFS(&jmask) - 1; + ATF_REQUIRE_EQ(0, socketpair(PF_UNIX, SOCK_STREAM, 0, sockpair)); + + /* We'll wait on the procdesc, too, so we can fail faster if it dies. */ + ATF_REQUIRE((pid = pdfork(&pfd, 0)) != -1); + + if (pid == 0) { + /* First child sets up the jail. */ + sock = sockpair[SP_CHILD]; + close(sockpair[SP_PARENT]); + + _exit(jail_attach_disjoint_newjail(sock)); + } + + close(sockpair[SP_CHILD]); + sock = sockpair[SP_PARENT]; + + ATF_REQUIRE((jid = wait_jail(sock, pfd)) > 0); + + /* + * This process will be clamped down to the first cpu, while the jail + * will simply have the first CPU removed to make it a completely + * disjoint operation. + */ + CPU_ZERO(&smask); + CPU_SET(fcpu, &smask); + CPU_CLR(fcpu, &jmask); + + /* + * We'll test with the first and second cpu set as well. Only the + * second cpu should be used. + */ + scpu = CPU_FFS(&jmask) - 1; + + ATF_REQUIRE_EQ(0, cpuset_setaffinity(CPU_LEVEL_ROOT, CPU_WHICH_JAIL, + jid, sizeof(jmask), &jmask)); + ATF_REQUIRE_EQ(0, cpuset_setaffinity(CPU_LEVEL_CPUSET, CPU_WHICH_CPUSET, + setid, sizeof(smask), &smask)); + + try_attach(jid, &jmask); + + CPU_SET(scpu, &smask); + ATF_REQUIRE_EQ(0, cpuset_setaffinity(CPU_LEVEL_CPUSET, CPU_WHICH_CPUSET, + setid, sizeof(smask), &smask)); + + CPU_CLR(fcpu, &smask); + try_attach(jid, &smask); +} + +ATF_TC(badparent); +ATF_TC_HEAD(badparent, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test parent assignment when assigning a new cpuset."); +} +ATF_TC_BODY(badparent, tc) +{ + cpuset_t mask; + cpusetid_t finalsetid, origsetid, setid; + + /* Need to mask off at least one CPU. */ + skip_ltncpu(2, &mask); + + ATF_REQUIRE_EQ(0, cpuset_getid(CPU_LEVEL_CPUSET, CPU_WHICH_TID, -1, + &origsetid)); + + ATF_REQUIRE_EQ(0, cpuset(&setid)); + + /* + * Mask off the first CPU, then we'll reparent ourselves to our original + * set. + */ + CPU_CLR(CPU_FFS(&mask) - 1, &mask); + ATF_REQUIRE_EQ(0, cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, + -1, sizeof(mask), &mask)); + + ATF_REQUIRE_EQ(0, cpuset_setid(CPU_WHICH_PID, -1, origsetid)); + ATF_REQUIRE_EQ(0, cpuset_getid(CPU_LEVEL_CPUSET, CPU_WHICH_TID, -1, + &finalsetid)); + + ATF_REQUIRE_EQ(finalsetid, origsetid); +} + +ATF_TP_ADD_TCS(tp) +{ + + ATF_TP_ADD_TC(tp, newset); + ATF_TP_ADD_TC(tp, transient); + ATF_TP_ADD_TC(tp, deadlk); + ATF_TP_ADD_TC(tp, jail_attach_newbase); + ATF_TP_ADD_TC(tp, jail_attach_newbase_plain); + ATF_TP_ADD_TC(tp, jail_attach_prevbase); + ATF_TP_ADD_TC(tp, jail_attach_plain); + ATF_TP_ADD_TC(tp, jail_attach_disjoint); + ATF_TP_ADD_TC(tp, badparent); + return (atf_no_error()); +} |