
static char rcsid[] = "ccache_hash.c,v 1.14 1995/11/29 00:48:30 duane Exp";
/******************************************************************************
**
** FILE
**      hash.c
**
** DESCRIPTION
**      This file contains the socket control algorithms and structures.
**	It manages the hash table and expirations of sockets.
**
** FUNCTIONS
**      SockInit()
**	free_url()
**	Compare_URL()
**	StripFileName()
**	SearchList()
**	GetConnection()
**	DisconnectOne()
**	TouchDate()
**	SockRetrieve()
**	SockGetData()
**	ExpireSocket()
**	ShutDownCache()
**	DestroyDataReturn()
**	DumpHash()
**
**  David Merkel & Mark Peterson, University of Colorado - Boulder, July 1994
 *
 *  ----------------------------------------------------------------------
 *  Copyright (c) 1994, 1995.  All rights reserved.
 *  
 *    The Harvest software was developed by the Internet Research Task
 *    Force Research Group on Resource Discovery (IRTF-RD):
 *  
 *          Mic Bowman of Transarc Corporation.
 *          Peter Danzig of the University of Southern California.
 *          Darren R. Hardy of the University of Colorado at Boulder.
 *          Udi Manber of the University of Arizona.
 *          Michael F. Schwartz of the University of Colorado at Boulder.
 *          Duane Wessels of the University of Colorado at Boulder.
 *  
 *    This copyright notice applies to software in the Harvest
 *    ``src/'' directory only.  Users should consult the individual
 *    copyright notices in the ``components/'' subdirectories for
 *    copyright information about other software bundled with the
 *    Harvest source code distribution.
 *  
 *  TERMS OF USE
 *    
 *    The Harvest software may be used and re-distributed without
 *    charge, provided that the software origin and research team are
 *    cited in any use of the system.  Most commonly this is
 *    accomplished by including a link to the Harvest Home Page
 *    (http://harvest.cs.colorado.edu/) from the query page of any
 *    Broker you deploy, as well as in the query result pages.  These
 *    links are generated automatically by the standard Broker
 *    software distribution.
 *    
 *    The Harvest software is provided ``as is'', without express or
 *    implied warranty, and with no support nor obligation to assist
 *    in its use, correction, modification or enhancement.  We assume
 *    no liability with respect to the infringement of copyrights,
 *    trade secrets, or any patents, and are not responsible for
 *    consequential damages.  Proper use of the Harvest software is
 *    entirely the responsibility of the user.
 *  
 *  DERIVATIVE WORKS
 *  
 *    Users may make derivative works from the Harvest software, subject 
 *    to the following constraints:
 *  
 *      - You must include the above copyright notice and these 
 *        accompanying paragraphs in all forms of derivative works, 
 *        and any documentation and other materials related to such 
 *        distribution and use acknowledge that the software was 
 *        developed at the above institutions.
 *  
 *      - You must notify IRTF-RD regarding your distribution of 
 *        the derivative work.
 *  
 *      - You must clearly notify users that your are distributing 
 *        a modified version and not the original Harvest software.
 *  
 *      - Any derivative product is also subject to these copyright 
 *        and use restrictions.
 *  
 *    Note that the Harvest software is NOT in the public domain.  We
 *    retain copyright, as specified above.
 *  
 *  HISTORY OF FREE SOFTWARE STATUS
 *  
 *    Originally we required sites to license the software in cases
 *    where they were going to build commercial products/services
 *    around Harvest.  In June 1995 we changed this policy.  We now
 *    allow people to use the core Harvest software (the code found in
 *    the Harvest ``src/'' directory) for free.  We made this change
 *    in the interest of encouraging the widest possible deployment of
 *    the technology.  The Harvest software is really a reference
 *    implementation of a set of protocols and formats, some of which
 *    we intend to standardize.  We encourage commercial
 *    re-implementations of code complying to this set of standards.  
 *  
******************************************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "ccache.h"
#include "time_it.h"
#include "util.h"

/* Allow differnet passwords on the same socket */
#define ALLOW_DIFFERENT_PASSWORDS

/* Hash function is last 8 bits of the IP number of the destination host. */
#define GetHKey(theIP) ((int) ((theIP) & 0xff))

/* Flag to prevent non-initialized hash from being used. */
static Boolean initialized = FALSE;

/* The hash table. */
static SockCntlRec *SockHash[HASH_SLOTS];

/* The expiration queue. */
static SockCntlRec *TimeOutListNew = NULL;
static SockCntlRec *TimeOutListOld = NULL;

/* Current number of connections being maintained.  */
static int currConnections = 0;

/* Default values. */
static int maxConnections = 10;
static long timeOut = 300;

/* Called on timer expiration. */
int ExpireSocket(int whichTimer);

/* Called on SIGUSR1 to dump the hash table to stdout. */
static void DumpHash();


/****************************************************************************
** function:  void SockInit()
**
** parameters: InitConfigRec sets the initialization parameters to the
**             library package.  If NULL, we use the defaults.
**
** postcondistions:  The structures are initialized and ready to accept
**                   connections.
**
** other: This function should only be called once, and must be called before
**        any other related function call.
*****************************************************************************/
void SockInit(InitConfigRec * initParam)
{
	if (initialized)	/* If we've already initialed, don't do it again. */
		return;

	initialized = TRUE;	/* Set initialization to TRUE */

	signal(SIGUSR1, DumpHash);	/* On this interrupt the Hash table */
	/* spills its guts. */

	/* Initialize the Hash Table to NULL's */
	memset((char *) SockHash, '\0', (sizeof(SockCntlRec *) * HASH_SLOTS));

	/* Initialize the timer package. */
	InitTimer();

	/* If no initailization was given use default values. */
	if (!initParam)
		return;

	/* Set the maximum number of connections to maintain. */
	if (initParam->maxConnections > MIN_CONNECTIONS)
		maxConnections = initParam->maxConnections;

	/* Set the timeOut for inactive connections. */
	if (initParam->timeOut > MIN_TIMEOUT)
		timeOut = initParam->timeOut;
}

/***************************************************************************
**  function: free_url()
**
**  parameters: An allocated URL structure.
**
**  postconditions:  The URL is deallocated.
****************************************************************************/
static void free_url(URL * toFree)
{
	if (toFree->url)
		xfree(toFree->url);
	if (toFree->pathname)
		xfree(toFree->pathname);
	if (toFree->host)
		xfree(toFree->host);
	if (toFree->user)
		xfree(toFree->user);
	if (toFree->password)
		xfree(toFree->password);
	if (toFree->raw_pathname)
		xfree(toFree->raw_pathname);
	xfree((char *) toFree);
	toFree = NULL;
}

/*************************************************************************
** function: CompareURL()
**
** parameters: Two preallocated URL structures.
**
** postconditions:  Returns TRUE if these to connections have the same
**                  hostname and port number.
*************************************************************************/
static int CompareURL(URL * ua, URL * ub)
{
#ifdef DEBUG
	printf("inside of CompareURL\n");
	printf("ub->host: %s\n", ub->host);
	printf("ua->host: %s\n", ua->host);
#endif


	if (strcmp(ua->host, ub->host) != 0)
		return FALSE;
	if (ua->port != ub->port)
		return FALSE;

	return TRUE;
}


/******************************************************************************
** function name: StripFileName()
**
** preconditions: Buffer must be an empty pre-allocated string.
**		  Path must contain a unix pathname.
**
** postconditions: The last unit of the pathname is placed into the buffer.
**
******************************************************************************/
static void StripFileName(buffer, path)
     char *buffer;
     char *path;
{
	int i;

	for (i = strlen(path); i >= 0; i--) {
		if (path[i] == '/')
			break;
	}

	strcpy(buffer, &path[i + 1]);
}


/**********************************************************************
**  function: SearchList()
**
**  parameters: An allocated SockCntlRec and a URL.  The SockCntrlRec *
**  should point to the beginning of the hash to search.
**
**  postconditions: A socket with a matching URL is returned if an
**  INACTIVE one is available.
**********************************************************************/
static SockCntlRec *SearchList(SockCntlRec * theRec, URL * theURL)
{
	SockCntlRec *tmpPtr;
	char errorReply[SERV_REPLY_LENGTH];
	URL *newURL;
	int errNum;

	/* Make sure we've initizlized and the URL is valid */
	if (!initialized || theURL == NULL)
		return (NULL);

	newURL = dup_url(theURL);
	tmpPtr = theRec;
	/* Go until we've checked all in the list. */
	while (tmpPtr != NULL) {

		/* See if this one is inactive and matches our URL. */
		if ((tmpPtr->sockStateStor == INACTIVE) &&
		    (CompareURL(tmpPtr->socketInfo, theURL))) {
			/* If the user or password is different relogin in. */
			if ((strcmp(tmpPtr->socketInfo->user, theURL->user) != 0)
#ifndef ALLOW_DIFFERENT_PASSWORDS
			    || (strcmp(tmpPtr->socketInfo->password, theURL->password) != 0)
#endif
			    ) {
				free_url(tmpPtr->socketInfo);
				tmpPtr->socketInfo = newURL;
				if ((errNum = Login(tmpPtr->socketInfo->user,
					    tmpPtr->socketInfo->password, tmpPtr->theSocket, FALSE,
					    errorReply)) < 0) {
					CancelTimer(tmpPtr->timerid);
					ExpireSocket(tmpPtr->timerid);
					DoError(errNum, errorReply);
					return (NULL);
				}
				return (tmpPtr);
			}
			free_url(tmpPtr->socketInfo);
			tmpPtr->socketInfo = newURL;
			return (tmpPtr);
		}
		/* Look at the next one. */
		tmpPtr = tmpPtr->hashNext;
	}
	return (NULL);
}

/****************************************************************************
** function: GetConnection()
**
** parameters:  An allocated URL and the IP number of the host to connect
** to.
**
** postconditions:  The socket is returned to the structure.
****************************************************************************/
static SockCntlRec *GetConnection(URL * theURL, long theIPnum)
{
	SockCntlRec *locSockRec;
	int errNum;
	char errorReply[SERV_REPLY_LENGTH];
	char errorIgnore[SERV_REPLY_LENGTH];

	locSockRec = (SockCntlRec *) malloc(sizeof(SockCntlRec));

	if (!locSockRec) {
		DoError(MEMORY_ERROR, NULL);
		return NULL;
	}
	/* Configure a new socket. */
	locSockRec->socketInfo = theURL;
	locSockRec->theHostIP = theIPnum;
	locSockRec->timerid = SetTimer(timeOut, ExpireSocket);
	locSockRec->incomDataBuf = NULL;
	locSockRec->incomDataFile = NULL;
	locSockRec->incomDataSize = 0;
	locSockRec->listPrev = NULL;
	locSockRec->listNext = NULL;
	locSockRec->hashPrev = NULL;
	locSockRec->hashNext = NULL;
	locSockRec->theSocket = FTPInit(theIPnum, theURL->port, errorReply);


	if (locSockRec->theSocket < 0) {
		DoError(locSockRec->theSocket, errorReply);
		free_url(theURL);
		theURL = NULL;
		free((char *) locSockRec);
		return NULL;
	}
	/* Try to Login, if not successful, close the connection. */
	if ((errNum = Login(theURL->user, theURL->password,
		    locSockRec->theSocket, TRUE, errorReply)) < 0) {
		CancelTimer(locSockRec->timerid);
		Disconnect(locSockRec->theSocket, errorIgnore);
		free_url(locSockRec->socketInfo);
		theURL = NULL;
		free((char *) locSockRec);
		locSockRec = NULL;

		if (errNum != SERV_REPLY_ERROR)
			DoError(errNum, NULL);
		else
			DoError(errNum, errorReply);
	}
	return (locSockRec);
}

/*********************************************************************
** function: DisconnectOne()
**
** parameters: None
**
** postconditions: Attempts to disconnect the oldest inactive connection.
*********************************************************************/
Boolean DisconnectOne()
{
	SockCntlRec *locSockRec;
	int theHKey;
	char errorReply[SERV_REPLY_LENGTH];

	/* Start by looking at the oldest connection. */
	locSockRec = TimeOutListOld;

	/* Search the list for the oldest inactive connection. */
	while ((locSockRec != NULL) && (locSockRec->sockStateStor != INACTIVE))
		locSockRec = locSockRec->listPrev;

	/* If we didn't find one, all are active. */
	if (locSockRec == NULL)
		return FALSE;

	/* Strip it from the expiration list. */
	if (locSockRec->listNext == NULL)
		TimeOutListOld = locSockRec->listPrev;
	else
		locSockRec->listNext->listPrev = locSockRec->listPrev;

	if (locSockRec->listPrev == NULL)
		TimeOutListNew = locSockRec->listNext;
	else
		locSockRec->listPrev->listNext = locSockRec->listNext;


	/* Strip it from the active hash. */
	theHKey = GetHKey(locSockRec->theHostIP);

	if (locSockRec->hashPrev == NULL) {
		SockHash[theHKey] = locSockRec->hashNext;
		if (SockHash[theHKey] != NULL)
			SockHash[theHKey]->hashPrev = NULL;
	} else
		locSockRec->hashPrev->hashNext = locSockRec->hashNext;

	if (locSockRec->hashNext != NULL)
		locSockRec->hashNext->hashPrev = locSockRec->hashPrev;

	/* Disconnect the socket. */
	CancelTimer(locSockRec->timerid);	/* Cancel the timer. */

	Disconnect(locSockRec->theSocket, errorReply);

	free_url(locSockRec->socketInfo);
	free((char *) locSockRec);

	return TRUE;
}

/**************************************************************************
**  function: TouchDate()
**
**  parameters: A record to update.
**
**  postconditions:  Last access time field is updated and this connection
**  is moved to the front of the expiration list.
***************************************************************************/
static void TouchDate(SockCntlRec * theRec)
{

	/* If it's already first don't need to move it. */
	if (theRec == TimeOutListNew)
		return;

	/* Move it to the front of the list. */
	theRec->listPrev->listNext = theRec->listNext;
	if (theRec->listNext != NULL)
		theRec->listNext->listPrev = theRec->listPrev;

	if (theRec == TimeOutListOld)
		TimeOutListOld = theRec->listPrev;

	theRec->listPrev = NULL;
	theRec->listNext = TimeOutListNew;

	TimeOutListNew->listPrev = theRec;
	TimeOutListNew = theRec;

	CancelTimer(theRec->timerid);
	theRec->timerid = SetTimer(timeOut, ExpireSocket);
}

/**********************************************************************
** function: SockRetrieve()
**
** parameters: An allocated URL and the way the returned data is to 
** be buffered.
**
** postconditions: A new SockCntlRec will be returned, which will either
** represent a new one or a preopened connection.
***********************************************************************/
static SockCntlRec *SockRetrieve(URL * theURL, BufferStatus sockRetrType)
{
	unsigned long theIPnum;
	unsigned int theHKey;
	SockCntlRec *newSock;
	URL *newURL;

	/* If invalid data or not initialized, just return a NULL. */
	if (theURL == NULL)
		return (NULL);

	if (!initialized)
		SockInit(NULL);

	/*Get the IP of the host we're looking for. */
	if ((theIPnum = gethostinhex(theURL->host)) == -1)
		return (NULL);

	DoError(NO_ERROR, NULL);

	/* Find the hash bucket to look in. */
	theHKey = GetHKey(theIPnum);

	/* Try to find a connection in that bucket. */
	newSock = SearchList(SockHash[theHKey], theURL);

	if (newSock != (NULL)) {	/* Move to front of list and update time. */
		TouchDate(newSock);
		newSock->sockStateStor = sockRetrType;
		return (newSock);
	}
	/* Need to open a new conection. */
	/* If we are already at the maxConnections, try and disconnect one. */
	if (currConnections == maxConnections) {
		if (!DisconnectOne())
			return (NULL);
	}
	/* Get a new connnection. */
	newURL = dup_url(theURL);	/* save a copy */
	newSock = GetConnection(newURL, theIPnum);

	if (newSock == NULL)
		return (NULL);

	/* Set it up. */
	newSock->sockStateStor = sockRetrType;

	newSock->hashPrev = NULL;
	newSock->hashNext = SockHash[theHKey];

	if (SockHash[theHKey] != NULL)
		SockHash[theHKey]->hashPrev = newSock;

	SockHash[theHKey] = newSock;

	newSock->listPrev = NULL;
	newSock->listNext = TimeOutListNew;

	if (TimeOutListNew != NULL)
		TimeOutListNew->listPrev = newSock;
	else
		TimeOutListOld = newSock;

	TimeOutListNew = newSock;

	return (newSock);
}

/*************************************************************************
** function: SockGetData()
**
** parameters: Need a URL struct parsed by liburl and return string for
**	fetched filename  dcm 6/28/94
**
** postcondition:  Data is returned as requested if possible.
**************************************************************************/
DataReturn *SockGetData(URL * myURL, BufferStatus theStatus, char *filename)
{

	SockCntlRec *mySocket;
	DataReturn *myDataReturn;
	int errNum;
	char errorReply[SERV_REPLY_LENGTH];

	if (!initialized)
		SockInit(NULL);

	/* If not an FTP url, then we can't ftp anything. */
	if (myURL->type != URL_FTP) {
		DoError(BAD_URL_TYPE, NULL);
		return NULL;
	}
	/* Get a socket. */

	Freeze();
	mySocket = SockRetrieve(myURL, theStatus);
	Thaw();

	if (!mySocket)
		return NULL;

	if (GetError() != noErr) {
		mySocket->sockStateStor = INACTIVE;
		return NULL;
	}
	myDataReturn = (DataReturn *) malloc(sizeof(DataReturn));

	/* Set flags for FTP layer. */
	if (theStatus == MEMORY_ONLY) {
		myDataReturn->inMemory = TRUE;
		myDataReturn->useTempFile = FALSE;
	} else {
		myDataReturn->inMemory = FALSE;

		if (theStatus == TEMP)
			myDataReturn->useTempFile = TRUE;
		else if (filename == NULL) {
			myDataReturn->useTempFile = FALSE;
			StripFileName(myDataReturn->fileName,
			    myURL->pathname);
		} else {
			myDataReturn->useTempFile = FALSE;
			strcpy(myDataReturn->fileName, filename);
		}
	}

	/* Try to retrieve the requested file. */
	errNum = Retrieve(myURL->pathname + 1, mySocket->theSocket, NULL, FALSE,
	    TRUE, errorReply, myDataReturn);

	mySocket->sockStateStor = INACTIVE;

	if (errNum < 0) {
		if (errNum == SERV_REPLY_ERROR)
			DoError(errNum, errorReply);
		else
			DoError(errNum, NULL);

		DestroyDataReturn(myDataReturn);

		return (NULL);
	}
	return (myDataReturn);
}


/******************************************************************************
** function name: ExpireSocket()
**
** preconditions: A timer must have already been set for this socket.
**                The whichTimer should identify the socket to be expired.
**
** postconditions:  The socket is expired and the connection is closed.
**
******************************************************************************/
int ExpireSocket(int whichTimer)
{
	SockCntlRec *locSockRec;
	int theHKey;
	char errorReply[SERV_REPLY_LENGTH];

	if (!initialized)
		return 1;


	locSockRec = TimeOutListOld;

	/* Try to find the correct socket. */
	while ((locSockRec != NULL) && (locSockRec->timerid != whichTimer))
		locSockRec = locSockRec->listPrev;

	if (locSockRec == NULL)
		return 1;

	/* If it's still being used reset the timer. */
	if (locSockRec->sockStateStor != INACTIVE) {
		locSockRec->timerid = SetTimer(timeOut, ExpireSocket);
		return 1;
	}
	/* Strip it from the expiration list. */
	if (locSockRec->listNext == NULL)
		TimeOutListOld = locSockRec->listPrev;
	else
		locSockRec->listNext->listPrev = locSockRec->listPrev;

	if (locSockRec->listPrev == NULL)
		TimeOutListNew = locSockRec->listNext;
	else
		locSockRec->listPrev->listNext = locSockRec->listNext;

	/* Strip it from the active hash. */
	theHKey = GetHKey(locSockRec->theHostIP);

	if (locSockRec->hashPrev == NULL) {
		SockHash[theHKey] = locSockRec->hashNext;
		if (SockHash[theHKey] != NULL)
			SockHash[theHKey]->hashPrev = NULL;
	} else
		locSockRec->hashPrev->hashNext = locSockRec->hashNext;

	if (locSockRec->hashNext != NULL)
		locSockRec->hashNext->hashPrev = locSockRec->hashPrev;

	/* Disconnect the socket. */
	Disconnect(locSockRec->theSocket, errorReply);

	free_url(locSockRec->socketInfo);
	free((char *) locSockRec);
	return 1;
}


/******************************************************************************
** function name: ShutDownCache()
**
** preconditions: The cache must have previously been initialized.
**
** postconditions:  Timer's are all cleared, everything is disconnected.
**
******************************************************************************/
void ShutDownCache()
{

	if (!initialized)
		return;

	/* Freeze Timers and disconnect all sockets. */
	Freeze();
	while (DisconnectOne());

	initialized = FALSE;

	free((char *) SockHash);

	/* Reset signals. */
	signal(SIGALRM, NULL);
	signal(SIGUSR1, NULL);

	return;
}


/******************************************************************************
** function name: DestroyDataReturn()
**
** preconditions: Need an allocated DataReturn.
**
** postconditions:  Memory is freed.
**
******************************************************************************/

void DestroyDataReturn(theDataReturn)
     DataReturn *theDataReturn;
{
	free(theDataReturn->buffer);
	free(theDataReturn);
	theDataReturn = NULL;
}


/******************************************************************************
** function name: DumpHash()
**
** preconditions: SIGUSR1 must be set up to call this function.
**
** postconditions: On a SIGUSR the hash tables internals are printed.
**
******************************************************************************/

static void DumpHash()
{
	int i;
	SockCntlRec *theSocket;

	/*
	 * If we're not initialized for some reason, print out an 
	 * appropriate message.
	 */
	if (!initialized) {
		printf("Sorry, the hash table has not been initialized.\n");
		printf("No data to output.\n");
		return;
	}
	/* Go through the slots to find all connections. */
	for (i = 0; i < HASH_SLOTS; i++) {
		/* If we find one, print out its stuff. */
		if (SockHash[i] != NULL) {
			printf("Hash Slot %d:\n", i);
			theSocket = SockHash[i];
			/* Print out ALL nodes in the linked list. */
			while (theSocket != NULL) {
				printf("\t%s\n", theSocket->socketInfo->url);
				theSocket = theSocket->hashNext;
			}
		}
	}
}
