/************************************************************************
	  Copyright 1988, 1991 by Carnegie Mellon University

			  All Rights Reserved

Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted, 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 Carnegie Mellon University not be used
in advertising or publicity pertaining to distribution of the software
without specific, written prior permission.

CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
IN NO EVENT SHALL CMU 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.
************************************************************************/

/*
 * Generalized hash table ADT
 *
 * Provides multiple, dynamically-allocated, variable-sized hash tables on
 * various data and keys.
 *
 * This package attempts to follow some of the coding conventions suggested
 * by Bob Sidebotham and the AFS Clean Code Committee of the
 * Information Technology Center at Carnegie Mellon.
 */


#include <sys/types.h>
#include <stdlib.h>

#include "defs.h"
#include "defaults.h"
#include "hash.h"
#include "hashf.h"
#include "report.h"
#include "my_syslog.h"

#define TRUE            1
#define FALSE           0
#ifndef NULL
#define NULL            0
#endif

/*
 * This can be changed to make internal routines visible to debuggers, etc.
 */
#ifndef PRIVATE
#define PRIVATE static
#endif

#ifdef  __STDC__
#define P(args) args
#else
#define P(args) ()
#endif

PRIVATE void hashi_FreeMembers P((hash_member *, hash_freefp));

#undef P



/*
 * Hash table initialization routine.
 *
 * This routine creates and intializes a hash table of size "tablesize"
 * entries.  Successful calls return a pointer to the hash table (which must
 * be passed to other hash routines to identify the hash table).  Failed
 * calls return NULL.
 */

hashf_tbl *
hashf_Init(dirname,size)
	char *dirname;
	unsigned long size;
{
	register hashf_tbl *hashtblptr;

	hashtblptr=(hashf_tbl*)malloc(sizeof(hashf_tbl));

	if(!hashtblptr) {
		report(LOG_ERR, "malloc: %s", get_errmsg());
		report(LOG_ERR, "exiting");
		exit(1);
	}
	
	hashtblptr->size=size;
	hashtblptr->dirp=opendir(dirname);
	if(!hashtblptr->dirp){
		free(hashtblptr);
		return NULL;
	}
	hashtblptr->dirname=(char*)malloc(strlen(dirname)+1);
	if(!hashtblptr) {
		report(LOG_ERR, "malloc: %s", get_errmsg());
		report(LOG_ERR, "exiting");
		exit(1);
	}
	if(!hashtblptr->dirname){
		closedir(hashtblptr->dirp);
		free(hashtblptr);
		return NULL;
	}
	strcpy(hashtblptr->dirname,dirname);
	hashtblptr->next=NULL;

	return hashtblptr;                      /* NULL if failure */
}





/*
 * This routine re-initializes the hash table.  It frees all the allocated
 * memory and resets all bucket pointers to NULL.
 */

void
hashf_Reset(hashtable)
	hashf_tbl *hashtable;
{
	DIR *dirp;
	struct dirent *direntry;		
	
	dirp=hashtable->dirp;
	if(hashtable->next){
		fclose(hashtable->next);
		hashtable->next=NULL;
	}
	rewinddir(dirp);
	while(TRUE){
		direntry=readdir(dirp);
		if(direntry==NULL || *direntry->d_name==0)
			return;
		if(direntry->d_name[0]=='.')
			continue;
		unlink(direntry->d_name);
	}
}



/*
 * Returns TRUE if at least one entry for the given key exists; FALSE
 * otherwise.
 */

int
hashf_Exists(hashtable, hashcode, compare, key)
	hashf_tbl *hashtable;
	unsigned hashcode;
	hash_cmpfp compare;
	hash_datum *key;
{
        FILE *fp;
        char name[MAXNAMLEN],buf[MAXELEMENTLEN];
        int len,rc;

        sprintf(name,"%s/%X",hashtable->dirname,hashcode % hashtable->size);
        fp=fopen(name,"r");
        if(!fp)
                return FALSE;
        while(TRUE){
                rc=fread(&len,1,4,fp);
				if(rc==0){
					fclose(fp);
					return FALSE;
				}
                rc=fread(buf,1,len,fp);
				if(rc==0){
					fclose(fp);
					return FALSE;
				}
                if((*compare)(key,buf)){
                        fclose(fp);
                        return TRUE;
                }
        }
}




/*
 * Insert the data item "element" into the hash table using "hashcode"
 * to determine the bucket number, and "compare" and "key" to determine
 * its uniqueness.
 *
 * If the insertion is successful 0 is returned.  If a matching entry
 * already exists in the given bucket of the hash table, or some other error
 * occurs, -1 is returned and the insertion is not done.
 */

int
hashf_Insert(hashtable, hashcode, compare, key, element, elen)
	hashf_tbl *hashtable;
	unsigned hashcode;
	hash_cmpfp compare;
	hash_datum *key, *element;
	unsigned long elen;
{
	FILE *fp;
	char name[MAXNAMLEN],buf[MAXELEMENTLEN];
	int rc,len;
	hashcode %= hashtable->size;
	sprintf(name,"%s/%X",hashtable->dirname,hashcode);

	fp=fopen(name,"a+");
	if(!fp)
		return -1;
	rewind(fp);

	while(TRUE) {
		rc=fread(&len,1,4,fp);
		if(rc==0)
			break;	/* EOF */
		rc=fread(buf,len,1,fp);
		if(rc==0){
			fclose(fp);
			return -1;
		}
		if((*compare)(key,buf)){
			fclose(fp);
			return -1;
		}
	} 
	rc=fwrite(&elen,4,1,fp);
	rc=fwrite(element,elen,1,fp);
	fclose(fp);

	if(rc!=0)
		return 0;
	return -1;

}



/*
 * Delete all data elements which match the given key.  If at least one
 * element is found and the deletion is successful, 0 is returned.
 * If no matching elements can be found in the hash table, -1 is returned.
 */

int
hashf_Delete(hashtable, hashcode, compare, key)
	hashf_tbl *hashtable;
	unsigned hashcode;
	hash_cmpfp compare;
	hash_datum *key;
{
	FILE *fp,*tmp;
	char name[MAXNAMLEN],buf[MAXELEMENTLEN];
	int rc,len;
	hashcode %= hashtable->size;
	sprintf(name,"%s/%X",hashtable->dirname,hashcode);

	fp=fopen(name,"r");
	if(!fp)
		return -1;
	tmp=tmpfile();
	if(!tmp){
		fclose(fp);
		return -1;
	}

	while(TRUE) {
		rc=fread(&len,1,4,fp);
		if(rc==0)
			break;	/* EOF */
		rc=fread(buf,len,1,fp);
		if(rc==0){
			fclose(fp);
			return -1;
		}
		if(!(*compare)(key,buf)){
			rc=fwrite(&len,4,1,tmp);
			if(rc==0){
				fclose(fp);
				fclose(tmp);
				return -1;
			}
			rc=fwrite(buf,len,1,tmp);
			if(rc==0){
				fclose(fp);
				fclose(tmp);
				return -1;
			}
		}
	} 
	if(ftell(tmp)==0){
		fclose(tmp);
		fclose(fp);
		unlink(name);
		return 0;
	}
	rewind(tmp);
	fclose(fp);
	fp=fopen(name,"w");
	rc=fgetc(tmp);
	while(rc!=EOF) {
		fputc(rc,fp);
		rc=fgetc(tmp);
	}

	fclose(fp);
	fclose(tmp);
	return 0;
}
int
hashf_Replace(hashtable, hashcode, compare, key,element,elen)
	hashf_tbl *hashtable;
	unsigned hashcode;
	hash_cmpfp compare;
	hash_datum *key,*element;
	unsigned long elen;
{
	FILE *fp,*tmp;
	char name[MAXNAMLEN],buf[MAXELEMENTLEN];
	int rc,len;
	char write,done;
	sprintf(name,"%s/%X",hashtable->dirname,hashcode % hashtable->size);

	fp=fopen(name,"r");
	if(!fp)
		return hashf_Insert(hashtable,hashcode,compare,key,element,elen);
	tmp=tmpfile();
	if(!tmp){
		fclose(fp);
		return -1;
	}

	done=0;
	write=0;
	while(TRUE) {
		rc=fread(&len,1,4,fp);
		if(rc==0)
			break;	/* EOF */
		rc=fread(buf,len,1,fp);
		if(rc==0){
			fclose(fp);
			return -2;
		}
		if(!(*compare)(key,buf)){
			rc=fwrite(&len,4,1,tmp);
			if(rc==0){
				fclose(fp);
				fclose(tmp);
				return -3;
			}
			rc=fwrite(buf,len,1,tmp);
			if(rc==0){
				fclose(fp);
				fclose(tmp);
				return -4;
			}
		} else if(!write) {
			rc=fwrite(&elen,4,1,tmp);
			if(rc==0){
				fclose(fp);
				fclose(tmp);
				return -5;
			}
			rc=fwrite(element,elen,1,tmp);
			if(rc==0){
				fclose(fp);
				fclose(tmp);
				return -6;
			}
			write=1;
		}
	} 
	if(!write){
		rc=fwrite(&elen,4,1,tmp);
		if(rc==0){
			fclose(fp);
			fclose(tmp);
			return -7;
		}
		rc=fwrite(element,elen,1,tmp);
		if(rc==0){
			fclose(fp);
			fclose(tmp);
			return -8;
		}
		write=1;
	}
	rewind(tmp);
	fclose(fp);
	fp=fopen(name,"w");
	rc=fgetc(tmp);
	while(rc!=EOF) {
		fputc(rc,fp);
		rc=fgetc(tmp);
	}

	fclose(fp);
	fclose(tmp);
	return 0;
}



/*
 * Locate and return the data entry associated with the given key.
 *
 * If the data entry is found, a pointer to it is returned.  Otherwise,
 * NULL is returned.
 */

hash_datum *
hashf_Lookup(hashtable, hashcode, compare, key, length)
	hashf_tbl *hashtable;
	unsigned hashcode;
	hash_cmpfp compare;
	hash_datum *key;
	int *length;
{
        FILE *fp;
        char name[MAXNAMLEN],buf[MAXELEMENTLEN];
        int len,rc;
		hash_datum *p;

        sprintf(name,"%s/%X",hashtable->dirname,hashcode % hashtable->size);
        fp=fopen(name,"r");
        if(!fp)
                return NULL;
        while(TRUE){
                rc=fread(&len,1,4,fp);
				if(rc==0){
					fclose(fp);
					return NULL;
				}
                rc=fread(buf,1,len,fp);
				if(rc==0){
					fclose(fp);
					return NULL;
				}
                if((*compare)(key,buf)){
						if(length)
							*length=len;
                        fclose(fp);
						p=(hash_datum*)malloc(len);
						if (!p) {
							report(LOG_ERR, "malloc: %s", get_errmsg());
							report(LOG_ERR, "exiting");
							exit(1);
						}
						bcopy(buf,p,len);
                        return p;
                }
        }
}



/*
 * Return the next available entry in the hashtable for a linear search
 */

hash_datum *
hashf_NextEntry(hashtable,length)
	hashf_tbl *hashtable;
	int *length;
{
	unsigned long len;
	char name[MAXNAMLEN];
    int rc;
	hash_datum *p;
	struct dirent *direntry;

	if(hashtable->next)
		rc=fread(&len,4,1,hashtable->next);
	if(!hashtable->next || rc==0){
		do {
			direntry=readdir(hashtable->dirp);
			if(direntry==NULL || *direntry->d_name==0)
				return NULL;
		}while(direntry->d_name[0]=='.');
		if(hashtable->next)
			fclose(hashtable->next);
        sprintf(name,"%s/%s",hashtable->dirname,direntry->d_name);
		hashtable->next=fopen(name,"r");
		rc=fread(&len,4,1,hashtable->next);
		if(rc==0)
			return NULL;
	}
	if(length)
		*length=len;
	p=(hash_datum*)malloc(len);
	if(p==NULL)
		return p;
	rc=fread(p,len,1,hashtable->next);
	if(rc==0)
		return NULL;
	return p;
}



/*
 * Return the first entry in a hash table for a linear search
 */

hash_datum *
hashf_FirstEntry(hashtable,length)
	hashf_tbl *hashtable;
	int *length;
{
	rewinddir(hashtable->dirp);
	hashtable->next=NULL;
	return hashf_NextEntry(hashtable,length);
}

/*
 * Local Variables:
 * tab-width: 4
 * c-indent-level: 4
 * c-argdecl-indent: 4
 * c-continued-statement-offset: 4
 * c-continued-brace-offset: -4
 * c-label-offset: -4
 * c-brace-offset: 0
 * End:
 */
