/*
 * libwebsockets - small server side websockets and web server implementation
 *
 * Copyright (C) 2010 - 2020 Andy Green <andy@warmcat.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include "private-lib-core.h"

#include <tchar.h>
#include <stdio.h>
#include <strsafe.h>
#include <Psapi.h>

#ifndef EXTENDED_STARTUPINFO_PRESENT
#define EXTENDED_STARTUPINFO_PRESENT 0x00080000
#endif

#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
#define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x00020016
#endif

typedef VOID* HPCON;
typedef HRESULT (WINAPI *PFN_CREATE_PSEUDO_CONSOLE)(COORD, HANDLE, HANDLE, DWORD, HPCON*);
typedef VOID (WINAPI *PFN_CLOSE_PSEUDO_CONSOLE)(HPCON);
typedef BOOL (WINAPI *PFN_INITIALIZE_PROC_THREAD_ATTRIBUTE_LIST)(LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD, PSIZE_T);
typedef BOOL (WINAPI *PFN_UPDATE_PROC_THREAD_ATTRIBUTE)(LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD_PTR, PVOID, SIZE_T, PVOID, PSIZE_T);
typedef VOID (WINAPI *PFN_DELETE_PROC_THREAD_ATTRIBUTE_LIST)(LPPROC_THREAD_ATTRIBUTE_LIST);

void
lws_spawn_timeout(struct lws_sorted_usec_list *sul)
{
	struct lws_spawn_piped *lsp = lws_container_of(sul,
					struct lws_spawn_piped, sul);

	lwsl_warn("%s: spawn exceeded timeout, killing\n", __func__);

	lws_spawn_piped_kill_child_process(lsp);
}

void
lws_spawn_sul_reap(struct lws_sorted_usec_list *sul)
{
	struct lws_spawn_piped *lsp = lws_container_of(sul,
					struct lws_spawn_piped, sul_reap);

       lwsl_info("%s: reaping spawn after last stdpipe, tries left %d\n",
		    __func__, lsp->reap_retry_budget);
	if (!lws_spawn_reap(lsp) && !lsp->pipes_alive) {
		if (--lsp->reap_retry_budget) {
			lws_sul_schedule(lsp->info.vh->context, lsp->info.tsi,
					 &lsp->sul_reap, lws_spawn_sul_reap,
					 250 * LWS_US_PER_MS);
		} else {
			lwsl_err("%s: Unable to reap lsp %p, killing\n",
				 __func__, lsp);
			lsp->reap_retry_budget = 20;
			lws_spawn_piped_kill_child_process(lsp);
		}
	}
}

static struct lws *
lws_create_basic_wsi(struct lws_context *context, int tsi,
		     const struct lws_role_ops *ops)
{
	struct lws_context_per_thread *pt = &context->pt[tsi];
	struct lws *new_wsi;

	if (!context->vhost_list)
		return NULL;

	if ((unsigned int)context->pt[tsi].fds_count ==
	    context->fd_limit_per_thread - 1) {
		lwsl_err("no space for new conn\n");
		return NULL;
	}

	lws_context_lock(context, __func__);
	new_wsi = __lws_wsi_create_with_role(context, tsi, ops, NULL);
	lws_context_unlock(context);
	if (new_wsi == NULL) {
		lwsl_err("Out of memory for new connection\n");
		return NULL;
	}

	new_wsi->rxflow_change_to = LWS_RXFLOW_ALLOW;

	/* initialize the instance struct */

	lws_role_transition(new_wsi, 0, LRS_ESTABLISHED, ops);

	new_wsi->hdr_parsing_completed = 0;
	new_wsi->position_in_fds_table = LWS_NO_FDS_POS;

	/*
	 * these can only be set once the protocol is known
	 * we set an unestablished connection's protocol pointer
	 * to the start of the defauly vhost supported list, so it can look
	 * for matching ones during the handshake
	 */

	new_wsi->user_space = NULL;
	new_wsi->desc.sockfd = LWS_SOCK_INVALID;

	return new_wsi;
}

void
lws_spawn_piped_destroy(struct lws_spawn_piped **_lsp)
{
	struct lws_spawn_piped *lsp = *_lsp;
	struct lws *wsi;
	int n;

	if (!lsp)
		return;

	if (lsp->hJob) {
		CloseHandle(lsp->hJob);
		lsp->hJob = NULL;
	}

	if (lsp->hPC) {
		HMODULE hKernel32 = GetModuleHandleW(L"kernel32.dll");
		PFN_CLOSE_PSEUDO_CONSOLE pClosePseudoConsole = NULL;
		if (hKernel32) {
			pClosePseudoConsole = (PFN_CLOSE_PSEUDO_CONSOLE)GetProcAddress(hKernel32, "ClosePseudoConsole");
			if (pClosePseudoConsole)
				pClosePseudoConsole(lsp->hPC);
		}
		lsp->hPC = NULL;
	}

	for (n = 0; n < 3; n++) {
		if (lsp->pipe_fds[n][!!(n == 0)]) {
			CloseHandle(lsp->pipe_fds[n][n == 0]);
			lsp->pipe_fds[n][n == 0] = NULL;
		}

		for (n = 0; n < 3; n++) {
			if (lsp->stdwsi[n]) {
				lwsl_notice("%s: closing stdwsi %d\n", __func__, n);
				wsi = lsp->stdwsi[n];
				lsp->stdwsi[n]->desc.filefd = NULL;
				lsp->stdwsi[n] = NULL;
				lws_set_timeout(wsi, 1, LWS_TO_KILL_SYNC);
			}
		}
	}

	lws_dll2_remove(&lsp->dll);

	lws_sul_cancel(&lsp->sul);
	lws_sul_cancel(&lsp->sul_reap);
	lws_sul_cancel(&lsp->sul_poll);

	lwsl_warn("%s: deleting lsp\n", __func__);

	lws_free_set_NULL((*_lsp));
}

int
lws_spawn_reap(struct lws_spawn_piped *lsp)
{
	lws_spawn_resource_us_t res = { };
	void *opaque = lsp->info.opaque;
	lsp_cb_t cb = lsp->info.reap_cb;
	PROCESS_MEMORY_COUNTERS pmc;
	struct _lws_siginfo_t lsi;
	ULARGE_INTEGER uli;
	FILETIME ftk, ftu;
	DWORD ex;

	if (!lsp->child_pid)
		return 0;

	if (!GetExitCodeProcess(lsp->child_pid, &ex)) {
               lwsl_notice("%s: GetExitCodeProcess failed, GetLastError: 0x%lx\n", __func__, (unsigned long)GetLastError());
		return 0;
	}

	/* nonzero = success */

	if (ex == STILL_ACTIVE) {
		lwsl_notice("%s: still active\n", __func__);
		return 0;
	}

	/* mark the earliest time we knew he had gone */
	if (!lsp->reaped) {
		lsp->reaped = lws_now_usecs();

		/*
		 * Switch the timeout to restrict the amount of grace time
		 * to drain stdwsi
		 */

		lws_sul_schedule(lsp->info.vh->context, lsp->info.tsi,
				 &lsp->sul, lws_spawn_timeout,
				 5 * LWS_US_PER_SEC);
	}

	/*
	 * Stage finalizing our reaction to the process going down until the
	 * stdwsi flushed whatever is in flight and all noticed they were
	 * closed.  For that reason, each stdwsi close must call lws_spawn_reap
	 * to check if that was the last one and we can proceed with the reap.
	 */

	if (!lsp->ungraceful && lsp->pipes_alive) {
		lwsl_notice("%s: stdwsi alive, not reaping\n", __func__);
		return 0;
	}

	/* we reached the reap point, no need for timeout wait */

	lws_sul_cancel(&lsp->sul);

	/*
	 * All the stdwsi went down, nothing more is coming... it's over
	 * Collect the final information and then reap the dead process
	 */

	if (GetProcessTimes(lsp->child_pid, &lsp->ft_create, &lsp->ft_exit,
			    &ftk, &ftu)) {
		uli.LowPart = ftu.dwLowDateTime;
		uli.HighPart = ftu.dwHighDateTime;
		lsp->res.us_cpu_user = uli.QuadPart / 10;
		if (lsp->info.res)
			lsp->info.res->us_cpu_user = lsp->res.us_cpu_user;

		uli.LowPart = ftk.dwLowDateTime;
		uli.HighPart = ftk.dwHighDateTime;
		lsp->res.us_cpu_sys = uli.QuadPart / 10;
		if (lsp->info.res)
			lsp->info.res->us_cpu_sys = lsp->res.us_cpu_sys;
	}

	if (lsp->hJob) {
		JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli;
		if (QueryInformationJobObject(lsp->hJob, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli), NULL)) {
			lsp->res.peak_mem_rss = (uint64_t)jeli.PeakJobMemoryUsed;
			if (lsp->info.res)
				lsp->info.res->peak_mem_rss = lsp->res.peak_mem_rss;
		}
	} else if (GetProcessMemoryInfo(lsp->child_pid, &pmc, sizeof(pmc))) {
		lsp->res.peak_mem_rss = pmc.PeakWorkingSetSize;
		if (lsp->info.res)
			lsp->info.res->peak_mem_rss = lsp->res.peak_mem_rss;
	}

	lsi.retcode = 0x10000 | (int)ex;
	lwsl_notice("%s: process exit 0x%x\n", __func__, lsi.retcode);
	lsp->child_pid = NULL;

	if (lsp->info.res)
		res = *lsp->info.res;
	else
		res = lsp->res;

	/* destroy the lsp itself first (it's freed and plsp set NULL */

	if (lsp->info.plsp)
		lws_spawn_piped_destroy(lsp->info.plsp);

	/* then do the parent callback informing it's destroyed */

	if (cb)
		cb(opaque, &res, &lsi, 0);

	lwsl_notice("%s: completed reap\n", __func__);

	return 1; /* was reaped */
}

int
lws_spawn_piped_kill_child_process(struct lws_spawn_piped *lsp)
{
	if (!lsp->child_pid)
		return 1;

	lsp->ungraceful = 1; /* don't wait for flushing, just kill it */

	lwsl_info("%s: calling TerminateProcess on child pid\n", __func__);
       if (!TerminateProcess(lsp->child_pid, 252)) {
               lwsl_warn("%s: TerminateProcess failed: 0x%lx\n", __func__, (unsigned long)GetLastError());
               return 0;
       }

	return 0;
}

static void
windows_pipe_poll_hack(lws_sorted_usec_list_t *sul)
{
	struct lws_spawn_piped *lsp = lws_container_of(sul,
					struct lws_spawn_piped, sul_poll);
	struct lws *wsi, *wsi1;
	DWORD br;
	char c;

	/*
	 * Do it first, we know lsp exists and if it's destroyed inbetweentimes,
	 * it will already have cancelled this
	 */

	lws_sul_schedule(lsp->context, 0, &lsp->sul_poll,
			 windows_pipe_poll_hack, 50 * LWS_US_PER_MS);

	if (lsp->child_pid) {
		DWORD ex = STILL_ACTIVE;
		if (GetExitCodeProcess(lsp->child_pid, &ex) && ex != STILL_ACTIVE) {
			if (lsp->hPC) {
				HMODULE hKernel32 = GetModuleHandleW(L"kernel32.dll");
				PFN_CLOSE_PSEUDO_CONSOLE pClosePseudoConsole = NULL;
				if (hKernel32) {
					pClosePseudoConsole = (PFN_CLOSE_PSEUDO_CONSOLE)GetProcAddress(hKernel32, "ClosePseudoConsole");
					if (pClosePseudoConsole)
						pClosePseudoConsole(lsp->hPC);
				}
				lsp->hPC = NULL;
			}
		}
	}

	wsi = lsp->stdwsi[LWS_STDOUT];
	wsi1 = lsp->stdwsi[LWS_STDERR];
	if (wsi && lsp->pipe_fds[LWS_STDOUT][0] != NULL) {
		if (!PeekNamedPipe(lsp->pipe_fds[LWS_STDOUT][0], &c, 1, &br,
				   NULL, NULL)) {

			// lwsl_notice("%s: stdout pipe errored\n", __func__);
			CloseHandle(lsp->stdwsi[LWS_STDOUT]->desc.filefd);
			lsp->pipe_fds[LWS_STDOUT][0] = NULL;
			lsp->stdwsi[LWS_STDOUT]->desc.filefd = NULL;
			lsp->stdwsi[LWS_STDOUT] = NULL;
			lws_set_timeout(wsi, 1, LWS_TO_KILL_SYNC);

			if (lsp->stdwsi[LWS_STDIN]) {
                               lwsl_info("%s: closing stdin from stdout close\n",
						__func__);
				CloseHandle(lsp->stdwsi[LWS_STDIN]->desc.filefd);
				wsi = lsp->stdwsi[LWS_STDIN];
				lsp->stdwsi[LWS_STDIN]->desc.filefd = NULL;
				lsp->stdwsi[LWS_STDIN] = NULL;
				lsp->pipe_fds[LWS_STDIN][1] = NULL;
				lws_set_timeout(wsi, 1, LWS_TO_KILL_SYNC);
			}

			/*
			 * lsp may be destroyed by here... if we wanted to
			 * handle a still-extant stderr we'll get it next time
			 */
		} else if (br) {
			wsi->a.protocol->callback(wsi, LWS_CALLBACK_RAW_RX_FILE,
						wsi->user_space, NULL, 0);
		}
	}

	/*
	 * lsp may have been destroyed above
	 */

	if (wsi1 && lsp->pipe_fds[LWS_STDERR][0]) {
		if (!PeekNamedPipe(lsp->pipe_fds[LWS_STDERR][0], &c, 1, &br,
				   NULL, NULL)) {

                       lwsl_info("%s: stderr pipe errored\n", __func__);
			CloseHandle(wsi1->desc.filefd);
			/*
			 * Assume is stderr still extant on entry, lsp can't
			 * have been destroyed by stdout/stdin processing
			 */
			lsp->stdwsi[LWS_STDERR]->desc.filefd = NULL;
			lsp->stdwsi[LWS_STDERR] = NULL;
			lsp->pipe_fds[LWS_STDERR][0] = NULL;
			lws_set_timeout(wsi1, 1, LWS_TO_KILL_SYNC);
			/*
			 * lsp may have been destroyed above
			 */
		} else if (br) {
			wsi1->a.protocol->callback(wsi1, LWS_CALLBACK_RAW_RX_FILE,
						wsi1->user_space, NULL, 0);
		}
	}
}



/*
 * Deals with spawning a subprocess and executing it securely with stdin/out/err
 * diverted into pipes
 */

struct lws_spawn_piped *
lws_spawn_piped(const struct lws_spawn_piped_info *i)
{
	const struct lws_protocols *pcol = i->vh->context->vhost_list->protocols;
	struct lws_context *context = i->vh->context;
	struct lws_spawn_piped *lsp;
	PROCESS_INFORMATION pi;
	SECURITY_ATTRIBUTES sa;
	char cli[300], *p;
	STARTUPINFO si;
	int n;

	if (i->protocol_name)
		pcol = lws_vhost_name_to_protocol(i->vh, i->protocol_name);
	if (!pcol) {
		lwsl_err("%s: unknown protocol %s\n", __func__,
			 i->protocol_name ? i->protocol_name : "default");

		return NULL;
	}

	lsp = lws_zalloc(sizeof(*lsp), __func__);
	if (!lsp) {
		lwsl_err("%s: OOM\n", __func__);
		return NULL;
	}

	/* wholesale take a copy of info */
	lsp->info = *i;
	lsp->context = context;
	lsp->reap_retry_budget = 20;

	/*
	 * Prepare the stdin / out / err pipes
	 */

	for (n = 0; n < 3; n++) {
		lsp->pipe_fds[n][0] = NULL;
		lsp->pipe_fds[n][1] = NULL;
	}

	/* create pipes for [stdin|stdout] and [stderr] */

	memset(&sa, 0, sizeof(sa));
	sa.nLength = sizeof(SECURITY_ATTRIBUTES);
	sa.bInheritHandle = TRUE; /* inherit the pipes */
	sa.lpSecurityDescriptor = NULL;

	for (n = 0; n < 3; n++) {
		if (i->pty_mode && n == LWS_STDERR) {
			/* fuse stderr to stdout for pty */
			lsp->pipe_fds[n][0] = NULL;
			lsp->pipe_fds[n][1] = lsp->pipe_fds[LWS_STDOUT][1];
		} else {
			DWORD waitmode = PIPE_NOWAIT;

			if (!CreatePipe(&lsp->pipe_fds[n][0], &lsp->pipe_fds[n][1],
					&sa, 0)) {
				lwsl_err("%s: CreatePipe() failed\n", __func__);
				goto bail1;
			}

			if (n != LWS_STDIN)
				SetNamedPipeHandleState(lsp->pipe_fds[n][0], &waitmode, NULL, NULL);

			/* don't inherit the pipe side that belongs to the parent */

			if (!SetHandleInformation(&lsp->pipe_fds[n][!n],
						  HANDLE_FLAG_INHERIT, 0)) {
				// lwsl_info("%s: SetHandleInformation() failed\n", __func__);
			}
		}
	}

	/* create wsis for each stdin/out/err fd */

	for (n = 0; n < 3; n++) {
		lsp->stdwsi[n] = lws_create_basic_wsi(i->vh->context, i->tsi,
					  i->ops ? i->ops : &role_ops_raw_file);
		if (!lsp->stdwsi[n]) {
			lwsl_err("%s: unable to create lsp stdwsi\n", __func__);
			goto bail2;
		}

                __lws_lc_tag(i->vh->context, &i->vh->context->lcg[LWSLCG_WSI],
                	     &lsp->stdwsi[n]->lc, "nspawn-stdwsi-%d", n);

		lsp->stdwsi[n]->lsp_channel = n;
		lws_vhost_bind_wsi(i->vh, lsp->stdwsi[n]);
		lsp->stdwsi[n]->a.protocol = pcol;
		lsp->stdwsi[n]->a.opaque_user_data = i->opaque;

		if (!lsp->pipe_fds[n][!n])
			continue;

		lsp->stdwsi[n]->desc.filefd = lsp->pipe_fds[n][!n];
		lsp->stdwsi[n]->file_desc = 1;

		lws_dll2_remove(&lsp->stdwsi[n]->pre_natal);

		lwsl_debug("%s: lsp stdwsi %p: pipe idx %d -> fd %p / %p\n",
			   __func__, lsp->stdwsi[n], n,
			   lsp->pipe_fds[n][!!(n == 0)],
			   lsp->pipe_fds[n][!(n == 0)]);

#if 0

		/* read side is 0, stdin we want the write side, others read */

		lsp->stdwsi[n]->desc.filefd = lsp->pipe_fds[n][!!(n == 0)];
		if (fcntl(lsp->pipe_fds[n][!!(n == 0)], F_SETFL, O_NONBLOCK) < 0) {
			lwsl_err("%s: setting NONBLOCK failed\n", __func__);
			goto bail2;
		}
#endif
	}

	for (n = 0; n < 3; n++)
		if (i->opt_parent) {
			lsp->stdwsi[n]->parent = i->opt_parent;
			lsp->stdwsi[n]->sibling_list = i->opt_parent->child_list;
			i->opt_parent->child_list = lsp->stdwsi[n];
		}

	// lwsl_notice("%s: pipe handles in %p, out %p, err %p\n", __func__,
	//	   lsp->stdwsi[LWS_STDIN]->desc.sockfd,
	//	   lsp->stdwsi[LWS_STDOUT]->desc.sockfd,
	//	   lsp->stdwsi[LWS_STDERR]->desc.sockfd);

	/*
	 * Windows nonblocking pipe handling is a mess that is unable
	 * to interoperate with WSA-based wait as far as I can tell.
	 *
	 * Let's set up a sul to poll the pipes and synthesize the
	 * protocol callbacks if anything coming.
	 */
	lws_sul_schedule(context, 0, &lsp->sul_poll, windows_pipe_poll_hack,
			 50 * LWS_US_PER_MS);


	/*
	 * Windows wants a single string commandline
	 */
	p = cli;
	n = 0;
	while (i->exec_array[n]) {
		lws_strncpy(p, i->exec_array[n],
			    sizeof(cli) - lws_ptr_diff(p, cli));
		if (sizeof(cli) - lws_ptr_diff(p, cli) < 4)
			break;
		p += strlen(p);
		*p++ = ' ';
		*p = '\0';
		n++;
	}

	if (p > cli && p[-1] == ' ')
		*(--p) = '\0';
	// puts(cli);

	STARTUPINFOEXA siex;
	STARTUPINFOA *psi;
	HMODULE hKernel32;
	PFN_CREATE_PSEUDO_CONSOLE pCreatePseudoConsole = NULL;
	PFN_INITIALIZE_PROC_THREAD_ATTRIBUTE_LIST pInitializeProcThreadAttributeList = NULL;
	PFN_UPDATE_PROC_THREAD_ATTRIBUTE pUpdateProcThreadAttribute = NULL;
	PFN_DELETE_PROC_THREAD_ATTRIBUTE_LIST pDeleteProcThreadAttributeList = NULL;
	SIZE_T attr_list_size = 0;
	DWORD creation_flags = CREATE_SUSPENDED;
	int pty_active = 0;

	memset(&pi, 0, sizeof(pi));
	memset(&si, 0, sizeof(si));
	memset(&siex, 0, sizeof(siex));

	if (i->pty_mode) {
		hKernel32 = GetModuleHandleW(L"kernel32.dll");
		if (hKernel32) {
			pCreatePseudoConsole = (PFN_CREATE_PSEUDO_CONSOLE)GetProcAddress(hKernel32, "CreatePseudoConsole");
			pInitializeProcThreadAttributeList = (PFN_INITIALIZE_PROC_THREAD_ATTRIBUTE_LIST)GetProcAddress(hKernel32, "InitializeProcThreadAttributeList");
			pUpdateProcThreadAttribute = (PFN_UPDATE_PROC_THREAD_ATTRIBUTE)GetProcAddress(hKernel32, "UpdateProcThreadAttribute");
			pDeleteProcThreadAttributeList = (PFN_DELETE_PROC_THREAD_ATTRIBUTE_LIST)GetProcAddress(hKernel32, "DeleteProcThreadAttributeList");
		}

		if (pCreatePseudoConsole && pInitializeProcThreadAttributeList && pUpdateProcThreadAttribute && pDeleteProcThreadAttributeList) {
			COORD size;
			size.X = 80;
			size.Y = 24;

			if (pCreatePseudoConsole(size, lsp->pipe_fds[LWS_STDIN][0], lsp->pipe_fds[LWS_STDOUT][1], 0, &lsp->hPC) == S_OK) {
				pty_active = 1;
				siex.StartupInfo.cb = sizeof(STARTUPINFOEXA);
				pInitializeProcThreadAttributeList(NULL, 1, 0, &attr_list_size);
				siex.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)lws_malloc(attr_list_size, "ptyattr");
				pInitializeProcThreadAttributeList(siex.lpAttributeList, 1, 0, &attr_list_size);
				pUpdateProcThreadAttribute(siex.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, lsp->hPC, sizeof(HPCON), NULL, NULL);
			}
		}
	}

	if (pty_active) {
		psi = (STARTUPINFOA *)&siex;
		creation_flags |= EXTENDED_STARTUPINFO_PRESENT;
	} else {
		si.cb = sizeof(STARTUPINFOA);
		psi = (STARTUPINFOA *)&si;
	}

	if (!pty_active) {
		psi->hStdInput	= lsp->pipe_fds[LWS_STDIN][0];
		psi->hStdOutput	= lsp->pipe_fds[LWS_STDOUT][1];
		psi->hStdError	= lsp->pipe_fds[LWS_STDERR][1];
		psi->dwFlags	= STARTF_USESTDHANDLES;
		creation_flags |= CREATE_NO_WINDOW;
	}
	psi->wShowWindow	= TRUE;

	if (!CreateProcessA(NULL, cli, NULL, NULL, TRUE, creation_flags, NULL, NULL, psi, &pi)) {
		lwsl_err("%s: CreateProcess failed 0x%lx\n", __func__,
				(unsigned long)GetLastError());
		if (pty_active && siex.lpAttributeList) {
			pDeleteProcThreadAttributeList(siex.lpAttributeList);
			lws_free(siex.lpAttributeList);
		}
		goto bail3;
	}

	if (pty_active && siex.lpAttributeList) {
		pDeleteProcThreadAttributeList(siex.lpAttributeList);
		lws_free(siex.lpAttributeList);
	}

	lsp->child_pid = pi.hProcess;
	lsp->hJob = CreateJobObjectW(NULL, NULL);
	if (lsp->hJob) {
		JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli;
		memset(&jeli, 0, sizeof(jeli));
		jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
		if (!SetInformationJobObject(lsp->hJob, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli)))
			lwsl_warn("%s: SetInformationJobObject failed\n", __func__);
		else
			AssignProcessToJobObject(lsp->hJob, pi.hProcess);
	}

	ResumeThread(pi.hThread);
	CloseHandle(pi.hThread);

	lwsl_notice("%s: lsp %p spawned PID %d\n", __func__, lsp, (int)(intptr_t)lsp->child_pid);

	lws_sul_schedule(context, i->tsi, &lsp->sul, lws_spawn_timeout,
			 i->timeout_us ? i->timeout_us : 300 * LWS_US_PER_SEC);

	/*
	 *  close:                stdin:r, stdout:w, stderr:w
	 */
	for (n = 0; n < 3; n++) {
		if (lsp->pipe_fds[n][n != 0] && (!i->pty_mode || n != LWS_STDERR))
			CloseHandle(lsp->pipe_fds[n][n != 0]);
	}

	lsp->pipes_alive = i->pty_mode ? 2 : 3;
	lsp->created = lws_now_usecs();

	if (i->owner)
		lws_dll2_add_head(&lsp->dll, i->owner);

	if (i->timeout_us)
		lws_sul_schedule(context, i->tsi, &lsp->sul,
				 lws_spawn_timeout, i->timeout_us);

       if (i->plsp)
               *(i->plsp) = lsp;

	return lsp;

bail3:

	lws_sul_cancel(&lsp->sul_poll);

	while (--n >= 0)
		__remove_wsi_socket_from_fds(lsp->stdwsi[n]);
bail2:
	for (n = 0; n < 3; n++)
		if (lsp->stdwsi[n])
			__lws_free_wsi(lsp->stdwsi[n]);

bail1:
	for (n = 0; n < 3; n++) {
		if (lsp->pipe_fds[n][0] >= 0)
			CloseHandle(lsp->pipe_fds[n][0]);
		if (lsp->pipe_fds[n][1] >= 0)
			CloseHandle(lsp->pipe_fds[n][1]);
	}

	lws_free(lsp);

	lwsl_err("%s: failed\n", __func__);

	return NULL;
}

int
lws_spawn_get_stdwsi_open_count(struct lws_spawn_piped *lsp)
{
	return lsp->pipes_alive;
}

void
lws_spawn_closedown_stdwsis(struct lws_spawn_piped *lsp)
{
	int n;

	for (n = 0; n < 3; n++)
		if (lsp->stdwsi[n])
			lws_wsi_close(lsp->stdwsi[n], LWS_TO_KILL_ASYNC);
}

int
lws_spawn_stdwsi_closed(struct lws_spawn_piped *lsp, struct lws *wsi)
{
	int n;

	assert(lsp);
	lsp->pipes_alive--;
       lwsl_wsi_warn(wsi, "stdxxx down: pipes alive %d\n", lsp->pipes_alive);
       if (!lsp->pipes_alive) {
               lwsl_wsi_warn(wsi, "Scheduling reap");
		lws_sul_schedule(lsp->info.vh->context, lsp->info.tsi,
				&lsp->sul_reap, lws_spawn_sul_reap, 1);
       }

	for (n = 0; n < 3; n++)
               if (lsp->stdwsi[n] == wsi) {
                       lwsl_wsi_warn(wsi, "Identified stxxx wsi in lsp");
			lsp->stdwsi[n] = NULL;
                       return !!lsp->pipes_alive;
               }

       lwsl_wsi_warn(wsi, "!!! unable to find stdwsi in lsp %p", lsp);

	return !!lsp->pipes_alive;
}

int
lws_spawn_cgroup_admin_init(const char *toplevel_name, const char *username, const char *groupname)
{
	return 1; /* Not supported on this platform */
}

int
lws_spawn_get_stdfd(struct lws *wsi)
{
	return wsi->lsp_channel;
}

lws_filefd_type
lws_spawn_get_fd_stdxxx(struct lws_spawn_piped *lsp, int std_idx)
{
	assert(std_idx >= 0 && std_idx < 3);

	return (lws_filefd_type)lsp->pipe_fds[std_idx][!!(std_idx == 0)];
}

int
lws_spawn_prepare_self_cgroup(const char *user, const char *group)
{
	return 0;
}
