/*
 * Copyright 2001 by Paul Mattes.
 *  Permission to use, copy, modify, and distribute this software and its
 *  documentation for any purpose and without fee is hereby granted,
 *  provided that the above copyright notice appear in all copies and that
 *  both that copyright notice and this permission notice appear in
 *  supporting documentation.
 *
 * x3270, c3270, s3270 and tcl3270 are distributed in the hope that they will
 * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the file LICENSE
 * for more details.
 */

/*
 *	child.c
 *		Child process output support.
 */
#include "globals.h"

#include <errno.h>
#include <fcntl.h>

#include "popupsc.h"
#include "utilc.h"

#define CHILD_BUF	1024

static Boolean child_initted = False;
static Boolean child_broken = False;
static Boolean child_discarding = False;
static int child_outpipe[2];
static int child_errpipe[2];

static struct pr3o {
	int fd;			/* file descriptor */
	unsigned long input_id;	/* input ID */
	unsigned long timeout_id; /* timeout ID */
	int count;		/* input count */
	char buf[CHILD_BUF];	/* input buffer */
} child_stdout = { -1, 0L, 0L, 0 },
  child_stderr = { -1, 0L, 0L, 0 };

static void child_output(void);
static void child_error(void);
static void child_otimeout(void);
static void child_etimeout(void);
static void child_dump(struct pr3o *p, Boolean is_err);

static void
init_child(void)
{
	/* If initialization failed, there isn't much we can do. */
	if (child_broken)
		return;

	/* Create pipes. */
	if (pipe(child_outpipe) < 0) {
		popup_an_errno(errno, "pipe()");
		child_broken = True;
		return;
	}
	if (pipe(child_errpipe) < 0) {
		popup_an_errno(errno, "pipe()");
		close(child_outpipe[0]);
		close(child_outpipe[1]);
		child_broken = True;
		return;
	}

	/* Make sure their read ends are closed in child processes. */
	(void) fcntl(child_outpipe[0], F_SETFD, 1);
	(void) fcntl(child_errpipe[0], F_SETFD, 1);

#if defined(X3270_DISPLAY) /*[*/
	/* Initialize the pop-ups. */
	child_popup_init();
#endif

	/* Express interest in their output. */
	child_stdout.fd = child_outpipe[0];
	child_stdout.input_id = AddInput(child_outpipe[0], child_output);
	child_stderr.fd = child_errpipe[0];
	child_stderr.input_id = AddInput(child_errpipe[0], child_error);

	child_initted = True;
}

/*
 * Fork a child process, with its stdout/stderr connected to pop-up windows.
 * Returns -1 for an error, 0 for child context, pid for parent context.
 */
int
fork_child(void)
{
	pid_t pid;

	/* Do initialization, if it hasn't been done already. */
	if (!child_initted)
		init_child();

	/* If output was being dumped, turn it back on now. */
	if (child_discarding)
		child_discarding = False;

	/* Fork and rearrange output. */
	pid = fork();
	if (pid == 0) {
		/* Child. */
		(void) dup2(child_outpipe[1], 1);
		(void) close(child_outpipe[1]);
		(void) dup2(child_errpipe[1], 2);
		(void) close(child_errpipe[1]);
	}
	return pid;
}

/* There's data from a child. */
static void
child_data(struct pr3o *p, Boolean is_err)
{
	int space;
	int nr;
	static char exitmsg[] = "Printer session exited";

	/*
	 * If we're discarding output, pull it in and drop it on the floor.
	 */
	if (child_discarding) {
		(void) read(p->fd, p->buf, CHILD_BUF);
		return;
	}

	/* Read whatever there is. */
	space = CHILD_BUF - p->count - 1;
	nr = read(p->fd, p->buf + p->count, space);

	/* Handle read errors and end-of-file. */
	if (nr < 0) {
		popup_an_errno(errno, "child session pipe input");
		return;
	}
	if (nr == 0) {
		if (child_stderr.timeout_id != 0L) {
			/*
			 * Append a termination error message to whatever the
			 * child process said, and pop it up.
			 */
			p = &child_stderr;
			space = CHILD_BUF - p->count - 1;
			if (p->count && *(p->buf + p->count - 1) != '\n') {
				*(p->buf + p->count) = '\n';
				p->count++;
				space--;
			}
			(void) strncpy(p->buf + p->count, exitmsg, space);
			p->count += strlen(exitmsg);
			if (p->count >= CHILD_BUF)
				p->count = CHILD_BUF - 1;
			child_dump(p, True);
		} else {
			popup_an_error(exitmsg);
		}
		return;
	}

	/* Add it to the buffer, and add a NULL. */
	p->count += nr;
	p->buf[p->count] = '\0';

	/*
	 * If there's no more room in the buffer, dump it now.  Otherwise,
	 * give it a second to generate more output.
	 */
	if (p->count >= CHILD_BUF - 1) {
		child_dump(p, is_err);
	} else if (p->timeout_id == 0L) {
		p->timeout_id = AddTimeOut(1000,
		    is_err? child_etimeout: child_otimeout);
	}
}

/* The child process has some output for us. */
static void
child_output(void)
{
	child_data(&child_stdout, False);
}

/* The child process has some error output for us. */
static void
child_error(void)
{
	child_data(&child_stderr, True);
}

/* Timeout from child output or error output. */
static void
child_timeout(struct pr3o *p, Boolean is_err)
{
	/* Forget the timeout ID. */
	p->timeout_id = 0L;

	/* Dump the output. */
	child_dump(p, is_err);
}

/* Timeout from child output. */
static void
child_otimeout(void)
{
	child_timeout(&child_stdout, False);
}

/* Timeout from child error output. */
static void
child_etimeout(void)
{
	child_timeout(&child_stderr, True);
}

/*
 * Abort button from child output.
 * Ignore output from the child process, so the user can abort it.
 */
void
child_ignore_output(void)
{
	/* Pitch pending output. */
	child_stdout.count = 0;
	child_stderr.count = 0;

	/* Remove pendnig timeouts. */
	if (child_stdout.timeout_id) {
		RemoveTimeOut(child_stdout.timeout_id);
		child_stdout.timeout_id = 0L;
	}
	if (child_stderr.timeout_id) {
		RemoveTimeOut(child_stderr.timeout_id);
		child_stderr.timeout_id = 0L;
	}

	/* Remember it. */
	child_discarding = True;
}

/* Dump pending child process output. */
static void
child_dump(struct pr3o *p, Boolean is_err)
{
	if (p->count) {
		/*
		 * Strip any trailing newline, and make sure the buffer is
		 * NULL terminated.
		 */
		if (p->buf[p->count - 1] == '\n')
			p->buf[--(p->count)] = '\0';
		else if (p->buf[p->count])
			p->buf[p->count] = '\0';

		/* Dump it and clear the buffer. */
#if defined(X3270_DISPLAY) /*[*/
		popup_child_output(is_err, child_ignore_output, "%s", p->buf);
#else /*][*/
		action_output("%s", p->buf);
#endif
		p->count = 0;
	}
}
