/*
 * Copyright 1993,1994 Globetrotter Software, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Globetrotter Software not be used
 * in advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.  Globetrotter Software makes
 * no representations about the suitability of this software for any purpose.
 * It is provided "as is" without express or implied warranty.
 *
 * GLOBETROTTER SOFTWARE DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.  IN NO
 * EVENT SHALL GLOBETROTTER SOFTWARE BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
 * OF THIS SOFTWARE.
 *
 * Author:  Jim McBeath, Globetrotter Software, jimmc@globes.com
 */
/* command.c - process commands from clients
 *
 * Jim McBeath, November 18, 1993
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#ifndef FIONREAD
#include <sys/filio.h>
#endif
#include <errno.h>
#include "htimp.h"
#include "app.h"

extern int ImpDebug;
extern int ImpMaxJobs;
extern int ImpJobCount;
extern int ImpSigPipeReceived;

extern AppInfo *ImpFindApp();
extern JobInfo *ImpNewJob();
extern JobInfo *ImpFindJob();

void
ImpMfprintf(f,str,job)
FILE *f;
char *str;
JobInfo *job;
{
	char *s, *p;

	s = str;
	while (*s) {
		p = strchr(s,'%');
		if (!p) {
			fputs(s,f);
			return;
		}
		*p = 0;
		fputs(s,f);
		*p++ = '%';	/* restore the string */
		switch (*p) {
		case 'I':	/* insert htimp handle */
			/* You can build talk URL by "%I/t/%H" */
			ImpPrintImpURL(f);
			break;
		case 'H':	/* insert handle */
			fputs(job->handle,f);
			break;
		case 'T':	/* insert complete URL handle for talking */
			ImpPrintThisURL(f,job->handle);
			break;
		case '%':
			fputc('%',f);
			break;
		case 0:
			p--;
			break;
		default:
			break;	/* ignore */
		}
		s = p+1;
	}
}

/* Process a start command for a new job */
void
ImpCmdStart(f,ci)
FILE *f;
CommandInfo *ci;
{
	AppInfo *app;
	JobInfo *job;

	if (!ci->app) {
		ImpReplyError(f,"No application specified");
		return;
	}
	app = ImpFindApp(ci->app);
	if (!app) {
		ImpReplyError(f,"Can't find application %s",ci->app);
		return;
	}
	if (ImpMaxJobs>0 && ImpJobCount>=ImpMaxJobs) {
		ImpReplyError(f,"Too many jobs currently running");
		return;
	}
	job = ImpNewJob(app);
	if (!job) {
		ImpReplyError(f,"Error allocating job for %s",ci->app);
		return;
	}
	time(&job->lastcmdtime);
	if (ImpStartJob(f,job,ci->str)) {
		ImpQuitJob(job);	/* clean it up */
		return;	/* message already delivered */
	}
	ImpReplyRedirectToHandle(f,job);
}

void
ImpCmdTalk(f,ci)
FILE *f;
CommandInfo *ci;
{
	AppInfo *app;
	JobInfo *job;
	int t;
	int k;
	static int bufalloc=0;
	static char *buf;
#define BUF_INITIAL_SIZE 500
#define BUF_MIN_FREE 250

	if (!ci->handle) {
		ImpReplyError(f,"No handle specified");
		return;
	}
	job = ImpFindJob(ci->handle);
	if (!job) {
		ImpReplyError(f,"Can't find job handle %s",ci->handle);
		return;
	}
	if (job->killed) {
		ImpReplyError(f,"Job %s has been killed\n",ci->handle);
		ImpQuitJob(job);  /* now get rid of it completely */
		return;
	}
	if (!job->pid) {
		ImpReplyError(f,"Job %s has exited\n",ci->handle);
		ImpQuitJob(job);  /* now get rid of it completely */
		return;
	}
	app = job->app;
	/* TBD - we might want to fork so that one process can wait on
	 * the application without worrying about hanging the rest.
	 * But then we would need to mark that job as being in a
	 * wait-for-response state and make sure we did not send it
	 * another command while waiting for it. */
	if (!job->numreads) {
		/* First time here, after initial redirection */
		/* Send nothing to app, we're waiting for first output*/
		;	/* do nothing */
	} else {
		/* Write the command to the app process */
		ImpSigPipeReceived = 0;
		if (ci->str)
			t = write(job->stdind,ci->str,strlen(ci->str));
			/* TBD - check for error */
		t = write(job->stdind,"\n",1);
		if (ImpSigPipeReceived) {
			ImpReplyError(f,"Job %s is gone\n",ci->handle);
			ImpQuitJob(job);  /* now get rid of it completely */
			return;
		}
	}
	if (bufalloc==0) {
		k = BUF_INITIAL_SIZE;
		buf = ImpMalloc(k);
		/* TBD - check for !buf */
		bufalloc = k;
	}
	k = 0;
	while (1) {
		fd_set rset;
		struct timeval to;
		int n,nbytes;

		FD_ZERO(&rset);
		FD_SET(job->stdoutd,&rset);
		to.tv_sec = app->readtimeout;
		to.tv_usec = 0;
		t = select(job->stdoutd+1,&rset,(fd_set *)0,(fd_set *)0,&to);
		if (t==0) {
			/* timed out, assume it's done replying */
			break;
		}
		if (t<0) {
			if (errno!=EINTR) {
				ImpReplyError("+Select error");
				return;
			}
			/* interrupted, wait and try again */
			sleep(1);
			continue;
		}
		/* else t>0 */
		n = ioctl(job->stdoutd,FIONREAD,&nbytes);
		if (n<0) {
			ImpReplyError(f,
			   "+Ioctl error trying to read from job - killed");
			ImpQuitJob(job);
			return;
		}
		if (nbytes==0) {
			if (k>0) break;
			/* must be EOF */
			ImpReplyInfo(f,"Job exited"); /* we assume */
			/* TBD - report back exit status? */
			ImpQuitJob(job);
			return;
		}
		if (bufalloc-k < nbytes+1) {
			char *p;
			p = ImpRealloc(buf,bufalloc+nbytes+BUF_INITIAL_SIZE);
			buf = p;
			bufalloc += BUF_INITIAL_SIZE;
		}
		t = read(job->stdoutd,buf+k,nbytes);
		if (t<0) {
			ImpReplyError(f,"+Error reading job reply");
			return;
		}
		if (t==0) {
			/* must be EOF */
			if (k>0) break;
			ImpReplyInfo(f,"Job exited"); /* we assume */
			/* TBD - report back exit status? */
			ImpQuitJob(job);
			return;
		}
		k += t;
		buf[k] = 0;
		if (app->lastoutlen>0 && k>=app->lastoutlen) {
			if (strcmp(buf+k-app->lastoutlen,app->lastout)==0) {
				/* found the magic string */
				if (app->striplastout) {
					buf[k-app->lastoutlen]=0;
					    /* throw away the magic string */
				}
				break;	/* time to stop */
			}
		}
	}
	buf[k] = 0;
	switch (app->headerstyle) {
	case HEADER_NONE:
	default:
		break;	/* no extra output, app does it all */
	case HEADER_MIME:
		ImpMimeHeader(f);	/* default mime header */
		break;
	case HEADER_HTML:
		ImpMsgHeader(f,app->title,"");
		if (app->subtitle)
			ImpMfprintf(f,app->subtitle,job);
		break;
	case HEADER_ISINDEX:
		ImpMsgHeader(f,app->title,"");
		if (app->subtitle)
			ImpMfprintf(f,app->subtitle,job);
		fprintf(f,"<ISINDEX>\n");
		break;
	case HEADER_PLAINTEXT:
		ImpMsgHeader(f,app->title,"");
		if (app->subtitle)
			ImpMfprintf(f,app->subtitle,job);
		fprintf(f,"<ISINDEX>\n<PLAINTEXT>\n");
		break;
	}
	if (app->formatout)
		ImpMfprintf(f,buf,job);
	else
		fputs(buf,f);
	fflush(f);
	job->numreads++;
	time(&job->lastcmdtime);
}

void
ImpCmdQuit(f,ci)
FILE *f;
CommandInfo *ci;
{
	AppInfo *app;
	JobInfo *job;

	if (!ci->handle) {
		ImpReplyError(f,"No handle specified");
		return;
	}
	job = ImpFindJob(ci->handle);
	if (!job) {
		ImpReplyError(f,"Can't find job handle %s",ci->handle);
		return;
	}
	ImpQuitJob(job);	/* kill it, remove from list */
	ImpReplyInfo(f,"Job %s killed by request",ci->handle);
}

/* Process one command, send results back to client */
void
ImpDoCommand(f,ci)
FILE *f;
CommandInfo *ci;
{
	AppInfo *app;
	JobInfo *job;

	if (ImpDebug)
		fprintf(stderr,"Master starting to process command\n");
	switch (ci->mode) {
	case MODE_START:
		ImpCmdStart(f,ci);
		return;
	case MODE_TALK:
		ImpCmdTalk(f,ci);
		return;
	case MODE_QUIT:
		ImpCmdQuit(f,ci);
		return;
	default:
		ImpReplyError(f,"Bad mode %d in DoCommand",ci->mode);
		/* NOTREACHED */
	}
}

/* end */
