/**************************************************************************
 * Source Id :
 *
 * $Id: rserv.cpp,v 1.4 2001/04/23 21:49:12 vlabella Exp $
 *-------------------------------------------------------------------------
 * Project Notes :
 *
 *  Diamond Base
 *  ============
 *      A solid database implementation, spurred on by the continuing
 *  Metal (Lead) Base saga.
 *
 *  Project Team :
 *        A. Davison
 *        K. Lentin
 *        D. Platt
 *
 *	Project Commenced : 05-02-1993
 *
 *-------------------------------------------------------------------------
 *  Module Notes :
 *
 *  recServer module
 *
 *  Original Author : Darren Platt
 *
 **************************************************************************/

// FOR NOW::
// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#include <fcntl.h>
#include <sys/types.h>
#include <diamond/dbconf.h>

#if defined(__BORLANDC__) || defined(__SASC) || defined(_MSC_VER)
#   ifndef __SASC
#      include <io.h>
#   endif
#   include <stdio.h>
#if defined(_MSC_VER)
#   include <sys/locking.h>
#endif
#else
#include <unistd.h>
#endif

#if defined(_WIN32) && !defined(__GNUG__)
#  define LOCK(f, c, p)  _locking(f, c, (p))
#else
#  define LOCK(f, c, p)  fcntl(f, c, (p))
#endif

#include <assert.h>
#include <errno.h>

#if HAVE_STDLIB_H
#include <stdlib.h>
#endif

#if HAVE_MALLOC_H && !MALLOC_H_INCLUDED
extern "C" {
	#include <malloc.h>
}
#define MALLOC_H_INCLUDED 1
#endif

#include <iostream.h>
#include <diamond/rserv.h>

//
// A record server which just retrieves records from a file,
// monitoring where the free slots occur.
//
// Turn on RSERV_DEBUG for crude debugging info
//
DIA_EXPORT
recServer::recServer(const char *name, const uint32 offset, const size_t cSize) : cache()
{
	commonInit();

	fd = open(OPEN_CAST name,O_RDWR | BINARY_FILE);
	if (fd==-1) {
		// Failed to open.
		isOpen = false;
		dbString err("Couldn't open the supplied file name ");
		err += name;
		dbErr(db_notopen,err);
	} else {
		headerOffset = offset;
		dataOffset = headerOffset + sizeof header;

		if (headerOffset) {
			// Seek to the header position if non-zero.
			if (lseek(fd, headerOffset,SEEK_SET)!=(SEEK_RETURN_T) headerOffset) {
				dbErr(db_corrupt,"Seeking to header:");
				return;
			}
		}
		// Now read in the header structure.
		if (!readHeader(header)) {
			// Eeek - can't read in header.
			dbErr(db_corrupt,"Failed reading header because:");
			return;
		}

		blockLength = bitPoolBytes + header.recLength * bitPoolRecs;

		// Read in the starting bitPool;
		if (!getBitPool(currentPool,indelPool)) {
			isOpen = false;
			return;
		}
		// Create a cache to speed things up
		createCache(header.recLength, cSize);
		isOpen = true;
	}
}

// Just create a recServer without opening any files

DIA_EXPORT recServer::recServer(void) : cache()
{
	commonInit();
	isOpen = false;
}

DIA_EXPORT recServer::~recServer()
{

#if !defined(__BORLANDC__)
	if (locked) {

#if defined(_WIN32) && !defined(__GNUG__)
		_locking (fd, _LK_UNLCK, 1);
#else
		struct flock flk;
		flk.l_type=F_UNLCK;
		flk.l_whence=SEEK_SET;
		flk.l_start=0;
		flk.l_len=1;
		fcntl(fd, F_SETLKW, (uint32)&flk);
#endif
	}
#endif
	if (isOpen) close(fd);
}

// Create a new recServer file

dbError
DIA_EXPORT recServer::createDb(
	const char *name,
	const uint32 recLen,
	const uint32 offset,
	const size_t cSize)
{
	// Create a new db.
	if (isOpen) return dbErr(db_alreadyopen);
	createCache(recLen, cSize);

	headerOffset = offset;
	dataOffset = headerOffset + sizeof header;

	fd = open(OPEN_CAST name,O_CREAT | O_RDWR | BINARY_FILE, 0644);
	if (fd==-1) {
		// Failed to open.
		isOpen = false;
		return dbErr(db_notopen,"Failed to open rec server");
	} else {

		headerOffset = offset;

		// Setup the header details.
		header.recLength = recLen;
		header.totalRecs = bitPoolRecs;
		header.recsUsed = 0;

		blockLength = bitPoolBytes + header.recLength * bitPoolRecs;

		indelPool.zero();
		if (!putBitPool(0,indelPool)) return dbErr(db_err,"Failed to write bitpool");

		if (!writeHeader(header)) return dbErr(db_err,"Failed to write rserv header");

		isOpen = true;
		return dbErr(db_ok);
	}
}

bool recServer::refresh(void)
{
	if (!readHeader(header))
		return false;
	return getBitPool(currentPool,indelPool);
}

dbError recServer::getRec(const uint32 idx,void *buffer)
{
	if (!isOpen) return dbErr(db_notopen);

	// Can we get it from the cache?
	if (getCache((char*)buffer, idx))
	{
//		cout << "Let's go to the video tape" << endl;
		return dbErr(db_ok);
	}

	// Not in the cache, read it in and put it in the cache

	int ofs = idx2offset(idx);
	if (lseek(fd, idx2offset(idx),SEEK_SET)!=(SEEK_RETURN_T) idx2offset(idx)) {
		return dbErr(db_corrupt,"Couldn't seek to record");
	}
	int ret;
	ret = read(fd,(char*)buffer,(size_t)header.recLength);
	if (ret!=(signed)header.recLength)	{
		dbString errMsg("Couldn't read record - error #");
		errMsg += intTodbString(errno);
		return dbErr(db_err,errMsg);
	}
	putCache((char*)buffer, idx);
	return dbErr(db_ok);
}

dbError
 recServer::putRec(const uint32 idx,void *buffer)
{
	if (!isOpen) return dbErr(db_notopen);

	// Save to the file and put it in the cache

	uint32 ret = lseek(fd,idx2offset(idx),SEEK_SET);
	if (ret!=idx2offset(idx)) {
		dbString errMsg("Couldn't seek to put record errno=");
		errMsg += intTodbString(errno);
		return dbErr(db_corrupt,errMsg);
	}
	ret = write(fd,(const char*)buffer,(size_t)header.recLength);
	if (ret!=header.recLength){
		dbString errMsg("Couldn't write record errno=");
		errMsg += intTodbString(errno);
		return dbErr(db_corrupt,errMsg);
	}
	putCache((char*)buffer, idx);
	return dbErr(db_ok);
}

// Open up a new spot in the recServer

#ifdef _MSC_VER
// disable 'Conditional is constant' warning
#pragma warning(disable:4127)
#endif

dbError recServer::newRec(uint32 &newIdx)
{
	if (!isOpen) return dbErr(db_notopen);

	if (flushed)
		if (!refresh())
			return dbErr(db_err);

	uint32 i;
	uint32 currNumBlocks = header.totalRecs >> bitPoolBits;

	// If the db gets over 80% full, extend it.

	if (header.recsUsed > (uint32)(0.8*header.totalRecs)) {
		// time to build a bigger house.
		// Extend the database by 25% of its current size.

#ifdef RSERV_DEBUG
		cout << "Expanding database" << endl;
#endif
		uint32 newNumBlocks = (uint32)(currNumBlocks * 1.25);
		if (newNumBlocks==currNumBlocks) newNumBlocks++;

		// Create a whole lot of zero bit blocks.
		// record where the end of the file is, since
		// that's a good place to continue insertion after extending
		// the file.
		currentPool = currNumBlocks;

		bitPool dummy;
		for(i=0;i<(newNumBlocks-currNumBlocks);i++) {
#ifdef RSERV_DEBUG
			cout << "Adding block" << endl;
#endif
			putBitPool(currNumBlocks+i,dummy);
		}
		header.totalRecs+=i*bitPoolRecs;
		// put it back on disk like a good boy - or we could
		// wait for the inevitable update below. nah.
		if (!writeHeader(header)) return dbErr(db_err);

		// we know that the pool at the end of the file is
		// empty, so don't fetch it, just use an empty one.
		indelPool.zero();
	}
	// Ok, there is enough space now.

	do { // Loop till we find an empty bitPool.
		// Is there enough space on the current bitpool.
		if (indelPool.bitsUsed<bitPoolRecs) {
			// Ok, this sucker is not full yet.

			// First find the byte where the zero bit is living.
			for(i=0;indelPool.map[i]==255;i++);
			assert(i<(uint32)bitPoolRecs);

			unsigned char t=indelPool.map[i],mask = 0x80;

			// convert x to an index offset.
			uint32 x=i << 3;
			while(t&mask) { mask >>= 1; x++; };

			t |= mask; // it was clear to start with, so set it.

			indelPool.map[i] = t; // put it back into the bitPool.

			// x is the index of the record we want into the block.

			indelPool.bitsUsed++;
			putBitPool(currentPool,indelPool);

			header.recsUsed++;
			if (!writeHeader(header)) return dbErr(db_err);

			// set the variable they passed in.
			newIdx = x + (currentPool << bitPoolBits);
#ifdef RSERV_DEBUG
			cout << "+++++ allocating bucket (" << fd << "," << newIdx
				<< ")" << endl;
#endif
			return dbErr(db_ok);
		}
		// Ok, this bitPool was full, so move to the next one.
		currentPool++;
		if (currentPool == currNumBlocks) currentPool = 0;
		if (!getBitPool(currentPool,indelPool)) return dbErr(db_err);
#ifdef RSERV_DEBUG
		cout << "Moving to bitpool #" << currentPool << endl;
#endif
	} while (1);
}

// Delete a record from the recServer

dbError recServer::delRec(const uint32 idx)
{
#ifdef RSERV_DEBUG
	cout << "+++++ attempting delete of (" << fd << "," << idx << ")" << endl;
#endif
	if (!isOpen) return dbErr(db_notopen);

	if (flushed)
		if (!refresh())
			return dbErr(db_err);

	// First locate the appropriate bitPool, then the appropriate
	// bit within it, then zero it, update the number of records used
	// and update the bitPool and header.

#ifdef _MSC_VER
#pragma warning(disable:4305 4309)
#endif

	static unsigned char bitTable[8] = {128,64,32,16,8,4,2,1};
	currentPool = idx >> bitPoolBits;

	// Fetch that bit pool;
	if (!getBitPool(currentPool,indelPool)) return dbErr(db_err);

	// Find out which bit number to delete.

	// We can use the (size_t) cast here b/c we hope that
	// the mask will be sufficient to ensure that the result
	// will fit.
	size_t whichBit = (size_t)(idx & ((1<<bitPoolBits) -1));

	// Find out which byte that occurs in.
	size_t whichByte = whichBit >> 3;

	// And which bit within the byte.
	whichBit &=7;

	if (!(indelPool.map[whichByte] & bitTable[whichBit])) {
		// Blow the whistle - deleting a non-existant record.
		return dbErr(db_nfound);
	}

	// Zero that bit
	indelPool.map[whichByte] ^= bitTable[whichBit];
	indelPool.bitsUsed--;
	header.recsUsed--;

	if (!writeHeader(header)) return dbErr(db_err);
	if (!putBitPool(currentPool,indelPool)) return dbErr(db_err);
#ifdef RSERV_DEBUG
	cout << "+++++ succeeded " << endl;
#endif
	return dbErr(db_ok);
}

// Read in the header of the record storage file.

bool recServer::readHeader(rsHeader &h)
{
	// Seek to the right part of the file.
	if (lseek(fd, headerOffset,SEEK_SET)!=(SEEK_RETURN_T) headerOffset) return false;

	// Now read in the header structure.
	if (read(fd,(char*)&h,sizeof h)!=(sizeof h)) {
		// Eeek - can't read in header.
		return false;
	}
	if (header.totalRecs < header.recsUsed) {
		// This *really* bad - flag corruption
		dbErr(db_corrupt,"more used records than total number of records");
		return false;
	}
	return true;
}

// Write out the header

bool recServer::writeHeader(rsHeader &h)
{
	// Seek to the right part of the file.
	if (lseek(fd, headerOffset,SEEK_SET)!=(SEEK_RETURN_T) headerOffset) return false;

	// Now write the header structure.
	if (write(fd,(const char*)&h,sizeof h)!=(sizeof h)) {
		// Eeek - can't write the header.
		return false;
	}
	return true;
}

// take an index and calculate where to look for it
// in the file - need to allow for the regular insertion
// of free bitpools.

uint32 recServer::idx2offset(const uint32 idx) const
{
	// every 2^bitPoolBits records, there is a bit pool of size
	// 2^(bitPoolBits / 8) to record which buckets are free in the
	// next partition.

	uint32 whichBlock = idx >> bitPoolBits;
	uint32 whichWithinBlock = idx & (bitPoolRecs - 1);

	return (whichBlock * blockLength) + whichWithinBlock * header.recLength
		+ dataOffset+sizeof(bitPool);
}

// Performs any initialization common to all constructors.

void
recServer::commonInit(void)
{
	locked = false;
	bitPoolBits = 12; // magic number. 4096 records between bitPools
	bitPoolRecs = (short)(1 << bitPoolBits);

	// The number of bytes in the bitPool is the number of bits
	// needed to map the following block of records, plus a short
	// to say how many are used in this block.

	bitPoolBytes = sizeof(bitPool);

	// currentPool is used to keep track of where we are inserting/
	// looking for spare buckets at the moment. It is the number of
	// the bitPool - insertions/ deletions occur from this block
	// until an operation can't be performed, and then we rotate
	// through the file until we find a suitable pool and the stop
	// there. If there is no more space in the file, then it gets
	// extended.
	currentPool = 0;
	flushed = false;

	dbErr(db_ok);
}

// fetch a particular bitpool.

bool
recServer::getBitPool(const uint32 idx, bitPool &bp)
{
	if (lseek(fd, bpIdx2Offset(idx), SEEK_SET) != (SEEK_RETURN_T) bpIdx2Offset(idx)) {
		return false;
	}
	int n = read(fd, (char*)&bp, sizeof bp);
	int sz = (sizeof bp);
	if ( n != sz) 
		return false;
	return true;
}

// Write back the bitpool.

bool
recServer::putBitPool(const uint32 idx, bitPool &bp)
{
	if (lseek(fd, bpIdx2Offset(idx), SEEK_SET) != (SEEK_RETURN_T) bpIdx2Offset(idx)) {
		return false;
	}
	if (write(fd, (const char*)&bp, sizeof bp) != (sizeof bp)) return false;
	return true;
}

uint32
recServer::bpIdx2Offset(const uint32 idx) const
{
	return dataOffset + idx * blockLength;
}

bool
recServer::readData(const uint32 offset, void *theData, const uint32 length)
{
	if (lseek(fd, offset, SEEK_SET) != (SEEK_RETURN_T) offset) return false;
	if (read(fd, (char*)theData, (size_t)length) != (signed)length) return false;
	return true;
}

bool
recServer::writeData(const uint32 offset, void *theData, const uint32 length)
{
	if (lseek(fd, offset, SEEK_SET) != (SEEK_RETURN_T) offset) return false;
	if (write(fd, (const char*)theData, (size_t)length) != (signed)length) {
		return false;
	}
	return true;
}

dbError
recServer::lock(const bool lockit, const bool block)
{
	dbError res;
	int cmd;
#if !defined(__BORLANDC__)
	int param;
#if defined(_MSC_VER)

	if (lockit) {
		if (block) {
		  cmd = _LK_LOCK;
		}
		else {
		  cmd = _LK_NBLCK;
		}
	}
	else {
		cmd = _LK_UNLCK;
	}
	param = 1;

#else
	struct flock flk;

	if (lockit)
		flk.l_type = F_WRLCK;
	else
		flk.l_type = F_UNLCK;

	flk.l_whence=SEEK_SET;
	flk.l_start=0;
	flk.l_len=1;

	if (block)
		cmd = F_SETLKW;
	else
		cmd = F_SETLK;

	// if (fcntl(fd, cmd, (uint32)&flk) == 0)
	param = (long)&flk;
#endif

	if (LOCK(fd, cmd, param) == 0)
	{
		res = db_ok;
		locked = lockit;
	}
	else switch(errno)
	{
		case EACCES:
			res = db_locked;
			break;
		default:
			res = db_err;
	}
	return dbErr(res);
#else
	return dbErr(db_unimp);
#endif

}

///////////////////////////////////////////////////////////////////////////
// Revision History:
//
// $Log: rserv.cpp,v $
// Revision 1.4  2001/04/23 21:49:12  vlabella
// Tweaks for Cygwin compatability
//
// Revision 1.3  2001/04/20 10:15:01  georgetasker
// Cygwin compilation fixes, compilation speed enhancements
// Code modified to get rid of some compiler warnings
//
// Revision 1.2  2001/04/19 21:12:54  georgetasker
// cygwin compilation fixes
//
// Revision 1.1  2001/04/04 12:05:49  aleksandras
// forgotten diamond .cpps
//
// Revision 1.46  1996/08/20  13:25:23  daz
// Changes by Alex Zeltser to support NT using MSVC.
//
// Revision 1.45  1996/08/06  10:46:07  daz
// Changes to incorporate the 'byte' type and the Windows NT patches.
//
// Revision 1.44  1996/03/05  22:04:29  dmb
// removed ^M.
//
// Revision 1.43  1996/03/05  21:42:35  dmb
// Preliminary modifications to get Dos/Windows 0.4 portability.
//
// Revision 1.42  1996/02/01  22:34:34  dmb
// long->int32, some use of dbStrings
//
// Revision 1.41  1994/12/22  03:57:40  kevinl
// read needed char* casts, write needed const char*.
// fcntl expects us to cast a struct to a int32! WOW!
//
// Revision 1.40  1994/12/12  03:50:40  kevinl
// stdlib.h
//
// Revision 1.39  1994/09/07  08:00:48  kevinl
// Removed locking for DOS
//
// Revision 1.38  1994/07/04  12:17:30  kevinl
// Fixed up open calls to remove conditional compilation and use autoconf
//
// Revision 1.37  1994/05/15  04:58:56  kevinl
// Fixed locking. Now should work over NFS properly and on more platforms.
// Added refresh of .db information.
//
// Revision 1.36  1994/04/29  06:37:47  darrenp
// Added sys/file.h include - which is needed for the file locking stuff
// on many machines.
//
// Revision 1.35  1994/03/17  04:34:45  kevinl
// Added lock
//
// Revision 1.34  1994/02/24  06:35:30  darrenp
// DOS/Windows modifications.
//
// Revision 1.33  1994/02/03  06:01:42  kevinl
// Moved include files to diamond/ and fixed HAVE_MALLOC_H
//
// Revision 1.32  1993/12/23  22:58:48  kevinl
// dbErr uses dbString, autoconf stuff, moved logs, added const and size_t
//
// Revision 1.31  1993/11/09  04:13:39  kevinl
// Fixed malloc include
// moved logs
//
// Revision 1.30  1993/07/19  11:58:50  kevinl
// Added a cast for bitPoolRecs
//
// Revision 1.29  1993/06/23  05:21:22  kevinl
// Mallocs are now in angular brackets
//
// Revision 1.28  1993/05/26  01:01:39  kevinl
// MALLOC_H_MISSING
//
// Revision 1.27  1993/05/25  12:59:27  kevinl
// Fixed #elif
//
// Revision 1.26  1993/05/06  04:01:10  kevinl
// SASC define for malloc.h, unistd.h is now stdio.h and BINARY mode opens
//
// Revision 1.25  1993/05/03  23:35:22  kevinl
// theData not data
//
// Revision 1.24  1993/05/01  14:43:17  kevinl
// Got rid of ints
//
// Revision 1.23  1993/04/28  14:45:52  darrenp
// Tidied up debuggin info.
//
// Revision 1.22  1993/04/28  14:42:40  darrenp
// Made delRec more paranoid - can now return db_nfound
//
// Revision 1.21  1993/04/27  06:59:47  kevinl
// Comments
//
// Revision 1.20  1993/04/15  04:21:52  kevinl
// Moved malloc.h
//
// Revision 1.19  1993/04/09  03:11:03  darrenp
// fixed a bug in db extension which made it break after 47,509 additions.
//
// Revision 1.18  1993/04/05  07:50:39  kevinl
// Put new records retrieved from file into cache
//
// Revision 1.17  1993/04/05  01:29:01  kevinl
// Rmeoved midnless debug ouput
//
// Revision 1.16  1993/04/04  23:59:57  kevinl
// Added cache support
//
// Revision 1.15  1993/03/29  08:20:09  darrenp
// Added new malloc library support
//
// Revision 1.14  1993/03/28  11:51:24  kevinl
// Modified for dbErr
//
// Revision 1.13  1993/03/28  19:08:35  davison
// Changed references to pError to dbErr().
//
// Revision 1.12  1993/03/26  06:16:38  darrenp
// standardized error codes.
//
// Revision 1.11  1993/03/24  06:22:09  kevinl
// Added better error handling code
//
// Revision 1.10  1993/03/20  14:14:00  kevinl
// Check return code of putBitPull in createDb
//
// Revision 1.9  1993/03/15  01:06:37  darrenp
// delRec added
//
///////////////////////////////////////////////////////////////////////////
