text: /*
   * 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);
  }
  
  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_basic_wsi(struct lws_context *context, int tsi,
  		     const struct lws_role_ops *ops)
  {
  	struct lws_context_per_thread *pt = &context->pt[tsi];
  	size_t s = sizeof(struct lws);
  	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;
  	}
  
  #if defined(LWS_WITH_EVENT_LIBS)
  	s += context->event_loop_ops->evlib_size_wsi;
  #endif
  
  	new_wsi = lws_zalloc(s, "new wsi");
  	if (new_wsi == NULL) {
  		lwsl_err("Out of memory for new connection\n");
  		return NULL;
  	}
  
  #if defined(LWS_WITH_EVENT_LIBS)
  	new_wsi->evlib_wsi = (uint8_t *)new_wsi + sizeof(*new_wsi);
  #endif
  
  	new_wsi->tsi = tsi;
  	new_wsi->a.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;
  	lws_pt_lock(pt, __func__);
  	pt->count_wsi_allocated++;
  	lws_pt_unlock(pt);
  
  	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;
  	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_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] = ((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;
  	n = waitid(P_PID, lsp->child_pid, &temp.si, WEXITED | WNOHANG);
  	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_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]->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(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__)
  		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(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE)
  #if defined(__linux__) || defined(__APPLE__)
  	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;
  }
  