/*
+-+-+-+-+-+-+-+-+-
*/
/*
**				DICOM 93
**		     Electronic Radiology Laboratory
**		   Mallinckrodt Institute of Radiology
**		Washington University School of Medicine
**
** Module Name(s):	HF_Create, HF_Open, HF_Close, HF_AddHunk,
**			HF_AllocateRecord, HF_DeallocateRecord,
**			HF_ReadRecord, HF_WriteRecord, HF_WriteCurrentHunk,
**			HF_ReadFileHeader, HF_WriteFileHeader,
**			HF_ReadCurrentHunk, HF_FindFreeNode, HF_Dumper
**			HF_AllocateStaticRecord, HF_DeallocateStaticRecord,
**			HF_ReadStaticRecord, HF_WriteStaticRecord
**			HF_InitAddresses
**
** Author, Date:	David E. Beecher, 5-May-93
**
** Intent:		These routines provide an orderly access mechanism
**			for managing a "hunk" file.  This is the underlying
**			mechanism used for the implementation of the DICOM
**			database system.  It provides orderly access for
**			allocating and deallocating records, reading and writing
**			those records, initializing a new file, and other
**			maintenence features.  The locking mechanisms are one
**			level up in the database code.
**
** Last Update:		$Author: beech $, $Date: 1993-05-12 22:11:05 $
** Source File:		$Source: /home/smm/ctn/ctn/facilities/hunks/hunk_file.c,v $
** Revision:		$Revision: 1.1 $
** Status:		$State: Exp $
*/

static char rcsid[] = "$Revision: 1.1 $ $RCSfile: hunk_file.c,v $";

#include <stdio.h>
#include <fcntl.h>
#include "hunk_file.h"

static int
    Opened = 0;			/* has the database been opened, initially no */

static HunkFileHeader
    GS_hfh;			/* the hunk file header	 */
static Hunk
    GS_curhunk;			/* the hunk currently in memory */
static int
    GS_hunkfd;			/* the hunk file file descriptor */
static void
   *GS_hunkstorage = NULL;	/* the storage for the hunk data */
static void
   *GS_addstorage = NULL;	/* the storage for the address data */

/* HF_Create
**
** Purpose:
**	This routine creates and initializes a new hunk file most probably
**	for implementing a new database.
**
** Parameter Dictionary:
**	char *fname:
**		a null terminated string indicating the file name to use.
**
**	int num_hunks:
**		the number of "hunks" to initially allocate, the minimum
**		number for this is one.
**
**	int hunk_record_length:
**		the length of fixed length records in this hunk file.
**
**	int num_recs_per_hunk:
**		the number of records per hunk in this hunk file.
**
** Return Values:
**
**	HF_NORMAL: 	The creation command succeeded
**	HF_OPENERROR: 	The hunk_file is already opened
**	HF_BADPARMS:	The parameter for hunk file creation are invalid
**	HF_CREATERROR:	An error in the open to create the file on disk
**	HF_NOMEMORY:	Can't allocate enough memory for hunk storage
**
** Algorithm:
**	A hunk file header starts every hunk file.  This contains information
**	including the length of hunks, the number currently allocated, and
**	an identification string to identify this file as a hunk file.
**
**	Each hunk can simplistically be viewed as a buffer pool with some
**	header information for management.  There are num_recs_per_hunk records
**	per hunk, where each record is hunk_record_length bytes in length.  Each
**	buffer in the pool has a corresponding node which indicates whether or
**	not it is currently being used.  Each hunk also contains header
**	information which includes the hunk number, a dirty bit, a static
**	pointer for this hunk, the number of free nodes (or buffers), and the
**	number of records per hunk and the size of the records in bytes.
**	This description is given more detail in the function descriptions
**	that follow.
*/

CONDITION
HF_Create(char *fname, int num_hunks,
	  int hunk_record_length, int num_recs_per_hunk)
{

    int
        wb,			/* the  number of bytes written */
        addsize,		/* the size of the addresses	 */
        hunksize,		/* the size of the hunk in memory */
        i,
        j;			/* temp indicies		 */
    CONDITION
        retval;
    if (Opened == 1)
	return (HF_OPENERROR);

    if (num_hunks < 1 || hunk_record_length < 1 || num_recs_per_hunk < 1)
	return (HF_BADPARMS);

    if ((GS_hunkfd = open(fname, O_RDWR | O_CREAT, 0777)) < 0)
	return (HF_CREATERROR);

/*
 * Compute the storage needed for addresses...
 */
    addsize = sizeof(HunkBuf) * num_recs_per_hunk;
/*
 * Compute the storage needed for the hunk in memory...
 */
    hunksize = sizeof(HunkHeader);
    hunksize += num_recs_per_hunk;	/* for the node_list */
    hunksize += ((hunk_record_length + sizeof(HunkBufAdd)) * num_recs_per_hunk);

    if ((GS_hunkstorage = (void *) malloc(hunksize)) == (void *) NULL) {
	return (HF_NOMEMORY);
    }
    if ((GS_addstorage = (void *) malloc(addsize)) == (void *) NULL) {
	return (HF_NOMEMORY);
    }
    GS_hfh.hunks_allocated = num_hunks;
    GS_hfh.hunk_length = hunksize;
    GS_hfh.hunk_record_length = hunk_record_length;
    GS_hfh.num_recs_per_hunk = num_recs_per_hunk;
    strcpy(GS_hfh.hunk_file_id, HUNK_ID);

    HF_InitAddresses();

    Opened = 1;
    if ((retval = HF_WriteFileHeader()) != HF_NORMAL) {
	Opened = 0;
	close(GS_hunkfd);
	return (retval);
    }
    for (i = 0; i < num_hunks; i++) {
	GS_curhunk.hunk_head->hunk_number = i;
	GS_curhunk.hunk_head->hunk_dirty = 1;
	GS_curhunk.hunk_head->static_buf_node.hunk_number = HUNK_PTR_NULL;
	GS_curhunk.hunk_head->static_buf_node.node_number = HUNK_PTR_NULL;
	GS_curhunk.hunk_head->free_nodes = GS_hfh.num_recs_per_hunk;
	for (j = 0; j < GS_hfh.num_recs_per_hunk; j++) {
	    GS_curhunk.node_list[j] = (char) HUNK_FREE;
	    GS_curhunk.buf_pool[j].nextp->hunk_number = HUNK_PTR_NULL;
	    GS_curhunk.buf_pool[j].nextp->node_number = HUNK_PTR_NULL;
	}
	if ((retval = HF_WriteCurrentHunk()) != HF_NORMAL) {
	    Opened = 0;
	    close(GS_hunkfd);
	    return (retval);
	}
    }
    Opened = 0;
    close(GS_hunkfd);
    return (HF_NORMAL);
}

/* HF_InitAddresses
**
** Purpose:
**	This function initailizes the address mapping for any hunk.
**
** Parameter Dictionary:
**	No formal parameters passed
**
** Return Values:
**	None.
**
** Algorithm:
**	This call is used to hook up all the nasty little pointers needed
**	to deal with hunks which can be sized at run time.  It's a little
**	esoteric but pretty straightforward if you study this along with the
**	data structure definitions in hunk_file.h
*/
void
HF_InitAddresses()
{

    int i;
    char *addp,
       *hunkp;

    addp = (char *) GS_addstorage;
    hunkp = (char *) GS_hunkstorage;
    GS_curhunk.hunk_head = (HunkHeader *) hunkp;
    hunkp += sizeof(HunkHeader);
    GS_curhunk.node_list = hunkp;
    hunkp += GS_hfh.num_recs_per_hunk;
    GS_curhunk.buf_pool = (HunkBuf *) addp;

    for (i = 0; i < GS_hfh.num_recs_per_hunk; i++) {
	GS_curhunk.buf_pool[i].buf = hunkp;
	hunkp += GS_hfh.hunk_record_length;
	GS_curhunk.buf_pool[i].nextp = (HunkBufAdd *) hunkp;
	hunkp += sizeof(HunkBufAdd);
    }

}
/* HF_Open
**
** Purpose:
**	This function opens a previously created hunk file.
**
** Parameter Dictionary:
**	char *fname:
**		a null terminated string indicating the file name to use.
**
** Return Values:
**
**	HF_NORMAL: 	The open command succeeded
**	HF_OPENERROR: 	The hunk_file is already opened or can't be opened
**	HF_NOMEMORY:	Can't allocate enough memory for hunk storage
**	HF_READERROR:	Can't read the hunk from disk
**	HF_BADMAGIC:	Bad verification string in hunk header
**	HF_SEEKERROR:	Can't seek to a particular location
**	HF_BADHUNK:	Bad parameters in hunk header
**
** Algorithm:
**	This function attempts to open the specified hunk file.  It performs
**	some simple sanity checking to make sure that at the minimum it has the
**	proper hunk identification string in the header and that the number of
**	hunks allocated and the hunk size is a reasonable number.
*/

CONDITION
HF_Open(char *fname)
{

    int
        addsize,
        hunksize,
        hs,
        seek_pos,
        rb;

    if (Opened == 1)
	return (HF_OPENERROR);

    if ((GS_hunkfd = open(fname, O_RDWR)) < 0)
	return (HF_OPENERROR);

    if ((read(GS_hunkfd, (char *) &GS_hfh, sizeof(HunkFileHeader))) !=
	sizeof(HunkFileHeader)) {
	close(GS_hunkfd);
	return (HF_READERROR);
    }
    if (strcmp(GS_hfh.hunk_file_id, HUNK_ID) != 0) {
	close(GS_hunkfd);
	return (HF_BADMAGIC);
    }
    hs = GS_hfh.hunk_length;
    if ((GS_hunkstorage == NULL) || (GS_addstorage == NULL)) {
	/*
	 * Compute the storage needed for addresses...
	 */
	addsize = sizeof(HunkBuf) * GS_hfh.num_recs_per_hunk;
	/*
	 * Compute the storage needed for the hunk in memory...
	 */
	hunksize = sizeof(HunkHeader);
	hunksize += GS_hfh.num_recs_per_hunk;
	hunksize += ((GS_hfh.hunk_record_length + sizeof(HunkBufAdd)) *
		     GS_hfh.num_recs_per_hunk);

	if ((GS_hunkstorage = (void *) malloc(hunksize)) == (void *) NULL) {
	    return (HF_NOMEMORY);
	}
	if ((GS_addstorage = (void *) malloc(addsize)) == (void *) NULL) {
	    return (HF_NOMEMORY);
	}
	HF_InitAddresses();
    }
    seek_pos = sizeof(HunkFileHeader);
    if ((lseek(GS_hunkfd, (off_t) seek_pos, SEEK_SET)) < 0) {
	return (HF_SEEKERROR);
    }
    rb = read(GS_hunkfd, (char *) GS_hunkstorage, GS_hfh.hunk_length);
    if (rb != GS_hfh.hunk_length) {
	close(GS_hunkfd);
	return (HF_READERROR);
    }
    if ((GS_hfh.hunks_allocated < 1) || (GS_hfh.hunk_length <= 0)) {
	close(GS_hunkfd);
	return (HF_BADHUNK);
    }
    GS_curhunk.hunk_head->hunk_dirty = 0;
    Opened = 1;
    return (HF_NORMAL);
}




/* HF_Close
**
** Purpose:
**	This function closes a previously opened hunk file.
**
** Parameter Dictionary:
**
**	char *fname:
**		a null terminated string indicating the file name to close.
**
** Return Values:
**
**	HF_NORMAL: 	The close command succeeded
**	HF_OPENERROR: 	The hunk_file is already opened or can't be opened
**	HF_WRITERROR:	Can't write the hunk to disk
**	HF_CLOSERROR:	Can't close the file descriptor
**
** Algorithm:
**	This function attempts to close the specified hunk file.  It attempts
**	to write the file header and the current open hunk if need be.
*/

CONDITION
HF_Close(char *fname)
{
    CONDITION
        retval;

    if (Opened == 0)
	return (HF_OPENERROR);

    if ((retval = HF_WriteCurrentHunk()) != HF_NORMAL) {
	return (retval);
    }
    if (HF_WriteFileHeader() != HF_NORMAL) {
	return (HF_WRITERROR);
    }
    if (GS_hunkstorage != (void *) NULL) {
	free(GS_hunkstorage);
	GS_hunkstorage = (void *) NULL;
    }
    if (GS_addstorage != (void *) NULL) {
	free(GS_addstorage);
	GS_addstorage = (void *) NULL;
    }
    if (close(GS_hunkfd) < 0)
	return (HF_CLOSERROR);

    Opened = 0;

    return (HF_NORMAL);
}




/* HF_AddHunk
**
** Purpose:
**	This function appends an additional hunk to an existing hunk file.
**
** Parameter Dictionary:
**	None
**
** Return Values:
**
**	HF_NORMAL: 	The add command succeeded
**	HF_OPENERROR: 	The hunk_file is already opened or can't be opened
**	HF_SEEKERROR:	Can't seek to a particular location
**
** Algorithm:
**	This function is called internally by the HF functions when they
**	decide they need to have additional hunks...for instance when a record
**	cannot be allocated anywhere else, it must be placed in a new hunk.
*/


CONDITION
HF_AddHunk()
{

    int
        i,
        j,
        wb,
        seek_pos;
    CONDITION
        retval;

    if (Opened == 0)
	return (HF_OPENERROR);

    if ((retval = HF_WriteCurrentHunk()) != HF_NORMAL)
	return (retval);
    if ((lseek(GS_hunkfd, (off_t) 0, SEEK_END)) < 0) {
	return (HF_SEEKERROR);
    }
    GS_curhunk.hunk_head->hunk_number = GS_hfh.hunks_allocated;
    GS_hfh.hunks_allocated++;
    GS_curhunk.hunk_head->hunk_dirty = 1;
    GS_curhunk.hunk_head->static_buf_node.hunk_number = HUNK_PTR_NULL;
    GS_curhunk.hunk_head->static_buf_node.node_number = HUNK_PTR_NULL;
    GS_curhunk.hunk_head->free_nodes = GS_hfh.num_recs_per_hunk;
    for (j = 0; j < GS_hfh.num_recs_per_hunk; j++) {
	GS_curhunk.node_list[j] = HUNK_FREE;
	GS_curhunk.buf_pool[j].nextp->hunk_number = HUNK_PTR_NULL;
	GS_curhunk.buf_pool[j].nextp->node_number = HUNK_PTR_NULL;
    }
    if ((retval = HF_WriteCurrentHunk()) != HF_NORMAL)
	return (retval);
    if ((retval = HF_WriteFileHeader()) != HF_NORMAL) {
	return (retval);
    }
    return (HF_NORMAL);
}




/* HF_WriteCurrentHunk
**
** Purpose:
**	This function writes the current hunk out to the disk file.
**
** Parameter Dictionary:
**	None
**
** Return Values:
**
**	HF_NORMAL: 	The write current hunk command succeeded
**	HF_SEEKERROR:	Can't seek to a particular location
**	HF_WRITERROR:	Can't write the requested hunk
**
** Algorithm:
**	The hunk is only written to disk if it is dirty, that is, some
**	piece of data on the memory image of the hunk changed due to buffers
**	being allocated and deallocated, or written.
*/

CONDITION
HF_WriteCurrentHunk(void)
{

    int
        wb,
        seek_pos;

    if (Opened == 0)
	return (HF_OPENERROR);

    if (GS_curhunk.hunk_head->hunk_dirty == 1) {	/* this page is
							 * dirty...write it out */
	seek_pos = sizeof(HunkFileHeader) +
	    (GS_curhunk.hunk_head->hunk_number * GS_hfh.hunk_length);
	if ((lseek(GS_hunkfd, (off_t) seek_pos, SEEK_SET)) < 0) {
	    return (HF_SEEKERROR);
	}
	GS_curhunk.hunk_head->hunk_dirty = 0;
	wb = write(GS_hunkfd, (char *) GS_hunkstorage, GS_hfh.hunk_length);
	if (wb != GS_hfh.hunk_length) {
	    GS_curhunk.hunk_head->hunk_dirty = 1;
	    return (HF_WRITERROR);
	}
    }
    return (HF_NORMAL);
}



/* HF_ReadFileHeader
**
** Purpose:
**	A convienience routine used to read the Hunk file header from the disk
**
** Parameter Dictionary:
**	None
**
** Return Values:
**
**	HF_NORMAL: 	The read file header command succeeded
**	HF_SEEKERROR:	Can't seek to a particular location
**	HF_READERROR:	Can't read the file header
**
** Algorithm:
**	N/A
*/

CONDITION
HF_ReadFileHeader()
{

    int
        rb,
        seek_pos;

    if (Opened == 0)
	return (HF_OPENERROR);

    seek_pos = 0;
    if ((lseek(GS_hunkfd, (off_t) seek_pos, SEEK_SET)) < 0) {
	return (HF_SEEKERROR);
    }
    if ((read(GS_hunkfd, (char *) &GS_hfh, sizeof(HunkFileHeader))) !=
	sizeof(HunkFileHeader)) {
	return (HF_READERROR);
    }
    return (HF_NORMAL);

}
/* HF_WriteFileHeader
**
** Purpose:
**	A convienience routine used to write the Hunk file header to the disk
**
** Parameter Dictionary:
**	None
**
** Return Values:
**
**	HF_NORMAL: 	The read file header command succeeded
**	HF_SEEKERROR:	Can't seek to a particular location
**	HF_WRITERROR:	Can't write the file header
**
** Algorithm:
**	N/A
*/
CONDITION
HF_WriteFileHeader()
{

    int
        rb,
        seek_pos;

    if (Opened == 0)
	return (HF_OPENERROR);

    seek_pos = 0;
    if ((lseek(GS_hunkfd, (off_t) seek_pos, SEEK_SET)) < 0) {
	return (HF_SEEKERROR);
    }
    if ((write(GS_hunkfd, (char *) &GS_hfh, sizeof(HunkFileHeader))) !=
	sizeof(HunkFileHeader)) {
	return (HF_WRITERROR);
    }
    return (HF_NORMAL);

}
/* HF_ReadCurrentHunk
**
** Purpose:
**	A routine used to read the requested hunk into the hunk memory space
**
** Parameter Dictionary:
**	int hunk_num:
*		the number of the hunk you want.
**
** Return Values:
**
**	HF_NORMAL: 	The read current hunk succeeded
**	HF_OPENERROR: 	The hunk_file is already opened
**	HF_BADHUNKNUM:	Invalid hunk number requested
**	HF_SEEKERROR:	Couldn't seek to requested location
**	HF_READERROR:	Couldn't read the hunk
**
** Algorithm:
**	The memory residient hunk is written if need be, then the requested
**	hunk is read from the disk given that it exists.
*/
CONDITION
HF_ReadCurrentHunk(int hunk_num)
{

    int
        rb,
        seek_pos;
    CONDITION
        retval;

    if (Opened == 0)
	return (HF_OPENERROR);

    if (hunk_num >= GS_hfh.hunks_allocated)
	return (HF_BADHUNKNUM);

    if (GS_curhunk.hunk_head->hunk_number == hunk_num)
	return (HF_NORMAL);

    if (GS_curhunk.hunk_head->hunk_dirty == 1)
	if ((retval = HF_WriteCurrentHunk()) != HF_NORMAL)
	    return (retval);

    seek_pos = sizeof(HunkFileHeader) + (hunk_num * GS_hfh.hunk_length);
    if ((lseek(GS_hunkfd, (off_t) seek_pos, SEEK_SET)) < 0) {
	return (HF_SEEKERROR);
    }
    rb = read(GS_hunkfd, (char *) GS_hunkstorage, GS_hfh.hunk_length);
    if (rb != GS_hfh.hunk_length) {
	return (HF_READERROR);
    }
    GS_curhunk.hunk_head->hunk_dirty = 0;
    return (HF_NORMAL);
}




/* HF_AllocateStaticRecord
**
** Purpose:
**	A routine to allocate the static buffer associated with a hunk
**
** Parameter Dictionary:
**	int hunk_number:
**		the number of the hunk you want to use for the static alloc
**	int size:
**		the size of this static allocation.
**
** Return Values:
**
**	HF_NORMAL: 	The allocate command succeeded
**	HF_OPENERROR: 	The hunk_file is already opened
**	HF_STATICINUSE:	The static buffer is already in use
**
** Algorithm:
**	Each hunk  can have a static buffer allocation associated with it so
**	that when the file is opened the first time, you have a "handle" to
**	go out and see what is where...this is really important if you are
**	implementing say a tree structure and need to find the root right off
**	the bat.
*/
CONDITION
HF_AllocateStaticRecord(int hunk_number, int size)
{

    HunkBufAdd add;
    CONDITION retval;

    if (Opened == 0)
	return (HF_OPENERROR);

    if ((retval = HF_AllocateRecord(size, &add)) != HF_NORMAL) {
	return (retval);
    }
    if ((retval = HF_ReadCurrentHunk(hunk_number)) != HF_NORMAL)
	return (retval);

    if (GS_curhunk.hunk_head->static_buf_node.hunk_number != HUNK_PTR_NULL &&
	GS_curhunk.hunk_head->static_buf_node.node_number != HUNK_PTR_NULL) {
	return (HF_STATICINUSE);
    }
    GS_curhunk.hunk_head->static_buf_node.hunk_number = add.hunk_number;
    GS_curhunk.hunk_head->static_buf_node.node_number = add.node_number;
    GS_curhunk.hunk_head->hunk_dirty = 1;

    return (HF_NORMAL);

}

/* HF_AllocateRecord
**
** Purpose:
**	The general fixed length record allocator.
**
** Parameter Dictionary:
**	int size:
**		the size of this record allocation.
**	HunkBufAdd *address
**		Gets the address of the record just allocated.
**
** Return Values:
**
**	HF_NORMAL: 	The allocate command succeeded
**	HF_OPENERROR: 	The hunk_file is already opened
**	HF_RECTOOBIG:	The record size requested is too big
**
** Algorithm:
**	This is the guts of the hunk system.  It allocates whatever size
**	record you want by linking together the proper number of buffers
**	from the buffer pool of a particular hunk.  There is waste here, but
**	it is simple to follow and not horribly slow.  One restriction is that
**	buffers cannot be larger than the size of a hunk.
*/
CONDITION
HF_AllocateRecord(int size, HunkBufAdd *address)
{

    int
        num_bufs,
        i,
        free_index,
        done,
        prev_node,
        prev_hunk;
    CONDITION
        retval;

    if (Opened == 0)
	return (HF_OPENERROR);

    if (size > (GS_hfh.hunk_record_length *
		GS_hfh.num_recs_per_hunk))
	return (HF_RECTOOBIG);

    num_bufs = size / GS_hfh.hunk_record_length;
    if ((size % GS_hfh.hunk_record_length) != 0)
	num_bufs++;

    if ((retval = HF_WriteCurrentHunk()) != HF_NORMAL)
	return (retval);
    done = 0;
    for (i = 0; i < GS_hfh.hunks_allocated; i++) {
	if ((retval = HF_ReadCurrentHunk(i)) != HF_NORMAL)
	    return (retval);

	if (num_bufs <= GS_curhunk.hunk_head->free_nodes) {	/* we can service it
								 * here... */
	    free_index = HF_FindFreeNode();
	    GS_curhunk.buf_pool[free_index].nextp->hunk_number = -1;
	    GS_curhunk.buf_pool[free_index].nextp->node_number = -1;
	    address->hunk_number = i;
	    address->node_number = free_index;
	    num_bufs--;
	    prev_node = address->node_number;
	    prev_hunk = address->hunk_number;
	    while (num_bufs > 0) {
		free_index = HF_FindFreeNode();
		GS_curhunk.buf_pool[prev_node].nextp->hunk_number = prev_hunk;
		GS_curhunk.buf_pool[prev_node].nextp->node_number = free_index;
		GS_curhunk.buf_pool[free_index].nextp->hunk_number = -1;
		GS_curhunk.buf_pool[free_index].nextp->node_number = -1;
		prev_node = free_index;
		num_bufs--;
	    }
	    done = 1;
	    break;
	}
    }
    if (done == 0) {		/* Need to allocate a new hunk */
	if ((retval = HF_AddHunk()) != HF_NORMAL)
	    return (retval);
	else {
	    if ((retval = HF_ReadCurrentHunk(GS_hfh.hunks_allocated - 1)) !=
		HF_NORMAL)
		return (retval);
	    else {
		free_index = HF_FindFreeNode();
		GS_curhunk.buf_pool[free_index].nextp->hunk_number = -1;
		GS_curhunk.buf_pool[free_index].nextp->node_number = -1;
		address->hunk_number = i;
		address->node_number = free_index;
		num_bufs--;
		prev_node = address->node_number;
		prev_hunk = address->hunk_number;
		while (num_bufs > 0) {
		    free_index = HF_FindFreeNode();
		    GS_curhunk.buf_pool[prev_node].nextp->hunk_number = prev_hunk;
		    GS_curhunk.buf_pool[prev_node].nextp->node_number = free_index;
		    GS_curhunk.buf_pool[free_index].nextp->hunk_number = -1;
		    GS_curhunk.buf_pool[free_index].nextp->node_number = -1;
		    prev_node = free_index;
		    num_bufs--;
		}
	    }
	}
    }
    return (HF_NORMAL);
}

/* HF_FindFreeNode
**
** Purpose:
**	A simple routine to find a free node in the buffer pool
**
** Parameter Dictionary:
**	None.
**
** Return Values:
**	A free node index.
**
** Algorithm:
**	This routine is never called unless a free node exists which is why
**	it doesn't have an error return.
**	And, yes, it is a linear search, again we could do more here but there
**	is the issue of time, and these things tend to be small.
*/
int
HF_FindFreeNode()
{

    int i;

    for (i = 0; i < GS_hfh.num_recs_per_hunk; i++) {
	if (GS_curhunk.node_list[i] == HUNK_FREE) {
	    GS_curhunk.node_list[i] = HUNK_USED;
	    GS_curhunk.hunk_head->free_nodes--;
	    GS_curhunk.hunk_head->hunk_dirty = 1;
	    return (i);
	}
    }
}
/* HF_DeallocateRecord
**
** Purpose:
**	A routine to deallocate a previously allocated record.
**
** Parameter Dictionary:
**	HunkBufAdd *address:
**		The address of the buffer to deallocate.
**
** Return Values:
**
**	HF_NORMAL: 	The deallocate command succeeded
**	HF_OPENERROR: 	The hunk_file is already opened
**
** Algorithm:
**	This routine simply frees the allocated buffer and any buffers
**	previously linked to it.
*/
CONDITION
HF_DeallocateRecord(HunkBufAdd *address)
{

    int
        cur_node;
    CONDITION
        retval;
    if (Opened == 0)
	return (HF_OPENERROR);

    if ((retval = HF_ReadCurrentHunk(address->hunk_number)) != HF_NORMAL)
	return (retval);

    cur_node = address->node_number;
    do {
	GS_curhunk.node_list[cur_node] = HUNK_FREE;
	(GS_curhunk.hunk_head->free_nodes)++;
	cur_node = GS_curhunk.buf_pool[cur_node].nextp->node_number;
    } while (cur_node != -1);
    GS_curhunk.hunk_head->hunk_dirty = 1;
    return (HF_NORMAL);

}
/* HF_DeallocateStaticRecord
**
** Purpose:
**	A routine to deallocate a previously allocated static record.
**
** Parameter Dictionary:
**	int hunk_number
**		The hunk_number of the statically allocated buffer.
**
** Return Values:
**
**	HF_NORMAL: 	The deallocate static command succeeded
**	HF_OPENERROR: 	The hunk_file is already opened
**
** Algorithm:
**	This routine simply frees the staticly allocated buffer
**	associated with a hunk.  Static buffers have there addresses stored
**	with the database so they don't need to be passed.
*/
CONDITION
HF_DeallocateStaticRecord(int hunk_number)
{


    HunkBufAdd add;
    CONDITION retval;

    if (Opened == 0)
	return (HF_OPENERROR);

    if ((retval = HF_ReadCurrentHunk(hunk_number)) != HF_NORMAL)
	return (retval);

    add.hunk_number = GS_curhunk.hunk_head->static_buf_node.hunk_number;
    add.node_number = GS_curhunk.hunk_head->static_buf_node.node_number;
    GS_curhunk.hunk_head->static_buf_node.hunk_number = HUNK_PTR_NULL;
    GS_curhunk.hunk_head->static_buf_node.node_number = HUNK_PTR_NULL;
    GS_curhunk.hunk_head->hunk_dirty = 1;
    if ((retval = HF_DeallocateRecord(&add)) != HF_NORMAL)
	return (retval);

    return (HF_NORMAL);
}
/* HF_Dumper
**
** Purpose:
**	A test routine to dump the data records and see links
**
** Parameter Dictionary:
**
** Return Values:
**	None.
**
** Algorithm:
**	No real algorithm.
*/
void
HF_Dumper(void)
{

    int i,
        j,
        k,
        node;

    printf("Allocated: %d, Length: %d, Rec Len: %d Recs/hunk: %d ID: %s\n\n",
	   GS_hfh.hunks_allocated, GS_hfh.hunk_length,
	   GS_hfh.hunk_record_length, GS_hfh.num_recs_per_hunk,
	   GS_hfh.hunk_file_id);
    for (i = 0; i < GS_hfh.hunks_allocated; i++) {

	HF_ReadCurrentHunk(i);
	printf("Hunk number: %d\n", GS_curhunk.hunk_head->hunk_number);
	printf("Hunk dirty: %d\n", GS_curhunk.hunk_head->hunk_dirty);
	printf("Hunk static_buf_node: %d %d\n",
	       GS_curhunk.hunk_head->static_buf_node.hunk_number,
	       GS_curhunk.hunk_head->static_buf_node.node_number);
	printf("Hunk free_nodes: %d\n", GS_curhunk.hunk_head->free_nodes);
	if (GS_curhunk.hunk_head->free_nodes < 100) {
	    for (j = 0; j < 100; j++) {
		if (GS_curhunk.node_list[j] == HUNK_USED) {
		    printf("\tBuffer %3d [Link: %3d] ",
			   j, GS_curhunk.buf_pool[j].nextp->node_number);
		    for (k = 0; k < 15; k++)
			printf("%c", GS_curhunk.buf_pool[j].buf[k]);
		    printf(" ... ");
		    for (k = GS_hfh.hunk_record_length - 15;
			 k < GS_hfh.hunk_record_length; k++)
			printf("%c", GS_curhunk.buf_pool[j].buf[k]);
		    printf("\n");
		}
	    }
	}
	printf("\n\n");
    }
}
/* HF_ReadStaticRecord
**
** Purpose:
**	A routine to read a previously allocated static record.
**
** Parameter Dictionary:
**	int hunk_number
**		The hunk_number of the statically allocated record.
**	int size:
**		The size of the user buffer.
**	void *buf:
**		The user buffer
**
** Return Values:
**
**	HF_NORMAL: 	The read static record command succeeded
**	HF_OPENERROR: 	The hunk_file is already opened
**
** Algorithm:
**	Simply reads the data from the static buffer associated with a
**	particular hunk into the user buffer pointed to by buf.  It will
**	read size bytes regardless of the size of the static buffer which
**	is not retained by the system.
*/
CONDITION
HF_ReadStaticRecord(int hunk_number, int size, void *buf)
{
    HunkBufAdd
        add;
    CONDITION retval;
    if (Opened == 0)
	return (HF_OPENERROR);

    if ((retval = HF_ReadCurrentHunk(hunk_number)) != HF_NORMAL)
	return (retval);
    add.hunk_number = GS_curhunk.hunk_head->static_buf_node.hunk_number;
    add.node_number = GS_curhunk.hunk_head->static_buf_node.node_number;

    return (HF_ReadRecord(&add, size, buf));

}
/* HF_ReadRecord
**
** Purpose:
**	A general purpose routine to read a previously allocated record.
**
** Parameter Dictionary:
**	HunkBufAdd *add:
**		The address of the allocated record.
**	int size:
**		The size of the user buffer.
**	void *buf:
**		The user buffer
**
** Return Values:
**
**	HF_NORMAL: 	The read record command succeeded
**	HF_OPENERROR: 	The hunk_file is already opened
**	HF_BADNODENUM:	Bad node number in address
**
** Algorithm:
**	Simply reads the data from the buffer associated with the address
**	passed into the user buffer pointed to by buf.  It will
**	read size bytes regardless of the size of the buffer which
**	is not retained by the system.
*/
CONDITION
HF_ReadRecord(HunkBufAdd *add, int bufsize, void *buffer)
{
    int
        left_to_transfer,
        current_node;
    char
       *pbuffer;

    CONDITION retval;

    if (Opened == 0)
	return (HF_OPENERROR);

    if ((retval = HF_ReadCurrentHunk(add->hunk_number)) != HF_NORMAL)
	return (retval);

    if (add->node_number < 0)
	return (HF_BADNODENUM);

    if (bufsize <= GS_hfh.hunk_record_length) {
	memcpy((char *) buffer,
	       (char *) GS_curhunk.buf_pool[add->node_number].buf,
	       bufsize);
    } else {
	left_to_transfer = bufsize;
	current_node = add->node_number;
	pbuffer = (char *) buffer;
	while (left_to_transfer > GS_hfh.hunk_record_length) {
	    memcpy((char *) pbuffer,
		   (char *) GS_curhunk.buf_pool[current_node].buf,
		   GS_hfh.hunk_record_length);
	    left_to_transfer -= GS_hfh.hunk_record_length;
	    pbuffer += GS_hfh.hunk_record_length;
	    current_node = GS_curhunk.buf_pool[current_node].nextp->node_number;
	    if (current_node < 0)
		return (HF_BADNODENUM);
	}
	memcpy((char *) pbuffer,
	       (char *) GS_curhunk.buf_pool[current_node].buf,
	       left_to_transfer);
    }
    return (HF_NORMAL);
}

/* HF_WriteStaticRecord
**
** Purpose:
**	A general purpose routine to write a previously allocated static record.
**
** Parameter Dictionary:
**	int hunk_number
**		The hunk number of the statically allocated record.
**	int size:
**		The size of the user buffer.
**	void *buf:
**		The user buffer
**
** Return Values:
**
**	HF_NORMAL: 	The write static command succeeded
**	HF_OPENERROR: 	The hunk_file is already opened
**
** Algorithm:
**	Simply writes the data to static hunk buffers specified  by the hunk
**	number passed.  It will write size bytes from the users buffer
**	regardless of the size of the buffer which is not retained by the
**	system.
*/
CONDITION
HF_WriteStaticRecord(int hunk_number, int size, void *buf)
{

    HunkBufAdd
        add;
    CONDITION
        retval;
    if (Opened == 0)
	return (HF_OPENERROR);

    if ((retval = HF_ReadCurrentHunk(hunk_number)) != HF_NORMAL)
	return (retval);
    add.hunk_number = GS_curhunk.hunk_head->static_buf_node.hunk_number;
    add.node_number = GS_curhunk.hunk_head->static_buf_node.node_number;

    return (HF_WriteRecord(&add, size, buf));
}

/* HF_WriteRecord
**
** Purpose:
**	A general purpose routine to write a previously allocated record.
**
** Parameter Dictionary:
**	HunkBufAdd *add:
**		The address of the allocated record.
**	int size:
**		The size of the user buffer.
**	void *buf:
**		The user buffer
**
** Return Values:
**
**	HF_NORMAL: 	The creation command succeeded
**	HF_OPENERROR: 	The hunk_file is already opened
**	HF_BADNODENUM:	Address contains a bad node number
**
** Algorithm:
**	Simply writes the data to hunk buffers specified  by the address
**	passed.  It will write size bytes from the users buffer regardless
**	regardless of the size of the buffer which is not retained by the
**	system.
*/
CONDITION
HF_WriteRecord(HunkBufAdd *add, int bufsize, void *buffer)
{

    int
        left_to_transfer,
        current_node;
    char
       *pbuffer;
    CONDITION retval;

    if (Opened == 0)
	return (HF_NORMAL);

    if ((retval = HF_ReadCurrentHunk(add->hunk_number)) != HF_NORMAL)
	return (retval);

    if (add->node_number < 0)
	return (HF_BADNODENUM);

    if (bufsize <= GS_hfh.hunk_record_length) {
	memcpy((char *) GS_curhunk.buf_pool[add->node_number].buf,
	       (char *) buffer,
	       bufsize);
    } else {
	left_to_transfer = bufsize;
	current_node = add->node_number;
	pbuffer = (char *) buffer;
	while (left_to_transfer > GS_hfh.hunk_record_length) {
	    memcpy((char *) GS_curhunk.buf_pool[current_node].buf,
		   pbuffer,
		   GS_hfh.hunk_record_length);
	    left_to_transfer -= GS_hfh.hunk_record_length;
	    pbuffer += GS_hfh.hunk_record_length;
	    current_node = GS_curhunk.buf_pool[current_node].nextp->node_number;
	    if (current_node < 0)
		return (HF_BADNODENUM);
	}
	memcpy((char *) GS_curhunk.buf_pool[current_node].buf,
	       pbuffer,
	       left_to_transfer);
    }
    GS_curhunk.hunk_head->hunk_dirty = 1;
    if ((retval = HF_WriteCurrentHunk()) != HF_NORMAL)
	return (retval);
    return (HF_NORMAL);
}
