/*
 * Copyright (c) 2017 Oracle and/or its affiliates. All Rights Reserved.
 * Copyright (c) 2013 Fujitsu Ltd.
 * Author: Zeng Linggang <zenglg.jy@cn.fujitsu.com>
 *
 * 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 <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sched.h>
#include <sys/wait.h>
#include <linux/futex.h>

#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)
{
	pid_t child;

	child = 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))
			tst_res(TFAIL | TERRNO, "futex failed");
		else
			tst_res(TPASS, "futex exit on ctid change");

		_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
};
