/*
 * Copyright (c) 2011-2016, 2018, 2021-2022 Jonas 'Sortie' Termansen.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * thread.cpp
 * Describes a thread belonging to a process.
 */

#include <sys/wait.h>

#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <timespec.h>

#include <sortix/clock.h>
#include <sortix/exit.h>
#include <sortix/futex.h>
#include <sortix/mman.h>
#include <sortix/signal.h>

#include <sortix/kernel/copy.h>
#include <sortix/kernel/interrupt.h>
#include <sortix/kernel/ioctx.h>
#include <sortix/kernel/kernel.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/memorymanagement.h>
#include <sortix/kernel/process.h>
#include <sortix/kernel/scheduler.h>
#include <sortix/kernel/syscall.h>
#include <sortix/kernel/thread.h>
#include <sortix/kernel/time.h>

#if defined(__i386__) || defined(__x86_64__)
#include "x86-family/float.h"
#endif

namespace Sortix {

Thread::Thread()
{
	assert(!((uintptr_t) registers.fpuenv & 0xFUL));
	name = "";
	system_tid = (uintptr_t) this;
	yield_to_tid = 0;
	id = 0; // TODO: Make a thread id.
	process = NULL;
	prevsibling = NULL;
	nextsibling = NULL;
	scheduler_list_prev = NULL;
	scheduler_list_next = NULL;
	state = NONE;
	memset(&registers, 0, sizeof(registers));
	kernelstackpos = 0;
	kernelstacksize = 0;
	signal_count = 0;
	signal_single_frame = 0;
	signal_canary = 0;
	kernelstackmalloced = false;
	pledged_destruction = false;
	force_no_signals = false;
	signal_single = false;
	has_saved_signal_mask = false;
	sigemptyset(&signal_pending);
	sigemptyset(&signal_mask);
	sigemptyset(&saved_signal_mask);
	memset(&signal_stack, 0, sizeof(signal_stack));
	signal_stack.ss_flags = SS_DISABLE;
	// execute_clock initialized in member constructor.
	// system_clock initialized in member constructor.
	Time::InitializeThreadClocks(this);
	futex_address = 0;
	kutex_address = 0;
	futex_woken = false;
	kutex_woken = false;
	futex_prev_waiting = NULL;
	futex_next_waiting = NULL;
	kutex_prev_waiting = NULL;
	kutex_next_waiting = NULL;
	yield_operation = YIELD_OPERATION_NONE;
}

Thread::~Thread()
{
	if ( process )
		process->OnThreadDestruction(this);
	assert(CurrentThread() != this);
	if ( kernelstackmalloced )
		delete[] (uint8_t*) kernelstackpos;
}

Thread* CreateKernelThread(Process* process,
                           struct thread_registers* regs,
                           const char* name)
{
	assert(process && regs && process->addrspace);

#if defined(__x86_64__)
	if ( regs->fsbase >> 48 != 0x0000 && regs->fsbase >> 48 != 0xFFFF )
		return errno = EINVAL, (Thread*) NULL;
	if ( regs->gsbase >> 48 != 0x0000 && regs->gsbase >> 48 != 0xFFFF )
		return errno = EINVAL, (Thread*) NULL;
#endif

	kthread_mutex_lock(&process->threadlock);

	// Note: Only allow the process itself to make threads, except the initial
	// thread. This requirement is because kthread_exit() needs to know when
	// it's the last thread in the process (using threads_not_exiting_count),
	// and that no more threads will appear, so it can run some final process
	// termination steps without any interference. It's always allowed to create
	// threads in the kernel process as it never exits.
	assert(!process->firstthread ||
	       process == CurrentProcess() ||
	       process == Scheduler::GetKernelProcess());

	Thread* thread = new Thread();
	if ( !thread )
		return NULL;
	thread->name = name;

	memcpy(&thread->registers, regs, sizeof(struct thread_registers));

	// Create the family tree.
	thread->process = process;
	Thread* firsty = process->firstthread;
	if ( firsty )
		firsty->prevsibling = thread;
	thread->nextsibling = firsty;
	process->firstthread = thread;
	process->threads_not_exiting_count++;

	kthread_mutex_unlock(&process->threadlock);

	return thread;
}

static void SetupKernelThreadRegs(struct thread_registers* regs,
                                  Process* process,
                                  void (*entry)(void*),
                                  void* user,
                                  uintptr_t stack,
                                  size_t stack_size)
{
	memset(regs, 0, sizeof(*regs));

	size_t stack_alignment = 16;
	while ( stack & (stack_alignment-1) )
	{
		assert(stack_size);
		stack++;
		stack_size--;
	}

	stack_size &= ~(stack_alignment-1);

#if defined(__i386__)
	uintptr_t* stack_values = (uintptr_t*) (stack + stack_size);

	assert(5 * sizeof(uintptr_t) <= stack_size);

	/* -- 16-byte aligned -- */
	/* -1 padding */
	stack_values[-2] = (uintptr_t) 0; /* null eip */
	stack_values[-3] = (uintptr_t) 0; /* null ebp */
	stack_values[-4] = (uintptr_t) user; /* thread parameter */
	/* -- 16-byte aligned -- */
	stack_values[-5] = (uintptr_t) kthread_exit; /* return to kthread_exit */
	/* upcoming ebp */
	/* -7 padding */
	/* -8 padding */
	/* -- 16-byte aligned -- */

	regs->eip = (uintptr_t) entry;
	regs->esp = (uintptr_t) (stack_values - 5);
	regs->eax = 0;
	regs->ebx = 0;
	regs->ecx = 0;
	regs->edx = 0;
	regs->edi = 0;
	regs->esi = 0;
	regs->ebp = (uintptr_t) (stack_values - 3);
	regs->cs = KCS | KRPL;
	regs->ds = KDS | KRPL;
	regs->ss = KDS | KRPL;
	regs->eflags = FLAGS_RESERVED1 | FLAGS_INTERRUPT | FLAGS_ID;
	regs->kerrno = 0;
	regs->signal_pending = 0;
	regs->kernel_stack = stack + stack_size;
	regs->cr3 = process->addrspace;
	memcpy(regs->fpuenv, Float::fpu_initialized_regs, 512);
#elif defined(__x86_64__)
	uintptr_t* stack_values = (uintptr_t*) (stack + stack_size);

	assert(3 * sizeof(uintptr_t) <= stack_size);

	stack_values[-1] = (uintptr_t) 0; /* null rip */
	stack_values[-2] = (uintptr_t) 0; /* null rbp */
	stack_values[-3] = (uintptr_t) kthread_exit; /* return to kthread_exit */

	regs->rip = (uintptr_t) entry;
	regs->rsp = (uintptr_t) (stack_values - 3);
	regs->rax = 0;
	regs->rbx = 0;
	regs->rcx = 0;
	regs->rdx = 0;
	regs->rdi = (uintptr_t) user;
	regs->rsi = 0;
	regs->rbp = 0;
	regs->r8  = 0;
	regs->r9  = 0;
	regs->r10 = 0;
	regs->r11 = 0;
	regs->r12 = 0;
	regs->r13 = 0;
	regs->r14 = 0;
	regs->r15 = 0;
	regs->cs = KCS | KRPL;
	regs->ds = KDS | KRPL;
	regs->ss = KDS | KRPL;
	regs->rflags = FLAGS_RESERVED1 | FLAGS_INTERRUPT | FLAGS_ID;
	regs->kerrno = 0;
	regs->signal_pending = 0;
	regs->kernel_stack = stack + stack_size;
	regs->cr3 = process->addrspace;
	memcpy(regs->fpuenv, Float::fpu_initialized_regs, 512);
#else
#warning "You need to add kernel thread register initialization support"
#endif
}

Thread* CreateKernelThread(Process* process, void (*entry)(void*), void* user,
                           const char* name, size_t stacksize)
{
	const size_t DEFAULT_KERNEL_STACK_SIZE = 8 * 1024UL;
	if ( !stacksize )
		stacksize = DEFAULT_KERNEL_STACK_SIZE;
	uint8_t* stack = new uint8_t[stacksize];
	if ( !stack )
		return NULL;

	struct thread_registers regs;
	SetupKernelThreadRegs(&regs, process, entry, user, (uintptr_t) stack,
	                      stacksize);

	Thread* thread = CreateKernelThread(process, &regs, name);
	if ( !thread ) { delete[] stack; return NULL; }

	thread->kernelstackpos = (uintptr_t) stack;
	thread->kernelstacksize = stacksize;
	thread->kernelstackmalloced = true;

	return thread;
}

Thread* CreateKernelThread(void (*entry)(void*), void* user, const char* name,
                           size_t stacksize)
{
	return CreateKernelThread(CurrentProcess(), entry, user, name, stacksize);
}

void StartKernelThread(Thread* thread)
{
	Scheduler::SetThreadState(thread, ThreadState::RUNNABLE);
}

Thread* RunKernelThread(Process* process, struct thread_registers* regs,
                        const char* name)
{
	Thread* thread = CreateKernelThread(process, regs, name);
	if ( !thread )
		return NULL;
	StartKernelThread(thread);
	return thread;
}

Thread* RunKernelThread(Process* process, void (*entry)(void*), void* user,
                        const char* name, size_t stacksize)
{
	Thread* thread = CreateKernelThread(process, entry, user, name, stacksize);
	if ( !thread )
		return NULL;
	StartKernelThread(thread);
	return thread;
}

Thread* RunKernelThread(void (*entry)(void*), void* user, const char* name,
                        size_t stacksize)
{
	Thread* thread = CreateKernelThread(entry, user, name, stacksize);
	if ( !thread )
		return NULL;
	StartKernelThread(thread);
	return thread;
}

int sys_exit_thread(int requested_exit_code,
                    int flags,
                    const struct exit_thread* user_extended)
{
	if ( flags & ~(EXIT_THREAD_ONLY_IF_OTHERS |
	               EXIT_THREAD_UNMAP |
	               EXIT_THREAD_ZERO |
	               EXIT_THREAD_TLS_UNMAP |
	               EXIT_THREAD_PROCESS |
	               EXIT_THREAD_DUMP_CORE |
	               EXIT_THREAD_FUTEX_WAKE) )
		return errno = EINVAL, -1;

	if ( (flags & EXIT_THREAD_ONLY_IF_OTHERS) && (flags & EXIT_THREAD_PROCESS) )
		return errno = EINVAL, -1;

	Thread* thread = CurrentThread();
	Process* process = CurrentProcess();

	struct exit_thread extended;
	if ( !user_extended )
		memset(&extended, 0, sizeof(extended));
	else if ( !CopyFromUser(&extended, user_extended, sizeof(extended)) )
		return -1;

	extended.unmap_size = Page::AlignUp(extended.unmap_size);

	kthread_mutex_lock(&thread->process->threadlock);
	bool is_others = false;
	for ( Thread* iter = thread->process->firstthread;
	      !is_others && iter;
	     iter = iter->nextsibling )
	{
		if ( iter == thread )
			continue;
		if ( iter->pledged_destruction )
			continue;
		is_others = true;
	}
	if ( !(flags & EXIT_THREAD_ONLY_IF_OTHERS) || is_others )
		thread->pledged_destruction = true;
	bool are_threads_exiting = false;
	bool do_exit = (flags & EXIT_THREAD_PROCESS) || !is_others;
	if ( do_exit )
		process->threads_exiting = true;
	else if ( process->threads_exiting )
		are_threads_exiting = true;
	kthread_mutex_unlock(&thread->process->threadlock);

	// Self-destruct if another thread began exiting the process.
	if ( are_threads_exiting )
		kthread_exit();

	if ( (flags & EXIT_THREAD_ONLY_IF_OTHERS) && !is_others )
		return errno = ESRCH, -1;

	if ( flags & EXIT_THREAD_UNMAP &&
	     Page::IsAligned((uintptr_t) extended.unmap_from) &&
	     extended.unmap_size )
	{
		ScopedLock lock(&process->segment_lock);
		extended.unmap_size = Page::AlignDown(extended.unmap_size);
		Memory::UnmapMemory(process, (uintptr_t) extended.unmap_from,
		                                         extended.unmap_size);
		Memory::Flush();
		// TODO: The segment is not actually removed!
	}

	if ( flags & EXIT_THREAD_TLS_UNMAP &&
	     Page::IsAligned((uintptr_t) extended.tls_unmap_from) &&
	     extended.tls_unmap_size )
	{
		ScopedLock lock(&process->segment_lock);
		extended.tls_unmap_size = Page::AlignDown(extended.tls_unmap_size);
		Memory::UnmapMemory(process, (uintptr_t) extended.tls_unmap_from,
		                                         extended.tls_unmap_size);
		Memory::Flush();
	}

	if ( flags & EXIT_THREAD_ZERO )
		ZeroUser(extended.zero_from, extended.zero_size);

	if ( flags & EXIT_THREAD_FUTEX_WAKE )
		sys_futex((int*) extended.zero_from, FUTEX_WAKE, 1, NULL);

	if ( do_exit )
	{
		// Validate the requested exit code such that the process can't exit
		// with an impossible exit status or that it wasn't actually terminated.

		int the_nature = WNATURE(requested_exit_code);
		int the_status = WEXITSTATUS(requested_exit_code);
		int the_signal = WTERMSIG(requested_exit_code);

		if ( the_nature == WNATURE_EXITED )
			the_signal = 0;
		else if ( the_nature == WNATURE_SIGNALED )
		{
			if ( the_signal == 0 /* null signal */ ||
			     the_signal == SIGSTOP ||
			     the_signal == SIGTSTP ||
			     the_signal == SIGTTIN ||
			     the_signal == SIGTTOU ||
			     the_signal == SIGCONT )
				the_signal = SIGKILL;
			the_status = 128 + the_signal;
		}
		else
		{
			the_nature = WNATURE_SIGNALED;
			the_signal = SIGKILL;
		}

		requested_exit_code = WCONSTRUCT(the_nature, the_status, the_signal);

		thread->process->ExitWithCode(requested_exit_code);
	}

	kthread_exit();
}

static void futex_timeout(Clock* /*clock*/, Timer* /*timer*/, void* ctx)
{
	Thread* thread = (Thread*) ctx;
	thread->timer_woken = true;
	kthread_wake_futex(thread);
}

int sys_futex(int* user_address,
              int op,
              int value,
              const struct timespec* user_timeout)
{
	ioctx_t ctx; SetupKernelIOCtx(&ctx);
	Thread* thread = CurrentThread();
	Process* process = thread->process;
	if ( FUTEX_GET_OP(op) == FUTEX_WAIT )
	{
		kthread_mutex_lock(&process->futex_lock);
		thread->futex_address = (uintptr_t) user_address;
		thread->futex_woken = false;
		thread->futex_prev_waiting = process->futex_last_waiting;
		thread->futex_next_waiting = NULL;
		(process->futex_last_waiting ?
		 process->futex_last_waiting->futex_next_waiting :
		 process->futex_first_waiting) = thread;
		process->futex_last_waiting = thread;
		kthread_mutex_unlock(&process->futex_lock);
		thread->timer_woken = false;
		Timer timer;
		if ( user_timeout )
		{
			clockid_t clockid = FUTEX_GET_CLOCK(op);
			bool absolute = op & FUTEX_ABSOLUTE;
			struct timespec timeout;
			if ( !CopyFromUser(&timeout, user_timeout, sizeof(timeout)) )
				return -1;
			if ( !timespec_is_canonical(timeout) )
				return errno = EINVAL, -1;
			Clock* clock = Time::GetClock(clockid);
			timer.Attach(clock);
			struct itimerspec timerspec;
			timerspec.it_value = timeout;
			timerspec.it_interval.tv_sec = 0;
			timerspec.it_interval.tv_nsec = 0;
			int timer_flags = (absolute ? TIMER_ABSOLUTE : 0) |
			                  TIMER_FUNC_INTERRUPT_HANDLER;
			timer.Set(&timerspec, NULL, timer_flags, futex_timeout, thread);
		}
		int result = 0;
		int current;
		if ( !ReadAtomicFromUser(&current, user_address) )
			result = -1;
		else if ( current != value )
		{
			errno = EAGAIN;
			result = -1;
		}
		else
			kthread_wait_futex_signal();
		if ( user_timeout )
			timer.Cancel();
		kthread_mutex_lock(&process->futex_lock);
		if ( result == 0 && !thread->futex_woken )
		{
			if ( Signal::IsPending() )
			{
				errno = EINTR;
				result = -1;
			}
			else if ( thread->timer_woken )
			{
				errno = ETIMEDOUT;
				result = -1;
			}
		}
		thread->futex_address = 0;
		thread->futex_woken = false;
		(thread->futex_prev_waiting ?
		 thread->futex_prev_waiting->futex_next_waiting :
		 process->futex_first_waiting) = thread->futex_next_waiting;
		(thread->futex_next_waiting ?
		 thread->futex_next_waiting->futex_prev_waiting :
		 process->futex_last_waiting) = thread->futex_prev_waiting;
		thread->futex_prev_waiting = NULL;
		thread->futex_next_waiting = NULL;
		kthread_mutex_unlock(&process->futex_lock);
		return result;
	}
	else if ( FUTEX_GET_OP(op) == FUTEX_WAKE )
	{
		kthread_mutex_lock(&process->futex_lock);
		int result = 0;
		for ( Thread* waiter = process->futex_first_waiting;
		      0 < value && waiter;
		      waiter = waiter->futex_next_waiting )
		{
			if ( waiter->futex_address == (uintptr_t) user_address )
			{
				waiter->futex_woken = true;
				kthread_wake_futex(waiter);
				if ( value != INT_MAX )
					value--;
				if ( result != INT_MAX )
					result++;
			}
		}
		kthread_mutex_unlock(&process->futex_lock);
		return result;
	}
	else
		return errno = EINVAL, -1;
}

} // namespace Sortix
