/*
 * 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.
 */

#if !defined(_GNU_SOURCE)
#define _GNU_SOURCE
#endif

#include "private-lib-core.h"
#include <unistd.h>

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

static struct lws *
lws_create_basic_wsi(struct lws_context *context, int tsi,
		     const struct lws_role_ops *ops)
{
	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;
	}

	new_wsi = lws_zalloc(sizeof(*new_wsi), "new wsi");
	if (new_wsi == NULL) {
		lwsl_err("Out of memory for new connection\n");
		return NULL;
	}

	new_wsi->tsi = tsi;
	new_wsi->context = context;
	new_wsi->pending_timeout = NO_PENDING_TIMEOUT;
	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;
	context->count_wsi_allocated++;

	return new_wsi;
}

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

	if (!lsp)
		return;

	for (n = 0; n < 3; n++) {
		if (lsp->pipe_fds[n][!!(n == 0)] == 0)
			lwsl_err("ZERO FD IN CGI CLOSE");

		if (lsp->pipe_fds[n][!!(n == 0)] >= 0) {
			close(lsp->pipe_fds[n][!!(n == 0)]);
			lsp->pipe_fds[n][!!(n == 0)] = LWS_SOCK_INVALID;
		}
	}

	lws_dll2_remove(&lsp->dll);

	lws_sul_schedule(lsp->info.vh->context, lsp->info.tsi, &lsp->sul,
			 NULL, LWS_SET_TIMER_USEC_CANCEL);

	lws_free_set_NULL((*_lsp));
}

int
lws_spawn_reap(struct lws_spawn_piped *lsp)
{
	long hz = sysconf(_SC_CLK_TCK); /* accounting Hz */
	void *opaque = lsp->info.opaque;
	lsp_cb_t cb = lsp->info.reap_cb;
	struct lws_spawn_piped temp;
	struct tms tms;
	int n;

	if (lsp->child_pid < 1)
		return 0;

	/* check if exited, do not reap yet */

	memset(&lsp->si, 0, sizeof(lsp->si));
	n = waitid(P_PID, lsp->child_pid, &lsp->si, WEXITED | WNOHANG | WNOWAIT);
	if (n < 0) {
		lwsl_info("%s: child %d still running\n", __func__, lsp->child_pid);
		return 0;
	}

	if (!lsp->si.si_code)
		return 0;

	/* his process has exited... */

	if (!lsp->reaped) {
		/* mark the earliest time we knew he had gone */
		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_debug("%s: stdwsi alive, not reaping\n", __func__);
		return 0;
	}

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

	lws_sul_schedule(lsp->info.vh->context, lsp->info.tsi, &lsp->sul, NULL,
			 LWS_SET_TIMER_USEC_CANCEL);

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

	if (times(&tms) != (clock_t) -1) {
		/*
		 * Cpu accounting in us
		 */
		lsp->accounting[0] = ((uint64_t)tms.tms_cstime * 1000000) / hz;
		lsp->accounting[1] = ((uint64_t)tms.tms_cutime * 1000000) / hz;
		lsp->accounting[2] = ((uint64_t)tms.tms_stime * 1000000) / hz;
		lsp->accounting[3] = ((uint64_t)tms.tms_utime * 1000000) / hz;
	}

	temp = *lsp;
	waitid(P_PID, lsp->child_pid, &lsp->si, WEXITED | WNOHANG);
	lsp->child_pid = -1;

	/* 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, temp.accounting, &temp.si,
		   temp.we_killed_him_timeout |
			   (temp.we_killed_him_spew << 1));

	return 1; /* was reaped */
}

int
lws_spawn_piped_kill_child_process(struct lws_spawn_piped *lsp)
{
	int status, n;

	if (lsp->child_pid <= 0)
		return 1;

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

	if (lws_spawn_reap(lsp))
		/* that may have invalidated lsp */
		return 0;

	/* kill the process group */
	n = kill(-lsp->child_pid, SIGTERM);
	lwsl_debug("%s: SIGTERM child PID %d says %d (errno %d)\n", __func__,
		   lsp->child_pid, n, errno);
	if (n < 0) {
		/*
		 * hum seen errno=3 when process is listed in ps,
		 * it seems we don't always retain process grouping
		 *
		 * Direct these fallback attempt to the exact child
		 */
		n = kill(lsp->child_pid, SIGTERM);
		if (n < 0) {
			n = kill(lsp->child_pid, SIGPIPE);
			if (n < 0) {
				n = kill(lsp->child_pid, SIGKILL);
				if (n < 0)
					lwsl_info("%s: SIGKILL PID %d "
						 "failed errno %d "
						 "(maybe zombie)\n", __func__,
						 lsp->child_pid, errno);
			}
		}
	}

	/* He could be unkillable because he's a zombie */

	n = 1;
	while (n > 0) {
		n = waitpid(-lsp->child_pid, &status, WNOHANG);
		if (n > 0)
			lwsl_debug("%s: reaped PID %d\n", __func__, n);
		if (n <= 0) {
			n = waitpid(lsp->child_pid, &status, WNOHANG);
			if (n > 0)
				lwsl_debug("%s: reaped PID %d\n", __func__, n);
		}
	}

	lws_spawn_reap(lsp);
	/* that may have invalidated lsp */

	return 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;
	const char *wd;
	int n, m;

	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)
		return NULL;

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

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

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

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

	for (n = 0; n < 3; n++)
		if (pipe(lsp->pipe_fds[n]) == -1)
			goto bail1;

	/* 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;
		}
		lsp->stdwsi[n]->lsp_channel = n;
		lws_vhost_bind_wsi(i->vh, lsp->stdwsi[n]);
		lsp->stdwsi[n]->protocol = pcol;
		lsp->stdwsi[n]->opaque_user_data = i->opaque;

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

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

		lsp->stdwsi[n]->desc.sockfd = 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;
		}
	}

	for (n = 0; n < 3; n++) {
		if (context->event_loop_ops->sock_accept)
			if (context->event_loop_ops->sock_accept(lsp->stdwsi[n]))
				goto bail3;

		if (__insert_wsi_socket_into_fds(context, lsp->stdwsi[n]))
			goto bail3;
		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];
		}
	}

	if (lws_change_pollfd(lsp->stdwsi[LWS_STDIN], LWS_POLLIN, LWS_POLLOUT))
		goto bail3;
	if (lws_change_pollfd(lsp->stdwsi[LWS_STDOUT], LWS_POLLOUT, LWS_POLLIN))
		goto bail3;
	if (lws_change_pollfd(lsp->stdwsi[LWS_STDERR], LWS_POLLOUT, LWS_POLLIN))
		goto bail3;

	lwsl_debug("%s: fds in %d, out %d, err %d\n", __func__,
		   lsp->stdwsi[LWS_STDIN]->desc.sockfd,
		   lsp->stdwsi[LWS_STDOUT]->desc.sockfd,
		   lsp->stdwsi[LWS_STDERR]->desc.sockfd);

	/* we are ready with the redirection pipes... run the thing */
#if !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE)
	lsp->child_pid = fork();
#else
	lsp->child_pid = vfork();
#endif
	if (lsp->child_pid < 0) {
		lwsl_err("%s: fork failed, errno %d", __func__, errno);
		goto bail3;
	}

#if defined(__linux__)
	prctl(PR_SET_PDEATHSIG, SIGTERM);
#endif

	if (lsp->info.disable_ctrlc)
		/* stops non-daemonized main processess getting SIGINT
		 * from TTY */
		setpgrp();

	if (lsp->child_pid) {

		/* we are the parent process */

		lwsl_info("%s: lsp %p spawned PID %d\n", __func__, lsp,
			  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
		 * hide from other forks: stdin:w, stdout:r, stderr:r
		 */
		for (n = 0; n < 3; n++) {
			lws_plat_apply_FD_CLOEXEC(lsp->pipe_fds[n][!!(n == 0)]);
			close(lsp->pipe_fds[n][!(n == 0)]);
		}

		lsp->pipes_alive = 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);

		return lsp;
	}

	/*
	 * We are the forked process, redirect and kill inherited things.
	 *
	 * Because of vfork(), we cannot do anything that changes pages in
	 * the parent environment.  Stuff that changes kernel state for the
	 * process is OK.  Stuff that happens after the execvpe() is OK.
	 */

	if (i->chroot_path && chroot(i->chroot_path)) {
		lwsl_err("%s: child chroot %s failed, errno %d\n",
			 __func__, i->chroot_path, errno);

		exit(2);
	}

	/* cwd: somewhere we can at least read things and enter it */

	wd = i->wd;
	if (!wd)
		wd = "/tmp";
	if (chdir(wd))
		lwsl_notice("%s: Failed to cd to %s\n", __func__, wd);

	for (m = 0; m < 3; m++) {
		if (dup2(lsp->pipe_fds[m][!(m == 0)], m) < 0) {
			lwsl_err("%s: stdin dup2 failed\n", __func__);
			goto bail3;
		}
		close(lsp->pipe_fds[m][0]);
		close(lsp->pipe_fds[m][1]);
	}

	// lwsl_notice("%s: child cd %s, exec %s\n", __func__, wd, i->exec_array[0]);

#if !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE)
#if defined(__linux__)
	m = 0;
	while (i->env_array[m]){
		char *p = strchr(i->env_array[m], '=');
		*p++ = '\0';
		setenv(i->env_array[m], p, 1);
		m++;
	}
#endif
	execvp(i->exec_array[0], (char * const *)&i->exec_array[0]);
#else
	execvpe(i->exec_array[0], (char * const *)&i->exec_array[0],
		&i->env_array[0]);
#endif

	lwsl_err("%s: child exec of %s failed %d\n", __func__, i->exec_array[0],
		 LWS_ERRNO);

	_exit(1);

bail3:

	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)
			close(lsp->pipe_fds[n][0]);
		if (lsp->pipe_fds[n][1] >= 0)
			close(lsp->pipe_fds[n][1]);
	}

	lws_free(lsp);

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

	return NULL;
}

void
lws_spawn_stdwsi_closed(struct lws_spawn_piped *lsp)
{
	assert(lsp);
	lsp->pipes_alive--;
	lwsl_debug("%s: pipes alive %d\n", __func__, lsp->pipes_alive);
	lws_spawn_reap(lsp);
}

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