/*	$Id$ */
/*
 * Copyright (c) 2012, 2014--2017 Kristaps Dzonsons <kristaps@bsd.lv>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include "config.h"

#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/wait.h>

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "kcgi.h"
#include "extern.h"

int
kxvasprintf(char **p, const char *fmt, va_list ap)
{
	int	 len;

	if ((len = vasprintf(p, fmt, ap)) >= 0)
		return len;

	kutil_warn(NULL, NULL, "vasprintf");
	*p = NULL;
	return -1;
}

int
kxasprintf(char **p, const char *fmt, ...)
{
	va_list	 ap;
	int	 ret;

	va_start(ap, fmt);
	ret = kxvasprintf(p, fmt, ap);
	va_end(ap);
	return ret;
}

void *
kxcalloc(size_t nm, size_t sz)
{
	void	 *p;

	if (nm == 0 || sz == 0)  {
		kutil_warnx(NULL, NULL, "calloc: zero length");
		return NULL;
	} else if ((p = calloc(nm, sz)) != NULL)
		return p;

	kutil_warn(NULL, NULL, "calloc: %zu, %zu", nm, sz);
	return NULL;
}

void *
kxmalloc(size_t sz)
{
	void	 *p;

	if (sz == 0) {
		kutil_warnx(NULL, NULL, "malloc: zero length");
		return NULL;
	} else if ((p = malloc(sz)) != NULL)
		return p;

	kutil_warn(NULL, NULL, "malloc: %zu", sz);
	return NULL;
}

void *
kxrealloc(void *pp, size_t sz)
{
	void	 *p;

	if (sz == 0) {
		kutil_warnx(NULL, NULL, "realloc: zero length");
		return NULL;
	} else if ((p = realloc(pp, sz)) != NULL)
		return p;

	kutil_warn(NULL, NULL, "realloc: %zu", sz);
	return NULL;
}

void *
kxreallocarray(void *pp, size_t nm, size_t sz)
{
	void	 *p;

	if (sz == 0 || nm == 0) {
		kutil_warnx(NULL, NULL, "reallocarray: zero length");
		return NULL;
	} else if ((p = reallocarray(pp, nm, sz)) != NULL)
		return p;

	kutil_warn(NULL, NULL, "reallocarray: %zu, %zu", nm, sz);
	return NULL;
}

char *
kxstrdup(const char *cp)
{
	char	*p;

	if (cp == NULL) {
		kutil_warnx(NULL, NULL, "strdup: NULL string");
		return NULL;
	} else if ((p = strdup(cp)) != NULL)
		return p;

	kutil_warn(NULL, NULL, "strdup");
	return NULL;
}

/*
 * waitpid() and logging anything but a return with EXIT_SUCCESS.
 * Returns KCGI_OK on EXIT_SUCCESS, KCGI_SYSTEM on waitpid() error,
 * KCGI_FORM on child process failure.
 */
enum kcgi_err
kxwaitpid(pid_t pid)
{
	int	st;

	if (waitpid(pid, &st, 0) == -1) {
		kutil_warn(NULL, NULL, "waitpid");
		return KCGI_SYSTEM;
	} else if (WIFEXITED(st) && WEXITSTATUS(st) == EXIT_SUCCESS)
		return KCGI_OK;

	if (WIFEXITED(st))
		kutil_warnx(NULL, NULL, "waitpid: child failure");
	else 
		kutil_warnx(NULL, NULL, "waitpid: child signal");

	return KCGI_FORM;
}

/*
 * Set a file-descriptor as being non-blocking.
 * Returns KCGI_SYSTEM on error, KCGI_OK on success.
 */
enum kcgi_err
kxsocketprep(int sock)
{
	int	 fl;

	if ((fl = fcntl(sock, F_GETFL, 0)) == -1) {
		kutil_warn(NULL, NULL, "fcntl");
		return KCGI_SYSTEM;
	} else if (fcntl(sock, F_SETFL, fl | O_NONBLOCK) == -1) {
		kutil_warn(NULL, NULL, "fcntl");
		return KCGI_SYSTEM;
	}

	return KCGI_OK;
}

/*
 * Create a non-blocking socketpair (AF_UNIX, SOCK_STREAM, protocol 0).
 * Return KCGI_ENFILE on temporary failure, KCGI_SYSTEM on fatal error,
 * KCGI_OK on success.
 * The input socket pair is only valid on success.
 */
enum kcgi_err
kxsocketpair(int sock[2])
{
	int	 	 rc;
	enum kcgi_err	 er;

	rc = socketpair(AF_UNIX, SOCK_STREAM, 0, sock);
	if (rc == -1 && (EMFILE == errno || ENFILE == errno)) {
		kutil_warn(NULL, NULL, "socketpair");
		return KCGI_ENFILE;
	} else if (rc == -1) {
		kutil_warn(NULL, NULL, "socketpair");
		return KCGI_SYSTEM;
	}

	if ((er = kxsocketprep(sock[0])) == KCGI_OK &&
	   ((er = kxsocketprep(sock[1])) == KCGI_OK))
		return KCGI_OK;
	
	close(sock[0]);
	close(sock[1]);
	return er;
}

/*
 * Write a string "buf".
 * If "buf" is NULL, then write a zero-length string.
 * See fullreadword().
 * On error (which shouldn't happen), this will exit the process with
 * the exit code EXIT_FAILURE.
 */
void
fullwriteword(int fd, const char *buf)
{
	size_t	 sz;

	if (buf != NULL) {
		sz = strlen(buf);
		fullwrite(fd, &sz, sizeof(size_t));
		fullwrite(fd, buf, sz);
	} else {
		sz = 0;
		fullwrite(fd, &sz, sizeof(size_t));
	}
}

/*
 * This is like fullwrite() but it does not bail out on errors.
 * We need this for writing our response to a socket that may be closed
 * at any time, like the FastCGI one.
 * Returns KCGI_SYSTEM, KCGI_HUP, or KCGI_OK.
 * Ignores SIGPIPE, restoring it on exit.
 */
enum kcgi_err
fullwritenoerr(int fd, const void *buf, size_t bufsz)
{
	ssize_t	 	  ssz;
	size_t	 	  sz;
	struct pollfd	  pfd;
	int		  rc;
	enum kcgi_err	  er = KCGI_OK;
	void		(*sig)(int);

	pfd.fd = fd;
	pfd.events = POLLOUT;

	if ((sig = signal(SIGPIPE, SIG_IGN)) == SIG_ERR) {
		kutil_warn(NULL, NULL, "signal");
		return KCGI_SYSTEM;
	}

	for (sz = 0; sz < bufsz; sz += (size_t)ssz) {
		if ((rc = poll(&pfd, 1, -1)) < 0) {
			kutil_warn(NULL, NULL, "poll");
			er = KCGI_SYSTEM;
			break;
		} else if (rc == 0) {
			kutil_warnx(NULL, NULL, "poll: timeout!?");
			ssz = 0;
			continue;
		}

		if (pfd.revents & POLLHUP) {
			kutil_warnx(NULL, NULL, "poll: hangup");
			er = KCGI_HUP;
			break;
		} else if (pfd.revents & POLLERR) {
			kutil_warnx(NULL, NULL, "poll: error");
			er = KCGI_SYSTEM;
			break;
		}

		/* See note in fullwrite(). */

#ifdef __APPLE__
		if (!(pfd.revents & POLLOUT) && 
		    !(pfd.revents & POLLNVAL)) {
			kutil_warnx(NULL, NULL, "poll: no output");
			er = KCGI_SYSTEM;
			break;
		}
#else
		if (!(pfd.revents & POLLOUT)) {
			kutil_warnx(NULL, NULL, "poll: no output");
			er = KCGI_SYSTEM;
			break;
		}
#endif

		if ((ssz = write(fd, buf + sz, bufsz - sz)) < 0) {
			er = errno == EPIPE ? KCGI_HUP : KCGI_SYSTEM;
			kutil_warn(NULL, NULL, "write");
			break;
		} else if (sz > SIZE_MAX - (size_t)ssz) {
			kutil_warnx(NULL, NULL, "write: overflow");
			er = KCGI_SYSTEM;
			break;
		} 
	}

	if (signal(SIGPIPE, sig) == SIG_ERR) {
		kutil_warn(NULL, NULL, "signal");
		er = KCGI_SYSTEM;
	}

	return er;
}

/*
 * Write "buf", which can be NULL so long as bufsz is zero in which case
 * it's a noop.
 * On error (which shouldn't happen), this will exit the process with
 * the exit code EXIT_FAILURE.
 */
void
fullwrite(int fd, const void *buf, size_t bufsz)
{
	ssize_t	 	 ssz;
	size_t	 	 sz;
	struct pollfd	 pfd;
	int		 rc;

	if (bufsz == 0)
		return;

	assert(buf != NULL);
	pfd.fd = fd;
	pfd.events = POLLOUT;

	for (sz = 0; sz < bufsz; sz += (size_t)ssz) {
		if ((rc = poll(&pfd, 1, INFTIM)) == 0) {
			kutil_warnx(NULL, NULL, "poll: timeout!?");
			ssz = 0;
			continue;
		} else if (rc < 0)
			kutil_err(NULL, NULL, "poll");

		if (pfd.revents & POLLHUP)
			kutil_errx(NULL, NULL, "poll: hangup");
		else if (pfd.revents & POLLERR)
			kutil_errx(NULL, NULL, "poll: error");

		/*
		 * This CPP exists because testing on Mac OS X will have
		 * "fd" point to a device and poll(2) returns POLLNVAL
		 * if the descriptor is to a device.
		 * This is documented in the BUGS section of poll(2).
		 */
#ifdef __APPLE__
		if (!(pfd.revents & POLLOUT) && 
		    !(pfd.revents & POLLNVAL))
			kutil_errx(NULL, NULL, "poll: no output");
#else
		if (!(pfd.revents & POLLOUT))
			kutil_errx(NULL, NULL, "poll: no output");
#endif

		if ((ssz = write(fd, buf + sz, bufsz - sz)) < 0)
			kutil_err(NULL, NULL, "write");
		else if (sz > SIZE_MAX - (size_t)ssz)
			kutil_errx(NULL, NULL, "write: overflow");
	}
}

/*
 * Read the contents of "buf" of size "bufsz".
 * If "eofok" is set, return zero if there is no data to read.
 * If it's not set, this condition returns <0.
 * Returns <0 on errors and >0 on success.
 */
int
fullread(int fd, void *buf, size_t bufsz, int eofok, enum kcgi_err *er)
{
	ssize_t	 	 ssz;
	size_t	 	 sz;
	struct pollfd	 pfd;
	int		 rc;

	pfd.fd = fd;
	pfd.events = POLLIN;

	for (sz = 0; sz < bufsz; sz += (size_t)ssz) {
		if ((rc = poll(&pfd, 1, INFTIM)) < 0) {
			kutil_warn(NULL, NULL, "poll");
			*er = KCGI_SYSTEM;
			return (-1);
		} else if (rc == 0) {
			kutil_warnx(NULL, NULL, "poll: timeout!?");
			ssz = 0;
			continue;
		} 
		
		if (!(pfd.revents & POLLIN)) {
			if (eofok && sz == 0) {
				*er = KCGI_OK;
				return 0;
			}
			kutil_warnx(NULL, NULL, "poll: no input");
			*er = KCGI_FORM;
			return (-1);
		} 
		
		if ((ssz = read(fd, buf + sz, bufsz - sz)) < 0) {
			kutil_warn(NULL, NULL, "read");
			*er = KCGI_SYSTEM;
			return (-1);
		} else if (ssz == 0 && sz > 0) {
			kutil_warnx(NULL, NULL, "read: short read");
			*er = KCGI_FORM;
			return (-1);
		} else if (ssz == 0 && sz == 0 && !eofok) {
			kutil_warnx(NULL, NULL, "read: end of file");
			*er = KCGI_FORM;
			return (-1);
		} else if (ssz == 0 && sz == 0 && eofok) {
			*er = KCGI_OK;
			return 0;
		} else if (sz > SIZE_MAX - (size_t)ssz) {
			kutil_warnx(NULL, NULL, "read: overflow");
			*er = KCGI_FORM;
			return (-1);
		}
	}

	*er = KCGI_OK;
	return 1;
}

/*
 * Read a string from the stream.
 * Return KCGI_OK on success or another on error.
 * This will set cp to NULL and sz to zero on failure.
 * The cp array (on success) is always NUL-terminated, although the
 * buffer it reads is opaque.
 * On success, "sz" may legit be zero.
 */
enum kcgi_err
fullreadwordsz(int fd, char **cp, size_t *sz)
{
	enum kcgi_err	 ke;
	int		 rc;

	*cp = NULL;
	*sz = 0;

	if (fullread(fd, sz, sizeof(size_t), 0, &ke) < 0)
		return ke;

	/* TODO: check additive overflow of "sz + 1". */

	if ((*cp = kxmalloc(*sz + 1)) == NULL) {
		*sz = 0;
		return KCGI_ENOMEM;
	}
	(*cp)[*sz] = '\0';

	if (*sz == 0)
		return KCGI_OK;

	/* Because we don't set "eofok", never returns zero. */

	if ((rc = fullread(fd, *cp, *sz, 0, &ke)) > 0) {
		assert(ke == KCGI_OK);
		return ke;
	}

	assert(rc < 0);
	assert(ke != KCGI_OK);
	free(*cp);
	*cp = NULL;
	*sz = 0;
	return ke;
}

/*
 * See fullreadwordsz() with a discarded size.
 */
enum kcgi_err
fullreadword(int fd, char **cp)
{
	size_t sz;

	return fullreadwordsz(fd, cp, &sz);
}

/*
 * Write a file-descriptor "sendfd" and a buffer "b" of length "bsz",
 * which must be 256 bytes or fewer, but not zero.
 * See fullwritefd().
 * Returns zero on failure (any kind), non-zero on success.
 */
int
fullwritefd(int fd, int sendfd, void *b, size_t bsz)
{
	struct msghdr	 msg;
	int		 rc;
	char		 buf[CMSG_SPACE(sizeof(fd))];
	struct iovec 	 io;
	struct cmsghdr	*cmsg;
	struct pollfd	 pfd;

	assert(bsz <= 256 && bsz > 0);

	memset(buf, 0, sizeof(buf));
	memset(&msg, 0, sizeof(struct msghdr));
	memset(&io, 0, sizeof(struct iovec));

	io.iov_base = b;
	io.iov_len = bsz;

	msg.msg_iov = &io;
	msg.msg_iovlen = 1;
	msg.msg_control = buf;
	msg.msg_controllen = sizeof(buf);

	cmsg = CMSG_FIRSTHDR(&msg);
	cmsg->cmsg_level = SOL_SOCKET;
	cmsg->cmsg_type = SCM_RIGHTS;
	cmsg->cmsg_len = CMSG_LEN(sizeof(fd));

	*((int *)CMSG_DATA(cmsg)) = sendfd;

	msg.msg_controllen = cmsg->cmsg_len;

	pfd.fd = fd;
	pfd.events = POLLOUT;

again:
	if ((rc = poll(&pfd, 1, INFTIM)) < 0) {
		kutil_warn(NULL, NULL, "poll");
		return 0;
	} else if (rc == 0) {
		kutil_warnx(NULL, NULL, "poll: timeout!?");
		goto again;
	}
	
	if (!(pfd.revents & POLLOUT)) {
		kutil_warnx(NULL, NULL, "poll: no output");
		return 0;
	} 
	
	if ((rc = sendmsg(fd, &msg, 0)) < 0) {
		kutil_warn(NULL, NULL, "sendmsg");
		return 0;
	} else if (rc == 0) {
		kutil_warnx(NULL, NULL, "sendmsg: short write");
		return 0;
	}

	return 1;
}

/*
 * Read a file-descriptor into "recvfd" and a buffer "b" of length
 * "bsz", which must be 256 bytes or fewer, but not zero.
 * See fullwritefd().
 * Returns <0 on system failure, 0 on hangup (remote end closed), and >0
 * on success.
 * The output pointers are only set on success.
 */
int 
fullreadfd(int fd, int *recvfd, void *b, size_t bsz)
{
	struct msghdr	 msg;
	char		 m_buffer[256];
	char 		 c_buffer[256];
	struct iovec	 io;
	struct cmsghdr	*cmsg;
	int		 rc;
	struct pollfd	 pfd;

	assert(bsz <= 256 && bsz > 0);

	memset(&msg, 0, sizeof(struct msghdr));
	memset(&io, 0, sizeof(struct iovec));

	io.iov_base = m_buffer;
	io.iov_len = sizeof(m_buffer);
	msg.msg_iov = &io;
	msg.msg_iovlen = 1;

	msg.msg_control = c_buffer;
	msg.msg_controllen = sizeof(c_buffer);

	pfd.fd = fd;
	pfd.events = POLLIN;

again:
	if ((rc = poll(&pfd, 1, INFTIM)) < 0) {
		kutil_warn(NULL, NULL, "poll");
		return (-1);
	} else if (rc == 0) {
		kutil_warnx(NULL, NULL, "poll timeout!?!?");
		goto again;
	}
	
	if (!(pfd.revents & POLLIN)) {
		kutil_warnx(NULL, NULL, "poll: no input");
		return 0;
	} 
	
	if ((rc = recvmsg(fd, &msg, 0)) < 0) {
		kutil_warn(NULL, NULL, "recvmsg");
		return (-1);
	} else if (rc == 0) {
		kutil_warnx(NULL, NULL, "recvmsg: short read");
		return 0;
	}

	memcpy(b, m_buffer, bsz);
	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
		 cmsg = CMSG_NXTHDR(&msg, cmsg)) {
		if (cmsg->cmsg_len == CMSG_LEN(sizeof(int)) &&
		    cmsg->cmsg_level == SOL_SOCKET &&
		    cmsg->cmsg_type == SCM_RIGHTS) {
			*recvfd = *(int *)CMSG_DATA(cmsg);
			return 1;
		}
	}

	kutil_warnx(NULL, NULL, "recvmsg: no SCM_RIGHTS");
	return (-1);
}
