/***************************************************************************

  gbx_c_process.c

  (c) 2000-2017 Benoît Minisini <g4mba5@gmail.com>

  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, 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., 51 Franklin Street, Fifth Floor, Boston,
  MA 02110-1301, USA.

***************************************************************************/

#define __GBX_C_PROCESS_C

#include "gbx_info.h"

#ifndef GBX_INFO

#include "gb_common.h"
#include "gb_common_buffer.h"

#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>

#include "gb_replace.h"
#include "gb_limit.h"
#include "gb_array.h"
#include "gbx_api.h"
#include "gambas.h"
#include "gbx_stream.h"
#include "gbx_exec.h"
#include "gbx_class.h"
#include "gbx_watch.h"
#include "gbx_project.h"
#include "gbx_c_array.h"
#include "gbx_local.h"
#include "gbx_signal.h"
#include "gbx_event.h"

#include "gbx_c_process.h"

//#define DEBUG_ME
//#define DEBUG_CHILD

char *CPROCESS_shell = NULL;

extern char **environ;

DECLARE_EVENT(EVENT_Read);
DECLARE_EVENT(EVENT_Error);
DECLARE_EVENT(EVENT_Kill);

static CPROCESS *_running_process_list = NULL;
static int _running_process = 0;
static int _ignore_process = 0;

static SIGNAL_CALLBACK *_SIGCHLD_callback;
static bool _init = FALSE;

static int _last_status = 0;

static int _last_child_error = 0;
static int _last_child_error_errno = 0;

static void init_child(void);
static void exit_child(void);
static bool wait_child(CPROCESS *process);

enum {
	CHILD_NO_ERROR,
	CHILD_CANNOT_OPEN_TTY,
	CHILD_CANNOT_INIT_TTY,
	CHILD_CANNOT_PLUG_INPUT,
	CHILD_CANNOT_PLUG_OUTPUT,
	CHILD_CANNOT_EXEC,
};

static const char *const _child_error[] = {
	NULL,
	"cannot open slave pseudo-terminal: ",
	"cannot initialize pseudo-terminal: ",
	"cannot plug standard input: ",
	"cannot plug standard output and standard error: ",
	"cannot run executable: "
};

//-------------------------------------------------------------------------

static void close_fd(int *pfd)
{
	int fd = *pfd;

	if (fd >= 0)
	{
		#ifdef DEBUG_ME
		fprintf(stderr, "unwatch & close: %d\n", fd);
		#endif
		GB_Watch(fd, GB_WATCH_NONE, NULL, 0);
		close(fd);
		*pfd = -1;
	}
}

static void add_process_to_running_list(CPROCESS *process)
{
	if (_running_process_list)
		_running_process_list->prev = process;

	process->next = _running_process_list;
	process->prev = NULL;

	_running_process_list = process;

	process->running = TRUE;
	_running_process++;
}

static void remove_process_from_running_list(CPROCESS *process)
{
	if (process->prev)
		process->prev->next = process->next;

	if (process->next)
		process->next->prev = process->prev;

	if (process == _running_process_list)
		_running_process_list = process->next;

	process->running = FALSE;
	
	_running_process--;
	if (process->ignore)
		_ignore_process--;
}

static void callback_write(int fd, int type, CPROCESS *process)
{
	#ifdef DEBUG_ME
	fprintf(stderr, "callback_write: %d %p\n", fd, process);
	#endif

	if (process->to_string)
	{
		int n;

		for(;;)
		{
			n = read(fd, COMMON_buffer, 256);
			if (n >= 0 || errno != EINTR)
				break;
		}

		if (n > 0)
		{
			process->result = STRING_add(process->result, COMMON_buffer, n);
			return;
		}
	}

	if (GB_CanRaise(process, EVENT_Read))
	{
		if (!STREAM_read_ahead(CSTREAM_TO_STREAM(process)))
		{
			GB_Raise(process, EVENT_Read, 0);
			return;
		}
	}

	close_fd(&process->out);
}


static void callback_error(int fd, int type, CPROCESS *process)
{
	char buffer[256];
	int n;

	#ifdef DEBUG_ME
	fprintf(stderr, "callback_error: %d %p\n", fd, process);
	#endif

	if (process->to_string && process->with_error)
	{
		int n;

		for(;;)
		{
			n = read(fd, COMMON_buffer, 256);
			if (n >= 0 || errno != EINTR)
				break;
		}

		if (n > 0)
		{
			process->result = STRING_add(process->result, COMMON_buffer, n);
			return;
		}
	}

	if (GB_CanRaise(process, EVENT_Error))
	{
		n = read(fd, buffer, sizeof(buffer));
		if (n > 0)
		{
			GB_Raise(process, EVENT_Error, 1, GB_T_STRING, buffer, n);
			return;
		}
	}

	close_fd(&process->err);
}


static void update_stream(CPROCESS *process)
{
	STREAM *stream = &process->ob.stream;

	stream->type = &STREAM_process;
	(*stream->type->open)(stream, NULL, 0, process);
}


static void init_process(CPROCESS *process)
{
	//process->watch = GB_WATCH_NONE;
	process->in = process->out = process->err = -1;
	update_stream(process);
}

static void exit_process(CPROCESS *_object)
{
	#ifdef DEBUG_ME
	fprintf(stderr, "exit_process: %p\n", _object);
	#endif

	if (THIS->in >= 0)
	{
		if (THIS->in != THIS->out)
			close(THIS->in);
		THIS->in = -1;
	}

	close_fd(&THIS->out);
	close_fd(&THIS->err);
	
	STREAM_close(&THIS->ob.stream);
}

static void prepare_child_error(CPROCESS *_object)
{
	int fd;
	char path[PATH_MAX];

	snprintf(path, sizeof(path), FILE_TEMP_DIR "/%d.child", (int)getuid(), (int)getpid(), (int)THIS->pid);

	#ifdef DEBUG_ME
	fprintf(stderr, "prepare_child_error: %p: %s\n", _object, path);
	#endif

	if (_last_child_error == 0)
	{
		fd = open(path, O_RDONLY);
		if (fd >= 0)
		{
			if (read(fd, &_last_child_error, sizeof(int)) != sizeof(int)
					|| read(fd, &_last_child_error_errno, sizeof(int)) != sizeof(int))
			{
				_last_child_error = -1;
				_last_child_error_errno = 0;
			}

			close(fd);
		}

		#ifdef DEBUG_ME
		fprintf(stderr, "prepare_child_error: error = %d errno = %d\n", _last_child_error, _last_child_error_errno);
		#endif
	}

	unlink(path);
}

static void throw_last_child_error()
{
	int child_error, child_errno;

	if (_last_child_error == 0)
		return;

	child_error = _last_child_error;
	child_errno = _last_child_error_errno;

	_last_child_error = 0;

	#ifdef DEBUG_ME
	fprintf(stderr, "throw_last_child_error: %d %d\n", child_error, child_errno);
	#endif

	if (child_error < 0)
		THROW(E_CHILD, "unknown error", "");
	else
		THROW(E_CHILD, _child_error[child_error], strerror(child_errno));
}

static void stop_process_after(CPROCESS *_object)
{
	STREAM *stream;
	bool do_exit_process = THIS->in >= 0;

	#ifdef DEBUG_ME
	fprintf(stderr, "stop_process_after: %p  out = %d err = %d\n", _object, THIS->out, THIS->err);
	#endif

	if (WIFEXITED(THIS->status) && WEXITSTATUS(THIS->status) == 255)
	{
		prepare_child_error(THIS);
		exit_process(THIS);
		//OBJECT_detach((OBJECT *)THIS);
	}

	/* Vidage du tampon d'erreur */
	while (THIS->err >= 0)
	{
		callback_error(THIS->err, 0, THIS);
		do_exit_process = TRUE;
	}

	/* Vidage du tampon de sortie */
	if (THIS->out >= 0)
	{
		stream = CSTREAM_TO_STREAM(THIS);
		while (THIS->out >= 0 && !STREAM_is_closed(stream))
			callback_write(THIS->out, 0, THIS);

		do_exit_process = TRUE;
	}

	if (do_exit_process)
		exit_process(THIS);

	/*printf("** stop_process_after\n");*/
	//GB_Unref((void **)&_object); /* Ref du post */
}


static void stop_process(CPROCESS *_object)
{
	if (!THIS->running)
		return;

	#ifdef DEBUG_ME
	fprintf(stderr, "stop_process: %p\n", THIS);
	#endif

	/* Remove from running process list */

	stop_process_after(THIS);
	remove_process_from_running_list(THIS);

	#ifdef DEBUG_ME
	fprintf(stderr, "Raising Kill event for %p: parent = %p  can raise = %d\n", THIS, OBJECT_parent(THIS), GB_CanRaise(THIS, EVENT_Kill));
	#endif
	GB_Raise(THIS, EVENT_Kill, 0);

	OBJECT_detach((OBJECT *)THIS);
	OBJECT_UNREF(_object);

	//if (!_running_process_list)
	if (_running_process <= _ignore_process)
		exit_child();
}

static void abort_child(int error)
{
	int fd;
	int save_errno;
	char path[PATH_MAX];

	fflush(stdout);
	fflush(stderr);

	save_errno = errno;

	#ifdef DEBUG_ME
	fprintf(stderr, "abort_child: %d %d\n", error, save_errno);
	#endif

	snprintf(path, sizeof(path), FILE_TEMP_DIR "/%d.child", (int)getuid(), (int)getppid(), (int)getpid());

	fd = open(path, O_CREAT | O_WRONLY, 0600);
	if (fd >= 0)
	{
		write(fd, &error, sizeof(int)) == sizeof(int)
		&& write(fd, &save_errno, sizeof(int)) == sizeof(int);
		close(fd);
	}

	_exit(255);
}

static void init_child_tty(int fd)
{
	struct termios terminal = { 0 };
	struct termios check;
	
	tcgetattr(fd, &terminal);

	terminal.c_iflag |= IXON | IXOFF | ICRNL;
	#ifdef IUTF8
	if (LOCAL_is_UTF8)
		terminal.c_iflag |= IUTF8;
	#endif

	terminal.c_oflag |= OPOST;
	//terminal.c_oflag &= ~ONLCR;

	terminal.c_lflag |= ISIG | ICANON | IEXTEN | ECHO;
	///terminal.c_lflag &= ~ECHO;

	#ifdef DEBUG_CHILD
	fprintf(stderr, "init_child_tty: %s\n", isatty(fd) ? ttyname(fd) : "not a tty!");
	#endif

	if (tcsetattr(fd, TCSANOW, &terminal))
	{
		#ifdef DEBUG_CHILD
		int save_errno = errno;
		fprintf(stderr, "init_child_tty: errno = %d\n", errno);
		errno = save_errno;
		#endif
		abort_child(CHILD_CANNOT_INIT_TTY);
	}
	
	tcgetattr(fd, &check);
	if (check.c_iflag != terminal.c_iflag || check.c_oflag != terminal.c_oflag || check.c_lflag != terminal.c_lflag)
		abort_child(CHILD_CANNOT_INIT_TTY);
}

const char *CPROCESS_search_program_in_path(char *name)
{
	char *search;
	char *p, *p2;
	int len;
	const char *path;
	
	if (!name)
		return NULL;

	if (strchr(name, '/'))
	{
		if (access(name, X_OK) == 0)
			return name;
		else
			return NULL;
	}

	search = getenv("PATH");
	if (!search || !*search)
		search = "/usr/bin:/bin";

	search = STRING_new_zero(search);

	path = NULL;

	//fprintf(stderr, "search_program_in_path: '%s' in '%s'\n", name, search);

	p = search;
	for(;;)
	{
		p2 = strchr(p, ':');
		if (p2)
			len = p2 - p;
		else
			len = strlen(p);

		if (len > 0)
		{
			p[len] = 0;
			//fprintf(stderr, "trying: %s\n", p);
			path = FILE_cat(p, name, NULL);
			if (access(path, X_OK) == 0)
				break;
			path = NULL;
		}

		if (!p2)
			break;

		p = p2 + 1;
	}

	STRING_free(&search);
	//fprintf(stderr, "--> %s\n", path);
	return path;
}

static void run_process(CPROCESS *process, int mode, void *cmd, CARRAY *env)
{
	static const char *shell[] = { NULL, "-c", NULL, NULL };

	int fdin[2], fdout[2], fderr[2];
	pid_t pid;
	char **argv;
	CARRAY *array;
	int i, n;
	sigset_t sig, old;
	int save_errno;

	// for virtual terminal
	int fd_master = -1;
	char *slave = NULL;
	//struct termios termios_stdin;
	//struct termios termios_check;
	struct termios termios_master;
	const char *exec;

	if (mode & PM_SHELL)
	{
		#ifdef DEBUG_ME
		fprintf(stderr, "run_process %p: %s\n", process, (char *)cmd);
		#endif

		argv = (char **)shell;

		if (CPROCESS_shell)
			argv[0] = CPROCESS_shell;
		else
			argv[0] = "/bin/sh";

		argv[2] = (char *)cmd;

		if (argv[2] == NULL || *argv[2] == 0)
			return;

		exec = argv[0];

		process->process_group = TRUE;
	}
	else
	{
		array = (CARRAY *)cmd;
		n = ARRAY_count(array->data);

		if (n == 0)
			return;

		ALLOC(&argv, sizeof(*argv) * (n + 1));
		memcpy(argv, array->data, sizeof(*argv) * n);
		argv[n] = NULL;

		exec = CPROCESS_search_program_in_path(argv[0]);
		if (!exec)
		{
			IFREE(argv);
			THROW(E_NEXIST);
		}

		for (i = 0; i < n; i++)
		{
			if (!argv[i])
				argv[i] = "";
		}

		#ifdef DEBUG_ME
		{
			int i;

			fprintf(stderr, "run_process %p: ", process);
			for (i = 0; i < n; i++)
				fprintf(stderr, "%s ", argv[i]);
			fprintf(stderr, "\n");
		}
		#endif
	}

	if (mode & PM_STRING)
	{
		process->to_string = TRUE;
		process->with_error = (mode & PM_ERROR) != 0;
		process->result = NULL;
		mode |= PM_READ;
	}

	if (mode & PM_TERM)
	{
		fd_master = posix_openpt(O_RDWR | O_NOCTTY);
		if (fd_master < 0)
			goto __ABORT_ERRNO;

		grantpt(fd_master);
		unlockpt(fd_master);
		slave = ptsname(fd_master);
		#ifdef DEBUG_ME
		fprintf(stderr, "run_process: slave = %s\n", slave);
		#endif
		
		if (mode & PM_TERM)
		{
			if (tcgetattr(fd_master, &termios_master))
				goto __ABORT_ERRNO;

			cfmakeraw(&termios_master);
			//termios_master.c_lflag &= ~ECHO;

			if (tcsetattr(fd_master, TCSANOW, &termios_master))
				goto __ABORT_ERRNO;
		}
	}
	else
	{
		/* Create pipes */

		if ((mode & PM_WRITE) && pipe(fdin) != 0)
			goto __ABORT_ERRNO;

		if ((mode & PM_READ) && (pipe(fdout) != 0 || pipe(fderr) != 0))
			goto __ABORT_ERRNO;
	}

	// Adding to the running process list

	add_process_to_running_list(process);
	OBJECT_REF(process);

	// Start the SIGCHLD callback

	init_child();

	// Block SIGCHLD and fork

	sigemptyset(&sig);

	sigaddset(&sig, SIGCHLD);
	sigprocmask(SIG_BLOCK, &sig, &old);

	if (mode & PM_SHELL || mode & PM_TERM || env)
		pid = fork();
	else
		pid = vfork();

	if (pid == (-1))
	{
		stop_process(process);
		sigprocmask(SIG_SETMASK, &old, NULL);
		goto __ABORT_ERRNO;
	}

	// parent process

	if (pid)
	{
		process->pid = pid;

		#ifdef DEBUG_ME
		fprintf(stderr, "fork: pid = %d\n", pid);
		#endif

		if (mode & PM_WRITE)
		{
			if (mode & PM_TERM)
			{
				process->in = fd_master;
			}
			else
			{
				close(fdin[0]);
				process->in = fdin[1];
			}
		}

		if (mode & PM_READ)
		{
			if (mode & PM_TERM)
			{
				process->out = fd_master;
				process->err = -1;
			}
			else
			{
				close(fdout[1]);
				close(fderr[1]);

				process->out = fdout[0];
				process->err = fderr[0];
			}

			#ifdef DEBUG_ME
			fprintf(stderr, "watch: out = %d err = %d\n", process->out, process->err);
			#endif
			GB_Watch(process->out, GB_WATCH_READ, (void *)callback_write, (intptr_t)process);
			if (process->err >= 0)
			{
				fcntl(process->err, F_SETFL, fcntl(process->err, F_GETFL) | O_NONBLOCK);
				GB_Watch(process->err, GB_WATCH_READ, (void *)callback_error, (intptr_t)process);
			}
		}

		if ((mode & PM_SHELL) == 0)
		{
			FREE(&argv);
		}

		sigprocmask(SIG_SETMASK, &old, NULL);
	}
	else //------------ child process ----------------------------
	{
		int fd_slave;
		bool pwd;
		int ch_i, ch_n;

		//bool stdin_isatty = isatty(STDIN_FILENO);

		sigprocmask(SIG_SETMASK, &old, NULL);

		if (mode & PM_SHELL)
			setpgid(0, 0);

		if (mode & PM_TERM)
		{
			close(fd_master);
			setsid();
			fd_slave = open(slave, O_RDWR);
			if (fd_slave < 0)
				abort_child(CHILD_CANNOT_OPEN_TTY);
			
			init_child_tty(fd_slave);

			/*#ifdef DEBUG_ME
			fprintf(stderr, "run_process (child): slave = %s isatty = %d\n", slave, isatty(fd_slave));
			#endif*/

			if (mode & PM_WRITE)
			{
				if (dup2(fd_slave, STDIN_FILENO) == -1)
					abort_child(CHILD_CANNOT_PLUG_INPUT);
			}

			if (mode & PM_READ)
			{
				if ((dup2(fd_slave, STDOUT_FILENO) == -1)
						|| (dup2(fd_slave, STDERR_FILENO) == -1))
					abort_child(CHILD_CANNOT_PLUG_OUTPUT);
			}

			// Strange Linux behaviour ?
			// Terminal initialization must be done on STDIN_FILENO after using dup2().
			// If it is done on fd_slave, before using dup2(), it sometimes fails with no error.

			/*if (mode & PM_WRITE)
				init_child_tty(STDIN_FILENO);
			else if (mode & PM_READ)
				init_child_tty(STDOUT_FILENO);*/

			/*puts("---------------------------------");
			if (stdin_isatty) puts("STDIN is a tty");*/
			/*tcgetattr(STDIN_FILENO, &termios_check);
			puts(termios_check.c_lflag & ISIG ? "+ISIG" : "-ISIG");
			//tcsetattr(STDIN_FILENO, TCSADRAIN, &termios_check);
			system("stty icanon");
			system("stty -a");
			puts("---------------------------------");*/
		}
		else
		{
			if (mode & PM_WRITE)
			{
				close(fdin[1]);

				if (dup2(fdin[0], STDIN_FILENO) == -1)
					abort_child(CHILD_CANNOT_PLUG_INPUT);
			}

			if (mode & PM_READ)
			{
				close(fdout[0]);
				close(fderr[0]);

				if ((dup2(fdout[1], STDOUT_FILENO) == -1)
						|| (dup2(fderr[1], STDERR_FILENO) == -1))
					abort_child(CHILD_CANNOT_PLUG_OUTPUT);
			}
		}

		pwd = FALSE;

		if (env)
		{
			char *str;
			ch_n = ARRAY_count(env->data);
			for (ch_i = 0; ch_i < ch_n; ch_i++)
			{
				str = ((char **)env->data)[ch_i];
				if (putenv(str))
					ERROR_warning("cannot set environment string: %s", str);
				if (strncmp(str, "PWD=", 4) == 0 && chdir(&str[4]) == 0)
					pwd = TRUE;
			}
		}

		// Return to the parent working directory if the PWD environment variable has not been set
		if (!pwd)
			FILE_chdir(PROJECT_oldcwd);

		execv(exec, (char **)argv);
		abort_child(CHILD_CANNOT_EXEC);
	}

	update_stream(process);

	#ifdef DEBUG_ME
	fprintf(stderr, "run_process: child is OK\n");
	#endif

	return;

__ABORT_ERRNO:

	save_errno = errno;
	stop_process(process);
	THROW_SYSTEM(save_errno, NULL);
}

void CPROCESS_check(void *_object)
{
	#ifdef DEBUG_ME
	fprintf(stderr, "CPROCESS_check: %p\n", THIS);
	#endif

	usleep(100);
	if (wait_child(THIS))
	{
		#ifdef DEBUG_ME
		fprintf(stderr, "CPROCESS_check: stop process later\n");
		#endif
		stop_process_after(THIS);
		EVENT_post(stop_process, (intptr_t)THIS);
		throw_last_child_error();
	}

	//fprintf(stderr, "CPROCESS_check: %p END\n", THIS);
}

static bool wait_child(CPROCESS *process)
{
	int status;
	int ret;
	
	#ifdef DEBUG_ME
	fprintf(stderr, "wait_child: check process %d\n", process->pid);
	#endif

	if ((ret = waitpid(process->pid, &status, WNOHANG)) == process->pid)
	{
		process->status = status;
		process->wait = TRUE;
		_last_status = status;

		#ifdef DEBUG_ME
		fprintf(stderr, "wait_child: process %d has returned %d\n", process->pid, status);
		#endif

		return TRUE;
	}
	else
	{
		#ifdef DEBUG_ME
		fprintf(stderr, "wait_child: waitpid() has returned %d\n", ret);
		#endif

		return FALSE;
	}
}

static void callback_child(int signum, intptr_t data)
{
	CPROCESS *process, *next;

	#ifdef DEBUG_ME
	fprintf(stderr, ">> callback_child\n");
	#endif

	for (process = _running_process_list; process; )
	{
		next = process->next;
		if (wait_child(process))
			stop_process(process);
		process = next;
	}
	
	throw_last_child_error();

	#ifdef DEBUG_ME
	fprintf(stderr, "<< callback_child\n");
	#endif
}

static void init_child(void)
{
	if (_init)
		return;

	#ifdef DEBUG_ME
	fprintf(stderr, "init_child()\n");
	#endif

	_SIGCHLD_callback = SIGNAL_register(SIGCHLD, callback_child, 0);

	_init = TRUE;
}

static void exit_child(void)
{
	if (!_init)
		return;

	#ifdef DEBUG_ME
	fprintf(stderr, "exit_child()\n");
	#endif

	SIGNAL_unregister(SIGCHLD, _SIGCHLD_callback);

	_init = FALSE;
}


static void error_CPROCESS_create(CPROCESS *process)
{
	OBJECT_UNREF(process);
}

CPROCESS *CPROCESS_create(int mode, void *cmd, char *name, CARRAY *env)
{
	CPROCESS *process;

	process = OBJECT_new(CLASS_Process, name, OP  ? (OBJECT *)OP : (OBJECT *)CP);
	//fprintf(stderr, "CPROCESS_create: %p\n", process);

	ON_ERROR_1(error_CPROCESS_create, process)
	{
		init_process(process);
		run_process(process, mode, cmd, env);
	}
	END_ERROR

	OBJECT_UNREF_KEEP(process);

	if (!name || !*name)
		STREAM_blocking(CSTREAM_TO_STREAM(process), TRUE);

	return process;
}

void CPROCESS_wait_for(CPROCESS *process, int timeout)
{
	int ret;
	int sigfd;

	#ifdef DEBUG_ME
	fprintf(stderr, "Waiting for %d\n", process->pid);
	#endif
	
	// If CPROCESS_check() caught the process end, process->running is not set yet, because
	// stop_process() will be raised at the next event loop. So no need to wait for it.
	
	if (process->wait)
		return;

	OBJECT_REF(process);

	sigfd = SIGNAL_get_fd();

	ON_ERROR_1(error_CPROCESS_create, process)
	{
		while (process->running)
		{
			#ifdef DEBUG_ME
			fprintf(stderr, "Watch process %d\n", process->pid);
			#endif
			ret = WATCH_process(sigfd, process->out, process->err, timeout);
			#ifdef DEBUG_ME
			fprintf(stderr, "Watch process %d ->%s%s%s%s\n", process->pid, ret & WP_END ? " END" : "", ret & WP_OUTPUT ? " OUTPUT" : "", ret & WP_ERROR ? " ERROR" : "", ret & WP_TIMEOUT ? " TIMEOUT" : "");
			#endif

			if (ret & WP_OUTPUT)
				callback_write(process->out, GB_WATCH_READ, process);

			if (ret & WP_ERROR)
				callback_error(process->err, GB_WATCH_READ, process);

			if (ret & WP_END)
				SIGNAL_raise_callbacks(sigfd, GB_WATCH_READ, 0);

			if (ret & WP_TIMEOUT)
				break;

			if (ret == 0)
				usleep(1000);
		}
	}
	END_ERROR

	OBJECT_UNREF(process);

	#if 0
	{
		sigsuspend(&old);
		if (!process->running)
			break;

		#ifdef DEBUG_ME
		fprintf(stderr, "Waiting: %d\n", process->running);
		#endif

		sleep(10);
	}
	#endif

	/*if (have_sigchld)
		sigaddset(&old, SIGCHLD);
	sigprocmask(SIG_SETMASK, &old, NULL);*/

	#ifdef DEBUG_ME
	fprintf(stderr, "Waiting for: got it !\n");
	#endif
}

//-------------------------------------------------------------------------

BEGIN_METHOD_VOID(Process_exit)

	//fprintf(stderr, "Process_exit\n");

	while (_running_process_list)
		stop_process(_running_process_list);

	exit_child();

	STRING_free(&CPROCESS_shell);

END_METHOD


BEGIN_METHOD_VOID(Process_new)

	init_process(THIS);

END_METHOD


BEGIN_METHOD_VOID(Process_free)

	#ifdef DEBUG_ME
	fprintf(stderr, "Process_free %p\n", THIS);
	#endif

	exit_process(THIS);

END_METHOD


BEGIN_PROPERTY(Process_Id)

	GB_ReturnInt(THIS->pid);

END_PROPERTY


BEGIN_METHOD_VOID(Process_Kill)

	pid_t pid;
	
	if (!THIS->running)
		return;

	if (THIS->process_group)
	{
		pid = getpgid(THIS->pid);
		if (pid < 0)
			return;
		pid = - pid;
	}
	else
		pid = THIS->pid;
	
	kill(pid, SIGKILL);

	//CPROCESS_wait_for(THIS);

END_METHOD

BEGIN_METHOD_VOID(Process_Signal)

	if (!THIS->running)
		return;

	/*
	printf("Send SIGUSR1 to process %d\n", THIS->pid);
	fflush(NULL);*/
	kill(THIS->pid, SIGUSR1);

END_METHOD


#if 0
BEGIN_METHOD(CPROCESS_send, GB_STRING text)

	if (!THIS->running || THIS->in < 0)
		return;

	STREAM_write(&THIS->ob.stream, STRING(text), LENGTH(text));

END_METHOD
#endif

BEGIN_PROPERTY(Process_State)

	if (THIS->running)
		GB_ReturnInteger(1);
	else
	{
		if (WIFEXITED(THIS->status))
			GB_ReturnInteger(0);
		else
			GB_ReturnInteger(2);
	}

END_PROPERTY


BEGIN_PROPERTY(Process_Value)

	int status;

	if (THIS->running)
	{
		GB_ReturnInteger(0);
		return;
	}

	status = THIS->status;

	if (WIFEXITED(status))
		GB_ReturnInteger(WEXITSTATUS(status));
	else if (WIFSIGNALED(status))
		GB_ReturnInteger(WTERMSIG(status));
	else
		GB_ReturnInteger(-1);

END_PROPERTY

BEGIN_PROPERTY(Process_LastState)

	if (WIFEXITED(_last_status))
		GB_ReturnInteger(0);
	else
		GB_ReturnInteger(2);

END_PROPERTY


BEGIN_PROPERTY(Process_LastValue)

	int status;

	status = _last_status;

	if (WIFEXITED(status))
		GB_ReturnInteger(WEXITSTATUS(status));
	else if (WIFSIGNALED(status))
		GB_ReturnInteger(WTERMSIG(status));
	else
		GB_ReturnInteger(-1);

END_PROPERTY

BEGIN_METHOD(Process_Wait, GB_FLOAT timeout)

	// FIXME: Does not work if Ignore is set
	CPROCESS_wait_for(THIS, (int)(VARGOPT(timeout, 0.0) * 1000));

END_METHOD

BEGIN_PROPERTY(Process_Ignore)

	if (READ_PROPERTY)
		GB_ReturnBoolean(THIS->ignore);
	else
	{
		bool ignore = VPROP(GB_BOOLEAN);
		if (THIS->ignore != ignore)
		{
			THIS->ignore = ignore;
			if (ignore)
			{
				_ignore_process++;
				if (_running_process <= _ignore_process)
					exit_child();
			}
			else
			{
				_ignore_process--;
				if (_running_process > _ignore_process)
					init_child();
			}
		}
	}

END_METHOD

BEGIN_METHOD_VOID(Process_CloseInput)

	close_fd(&THIS->in);

END_METHOD

/*
static int calc_mode(int arg)
{
	int mode = 0;
	
	if (arg & R_OK) mode += PM_READ;
	if (arg & W_OK) mode += PM_WRITE;
	if (arg & X_OK) mode += PM_SHELL;
	
	return mode;
}

BEGIN_METHOD(Process_Exec, GB_OBJECT command; GB_INTEGER mode; GB_OBJECT env)

	CARRAY *command = (CARRAY *)VARG(command);
	CARRAY *env = (CARRAY *)VARG(env);
	
	if (GB_CheckObject(command))
		return;

	init_process(THIS);
	run_process(THIS, calc_mode(VARGOPT(mode, 0)), command, env);

END_METHOD

BEGIN_METHOD(Process_Shell, GB_STRING command; GB_INTEGER mode; GB_OBJECT env)

	char *command = GB_ToZeroString(ARG(command));
	CARRAY *env = (CARRAY *)VARG(env);
	
	init_process(THIS);
	run_process(THIS, calc_mode(VARGOPT(mode, 0)) | PM_SHELL, command, env);

END_METHOD
*/

#endif

GB_DESC NATIVE_Process[] =
{
	GB_DECLARE("Process", sizeof(CPROCESS)), GB_NOT_CREATABLE(),
	GB_INHERITS("Stream"),

	GB_CONSTANT("Stopped", "i", 0),
	GB_CONSTANT("Running", "i", 1),
	GB_CONSTANT("Crashed", "i", 2),
	GB_CONSTANT("Signaled", "i", 2),

	GB_STATIC_PROPERTY_READ("LastState", "i", Process_LastState),
	GB_STATIC_PROPERTY_READ("LastValue", "i", Process_LastValue),

	GB_PROPERTY_READ("Id", "i", Process_Id),
	GB_PROPERTY_READ("Handle", "i", Process_Id),
	GB_PROPERTY_READ("State", "i", Process_State),
	GB_PROPERTY_READ("Value", "i", Process_Value),

	GB_STATIC_METHOD("_exit", NULL, Process_exit, NULL),
	GB_METHOD("_new", NULL, Process_new, NULL),
	GB_METHOD("_free", NULL, Process_free, NULL),

	GB_METHOD("Kill", NULL, Process_Kill, NULL),
	GB_METHOD("Signal", NULL, Process_Signal, NULL),
	GB_METHOD("Wait", NULL, Process_Wait, "[(Timeout)f]"),
	
	GB_METHOD("CloseInput", NULL, Process_CloseInput, NULL),

	GB_PROPERTY("Ignore", "b", Process_Ignore),
	
	/*GB_METHOD("Exec", NULL, Process_Exec, "(Command)String[];[(Mode)i(Environment)String[];]"),
	GB_METHOD("Shell", NULL, Process_Shell, "(Command)s;[(Mode)i(Environment)String[];]"),*/
	
	GB_EVENT("Read", NULL, NULL, &EVENT_Read),
	GB_EVENT("Error", NULL, "(Error)s", &EVENT_Error),
	GB_EVENT("Kill", NULL, NULL, &EVENT_Kill),

	GB_END_DECLARE
};

