/*-*-c++-*-
 * $Id: localplugin.cpp,v 1.2 2002/04/19 15:24:17 felfert Exp $
 *
 * This file is part of qlcrash, a GUI for the dump-analysis tool lcrash.
 *
 * Copyright (C) 2001 IBM Deutschland Entwicklung GmbH, IBM Corporation
 *
 * Authors:
 * Michael Geselbracht (Michael.Geselbracht@de.ibm.com)
 * Fritz Elfert (elfert@de.ibm.com)
 * Michael Holzheu (holzheu@de.ibm.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version. See the file COPYING for more
 * information.
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "generic_cpp.h"
#include "localplugin.h"

#include <qobject.h>
#include <qsocket.h>
#include <qsocketnotifier.h>
#include <qtimer.h>
#include <qmessagebox.h>
#include <qdialog.h>
#include <qlineedit.h>
#include <qspinbox.h>
#include <qlayout.h>
#include <qlabel.h>
#include <qpushbutton.h>
#include <qapplication.h>

//#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/stat.h>

static CLocalChild *theChild = 0;

void CHLDhandler(int) {
	if (theChild)
		theChild->childDied();
}

CLocalChild::CLocalChild()
	: CChildImplementation()
, runs(false)
, pid(0)
, in_sn(0)
, out_sn(0)
, err_sn(0)
{
	  struct sigaction act;

	  act.sa_handler = CHLDhandler;
	  sigemptyset(&(act.sa_mask));
	  sigaddset(&(act.sa_mask), SIGCHLD);
	  // Make sure we don't block this signal. gdb tends to do that :-(
	  sigprocmask(SIG_UNBLOCK, &(act.sa_mask), 0);
	  act.sa_flags = SA_NOCLDSTOP;
#ifdef SA_RESTART
	  act.sa_flags |= SA_RESTART;
#endif
	  sigaction(SIGCHLD, &act, 0L);

	  act.sa_handler=SIG_IGN;
	  sigemptyset(&(act.sa_mask));
	  sigaddset(&(act.sa_mask), SIGPIPE);
	  act.sa_flags = 0;
	  sigaction( SIGPIPE, &act, 0L);

	  timer = new QTimer(this);
	  connect(timer, SIGNAL(timeout()), this, SLOT(slotTimeout()));
}

CLocalChild::~CLocalChild()
{
	if (runs) {
		killChild(SIGKILL);
		while (runs)
			;
	}
	timer->stop();
}

void
CLocalChild::childDied() {
	int status;
	pid_t died_pid;
	int saved_errno;

	saved_errno = errno;
	do {
		died_pid = waitpid(-1, &status, WNOHANG);
		if (died_pid > 0)
			diedPids.append(died_pid);
	} while (died_pid > 0);
 	errno = saved_errno;
}

void
CLocalChild::slotTimeout() {
	if (diedPids.find(pid) != diedPids.end()) {
		runs = false;
		pid = 0;
		parentIOclose();
		diedPids.clear();
		emit sigChildDied();
	}
}

void
CLocalChild::slotStdinReady(int fd) {
	if (outBuffer.isEmpty()) {
		in_sn->setEnabled(false);
		return;
	}
	int l = ::write(in[1], (const char *)outBuffer, outBuffer.length());
	if (l > 0)
		outBuffer.remove(0, l);
}

void
CLocalChild::slotStdoutReady(int fd) {
	char buf[1024];
	memset(buf, 0, sizeof(buf));
	int l = ::read(fd, buf, sizeof(buf) - 1);
	if (l > 0)
		emit sigStdout(QString(buf));
}

void
CLocalChild::slotStderrReady(int fd) {
	char buf[1024];
	memset(buf, 0, sizeof(buf));
	int l = ::read(fd, buf, sizeof(buf) - 1);
	if (l > 0)
		emit sigStderr(QString(buf));
}

bool
CLocalChild::valid()
{
	struct stat stbuf;

	if (path.isEmpty())
		return false;
	if (stat(path.data(), &stbuf) < 0)
		return false;

	if ( (S_ISDIR(stbuf.st_mode))  ||
	     (S_ISCHR(stbuf.st_mode))  ||
	     (S_ISBLK(stbuf.st_mode))  ||
#ifdef S_ISSOCK
	     (S_ISSOCK(stbuf.st_mode)) ||
#endif
	     (S_ISFIFO(stbuf.st_mode)) ||
	     (S_ISDIR(stbuf.st_mode)) ) {
		return false;
	}
 	if (access(path.data(), X_OK) != 0)
		return false;
 	return true;
}

bool
CLocalChild::running()
{
	return runs;
}

bool
CLocalChild::parentIOsetup() {
	bool ok = true;

	close(in[0]);
	close(out[1]);
	close(err[1]);
	ok &= (fcntl(in[1], F_SETFL, O_NONBLOCK) != -1);
	in_sn =  new QSocketNotifier(in[1], QSocketNotifier::Write, this);
	in_sn->setEnabled(false);
	connect(in_sn, SIGNAL(activated(int)), this, SLOT(slotStdinReady(int)));
	ok &= (fcntl(out[0], F_SETFL, O_NONBLOCK) != -1);
	out_sn = new QSocketNotifier(out[0], QSocketNotifier::Read, this);
	connect(out_sn, SIGNAL(activated(int)), this, SLOT(slotStdoutReady(int)));
	ok &= (fcntl(err[0], F_SETFL, O_NONBLOCK) != -1);
	err_sn = new QSocketNotifier(err[0], QSocketNotifier::Read, this );
	connect(err_sn, SIGNAL(activated(int)), this, SLOT(slotStderrReady(int)));
	return ok;
}

bool
CLocalChild::childIOsetup() {
	bool ok = true;
	struct linger so;

	close(in[1]);
	close(out[0]);
	close(err[0]);
	ok &= (dup2(in[0],  STDIN_FILENO) != -1);
	ok &= (dup2(out[1], STDOUT_FILENO) != -1);
	ok &= (dup2(err[1], STDERR_FILENO) != -1);
	ok &= (setsockopt(out[1], SOL_SOCKET, SO_LINGER, &so, sizeof(so)) == 0);
	ok &= (setsockopt(err[1], SOL_SOCKET, SO_LINGER, &so, sizeof(so)) == 0);
	return ok;
}

void
CLocalChild::parentIOclose() {
	close(in[0]); close(in[1]);
	close(out[0]); close(out[1]);
	close(err[0]); close(err[1]);
	if (in_sn)
		delete in_sn;
	in_sn = 0;
	if (out_sn)
		delete out_sn;
	out_sn = 0;
	if (err_sn)
		delete err_sn;
	err_sn = 0;
}

void
CLocalChild::forkChild()
{
	int acount = 1;
	int in_ws = 1;
	int in_quote = 0;
	char *tmp;
	char *p;
	char *q;
	char **arglist = (char **)malloc(2 * sizeof(char *));

	if (path.isEmpty())
		return;

	arglist[0] = (char *)path.data();
	if (!argv.isEmpty()) {
		tmp = q = strdup(argv.data());
		for (p = tmp; p && *p; p++) {
			switch (*p) {
				case '"':
					in_quote = !in_quote;
					if (in_quote)
						break;
					else
						in_ws = 0;
					// fall thru
				case ' ':
				case '\t':
				case '\n':
					if (!(in_quote || in_ws)) {
						*p = 0;
						arglist[acount++] = strdup(q);
						arglist = (char **)realloc(arglist,
									   (acount + 1) * sizeof(char *));
						in_ws = 1;
					}
					break;
				default:
					if (in_ws)
						q = p;
					in_ws = 0;
					break;
			}
		}
		if (strlen(q)) {
			arglist[acount++] = strdup(q);
			arglist = (char **)realloc(arglist, (acount + 1) * sizeof(char *));
		}
		free(tmp);
	}
	arglist[acount] = 0;
	
	if ((socketpair(AF_UNIX, SOCK_STREAM, 0, in) < 0))
		return;
	if ((socketpair(AF_UNIX, SOCK_STREAM, 0, out) < 0)) {
		close(in[0]);
		close(in[1]);
		return;
	}
	if ((socketpair(AF_UNIX, SOCK_STREAM, 0, err) < 0)) {
		close(in[0]);
		close(in[1]);
		close(out[0]);
		close(out[1]);
		return;
	}

	int fd[2];
	if (pipe(fd) > 0)
		fd[0] = fd[1] = 0; // Pipe failed, ignore

	runs = true;
	pid = fork();
	if (pid == 0) {
		// child process
		if (fd[0])
			close(fd[0]);

		if (!childIOsetup())
			exit(-1);

		struct sigaction act;
		sigemptyset(&(act.sa_mask));
		sigaddset(&(act.sa_mask), SIGPIPE);
		act.sa_handler = SIG_DFL;
		act.sa_flags = 0;
		sigaction(SIGPIPE, &act, 0L);

		// Closing of fd[1] indicates that the execvp succeeded!
		if (fd[1])
			fcntl(fd[1], F_SETFD, FD_CLOEXEC);
		execvp(arglist[0], arglist);
		char resultByte = 1;
		if (fd[1])
			write(fd[1], &resultByte, 1);
		exit(-1);
	} else if (pid == -1) {
		// forking failed
		runs = false;
		free(arglist);
		return;
	} else {
		// parent process
		if (fd[1])
			close(fd[1]);

		// Check whether client could be started.
		if (fd[0])
			for(;;) {
				char resultByte;
				int n = ::read(fd[0], &resultByte, 1);
				if (n == 1) {
					// Error
					runs = false;
					parentIOclose();
					pid = 0;
					free(arglist);
					return;
				}
				if (n == -1) {
					if ((errno == ECHILD) ||
					    (errno == EINTR)    )
						continue; // Ignore
				}
				break; // success
			}
		if (fd[0])
			close(fd[0]);

		if (!parentIOsetup()) {
			runs = false;
			parentIOclose();
			pid = 0;
			free(arglist);
			return;
		}
		timer->start(200);
        }
	free(arglist);
}

void
CLocalChild::killChild(int sig)
{
	if (pid > 0)
		::kill(pid, sig);
}

bool
CLocalChild::writeStdin(QString s)
{
	if ((!runs) || (pid <= 0) || s.isEmpty())
		return false;
	emit sigBusy();
	outBuffer += s;
	slotStdinReady(0);
	in_sn->setEnabled(true);
	return true;
}

int CLocalChild::
configure(QWidget *parent) {
	QDialog d(parent, "configDialog", true);
	d.setCaption(tr("Local plugin Configure"));
	QGridLayout *l = new QGridLayout(&d, 5, 2, 5);

	l->addWidget(new QLabel(tr("Path"), &d),      0, 0);
	l->addWidget(new QLabel(tr("Arguments"), &d), 1, 0);

	QLineEdit *pathEntry = new QLineEdit(path, &d);
	l->addWidget(pathEntry, 0, 1);

	QLineEdit *argvEntry = new QLineEdit(argv, &d);
	l->addWidget(argvEntry, 1, 1);

	l->setRowStretch(2, 10);

	QFrame *separator = new QFrame(&d);
	separator->setLineWidth(1);
	separator->setMidLineWidth(0);
	separator->setFrameStyle(QFrame::HLine | QFrame::Sunken);
	separator->setMinimumSize(0, 2);
	l->addMultiCellWidget(separator, 3, 3, 0, 1);

	QBoxLayout *bl = new QHBoxLayout();
	bl->addStretch(10);
	QPushButton *okButton = new QPushButton("Ok", &d);
	bl->addWidget(okButton);
	QPushButton *cancelButton = new QPushButton("Cancel", &d);
	bl->addWidget(cancelButton);

	l->addMultiCellLayout(bl, 4, 4, 0, 1);

	connect(cancelButton, SIGNAL(clicked()), &d, SLOT(reject()));
	connect(okButton, SIGNAL(clicked()), &d, SLOT(accept()));

	d.setMinimumWidth(350);
	okButton->setFocus();

	int ret = d.exec();
	if (ret == QDialog::Accepted) {
		_configured = true;
		path = pathEntry->text().stripWhiteSpace();
		argv = argvEntry->text().stripWhiteSpace();
		if (runs) {
			killChild(SIGKILL);
			forkChild();
		}
	}
	return ret;
}

cfgMap
CLocalChild::getConfigValues() {
	QString tmp;
	cfgMap *map = new cfgMap();
	map->insert("path", path);
	map->insert("argv", argv);
	return *map;
}

void
CLocalChild::setConfigValues(cfgMap v) {
	path = v["path"];
	argv = v["argv"];
	_configured = true;
}

bool
CLocalChild::configured() {
	return _configured;
}

#ifdef __cplusplus
extern "C" {
#endif

static CChildImplementation *my_factory() {
	if (!theChild)
		theChild = new CLocalChild();
	return theChild;
}

DLLEXPORT DECLARE_PLUGIN(
	"Local lcrash",
	"1.0",
	"This runs lcrash locally",
	"Fritz Elfert &lt;elfert@de.ibm.com&gt;",
	"Copyright (C) 2001 IBM Deutschland Entwicklung GmbH, IBM Corporation<BR/>This plugin is freely redistributable under LGPL.<BR/>See COPYING for more information.",
	my_factory,
	1
);

#ifdef __cplusplus
};
#endif

#ifndef WIN32
#include "localplugin.moc"
#endif

/*
 * Local variables:
 * c-basic-offset: 8
 * End:
 */

