/* * Copyright (c) 2017 Oracle and/or its affiliates. All Rights Reserved. * Copyright (c) 2013 Fujitsu Ltd. * Author: Zeng Linggang * * This program is free software; you can redistribute it and/or modify it * under the terms of version 2 of the GNU General Public License as * published by the Free Software Foundation. * * This program is distributed in the hope that it would be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the GNU General Public License along * with this program. */ #define _GNU_SOURCE #include #include #include #include #include #include #include "tst_test.h" #include "clone_platform.h" #include "lapi/syscalls.h" static pid_t ptid, ctid, tgid; static void *child_stack; static void test_clone_parent(int t); static int child_clone_parent(void *); static pid_t parent_ppid; static void test_clone_tid(int t); static int child_clone_child_settid(void *); static int child_clone_parent_settid(void *); #ifdef CLONE_STOPPED static void test_clone_stopped(int t); static int child_clone_stopped(void *); static int stopped_flag; #endif static void test_clone_thread(int t); static int child_clone_thread(void *); /* * Children cloned with CLONE_VM should avoid using any functions that * might require dl_runtime_resolve, because they share thread-local * storage with parent. If both try to resolve symbols at same time you * can crash, likely at _dl_x86_64_restore_sse(). * See this thread for relevant discussion: * http://www.mail-archive.com/utrace-devel@redhat.com/msg01944.html */ static struct test_case { char *name; int flags; void (*testfunc)(int); int (*do_child)(void *); } test_cases[] = { {"CLONE_PARENT", CLONE_PARENT | SIGCHLD, test_clone_parent, child_clone_parent}, {"CLONE_CHILD_SETTID", CLONE_CHILD_SETTID | SIGCHLD, test_clone_tid, child_clone_child_settid}, {"CLONE_PARENT_SETTID", CLONE_PARENT_SETTID | CLONE_VM | SIGCHLD, test_clone_tid, child_clone_parent_settid}, #ifdef CLONE_STOPPED {"CLONE_STOPPED", CLONE_STOPPED | CLONE_VM | SIGCHLD, test_clone_stopped, child_clone_stopped}, #endif {"CLONE_THREAD", CLONE_THREAD | CLONE_SIGHAND | CLONE_VM | CLONE_CHILD_CLEARTID | SIGCHLD, test_clone_thread, child_clone_thread}, }; static void do_test(unsigned int i) { tst_res(TINFO, "running %s", test_cases[i].name); test_cases[i].testfunc(i); } static void setup(void) { child_stack = SAFE_MALLOC(CHILD_STACK_SIZE); } static void cleanup(void) { free(child_stack); } static long clone_child(const struct test_case *t) { TEST(ltp_clone7(t->flags, t->do_child, NULL, CHILD_STACK_SIZE, child_stack, &ptid, NULL, &ctid)); if (TEST_RETURN == -1 && TTERRNO == ENOSYS) tst_brk(TCONF, "clone does not support 7 args"); if (TEST_RETURN == -1) tst_brk(TBROK | TTERRNO, "%s clone() failed", t->name); return TEST_RETURN; } static void test_clone_parent(int t) { pid_t child; child = SAFE_FORK(); if (!child) { parent_ppid = getppid(); clone_child(&test_cases[t]); _exit(0); } tst_reap_children(); } static int child_clone_parent(void *arg LTP_ATTRIBUTE_UNUSED) { if (parent_ppid == getppid()) { tst_res(TPASS, "clone and forked child has the same parent"); } else { tst_res(TFAIL, "getppid != parent_ppid (%d != %d)", parent_ppid, getppid()); } tst_syscall(__NR_exit, 0); return 0; } static void test_clone_tid(int t) { clone_child(&test_cases[t]); tst_reap_children(); } static int child_clone_child_settid(void *arg LTP_ATTRIBUTE_UNUSED) { if (ctid == tst_syscall(__NR_getpid)) tst_res(TPASS, "clone() correctly set ctid"); else tst_res(TFAIL, "ctid != getpid() (%d != %d)", ctid, getpid()); tst_syscall(__NR_exit, 0); return 0; } static int child_clone_parent_settid(void *arg LTP_ATTRIBUTE_UNUSED) { if (ptid == tst_syscall(__NR_getpid)) tst_res(TPASS, "clone() correctly set ptid"); else tst_res(TFAIL, "ptid != getpid() (%d != %d)", ptid, getpid()); tst_syscall(__NR_exit, 0); return 0; } #ifdef CLONE_STOPPED static void test_clone_stopped(int t) { pid_t child; if (tst_kvercmp(2, 6, 38) >= 0) { tst_res(TCONF, "CLONE_STOPPED skipped for kernels >= 2.6.38"); return; } child = clone_child(&test_cases[t]); TST_PROCESS_STATE_WAIT(child, 'T'); stopped_flag = 0; SAFE_KILL(child, SIGCONT); tst_reap_children(); if (stopped_flag == 1) tst_res(TPASS, "clone stopped and resumed as expected"); else tst_res(TFAIL, "clone not stopped, flag %d", stopped_flag); } static int child_clone_stopped(void *arg LTP_ATTRIBUTE_UNUSED) { stopped_flag = 1; tst_syscall(__NR_exit, 0); return 0; } #endif static void test_clone_thread(int t) { pid_t child; child = SAFE_FORK(); if (!child) { struct timespec timeout = { 5 /* sec */, 0 }; tgid = tst_syscall(__NR_getpid); ctid = -1; clone_child(&test_cases[t]); if (syscall(SYS_futex, &ctid, FUTEX_WAIT, -1, &timeout)) { /* * futex here is racing with clone() above. * Which means we can get EWOULDBLOCK if * ctid has been already changed by clone() * before we make the call. As long as ctid * changes we should not report error when * futex returns EWOULDBLOCK. */ if (errno != EWOULDBLOCK || ctid == -1) { tst_res(TFAIL | TERRNO, "futex failed, ctid: %d", ctid); _exit(0); } } tst_res(TPASS, "futex exit on ctid change, ctid: %d", ctid); _exit(0); } tst_reap_children(); } static int child_clone_thread(void *arg LTP_ATTRIBUTE_UNUSED) { if (tgid == tst_syscall(__NR_getpid)) tst_res(TPASS, "clone has the same thread id"); else tst_res(TFAIL, "clone's thread id not equal parent's id"); tst_syscall(__NR_exit, 0); return 0; } static struct tst_test test = { .tcnt = ARRAY_SIZE(test_cases), .test = do_test, .setup = setup, .cleanup = cleanup, .forks_child = 1 };