#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <grp.h>
#include <sys/wait.h>
#include <netax25/axlib.h>
#include <netax25/axconfig.h>

#include "node.h"

#define ECMD_PIPE	1		/* Run through pipe 		*/
#define ECMD_RECONN	2		/* */

static int norm_extcmd(struct cmd *cmdp, char **argv)
{
	int pid;

	alarm(0L);
	pid = fork();
	if (pid == -1) {
		/* fork error */
		node_perror("norm_extcmd: fork", errno);
		return 0;
	}
	if (pid == 0) {
		/* child */
		setgroups(0, NULL);
		setgid(cmdp->gid);
		setuid(cmdp->uid);
		execve(cmdp->path, argv, NULL);
		node_perror("norm_extcmd: execve", errno);
		exit(1);
	}
	/* parent */
	waitpid(pid, NULL, 0);
	return 0;
}

static int pipe_extcmd(struct cmd *cmdp, char **argv)
{
	ax25io *iop;
	int pipe_in[2], pipe_out[2];
	int maxfd, pid, c;
	fd_set rdfdset, wrfdset;
	int pend_nodew = -1, pend_pipew = -1;

	if (pipe(pipe_in) == -1) {
		node_perror("pipe_extcmd: pipe_in", errno);
		return 0;
	}
	if (pipe(pipe_out) == -1) {
		node_perror("pipe_extcmd: pipe_out", errno);
		return 0;
	}

	signal(SIGCHLD, SIG_IGN);

	pid = fork();
	if (pid == -1) {	/* fork error */
		node_perror("pipe_extcmd: fork", errno);
		signal(SIGCHLD, SIG_DFL);
		return 0;
	}
	if (pid == 0) {		/* child */
		/*
		 * Redirect childs output to the pipes closing
		 * stdin/out/err as we go.
		 */
		dup2(pipe_in[0], STDIN_FILENO);
		dup2(pipe_out[1], STDOUT_FILENO);
		dup2(pipe_out[1], STDERR_FILENO);

		/* Close the other ends */
		close(pipe_in[1]);
		close(pipe_out[0]);

		/* Set uid, gid and supplementary groups */
		setgroups(0, NULL);
		setgid(cmdp->gid);
		setuid(cmdp->uid);

		execve(cmdp->path, argv, NULL);
		perror("pipe_extcmd: execve");
		exit(1);
	}

	/* parent */
	close(pipe_in[0]);
	close(pipe_out[1]);

	iop = axio_init(pipe_out[0], pipe_in[1], 1024, UNSPEC_EOL);
	if (iop == NULL) {
		node_perror("pipe_extcmd: Error initializing I/O", -1);

		signal(SIGCHLD, SIG_DFL);
		kill(pid, SIGKILL);

		return 0;
	}

	node_set_nonblock(NodeIo, 1);
	node_set_nonblock(iop, 1);

	maxfd = NodeIo->ifd;
	maxfd = MAX(maxfd, NodeIo->ofd);
	maxfd = MAX(maxfd, iop->ifd);
	maxfd = MAX(maxfd, iop->ofd);
	maxfd++;

	while (1) {
		FD_ZERO(&rdfdset);
		FD_ZERO(&wrfdset);

		FD_SET(NodeIo->ifd, &rdfdset);
		FD_SET(iop->ifd, &rdfdset);

		if (pend_nodew != -1)
			FD_SET(NodeIo->ofd, &wrfdset);
		if (pend_pipew != -1)
			FD_SET(iop->ofd, &wrfdset);

		if (select(maxfd, &rdfdset, &wrfdset, NULL, NULL) == -1) {
			node_perror("pipe_extcmd: select", errno);
			break;
		}

		/* Try to write pending char to pipe */
		if (pend_pipew != -1) {
			if (axio_putc(pend_pipew, iop) != -1)
				pend_pipew = -1;
			else if (errno != EAGAIN)
				break;
		}

		/* If nothing is pending we can try to get new data */
		if (pend_pipew == -1) {
			while ((c = axio_getc(NodeIo)) != -1) {
				alarm(ConnTimeout);
				if (axio_putc(c, iop) == -1) {
					pend_pipew = c;
					break;
				}
			}
			if (errno != EAGAIN)
				break;
		}

		/* Try to write pending char to node connection */
		if (pend_nodew != -1) {
			if (axio_putc(pend_nodew, NodeIo) != -1)
				pend_nodew = -1;
			else if (errno != EAGAIN)
				break;
		}

		/* If nothing is pending we can try to get new data */
		if (pend_nodew == -1) {
			while ((c = axio_getc(iop)) != -1) {
				alarm(ConnTimeout);
				if (axio_putc(c, NodeIo) == -1) {
					pend_nodew = c;
					break;
				}
			}
			if (errno != EAGAIN) {
                                if (errno)
                                        node_msg("%s", strerror(errno));
                                break;
			}

		}

		/* blocking flush */
		if (node_flush(NodeIo) == -1)
			break;
		if (node_flush(iop) == -1)
			break;
	}

	node_set_nonblock(NodeIo, 0);
	node_set_nonblock(iop, 0);

	axio_end(iop);

	signal(SIGCHLD, SIG_DFL);
	kill(pid, SIGKILL);

	return 0;
}

int extcmd(struct cmd *cmdp, char **argv)
{
	int ret;

	User.state = STATE_EXTCMD;
	User.dl_type = AF_UNSPEC;
	strcpy(User.dl_name, cmdp->name);
	strupr(User.dl_name);
	update_user();

	if (cmdp->flags & ECMD_PIPE)
		ret = pipe_extcmd(cmdp, argv);
	else
		ret = norm_extcmd(cmdp, argv);

	if (cmdp->flags & ECMD_RECONN)
		node_msg("Reconnected to %s", HostName);

	return ret;
}
