/*
 * clienthandle.c
 *
 * Copyright (C) Leni Mayo 2001-2002
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License, version 2, as
 *	published by the Free Software Foundation.
 *	
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *	
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
/*
 * Overview
 *
 * The client / server interaction is heavily influenced by a design
 * decision to assume minimal communction services, namely
 * one or two named fifo's and two user-signals.
 *
 * The thinking was that this would allow use of the need/provide
 * primitives as early as possible in the life cycle of machine boot.
 * Otherwise, we could have avoided writing all this handle creation/deletion
 * and message marshalling/unmarshalling stuff and used any number of
 * higher-level services instead.
 *
 * A "handle" below is a sink and source of events, the handle* fuctions are
 * the associated methods.
 *
 * Despite the use of low-level transport primitives, this interface
 * provides some support for separating the client/server
 * messaging/signalling layer from the application logic of the Commands.
 *
 * leni - Sun Nov 25 06:19:58 EST 2001
 */
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/time.h> /* for gettimeofday() stuff */
#include <fcntl.h>
#include <string.h> /* for strerror() */
#include <errno.h>
#include "command.h"
#include "clienthandle.h"
#include "log.h"

static void signal_handler(int sig);

static void handleSet(Handle *pHandle, int retval);

/*
 * handleInitialise(Handle *pHandle)
 *
 * Return 0 on success, > 0 otherwise
 */
int handleInitialise(Handle *pHandle)
{
	struct sigaction sa;
	sigset_t         ss;
	int ret = 1;

	sigemptyset (&ss);
	sigaddset (&ss, SIG_RETURNSUCCESS);
	sigaddset (&ss, SIG_RETURNFAIL);

	/*
	 * stricly speaking, we don't need to block our signals until immediately
	 * prior to the write to the fifo, but doing it here make the code
	 * look neater.
	 */
	ret = sigprocmask (SIG_BLOCK, &ss, &pHandle->ssOldMask);

	if (ret == 0)
	{
		sigemptyset (&sa.sa_mask);
		sa.sa_flags = 0;
		sa.sa_handler = signal_handler;

		ret = sigaction (SIG_RETURNSUCCESS,  &sa, NULL);
	}

	if (ret == 0)
	{
		ret = sigaction (SIG_RETURNFAIL, &sa, NULL);
	}

	if (ret != 0)
	{
		printLog(eLogLevelError, "Unable to set signal handler\n");
	}
	else 
	{
		char fifoName[PATH_MAX];

		getClientServerPipePath(fifoName, sizeof(fifoName));

		pHandle->fdFifo = open(fifoName, O_WRONLY | O_NONBLOCK, 0);

		ret = pHandle->fdFifo < 0 ? 1 : 0;

		if (ret != 0)
		{
			printLog(eLogLevelError, "Server down?  Unable to open \"%s\" for writing - %s\n", fifoName, strerror(errno));
		}
	}

	return ret;
}

/*
 *	handleInitiate()
 *	Return 0 if everything worked out ok.
 *
 */
int handleInitiate(Handle *pHandle, const Message *pMessage, HandleCallback callback, void *pUserData)
{
	int cBytesToWrite, cBytesWritten;
	char fifoName[PATH_MAX];
	int fRetryWrite = TRUE;
	int i = 0;
	const int nMaxRetries = 200;   /* cap the retries at 200 x 0.05 seconds */
	const int nSleepMicro = 50000; /* to avoid possibility of infinite loop */
	struct timeval tv;

	getClientServerPipePath(fifoName, sizeof(fifoName));

	assert(pHandle && pMessage);
	
	pHandle->callback = callback;
	pHandle->pUserData = pUserData;

	cBytesToWrite = pMessage->header.cBytes + sizeof(MessageHeader);

	while (fRetryWrite)
	{
		cBytesWritten = write(pHandle->fdFifo, pMessage, cBytesToWrite);

		i++; /* count how many times we're retrying */

		if (i < nMaxRetries && cBytesWritten != cBytesToWrite && errno == EAGAIN)
		{
			fRetryWrite = TRUE;
			usleep(nSleepMicro);
		}
		else
		{
			fRetryWrite = FALSE;
		}
	}

	if (cBytesWritten != cBytesToWrite)
	{
		int r = gettimeofday(&tv, NULL);
		printLog(eLogLevelError, "%d: Error in writing to fifo %s: %s.  Desired bytes (%d), actual bytes (%d), i = %d, ppid = %d, time is %ld s, %ld us\n",
		   	getpid(), fifoName, strerror(errno), cBytesToWrite, cBytesWritten,
			i, getppid(), tv.tv_sec, tv.tv_usec);

		if (errno == EAGAIN && i >= nMaxRetries)
		{
			printLog(eLogLevelError,
			    "%d: Strange that the server allowed the pipe to remain full for over %f seconds.\n",
		    	nMaxRetries * (nSleepMicro / 1000000.0));
		}
	}
	else
	{
		/* fprintf(stdout, "Wrote to fifo %s: %d total bytes\n", fifoName, ret); */
	}

	return cBytesWritten == cBytesToWrite ? 0 : 1;
}

void handleEventLoop(Handle *pHandle)
{
	sigset_t ssZero;
	int fContinueEventLoop = TRUE;

	sigemptyset (&ssZero);

	while (fContinueEventLoop)
	{
		sigsuspend (&ssZero);

		fContinueEventLoop = pHandle->callback(&pHandle->msgReply, pHandle->pUserData);
	}
}

/*
 * Just for completeness, unblock the signals that
 * were blocked by handleInitialise() and close the pipe
 */
void handleCleanup(Handle *pHandle)
{
	/* leni TODO: check return codes from all signal operations
	*/
	sigprocmask(SIG_SETMASK, &(pHandle->ssOldMask), NULL);
	close(pHandle->fdFifo);
}

static void handleSet(Handle *pHandle, int retval)
{
	assert(pHandle != NULL);

	pHandle->msgReply.retval = retval;
}

static void signal_handler(int sig)
{
	if (sig == SIG_RETURNSUCCESS)
		handleSet(&eventHandle, 0);
	else if (sig == SIG_RETURNFAIL)
		handleSet(&eventHandle, 1);
	else
	{
		assert(FALSE);  /* caught an unexpected signal */
	}
}

int onEventDefaultBase(const MessageReply *pReplyMessage, void *pUserData)
{
	HandleEventDefaultUserData *pUd = (HandleEventDefaultUserData *) pUserData;
	Command *pCommand;
	char *pszFileChannel = NULL;

	assert(pReplyMessage != NULL);
	assert(pUserData != NULL);

	pCommand = pUd->pCommand;

	pUd->retval = pReplyMessage->retval;

	if (pCommand->c == eCommandTypeQuery)
	{
		CommandQuery *p = (CommandQuery *) pCommand;

		pszFileChannel = p->fileChannel;
	}
	else if (pCommand->c == eCommandTypeViaFile) 
	{
		CommandViaFile *p = (CommandViaFile *) pCommand;

		pszFileChannel = p->fileChannel;
	}

	if (pszFileChannel)
		strcpy(pUd->fileChannel, pszFileChannel);
	else
		strcpy(pUd->fileChannel, "");

	return FALSE;
}

int onEventDefault(const MessageReply *pReplyMessage, void *pUserData)
{
	HandleEventDefaultUserData *pUd = (HandleEventDefaultUserData *) pUserData;
	int ret;

	assert(pReplyMessage != NULL);
	assert(pUserData != NULL);

	ret = onEventDefaultBase(pReplyMessage, pUserData);

	if (strlen(pUd->fileChannel) > 0)
	{
		FILE *fp;

		fp = fopen(pUd->fileChannel, "r");

		if (fp == NULL)
		{
			printLog(eLogLevelError, "Unable to open file for reading: \"%s\" - %s\n",
					pUd->fileChannel, strerror(errno));

			pUd->retval = 1;
		}

		copyFpToFp(stdout, fp);

		fclose(fp);

		unlink(pUd->fileChannel);
	}

	return ret;
}

/*
 * int openChannel()
 *	Put the name of a temporary file into filename[]
 *	and make sure that it can be opened.
 * Returns:
 *	a file descriptor if the filename is usable, -1 otherwise.
 *
 */
int openChannel(char filename[], int len)
{
	int fd = -1;
	const char *pszTemplate = "/tmp/XXXXXX";

	assert(len > 0);

	filename[0] = '\0';

	if (len > (strlen(pszTemplate) + 1))
	{
		strcpy(filename, pszTemplate);

		fd = mkstemp(filename);
	}

	return fd;
}
