/*
** 1998-09-25 -	This module deals with child processes. Cut out from the old commands module,
**		which really was kind of bloated.
** 1998-12-15 -	Rewritten. Now uses the POSIX sigaction() API, which perhaps is more portable.
**		I've been noticing some odd problems on Solaris, with "infinite signals" etc.
**		I found my (virtual) copy of the Linux Programmer's Guide, and it makes me
**		believe that POSIX signals are reliable by default (i.e. the handler need not
**		be reinstalled). New problem: possible interruption of system calls. :(
** 1999-03-31 -	Made this module a lot more self-contained.
** 1999-04-08 -	Now waitpid()s for the child after chd_kill_child(), which really is better.
*/

#include "gentoo.h"

#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "strutil.h"
#include "queue.h"
#include "children.h"

/* ----------------------------------------------------------------------------------------- */

typedef struct {			/* Information about a single child process. */
	pid_t	pid;				/* The pid of the process. */
	gchar	prog[MAXNAMLEN];		/* Name of program running as this child (argv[0]). */
	guint32	gflags;				/* The general flags for the command. */
	guint32	aflags;				/* After-flags to associate with this child. */
} Child;

typedef struct {			/* Holds data about child processes we have spawned. */
	MainInfo	*min;			/* Very handy to have around. Keep first! */
	GSList		*child_list;		/* List of runnings processes. */
	pid_t		lock_pid;		/* Pid of a command that we want to wait for. */
	CmdSeq		*running;		/* If non-null, we're currently running this sequence. */
	guint		index;			/* When 'running' is set, this is the next position to run. */
} ChildInfo;

static ChildInfo	the_chi = { NULL };

/* ----------------------------------------------------------------------------------------- */

static void handler_install(void);

/* ----------------------------------------------------------------------------------------- */

/* 1999-04-08 -	Rewritten, now simply initializes our private ChildInfo. */
gboolean chd_initialize(MainInfo *min)
{
	if(the_chi.min == NULL)
	{
		the_chi.min	   = min;
		the_chi.child_list = NULL;
		the_chi.lock_pid   = -1;
		the_chi.running    = NULL;
		the_chi.index	   = 0;

		return TRUE;
	}
	return FALSE;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-10 -	This handler is entered as we receive a SIGCHLD signal, telling us that a
**		child process just died. We now need to wait() for that pid, in order not
**		to create horrible zombies all over the place. This is done by waiting for
**		all known children (with the WNOHANG flag).
*/
static void sigchld_handler(int sig)
{
	GSList		*iter, *next;
	Child		*ch;
	ChildInfo	*chi = &the_chi;
	gint		status;

	for(iter = chi->child_list; iter != NULL; iter = next)
	{
		next = g_slist_next(iter);
		ch = (Child *) iter->data;
		if(waitpid(ch->pid, &status, WNOHANG) == ch->pid)
		{
			if(chi->lock_pid == ch->pid)		/* Running in foreground? */
				que_enqueue(chi->min, QEVT_CONTINUE_CMD);
			que_enqueue(chi->min, QEVT_END_CMD, ch->aflags);
			chi->child_list = g_slist_remove(chi->child_list, ch);
			g_free(ch);
		}
	}
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-12-15 -	(Re)install a signal handler for the SIGCHLD signal. */
static void handler_install(void)
{
	struct sigaction	sa;

	sa.sa_handler	= sigchld_handler;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags	= SA_NOCLDSTOP;

/* If the platform defines the SA_RESTART flag, it is probably a good idea
** to use it. It should not be necessary on Linux, but let's play it safe.
*/
#if defined(SA_RESTART)
	sa.sa_flags	|= SA_RESTART;
#endif

	sigaction(SIGCHLD, &sa, NULL);
}

/* 1998-12-15 -	Remove any SIGCHLD signal handler, making the response the default of
**		the OS.
*/
static void handler_remove(void)
{
	struct sigaction	sa;

	sa.sa_handler	= SIG_DFL;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags	= 0;

	sigaction(SIGCHLD, &sa, NULL);
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-09 -	Register a child process started by a user command. Keeps not only
**		the <pid>, This information is used to kill previous instance(s).
** 1998-09-10 -	Now takes an additional parameter, <lock>. If set, the GUI is locked.
** 1998-09-18 -	This routine is now exported for use by other code (the new "file" module).
*/
void chd_register(const gchar *prog, pid_t pid, guint32 gflags, guint32 aflags)
{
	Child		*ch;
	ChildInfo	*chi = &the_chi;

	if(chi->child_list == NULL)	/* No children registered yet? */
		handler_install();	/* Then this is the first (!) and the handler needs to be installed. */

	ch = g_malloc(sizeof *ch);
	str_strncpy(ch->prog, prog, sizeof ch->prog);
	ch->pid	   = pid;
	ch->gflags = gflags;
	ch->aflags = aflags;
	chi->child_list = g_slist_append(chi->child_list, (gpointer) ch);
	if(!(gflags & CGF_RUNINBG))
	{
		chi->lock_pid = pid;
		gtk_widget_set_sensitive(the_chi.min->gui->window, FALSE);
	}
}

/* 1999-04-08 -	Unregister the child named <name>. This is not meant for public consumption,
**		hence it's static. Returns TRUE if a child was found, else FALSE.
** NOTE NOTE	This function doesn't kill any processes or otherwise affect the system's
**		process table: it just removes the internal data.
*/
static gboolean chd_unregister(const gchar *name)
{
	GSList		*iter;
	Child		*ch;
	ChildInfo	*chi = &the_chi;

	for(iter = chi->child_list; iter != NULL; iter = g_slist_next(iter))
	{
		ch = iter->data;
		if(strcmp(ch->prog, name) == 0)
		{
			chi->child_list = g_slist_remove_link(chi->child_list, iter);
			g_slist_free(iter);
			g_free(ch);
			return TRUE;
		}
	}
	return FALSE;
}

/* 1999-03-31 -	Set the continuation information. */
void chd_set_running(CmdSeq *cs, guint index)
{
	the_chi.running	= cs;
	the_chi.index	= index;
}

/* 1999-03-31 -	Get the currently running command sequence. If non-NULL, <index> will
**		receive the index of the next row to run.
*/
CmdSeq * chd_get_running(guint *index)
{
	if(index != NULL)
		*index = the_chi.index;
	return the_chi.running;
}

/* 1999-03-31 -	Clear continuation info (since the sequence has finished). */
void chd_clear_running(void)
{
	the_chi.running	= NULL;
	the_chi.index	= 0;
}

/* 1999-03-31 -	Just clear the locking child PID (since it just died, typically). */
void chd_clear_lock(void)
{
	the_chi.lock_pid = -1;
}

/* 1998-09-28 -	Kill (all) running instances of command named <name>. */
void chd_kill_child(const gchar *name)
{
	GSList	*iter;
	Child	*ch;
	gint	ret, rcount = 0;
	pid_t	pid;

	for(iter = the_chi.child_list; iter != NULL; iter = g_slist_next(iter))
	{
		ch = (Child *) iter->data;
		if(strcmp(ch->prog, name) == 0)
		{
			pid = ch->pid;		/* Buffer in case sighandler g_free()s it. */
			kill(pid, SIGTERM);
			if((ret = waitpid(pid, NULL, 0)) == pid)
				rcount++;
			else
				perror("CHILDREN: waitpid() failed");
		}
	}
	while(rcount && chd_unregister(name))
		rcount--;
}

/* 1998-05-26 -	Kill any child processes (left over from running asynchronous commands).
** 1998-09-09 -	Rewritten due a more advanced child data format.
** 1998-10-11 -	Moved into the children module, where it belongs.
*/
void chd_kill_children(void)
{
	GSList	*iter, *next;
	Child	*ch;

	/* When this is being done, we don't want no signal handler. */
	handler_remove();
	for(iter = the_chi.child_list; iter != NULL; iter = next)
	{
		next = g_slist_next(iter);
		ch = (Child *) iter->data;
		if(ch->gflags & CGF_SURVIVE)
			continue;
		if(kill(ch->pid, SIGTERM))
			fprintf(stderr, "gentoo: Killing child %d failed - zombie alert!\n", (int) ch->pid);
		else
		{
			waitpid(ch->pid, NULL, 0);
			the_chi.child_list = g_slist_remove(the_chi.child_list, (gpointer) ch);
			g_free(ch);
		}
	}
}
