// Copyright 2012 Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * 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. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT // OWNER 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. #if defined(HAVE_CONFIG_H) # include "config.h" #endif #include "run.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "defs.h" #include "env.h" #include "error.h" #include "fs.h" /// Evalutes an expression and ensures it does not return an error. /// /// \param expr A expression that must evaluate to kyua_error_t. #define RE(expr) ATF_REQUIRE(!kyua_error_is_set(expr)) static void check_env(const void* KYUA_DEFS_UNUSED_PARAM(cookie)) KYUA_DEFS_NORETURN; /// Subprocess that validates the cleanliness of the environment. /// /// \param unused_cookie NULL. /// /// \post Exits with success if the environment is clean; failure otherwise. static void check_env(const void* KYUA_DEFS_UNUSED_PARAM(cookie)) { bool failed = false; const char* empty[] = { "LANG", "LC_ALL", "LC_COLLATE", "LC_CTYPE", "LC_MESSAGES", "LC_MONETARY", "LC_NUMERIC", "LC_TIME", NULL }; const char** iter; for (iter = empty; *iter != NULL; ++iter) { if (getenv(*iter) != NULL) { failed = true; printf("%s was not unset\n", *iter); } } if (strcmp(getenv("HOME"), ".") != 0) { failed = true; printf("HOME was not set to .\n"); } if (strcmp(getenv("TZ"), "UTC") != 0) { failed = true; printf("TZ was not set to UTC\n"); } if (strcmp(getenv("LEAVE_ME_ALONE"), "kill-some-day") != 0) { failed = true; printf("LEAVE_ME_ALONE was modified while it should not have been\n"); } exit(failed ? EXIT_FAILURE : EXIT_SUCCESS); } static void check_process_group(const void* KYUA_DEFS_UNUSED_PARAM(cookie)) KYUA_DEFS_NORETURN; /// Subprocess that validates that it has become the leader of a process group. /// /// \param unused_cookie NULL. /// /// \post Exits with success if the process lives in its own process group; /// failure otherwise. static void check_process_group(const void* KYUA_DEFS_UNUSED_PARAM(cookie)) { exit(getpgid(getpid()) == getpid() ? EXIT_SUCCESS : EXIT_FAILURE); } static void check_signals(const void* KYUA_DEFS_UNUSED_PARAM(cookie)) KYUA_DEFS_NORETURN; /// Subprocess that validates that signals have been reset to their defaults. /// /// \param unused_cookie NULL. /// /// \post Exits with success if the process has its signals reset to their /// default values; failure otherwise. static void check_signals(const void* KYUA_DEFS_UNUSED_PARAM(cookie)) { int signo; for (signo = 1; signo <= LAST_SIGNO; signo++) { if (signo == SIGKILL || signo == SIGSTOP) { // Don't attempt to check immutable signals, as this results in // an unconditional error in some systems. E.g. Mac OS X 10.8 // reports 'Invalid argument' when querying SIGKILL. continue; } struct sigaction old_sa; if (sigaction(signo, NULL, &old_sa) == -1) { err(EXIT_FAILURE, "Failed to query signal information for %d", signo); } if (old_sa.sa_handler != SIG_DFL) { errx(EXIT_FAILURE, "Signal %d not reset to its default handler", signo); } printf("Signal %d has its default handler set\n", signo); } exit(EXIT_SUCCESS); } static void check_umask(const void* KYUA_DEFS_UNUSED_PARAM(cookie)) KYUA_DEFS_NORETURN; /// Subprocess that validates that the umask has been reset. /// /// \param unused_cookie NULL. /// /// \post Exits with success if the umask matches the expected value; failure /// otherwise. static void check_umask(const void* KYUA_DEFS_UNUSED_PARAM(cookie)) { exit(umask(0) == 0022 ? EXIT_SUCCESS : EXIT_FAILURE); } static void check_work_directory(const void* cookie) KYUA_DEFS_NORETURN; /// Subprocess that validates that the umask has been reset. /// /// \param cookie The name of a file to expect in the current directory. /// /// \post Exits with success if the umask matches the expected value; failure /// otherwise. static void check_work_directory(const void* cookie) { const char* exp_file = (const char*)cookie; exit(atf_utils_file_exists(exp_file) ? EXIT_SUCCESS : EXIT_FAILURE); } static void check_uid_not_root(const void* KYUA_DEFS_UNUSED_PARAM(cookie)) KYUA_DEFS_NORETURN; /// Subprocess that validates that the UID is not root. /// /// \param unused_cookie NULL. /// /// \post Exits with success if the UID is not root; failure otherwise. static void check_uid_not_root(const void* KYUA_DEFS_UNUSED_PARAM(cookie)) { exit(getuid() != 0 ? EXIT_SUCCESS : EXIT_FAILURE); } static void check_gid_not_root(const void* KYUA_DEFS_UNUSED_PARAM(cookie)) KYUA_DEFS_NORETURN; /// Subprocess that validates that the GID is not root. /// /// \param unused_cookie NULL. /// /// \post Exits with success if the GID is not root; failure otherwise. static void check_gid_not_root(const void* KYUA_DEFS_UNUSED_PARAM(cookie)) { exit(getgid() != 0 ? EXIT_SUCCESS : EXIT_FAILURE); } static void check_not_root(const void* KYUA_DEFS_UNUSED_PARAM(cookie)) KYUA_DEFS_NORETURN; /// Subprocess that validates that the UID and GID are not root. /// /// \param unused_cookie NULL. /// /// \post Exits with success if the UID and GID are not root; failure otherwise. static void check_not_root(const void* KYUA_DEFS_UNUSED_PARAM(cookie)) { exit(getuid() != 0 && getgid() != 0 ? EXIT_SUCCESS : EXIT_FAILURE); } /// Uses kyua_fork, kyua_exec and kyua_wait to execute a subprocess. /// /// \param program Path to the program to run. /// \param args Arguments to the program. /// \param [out] exitstatus The exit status of the subprocess, if it exits /// successfully without timing out nor receiving a signal. /// /// \return Returns the error code of kyua_run_wait (which should have the /// error representation of the exec call in the subprocess). static kyua_error_t exec_check(const char* program, const char* const* args, int* exitstatus) { kyua_run_params_t run_params; kyua_run_params_init(&run_params); pid_t pid; kyua_error_t error = kyua_run_fork(&run_params, &pid); if (!kyua_error_is_set(error) && pid == 0) kyua_run_exec(program, args); ATF_REQUIRE(!kyua_error_is_set(error)); int status; bool timed_out; error = kyua_run_wait(pid, &status, &timed_out); if (!kyua_error_is_set(error)) { ATF_REQUIRE(!timed_out); ATF_REQUIRE_MSG(WIFEXITED(status), "Subprocess expected to exit successfully"); *exitstatus = WEXITSTATUS(status); } return error; } /// Uses kyua_fork and kyua_wait to spawn a subprocess. /// /// \param run_params The parameters to configure the subprocess. Can be NULL /// to indicate to use the default set of parameters. /// \param hook Any of the check_* functions provided in this module. /// \param cookie The data to pass to the hook. /// /// \return True if the subprocess exits successfully; false otherwise. static bool fork_check(const kyua_run_params_t* run_params, void (*hook)(const void*), const void* cookie) { kyua_run_params_t default_run_params; if (run_params == NULL) { kyua_run_params_init(&default_run_params); run_params = &default_run_params; } pid_t pid; kyua_error_t error = kyua_run_fork(run_params, &pid); if (!kyua_error_is_set(error) && pid == 0) hook(cookie); ATF_REQUIRE(!kyua_error_is_set(error)); int status; bool timed_out; error = kyua_run_wait(pid, &status, &timed_out); if (kyua_error_is_set(error)) atf_tc_fail("wait failed; unexpected problem during exec?"); ATF_REQUIRE(!timed_out); return WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS; } ATF_TC_WITHOUT_HEAD(run_params_init__defaults); ATF_TC_BODY(run_params_init__defaults, tc) { kyua_run_params_t run_params; kyua_run_params_init(&run_params); ATF_REQUIRE_EQ(60, run_params.timeout_seconds); ATF_REQUIRE_EQ(getuid(), run_params.unprivileged_user); ATF_REQUIRE_EQ(getgid(), run_params.unprivileged_group); ATF_REQUIRE_STREQ(".", run_params.work_directory); } ATF_TC_WITHOUT_HEAD(fork_exec_wait__ok); ATF_TC_BODY(fork_exec_wait__ok, tc) { const char* const args[] = {"sh", "-c", "exit 42", NULL}; int exitstatus = -1; // Shut up GCC warning. const kyua_error_t error = exec_check("/bin/sh", args, &exitstatus); ATF_REQUIRE(!kyua_error_is_set(error)); ATF_REQUIRE_EQ(42, exitstatus); } ATF_TC(fork_exec_wait__eacces); ATF_TC_HEAD(fork_exec_wait__eacces, tc) { atf_tc_set_md_var(tc, "require.user", "unprivileged"); } ATF_TC_BODY(fork_exec_wait__eacces, tc) { ATF_REQUIRE(mkdir("dir", 0000) != -1); const char* const args[] = {"foo", NULL}; int unused_exitstatus; const kyua_error_t error = exec_check("./dir/foo", args, &unused_exitstatus); ATF_REQUIRE(kyua_error_is_set(error)); ATF_REQUIRE(kyua_error_is_type(error, "libc")); ATF_REQUIRE_EQ(EACCES, kyua_libc_error_errno(error)); } ATF_TC_WITHOUT_HEAD(fork_exec_wait__enoent); ATF_TC_BODY(fork_exec_wait__enoent, tc) { const char* const args[] = {"foo", NULL}; int unused_exitstatus; const kyua_error_t error = exec_check("./foo", args, &unused_exitstatus); ATF_REQUIRE(kyua_error_is_set(error)); ATF_REQUIRE(kyua_error_is_type(error, "libc")); ATF_REQUIRE_EQ(ENOENT, kyua_libc_error_errno(error)); } ATF_TC_WITHOUT_HEAD(fork_wait__core_size); ATF_TC_BODY(fork_wait__core_size, tc) { struct rlimit rl; rl.rlim_cur = 0; rl.rlim_max = RLIM_INFINITY; if (setrlimit(RLIMIT_CORE, &rl) == -1) atf_tc_skip("Failed to lower the core size limit"); kyua_run_params_t run_params; kyua_run_params_init(&run_params); pid_t pid; kyua_error_t error = kyua_run_fork(&run_params, &pid); if (!kyua_error_is_set(error) && pid == 0) abort(); ATF_REQUIRE(!kyua_error_is_set(error)); int status; bool timed_out; error = kyua_run_wait(pid, &status, &timed_out); if (kyua_error_is_set(error)) atf_tc_fail("wait failed; unexpected problem during exec?"); ATF_REQUIRE(!timed_out); ATF_REQUIRE(WIFSIGNALED(status)); ATF_REQUIRE_MSG(WCOREDUMP(status), "Core not dumped as expected"); } ATF_TC_WITHOUT_HEAD(fork_wait__env); ATF_TC_BODY(fork_wait__env, tc) { kyua_env_set("HOME", "/non-existent/directory"); kyua_env_set("LANG", "C"); kyua_env_set("LC_ALL", "C"); kyua_env_set("LC_COLLATE", "C"); kyua_env_set("LC_CTYPE", "C"); kyua_env_set("LC_MESSAGES", "C"); kyua_env_set("LC_MONETARY", "C"); kyua_env_set("LC_NUMERIC", "C"); kyua_env_set("LC_TIME", "C"); kyua_env_set("LEAVE_ME_ALONE", "kill-some-day"); kyua_env_set("TZ", "EST+5"); ATF_REQUIRE_MSG(fork_check(NULL, check_env, NULL), "Unclean environment in subprocess"); } ATF_TC_WITHOUT_HEAD(fork_wait__process_group); ATF_TC_BODY(fork_wait__process_group, tc) { ATF_REQUIRE_MSG(fork_check(NULL, check_process_group, NULL), "Subprocess not in its own process group"); } ATF_TC_WITHOUT_HEAD(fork_wait__signals); ATF_TC_BODY(fork_wait__signals, tc) { ATF_REQUIRE_MSG(LAST_SIGNO > 10, "LAST_SIGNO as detected by configure is " "suspiciously low"); int signo; for (signo = 1; signo <= LAST_SIGNO; signo++) { if (signo == SIGKILL || signo == SIGSTOP) { // Ignore immutable signals. continue; } if (signo == SIGCHLD) { // If we were to reset SIGCHLD to SIG_IGN (which is different than // not touching the signal at all, leaving it at its default value), // our child process will not become a zombie and the call to // kyua_run_wait will fail. Avoid this. continue; } struct sigaction sa; sa.sa_handler = SIG_IGN; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; printf("Ignoring signal %d\n", signo); ATF_REQUIRE(sigaction(signo, &sa, NULL) != -1); } ATF_REQUIRE_MSG(fork_check(NULL, check_signals, NULL), "Signals not reset to their default state"); } ATF_TC_WITHOUT_HEAD(fork_wait__timeout); ATF_TC_BODY(fork_wait__timeout, tc) { kyua_run_params_t run_params; kyua_run_params_init(&run_params); run_params.timeout_seconds = 1; pid_t pid; kyua_error_t error = kyua_run_fork(&run_params, &pid); if (!kyua_error_is_set(error) && pid == 0) { sigset_t mask; sigemptyset(&mask); for (;;) sigsuspend(&mask); } ATF_REQUIRE(!kyua_error_is_set(error)); int status; bool timed_out; kyua_run_wait(pid, &status, &timed_out); ATF_REQUIRE(timed_out); ATF_REQUIRE(WIFSIGNALED(status)); ATF_REQUIRE_EQ(SIGKILL, WTERMSIG(status)); } ATF_TC_WITHOUT_HEAD(fork_wait__umask); ATF_TC_BODY(fork_wait__umask, tc) { (void)umask(0222); ATF_REQUIRE_MSG(fork_check(NULL, check_umask, NULL), "Subprocess does not have the predetermined 0022 umask"); } ATF_TC(fork_wait__unprivileged_user); ATF_TC_HEAD(fork_wait__unprivileged_user, tc) { atf_tc_set_md_var(tc, "require.config", "unprivileged-user"); atf_tc_set_md_var(tc, "require.user", "root"); } ATF_TC_BODY(fork_wait__unprivileged_user, tc) { const struct passwd* pw = getpwnam(atf_tc_get_config_var( tc, "unprivileged-user")); ATF_REQUIRE_MSG(pw != NULL, "Cannot find unprivileged user"); kyua_run_params_t run_params; kyua_run_params_init(&run_params); run_params.unprivileged_user = pw->pw_uid; ATF_REQUIRE_MSG(fork_check(&run_params, check_uid_not_root, NULL), "Subprocess is still running with UID set to root"); } ATF_TC(fork_wait__unprivileged_group); ATF_TC_HEAD(fork_wait__unprivileged_group, tc) { atf_tc_set_md_var(tc, "require.config", "unprivileged-user"); atf_tc_set_md_var(tc, "require.user", "root"); } ATF_TC_BODY(fork_wait__unprivileged_group, tc) { const struct passwd* pw = getpwnam(atf_tc_get_config_var( tc, "unprivileged-user")); ATF_REQUIRE_MSG(pw != NULL, "Cannot find unprivileged user"); kyua_run_params_t run_params; kyua_run_params_init(&run_params); run_params.unprivileged_group = pw->pw_gid; ATF_REQUIRE_MSG(fork_check(&run_params, check_gid_not_root, NULL), "Subprocess is still running with GID set to root"); } ATF_TC(fork_wait__unprivileged_both); ATF_TC_HEAD(fork_wait__unprivileged_both, tc) { atf_tc_set_md_var(tc, "require.config", "unprivileged-user"); atf_tc_set_md_var(tc, "require.user", "root"); } ATF_TC_BODY(fork_wait__unprivileged_both, tc) { const struct passwd* pw = getpwnam(atf_tc_get_config_var( tc, "unprivileged-user")); ATF_REQUIRE_MSG(pw != NULL, "Cannot find unprivileged user"); kyua_run_params_t run_params; kyua_run_params_init(&run_params); run_params.unprivileged_user = pw->pw_uid; run_params.unprivileged_group = pw->pw_gid; ATF_REQUIRE_MSG(fork_check(&run_params, check_not_root, NULL), "Subprocess is still running with root privileges"); } ATF_TC_WITHOUT_HEAD(fork_wait__work_directory); ATF_TC_BODY(fork_wait__work_directory, tc) { ATF_REQUIRE(mkdir("the-work-directory", 0755) != -1); atf_utils_create_file("the-work-directory/data-file", "%s", ""); kyua_run_params_t run_params; kyua_run_params_init(&run_params); run_params.work_directory = "./the-work-directory"; ATF_REQUIRE_MSG(fork_check(&run_params, check_work_directory, "data-file"), "Subprocess not in its own process group"); } ATF_TC_WITHOUT_HEAD(work_directory__builtin_tmpdir); ATF_TC_BODY(work_directory__builtin_tmpdir, tc) { char* tmpdir; RE(kyua_fs_make_absolute("worktest", &tmpdir)); ATF_REQUIRE(mkdir(tmpdir, 0755) != -1); RE(kyua_env_unset("TMPDIR")); kyua_run_tmpdir = tmpdir; char* work_directory; RE(kyua_run_work_directory_enter("template.XXXXXX", getuid(), getgid(), &work_directory)); { char* template_test; RE(kyua_fs_concat(&template_test, atf_tc_get_config_var(tc, "srcdir"), "worktest", "template.XXXXXX", NULL)); ATF_REQUIRE(access(template_test, X_OK) == -1); free(template_test); } ATF_REQUIRE(access(work_directory, X_OK) != -1); ATF_REQUIRE(rmdir(tmpdir) == -1); // Not yet empty. RE(kyua_run_work_directory_leave(&work_directory)); ATF_REQUIRE(rmdir(tmpdir) != -1); free(tmpdir); } ATF_TC_WITHOUT_HEAD(work_directory__env_tmpdir); ATF_TC_BODY(work_directory__env_tmpdir, tc) { char* tmpdir; RE(kyua_fs_make_absolute("worktest", &tmpdir)); ATF_REQUIRE(mkdir(tmpdir, 0755) != -1); RE(kyua_env_set("TMPDIR", tmpdir)); char* work_directory; RE(kyua_run_work_directory_enter("template.XXXXXX", getuid(), getgid(), &work_directory)); { char* template_test; RE(kyua_fs_concat(&template_test, atf_tc_get_config_var(tc, "srcdir"), "worktest", "template.XXXXXX", NULL)); ATF_REQUIRE(access(template_test, X_OK) == -1); free(template_test); } ATF_REQUIRE(access(work_directory, X_OK) != -1); ATF_REQUIRE(rmdir(tmpdir) == -1); // Not yet empty. RE(kyua_run_work_directory_leave(&work_directory)); ATF_REQUIRE(rmdir(tmpdir) != -1); free(tmpdir); } ATF_TC(work_directory__permissions); ATF_TC_HEAD(work_directory__permissions, tc) { atf_tc_set_md_var(tc, "require.config", "unprivileged-user"); atf_tc_set_md_var(tc, "require.user", "root"); } ATF_TC_BODY(work_directory__permissions, tc) { const struct passwd* pw = getpwnam(atf_tc_get_config_var( tc, "unprivileged-user")); printf("%d %d %d %d\n", getuid(), getgid(), pw->pw_uid, pw->pw_gid); char* work_directory; RE(kyua_run_work_directory_enter("template.XXXXXX", pw->pw_uid, pw->pw_gid, &work_directory)); struct stat sb; ATF_REQUIRE(stat(work_directory, &sb) != -1); ATF_REQUIRE_EQ(pw->pw_uid, sb.st_uid); ATF_REQUIRE_EQ(pw->pw_gid, sb.st_gid); RE(kyua_run_work_directory_leave(&work_directory)); } ATF_TC(work_directory__mkdtemp_error); ATF_TC_HEAD(work_directory__mkdtemp_error, tc) { atf_tc_set_md_var(tc, "require.user", "unprivileged"); } ATF_TC_BODY(work_directory__mkdtemp_error, tc) { char* tmpdir; RE(kyua_fs_make_absolute("worktest", &tmpdir)); ATF_REQUIRE(mkdir(tmpdir, 0555) != -1); RE(kyua_env_set("TMPDIR", tmpdir)); char* work_directory; const kyua_error_t error = kyua_run_work_directory_enter( "template.XXXXXX", getuid(), getgid(), &work_directory); ATF_REQUIRE(kyua_error_is_set(error)); ATF_REQUIRE(kyua_error_is_type(error, "libc")); ATF_REQUIRE_EQ(EACCES, kyua_libc_error_errno(error)); kyua_error_free(error); ATF_REQUIRE(rmdir(tmpdir) != -1); // Empty; subdirectory not created. free(tmpdir); } ATF_TC(work_directory__permissions_error); ATF_TC_HEAD(work_directory__permissions_error, tc) { atf_tc_set_md_var(tc, "require.user", "unprivileged"); } ATF_TC_BODY(work_directory__permissions_error, tc) { char* tmpdir; RE(kyua_fs_make_absolute("worktest", &tmpdir)); ATF_REQUIRE(mkdir(tmpdir, 0755) != -1); RE(kyua_env_set("TMPDIR", tmpdir)); char* work_directory; const kyua_error_t error = kyua_run_work_directory_enter( "template.XXXXXX", getuid() + 1, getgid(), &work_directory); ATF_REQUIRE(kyua_error_is_set(error)); ATF_REQUIRE(kyua_error_is_type(error, "libc")); ATF_REQUIRE_EQ(EPERM, kyua_libc_error_errno(error)); kyua_error_free(error); ATF_REQUIRE(rmdir(tmpdir) != -1); // Empty; subdirectory not created. free(tmpdir); } /// Performs a signal delivery test to the work directory handling code. /// /// \param signo The signal to deliver. static void work_directory_signal_check(const int signo) { char* tmpdir; RE(kyua_fs_make_absolute("worktest", &tmpdir)); ATF_REQUIRE(mkdir(tmpdir, 0755) != -1); RE(kyua_env_set("TMPDIR", tmpdir)); char* work_directory; RE(kyua_run_work_directory_enter("template.XXXXXX", getuid(), getgid(), &work_directory)); kyua_run_params_t run_params; kyua_run_params_init(&run_params); run_params.work_directory = work_directory; pid_t pid; RE(kyua_run_fork(&run_params, &pid)); if (pid == 0) { sleep(run_params.timeout_seconds * 2); abort(); } // This should cause the handled installed by the work_directory management // code to terminate the subprocess so that we get a chance to run the // cleanup code ourselves. kill(getpid(), signo); int status; bool timed_out; RE(kyua_run_wait(pid, &status, &timed_out)); ATF_REQUIRE(!timed_out); ATF_REQUIRE(WIFSIGNALED(status)); ATF_REQUIRE_EQ(SIGKILL, WTERMSIG(status)); ATF_REQUIRE(rmdir(tmpdir) == -1); // Not yet empty. RE(kyua_run_work_directory_leave(&work_directory)); ATF_REQUIRE(rmdir(tmpdir) != -1); free(tmpdir); } ATF_TC_WITHOUT_HEAD(work_directory__sighup); ATF_TC_BODY(work_directory__sighup, tc) { work_directory_signal_check(SIGHUP); } ATF_TC_WITHOUT_HEAD(work_directory__sigint); ATF_TC_BODY(work_directory__sigint, tc) { work_directory_signal_check(SIGINT); } ATF_TC_WITHOUT_HEAD(work_directory__sigterm); ATF_TC_BODY(work_directory__sigterm, tc) { work_directory_signal_check(SIGTERM); } ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, run_params_init__defaults); ATF_TP_ADD_TC(tp, fork_exec_wait__ok); ATF_TP_ADD_TC(tp, fork_exec_wait__eacces); ATF_TP_ADD_TC(tp, fork_exec_wait__enoent); ATF_TP_ADD_TC(tp, fork_wait__core_size); ATF_TP_ADD_TC(tp, fork_wait__env); ATF_TP_ADD_TC(tp, fork_wait__process_group); ATF_TP_ADD_TC(tp, fork_wait__signals); ATF_TP_ADD_TC(tp, fork_wait__timeout); ATF_TP_ADD_TC(tp, fork_wait__umask); ATF_TP_ADD_TC(tp, fork_wait__unprivileged_user); ATF_TP_ADD_TC(tp, fork_wait__unprivileged_group); ATF_TP_ADD_TC(tp, fork_wait__unprivileged_both); ATF_TP_ADD_TC(tp, fork_wait__work_directory); ATF_TP_ADD_TC(tp, work_directory__builtin_tmpdir); ATF_TP_ADD_TC(tp, work_directory__env_tmpdir); ATF_TP_ADD_TC(tp, work_directory__permissions); ATF_TP_ADD_TC(tp, work_directory__permissions_error); ATF_TP_ADD_TC(tp, work_directory__mkdtemp_error); ATF_TP_ADD_TC(tp, work_directory__sighup); ATF_TP_ADD_TC(tp, work_directory__sigint); ATF_TP_ADD_TC(tp, work_directory__sigterm); return atf_no_error(); }