/* $Id: child.cpp,v 1.12 2004/01/02 04:05:35 fesnel Exp $ */
/*******************************************************************************
 *   This program is part of the XFMail email client.                          *
 *                                                                             *
 *   Copyright : (C) 1995-1998 Gennady B. Sorokopud (gena@NetVision.net.il)    *
 *               (C) 1995 Ugen. J. S. Antsilevich (ugen@latte.worldbank.org)   *
 *               (C) 1998-2004 by the Archimedes Project                       *
 *                   http://sourceforge.net/projects/archimedes                *
 *                                                                             *
 *             --------------------------------------------                    *
 *                                                                             *
 *   This program is free software; you can redistribute it and/or modify      *
 *   it under the terms of the GNU General Public License as published by      *
 *   the Free Software Foundation; either version 2 of the License, or         *
 *   (at your option) any later version.                                       *
 *                                                                             *
 *   This program is distributed in the hope that it will be useful,           *
 *   but WITHOUT ANY WARRANTY, without even the implied warranty of            *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
 *   GNU General Public License for more details.                              *
 *                                                                             *
 *   You should have received a copy of the GNU General Public License         *
 *   along with this program; if not, write to the Free Software               *
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307, USA.  *
 *                                                                             *
 *   Additional Permission granted:                                            *
 *                                                                             *
 *   This program is designed to use the XForms library, so we consider        *
 *   permission to link to that non-GPL-compatible library is implicit.        *
 *   However, in case this is not considered so, we explicitly state:          *
 *                                                                             *
 *   "As a special exception, the Archimedes Project, with the permission      *
 *    of all earlier copyright holders, formally gives permission to link      *
 *    this program with the XForms library, and distribute the resulting       *
 *    executable without the source code for XForms in the source              *
 *    distribution".                                                           *
 *                                                                             *
 ******************************************************************************/



#include <glib.h>

#include <fmail.h>
#include <umail.h>

#ifdef  HAVE_SYS_SELECT_H
	#include <sys/select.h>
#endif

#ifdef  HAVE_VFORK_H
	#include <vfork.h>
#endif

static struct _proc_info *procs[MAX_CHILDREN];
static int pprogress = 0, runprocs = 0, finishprocs = 0;

#define SEL_SEC 10

void close_procs() {
	int i;
	char xbuf[64];

	for(i = 0; i < MAX_CHILDREN; i++) {
		if((procs[i] == NULL) || (procs[i]->pid <= 0))
			continue;

		if(procs[i]->wait != WAIT_ASYNC)
			if(kill(procs[i]->pid, SIGTERM) == -1)
				display_msg(MSG_WARN, "close procs",
							"Failed to kill pid %d", procs[i]->pid);

		if(procs[i] == NULL)
			continue;

		snprintf(xbuf, sizeof(xbuf), "%s/xfexec.%d", tmpdir,
				 procs[i]->pid);
		unlink(xbuf);

		if(procs[i]->ifd > 0)
			close(procs[i]->ifd);

		if(procs[i]->ofd > 0)
			close(procs[i]->ofd);

		if(procs[i]->efd > 0)
			close(procs[i]->efd);

		if(procs[i]->fd_in[1] > 0)
			close(procs[i]->fd_in[1]);

		if(procs[i]->fd_out[0] > 0)
			close(procs[i]->fd_out[0]);

		if(procs[i]->fd_err[0] > 0)
			close(procs[i]->fd_err[0]);

		if(procs[i]->handle)
			(procs[i]->handle) (procs[i]);

		free(procs[i]);
		procs[i] = NULL;
	}

	runprocs = 0;
	finishprocs = 0;
	pprogress = 0;
}

void init_procs() {
	int i;

	for(i = 0; i < MAX_CHILDREN; i++)
		procs[i] = NULL;

	runprocs = 0;
	finishprocs = 0;
	pprogress = 0;
}

void init_pinfo(struct _proc_info *pinfo) {
	if(!pinfo)
		return;

	pinfo->pid = -1;
	pinfo->status = 0;
	pinfo->wait = WAIT_ASYNC;
	pinfo->u_data = NULL;
	pinfo->ul_data = 0;
	pinfo->handle = NULL;
	pinfo->init = NULL;
	pinfo->ifd = -1;
	pinfo->ofd = -1;
	pinfo->efd = -1;
	pinfo->fd_in[0] = -1;
	pinfo->fd_in[1] = -1;
	pinfo->fd_out[0] = -1;
	pinfo->fd_out[1] = -1;
	pinfo->fd_err[0] = -1;
	pinfo->fd_err[1] = -1;

	return;
}

struct _proc_info *dup_pid_info(struct _proc_info *pinfo) {
	struct _proc_info *npinfo;

	if((npinfo = (struct _proc_info *) malloc(sizeof(struct _proc_info)))
	   == NULL) {
		display_msg(MSG_FATAL, "dup_pid_info", "malloc failed");
		return NULL;
	}

	if(pinfo)
		memcpy(npinfo, pinfo, sizeof(struct _proc_info));
	else
		init_pinfo(npinfo);

	npinfo->status = -2;
	npinfo->pid = -1;
	return npinfo;
}

RETSIGTYPE handler(int ret) {
	int i, status, expid;

	pprogress = 1;

	if((expid = wait(&status)) == -1)
		status = -1;

	for(i = 0; i < MAX_CHILDREN; i++) {
		if(procs[i] && ((procs[i]->pid == expid) || (procs[i]->pid == 0))) {
			procs[i]->status = status;
			procs[i]->wait |= WAIT_FINISHED;
			if(runprocs)
				runprocs--;
			finishprocs++;
			break;
		}
	}

	signal(SIGCHLD, handler);
	pprogress = 0;

	return;
}

void check_extprocs() {
	int i, status;
	char xbuf[64], pbuf[32], buf[255];
	FILE *xfd;

	if(pprogress || !finishprocs)
		return;

	for(i = 0; i < MAX_CHILDREN; i++) {
		if(procs[i] && (procs[i]->wait & WAIT_FINISHED)) {
			if(finishprocs)
				finishprocs--;
			status = procs[i]->status;
			procs[i]->wait &= ~WAIT_FINISHED;
			snprintf(xbuf, sizeof(xbuf), "%s/xfexec.%d", tmpdir,
					 procs[i]->pid);
			snprintf(pbuf, sizeof(pbuf), "pid %d", procs[i]->pid);
			if((xfd = fopen(xbuf, "r")) != NULL) {
				while(fgets(buf, 255, xfd)) {
					strip_newline(buf);
					display_msg(MSG_LOG, pbuf, "%-.127s", buf);
				}
				fclose(xfd);
			}
			unlink(xbuf);

			if(procs[i]->ifd > 0)
				close(procs[i]->ifd);

			if(procs[i]->ofd > 0)
				close(procs[i]->ofd);

			if(procs[i]->efd > 0)
				close(procs[i]->efd);

			/*
			   if (procs[i]->fd_in[1] > 0)
				close(procs[i]->fd_in[1]);
			*/

			if(status == -1)
				display_msg(MSG_WARN, "exec",
							"Child process was not terminated normally (See log for output)");
			else if(WIFEXITED(status) && (procs[i]->fd_err[0] == -1)) {
				if(WEXITSTATUS(status) != 0) {
					display_msg(MSG_WARN, "exec",
								"Child process terminated with status %d (See log for output)",
								WEXITSTATUS(status));
					display_msg(MSG_LOG, "exec_child failure", "Could not start \"%s\"", procs[i]->name);
				}
			}

			procs[i]->status = WEXITSTATUS(status);
			if(procs[i]->handle)
				(procs[i]->handle) (procs[i]);

			if(procs[i]->wait == WAIT_ASYNC) {
				free(procs[i]);
				procs[i] = NULL;
			}
		}
	}

	return;
}

int exec_child(char *str, struct _proc_info *pinfo) {
	struct _proc_info *npinfo;
	char buf[MAX_EXEC_STRING];
	char **namelist;
	char **ap;
	char *p, c;
	int params;
	int i, pnum;
	int nfd;

	if(!str || !*str || pprogress)
		return -1;

	display_msg(MSG_LOG, "exec_child", "Preparing to start \"%s\"", str);

	for(pnum = 0; pnum < MAX_CHILDREN; pnum++) {
		if(procs[pnum] == NULL)
			break;
	}

	if(procs[pnum] != NULL) {
		display_msg(MSG_WARN, "exec_child",
					"Can not run more then %d child processes",
					MAX_CHILDREN);
		return -1;
	}

	if((procs[pnum] = dup_pid_info(pinfo)) == NULL)
		return -1;

	npinfo = procs[pnum];

	if(strlen(str) > sizeof(buf)) {
		display_msg(MSG_WARN, "exec", "too many arguments");
		free(procs[pnum]);
		procs[pnum] = NULL;
		return -1;
	}

	str = rem_tr_space(str);
	p = str;

	params = 1;
	i = 0;
	buf[0] = '\0';

	while(*p != '\0') {
		c = *p;
		switch(c) {
			case ' ':
				buf[i] = '\0';
				i++;
				params++;
				p++;
				while(*p == ' ')
					p++;

				break;

			case '\\':
				p++;
				if((*p == '\\') || (*p == '"') || (*p == '\'')) {
					buf[i] = '\\';
					i++;
					p++;
					break;
				}
				buf[i] = c;
				i++;
				break;

			case '\'':
			case '"':
				buf[i] = c;
				i++;
				p++;

				if(strchr(p, c) == NULL)
					break;

				i--;
				while(*p != c) {
					buf[i] = *p;
					i++;
					p++;
				}

				p++;
				break;

			default:
				buf[i] = c;
				i++;
				p++;
				break;
		}
	}

	if(!*buf) {
		display_msg(MSG_WARN, "exec", "empty command");
		free(procs[pnum]);
		procs[pnum] = NULL;
		return -1;
	}

	buf[i] = '\0';
	buf[i + 1] = '\0';

	params += 2;
	namelist = (char **) malloc(params * sizeof(char *));
	ap = namelist;

	p = buf;
	while(*p != '\0') {
		*ap++ = p;
		p += strlen(p);
		p++;
	}

	*ap = NULL;

	signal(SIGCHLD, handler);

	if(npinfo->fd_in[0] == 0) {
		if(pipe(npinfo->fd_in) == -1) {
			display_msg(MSG_WARN, "exec", "Can not open pipe");
			free(procs[pnum]);
			procs[pnum] = NULL;
			return -1;
		}
		if(pinfo)
			pinfo->fd_in[1] = npinfo->fd_in[1];
	}

	if(npinfo->fd_out[0] == 0) {
		if(pipe(npinfo->fd_out) == -1) {
			display_msg(MSG_WARN, "exec", "Can not open pipe");
			free(procs[pnum]);
			procs[pnum] = NULL;
			return -1;
		}
		if(pinfo)
			pinfo->fd_out[0] = npinfo->fd_out[0];
	}

	if(npinfo->fd_err[0] == 0) {
		if(pipe(npinfo->fd_err) == -1) {
			display_msg(MSG_WARN, "exec", "Can not open pipe");
			free(procs[pnum]);
			procs[pnum] = NULL;
			return -1;
		}
		if(pinfo)
			pinfo->fd_err[0] = npinfo->fd_err[0];
	}

	npinfo->pid = 0;
	runprocs++;

	npinfo->pid = vfork();
	if(npinfo->pid == -1) {
		display_msg(MSG_WARN, "exec", "Can not fork");
		free(namelist);
		if(runprocs)
			runprocs--;
		return -1;
	}

	if(npinfo->pid == 0) {
		char xbuf[64];

		if(npinfo->fd_in[0] > 0) {
			dup2(npinfo->fd_in[0], 0);
			close(npinfo->fd_in[1]);
		}

		if(npinfo->fd_out[0] > 0) {
			dup2(npinfo->fd_out[1], 1);
			close(npinfo->fd_out[0]);
		}

		if(npinfo->fd_err[0] > 0) {
			dup2(npinfo->fd_err[1], 2);
			close(npinfo->fd_err[0]);
		}

		snprintf(xbuf, sizeof(xbuf), "%s/xfexec.%d", tmpdir,
				 (int) getpid());
		if((nfd = open(xbuf, O_RDWR | O_CREAT | O_TRUNC, 00600)) != -1) {
			if((npinfo->fd_out[0] <= 0) && (npinfo->ofd <= 0))
				dup2(nfd, 1);
			if((npinfo->fd_err[0] <= 0) && (npinfo->efd <= 0))
				dup2(nfd, 2);
		}

		if((npinfo->fd_in[0] <= 0) && (npinfo->ifd > 0))
			dup2(npinfo->ifd, 0);

		if((npinfo->fd_out[0] <= 0) && (npinfo->ofd > 0))
			dup2(npinfo->ofd, 1);

		if((npinfo->fd_err[0] <= 0) && (npinfo->efd > 0))
			dup2(npinfo->efd, 2);

		if(npinfo->init)
			npinfo->init(npinfo);

		memset(npinfo->name,'0',MAX_EXEC_STRING);
		strncpy(npinfo->name,buf,MAX_EXEC_STRING);

		signal(SIGHUP, SIG_IGN);
		signal(SIGINT, SIG_IGN);
		signal(SIGQUIT, SIG_IGN);
#ifndef __EMX__
		signal(SIGTTIN, SIG_IGN);
		signal(SIGTTOU, SIG_IGN);
#endif
		execvp(buf, namelist);
		unlink(xbuf);
		_exit(2);
	}

	pinfo->pid = npinfo->pid;

	if(npinfo->fd_in[0] > 0)
		close(npinfo->fd_in[0]);

	if(npinfo->fd_out[1] > 0)
		close(npinfo->fd_out[1]);

	if(npinfo->fd_err[1] > 0)
		close(npinfo->fd_err[1]);

	free(namelist);

	if(npinfo->wait == WAIT_ASYNC)
		return 0;

	my_check_forms(npinfo);

	i = npinfo->status;

	free(procs[pnum]);
	procs[pnum] = NULL;

	return i;
}

	  
void my_check_forms(struct _proc_info *npinfo) {
	struct timeval t;
	  
#if defined(__linux__) || defined (__EMX__)
	fd_set read_fds;
#else
	struct fd_set read_fds;
#endif
	int xconn = 0;

	if(!npinfo)
		return;

	if(npinfo->wait == WAIT_BG) {
		check_extprocs();
		fl_check_forms(); 
	}

	xconn = ConnectionNumber(fl_display);
	FD_ZERO(&read_fds);
	FD_SET(xconn, &read_fds);

	while(npinfo->status == -2) {
		t.tv_sec = SEL_SEC;
		t.tv_usec = 0;
/* I am getting rid of all HPUX9 traces */
#if defined(__linux__) | defined (__EMX__)
		if(select(xconn + 1, &read_fds, (fd_set *) 0, (fd_set *) 0, &t) <
	  	  0) {
#else
		if(select (xconn + 1, &read_fds, (struct fd_set *) 0, (struct fd_set *) 0, &t) <
		  0) {
#endif
			if(errno == EINTR) {
				if(npinfo->wait == WAIT_BG) {
					check_extprocs();
					fl_check_forms();
				}
				if(npinfo->status != -2)
					break;
				else
					continue;
			}
		}
		if(npinfo->wait == WAIT_BG) {
			check_extprocs();
			if(abortpressed()) {
				if(npinfo->pid > 0)
					kill(npinfo->pid, SIGKILL);
				return;
			}
		}
	}

	check_extprocs();
	return;
}

int my_check_io_forms(int fd, int type, int timeout) {
	struct timeval t, *tptr = NULL;
#if defined(__linux__) || defined(__EMX__)
	fd_set read_fds, write_fds, except_fds;
#else
	struct fd_set read_fds, write_fds, except_fds;
#endif
	int xconn = 0, res;

	if(fd < 0)
		return -1;

	if(timeout > 0) {
		t.tv_sec = timeout;
		t.tv_usec = 0;
		tptr = &t;
	}

	xconn = ConnectionNumber(fl_display);

	fwait:
	FD_ZERO(&read_fds);
	FD_ZERO(&write_fds);

	switch(type) {
		case 0:
			FD_SET(fd, &read_fds);
			break;

		case 1:
			FD_SET(fd, &write_fds);
			break;

		case 2:
			FD_SET(fd, &read_fds);
			FD_SET(fd, &write_fds);
			break;
	}

	FD_ZERO(&except_fds);
	FD_SET(fd, &except_fds);

	FD_SET(xconn, &read_fds);

	if((res = select(fd > xconn ? fd + 1 : xconn + 1, &read_fds, &write_fds,
	  &except_fds, tptr)) <= 0) {
		
		if(errno == EINTR)
			return 0;

		if(res == 0)
			return -3;

		return -1;
	}


	if(FD_ISSET(fd, &except_fds))
		return -1;

	if(FD_ISSET(xconn, &read_fds)) {
		if(abortpressed())
			return -2;
	}

	if(FD_ISSET(fd, &read_fds) || FD_ISSET(fd, &write_fds))
		return 0;

	goto fwait;

	return 0;
}

void my_check_forms2() {
	struct timeval t;
#if defined(__linux__) || defined (__EMX__)
	fd_set read_fds;
#else
	struct fd_set read_fds;
#endif
	int xconn = 0;

	xconn = ConnectionNumber(fl_display);
	FD_ZERO(&read_fds);
	FD_SET(xconn, &read_fds);

	t.tv_sec = 0;
	t.tv_usec = 0;

#if defined(__linux__) | defined (__EMX__)
	if(select(xconn + 1, &read_fds, (fd_set *) 0, (fd_set *) 0, &t) == 1)
#else
	if(select(xconn + 1, &read_fds, (struct fd_set *) 0, (struct fd_set *) 0,
	  &t) == 1) 
#endif
		fl_check_forms(); 

	return;
}
