// ------------------------------- //
// -------- Start of File -------- //
// ------------------------------- //
// ----------------------------------------------------------- // 
// C++ Source Code File Name: vbdfile.cpp 
// Compiler Used: MSVC40, DJGPP 2.7.2.1, GCC 2.7.2.1, HP CPP 10.24
// Produced By: Doug Gaer  
// File Creation Date: 02/04/1997 
// Date Last Modified: 11/05/1998
// Copyright (c) 1997 Douglas M. Gaer
// ----------------------------------------------------------- // 
// ------------- Program Description and Details ------------- // 
// ----------------------------------------------------------- // 
/*
The VBD C++ classes are copyright (c) 1997, by Douglas M. Gaer.
All those who put this code or its derivatives in a commercial
product MUST mention this copyright in their documentation for
users of the products in which this code or its derivative
classes are used. Otherwise, you have the freedom to redistribute
verbatim copies of this source code, adapt it to your specific
needs, or improve the code and release your improvements to the
public provided that the modified files carry prominent notices
stating that you changed the files and the date of any change.

THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
THE ENTIRE RISK OF THE QUALITY AND PERFORMANCE OF THIS SOFTWARE
IS WITH YOU. SHOULD ANY ELEMENT OF THIS SOFTWARE PROVE DEFECTIVE,
YOU WILL ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR
CORRECTION.

The VBD file manager class is responsible for handling all
low-level file operations through the use of VBDFile objects
or by inheriting the VBDFile class. Low-level file operations
refer to functions such as: Create(), Open(), Read(), Write(),
Alloc(), Delete(), and Close(). These functions contain all the
routines needed to create and maintain VBD files in accordance
with the VBD File Format. VBDFile objects are reference counted
and must be created dynamically due to the way reference
counting is implemented. 
*/
// ----------------------------------------------------------- // 
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include "vbdfile.hpp"

// Define the __DOS_INCLUDES__ macro to use DOS path separators
#ifdef __DOS_INCLUDES__
#include <sys\types.h>
#include <sys\stat.h>
#else
#include <sys/types.h>
#include <sys/stat.h>
#endif

// Init all the static data members
static char ClosedFileName[MaxNameLength] = "closed.vbd";

// Current VBD file manager version number.
// 07/22/1998 changed to version 1025 from version 1024.
// 08/31/1998 changed to version 1027 from version 1025.
__LWORD__ VBDFile::VBDVersion = 1027;

// 08/31/1998 added revision letter 'A' to signature. Revision 'A'
// adds a 32-bit checksum routines, not included in previous versions.
__SBYTE__ VBDFile::VBDSignature[8] = {'V', 'B', 'D', 'F', 'I', 'L', 'E', 'A'};

VBDFile::VBDFile()
// Creates a VBD file object, with refcount of one.
{
  strcpy(FileName, ClosedFileName); // Set the initial file name
  fp = 0;
  Status = 0x01; // good, read-only, and closed
}

VBDFile::~VBDFile()
{
  Close();
}

int VBDFile::Create(const char *FName, FAU StaticSize)
// Creates and opens a new file named FName, truncating it
// if it already exists. The area at the front of the file
// of length StaticSize + sizeof(FileHeader) is reserved.
{
  // First, close the current file if open.
  Close();

  Status = 0x05; // Set read/write bit and good bit

  // Create, truncate if already exists
  fp = ::fopen(FName, "w+b");

  if(fp == 0) {
#ifdef CPP_EXCEPTIONS
    throw CFileCreationError();
#else
    Error->SignalException(EHandler::FileCreationError);
#endif
  }
  else {
    Status |= 2; // Set open bit
    strcpy(FileName, FName);
    Header.HeapStart = StaticSize + sizeof(FileHeader);
    LastOperation = READ; // So Write() works right the first time
    InitHdr();
  }

  // Returns 1 if the file was successfully created and opened.
  return IsOpen(); 
}

void VBDFile::InitHdr()
// Sets up the header area in the file. If there's
// statically allocated data beyond the header, write
// a 0 to the last byte of it, so that there will be no
// unexpected end of file errors. The rest of the static
// area stays uninitialized.
{
  Header.FreeSpace = 0;
  Header.EndOfFile = Header.HeapStart;
  Header.HighestVB = 0; // No blocks allocated in the file 
  memcpy(Header.Signature, VBDSignature, 8); // Copy signature
  Header.Version = VBDVersion;

  // Set the revision letter 
  char revision[8];
  memmove(revision, VBDSignature, 8);
  rev_letter = revision[7];

  // 03/13/1998: Write the VBD file header and flush the disk
  // buffers to maintain file integrity during multiple file
  // access.
  WriteHdr();
  
  if(Header.HeapStart > sizeof(FileHeader)) {
     __SBYTE__ zero_byte = 0;
     Write(&zero_byte, 1, Header.HeapStart-1);
  }
}

int VBDFile::Open(const char *FName, AccessMode Mode)
// Opens the FName file. File must exist and be a VBDFile type 
// or error occurs. This function will check the revsion letter
// when opening an existing file.
{
  char *mode_str;

  // First, close the current file if open.
  Close();
  
  if(Mode == READONLY) {
    mode_str = "rb";
    Status = 0x01; // set good bit, reset read/write bit
  }
  else {
    mode_str = "r+b";
    Status = 0x05; // Set good bit and read/write bit
  }

  fp = ::fopen(FName, mode_str);
  
  if(fp == 0) {
#ifdef CPP_EXCEPTIONS
    throw CFileOpenError();
#else
    Error->SignalException(EHandler::FileOpenError);
#endif
  }
  else {
    Status |= 2; // Set open bit
    strcpy(FileName, FName);
    LastOperation = WRITE; // So Read() works right the first time
    
    ReadHdr();
    // Test file type, checking the revision letter
    if(memcmp(Header.Signature, VBDSignature, 7)) { 
#ifdef CPP_EXCEPTIONS
       throw CWrongFileType();
#else
       Error->SignalException(EHandler::WrongFileType);
#endif
    }

    // Set the revision letter according to the file header
    char revision[8];
    memmove(revision, Header.Signature, 8);
    rev_letter = revision[7];
  }

  // 03/11/1998: This code was added to ensure that true end of
  // file is stored in the VBD file header. 
  INT32 filesize;
  filesize = FileSize(FName);
  if(Header.EndOfFile < filesize) {
    Header.EndOfFile = filesize;
    Flush();
  }
  
  return IsOpen(); // Returns 1 if file opened successfully, else 0.
}

int VBDFile::ReOpen(const char *FName, AccessMode Mode)
// Reopens the FName file. File must exist and be a VBD file type 
// or error occurs. This function will check the revsion letter
// when reopening an existing file.
{
  char *mode_str;
  
  if(Mode == READONLY) {
    mode_str = "rb";
    Status = 0x01; // set good bit, reset read/write bit
  }
  else {
    mode_str = "r+b";
    Status = 0x05; // Set good bit and read/write bit
  }

  // Close the file and flush all buffers 
  ::rewind(fp);
  if(!ReadOnly()) ::fflush(fp);
  ::fclose(fp);

  // Reopen after closing the file
  fp = ::fopen(FName, mode_str);

  if(fp == 0) {
#ifdef CPP_EXCEPTIONS
    throw CFileOpenError();
#else
    Error->SignalException(EHandler::FileOpenError);
#endif
  }
  else {
    Status |= 2; // Set open bit
    strcpy(FileName, FName);
    LastOperation = WRITE; // So Read() works right the first time
    
    ReadHdr();
    // Test file type, checking the revision letter
    if(memcmp(Header.Signature, VBDSignature, 7)) { 
#ifdef CPP_EXCEPTIONS
       throw CWrongFileType();
#else
       Error->SignalException(EHandler::WrongFileType);
#endif
    }

    // Set the revision letter according to the file header
    char revision[8];
    memmove(revision, Header.Signature, 8);
    rev_letter = revision[7];
  }

  // 03/11/1998: This code was added to ensure that true end of
  // file is stored in the VBD file header. 
  INT32 filesize;
  filesize = FileSize(FName);
  if(Header.EndOfFile < filesize) {
    Header.EndOfFile = filesize;
    Flush();
  }
  
  return IsOpen(); // Returns 1 if file opened successfully, else 0.
}

void VBDFile::Close(int flush)
// Closes the file if not already closed, flushing the
// basic header if flush = 1. Does nothing if in the
// error state. Checks for dangling references to
// this file.
{
  if(IsOK()) {
    if(refcount > 1) // Check the object pointer's reference count
#ifdef CPP_EXCEPTIONS
      throw CDanglingPtr();
#else
    Error->SignalException(EHandler::DanglingPtr);
#endif
    
    if(flush && !ReadOnly())
      WriteHdr();
    if(::fclose(fp) != 0)
#ifdef CPP_EXCEPTIONS
      throw CFileCloseError();
#else
      Error->SignalException(EHandler::FileCloseError);
#endif
    strcpy(FileName, ClosedFileName); // Set the initial file name
  }
  Status &= 0xfd; // Reset open bit
}

void VBDFile::Flush()
// Writes the VBDFile header to the file, and then flushes
// any internal buffers the file might have.
{
  if(ReadyForWriting()) {
    WriteHdr();
    if(fflush(fp) != 0)
#ifdef CPP_EXCEPTIONS
      throw CFileWriteError();
#else
    Error->SignalException(EHandler::FileWriteError);
#endif
    Seek(0, SEEK_CUR); // So that the LastOperation logic works right
  }
}

void VBDFile::Seek(FAU Offset, int SeekMode)
// Moves the file pointer to the byte Offset,
// using SeekMode, (which should be either SEEK_SET,
// SEEK_CUR, or SEEK_END).
{
  if(IsOK()) {
     int rv = ::fseek(fp, Offset, SeekMode);
     if(rv != 0)
#ifdef CPP_EXCEPTIONS
       throw CFileSeekError();
#else
       Error->SignalException(EHandler::FileSeekError);
#endif
     LastOperation = SEEK;
  }
  else
#ifdef CPP_EXCEPTIONS
    throw CFileNotReady();
#else
    Error->SignalException(EHandler::FileNotReady);
#endif
}

FAU VBDFile::SeekTo(FAU Address)
// Seek to the specified address, optimizing the seek
// operation by moving the file position indicator based
// on the current stream position. Returns the current
// file position after performing the seek operation.
{
  // Get the current stream position
  StreamPos pos = FilePosition();

  if(Address == CurrAddress) { // Do not perform a seek operation
    return pos;
  }
  else if(Address > pos) { // Seek forward to the specified address
    StreamPos offset = Address - pos;
    Seek(offset, SEEK_CUR);
  }
  else if(Address < pos) { // Seek backward to the specified address
    if((__LWORD__)Address == 0) {
      Rewind();
    }
    else {
      fpos_t f_offset = (__LWORD__)pos - (__LWORD__)Address; 
      fpos_t fpos = GetPosition();
      fpos -= f_offset;
      SetPosition(fpos);
    }
  }
  else { // Current file position equals the specified address
    // Ensure seeks between intervening reads and writes
    Seek(0, SEEK_CUR);
  }
  
  return FilePosition(); // Return current file position after seeking
}

void VBDFile::Rewind()
// Repositions the file pointer to the beginning of the file.
{
  ::rewind(fp);
  LastOperation = REWIND;
}

fpos_t VBDFile::GetPosition()
// Returns the current value of the file-position indicator.
// The SetPosition() function can later use information to reset
// the file pointer to its position at the time GetPosition() was
// called. The fpos_t type is an internal format and is intended
// for use only by the GetPosition() and SetPosition() functions 
// to record information for uniquely specifying positions within 
// a file.
{
  fpos_t pos;
  
  if(IsOK()) {
    int rv = ::fgetpos(fp, &pos); 
    if(rv != 0)
#ifdef CPP_EXCEPTIONS
      throw CFileSeekError();
#else
    Error->SignalException(EHandler::FileSeekError);
#endif
    LastOperation = SEEK;
  }
  else
#ifdef CPP_EXCEPTIONS
    throw CFileNotReady();
#else
  Error->SignalException(EHandler::FileNotReady);
#endif

  return pos;
}

void VBDFile::SetPosition(const fpos_t pos)
// Set the file-position indicator for stream to the value of
// pos, which is obtained in a prior call to the GetPosition()
// function. The fpos_t type is an internal format and is intended
// for use only by the GetPosition() and SetPosition() functions to
// record information for uniquely specifying positions within a
// file.
{
  if(IsOK()) {
    int rv = ::fsetpos(fp, &pos); 
    if(rv != 0)
#ifdef CPP_EXCEPTIONS
      throw CFileSeekError();
#else
    Error->SignalException(EHandler::FileSeekError);
#endif
    LastOperation = SEEK;
  }
  else
#ifdef CPP_EXCEPTIONS
    throw CFileNotReady();
#else
  Error->SignalException(EHandler::FileNotReady);
#endif
}

void VBDFile::Read(void *buf, __UWORD__ Bytes, FAU Address)
// Reads Bytes from Address into buf. The address 
// is always interpreted to be from the beginning of the
// file, unless it's CurrAddress, which means from the
// current position. 
{
  if(IsOK()) {
     // Ensure seeks between intervening reads and writes,
     // and optimize for sequential reads
    if(Address == CurrAddress) {
      if(LastOperation == WRITE) Seek(0, SEEK_CUR);
    }
    else
      Seek(Address, SEEK_SET);
    
    __UWORD__ bytesmoved = ::fread(buf, 1, Bytes, fp);
    if(bytesmoved != Bytes) {
      
      if(feof(fp)) {
	// 03/17/1998: Modified to throw CAccessViolation exception
	// if an end of file error occurs during multiple file access
	// over an NFS mount.
#ifdef CPP_EXCEPTIONS
	throw CAccessViolation();
#else
	  Error->SignalException(EHandler::AccessViolation,
				 EHandler::DISPLAY);
	return;
#endif
      }
      else
#ifdef CPP_EXCEPTIONS
	throw CFileReadError();
#else
      Error->SignalException(EHandler::FileReadError);
#endif
    }
    LastOperation = READ;
  }
  else
#ifdef CPP_EXCEPTIONS
    throw CFileNotReady();
#else
  Error->SignalException(EHandler::FileNotReady);
#endif
}

void VBDFile::Write(const void *buf, __UWORD__ Bytes, FAU Address,
                    int flush, int bit_test)
// Stores Bytes from buf to Address. The Address is
// always interpreted to be from the beginning of the
// file, unless it's CurrAddress, which means from the
// current position. If flush equals one, the file
// buffers will be flushed to disk with each write
// operation. If bit_test equals one, the CRC of the
// of the buffer will be compared to the CRC of the
// actual bytes written to disk.
{
  FAU block_address;
  
  if(ReadyForWriting()) {
     // Ensure seeks between intervening reads and writes,
     // and optimize for sequential writes
     if(Address == CurrAddress) {
       if(LastOperation == READ) Seek(0, SEEK_CUR);
     }
     else 
       Seek(Address, SEEK_SET);
     
     block_address = FilePosition(); 
     __UWORD__ bytesmoved = ::fwrite(buf, 1, Bytes, fp);
     
     if(bytesmoved != Bytes) {
       if(feof(fp))
#ifdef CPP_EXCEPTIONS
	 throw CEOFError();
#else
       Error->SignalException(EHandler::EOFError);
#endif
       else
#ifdef CPP_EXCEPTIONS
	 throw CFileWriteError();
#else
       Error->SignalException(EHandler::FileWriteError);
#endif
     }
     LastOperation = WRITE;
  }
  else
#ifdef CPP_EXCEPTIONS
    throw CFileNotWriteable();
#else
  Error->SignalException(EHandler::FileNotWriteable);
#endif
  
  // 03/13/1998: Allow application to flush disk buffers after
  // each write operation to ensure the file data stays in sync
  // during multiple file access.
  if(flush) {  
    if(fflush(fp) != 0)
#ifdef CPP_EXCEPTIONS
      throw CFileWriteError();
#else
    Error->SignalException(EHandler::FileWriteError);
#endif
    Seek(0, SEEK_CUR); // So that the LastOperation logic works right
  }

  if(bit_test) {
    __ULWORD__ w_csum = calcCRC32((char *)buf, Bytes);
    __ULWORD__ r_csum = CalcChecksum(Bytes, block_address);

    if(w_csum ^ r_csum)
#ifdef CPP_EXCEPTIONS
      throw CChecksumError();
#else
    Error->SignalException(EHandler::ChecksumError);
#endif
  }
}

UINT32 VBDFile::WriteObjectChecksum(FAU Address)
// Used to write a 32-bit checksum for the object at the
// end of a block. The Address variable must be set to the
// file address of the block data, not the block header.
// This function assumes that the data has already been
// written to the block. Returns the 32-bit CRC checksum
// value for the object stored in the block.
{
  // Perform operation according to the revision letter
  switch(rev_letter) {
    case 'A': case 'a' : // Version 1027, rev A
      // 9/24/1998: Revision 'A' reserves four bytes at the end
      // of each block for a 32-bit CRC checksum.
      break;
      
    default: // Always return zero for any file below revision 'A'
      return 0; 
  }
  
  UINT32 CRC;
  __UWORD__ Bytes;
  VBHeader vb;

  // Calculate the address of the block header
  FAU block_address = Address - sizeof(VBHeader);
  
  if(IsOK()) {
    // Make sure that the this is a pre-alloacted block
    // Will throw a CSyncError if this is a bad file address
    Read(&vb, sizeof(VBHeader), block_address);
    if(vb.CkWord != CheckWord)
#ifdef CPP_EXCEPTIONS
      throw CSyncError();
#else
    Error->SignalException(EHandler::SyncError);
#endif

    // Calculate a checksum based on the block data
    Bytes = (vb.Length - sizeof(VBHeader)) - sizeof(vbChecksum);
    CRC = CalcChecksum(Bytes, Address);

    // Offset address to point to the checksum field located
    // at the end of the block.
    Address += Bytes;

    // Write the CRC for the block header and the block data.
    Write(&CRC, sizeof(CRC), Address);
  }

  return CRC;
}

int VBDFile::ReadObjectChecksum(FAU Address, __ULWORD__ *object_crc,
				__ULWORD__ *calc_crc)
// Tests the object's CRC value stored on disk against
// the actual CRC of the bytes stored on disk. The Address
// variable must be set to the file address of the block
// data, not the block header. This function assumes that
// the data has already been written to the block. Returns
// true if the object's CRC test good or false if the CRC
// tests bad. Passes back the object's CRC stored on disk
// in the object_crc variable and the calculated CRC value
// in the calc_crc variable.
{
  // Perform operation according to the revision letter
  switch(rev_letter) {
    case 'A': case 'a' : // Version 1027, rev A
      // 9/24/1998: Revision 'A' reserves four bytes at the end
      // of each block for a 32-bit CRC checksum.
      break;
      
    default: // Always return true for any file below revision 'A'
      return 1; 
  }

  UINT32 CRC, objectCRC;
  VBHeader vb;
  __UWORD__ Bytes;

  // Calculate the address of the block header
  FAU block_address = Address - sizeof(VBHeader);
  
  if(IsOK()) {
    // Make sure that the this is a pre-alloacted block
    // Will throw a CSyncError if this is a bad file address
    Read(&vb, sizeof(VBHeader), block_address);
    if(vb.CkWord != CheckWord)
#ifdef CPP_EXCEPTIONS
      throw CSyncError();
#else
    Error->SignalException(EHandler::SyncError);
#endif

    // Calculate a checksum based on the block data
    Bytes = (vb.Length - sizeof(VBHeader)) - sizeof(vbChecksum);
    CRC = CalcChecksum(Bytes, Address);

    // Offset address to point to the checksum field located
    // at the end of the block.
    Address += Bytes;

    // Read the CRC value stored on disk
    Read(&objectCRC, sizeof(objectCRC), Address);
  }

  if(object_crc) *object_crc = objectCRC;
  if(calc_crc) *calc_crc = CRC;
     
  if(CRC ^ objectCRC) return 0; // Return false if CRC check fails

  return 1; // Return true if the CRC values match
}

__ULWORD__ VBDFile::CalcChecksum(__UWORD__ Bytes, FAU Address, int mem_alloc)
// Calculate a 32-bit CRC checksum for a given number
// of bytes starting at the specified address. Returns a
// 32-bit CRC value. If the mem_alloc variable is true, a
// buffer equal to the specified number of bytes will be
// created in memory. If the mem_alloc variable is false
// or memory allocation fails, the CRC will be calculated
// byte by byte starting at the specified address.
{
  __ULWORD__ CRC;
  __UWORD__ len = Bytes;
  unsigned char data;
  char *buf = 0;  

  // Create a buffer equal to the object length
  if(mem_alloc) buf = new char[Bytes]; 

  if(buf) {
    if(IsOK()) {
      Read(buf, Bytes, Address);
      CRC = calcCRC32(buf, Bytes);
      delete buf;
    }
  }
  else {
    if(IsOK()) {
      SeekTo(Address); // Seek to the specified file address
      CRC = 0xffffffffL;
      while(len--) {
	Read(&data, sizeof(data));
	CRC = calcCRC32(data, CRC);
      }
      CRC ^= 0xffffffffL;
    }
  }

  return CRC; 
}

void VBDFile::ReadVBHdr(VBHeader &hdr, FAU Address)
// Reads in VB header, and tests check word to make
// sure the file is still in sync.
{
  Read(&hdr, sizeof(VBHeader), Address);
  if(hdr.CkWord != CheckWord)
#ifdef CPP_EXCEPTIONS
    throw CSyncError();
#else
    Error->SignalException(EHandler::SyncError);
#endif
}

void VBDFile::WriteVBHdr(const VBHeader &hdr, FAU Address)
// Writes the block header to disk.
{
  Write(&hdr, sizeof(VBHeader), Address);
}

void VBDFile::ReadHdr()
// Reads the VBD file header from disk.
{
  Read(&Header, sizeof(FileHeader), StartOfFile);
}

void VBDFile::WriteHdr()
// Writes the VBD file header to disk.
{
  Write(&Header, sizeof(FileHeader), StartOfFile);
}

FAU VBDFile::Alloc(__UWORD__ Bytes)
// Allocates a Variable Data Block of x Bytes of data from either
// the free space list, or from the end of the file. The number
// of bytes allocated is adjusted to hold a VBHeader plus the
// object. Only the VBHeader is written to the allocated space.
// Alloc() returns the location of the space allocated for the
// object, or returns a 0 if an error occurred.
{
  FAU Address = 0;
  VBHeader vb;
  
  if(ReadyForWriting()) {

    // Adjust the number of bytes to allocate space of VB header
    // according to the revision letter.
    switch(rev_letter) {
      case 'A': case 'a' : // Version 1027, rev A
	// 9/24/1998: Revision 'A' adjusts the number of bytes to
	// include a 32-bit CRC. Space for a four byte checksum will
	// be reserved at the end of the block.
	Bytes += (sizeof(vbChecksum) + sizeof(VBHeader));
	break;

      default: // Default to revision zero
	Bytes += sizeof(VBHeader);
	break;
    }
    
    // Try to reclaim a block if the free space list is not empty
    if(Header.FreeSpace != 0) Address = Reclaim(Bytes); 

    if(IsOK() && Address == 0) { // Extend the file 
      Address = Header.EndOfFile;
      Header.EndOfFile += Bytes;

      // Write to the last byte to avoid possible end of file errors
      __SBYTE__ zero = 0;
      Write(&zero, sizeof(__SBYTE__), Header.EndOfFile-1);
      Header.HighestVB = Address;  
    }

    vb.Status = NormalVB;  // Mark VB with normal attribute
    vb.NextDeletedVB = 0;  // Always 0 unless block is marked deleted
    vb.CkWord = CheckWord; // Assign the check word value
    vb.Length = Bytes;     // Total number of bytes for this VB
    
    WriteVBHdr(vb, Address); // Write header for this VB
  }
  else
#ifdef CPP_EXCEPTIONS
    throw CFileNotWriteable();
#else
    Error->SignalException(EHandler::FileNotWriteable);
#endif
  if(Address > Header.HighestVB) Header.HighestVB = Address;  

  // 03/13/1998: Ensure that the VBD file header stays in sync
  // during multiple file access.
  WriteHdr();

  return SeekTo(Address + sizeof(VBHeader));
}

int VBDFile::Remove(FAU Address, int mem_alloc)
// Deletes the VB and removes the object by setting all
// the data bytes to zero. The VB is marked removed,
// indicating that the object cannot be undeleted. If
// the mem_alloc variable is true, a buffer equal to
// the object length of the object will be created in
// memory. If the mem_alloc variable is false or memory
// allocation fails, a single byte value, equal to zero,
// is written to the file byte by byte for the length of
// the object. 
{
  // Return false if VB is already deleted or marked bad
  if(!Delete(Address)) return 0;
    
  VBHeader vb;
  FAU VBAddress = Address - sizeof(VBHeader); // Address of VB header
  ReadVBHdr(vb, VBAddress);
  vb.Status = RemovedVB; // Mark VB removed
  __UWORD__ Bytes = vb.Length - sizeof(VBHeader);

  const unsigned char data = 0;
  const __UWORD__ len = Bytes;
  char *buf = 0;
  
  // Create a buffer equal to the object length
  if(mem_alloc) buf = new char[len]; 

  if(buf) {
    for(__UWORD__ i = 0; i < len; i++)  // Zero out the buffer
      buf[i] = 0;
  
    if(ReadyForWriting()) {
      WriteVBHdr(vb, VBAddress);
      Write(buf, Bytes, Address, 1, 0); 
      delete buf;
      return 1; // Return true if successful
    }
    else
#ifdef CPP_EXCEPTIONS
      throw CFileNotWriteable();
#else
    Error->SignalException(EHandler::FileNotWriteable);
#endif
  }
  else {
    if(ReadyForWriting()) {
      WriteVBHdr(vb, VBAddress);
      while(Bytes--) // Write all zeros in place of the object
	Write(&data, sizeof(data), CurrAddress, 0, 0); 
      return 1; // Return true if successful
    }
    else
#ifdef CPP_EXCEPTIONS
      throw CFileNotWriteable();
#else
    Error->SignalException(EHandler::FileNotWriteable);
#endif
  }

  return 0; // Ensure all paths return a value
}

int VBDFile::Delete(FAU Address)
// Marks the VB at location Address deleted and leaves the
// object unchanged, allowing it to be undeleted. The deleted
// VB is placed on the front of the free space list. 
{
  VBHeader vb;
  
  FAU VBAddress = Address - sizeof(VBHeader); // Address of VB header
  ReadVBHdr(vb, VBAddress);

  // Return false if VB is already deleted
  if((vb.Status & 0xff) != NormalVB) return 0; 
  
  vb.Status = DeletedVB; // Mark VB deleted
  
  if(ReadyForWriting()) {
    vb.NextDeletedVB = Header.FreeSpace; // VB to become head of free list
    WriteVBHdr(vb, VBAddress);
    
    // Make sure the free space list is not corrupt
    if(Header.FreeSpace != FSListCorrupt) {
      Header.FreeSpace = VBAddress;
      WriteHdr();
    }
    return 1; // Return true if successful
  }
  else
#ifdef CPP_EXCEPTIONS
    throw CFileNotWriteable();
#else
    Error->SignalException(EHandler::FileNotWriteable);
#endif
    return 0; // Ensure all paths return a value
}

StreamPos VBDFile::FilePosition()
// Returns the current file posistion
{
  if(!IsOK())
#ifdef CPP_EXCEPTIONS
    throw CFileNotReady();
#else
    Error->SignalException(EHandler::FileNotReady);
#endif

  return ::ftell(fp);
}

const char *VBDFile::GetSignature() const
// Return the VBD file signature with no revision letter
{
  size_t len = sizeof(Header.Signature);
  char *s = new char[len];
  memcpy(s, Header.Signature, len);
  s[len-1] = 0; 
  return (const char *)s;
}

char *VBDFile::GetSignature() 
// Return the VBD file signature with no revision letter
{
  size_t len = sizeof(Header.Signature);
  char *s = new char[len];
  memcpy(s, Header.Signature, len);
  s[len-1] = 0;
  return s;
}

__UWORD__ VBDFile::VBTotal()
// Returns the total number of valid VBs in the file
{
  // 03/13/1998: Ensure that the VBD file header stays in sync
  // during multiple file access
  TestVBDHeader();

  VBHeader vb;
  FAU Address = FindFirstVB(0); // Search the entire file
  __UWORD__ i = 0;

  while(Address) { // Until a block of a valid size is found
    if(Address >= Header.EndOfFile) break;
    Read(&vb, sizeof(VBHeader), Address);
    if(!IsOK()) break;

    // If this is not a valid block, find the next one
    if(vb.CkWord != CheckWord) {
      Address = FindFirstVB(Address);
    }
    else {
      Address += vb.Length;
      i++;
    }
  }
  return i;
}

INT32 VBDFile::VBDeleted(__UWORD__ *d, __UWORD__ *r)
// Returns the total number of removed and deleted VBs
{
  // 03/13/1998: Ensure that the VBD file header stays in sync
  // during multiple file access
  TestVBDHeader();

  VBHeader vb;
  FAU addr = Header.FreeSpace; 
  __UWORD__ i = 0;

  // Set the deleted and removed pointers to zero
  if(d) *d = 0; if(r) *r = 0;
  
  if(addr == FSListCorrupt) return FSListCorrupt;

  while(addr) { // Until a block of a valid size is found
    Read(&vb, sizeof(VBHeader), addr);
    if(!IsOK()) break;

    // If this is not a valid block, the free space list is corrupt
    if(vb.CkWord != CheckWord) {
      Header.FreeSpace = FSListCorrupt;
      WriteHdr(); 
      return FSListCorrupt;
    }

    // Added: 09/04/1998 to prevent an infinite loop. 
    // If the block is not marked deleted or removed, the
    // Next deleted VBD pointer is bad. This will cause
    // an infinite loop if the end of the free space
    // list is pointing to valid block.
    switch((__SBYTE__)(vb.Status & 0xff)) {
      case DeletedVB :
	// Make sure the block is not pointing to itself
	if(addr == vb.NextDeletedVB) { 
	  Header.FreeSpace = FSListCorrupt;
	  WriteHdr(); 
	  return 0;
	}
	break;

      case RemovedVB :
	// Make sure the block is not pointing to itself
	if(addr == vb.NextDeletedVB) { 
	  Header.FreeSpace = FSListCorrupt;
	  WriteHdr(); 
	  return 0;
	}
	break;

      default :
	Header.FreeSpace = FSListCorrupt;
	WriteHdr(); 
	return 0;
    }

    addr = vb.NextDeletedVB;

    if(d) {
      if((vb.Status & 0xff) == DeletedVB)
	*d = *d+1;
    }

    if(r) {
      if((vb.Status & 0xff) == RemovedVB)
	*r = *r+1;
    }

    i++;
  }
  return i;
}

__UWORD__ VBDFile::ObjectLength(FAU Address)
// Returns the object length in bytes at the specified address
{
  VBHeader vb;
  FAU VBAddress;

  // Calculate the address of the block header
  if(Address == CurrAddress)
    VBAddress = FilePosition() - sizeof(VBHeader); 
  else
    VBAddress = Address - sizeof(VBHeader); 
  
  ReadVBHdr(vb, VBAddress);

  __UWORD__ len;
  
  // Adjust the number of bytes to allocate space of VB header
  // according to the revision letter.
  switch(rev_letter) {
    case 'A': case 'a' : // Version 1027, rev A
      // 9/24/1998: Revision 'A' adjusts the number of bytes to
      // include a 32-bit CRC. Space for a four byte checksum is
      // reserved at the end of the block.
      len = vb.Length - sizeof(VBHeader);
      len -= sizeof(vbChecksum);
      break;

    default: // Default to revision zero
      len = vb.Length - sizeof(VBHeader);
      break;
  }
  return len;
}

__UWORD__ VBDFile::VBLength(FAU Address)
// Returns the total VB length in bytes at the specified address
{
  VBHeader vb;
  FAU VBAddress;
  
  // Calculate the address of the block header
  if(Address == CurrAddress)
    VBAddress = FilePosition() - sizeof(VBHeader); 
  else
    VBAddress = Address - sizeof(VBHeader); 

  ReadVBHdr(vb, VBAddress);
  return vb.Length;
}

int VBDFile::UnDelete(FAU Address)
// Undeletes the VB if it has not been removed or reclaimed
{
  VBHeader vb, prev_VB; 
  FAU addr, vb_addr, prev_addr;
  addr = Header.FreeSpace; 

  vb_addr = Address - sizeof(VBHeader); // Address of VB header
  Read(&vb, sizeof(VBHeader), vb_addr);
  
  // Return false if VB is not marked deleted 
  if((vb.Status & 0xff) != DeletedVB) return 0; 

  // Loop until the block is found in the free space list
  while(addr) { 
    Read(&vb, sizeof(VBHeader), addr);
    if(!IsOK()) break;
    
    // Added: 09/04/1998 to signal not to use the free space list.
    // If this is not a valid block, the free space list is corrupt
    if(vb.CkWord != CheckWord) {
      Header.FreeSpace = FSListCorrupt;
      WriteHdr(); 
      return 0;
    }
    
    // Added: 09/04/1998 to prevent an infinite loop. 
    // If the block is not marked deleted or removed, the
    // Next deleted VBD pointer is bad. This will cause
    // an infinite loop if the end of the free space
    // list is pointing to valid block.
    switch((__SBYTE__)(vb.Status & 0xff)) {
      case DeletedVB :
	// Make sure the block is not pointing to itself 
	if(addr == vb.NextDeletedVB) { 
	  Header.FreeSpace = FSListCorrupt;
	  WriteHdr(); 
	  return 0;
	}
	break;

      case RemovedVB :
	// Make sure the block is not pointing to itself 
	if(addr == vb.NextDeletedVB) { 
	  Header.FreeSpace = FSListCorrupt;
	  WriteHdr(); 
	  return 0;
	}
	break;

      default :
	Header.FreeSpace = FSListCorrupt;
	WriteHdr(); 
	return 0;
    }

    // Found the block in the free space list
    if(addr == vb_addr) { 
      if(prev_addr == 0) { // Adjust the free space list
	// At the head of freespace list, so make a new head
	Header.FreeSpace = vb.NextDeletedVB;
	WriteHdr(); 
      }
      else {
	// In the middle of free space, so link prev to Next
	prev_VB.NextDeletedVB = vb.NextDeletedVB;
	WriteVBHdr(prev_VB, prev_addr);
      }

      // Undelete the specified block
      vb.Status = NormalVB;  // Mark VB with normal attribute
      vb.NextDeletedVB = 0;  // Always 0 unless VB is marked deleted
      WriteVBHdr(vb, addr);  // Write header for this VB

      return 1; // Return true if successful
    }
    
    // Keep looping through the free space list
    prev_addr = addr;
    prev_VB = vb;
    addr = vb.NextDeletedVB;
  } 

  return 0; // Return false if block was not undeleted
}

FAU VBDFile::ReAlloc(FAU Address, __UWORD__ Bytes)
// Reallocates a Variable Data Block of x Bytes of data from either
// the free space list, or from the end of the file. ReAlloc()
// returns the location of the space reallocated for the object,
// or returns a 0 if an error occurred.
{
  // Delete the object before allocating space for it
  if(!Delete(Address)) return 0; 
  return Alloc(Bytes);
}

FAU VBDFile::FindFirstVB(FAU Offset)
// Search through the VBD file until a valid VB is found.
// The search starts at the heap start or the offset value.
// Returns 0 if no valid VB is found in the file.
{
  VBHeader vb;
  FAU Address = 0;

  // 03/13/1998: Ensure that the VBD file header stays in sync
  // during multiple file access
  TestVBDHeader();

  // No VBs have been allocated yet
  if(Header.HeapStart == Header.EndOfFile) return 0;
     
  if(!Offset)
    Address = Header.HeapStart; // If no Offset, start at heap
  else {
    Address = Address + Offset; // Offset the starting address 

    if(Address >= Header.EndOfFile) // Prevent offsetting past EOF
      return 0; // Invalid address
  }
  
  while(1) {
    if(Address + sizeof(VBHeader) >= Header.EndOfFile || !IsOK()) return 0;
    Read(&vb, sizeof(VBHeader), Address);
    if(vb.CkWord != CheckWord)
      Address++; // Loop through the file byte by byte
    else
      break; // Found valid VB
  }
  return SeekTo(Address);
}

FAU VBDFile::FindFirstObject(FAU Offset)
// Search through the VBD file until a valid VB is found and
// then return the object's address. If the VB is marked deleted
// continue searching until the first normal VB is found. The
// search starts at the heap start or the offset. Returns 0 if
// no valid VB is found in the file.
{
  VBHeader vb;
  FAU Address = FindFirstVB(Offset);
  if(!Address) return 0;

  while(1) { // Loop until a normal VB status is found
    ReadVBHdr(vb, Address);
    if((vb.Status & 0xff) == NormalVB) break;
    Address = FindFirstVB(Address+vb.Length);
    if(!Address) return 0;
  }

  return Address + sizeof(VBHeader);
}

FAU VBDFile::FindNextVB(FAU Offset)
// Search through the VBD file until the next valid VB after the
// first valid VB is found. The search starts at the heap start
// or the offset value. Returns 0 if no valid VB is found.
{
  VBHeader vb;

  // No VBs have been allocated yet
  if(Header.HeapStart == Header.EndOfFile) return 0;

  FAU Address = FindFirstVB(Offset);

  if(!Address) return 0; // No Vaild VB found

  ReadVBHdr(vb, Address);
  FAU NextVB = Address + vb.Length;

  if(NextVB >= Header.EndOfFile) return Address; // This is last the VB

  ReadVBHdr(vb, NextVB); // Ensure VB header is valid

  return NextVB;
}

FAU VBDFile::FindNextObject(FAU Offset)
// Search through the VBD file until the next valid VB after the
// first valid VB is found and then return the object's address.
// If the VB is marked deleted continue searching until the next
// normal VB is found. The search starts at the heap start or the
// offset value. Returns 0 if no valid VB is found.
{
  VBHeader vb;
  FAU Address = FindNextVB(Offset);
  if(!Address) return 0;
  
  while(1) { // Loop until a normal VB status is found
    ReadVBHdr(vb, Address);
    if((vb.Status & 0xff) == NormalVB) break;
    Address = FindNextVB(Address+vb.Length);
    if(!Address) return 0;
  }

  return Address + sizeof(VBHeader);
}

FAU VBDFile::GetFreeSpace() 
{
  FileHeader fh;
  Read(&fh, sizeof(FileHeader), StartOfFile);

  // Ensure the in memory copy and the disk copy are the same
  if(fh.FreeSpace != Header.FreeSpace) { 
    ReadHdr();
  }

  return Header.FreeSpace;
}
  
FAU VBDFile::GetVBDFreeSpace() 
// Use this version of GetFreeSpace for wxWindows programs
{
  FileHeader fh;
  Read(&fh, sizeof(FileHeader), StartOfFile);

  // Ensure the in memory copy and the disk copy are the same
  if(fh.FreeSpace != Header.FreeSpace) { 
    ReadHdr();
  }

  return Header.FreeSpace;
}

FAU VBDFile::GetEOF() 
{
  FileHeader fh;
  Read(&fh, sizeof(FileHeader), StartOfFile);

  // Ensure the in memory copy and the disk copy are the same
  if(fh.EndOfFile != Header.EndOfFile) { 
    ReadHdr();
  }

  return Header.EndOfFile;
}

FAU VBDFile::GetHeapStart() 
{
  FileHeader fh;
  Read(&fh, sizeof(FileHeader), StartOfFile);

  // Ensure the in memory copy and the disk copy are the same
  if(fh.HeapStart != Header.HeapStart) { 
    ReadHdr();
  }

  return Header.HeapStart;
}

FAU VBDFile::GetHighestVB()
{
  FileHeader fh;
  Read(&fh, sizeof(FileHeader), StartOfFile);

  // Ensure the in memory copy and the disk copy are the same
  if(fh.HighestVB != Header.HighestVB) { 
    ReadHdr();
  }

  return Header.HighestVB;
}

__LWORD__ VBDFile::StaticArea()
{
  FileHeader fh;
  Read(&fh, sizeof(FileHeader), StartOfFile);

  // Ensure the in memory copy and the disk copy are the same
  if(fh.HeapStart != Header.HeapStart) { 
    ReadHdr();
  }

  return Header.HeapStart - sizeof(FileHeader);
}   


int VBDFile::TestVBDHeader()
// This function is used to ensure that the in memory copy
// of the VBD file header and the disk copy stay in sync
// during multiple file access.
{
  FileHeader fh;
  int errors = 0;
  
  Read(&fh, sizeof(FileHeader), StartOfFile);

  if(fh.FreeSpace != Header.FreeSpace) { 
    ReadHdr();
    errors++;
  }

  if(fh.EndOfFile != Header.EndOfFile) { 
    ReadHdr();
    errors++;
  }

  if(fh.HeapStart != Header.HeapStart) { 
    ReadHdr();
    errors++;
  }

  if(fh.HighestVB != Header.HighestVB) { 
    ReadHdr();
    errors++;
  }

  if(fh.HeapStart != Header.HeapStart) { 
    ReadHdr();
    errors++;
  }

  return errors;
}

// ==============================================================
// General purpose file utilites (BEGIN HERE)
// ==============================================================
int VBDFile::Exists(const char *FName)
// Returns true if the file exists
{
  FILE *tmp; // Temporary file pointer
  tmp = ::fopen(FName, "rb");

  if(!tmp) return 0; // Return 0 if file does not exist
  
  fclose(tmp);
  return 1;
}

__LWORD__ VBDFile::FileSize(const char *FName)
// Returns the file size. Use after file has been closed
// and re-opened to ensure that all the buffers are flushed
// to disk. 
{
  struct stat buf;
  int result = stat(FName, &buf);
  if(result != 0)
#ifdef CPP_EXCEPTIONS
    throw CFileOpenError();
#else
    Error->SignalException(EHandler::FileOpenError);
#endif
  return buf.st_size;
}
// ==============================================================
// General purpose file utilites (BEGIN HERE)
// ==============================================================

// ==============================================================
// Debug VBD File manager:  revisions 01/20/1997 (BEGIN HERE)
// ==============================================================
int VBDFile::BlindOpen(const char *FName, AccessMode Mode)
// Opens the FName file without checking the file type.
{
  char *mode_str;

  // First, close the current file if open.
  Close();

  if(Mode == READONLY) {
     mode_str = "rb";
     Status = 0x01; // set good bit, reset read/write bit
  }
  else {
     mode_str = "r+b";
     Status = 0x05; // Set good bit and read/write bit
  }

  fp = ::fopen(FName, mode_str);

  if(fp == 0) {
#ifdef CPP_EXCEPTIONS
    throw CFileOpenError();
#else
    Error->SignalException(EHandler::FileOpenError);
#endif
  }
  else {
    Status |= 2; // Set open bit
    strcpy(FileName, FName);
    LastOperation = WRITE; // So Read() works right the first time
    ReadHdr();

    // Set the revision letter according to the file header
    if(memcmp(Header.Signature, VBDSignature, 7) == 0) { 
      char revision[8];
      memmove(revision, Header.Signature, 8);
      rev_letter = revision[7];
    }    
  }

  return IsOpen(); // Returns 1 if file opened successfully, else 0.
}

FAU VBDFile::VBSearch(FAU Offset)
// Search through the VBD file until a valid VB is found.
// The search starts at the beginning  of the file or the
// offset value. Returns 0 if no valid VB is found in the
// file.
{
  VBHeader vb;
  FAU Address = 0;

  FAU StaticEOF = FileSize(VBDFileName());

  if(!Offset)
    Address = sizeof(FileHeader); // If no Offset, start after VBD header
  else {
    Address = Address + Offset; // Offset the starting address 

    if(Address >= StaticEOF) // Prevent offsetting past EOF
      return 0; // Invalid address
  }
  
  while(1) {
    if(Address + sizeof(VBHeader) >= StaticEOF || !IsOK()) return 0;
    Read(&vb, sizeof(VBHeader), Address);
    if(vb.CkWord != CheckWord)
      Address++; // Loop through the file byte by byte
    else
      break; // Found valid VB
  }
  return Address;
}
// ==============================================================
// Debug VBD File manager:  revisions 01/20/1997 (END HERE)
// ==============================================================

// ==============================================================
// Reclaim function:  revisions 09/11/1998 (BEGIN HERE)
// ==============================================================
// Define the __RECLAIM_BEST_FIT__ macro to use the best-fit routine
// or the __RECLAIM_FIRST_FIT__ macro to use the first-fit routine.
// Will default to first-fit. The best-fit method will prevent
// fragmentation as much as possible but is costly in terms of speed.
// The first-fit method will not prevent fragmentation as well as the
// best-fit method but offers a tremendous speed advantage.

#ifdef __RECLAIM_BEST_FIT__ // Defaults to first-fit
FAU VBDFile::Reclaim(__UWORD__ Bytes)
// Searches the free space list for a block that can be reuesd.
// This function will search the free space list for an "exact- 
// fit" first and then try to find the "best-fit" for the number
// of bytes requested. NOTE: The byte size is adjusted by the
// Alloc() function to allocate space for the VB header plus
// the object. Returns address of the reclaimed space, or zero
// if a deleted or removed block of the appropriate size is not
// found. An exact-fit is a block that matches the exact number
// of bytes requested. If an exact-fit cannot be found, the next
// block big enough to hold number of bytes requested plus the
// size a block header with a least one byte left over becomes
// a best-fit block. The search continues until a best-fit
// block with the least number of unsed bytes is found. The
// used bytes in the best-fit block are used to create a new
// block that will be put back on the free space list. This
// will keep the gaps between the blocks as small as possible,
// with the smallest gap being as large as a single block header
// plus one byte.
{
  // Cannot reuse any blocks if the free space list is corrupt
  if(Header.FreeSpace == FSListCorrupt) return 0;

  VBHeader vb, prev_VB, new_VB;
  VBHeader best_fit_prev_VB, best_fit_VB;
  FAU addr, prev_addr, new_addr;
  FAU best_fit_addr, best_fit_prev_addr;
  __UWORD__ avail_len, unused_len, best_fit_unused_len = 0;
  
  // Constants for the best-fit criteria. NOTE: The maximum length
  // of a block to reuse equals: (max_limit * byte_multiple) * Bytes 
  const unsigned max_limit = 10;    // Maximum number of byte multiples
  const double byte_multiple = .25; // Byte multiples  

  double best_byte_len, byte_percent = 0;
  double bytes_requested = (double)Bytes;
  unsigned i;
  unsigned best_length[max_limit];
  
  // Calculate the best-fit byte values
  for(i = 0; i < max_limit; i++) {
    byte_percent += byte_multiple;
    best_byte_len = bytes_requested * byte_percent;
    best_length[i] = (unsigned)best_byte_len;// + sizeof(VBHeader);
  }

  addr = Header.FreeSpace;
  prev_addr = best_fit_addr = 0;
  
  // Search the entire free space list until an exact-fit 
  // or a best-fit block is found.
  while(addr) { 
    Read(&vb, sizeof(VBHeader), addr);
    if(!IsOK()) break;
    
    // Added: 09/04/1998 to signal not to use the free space list.
    // If this is not a valid block, the free space list is corrupt
    if(vb.CkWord != CheckWord) {
      Header.FreeSpace = FSListCorrupt;
      WriteHdr(); 
      return 0;
    }

    // Added: 09/04/1998 to prevent an infinite loop. 
    // If the block is not marked deleted or removed, the
    // Next deleted VBD pointer is bad. This will cause
    // an infinite loop if the end of the free space
    // list is pointing to valid block.
    switch((__SBYTE__)(vb.Status & 0xff)) {
      case DeletedVB :
	// Make sure the block is not pointing to itself 
	if(addr == vb.NextDeletedVB) { 
	  Header.FreeSpace = FSListCorrupt;
	  WriteHdr(); 
	  return 0;
	}
	break;

      case RemovedVB :
	// Make sure the block is not pointing to itself 
	if(addr == vb.NextDeletedVB) { 
	  Header.FreeSpace = FSListCorrupt;
	  WriteHdr(); 
	  return 0;
	}
	break;

      default :
	Header.FreeSpace = FSListCorrupt;
	WriteHdr(); 
	return 0;
    }

    avail_len = vb.Length; // Length of object plus sizeof VB header

    // Unused length must be big enough to hold a two VB headers
    // plus the object
    if(avail_len > Bytes + sizeof(VBHeader)) 
      unused_len = avail_len - Bytes;
    else
      unused_len = 0;
    
    if(avail_len == Bytes) {
      // Block is an exact fit, so link prev link to Next link
      if(prev_addr == 0) {
	// At the head of freespace list, so make a new head
	Header.FreeSpace = vb.NextDeletedVB;
	WriteHdr(); 
      }
      else {
	// In the middle of free space, so link prev to Next
	prev_VB.NextDeletedVB = vb.NextDeletedVB;
	WriteVBHdr(prev_VB, prev_addr);
      }
      return IsOK() ? (__LWORD__)addr : 0;
    }

    if(unused_len > 0) { // Found bigger block with room for header
      for(i = 0; i < max_limit; i++) {
	if(unused_len <= best_length[i]) { 
	  // Use the block matching the best-fit criteria
	  if(best_fit_unused_len > best_length[i]) { 
	    // Use the block if it is a better then the current one
	    best_fit_addr = addr;
	    best_fit_prev_addr = prev_addr;
	    best_fit_VB = vb;
	    best_fit_prev_VB = prev_VB;
	    best_fit_unused_len = unused_len;
	  }
	}
      }
    }
    
    // Block not big enough, so try Next block
    prev_addr = addr;
    prev_VB = vb;
    addr = vb.NextDeletedVB;
  
  } // End of block search

  // Could not find a best fit
  if(best_fit_addr == 0) return 0; 

  // Reuse the block and any remaining bytes
  new_addr = best_fit_addr + Bytes;
  new_VB.CkWord = CheckWord;
  new_VB.Status = RemovedVB;
  new_VB.NextDeletedVB = best_fit_VB.NextDeletedVB;
  new_VB.Length = best_fit_unused_len;
  WriteVBHdr(new_VB, new_addr);

  // Adjust the free space list
  if(best_fit_prev_addr == 0) {
    // At the head of freespace, so this is the new head
    Header.FreeSpace = new_addr;
    WriteHdr(); 
  }
  else { // In the middle of freespace
    best_fit_prev_VB.NextDeletedVB = new_addr;
    WriteVBHdr(best_fit_prev_VB, best_fit_prev_addr);
  }
  
  return IsOK() ? (__LWORD__)best_fit_addr : 0;
}

#else // __RECLAIM_FIRST_FIT__ 
FAU VBDFile::Reclaim(__UWORD__ Bytes)
// Searchs the free space list for the first block that can
// be reused. This function will search the free space list
// for a "first-fit" big enough to hold the number of bytes
// requested. NOTE: The byte size is adjusted by the Alloc()
// function to allocate space for the VB header plus the object.
// Returns address of the reclaimed space, or zero if a deleted
// or removed block of the appropriate size is not found. If an
// "exact-fit" is found (a block that matches the exact number
// of bytes requested) the address of that block is returned.
// Otherwise the address of the frist block big enough to hold
// number of bytes requested plus the size a block header with a
// least one byte left over is returned. The used bytes in the
// frist-fit block are used to create a new block that will be
// put back on the free space list. 
{
  // Cannot reuse any blocks if the free space list is corrupt
  if(Header.FreeSpace == FSListCorrupt) return 0;

  VBHeader vb, prev_VB, new_VB;
  FAU addr, prev_addr, new_addr;
  __UWORD__ avail_len, unused_len;

  addr = Header.FreeSpace; prev_addr = 0;

  // Search the free space list until a first-fit is found
  while(addr) { 
    Read(&vb, sizeof(VBHeader), addr);
    if(!IsOK()) break;
    
    // Added: 09/04/1998 to signal not to use the free space list.
    // If this is not a valid block, the free space list is corrupt
    if(vb.CkWord != CheckWord) {
      Header.FreeSpace = FSListCorrupt;
      WriteHdr(); 
      return 0;
    }

    // Added: 09/04/1998 to prevent an infinite loop. 
    // If the block is not marked deleted or removed, the
    // Next deleted VBD pointer is bad. This will cause
    // an infinite loop if the end of the free space
    // list is pointing to valid block.
    switch((__SBYTE__)(vb.Status & 0xff)) {
      case DeletedVB :
	// Make sure the block is not pointing to itself 
	if(addr == vb.NextDeletedVB) { 
	  Header.FreeSpace = FSListCorrupt;
	  WriteHdr(); 
	  return 0;
	}
	break;

      case RemovedVB :
	// Make sure the block is not pointing to itself 
	if(addr == vb.NextDeletedVB) { 
	  Header.FreeSpace = FSListCorrupt;
	  WriteHdr(); 
	  return 0;
	}
	break;

      default :
	Header.FreeSpace = FSListCorrupt;
	WriteHdr(); 
	return 0;
    }

    avail_len = vb.Length; // Length of object plus sizeof VB header

    // Unused length must be big enough to hold a two VB headers
    // plus the object
    if(avail_len > Bytes + sizeof(VBHeader)) 
      unused_len = avail_len - Bytes;
    else
      unused_len = 0;
    
    if(avail_len == Bytes) {
      // Block is an exact fit, so link prev link to Next link
      if(prev_addr == 0) {
	// At the head of freespace list, so make a new head
	Header.FreeSpace = vb.NextDeletedVB;
	WriteHdr(); 
      }
      else {
	// In the middle of free space, so link prev to Next
	prev_VB.NextDeletedVB = vb.NextDeletedVB;
	WriteVBHdr(prev_VB, prev_addr);
      }
      break;
    }

    if(unused_len > 0) {
      // Block too big, and there's room for a VB header
      // in the unused portion, so reuse any remaining bytes.
      new_addr = addr + Bytes;
      new_VB.CkWord = CheckWord;
      new_VB.Status = RemovedVB;
      new_VB.NextDeletedVB = vb.NextDeletedVB;
      new_VB.Length = unused_len;
      WriteVBHdr(new_VB, new_addr);
      if(prev_addr == 0) {
	// At the head of freespace, so this is the new head
	Header.FreeSpace = new_addr;
	WriteHdr(); 
      }
      else { // In the middle of freespace
	prev_VB.NextDeletedVB = new_addr;
	WriteVBHdr(prev_VB, prev_addr);
      }
      break;
    }

    // Block not big enough, so try Next block
    prev_addr = addr;
    prev_VB = vb;
    addr = vb.NextDeletedVB;

  } // End of block search
  
  return IsOK() ? (__LWORD__)addr : 0;
}
#endif //__RECLAIM_FIRST_FIT__, __RECLAIM_BEST_FIT__
// ==============================================================
// Reclaim function:  revisions 09/11/1998 (END HERE)
// ==============================================================

// ----------------------------------------------------------- //
// ------------------------------- //
// --------- End of File --------- //
// ------------------------------- //
