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

#if defined(__OpenBSD__) || defined(__NetBSD__)
#include <sys/resource.h>
#include <sys/wait.h>
#endif

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_notice("%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_stdwsi(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)pt->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);
	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;

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

	return new_wsi;
}

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

	if (!lsp)
		return;

	lws_dll2_remove(&lsp->dll);

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

	for (n = 0; n < 3; n++) {
#if 0
		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;
		}
#endif
		if (lsp->stdwsi[n]) {
			lws_set_timeout(lsp->stdwsi[n], 1, LWS_TO_KILL_ASYNC);
			lsp->stdwsi[n] = NULL;
		}
	}

	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;
#if defined(__OpenBSD__) || defined(__NetBSD__)
	struct rusage rusa;
	int status;
#endif
	int n;

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

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

	memset(&lsp->si, 0, sizeof(lsp->si));
#if defined(__OpenBSD__) || defined(__NetBSD__)
	n = wait4(lsp->child_pid, &status, WNOHANG, &rusa);
	if (!n)
		return 0;
	lsp->si.si_code = WIFEXITED(status);
#else
	n = waitid(P_PID, (id_t)lsp->child_pid, &lsp->si, WEXITED | WNOHANG | WNOWAIT);
#endif
	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_info("%s: %d stdwsi alive, not reaping\n", __func__,
				lsp->pipes_alive);
		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 (times(&tms) != (clock_t) -1) {
		/*
		 * Cpu accounting in us
		 */
		lsp->accounting[0] = (lws_usec_t)((uint64_t)tms.tms_cstime * 1000000) / hz;
		lsp->accounting[1] = (lws_usec_t)((uint64_t)tms.tms_cutime * 1000000) / hz;
		lsp->accounting[2] = (lws_usec_t)((uint64_t)tms.tms_stime * 1000000) / hz;
		lsp->accounting[3] = (lws_usec_t)((uint64_t)tms.tms_utime * 1000000) / hz;
	}

	temp = *lsp;
#if defined(__OpenBSD__) || defined(__NetBSD__)
	n = wait4(lsp->child_pid, &status, WNOHANG, &rusa);
	if (!n)
		return 0;
	lsp->si.si_code = WIFEXITED(status);
	if (lsp->si.si_code == CLD_EXITED)
		temp.si.si_code = CLD_EXITED;
	temp.si.si_status = WEXITSTATUS(status);
#else
	n = waitid(P_PID, (id_t)lsp->child_pid, &temp.si, WEXITED | WNOHANG);
#endif
	temp.si.si_status &= 0xff; /* we use b8 + for flags */
	lwsl_info("%s: waitd says %d, process exit %d\n",
		    __func__, n, temp.si.si_status);

	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;
	lsp->reap_retry_budget = 20;

	/*
	 * 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;
		lws_plat_apply_FD_CLOEXEC(lsp->pipe_fds[n][n == 0]);
	}

	/*
	 * At this point, we have 6 pipe fds open on lws side and no wsis
	 * bound to them
	 */

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

	for (n = 0; n < 3; n++) {
		lsp->stdwsi[n] = lws_create_stdwsi(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->lcg[LWSLCG_WSI], &lsp->stdwsi[n]->lc,
                             "nspawn-stdwsi-%d", n);

		lsp->stdwsi[n]->lsp_channel = (uint8_t)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;

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

		/*
		 * We have bound 3 x pipe fds to wsis, wr side of stdin and rd
		 * side of stdout / stderr... those are marked CLOEXEC so they
		 * won't go through the fork
		 *
		 * rd side of stdin and wr side of stdout / stderr are open but
		 * not bound to anything on lws side.
		 */
	}

	/*
	 * Stitch the wsi fd into the poll wait
	 */

	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_info("%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... do the (v)fork */
#if defined(__sun) || !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__)
	if (!lsp->child_pid)
		prctl(PR_SET_PDEATHSIG, SIGTERM);
#endif

	if (lsp->info.disable_ctrlc)
		/* stops non-daemonized main processess getting SIGINT
		 * from TTY */
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
		setpgid(0, 0);
#else
		setpgrp();
#endif

	if (lsp->child_pid) {

		/*
		 * We are the parent process.  We can close our copy of the
		 * "other" side of the pipe fds, ie, rd for stdin and wr for
		 * stdout / stderr.
		 */
		for (n = 0; n < 3; n++)
			/* these guys didn't have any wsi footprint */
			close(lsp->pipe_fds[n][n != 0]);

		lsp->pipes_alive = 3;
		lsp->created = lws_now_usecs();

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

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

	/*
	 * Bind the child's stdin / out / err to its side of our pipes
	 */

	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;
		}
		/*
		 * CLOEXEC on the lws-side of the pipe fds should have already
		 * dealt with closing those for the child perspective.
		 *
		 * Now it has done the dup, the child should close its original
		 * copies of its side of the pipes.
		 */

		close(lsp->pipe_fds[m][m != 0]);
	}

#if defined(__sun) || !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE)
#if defined(__linux__) || defined(__APPLE__) || defined(__sun)
	m = 0;
	while (i->env_array[m]){
		const char *p = strchr(i->env_array[m], '=');
		int naml = lws_ptr_diff(p, i->env_array[m]);
		char enam[32];

		lws_strnncpy(enam, i->env_array[m], naml, sizeof(enam));
		setenv(enam, 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],
		(char **)&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, struct lws *wsi)
{
	int n;

	assert(lsp);
	lsp->pipes_alive--;
	lwsl_debug("%s: pipes alive %d\n", __func__, lsp->pipes_alive);
	if (!lsp->pipes_alive)
		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)
			lsp->stdwsi[n] = NULL;
}

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