/*
 * 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
 */
/* master.c - master/slave routines
 *
 * Jim McBeath, November 18, 1993
 */

#include "htimp.h"
#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <errno.h>
#include <ctype.h>
#include <signal.h>

#define CURRENT_VERSION_STR "1"

extern int ImpDebug;
extern int ImpDebugReply;
extern int ImpDebugStayConnected;
extern char *ProgPath;

int ImpSigPipeReceived;
int ImpSigPipeCount;
int ImpListenDesc= -1;
int ImpCheckInterval=DEFAULT_CHECK_INTERVAL;

void
ImpSigPipe()
{
	if (ImpDebug)
		fprintf(stderr,"Master received SIGPIPE\n");
	ImpSigPipeReceived = 1;
	ImpSigPipeCount++;
	signal(SIGPIPE,ImpSigPipe);	/* reset it */
}

void
ImpSigInt()
{
	if (ImpDebug)
		fprintf(stderr,"Master received signal, cleaning up\n");
	if (ImpListenDesc>=0)
		ImpMasterDisconnect(ImpListenDesc);
	exit(0);
}

void
ImpDetach()
{
	int pgrp;
	int i;
	int maxd;

	if (ImpDebug)
		fprintf(stderr,"Master detaching\n");
	fclose(stdin);
	fclose(stdout);	/* have to close these to avoid hang */
	/* leave stderr open for debugging */

	maxd = ImpGetDescCount();
	/* ncsa httpd doesn't close it's www socket before calling us.
	 * We have to close it, otherwise the request will hang because
	 * the connection stays open as long as we are running. */
	for (i=3; i<maxd; i++) {
		if (i!=ImpListenDesc)
			close(i);
	}
	return;
}

void
ImpSetCheckInterval(n)
int n;
{
	ImpCheckInterval = n;
}

void
ImpModProgPath()
{
#if 0	/* doesn't do anything on my Solaris system */
	char *r;
	int n;

	r = strrchr(ProgPath,'/');
	if (!r) r = ProgPath;
	n = 0;
	while (*r) {
		if (islower(*r)) {
			*r = toupper(*r);
			n++;
		}
		r++;
	}
	if (ImpDebug)
		fprintf(stderr,"Master uppercased %d chars in name\n",n);
#endif
}

void
ImpWriteMasterPid()
{
	pid_t pid;

	pid = getpid();
	if (ImpDebug)
		fprintf(stderr,"Master PID is %d\n",pid);
	/* TBD - write pid to file as specified in config file */
}

void
ImpMasterMain()
{
	int d;		/* file descriptor for our listen port */
	int cd;
	FILE *f;
	int t;
	int n;
	struct timeval to;
	long r;
	time_t lastchecktime, currenttime;

	if (ImpDebug)
		fprintf(stderr,"Master in ImpMasterMain\n");
	ImpWriteMasterPid();
	ImpReadAppConfDir();
	signal(SIGINT,ImpSigInt);
	signal(SIGTERM,ImpSigInt);
	signal(SIGPIPE,ImpSigPipe);
	ImpListenDesc = d = ImpMasterListen();
	if (!ImpDebugStayConnected) {
		ImpDetach();	/* disconnect from client who started us */
	}
	time(&lastchecktime);
	while (1) {
		ImpCheckChildren();	/* see if anyone exited */
		time(&currenttime);
		n = (lastchecktime+ImpCheckInterval) - currenttime;
			/* amount of time remaining to next check time */
		if (n<0) n=0;
		to.tv_sec = n;	/* sleep until next check time */
		to.tv_usec = 0;
		r = (1<<d);
		if (ImpDebug)
			fprintf(stderr,"Begin select(%d) in master\n",
					to.tv_sec);
		t = select(d+1,(fd_set *)&r,(fd_set *)0,(fd_set *)0,&to);
		if (t<0) {
			if (errno==EINTR) {
				if (ImpDebug) {
					fprintf(stderr,
						"Master select interrupted\n");
				}
				/* See if a child exited */
				if (!ImpCheckChildren()) {
					/* hmmm, no children, what was it? */
					sleep(1);
				}
			} else {
				perror("Master select");
				sleep(1);
			}
		} else if (t>0) {
			if (ImpDebug)
				fprintf(stderr,"About to accept\n");
			cd = ImpMasterAccept(d);
			if (cd>=0) {
				CommandInfo ci;
				ImpReadCommand(cd,&ci);	/* read from client */
				f = fdopen(cd,"w");	/* so we can fprintf */
				if (f) {
					ImpDoCommand(f,&ci);
					fclose(f);
				}
				ImpMasterDisconnect(cd);
			}
		} else /* t==0 */ {
			if (ImpDebug)
				fprintf(stderr,"Processing Timeout\n");
			ImpCheckJobs();	/* check for stale jobs */
			time(&lastchecktime);
		}
	}
}

ImpStartMaster()
{
	pid_t pid;

	if (ImpDebug)
		fprintf(stderr,"About to fork\n");
	pid = fork();
	if (pid<0) {
		ImpFatal("+Can't fork to start master");
		/* NOTREACHED */
	}
	if (pid) {
		if (ImpDebug) {
			fprintf(stderr,"Client about to sleep\n");
			sleep(2);	/* give child a chance to run */
			fprintf(stderr,"Client resuming processing\n");
		} else {
			sleep(2);	/* give child a chance to run */
		}
	} else {
		/* We are in the child now, set up as master */
		ImpModProgPath();	/* so we can tell with ps it's us */
		ImpMasterMain();
		/* NOTREACHED */
	}
}

ImpSendCommandToMaster(ci)
CommandInfo *ci;
{
	FILE *fd;
	extern int ImpMasterConn;
	int n;

	if (ImpDebug)
		fprintf(stderr,"Client sending command to master\n");
	fd = fdopen(ImpMasterConn,"w");
	if (!fd) {
		ImpFatal("+Can't open write fd to master");
		/* NOTREACHED */
	}
	fprintf(fd,"%s\n",CURRENT_VERSION_STR);	/* start with a version line */
	n = 2;			/* number of lines following */
	if (ci->str) n++;
	fprintf(fd,"%d\n",n);
	if (ImpDebug)
		fprintf(stderr,"Client sends numlines=%d to server\n",n);
	switch (ci->mode) {
	case MODE_START:
		fprintf(fd,"s\n");
		fprintf(fd,"%s\n",ci->app);
		if (ci->str)
			fprintf(fd,"%s\n",ci->str);
		break;
	case MODE_TALK:
		fprintf(fd,"t\n");
		fprintf(fd,"%s\n",ci->handle);
		if (ci->str)
			fprintf(fd,"%s\n",ci->str);
		break;
	case MODE_QUIT:
		fprintf(fd,"q\n");
		fprintf(fd,"%s\n",ci->handle);
		break;
	default:
		ImpFatal("Bad mode %d when trying to send to master\n",
				ci->mode);
		/* NOTREACHED */
	}
	fflush(fd);
	if (ImpDebug)
		fprintf(stderr,"Client done sending to master\n");
}

void
ImpPrintReplyFromMaster()
{
	FILE *fd;
	extern int ImpMasterConn;
	char line[500];
	int nc;

	if (ImpDebug)
		fprintf(stderr,"Client waiting for response from master\n");
	fd = fdopen(ImpMasterConn,"r");
	if (!fd) {
		ImpFatal("+Can't open write fd to master");
		/* NOTREACHED */
	}
	nc = 0;
	while (!feof(fd) && fgets(line,sizeof(line),fd)) {
		if (ImpDebugReply)
			fprintf(stderr,"Reply: %s",line);
		if (line[0])
			nc = 1;	/* note we printed at least one char */
		printf("%s",line);
	}
	if (nc==0) {
		ImpFatal("No response from job server");
		/* NOTREACHED */
	}
	if (ImpDebug)
		fprintf(stderr,"Client done receiving response from master\n");
}

char *
ImpFdGetLine(d,bufp,bufusedp,bufsiz)
int d;
char *bufp;
int *bufusedp;
int bufsiz;
{
	static char line[500];
	char *p;
	int u,n;
	int readcount;
	long r;
	struct timeval to;
	int t, i;

	readcount = 0;
	u = *bufusedp;
tryAgain:
	bufp[u] = 0;	/* ensure null terminated */
	p = strchr(bufp,'\n');
	if (p) {
		/* We already have a complete line */
		*p++ = 0;
		strcpy(line,bufp);
		u -= (p-bufp);
		for (i=0; i<u; i++) bufp[i] = p[i];
			/* above line is for: memmove(bufp,p,u); */
		bufp[u] = 0;
		*bufusedp = u;
		return line;
	}
reread:
	errno = 0;
	n = read(d,bufp+u,bufsiz-u-1);
	readcount++;
	if (errno==EWOULDBLOCK || errno==EAGAIN || errno==EINTR) {
/* TBD - need to fix this code to work for d>=32 */
		r = 1<<d;
		to.tv_sec = 1;
		to.tv_usec = 0;
		t = select(d+1,(fd_set *)&r,(fd_set *)0,(fd_set *)0,&to);
			/* wait for it to be ready */
		if (t>0)
			goto reread;	/* it's ready */
		/* TBD - we could give up after a while... */
		goto reread;	/* for now, just try again */
	}
	if (n<0) {
		perror("Error reading commands");
		strcpy(line,bufp);
		*bufusedp = 0;
		return line;
	}
	if (n==0) {	/* eof */
		strcpy(line,bufp);
		*bufusedp = 0;
		return line;
	}
	u += n;
	if (readcount>1)
		sleep(1);	/* hopefully we will never get here */
	goto tryAgain;
}

ImpReadCommand(d,ci)
int d;
CommandInfo *ci;
{
	static char prog[500];
	static char args[500];
	int l;
	char *p;
	int numlines;
	char rbuf[500];
	int rbufcount;

	if (ImpDebug)
		fprintf(stderr,"Master reading command\n");
	memset((char *)ci,'\0',sizeof(*ci));

	if (ImpDebug)
		fprintf(stderr,"Master reading version number\n");
	rbuf[0] = 0;
	rbufcount = 0;
	p = ImpFdGetLine(d,rbuf,&rbufcount,sizeof(rbuf));/* version number */
	if (!p) {
		fprintf(stderr,"No version number in client command\n");
		return;
	}
	if (strcmp(p,CURRENT_VERSION_STR)!=0) {
		fprintf(stderr,"Expected version %s, receieved %s\n",
			CURRENT_VERSION_STR,p);
		return;		/* ignore if wrong version */
	}
	if (ImpDebug)
		fprintf(stderr,"Master gets version %s\n",p);
	
	p = ImpFdGetLine(d,rbuf,&rbufcount,sizeof(rbuf));
			/* number of lines to follow */
	if (!p) {
		fprintf(stderr,"No linecount in client command\n");
		return;
	}
	numlines = atoi(p);
	if (numlines<=0) {
		fprintf(stderr,"No data lines in client command\n");
		return;
	}
	if (ImpDebug)
		fprintf(stderr,"Master gets numlines %d\n",numlines);

	if (--numlines<0) return;
	p = ImpFdGetLine(d,rbuf,&rbufcount,sizeof(rbuf));
	if (strcmp(p,"s")==0) {
		ci->mode = MODE_START;
	}
	else if (strcmp(p,"t")==0) {
		ci->mode = MODE_TALK;
	}
	else if (strcmp(p,"q")==0) {
		ci->mode = MODE_QUIT;
	}
	else {
		fprintf(stderr,"Unknown mode line \"%s\"\n",p);
		return;		/* ignore unknown stuff */
	}
	if (ImpDebug)
		fprintf(stderr,"Master gets mode string %s\n",p);

	if (--numlines<0) return;
	p = ImpFdGetLine(d,rbuf,&rbufcount,sizeof(rbuf));
	strcpy(prog,p);
	switch (ci->mode) {
	case MODE_START:
		ci->app = prog;
		break;
	case MODE_TALK:
	case MODE_QUIT:
		ci->handle = prog;
		break;
	}
	if (ImpDebug)
		fprintf(stderr,"Master gets prog string %s\n",prog);

	if (--numlines<0) return;
	p = ImpFdGetLine(d,rbuf,&rbufcount,sizeof(rbuf));
	if (p) {
		strcpy(args,p);
		l = strlen(args);
		ci->str = args;
	}
	if (ImpDebug)
		fprintf(stderr,"Master gets args string %s\n", args);

	if (ImpDebug) {
		fprintf(stderr,"Mode=%d, app=%s, handle=%s, str=%s\n",
			ci->mode, ci->app?ci->app:"",
			ci->handle?ci->handle:"",
			ci->str?ci->str:"");
	}
}

/* end */
