/* coding: utf-8; */
#include <stdint.h>
#include <assert.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#define _GNU_SOURCE /* execvpe */
#include <unistd.h>
#include <poll.h>
#include <string.h>
#include <signal.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/wait.h>

#include "config.h"
#include "soundio-util.h"

int ignore_signals(int sig, ...)
{
	struct sigaction sa;
	va_list ap;
	int r = 0;

	memset(&sa, 0, sizeof(sa));
	sa.sa_handler = SIG_IGN;
	sa.sa_flags = SA_RESTART;

	if (sigaction(sig, &sa, NULL) < 0)
		r = -errno;

	va_start(ap, sig);
	while ((sig = va_arg(ap, int)) > 0)
		if (sigaction(sig, &sa, NULL) < 0)
			r = -errno;
	va_end(ap);

	return r;
}

int default_signals(int sig, ...)
{
	struct sigaction sa;
	va_list ap;
	int r = 0;

	memset(&sa, 0, sizeof(sa));
	sa.sa_handler = SIG_DFL;
	sa.sa_flags = SA_RESTART;

	if (sigaction(sig, &sa, NULL) < 0)
		r = -errno;

	va_start(ap, sig);
	while ((sig = va_arg(ap, int)) > 0)
		if (sigaction(sig, &sa, NULL) < 0)
			r = -errno;
	va_end(ap);

	return r;
}

ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll)
{
	uint8_t *p;
	ssize_t n = 0;

	assert(fd >= 0);
	assert(buf);

	p = buf;

	while (nbytes > 0) {
		ssize_t k;

		if ((k = read(fd, p, nbytes)) <= 0) {
			if (k < 0 && errno == EINTR)
				continue;

			if (k < 0 && errno == EAGAIN && do_poll) {
				struct pollfd pollfd;

				memset(&pollfd, 0, sizeof(pollfd));
				pollfd.fd = fd;
				pollfd.events = POLLIN;

				if (poll(&pollfd, 1, -1) < 0) {
					if (errno == EINTR)
						continue;

					return n > 0 ? n : -errno;
				}

				if (pollfd.revents != POLLIN)
					return n > 0 ? n : -EIO;

				continue;
			}

			return n > 0 ? n : (k < 0 ? -errno : 0);
		}

		p += k;
		nbytes -= k;
		n += k;
	}

	return n;
}

ssize_t loop_write(int fd, const void *buf, size_t nbytes, bool do_poll)
{
	const uint8_t *p;
	ssize_t n = 0;

	assert(fd >= 0);
	assert(buf);

	p = buf;

	while (nbytes > 0) {
		ssize_t k;

		if ((k = write(fd, p, nbytes)) <= 0) {
			if (k < 0 && errno == EINTR)
				continue;

			if (k < 0 && errno == EAGAIN && do_poll) {
				struct pollfd pollfd;

				memset(&pollfd, 0, sizeof(pollfd));
				pollfd.fd = fd;
				pollfd.events = POLLOUT;

				if (poll(&pollfd, 1, -1) < 0) {
					if (errno == EINTR)
						continue;

					return n > 0 ? n : -errno;
				}

				if (pollfd.revents != POLLOUT)
					return n > 0 ? n : -EIO;

				continue;
			}

			return n > 0 ? n : (k < 0 ? -errno : 0);
		}

		p += k;
		nbytes -= k;
		n += k;
	}

	return n;
}

/*
 * loop_read_all will try to read some data, data will be placed at 
 * a newly-allocated mem and appended a zero.
 *
 * The addr of the newly-allocated mem will be returned by @buf 
 * Num of total read bytes will be returned by @nbytes
 *
 * If do_poll is true and fd is non block. loop_read_all will wait
 * util reading some data.
 */
ssize_t loop_read_all(int fd, void **buf, size_t *nbytes, bool do_poll)
{
	uint8_t *b;
	int offset = 0;
	int m = 1024, b_len = m;

	ssize_t k, n = 0;

	assert(fd >= 0);
	assert(buf);

	b = malloc(b_len);
	if (!b)
		return -ENOMEM;

again:
	k = read(fd, b + offset, m-1);

	if (k > 0)
		n += k;

	if (k == m-1) {
		uint8_t *new_b;

		offset += m;
		/* Max step: 2M  */
		m = m <= 1024 * 1024 ? m * 2 : m;
		b_len += m;

		if (!(new_b = realloc(b, b_len))) {
			n = -ENOMEM;
			goto quit;
		}

		b = new_b;
		goto again;
	}

	if (k < 0) {
		if (errno == EINTR)
			goto again;

		if (errno == EAGAIN && n == 0 && do_poll) {
			/* We have to read something or error */
			struct pollfd pollfd;

			memset(&pollfd, 0, sizeof(pollfd));
			pollfd.fd = fd;
			pollfd.events = POLLIN;

			while (poll(&pollfd, 1, -1) < 0) {
				if (errno == EINTR)
					continue;

				n = -errno;
				goto quit;
			}

			if (pollfd.revents != POLLIN) {
				n = -EIO;
				goto quit;
			}

			goto again;
		}

		n = -errno;
	}

quit:
	if (n > 0)
		b[n] = 0;
	else {
		free(b);
		b = NULL;
	}

	*buf = b;
	if (nbytes)
		*nbytes = n < 0 ? 0 : n;

	return n;
}

int startswith(const char *s, const char *prefix)
{
	size_t sl, pl;

	assert(s);
	assert(prefix);

	sl = strlen(s);
	pl = strlen(prefix);

	if (pl == 0)
		return true;

	if (sl < pl)
		return false;

	return memcmp(s, prefix, pl) == 0;
}

char *strstrip(char *s)
{
	char *e, *l = NULL;

	/* Drops trailing whitespace. Modifies the string in
	 * place. Returns pointer to first non-space character */

	s += strspn(s, WHITESPACE);

	for (e = s; *e; e++)
		if (!strchr(WHITESPACE, *e))
			l = e;

	if (l)
		*(l+1) = 0;
	else
		*s = 0;

	return s;
}

int fd_nonblock(int fd, bool nonblock)
{
	int flags;

	assert(fd >= 0);

	if ((flags = fcntl(fd, F_GETFL, 0)) < 0)
		return -errno;

	if (nonblock)
		flags |= O_NONBLOCK;
	else
		flags &= ~O_NONBLOCK;

	if (fcntl(fd, F_SETFL, flags) < 0)
		return -errno;

	return 0;
}

int fd_cloexec(int fd, bool cloexec)
{
	int flags;

	assert(fd >= 0);

	if ((flags = fcntl(fd, F_GETFD, 0)) < 0)
		return -errno;

	if (cloexec)
		flags |= FD_CLOEXEC;
	else
		flags &= ~FD_CLOEXEC;

	if (fcntl(fd, F_SETFD, flags) < 0)
		return -errno;

	return 0;
}

int close_nointr(int fd)
{
	for (;;) {
		int r;

		if ((r = close(fd)) >= 0)
			return r;

		if (errno != EINTR)
			return r;
	}
}

int close_and_invalidate(int *fd)
{
	int r = close_nointr(*fd);
	*fd = -1;

	return r;
}

int sane_dup2(int fd1, int fd2)
{
	int ret;

retry:
	ret = dup2(fd1, fd2);
	if (ret < 0 && errno == EINTR)
		goto retry;

	return ret;
}

int sane_open(const char *path, int mode)
{
	int ret;

retry:
	ret = open(path, mode);
	if (ret < 0 && errno == EINTR)
		goto retry;

	return ret;
}

static int _fdwalk(int (*cb)(void *data, int fd), void *data)
{
	int fd;
	int res = 0;
	struct dirent *de;
  
	DIR *d;

	if (!(d = opendir("/proc/self/fd")))
		return -errno;

	while ((de = readdir(d))) {
		long l;
		char *e = NULL;

		if (de->d_name[0] == '.')
			continue;
	    
		errno = 0;
		l = strtol(de->d_name, &e, 10);
		if (errno != 0 || !e || *e)
			continue;

		fd = (int) l;

		if ((long) fd != l)
			continue;

		if (fd == dirfd(d))
			continue;

		if ((res = cb(data, fd)) != 0)
			break;
	}

	closedir(d);
	return res;
}

static int my_execute(const char *file,
		      char **argv,
		      char **envp,
		      int search_path)
{
	if (*file == '\0') {
		/* We check the simple case first. */
		errno = ENOENT;
		return -1;
	}

	if (!(search_path || strchr(file, '/') != NULL)) {
		/* Don't search when it contains a slash. */
		if (envp)
			execve(file, argv, envp);
		else
			execv(file, argv);
	} else {
		if (envp)
#ifdef HAVE_EXECVPE
			execvpe(file, argv, envp);
#else
			execve(file, argv, envp);
#endif
		else
			execvp(file, argv);
	}

	return -1;
}

static void write_err_and_exit(int fd, int msg)
{
	int en = errno;
  
	loop_write(fd, &msg, sizeof(msg), false);
	loop_write(fd, &en, sizeof(en), false);

	_exit(1);
}

enum
{
	CHILD_CHDIR_FAILED,
	CHILD_EXEC_FAILED,
	CHILD_DUP2_FAILED,
};

static int set_cloexec(void *data, int fd)
{
	if (fd >= (intptr_t) data)
		fd_cloexec(fd, true);

	return 0;
}

/* Help functions start */
static void do_exec(int child_err_report_fd,
		    int stdin_fd,
		    int stdout_fd,
		    int stderr_fd,
		    const char *working_directory,
		    char **argv,
		    char **envp,
		    bool close_descriptors,
		    bool search_path,
		    bool stdout_to_null,
		    bool stderr_to_null,
		    bool child_inherits_stdin,
		    bool file_and_argv_zero)
{

	if (working_directory && chdir(working_directory) < 0)
		write_err_and_exit(child_err_report_fd, CHILD_CHDIR_FAILED);

	/* Close all file descriptors but stdin stdout and stderr as
	 * soon as we exec. Note that this includes
	 * child_err_report_fd, which keeps the parent from blocking
	 * forever on the other end of that pipe.
	 */
	if (close_descriptors)
		_fdwalk(set_cloexec, (void *) (intptr_t) 3);
	else
		fd_cloexec(child_err_report_fd, true);

	/* Redirect pipes as required */
	if (stdin_fd >= 0) {
		/* dup2 can't actually fail here I don't think */
		if (sane_dup2 (stdin_fd, 0) < 0)
			write_err_and_exit(child_err_report_fd, CHILD_DUP2_FAILED);

		/* ignore this if it doesn't work */
		close_and_invalidate(&stdin_fd);
	} else if (!child_inherits_stdin) {
		/* Keep process from blocking on a read of stdin */
		int read_null = sane_open("/dev/null", O_RDONLY);

		sane_dup2(read_null, 0);
		close_and_invalidate(&read_null);
	}

	if (stdout_fd >= 0) {
		/* dup2 can't actually fail here I don't think */
	  
		if (sane_dup2(stdout_fd, 1) < 0)
			write_err_and_exit(child_err_report_fd, CHILD_DUP2_FAILED);

		/* ignore this if it doesn't work */
		close_and_invalidate(&stdout_fd);
	} else if (stdout_to_null) {
		int write_null = sane_open("/dev/null", O_WRONLY);

		sane_dup2(write_null, 1);
		close_and_invalidate(&write_null);
	}

	if (stderr_fd >= 0) {
		/* dup2 can't actually fail here I don't think */

		if (sane_dup2(stderr_fd, 2) < 0)
			write_err_and_exit(child_err_report_fd, CHILD_DUP2_FAILED);

		/* ignore this if it doesn't work */
		close_and_invalidate(&stderr_fd);
	} else if (stderr_to_null) {
		int write_null = sane_open("/dev/null", O_WRONLY);

		sane_dup2(write_null, 2);
		close_and_invalidate(&write_null);
	}
 
	my_execute(argv[0], file_and_argv_zero ? argv + 1 : argv, envp, search_path);

	/* Exec failed */
	write_err_and_exit(child_err_report_fd, CHILD_EXEC_FAILED);
}

static int read_ints(int fd,
		     int *buf,
		     int n_ints_in_buf,    
		     int *n_ints_read)      
{
	size_t bytes = 0;    
  
	while (true) {
		ssize_t chunk;    

		if (bytes >= sizeof(int)*2)
			break; /* give up, who knows what happened, should not be
				* possible.
				*/
	  
		chunk = loop_read(fd, ((char*)buf) + bytes,
				  sizeof(int) * n_ints_in_buf - bytes,
				  false);
		if (chunk < 0)
			return chunk;
		else if (chunk == 0)
			break; /* EOF */
		else /* chunk > 0 */
			bytes += chunk;
		
	}

	*n_ints_read = (int)(bytes / sizeof(int));

	return 0;
}

int spawn_async_with_pipes(char *working_directory,
			   char **argv,
			   char **envp,
			   SpawnFlags flags,
			   pid_t *pid,
			   int *in,
			   int *out,
			   int *err)
{
	pid_t _pid = -1;
	int stdin_pipe[2] = { -1, -1 };
	int stdout_pipe[2] = { -1, -1 };
	int stderr_pipe[2] = { -1, -1 };
	int child_err_report_pipe[2] = { -1, -1 };
	int r = 0;
  
	if (pipe(child_err_report_pipe) < 0)
		return -errno;

	if (in && pipe(stdin_pipe) < 0) {
		r = -errno;
		goto cleanup_and_fail;
	}

	if (out && pipe(stdout_pipe) < 0) {
		r = -errno;
		goto cleanup_and_fail;
	}

	if (err && pipe(stderr_pipe) < 0) {
		r = -errno;
		goto cleanup_and_fail;
	}

	_pid = fork();

	if (_pid < 0) {
		r = -errno;
		goto cleanup_and_fail;
	} else if (_pid == 0) {
		/* Reset some signal handlers that we may use */
		default_signals(SIGCHLD, SIGINT, SIGTERM, SIGHUP, -1);
      
		/* Be sure we crash if the parent exits
		 * and we write to the err_report_pipe
		 */
		default_signals(SIGPIPE, -1);

		/* Close the parent's end of the pipes;
		 * not needed in the close_descriptors case,
		 * though
		 */
		close_and_invalidate(&child_err_report_pipe[0]);
		close_and_invalidate(&stdin_pipe[1]);
		close_and_invalidate(&stdout_pipe[0]);
		close_and_invalidate(&stderr_pipe[0]);
      
		do_exec(child_err_report_pipe[1],
			stdin_pipe[0],
			stdout_pipe[1],
			stderr_pipe[1],
			working_directory,
			argv,
			envp,
			!(flags & SPAWN_LEAVE_DESCRIPTORS_OPEN),
			(flags & SPAWN_SEARCH_PATH) != 0,
 			(flags & SPAWN_STDOUT_TO_DEV_NULL) != 0,
			(flags & SPAWN_STDERR_TO_DEV_NULL) != 0,
			(flags & SPAWN_CHILD_INHERITS_STDIN) != 0,
			(flags & SPAWN_FILE_AND_ARGV_ZERO) != 0);

	} else {
		/* Parent */
		int buf[2];
		int n_ints = 0;    

		/* Close the uncared-about ends of the pipes */
		close_and_invalidate(&child_err_report_pipe[1]);
		close_and_invalidate(&stdin_pipe[0]);
		close_and_invalidate(&stdout_pipe[1]);
		close_and_invalidate(&stderr_pipe[1]);

		if ((r = read_ints(child_err_report_pipe[0], buf, 2, &n_ints)) < 0)
			goto cleanup_and_fail;
		
		if (n_ints >= 2) {
			/* Error from the child. */
			switch (buf[0]) {
				case CHILD_CHDIR_FAILED:
				case CHILD_EXEC_FAILED:
				case CHILD_DUP2_FAILED:
				default:
					r = -buf[1];
					break;
			}
			goto cleanup_and_fail;
		}

		/* Success against all odds! return the information */
		close_and_invalidate(&child_err_report_pipe[0]);
	 
		if (_pid)
			*pid = _pid;

		if (in)
			*in = stdin_pipe[1];
		if (out)
			*out = stdout_pipe[0];
		if (err)
			*err = stderr_pipe[0];
      
		return 0;
	}

cleanup_and_fail:
	/* There was an error from the Child, reap the child to avoid it being
	 * a zombie.
	 */

	if (_pid > 0) {
		while ((r = waitpid(_pid, NULL, 0)) < 0) {
			if (errno != EINTR)
				break;
		}
	}

	close_and_invalidate(&child_err_report_pipe[0]);
	close_and_invalidate(&child_err_report_pipe[1]);
	close_and_invalidate(&stdin_pipe[0]);
	close_and_invalidate(&stdin_pipe[1]);
	close_and_invalidate(&stdout_pipe[0]);
	close_and_invalidate(&stdout_pipe[1]);
	close_and_invalidate(&stderr_pipe[0]);
	close_and_invalidate(&stderr_pipe[1]);

	return r;
}

