Index: sandbox/linux/seccomp/tests/test_syscalls.cc |
=================================================================== |
--- sandbox/linux/seccomp/tests/test_syscalls.cc (revision 57969) |
+++ sandbox/linux/seccomp/tests/test_syscalls.cc (working copy) |
@@ -1,758 +0,0 @@ |
-// Copyright (c) 2010 The Chromium Authors. All rights reserved. |
-// Use of this source code is governed by a BSD-style license that can be |
-// found in the LICENSE file. |
- |
-#include <assert.h> |
-#include <dirent.h> |
-#include <pthread.h> |
-#include <pty.h> |
-#include <sys/types.h> |
-#include <sys/wait.h> |
- |
-#include "sandbox_impl.h" |
- |
-#ifdef DEBUG |
-#define MSG(fmt, ...) printf(fmt, ##__VA_ARGS__) |
-#else |
-#define MSG(fmt, ...) do { } while (0) |
-#endif |
- |
-int g_intended_status_fd = -1; |
- |
-// Declares the wait() status that the test subprocess intends to exit with. |
-void intend_exit_status(int val, bool is_signal) { |
- if (is_signal) { |
- val = W_EXITCODE(0, val); |
- } else { |
- val = W_EXITCODE(val, 0); |
- } |
- if (g_intended_status_fd != -1) { |
- int sent = write(g_intended_status_fd, &val, sizeof(val)); |
- assert(sent == sizeof(val)); |
- } else { |
- // This prints in cases where we run one test without forking |
- printf("Intending to exit with status %i...\n", val); |
- } |
-} |
- |
- |
-// This is basically a marker to grep for. |
-#define TEST(name) void name() |
- |
-TEST(test_dup) { |
- StartSeccompSandbox(); |
- // Test a simple syscall that is marked as UNRESTRICTED_SYSCALL. |
- int fd = dup(1); |
- assert(fd >= 0); |
- int rc = close(fd); |
- assert(rc == 0); |
-} |
- |
-TEST(test_segfault) { |
- StartSeccompSandbox(); |
- // Check that the sandbox's SIGSEGV handler does not stop the |
- // process from dying cleanly in the event of a real segfault. |
- intend_exit_status(SIGSEGV, true); |
- asm("hlt"); |
-} |
- |
-TEST(test_exit) { |
- StartSeccompSandbox(); |
- intend_exit_status(123, false); |
- _exit(123); |
-} |
- |
-// This has an off-by-three error because it counts ".", "..", and the |
-// FD for the /proc/self/fd directory. This doesn't matter because it |
-// is only used to check for differences in the number of open FDs. |
-static int count_fds() { |
- DIR *dir = opendir("/proc/self/fd"); |
- assert(dir != NULL); |
- int count = 0; |
- while (1) { |
- struct dirent *d = readdir(dir); |
- if (d == NULL) |
- break; |
- count++; |
- } |
- int rc = closedir(dir); |
- assert(rc == 0); |
- return count; |
-} |
- |
-static void *thread_func(void *x) { |
- int *ptr = (int *) x; |
- *ptr = 123; |
- MSG("In new thread\n"); |
- return (void *) 456; |
-} |
- |
-TEST(test_thread) { |
- playground::g_policy.allow_file_namespace = true; // To allow count_fds() |
- StartSeccompSandbox(); |
- int fd_count1 = count_fds(); |
- pthread_t tid; |
- int x = 999; |
- void *result; |
- pthread_create(&tid, NULL, thread_func, &x); |
- MSG("Waiting for thread\n"); |
- pthread_join(tid, &result); |
- assert(result == (void *) 456); |
- assert(x == 123); |
- // Check that the process has not leaked FDs. |
- int fd_count2 = count_fds(); |
- assert(fd_count2 == fd_count1); |
-} |
- |
-static int clone_func(void *x) { |
- int *ptr = (int *) x; |
- *ptr = 124; |
- MSG("In thread\n"); |
- // On x86-64, returning from this function calls the __NR_exit_group |
- // syscall instead of __NR_exit. |
- syscall(__NR_exit, 100); |
- // Not reached. |
- return 200; |
-} |
- |
-#if defined(__i386__) |
-static int get_gs() { |
- int gs; |
- asm volatile("mov %%gs, %0" : "=r"(gs)); |
- return gs; |
-} |
-#endif |
- |
-static void *get_tls_base() { |
- void *base; |
-#if defined(__x86_64__) |
- asm volatile("mov %%fs:0, %0" : "=r"(base)); |
-#elif defined(__i386__) |
- asm volatile("mov %%gs:0, %0" : "=r"(base)); |
-#else |
-#error Unsupported target platform |
-#endif |
- return base; |
-} |
- |
-TEST(test_clone) { |
- playground::g_policy.allow_file_namespace = true; // To allow count_fds() |
- StartSeccompSandbox(); |
- int fd_count1 = count_fds(); |
- int stack_size = 0x1000; |
- char *stack = (char *) malloc(stack_size); |
- assert(stack != NULL); |
- int flags = CLONE_VM | CLONE_FS | CLONE_FILES | |
- CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM | |
- CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID; |
- int tid = -1; |
- int x = 999; |
- |
- // The sandbox requires us to pass CLONE_TLS. Pass settings that |
- // are enough to copy the parent thread's TLS setup. This allows us |
- // to invoke libc in the child thread. |
-#if defined(__x86_64__) |
- void *tls = get_tls_base(); |
-#elif defined(__i386__) |
- struct user_desc tls_desc, *tls = &tls_desc; |
- tls_desc.entry_number = get_gs() >> 3; |
- tls_desc.base_addr = (long) get_tls_base(); |
- tls_desc.limit = 0xfffff; |
- tls_desc.seg_32bit = 1; |
- tls_desc.contents = 0; |
- tls_desc.read_exec_only = 0; |
- tls_desc.limit_in_pages = 1; |
- tls_desc.seg_not_present = 0; |
- tls_desc.useable = 1; |
-#else |
-#error Unsupported target platform |
-#endif |
- |
- int rc = clone(clone_func, (void *) (stack + stack_size), flags, &x, |
- &tid, tls, &tid); |
- assert(rc > 0); |
- while (tid == rc) { |
- syscall(__NR_futex, &tid, FUTEX_WAIT, rc, NULL); |
- } |
- assert(tid == 0); |
- assert(x == 124); |
- // Check that the process has not leaked FDs. |
- int fd_count2 = count_fds(); |
- assert(fd_count2 == fd_count1); |
-} |
- |
-static int uncalled_clone_func(void *x) { |
- printf("In thread func, which shouldn't happen\n"); |
- return 1; |
-} |
- |
-TEST(test_clone_disallowed_flags) { |
- StartSeccompSandbox(); |
- int stack_size = 4096; |
- char *stack = (char *) malloc(stack_size); |
- assert(stack != NULL); |
- /* We omit the flags CLONE_SETTLS, CLONE_PARENT_SETTID and |
- CLONE_CHILD_CLEARTID, which is disallowed by the sandbox. */ |
- int flags = CLONE_VM | CLONE_FS | CLONE_FILES | |
- CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM; |
- int rc = clone(uncalled_clone_func, (void *) (stack + stack_size), |
- flags, NULL, NULL, NULL, NULL); |
- assert(rc == -1); |
- assert(errno == EPERM); |
-} |
- |
-static void *fp_thread(void *x) { |
- int val; |
- asm("movss %%xmm0, %0" : "=m"(val)); |
- MSG("val=%i\n", val); |
- return NULL; |
-} |
- |
-TEST(test_fp_regs) { |
- StartSeccompSandbox(); |
- int val = 1234; |
- asm("movss %0, %%xmm0" : "=m"(val)); |
- pthread_t tid; |
- pthread_create(&tid, NULL, fp_thread, NULL); |
- pthread_join(tid, NULL); |
- MSG("thread done OK\n"); |
-} |
- |
-static long long read_tsc() { |
- long long rc; |
- asm volatile( |
- "rdtsc\n" |
- "mov %%eax, (%0)\n" |
- "mov %%edx, 4(%0)\n" |
- : |
- : "c"(&rc), "a"(-1), "d"(-1)); |
- return rc; |
-} |
- |
-TEST(test_rdtsc) { |
- StartSeccompSandbox(); |
- // Just check that we can do the instruction. |
- read_tsc(); |
-} |
- |
-TEST(test_getpid) { |
- int pid1 = getpid(); |
- StartSeccompSandbox(); |
- int pid2 = getpid(); |
- assert(pid1 == pid2); |
- // Bypass any caching that glibc's getpid() wrapper might do. |
- int pid3 = syscall(__NR_getpid); |
- assert(pid1 == pid3); |
-} |
- |
-TEST(test_gettid) { |
- // glibc doesn't provide a gettid() wrapper. |
- int tid1 = syscall(__NR_gettid); |
- assert(tid1 > 0); |
- StartSeccompSandbox(); |
- int tid2 = syscall(__NR_gettid); |
- assert(tid1 == tid2); |
-} |
- |
-static void *map_something() { |
- void *addr = mmap(NULL, 0x1000, PROT_READ, |
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
- assert(addr != MAP_FAILED); |
- return addr; |
-} |
- |
-TEST(test_mmap_disallows_remapping) { |
- void *addr = map_something(); |
- StartSeccompSandbox(); |
- // Overwriting a mapping that was created before the sandbox was |
- // enabled is not allowed. |
- void *result = mmap(addr, 0x1000, PROT_READ, |
- MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); |
- assert(result == MAP_FAILED); |
- assert(errno == EINVAL); |
-} |
- |
-TEST(test_mmap_disallows_low_address) { |
- StartSeccompSandbox(); |
- // Mapping pages at low addresses is not allowed because this helps |
- // with exploiting buggy kernels. |
- void *result = mmap(NULL, 0x1000, PROT_READ, |
- MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); |
- assert(result == MAP_FAILED); |
- assert(errno == EINVAL); |
-} |
- |
-TEST(test_munmap_allowed) { |
- StartSeccompSandbox(); |
- void *addr = map_something(); |
- int result = munmap(addr, 0x1000); |
- assert(result == 0); |
-} |
- |
-TEST(test_munmap_disallowed) { |
- void *addr = map_something(); |
- StartSeccompSandbox(); |
- int result = munmap(addr, 0x1000); |
- assert(result == -1); |
- assert(errno == EINVAL); |
-} |
- |
-TEST(test_mprotect_allowed) { |
- StartSeccompSandbox(); |
- void *addr = map_something(); |
- int result = mprotect(addr, 0x1000, PROT_READ | PROT_WRITE); |
- assert(result == 0); |
-} |
- |
-TEST(test_mprotect_disallowed) { |
- void *addr = map_something(); |
- StartSeccompSandbox(); |
- int result = mprotect(addr, 0x1000, PROT_READ | PROT_WRITE); |
- assert(result == -1); |
- assert(errno == EINVAL); |
-} |
- |
-static int get_tty_fd() { |
- int master_fd, tty_fd; |
- int rc = openpty(&master_fd, &tty_fd, NULL, NULL, NULL); |
- assert(rc == 0); |
- return tty_fd; |
-} |
- |
-TEST(test_ioctl_tiocgwinsz_allowed) { |
- int tty_fd = get_tty_fd(); |
- StartSeccompSandbox(); |
- int size[2]; |
- // Get terminal width and height. |
- int result = ioctl(tty_fd, TIOCGWINSZ, size); |
- assert(result == 0); |
-} |
- |
-TEST(test_ioctl_disallowed) { |
- int tty_fd = get_tty_fd(); |
- StartSeccompSandbox(); |
- // This ioctl call inserts a character into the tty's input queue, |
- // which provides a way to send commands to an interactive shell. |
- char c = 'x'; |
- int result = ioctl(tty_fd, TIOCSTI, &c); |
- assert(result == -1); |
- assert(errno == EINVAL); |
-} |
- |
-TEST(test_socket) { |
- StartSeccompSandbox(); |
- int fd = socket(AF_UNIX, SOCK_STREAM, 0); |
- assert(fd == -1); |
- // TODO: Make it consistent between i386 and x86-64. |
- assert(errno == EINVAL || errno == ENOSYS); |
-} |
- |
-TEST(test_open_disabled) { |
- StartSeccompSandbox(); |
- int fd = open("/dev/null", O_RDONLY); |
- assert(fd == -1); |
- assert(errno == EACCES); |
- |
- // Writing to the policy flag does not change this. |
- playground::g_policy.allow_file_namespace = true; |
- fd = open("/dev/null", O_RDONLY); |
- assert(fd == -1); |
- assert(errno == EACCES); |
-} |
- |
-TEST(test_open_enabled) { |
- playground::g_policy.allow_file_namespace = true; |
- StartSeccompSandbox(); |
- int fd = open("/dev/null", O_RDONLY); |
- assert(fd >= 0); |
- int rc = close(fd); |
- assert(rc == 0); |
- fd = open("/dev/null", O_WRONLY); |
- assert(fd == -1); |
- assert(errno == EACCES); |
-} |
- |
-TEST(test_access_disabled) { |
- StartSeccompSandbox(); |
- int rc = access("/dev/null", R_OK); |
- assert(rc == -1); |
- assert(errno == EACCES); |
-} |
- |
-TEST(test_access_enabled) { |
- playground::g_policy.allow_file_namespace = true; |
- StartSeccompSandbox(); |
- int rc = access("/dev/null", R_OK); |
- assert(rc == 0); |
- rc = access("path-that-does-not-exist", R_OK); |
- assert(rc == -1); |
- assert(errno == ENOENT); |
-} |
- |
-TEST(test_stat_disabled) { |
- StartSeccompSandbox(); |
- struct stat st; |
- int rc = stat("/dev/null", &st); |
- assert(rc == -1); |
- assert(errno == EACCES); |
-} |
- |
-TEST(test_stat_enabled) { |
- playground::g_policy.allow_file_namespace = true; |
- StartSeccompSandbox(); |
- struct stat st; |
- int rc = stat("/dev/null", &st); |
- assert(rc == 0); |
- rc = stat("path-that-does-not-exist", &st); |
- assert(rc == -1); |
- assert(errno == ENOENT); |
-} |
- |
-static int g_value; |
- |
-static void signal_handler(int sig) { |
- g_value = 300; |
- MSG("In signal handler\n"); |
-} |
- |
-static void sigaction_handler(int sig, siginfo_t *a, void *b) { |
- g_value = 300; |
- MSG("In sigaction handler\n"); |
-} |
- |
-static void (*g_sig_handler_ptr)(int sig, void *addr) asm("g_sig_handler_ptr"); |
- |
-static void non_fatal_sig_handler(int sig, void *addr) { |
- g_value = 300; |
- MSG("Caught signal %d at %p\n", sig, addr); |
-} |
- |
-static void fatal_sig_handler(int sig, void *addr) { |
- // Recursively trigger another segmentation fault while already in the SEGV |
- // handler. This should terminate the program if SIGSEGV is marked as a |
- // deferred signal. |
- // Only do this on the first entry to this function. Otherwise, the signal |
- // handler was probably marked as SA_NODEFER and we want to continue |
- // execution. |
- if (!g_value++) { |
- MSG("Caught signal %d at %p\n", sig, addr); |
- if (sig == SIGSEGV) { |
- asm volatile("hlt"); |
- } else { |
- asm volatile("int3"); |
- } |
- } |
-} |
- |
-static void (*generic_signal_handler(void)) |
- (int signo, siginfo_t *info, void *context) { |
- void (*hdl)(int, siginfo_t *, void *); |
- asm volatile( |
- "lea 0f, %0\n" |
- "jmp 999f\n" |
- "0:\n" |
- |
-#if defined(__x86_64__) |
- "mov 0xB0(%%rsp), %%rsi\n" // Pass original %rip to signal handler |
- "cmpb $0xF4, 0(%%rsi)\n" // hlt |
- "jnz 1f\n" |
- "addq $1, 0xB0(%%rsp)\n" // Adjust %eip past failing instruction |
- "1:jmp *g_sig_handler_ptr\n" // Call actual signal handler |
-#elif defined(__i386__) |
- // TODO(markus): We currently don't guarantee that signal handlers always |
- // have the correct "magic" restorer function. If we fix |
- // this, we should add a test for it (both for SEGV and |
- // non-SEGV). |
- "cmpw $0, 0xA(%%esp)\n" |
- "lea 0x40(%%esp), %%eax\n" // %eip at time of exception |
- "jz 1f\n" |
- "add $0x9C, %%eax\n" // %eip at time of exception |
- "1:mov 0(%%eax), %%ecx\n" |
- "cmpb $0xF4, 0(%%ecx)\n" // hlt |
- "jnz 2f\n" |
- "addl $1, 0(%%eax)\n" // Adjust %eip past failing instruction |
- "2:push %%ecx\n" // Pass original %eip to signal handler |
- "mov 8(%%esp), %%eax\n" |
- "push %%eax\n" // Pass signal number to signal handler |
- "call *g_sig_handler_ptr\n" // Call actual signal handler |
- "pop %%eax\n" |
- "pop %%ecx\n" |
- "ret\n" |
-#else |
-#error Unsupported target platform |
-#endif |
- |
-"999:\n" |
- : "=r"(hdl)); |
- return hdl; |
-} |
- |
-TEST(test_signal_handler) { |
- sighandler_t result = signal(SIGTRAP, signal_handler); |
- assert(result != SIG_ERR); |
- |
- StartSeccompSandbox(); |
- |
- result = signal(SIGTRAP, signal_handler); |
- assert(result != SIG_ERR); |
- |
- g_value = 200; |
- asm("int3"); |
- assert(g_value == 300); |
-} |
- |
-TEST(test_sigaction_handler) { |
- struct sigaction act; |
- act.sa_sigaction = sigaction_handler; |
- sigemptyset(&act.sa_mask); |
- act.sa_flags = SA_SIGINFO; |
- int rc = sigaction(SIGTRAP, &act, NULL); |
- assert(rc == 0); |
- |
- StartSeccompSandbox(); |
- |
- rc = sigaction(SIGTRAP, &act, NULL); |
- assert(rc == 0); |
- |
- g_value = 200; |
- asm("int3"); |
- assert(g_value == 300); |
-} |
- |
-TEST(test_blocked_signal) { |
- sighandler_t result = signal(SIGTRAP, signal_handler); |
- assert(result != SIG_ERR); |
- StartSeccompSandbox(); |
- |
- // Initially the signal should not be blocked. |
- sigset_t sigs; |
- sigfillset(&sigs); |
- int rc = sigprocmask(0, NULL, &sigs); |
- assert(rc == 0); |
- assert(!sigismember(&sigs, SIGTRAP)); |
- |
- sigemptyset(&sigs); |
- sigaddset(&sigs, SIGTRAP); |
- rc = sigprocmask(SIG_BLOCK, &sigs, NULL); |
- assert(rc == 0); |
- |
- // Check that we can read back the blocked status. |
- sigemptyset(&sigs); |
- rc = sigprocmask(0, NULL, &sigs); |
- assert(rc == 0); |
- assert(sigismember(&sigs, SIGTRAP)); |
- |
- // Check that the signal handler really is blocked. |
- intend_exit_status(SIGTRAP, true); |
- asm("int3"); |
-} |
- |
-TEST(test_sigaltstack) { |
- // The sandbox does not support sigaltstack() yet. Just test that |
- // it returns an error. |
- StartSeccompSandbox(); |
- stack_t st; |
- st.ss_size = 0x4000; |
- st.ss_sp = malloc(st.ss_size); |
- assert(st.ss_sp != NULL); |
- st.ss_flags = 0; |
- int rc = sigaltstack(&st, NULL); |
- assert(rc == -1); |
- assert(errno == ENOSYS); |
-} |
- |
-TEST(test_sa_flags) { |
- StartSeccompSandbox(); |
- int flags[4] = { 0, SA_NODEFER, SA_SIGINFO, SA_SIGINFO | SA_NODEFER }; |
- for (int i = 0; i < 4; ++i) { |
- struct sigaction sa; |
- memset(&sa, 0, sizeof(sa)); |
- sa.sa_sigaction = generic_signal_handler(); |
- g_sig_handler_ptr = non_fatal_sig_handler; |
- sa.sa_flags = flags[i]; |
- |
- // Test SEGV handling |
- g_value = 200; |
- sigaction(SIGSEGV, &sa, NULL); |
- asm volatile("hlt"); |
- assert(g_value == 300); |
- |
- // Test non-SEGV handling |
- g_value = 200; |
- sigaction(SIGTRAP, &sa, NULL); |
- asm volatile("int3"); |
- assert(g_value == 300); |
- } |
-} |
- |
-TEST(test_segv_defer) { |
- StartSeccompSandbox(); |
- struct sigaction sa; |
- memset(&sa, 0, sizeof(sa)); |
- sa.sa_sigaction = generic_signal_handler(); |
- g_sig_handler_ptr = fatal_sig_handler; |
- |
- // Test non-deferred SEGV (should continue execution) |
- sa.sa_flags = SA_NODEFER; |
- sigaction(SIGSEGV, &sa, NULL); |
- g_value = 0; |
- asm volatile("hlt"); |
- |
- // Test deferred SEGV (should terminate program) |
- sa.sa_flags = 0; |
- sigaction(SIGSEGV, &sa, NULL); |
- g_value = 0; |
- intend_exit_status(SIGSEGV, true); |
- asm volatile("hlt"); |
-} |
- |
-TEST(test_trap_defer) { |
- StartSeccompSandbox(); |
- struct sigaction sa; |
- memset(&sa, 0, sizeof(sa)); |
- sa.sa_sigaction = generic_signal_handler(); |
- g_sig_handler_ptr = fatal_sig_handler; |
- |
- // Test non-deferred TRAP (should continue execution) |
- sa.sa_flags = SA_NODEFER; |
- sigaction(SIGTRAP, &sa, NULL); |
- g_value = 0; |
- asm volatile("int3"); |
- |
- // Test deferred TRAP (should terminate program) |
- sa.sa_flags = 0; |
- sigaction(SIGTRAP, &sa, NULL); |
- g_value = 0; |
- intend_exit_status(SIGTRAP, true); |
- asm volatile("int3"); |
-} |
- |
-TEST(test_segv_resethand) { |
- StartSeccompSandbox(); |
- struct sigaction sa; |
- memset(&sa, 0, sizeof(sa)); |
- sa.sa_sigaction = generic_signal_handler(); |
- g_sig_handler_ptr = non_fatal_sig_handler; |
- sa.sa_flags = SA_RESETHAND; |
- sigaction(SIGSEGV, &sa, NULL); |
- |
- // Test first invocation of signal handler (should continue execution) |
- asm volatile("hlt"); |
- |
- // Test second invocation of signal handler (should terminate program) |
- intend_exit_status(SIGSEGV, true); |
- asm volatile("hlt"); |
-} |
- |
-TEST(test_trap_resethand) { |
- StartSeccompSandbox(); |
- struct sigaction sa; |
- memset(&sa, 0, sizeof(sa)); |
- sa.sa_sigaction = generic_signal_handler(); |
- g_sig_handler_ptr = non_fatal_sig_handler; |
- sa.sa_flags = SA_RESETHAND; |
- sigaction(SIGTRAP, &sa, NULL); |
- |
- // Test first invocation of signal handler (should continue execution) |
- asm volatile("int3"); |
- |
- // Test second invocation of signal handler (should terminate program) |
- intend_exit_status(SIGTRAP, true); |
- asm volatile("int3"); |
-} |
- |
-struct testcase { |
- const char *test_name; |
- void (*test_func)(); |
-}; |
- |
-struct testcase all_tests[] = { |
-#include "test-list.h" |
- { NULL, NULL }, |
-}; |
- |
-static int run_test_forked(struct testcase *test) { |
- printf("** %s\n", test->test_name); |
- int pipe_fds[2]; |
- int rc = pipe(pipe_fds); |
- assert(rc == 0); |
- int pid = fork(); |
- if (pid == 0) { |
- rc = close(pipe_fds[0]); |
- assert(rc == 0); |
- g_intended_status_fd = pipe_fds[1]; |
- |
- test->test_func(); |
- intend_exit_status(0, false); |
- _exit(0); |
- } |
- rc = close(pipe_fds[1]); |
- assert(rc == 0); |
- |
- int intended_status; |
- int got = read(pipe_fds[0], &intended_status, sizeof(intended_status)); |
- bool got_intended_status = got == sizeof(intended_status); |
- if (!got_intended_status) { |
- printf("Test runner: Did not receive intended status\n"); |
- } |
- |
- int status; |
- int pid2 = waitpid(pid, &status, 0); |
- assert(pid2 == pid); |
- if (!got_intended_status) { |
- printf("Test returned exit status %i\n", status); |
- return 1; |
- } |
- else if ((status & ~WCOREFLAG) != intended_status) { |
- printf("Test failed with exit status %i, expected %i\n", |
- status, intended_status); |
- return 1; |
- } |
- else { |
- return 0; |
- } |
-} |
- |
-static int run_test_by_name(const char *name) { |
- struct testcase *test; |
- for (test = all_tests; test->test_name != NULL; test++) { |
- if (strcmp(name, test->test_name) == 0) { |
- printf("Running test %s...\n", name); |
- test->test_func(); |
- printf("OK\n"); |
- return 0; |
- } |
- } |
- fprintf(stderr, "Test '%s' not found\n", name); |
- return 1; |
-} |
- |
-int main(int argc, char **argv) { |
- setvbuf(stdout, NULL, _IONBF, 0); |
- setvbuf(stderr, NULL, _IONBF, 0); |
- if (argc == 2) { |
- // Run one test without forking, to aid debugging. |
- return run_test_by_name(argv[1]); |
- } |
- else if (argc > 2) { |
- // TODO: run multiple tests. |
- fprintf(stderr, "Too many arguments\n"); |
- return 1; |
- } |
- else { |
- // Run all tests. |
- struct testcase *test; |
- int failures = 0; |
- for (test = all_tests; test->test_name != NULL; test++) { |
- failures += run_test_forked(test); |
- } |
- if (failures == 0) { |
- printf("OK\n"); |
- return 0; |
- } |
- else { |
- printf("%i FAILURE(S)\n", failures); |
- return 1; |
- } |
- } |
-} |