/**
 * WinPR: Windows Portable Runtime
 * Process Thread Functions
 *
 * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
 * Copyright 2014 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <winpr/config.h>

#include <winpr/handle.h>
#include "../handle/nonehandle.h"

#include <winpr/thread.h>
#include <winpr/wlog.h>

/**
 * CreateProcessA
 * CreateProcessW
 * CreateProcessAsUserA
 * CreateProcessAsUserW
 * ExitProcess
 * GetCurrentProcess
 * GetCurrentProcessId
 * GetExitCodeProcess
 * GetProcessHandleCount
 * GetProcessId
 * GetProcessIdOfThread
 * GetProcessMitigationPolicy
 * GetProcessTimes
 * GetProcessVersion
 * OpenProcess
 * OpenProcessToken
 * ProcessIdToSessionId
 * SetProcessAffinityUpdateMode
 * SetProcessMitigationPolicy
 * SetProcessShutdownParameters
 * TerminateProcess
 */

#ifndef _WIN32

#include <winpr/assert.h>
#include <winpr/crt.h>
#include <winpr/path.h>
#include <winpr/environment.h>

#include <grp.h>

#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

#ifdef __linux__
#include <sys/syscall.h>
#include <fcntl.h>
#include <errno.h>
#endif /* __linux__ */

#include "thread.h"

#include "../security/security.h"

#ifndef NSIG
#ifdef SIGMAX
#define NSIG SIGMAX
#else
#define NSIG 64
#endif
#endif

/**
 * If the file name does not contain a directory path, the system searches for the executable file
 * in the following sequence:
 *
 * 1) The directory from which the application loaded.
 * 2) The current directory for the parent process.
 * 3) The 32-bit Windows system directory. Use the GetSystemDirectory function to get the path of
 * this directory. 4) The 16-bit Windows system directory. There is no function that obtains the
 * path of this directory, but it is searched. The name of this directory is System. 5) The Windows
 * directory. Use the GetWindowsDirectory function to get the path of this directory. 6) The
 * directories that are listed in the PATH environment variable. Note that this function does not
 * search the per-application path specified by the App Paths registry key. To include this
 * per-application path in the search sequence, use the ShellExecute function.
 */

static char* FindApplicationPath(char* application)
{
	LPCSTR pathName = "PATH";
	char* path = nullptr;
	char* save = nullptr;
	DWORD nSize = 0;
	LPSTR lpSystemPath = nullptr;
	char* filename = nullptr;

	if (!application)
		return nullptr;

	if (application[0] == '/')
		return _strdup(application);

	nSize = GetEnvironmentVariableA(pathName, nullptr, 0);

	if (!nSize)
		return _strdup(application);

	lpSystemPath = (LPSTR)malloc(nSize);

	if (!lpSystemPath)
		return nullptr;

	if (GetEnvironmentVariableA(pathName, lpSystemPath, nSize) != nSize - 1)
	{
		free(lpSystemPath);
		return nullptr;
	}

	save = nullptr;
	path = strtok_s(lpSystemPath, ":", &save);

	while (path)
	{
		filename = GetCombinedPath(path, application);

		if (winpr_PathFileExists(filename))
		{
			break;
		}

		free(filename);
		filename = nullptr;
		path = strtok_s(nullptr, ":", &save);
	}

	free(lpSystemPath);
	return filename;
}

static HANDLE CreateProcessHandle(pid_t pid);
static BOOL ProcessHandleCloseHandle(HANDLE handle);

static BOOL CreateProcessExA(HANDLE hToken, WINPR_ATTR_UNUSED DWORD dwLogonFlags,
                             LPCSTR lpApplicationName, WINPR_ATTR_UNUSED LPSTR lpCommandLine,
                             WINPR_ATTR_UNUSED LPSECURITY_ATTRIBUTES lpProcessAttributes,
                             WINPR_ATTR_UNUSED LPSECURITY_ATTRIBUTES lpThreadAttributes,
                             WINPR_ATTR_UNUSED BOOL bInheritHandles,
                             WINPR_ATTR_UNUSED DWORD dwCreationFlags, LPVOID lpEnvironment,
                             LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo,
                             LPPROCESS_INFORMATION lpProcessInformation)
{
	pid_t pid = 0;
	int numArgs = 0;
	LPSTR* pArgs = nullptr;
	char** envp = nullptr;
	char* filename = nullptr;
	HANDLE thread = nullptr;
	HANDLE process = nullptr;
	WINPR_ACCESS_TOKEN* token = nullptr;
	LPTCH lpszEnvironmentBlock = nullptr;
	BOOL ret = FALSE;
	sigset_t oldSigMask;
	sigset_t newSigMask;
	BOOL restoreSigMask = FALSE;
	numArgs = 0;
	lpszEnvironmentBlock = nullptr;
	/* https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa
	 */
	if (lpCommandLine)
		pArgs = CommandLineToArgvA(lpCommandLine, &numArgs);
	else
		pArgs = CommandLineToArgvA(lpApplicationName, &numArgs);

	if (!pArgs)
		return FALSE;

	token = (WINPR_ACCESS_TOKEN*)hToken;

	if (lpEnvironment)
	{
		envp = EnvironmentBlockToEnvpA(lpEnvironment);
	}
	else
	{
		lpszEnvironmentBlock = GetEnvironmentStrings();

		if (!lpszEnvironmentBlock)
			goto finish;

		envp = EnvironmentBlockToEnvpA(lpszEnvironmentBlock);
	}

	if (!envp)
		goto finish;

	filename = FindApplicationPath(pArgs[0]);

	if (nullptr == filename)
		goto finish;

	/* block all signals so that the child can safely reset the caller's handlers */
	sigfillset(&newSigMask);
	restoreSigMask = !pthread_sigmask(SIG_SETMASK, &newSigMask, &oldSigMask);
	/* fork and exec */
	pid = fork();

	if (pid < 0)
	{
		/* fork failure */
		goto finish;
	}

	if (pid == 0)
	{
		/* child process */
#ifndef __sun
		int maxfd = 0;
#endif
		sigset_t set = WINPR_C_ARRAY_INIT;
		struct sigaction act = WINPR_C_ARRAY_INIT;
		/* set default signal handlers */
		act.sa_handler = SIG_DFL;
		act.sa_flags = 0;
		sigemptyset(&act.sa_mask);

		for (int sig = 1; sig < NSIG; sig++)
			sigaction(sig, &act, nullptr);

		/* unblock all signals */
		sigfillset(&set);
		pthread_sigmask(SIG_UNBLOCK, &set, nullptr);

		if (lpStartupInfo)
		{
			int handle_fd = 0;
			handle_fd = winpr_Handle_getFd(lpStartupInfo->hStdOutput);

			if (handle_fd != -1)
				dup2(handle_fd, STDOUT_FILENO);

			handle_fd = winpr_Handle_getFd(lpStartupInfo->hStdError);

			if (handle_fd != -1)
				dup2(handle_fd, STDERR_FILENO);

			handle_fd = winpr_Handle_getFd(lpStartupInfo->hStdInput);

			if (handle_fd != -1)
				dup2(handle_fd, STDIN_FILENO);
		}

#ifdef __sun
		closefrom(3);
#else
#ifdef F_MAXFD // on some BSD derivates
		maxfd = fcntl(0, F_MAXFD);
#else
		{
			const long rc = sysconf(_SC_OPEN_MAX);
			if ((rc < INT32_MIN) || (rc > INT32_MAX))
				goto finish;
			maxfd = (int)rc;
		}
#endif

		for (int fd = 3; fd < maxfd; fd++)
			close(fd);

#endif // __sun

		if (token)
		{
			if (token->GroupId)
			{
				int rc = setgid((gid_t)token->GroupId);

				if (rc < 0)
				{
				}
				else
				{
					initgroups(token->Username, (gid_t)token->GroupId);
				}
			}

			if (token->UserId)
			{
				int rc = setuid((uid_t)token->UserId);
				if (rc != 0)
					goto finish;
			}
		}

		/* TODO: add better cwd handling and error checking */
		if (lpCurrentDirectory && strlen(lpCurrentDirectory) > 0)
		{
			int rc = chdir(lpCurrentDirectory);
			if (rc != 0)
				goto finish;
		}

		if (execve(filename, pArgs, envp) < 0)
		{
			/* execve failed - end the process */
			_exit(1);
		}
	}
	else
	{
		/* parent process */
	}

	process = CreateProcessHandle(pid);

	if (!process)
	{
		goto finish;
	}

	thread = CreateNoneHandle();

	if (!thread)
	{
		ProcessHandleCloseHandle(process);
		goto finish;
	}

	lpProcessInformation->hProcess = process;
	lpProcessInformation->hThread = thread;
	lpProcessInformation->dwProcessId = (DWORD)pid;
	lpProcessInformation->dwThreadId = (DWORD)pid;
	ret = TRUE;
finish:

	/* restore caller's original signal mask */
	if (restoreSigMask)
		pthread_sigmask(SIG_SETMASK, &oldSigMask, nullptr);

	free(filename);
	free((void*)pArgs);

	if (lpszEnvironmentBlock)
		FreeEnvironmentStrings(lpszEnvironmentBlock);

	if (envp)
	{
		int i = 0;

		while (envp[i])
		{
			free(envp[i]);
			i++;
		}

		free((void*)envp);
	}

	return ret;
}

BOOL CreateProcessA(LPCSTR lpApplicationName, LPSTR lpCommandLine,
                    LPSECURITY_ATTRIBUTES lpProcessAttributes,
                    LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles,
                    DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory,
                    LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation)
{
	return CreateProcessExA(nullptr, 0, lpApplicationName, lpCommandLine, lpProcessAttributes,
	                        lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment,
	                        lpCurrentDirectory, lpStartupInfo, lpProcessInformation);
}

BOOL CreateProcessW(WINPR_ATTR_UNUSED LPCWSTR lpApplicationName,
                    WINPR_ATTR_UNUSED LPWSTR lpCommandLine,
                    WINPR_ATTR_UNUSED LPSECURITY_ATTRIBUTES lpProcessAttributes,
                    WINPR_ATTR_UNUSED LPSECURITY_ATTRIBUTES lpThreadAttributes,
                    WINPR_ATTR_UNUSED BOOL bInheritHandles, WINPR_ATTR_UNUSED DWORD dwCreationFlags,
                    WINPR_ATTR_UNUSED LPVOID lpEnvironment,
                    WINPR_ATTR_UNUSED LPCWSTR lpCurrentDirectory,
                    WINPR_ATTR_UNUSED LPSTARTUPINFOW lpStartupInfo,
                    WINPR_ATTR_UNUSED LPPROCESS_INFORMATION lpProcessInformation)
{
	WLog_ERR("TODO", "TODO: implement");
	return FALSE;
}

BOOL CreateProcessAsUserA(HANDLE hToken, LPCSTR lpApplicationName, LPSTR lpCommandLine,
                          LPSECURITY_ATTRIBUTES lpProcessAttributes,
                          LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles,
                          DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory,
                          LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation)
{
	return CreateProcessExA(hToken, 0, lpApplicationName, lpCommandLine, lpProcessAttributes,
	                        lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment,
	                        lpCurrentDirectory, lpStartupInfo, lpProcessInformation);
}

BOOL CreateProcessAsUserW(WINPR_ATTR_UNUSED HANDLE hToken,
                          WINPR_ATTR_UNUSED LPCWSTR lpApplicationName,
                          WINPR_ATTR_UNUSED LPWSTR lpCommandLine,
                          WINPR_ATTR_UNUSED LPSECURITY_ATTRIBUTES lpProcessAttributes,
                          WINPR_ATTR_UNUSED LPSECURITY_ATTRIBUTES lpThreadAttributes,
                          WINPR_ATTR_UNUSED BOOL bInheritHandles,
                          WINPR_ATTR_UNUSED DWORD dwCreationFlags,
                          WINPR_ATTR_UNUSED LPVOID lpEnvironment,
                          WINPR_ATTR_UNUSED LPCWSTR lpCurrentDirectory,
                          WINPR_ATTR_UNUSED LPSTARTUPINFOW lpStartupInfo,
                          WINPR_ATTR_UNUSED LPPROCESS_INFORMATION lpProcessInformation)
{
	WLog_ERR("TODO", "TODO: implement");
	return FALSE;
}

BOOL CreateProcessWithLogonA(
    WINPR_ATTR_UNUSED LPCSTR lpUsername, WINPR_ATTR_UNUSED LPCSTR lpDomain,
    WINPR_ATTR_UNUSED LPCSTR lpPassword, WINPR_ATTR_UNUSED DWORD dwLogonFlags,
    WINPR_ATTR_UNUSED LPCSTR lpApplicationName, WINPR_ATTR_UNUSED LPSTR lpCommandLine,
    WINPR_ATTR_UNUSED DWORD dwCreationFlags, WINPR_ATTR_UNUSED LPVOID lpEnvironment,
    WINPR_ATTR_UNUSED LPCSTR lpCurrentDirectory, WINPR_ATTR_UNUSED LPSTARTUPINFOA lpStartupInfo,
    WINPR_ATTR_UNUSED LPPROCESS_INFORMATION lpProcessInformation)
{
	WLog_ERR("TODO", "TODO: implement");
	return FALSE;
}

BOOL CreateProcessWithLogonW(
    WINPR_ATTR_UNUSED LPCWSTR lpUsername, WINPR_ATTR_UNUSED LPCWSTR lpDomain,
    WINPR_ATTR_UNUSED LPCWSTR lpPassword, WINPR_ATTR_UNUSED DWORD dwLogonFlags,
    WINPR_ATTR_UNUSED LPCWSTR lpApplicationName, WINPR_ATTR_UNUSED LPWSTR lpCommandLine,
    WINPR_ATTR_UNUSED DWORD dwCreationFlags, WINPR_ATTR_UNUSED LPVOID lpEnvironment,
    WINPR_ATTR_UNUSED LPCWSTR lpCurrentDirectory, WINPR_ATTR_UNUSED LPSTARTUPINFOW lpStartupInfo,
    WINPR_ATTR_UNUSED LPPROCESS_INFORMATION lpProcessInformation)
{
	WLog_ERR("TODO", "TODO: implement");
	return FALSE;
}

BOOL CreateProcessWithTokenA(WINPR_ATTR_UNUSED HANDLE hToken, WINPR_ATTR_UNUSED DWORD dwLogonFlags,
                             LPCSTR lpApplicationName, LPSTR lpCommandLine, DWORD dwCreationFlags,
                             LPVOID lpEnvironment, LPCSTR lpCurrentDirectory,
                             LPSTARTUPINFOA lpStartupInfo,
                             LPPROCESS_INFORMATION lpProcessInformation)
{
	return CreateProcessExA(nullptr, 0, lpApplicationName, lpCommandLine, nullptr, nullptr, FALSE,
	                        dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo,
	                        lpProcessInformation);
}

BOOL CreateProcessWithTokenW(WINPR_ATTR_UNUSED HANDLE hToken, WINPR_ATTR_UNUSED DWORD dwLogonFlags,
                             WINPR_ATTR_UNUSED LPCWSTR lpApplicationName,
                             WINPR_ATTR_UNUSED LPWSTR lpCommandLine,
                             WINPR_ATTR_UNUSED DWORD dwCreationFlags,
                             WINPR_ATTR_UNUSED LPVOID lpEnvironment,
                             WINPR_ATTR_UNUSED LPCWSTR lpCurrentDirectory,
                             WINPR_ATTR_UNUSED LPSTARTUPINFOW lpStartupInfo,
                             WINPR_ATTR_UNUSED LPPROCESS_INFORMATION lpProcessInformation)
{
	WLog_ERR("TODO", "TODO: implement");
	return FALSE;
}

VOID ExitProcess(UINT uExitCode)
{
	// NOLINTNEXTLINE(concurrency-mt-unsafe)
	exit((int)uExitCode);
}

BOOL GetExitCodeProcess(HANDLE hProcess, LPDWORD lpExitCode)
{
	WINPR_PROCESS* process = nullptr;

	if (!hProcess)
		return FALSE;

	if (!lpExitCode)
		return FALSE;

	process = (WINPR_PROCESS*)hProcess;
	*lpExitCode = process->dwExitCode;
	return TRUE;
}

HANDLE _GetCurrentProcess(VOID)
{
	WLog_ERR("TODO", "TODO: implement");
	return nullptr;
}

DWORD GetCurrentProcessId(VOID)
{
	return ((DWORD)getpid());
}

BOOL TerminateProcess(HANDLE hProcess, WINPR_ATTR_UNUSED UINT uExitCode)
{
	WINPR_PROCESS* process = nullptr;
	process = (WINPR_PROCESS*)hProcess;

	if (!process || (process->pid <= 0))
		return FALSE;

	if (kill(process->pid, SIGTERM))
		return FALSE;

	return TRUE;
}

static BOOL ProcessHandleCloseHandle(HANDLE handle)
{
	WINPR_PROCESS* process = (WINPR_PROCESS*)handle;
	WINPR_ASSERT(process);
	if (process->fd >= 0)
	{
		close(process->fd);
		process->fd = -1;
	}
	free(process);
	return TRUE;
}

static BOOL ProcessHandleIsHandle(HANDLE handle)
{
	return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_PROCESS, FALSE);
}

static int ProcessGetFd(HANDLE handle)
{
	WINPR_PROCESS* process = (WINPR_PROCESS*)handle;

	if (!ProcessHandleIsHandle(handle))
		return -1;

	return process->fd;
}

static DWORD ProcessCleanupHandle(HANDLE handle)
{
	WINPR_PROCESS* process = (WINPR_PROCESS*)handle;

	WINPR_ASSERT(process);
	if (process->fd > 0)
	{
		if (waitpid(process->pid, &process->status, WNOHANG) == process->pid)
			process->dwExitCode = (DWORD)process->status;
	}
	return WAIT_OBJECT_0;
}

static HANDLE_OPS ops = { ProcessHandleIsHandle,
	                      ProcessHandleCloseHandle,
	                      ProcessGetFd,
	                      ProcessCleanupHandle, /* CleanupHandle */
	                      nullptr,
	                      nullptr,
	                      nullptr,
	                      nullptr,
	                      nullptr,
	                      nullptr,
	                      nullptr,
	                      nullptr,
	                      nullptr,
	                      nullptr,
	                      nullptr,
	                      nullptr,
	                      nullptr,
	                      nullptr,
	                      nullptr,
	                      nullptr,
	                      nullptr };

static int pidfd_open(pid_t pid)
{
#ifdef __linux__
#if !defined(__NR_pidfd_open)
#define __NR_pidfd_open 434
#endif /* __NR_pidfd_open */

#ifndef PIDFD_NONBLOCK
#define PIDFD_NONBLOCK O_NONBLOCK
#endif /* PIDFD_NONBLOCK */

	long fd = syscall(__NR_pidfd_open, pid, PIDFD_NONBLOCK);
	if (fd < 0 && errno == EINVAL)
	{
		/* possibly PIDFD_NONBLOCK is not supported, let's try to create a pidfd and set it
		 * non blocking afterward */
		int flags = 0;
		fd = syscall(__NR_pidfd_open, pid, 0);
		if ((fd < 0) || (fd > INT32_MAX))
			return -1;

		flags = fcntl((int)fd, F_GETFL);
		if ((flags < 0) || fcntl((int)fd, F_SETFL, flags | O_NONBLOCK) < 0)
		{
			close((int)fd);
			fd = -1;
		}
	}
	if ((fd < 0) || (fd > INT32_MAX))
		return -1;
	return (int)fd;
#else
	return -1;
#endif
}

HANDLE CreateProcessHandle(pid_t pid)
{
	WINPR_PROCESS* process = nullptr;
	process = (WINPR_PROCESS*)calloc(1, sizeof(WINPR_PROCESS));

	if (!process)
		return nullptr;

	process->pid = pid;
	process->common.Type = HANDLE_TYPE_PROCESS;
	process->common.ops = &ops;
	process->fd = pidfd_open(pid);
	if (process->fd >= 0)
		process->common.Mode = WINPR_FD_READ;
	return (HANDLE)process;
}

#endif
