/*	$Id$
 *	Author:	Roger A. Cole
 *	Date:	03-05-90
 *
 *	Experimental Physics and Industrial Control System (EPICS)
 *
 *	Copyright 1991-92, the Regents of the University of California,
 *	and the University of Chicago Board of Governors.
 *
 *	This software was produced under  U.S. Government contracts:
 *	(W-7405-ENG-36) at the Los Alamos National Laboratory,
 *	and (W-31-109-ENG-38) at Argonne National Laboratory.
 *
 *	Initial development by:
 *		The Controls and Automation Group (AT-8)
 *		Ground Test Accelerator
 *		Accelerator Technology Division
 *		Los Alamos National Laboratory
 *
 *	Co-developed with
 *		The Controls and Computing Group
 *		Accelerator Systems Division
 *		Advanced Photon Source
 *		Argonne National Laboratory
 *
 * Modification Log:
 * -----------------
 *  .01 03-05-90 rac	initial version
 *  .02 07-30-91 rac	installed in SCCS
 *  .03 09-14-92 rac	discontinue using special malloc routines
 *  .04 10-29-92 rac	under Unix, cope with write getting EINTR status
 *
 * make options
 *	-DvxWorks	makes a version for VxWorks
 *	-DNDEBUG	don't compile assert() checking
 *      -DDEBUG         compile various debug code
 */
/*+/mod***********************************************************************
* TITLE	bfSubr.c - 'block file' subroutines
*
* DESCRIPTION
*    These subroutines implement a fixed block size file system based on
*    the usual byte-oriented file system available to C.  The operations
*    used by these routines are:
*    o  open() with O_RDONLY, O_RDWR, and O_CREAT as options.  The O_EXCL
*	option found in some operating systems isn't used.  Instead, these
*	routines create a "fileName.lock" file to enforce a single writer.
*    o  lseek() with L_SET as the option used.
*    o  read(), write(), and close()
*
*    On disk, the file system is a file containing fixed size blocks.  These
*    routines maintain a singly-threaded list of 'free' blocks which are
*    available for use.  User routines built on top of these routines
*    must maintain singly-threaded lists of 'in use' blocks.
*
*    These routines maintain some information in a file descriptor in memory.
*    This information isn't written to the disk until the file is closed.
*    Users of these routines must take particular care to ensure that
*    bfClose() is called for all open files.
*
*    Since information about the file is kept in the file descriptor, these
*    routines aren't reentrant with respect to a single file.  Several file
*    descriptors can be used simultaneously, one per thread, without
*    interference.
*
*    Although these routines do a reasonable amount of error checking, the
*    user must bear a significant amount of responsibility in abiding by
*    the rules.  Some cautions for the user include:
*    o  don't use pointers to file descriptors after bfClose() is called
*    o  don't exceed the block size for the file.  BF_MAX_BLOCK_SIZE is the
*       largest a block will ever be, but BfBlockSize(pDesc) returns the
*       actual block size for a particular file.
*
*    Users are strongly encouraged to use the BfXxx() macros when dealing
*    with block0 or the file descriptor.
*
* INCLUDE FILES
*    In general, use of the block file routines requires using several
*    include files.  The set used differs between SunOS and VxWorks:
*
*		   SunOs	   VxWorks
*
*		<bfDefs.h>	<bfDefs.h>
*		<stdio.h>	<vxWorks.h>
*		<sys/file.h>	<ioLib.h>
*				<stdioLib.h>
*
* BUGS
* o	routines aren't reentrant with respect to a single file
* o	some operations require an operating system file I/O call when, with
*	appropriate enhancements, such I/O could be avoided, or at least
*	deferred
* o	a list of open block files should be kept for a task, and an exit
*	handler should be established for a task, so that cleanup is assured
* o	the issue of blocking signals during critical operations should be
*	addressed
* o	error handling is rudimentary: there are no individualized error
*	codes; most error messages don't include file name; error messages
*	are sent to stdout; etc.
* o	these routines use blocking I/O.  Under SunOS, non-blocking I/O
*	might be better.
*	
*-***************************************************************************/


/*----------------------------------------------------------------------------
*    synopsis summaries
*	< indicates input arg to function
*	> indicates output arg from function
*	<> indicates arg both input to and output from function
*
*	except as noted, 'int' functions return OK or ERROR
*	blockNum must be 'BF_BLKNUM'
*
*     char *bfRingFgets(>pBuf,      <bufDim,  <>pBfDesc			)
*						returns pBuf or NULL
*       int bfRingFputs(<pBuf,     <>pBfDesc				)
*						returns last character or EOF
*       int BfRingGetc(<>pDesc						)
*						returns character or EOF
*       int BfRingPutc( <c,        <>pBfDesc				)
*						returns character or EOF
*
*BF_BLKNUM bfAcquire( <>pBfDesc,  <>pBfBlock				)
*		returns blockNum or ERROR
*	int bfClose(   <>ppBfDesc					)
*  BF_DESC *bfCreate(	<name,	    <type, <nblk, <blkSize, <flags, <maxNblk )
*	int bfDumpChar(	<pBfDesc,   <blockNum				)
*	int bfDumpHex(	<pBfDesc,   <blockNum				)
*	int bfInfo(	<name						)
*  BF_DESC *bfOpen(	<name,	    <mode, <type, <blkSize		)
*	int bfRead(	<pBfDesc,  <>pBfBlock, <blockNum		)
*	int bfRelease( <>pBfDesc,  <>pBfBlock, <blockNum		)
*	int bfWrite(	<pBfDesc,  <>pBfBlock, <blockNum		)
*---------------------------------------------------------------------------*/
#define BF_TEXT_GLBLSOURCE	/* must precede incl bfDefs.h */
#include <genDefs.h>
#include <bfDefs.h>
#include <ctype.h>

#ifdef vxWorks
#   include <vxWorks.h>
#   include <ioLib.h>
#   include <stdioLib.h>
    typedef long off_t;			/* for lseek() */
#else
#   include <errno.h>
#   include <stdio.h>
#   include <sys/types.h>
#   include <sys/file.h>
#endif

#ifdef LINT
    char	*glBfTypeKw[];

    main()
    {
	char	*pBuf;
	BF_DESC	*pDesc;
	BF_BLOCK *pBlock;

	exit(1);

	pBuf = NULL;
	pDesc = NULL;
	pBlock = NULL;

	(void) bfRingFgets(pBuf, 0, pDesc);
	(void) bfRingFputs(pBuf, pDesc);
	(void) bfClose(&pDesc);
	pDesc = bfCreate(" ", 0, (BF_BLKNUM)0, 0, (long)0, (BF_BLKNUM)0);
	(void) bfInfo(" ");
	(void) bfRelease(pDesc, pBlock, (BF_BLKNUM)0);
    }
#endif

BF_DESC *bfMallocDesc()
{  return (BF_DESC *)malloc(sizeof(BF_DESC));  }
void bfFreeDesc(ptr)
BF_DESC *ptr;
{  free(ptr);  }

BF_BLOCK0 *bfMallocBlock0()
{  return (BF_BLOCK0 *)malloc(sizeof(BF_BLOCK0));  }
void bfFreeBlock0(ptr)
BF_BLOCK0 *ptr;
{  free(ptr);  }


/*+/subr**********************************************************************
* NAME	bfRingFgets - get a line from a ring file
*
* DESCRIPTION
*	Read the next line, including the newline, from the ring file into
*	the caller's buffer.  The last character placed in the caller's buffer
*	is always followed by '\0'.
*
*	At most bufDim-1 characters are transferred; if no newline was
*	encountered, then the remaining characters will be read with the
*	next call.
*
*	Due to the nature of ring files, the first call to bfRingFgets()
*	may encounter a partial line.  If this occurs, the partial line
*	is discarded and the first full line is returned.
*
* RETURNS
*	pBuf, or
*	NULL if an error occurs or EOF is encountered
*
*-*/
char *
bfRingFgets(pBuf, bufDim, pDesc)
char	*pBuf;		/* buffer to receive up to 'bufDim-1' characters */
int	bufDim;		/* dimension of buffer */
BF_DESC *pDesc;		/* block file descriptor */
{
    int		c;		/* character from file */
    char	*pBuf1;		/* temporary buffer pointer */

    pBuf1 = pBuf;
    while (--bufDim > 0  &&  (c = BfRingGetc(pDesc)) != EOF) {
	if ((*pBuf1++ = c) == '\n')
	    break;
    }
    *pBuf1 = '\0';
    return ((c == EOF  &&  pBuf1 == pBuf) ? NULL : pBuf);
}

/*+/subr**********************************************************************
* NAME	bfRingFillRbufAndGetc - get next block and return a character
*
* DESCRIPTION
*	Reads the next readable block from the ring file and returns the
*	first readable character from the block.  (If the present block, if
*	any, contains unread characters, then those characters will be
*	skipped.)
*
*	Due to the nature of ring files, the first call to bfRingFillRbuf()
*	may encounter a partial line.  If this occurs, the partial line
*	is discarded and the character is obtained from the first full line.
*
*	The macro BfRingGetc(pDesc) is the recommended method for reading
*	a ring file a character at a time.  That macro calls this routine
*	as necessary.
*
*	The routine bfRingFgets() is available for reading a ring file a line
*	at a time.
*
* RETURNS
*	character, or
*	EOF if an error occurs or EOF is encountered
*
*-*/
int
bfRingFillRbufAndGetc(pDesc)
BF_DESC *pDesc;		/* IO block file descriptor for ring file */
{
    int		c;		/* character to return to caller */
    int	stat;

/*----------------------------------------------------------------------------
*    first, decide which block to read for more data.  If this is the first
*    time a read has been done, use the ringHead block; otherwise, use the
*    flink from the present block (if the present block is ringTail, then
*    there is no next block).
*---------------------------------------------------------------------------*/
    if (pDesc->readCount < -9)
	pDesc->readBlock = BfB0RingHead(pDesc);
    else {
	if (pDesc->readBlock == BfB0RingTail(pDesc))
	    return EOF;
	else
	    pDesc->readBlock = pDesc->pReadBuf->flink;
    }
/*----------------------------------------------------------------------------
*    if the block number to read is zero, then there is nothing to do but
*    return EOF.  If the block number isn't zero, read the block; otherwise,
*    return EOF.  Once the block has been read, check lastByte; if it indicates
*    no data, return EOF.
*
*    When the first read is to be done, a buffer is obtained from malloc,
*    with the pointer stored in the descriptor.  This buffer will be free'd
*    when the bfClose() is called.
*
*---------------------------------------------------------------------------*/
    if (pDesc->readBlock > 0) {
	if (pDesc->pReadBuf == NULL) {
	    if ((pDesc->pReadBuf =
		    (BF_BLOCK *)malloc((unsigned)BfB0BlockSize(pDesc))) ==
								NULL) {
		(void)fprintf(stderr,
				"bfRingFillRbufAndGetc: can't get memory\n");
		return EOF;
	    }
	}
	stat = bfRead(pDesc, pDesc->pReadBuf, pDesc->readBlock);
	if (stat != OK)
	    c = EOF;
	else {
	    if (pDesc->pReadBuf->lastByte < BF_BLOCK_DATA)
		c = EOF;
	    else {
/*----------------------------------------------------------------------------
*    set the read pointer and readCount.  If this is the first read for the
*    file, then start reading at firstByte; succeeding blocks start reading
*    at BF_BLOCK_DATA, in order to read any overflow bytes from previous
*    blocks.  (If the block for the first read for the file contains only
*    overflow bytes, then EOF is returned.)
*
*    If the file isn't organized by lines, then reading always starts at
*    BF_BLOCK_DATA.
*
*---------------------------------------------------------------------------*/
		int i=0;

		if (pDesc->readCount < -9) {	/* first read for file */
		    i = pDesc->pReadBuf->firstByte;
		    if (i == 0)			/* file not line-oriented */
			i = BF_BLOCK_DATA;
		}
		else
		    i = BF_BLOCK_DATA;

		if (i < BF_BLOCK_DATA) {	/* only overflow bytes */
		    pDesc->pRead = NULL;
		    pDesc->readCount = 0;
		    c = EOF;
		}
		else {
		    pDesc->pRead = (char *)pDesc->pReadBuf + i - 1;
		    pDesc->readCount = pDesc->pReadBuf->lastByte - i + 1;

		    c = (int)*(char *)(++pDesc->pRead);
		    pDesc->readCount--;
		}
	    }
	}
    }
    else
	c = EOF;

    return c;
}

/*+/subr**********************************************************************
* NAME	bfRingFputs - put a line into a ring file
*
* DESCRIPTION
*	Write the line, including the newline, into the ring file.  The
*	'\0' at the end of the caller's line isn't written into the file.
*
*	If this is the first line written to a block, then firstByte is
*	set to point to this line.
*
* RETURNS
*	last character scanned from 'line' (typically '\0'), or
*	EOF, if any error occurs
*
*-*/
int
bfRingFputs(line, pDesc)
char	*line;		/* I line to write */
BF_DESC	*pDesc;		/* IO pointer to descriptor */
{
    if (*line != '\0') {
	if (BfRingPutc(*line, pDesc) == EOF)
	    return EOF;
	if (pDesc->pWriteBuf->firstByte == 0) {
	    pDesc->pWriteBuf->firstByte = pDesc->pWrite -
						(char *)pDesc->pWriteBuf;
	}
    }
    else
	return *line;

    while (*(++line) != '\0') {
	if (BfRingPutc(*line, pDesc) == EOF)
	    return EOF;
    }
    return *line;
}

/*+/subr**********************************************************************
* NAME	bfRingFlushWbufAndPutc - flush write buffer and write a character
*
* DESCRIPTION
*	Write a block to the ring file, get the next block for writing, and
*	put the character into the block.  The file descriptor must have
*	a pointer to the memory buffer for the block at entry (except
*	immediately after bfOpen() ); at exit, a pointer to the memory buffer
*	for the new block has been placed in the file descriptor.
*
*	For the first write following bfOpen(), this routine obtains a
*	memory buffer for the block and places the pointer into the descriptor.
*	In this case, there is no buffer to flush.  Succeeding calls to this
*	routine will function as described above.
*
*	When this routine is called the first time following bfOpen(), it
*	attempts to begin writing in the ringTail block, if there is one.
*	When additional blocks are needed for writing, attempts are made to
*	acquire them in the following order:
*	o   try to get a block from the free block list
*	o   try to get a block by expanding the file if it is BF_FLAG_GROW
*	o   follow the current block's flink, if it isn't zero
*	o   if the current block's flink is zero, then grab the ringHead
*	    block for writing and move ringHead forward to its flink
*
*	The macro BfRingFputc(c, pDesc) is the recommended method for
*	writing a ring file a character at a time.  That macro calls this
*	routine as necessary.
*
*	The routine bfRingFputs() is available for writing a ring file
*	a line at a time.
*
* RETURNS
*	character written, or
*	EOF if an error occurs
*
* BUGS
* o	does a malloc/free for every call
*
* EXAMPLE
*
*-*/
int
bfRingFlushWbufAndPutc(c, pDesc)
char	c;		/* I character to write */
BF_DESC *pDesc;		/* IO pointer to ring file descriptor */
{
    int		stat;		/* status return from calls */
    BF_BLKNUM	flink;		/* link to next write block */
    BF_BLOCK   *pBuf;		/* next write block */

    if ((pBuf = (BF_BLOCK *)malloc((unsigned)BfB0BlockSize(pDesc))) == NULL) {
	(void)fprintf(stderr, "bfRingFlushWbufAndPutc: can't get memory\n");
	return EOF;
    }

    if (pDesc->writeBlock == 0) {
/*----------------------------------------------------------------------------
*	this is the first write for the file.  Get a block for writing by
*	trying first the ringTail block and then, if there isn't a ringTail,
*	acquiring a block.  If the ringTail block is full, make it look like
*	the 'old' block, so that a new block will be acquired by the normal
*	mechanism.
*---------------------------------------------------------------------------*/
	if ((flink = BfB0RingTail(pDesc)) > 0) {
	    stat = bfRead(pDesc, pBuf, flink);
	    if (stat != OK) {
		(void)free((char *)pBuf);
		return EOF;
	    }
	    if (pBuf->lastByte >= BfB0BlockSize(pDesc)-1) {
		pDesc->pWriteBuf = pBuf;	/* masquerade as old */
		pDesc->writeBlock = flink;
	    }
	}
	else {
	    flink = bfAcquire(pDesc, pBuf);
	    if (flink == ERROR) {
		(void)free((char *)pBuf);
		return EOF;
	    }
	}
    }
    if (pDesc->writeBlock != 0) {
/*----------------------------------------------------------------------------
*	the current block is full.  Obtain a new block.  Attempts are made
*	in the following order:
*	o  try to get a block from bfAcquire(), which will either remove
*	   a block from the free block list or which will expand the file.
*	   If neither succeeds, then:
*	o  follow the block's flink, if the flink isn't zero.  If the flink
*	   is zero, then:
*	o  use the block which is currently the ringHead
*	In the latter two cases, the block must be made to appear empty by
*	setting firstByte and lastByte appropriately.
*---------------------------------------------------------------------------*/
	int	readFlag;

	if ((flink = bfAcquire(pDesc, pBuf)) > 0)
	    readFlag = 0;
	else {
	    if ((flink = pDesc->pWriteBuf->flink) > 0)
		readFlag = 1;
	    else {
	        if ((flink = BfB0RingHead(pDesc)) > 0)
		    readFlag = 1;
		else {
		    free((char *)pBuf);
		    return EOF;		/* no new block available */
		}
	    }
	}
	if (readFlag) {		/* only have flink; get the actual block */
	    stat = bfRead(pDesc, pBuf, flink);
	    if (stat != OK) {
		free((char *)pBuf);
		return EOF;
	    }
	    pBuf->firstByte = 0;
	    pBuf->lastByte = BF_BLOCK_DATA - 1;
	}
    }
/*----------------------------------------------------------------------------
* 'flink' is the number of a new block; 'pBuf' has its memory address.  Wrap
*    up processing on the 'old' block and then install the new one.
*
*    first, install the proper flink into the current block.  If the new block
*    was obtained via bfAcquire(), then it is necessary to splice the new
*    block into the flink chain.
*
*    then, update the lastByte in the block to correspond with the pointer
*    in the descriptor.
*
*    finally, write the current block.  (In some cases there isn't a current
*    block to write; this will usually be the case on the first call after
*    a file has been opened.)  After writing the current block, free the
*    buffer.
*---------------------------------------------------------------------------*/
    if (pDesc->pWriteBuf != NULL) {
	if (pDesc->pWriteBuf->flink == flink)
	    ;					/* old block's flink OK */
	else if (pDesc->pWriteBuf->flink == 0)
	    pDesc->pWriteBuf->flink = flink;	/* link old to new */
	else {
	    pBuf->flink = pDesc->pWriteBuf->flink;	/* splice flinks */
	    pDesc->pWriteBuf->flink = flink;
	}

	pDesc->pWriteBuf->lastByte = pDesc->pWrite - (char *)pDesc->pWriteBuf;

	stat = bfWrite(pDesc, pDesc->pWriteBuf, pDesc->writeBlock);
	if (stat != OK) {
	    free((char *)pBuf);
	    return EOF;
	}
	free((char *)pDesc->pWriteBuf);
    }

/*----------------------------------------------------------------------------
* the current block has been dealt with.  Install the new one into the
*    descriptor.  (In the descriptor is the address of the memory that was
*    malloc'ed above.)
*---------------------------------------------------------------------------*/
    if (BfB0RingHead(pDesc) == 0)
	BfB0RingHead(pDesc) = flink;
    else if (BfB0RingHead(pDesc) == flink)
	BfB0RingHead(pDesc) = pBuf->flink;

    BfB0RingTail(pDesc) = flink;
    pDesc->writeBlock = flink;
    pDesc->pWriteBuf = pBuf;
    pDesc->writeCount = BfB0BlockSize(pDesc) - pBuf->lastByte - 1;
    pDesc->pWrite = (char *)pDesc->pWriteBuf + pBuf->lastByte;

/*----------------------------------------------------------------------------
* FINALLY (whew!), store the character that was sent in!
*---------------------------------------------------------------------------*/
    *(++pDesc->pWrite) = c;
    --pDesc->writeCount;

    return c;
}

/*+/subr**********************************************************************
* NAME	bfAcquire - acquire a block for use
*
* DESCRIPTION
*	Acquires a block for use by the caller, placing the block in the
*	caller's buffer.
*
*	bfAcquire() will acquire the block from the free block list, if any
*	free blocks are available.  If no free blocks are available, then
*	the file is extended, if the BF_FLAG_GROW flag is set and the maximum
*	size won't be violated.  Otherwise, no block is obtained.
*
*	The block number returned by bfAcquire() is the value which must
*	be used with bfRead(), bfWrite(), etc.
*
*	'firstByte' is set to 0 .  'lastByte' points one character before
*	the first available byte location.  'flink' is set to 0 .
*
*	The free block list on disk isn't updated by bfAcquire(), so it is
*	extremely important that bfClose() be called before exiting.
*
* RETURNS
*	block number or
*	ERROR (no error message is printed if reason for not being able to
*		acquire a block is either: no free block or can't grow file)
*
* BUGS
* o	should maintain an 'in memory' list of free blocks and replenish
*	the list as necessary.  This would avoid I/O delays on bfAcquire()
*	calls.
* o	should be able to get a free block without needing to do disk I/O
* o	multiple, simultaneous calls to this routine could result in giving
*	the same block to several callers.  The callers must presently
*	cooperate to prevent simultaneous calls.
*
* SEE ALSO
*	bfRelease()
*
* EXAMPLE
*	BF_DESC	*pDesc;		pointer to descriptor
*	BF_BLOCK *pBuf;		pointer to buffer containing block
*	BF_BLKNUM blockNum;	block number of acquired block
*
*	pDesc = bfOpen("myFile", O_RDWR, BF_TYPE_UNDEF);
*	pBuf = (BF_BLOCK *)malloc(BfB0BlockSize(pDesc));
*	blockNum = bfAcquire(pDesc, pBuf);
*	pBuf->firstByte = pBuf->lastByte + 1;
*	for (i=0; i<nBytes; i++)
*	    ((char *)pBuf)[++pBuf->lastByte] = myData[i];
*	stat = bfWrite(pDesc, pBuf, blockNum, 0);
*	stat = bfClose(pDesc);
*	free((char *)pBuf);
*
*-*/
BF_BLKNUM
bfAcquire(pDesc, pBuf)
BF_DESC	*pDesc;		/* IO pointer to descriptor */
BF_BLOCK  *pBuf;	/* IO buffer ptr; buf size is BfB0BlockSize(pDesc) */
{
    int		stat;		/* status from calls */
    int		blockNum;	/* number of acquired block */

    assert(pDesc != NULL);
    assert(pBuf != NULL);

    if (!BfWriteable(pDesc)) {
	(void)fprintf(stderr, "bfAcquire: file is O_RDONLY\n");
	return ERROR;
    }

    if (BfB0Flink(pDesc) == 0) {
/*----------------------------------------------------------------------------
* NO FREE BLOCK -- try to extend the file
*---------------------------------------------------------------------------*/
	if (  BfB0CurrentSize(pDesc) >= BfB0MaxSize(pDesc) ||
	      (BfB0Flags(pDesc) & BF_FLAG_GROW) == 0  ) {
	    (void)fprintf(stderr, "bfAcquire: file maxSize reached\n");
	    return ERROR;
	}
	else
	    blockNum = BfB0CurrentSize(pDesc)++;
    }
    else {
/*----------------------------------------------------------------------------
* USE A FREE BLOCK
*---------------------------------------------------------------------------*/
	off_t	offset;		/* offset into file, for lseek() */
	off_t	lseek();	/* lseek function */

	blockNum = BfB0Flink(pDesc);
	offset = blockNum * BfB0BlockSize(pDesc);
	stat = lseek((int)pDesc->fd, offset, L_SET);
	if (stat == ERROR) {
	    (void)fprintf(stderr, "bfAcquire: error on lseek for free block\n");
	    return ERROR;
	}
	stat = read((int)pDesc->fd, (char *)pBuf, (int)BfB0BlockSize(pDesc));
	if (stat != BfB0BlockSize(pDesc)) {
	    (void)fprintf(stderr, "bfAcquire: error reading free block\n");
	    return ERROR;
	}

/*----------------------------------------------------------------------------
*    move the free block's flink into block0 and decrement the free block count
*---------------------------------------------------------------------------*/
	BfB0Flink(pDesc) = pBuf->flink;
	BfB0CurrentFree(pDesc)--;
    }

    pBuf->flink = 0;
    pBuf->firstByte = 0;
    pBuf->lastByte = BF_BLOCK_DATA - 1;

    return blockNum;
}

/*+/subr**********************************************************************
* NAME	bfClose - close a block file
*
* DESCRIPTION
*	Close a file.  The actions taken are:
*	o  if pDesc->writeBlock > 0, write the block
*	o  if pDesc->pReadBuf != NULL, free the buffer
*	o  if pDesc->pWriteBuf != NULL, free the buffer
*	o  write block0
*	o  if the file was opened O_RDWR, delete the write lock file
*	o  free the descriptor and store null in the caller's pointer
*
* RETURNS
*	OK, or
*	ERROR
*
* BUGS
* o	writing the block and freeing the buffers are somewhat controversial.
*	If bfClose() free's the blocks, that may interfere with the caller's
*	memory management.  If the caller free's the buffers in an exit
*	handler, that may interfere with bfClose() writing the block.  There
*	is also a potential conflict between exit handlers.  The caller won't
*	know how large to make the buffer until after bfOpen() has been
*	called, and thus may be inclined to wait until after bfOpen() to
*	register the block with the caller's exit handler, which might result
*	in the caller's exit handler having precedence over one possibly
*	established by bfOpen(); if this scenario were acted out, the buffer
*	would be free'd before bfClose() got a chance to write it.
*
* SEE ALSO
*	bfOpen()
*
*-*/
int
bfClose(ppDesc)
BF_DESC	**ppDesc;	/* IO pointer to pointer to descriptor */
{
    int		stat;		/* status from calls */
    int		retStat=OK;	/* return status */
    off_t	offset;		/* offset into file, for lseek() */
    off_t	lseek();	/* lseek function */
    BF_DESC    *pDesc;		/* pointer to descripter */

    assert(*ppDesc != NULL);

    pDesc = *ppDesc;

/*----------------------------------------------------------------------------
*    first, see if a partial block needs to be written; write if needed.  If
*    read and/or write buffers are registered in descriptor, release them.
*---------------------------------------------------------------------------*/
    if (pDesc->writeBlock > 0) {
	pDesc->pWriteBuf->lastByte = pDesc->pWrite -
						(char *)pDesc->pWriteBuf;
	stat = bfWrite(pDesc, pDesc->pWriteBuf, pDesc->writeBlock);
	if (stat != OK) {
	    (void)fprintf(stderr, "bfClose: error writing partial block\n");
	    retStat = ERROR;
	}
    }
    if (pDesc->pWriteBuf != NULL)
	free((char *)pDesc->pWriteBuf);
    if (pDesc->pReadBuf != NULL)
	free((char *)pDesc->pReadBuf);

/*----------------------------------------------------------------------------
*    next, position file at beginning and write block0
*---------------------------------------------------------------------------*/
    if (BfWriteable(pDesc)) {
	offset = 0;
	stat = lseek((int)pDesc->fd, offset, L_SET);
	if (stat >= 0) {
	    stat = write((int)pDesc->fd, (char *)pDesc->pBlock0,
						(int)BfB0BlockSize(pDesc));
	    if (stat != BfB0BlockSize(pDesc)) {
		perror("bfClose: error writing block0");
		retStat = ERROR;
	    }
	}
	else {
	    perror("bfClose: error on lseek");
	    retStat = ERROR;
	}
    }

/*----------------------------------------------------------------------------
*    then close the file
*---------------------------------------------------------------------------*/
    stat = close((int)pDesc->fd);
    if (stat != 0)
	retStat = ERROR;

/*----------------------------------------------------------------------------
*    if the file wasn't read only, delete the lock file
*---------------------------------------------------------------------------*/
    if (BfWriteable(pDesc)) {
	char	lockName[GEN_FNAME_DIM+5];/* name of lock file for write acc.*/

	(void) sprintf(lockName, "%s.lock", pDesc->name);
	if ((stat = unlink(lockName)) != 0) {
	    perror("bfClose: error deleting lock file");
	    retStat = ERROR;
	}
    }

/*----------------------------------------------------------------------------
*    and, finally, free the memory for the descriptor and put NULL in the ptr
*---------------------------------------------------------------------------*/
    if (pDesc->pBlock0 != NULL)
	bfFreeBlock0(pDesc->pBlock0);
    if (pDesc != NULL)
	bfFreeDesc(pDesc);
    *ppDesc = NULL;

    return retStat;
}

/*+/subr**********************************************************************
* NAME	bfCreate - create and initialize a block file
*
* DESCRIPTION
*	Creates and initializes a block file.  If the file already exists,
*	nothing is done.
*
*	The file is created with the specified number of blocks, with all
*	except the first one on the free block list.  The specified type,
*	flags, block size, and maximum file size will be installed in block 0.
*
*	File mode is set to -rw-rw-r--.  Owner and group for the file are
*	set by the operating system.
*
* RETURNS
*	pointer to descriptor, or
*	NULL
*
* BUGS
* o	there is a potential race when creating the file.  This routine
*	checks to see if the file already exists and balks if it does.  But
*	the file could be created between the check and the create done here.
*	Under UNIX, O_CREAT | O_EXCL avoids the race, but VxWorks can't
*	handle O_EXCL.
* o	this routine creates a file by successive writes.  Some file system
*	protocols require creating an empty file of the desired size before
*	any writing is done.
*
* EXAMPLE
*	BF_DESC *pDesc;		pointer to descriptor
*
*	pDesc = bfCreate("myFile", BF_TYPE_RING, 12, 1000, BF_FLAG_GROW, 100);
*	BfB0RingHead(pDesc) = 0;
*	BfB0RingTail(pDesc) = 0;
*	stat = bfClose(pDesc)
*
*-*/
BF_DESC *
bfCreate(name, type, nBlocks, blockSize, fileFlags, maxBlocks)
char	*name;		/* I name of file */
int	type;		/* I file type, one of the BF_TYPE_xxx */
BF_BLKNUM nBlocks;	/* I number of blocks; must be < BF_MAX_BLOCKS */
unsigned blockSize;	/* I size of blocks; must be >= BF_MIN_BLOCK_SIZE
				and <= BF_MAX_BLOCK_SIZE */
long	fileFlags;	/* I flags, one of the BF_FLAG_xxx */
BF_BLKNUM maxBlocks;	/* I size limit in blocks; must be <= BF_MAX_BLOCKS */
{
    int		stat;		/* status from calls */
    int		retStat=OK;	/* return status */
    BF_DESC	*pDesc;		/* block file descriptor */
    int		i;		/* temp for loops */
    int		fd;		/* temp for file descriptor */

    assert(type >= (int)BF_TYPE_UNDEF && type < (int)BF_TYPE_PAST);
    assert(nBlocks >= 1 && nBlocks <= BF_MAX_BLOCKS);
    assert(blockSize >= BF_MIN_BLOCK_SIZE && blockSize <= BF_MAX_BLOCK_SIZE);
    assert(maxBlocks >= BF_MIN_BLOCKS && maxBlocks <= BF_MAX_BLOCKS);

    if ((fd = open(name, O_RDWR, 0664)) >= 0) {
	(void)close(fd);
	(void)fprintf(stderr, "bfCreate: can't create--file already exists\n");
	retStat = ERROR;
    }

    if (retStat == OK) {
	pDesc = bfOpen(name, O_RDWR | O_CREAT, type, (int)blockSize);
	if (pDesc == NULL) {
	    perror("bfCreate: error creating file");
	    retStat = ERROR;
	}
    }

    if (retStat == OK) {
/*----------------------------------------------------------------------------
*    set up block 0; special fields are the caller's responsibility
*---------------------------------------------------------------------------*/
	if (nBlocks > 1) {
	    BfB0Flink(pDesc) = 1;
	    BfB0CurrentSize(pDesc) = nBlocks;
	    BfB0CurrentFree(pDesc) = nBlocks - 1;
	}
	BfB0Flags(pDesc) = fileFlags;
	BfB0MaxSize(pDesc) = maxBlocks;

/*----------------------------------------------------------------------------
*    write out block 0.
*
*    write out the free block list.  Each block except the last has a flink
*    to the next; the last block has a flink of 0.
*
*    If an error occurs, the write process is aborted and an error message is
*    printed.
*---------------------------------------------------------------------------*/
	stat = write((int)pDesc->fd, (char *)pDesc->pBlock0, (int)blockSize);
	if (stat != blockSize) {
	    retStat = ERROR;
	    perror("bfCreate: error writing block0");
	}
	if (retStat == OK && nBlocks > 1) {
	    BF_BLOCK *pBuf;

	    pBuf = (BF_BLOCK *)malloc(blockSize);
	    if (pBuf == NULL) {
		retStat = ERROR;
		(void)fprintf(stderr,
			"bfCreate: can't get memory for writing free blocks\n");
	    }
	    else {
		pBuf->firstByte = 0;
		pBuf->lastByte = 0;
		for (i=1; i++<nBlocks; ) {
		    pBuf->flink = (i != nBlocks ? i : 0);
		    stat = write((int)pDesc->fd, (char *)pBuf, (int)blockSize);
		    if (stat != blockSize) {
			retStat = ERROR;
			perror("bfCreate: error writing free block list");
			break;
		    }
		free((char *)pBuf);
		}
	    }
	}
    }

    if (retStat == OK)
	return pDesc;
    else
	return NULL;
}

/*+/subr**********************************************************************
* NAME	bfDumpChar - dump, as text, contents of block from block file
*
* DESCRIPTION
*
* RETURNS
*
* BUGS
* o	text
*
* SEE ALSO
*
* EXAMPLE
*
*-*/
int
bfDumpChar(pBfDesc, blockNum)
BF_DESC	*pBfDesc;	/* I pointer to descriptor */
BF_BLKNUM blockNum;	/* I block number */
{
    int		stat;		/* status from calls */
    int		retStat=OK;	/* return status to caller */
    char	*pBuf;		/* buffer pointer */

    if ((pBuf = (char *)malloc((unsigned)BfB0BlockSize(pBfDesc))) == NULL) {
	(void)printf("bfDumpHex: can't malloc buffer\n");
	retStat = ERROR;
    }
    if (retStat == OK) {
	if ((stat = bfRead(pBfDesc, (BF_BLOCK *)pBuf, blockNum)) != OK) {
	    (void)printf("bfDumpHex: error reading block\n");
	    retStat = ERROR;
	}
    }
    if (retStat == OK) {
	char *pBuf1;
	char c;
	int i,j,last;

	(void)printf("block:%d flink:%d firstByte:%d lastByte:%d\n",                                blockNum,
		    ((BF_BLOCK *)pBuf)->flink,
		    ((BF_BLOCK *)pBuf)->firstByte,
		    ((BF_BLOCK *)pBuf)->lastByte);
	pBuf1 = pBuf;
	last = ((BF_BLOCK *)pBuf)->lastByte;
	for (i=0; i<=last; ) {
	    (void)printf("%04x \"", i);
	    for (j=0; j<64; j++) {
		(void)printf("%c",
			(isascii(c=pBuf1[i++])&&isprint(c))?c:'.');
		if (i>last)
		    break;
	    }
	    (void)printf("\"\n");
	}
    }
    if (pBuf != NULL)
	(void)free(pBuf);

    return retStat;
}

/*+/subr**********************************************************************
* NAME	bfDumpHex - dump, in hex, contents of block from block file
*
* DESCRIPTION
*
* RETURNS
*
* BUGS
* o	text
*
* SEE ALSO
*
* EXAMPLE
*
*-*/
int
bfDumpHex(pBfDesc, blockNum)
BF_DESC	*pBfDesc;	/* I pointer to descriptor */
BF_BLKNUM blockNum;	/* I block number */
{
    int		stat;		/* status from calls */
    int		retStat=OK;	/* return status to caller */
    char	*pBuf;		/* buffer pointer */

    if ((pBuf = (char *)malloc((unsigned)BfB0BlockSize(pBfDesc))) == NULL) {
	(void)printf("bfDumpHex: can't malloc buffer\n");
	retStat = ERROR;
    }
    if (retStat == OK) {
	if ((stat = bfRead(pBfDesc, (BF_BLOCK *)pBuf, blockNum)) != OK) {
	    (void)printf("bfDumpHex: error reading block\n");
	    retStat = ERROR;
	}
    }
    if (retStat == OK) {
	char *pBuf1;
	int i,j,last;

	(void)printf("block:%d flink:%d firstByte:%d lastByte:%d\n",                                blockNum,
		    ((BF_BLOCK *)pBuf)->flink,
		    ((BF_BLOCK *)pBuf)->firstByte,
		    ((BF_BLOCK *)pBuf)->lastByte);
	pBuf1 = pBuf;
	last = ((BF_BLOCK *)pBuf)->lastByte;
	for (i=0; i<=last; ) {
	    (void)printf("%04x", i);
	    for (j=0; j<8; j++) {
		(void)printf(" ");
		(void)printf("%02x", ((int)pBuf1[i++])&0xff);
		(void)printf("%02x", ((int)pBuf1[i++])&0xff);
		(void)printf("%02x", ((int)pBuf1[i++])&0xff);
		(void)printf("%02x", ((int)pBuf1[i++])&0xff);
		if (i>last)
		    break;
	    }
	    (void)printf("\n");
	}
    }
    if (pBuf != NULL)
	(void)free(pBuf);

    return retStat;
}

/*+/subr**********************************************************************
* NAME	bfInfo - print information from block0 of block file
*
* DESCRIPTION
*
* RETURNS
*
* BUGS
* o	text
*
* SEE ALSO
*
* EXAMPLE
*
*-*/
int
bfInfo(name)
char	*name;		/* I name of file */
{
    int		stat;		/* status from calls */
    int		retStat=OK;	/* return status */
    BF_DESC	*pDesc;		/* block file descriptor */
    long	blockSize;	/* size of blocks in this file */
    off_t	offset;		/* offset into file, for lseek() */
    off_t	lseek();	/* lseek function */
    int		fd;

    if ((pDesc = bfMallocDesc()) == NULL) {
	retStat = ERROR;
	(void)fprintf(stderr, "bfInfo: can't get memory for bfDesc\n");
    }
    if (retStat == OK) {
	if ((pDesc->pBlock0 = bfMallocBlock0()) == NULL) {
	    retStat = ERROR;
	    (void)fprintf(stderr, "bfInfo: can't get memory for block0\n");
	}
    }

    if (retStat == OK) {
	fd = open(name, O_RDWR, 0664);
	pDesc->fd = fd;
	if (pDesc->fd < 0) {
	    perror("bfInfo: error opening file");
	    retStat = ERROR;
	}
    }

/*----------------------------------------------------------------------------
*    first, read only the first few bytes of the record, mostly to find out
*    how big the blocks are.
*---------------------------------------------------------------------------*/
    if (retStat == OK) {
	stat = read((int)pDesc->fd, (char *)pDesc->pBlock0, BF_MIN_BLOCK_SIZE);
	if (stat != BF_MIN_BLOCK_SIZE) {
	    (void)fprintf(stderr, "bfInfo: error reading block0 preamble\n");
	    retStat = ERROR;
	}
	else {
	    blockSize = BfB0BlockSize(pDesc);
	    (void)printf("BLOCK 0: blockSize is %d\n", blockSize);
	}
    }

    if (retStat == OK) {
	offset = 0;
	stat = lseek((int)pDesc->fd, offset, L_SET);
	if (stat == ERROR) {
	    perror("bfInfo: error on lseek");
	    retStat = ERROR;
	}
	if (retStat == OK) {
	    stat = read((int)pDesc->fd, (char *)pDesc->pBlock0, (int)blockSize);
	    if (stat != blockSize) {
		(void)fprintf(stderr, "bfInfo: error reading block0\n");
		retStat = ERROR;
	    }
	    else {
		(void)printf("BLOCK 0: type, flink, fByte, lByte %s %d %d %d\n",
		    BfTypeToText(BfB0FileType(pDesc)),
		    BfB0Flink(pDesc), BfB0FirstByte(pDesc),
		    BfB0LastByte(pDesc));
		(void)printf("maxSize, currSize, currFree %d %d %d\n",
		    BfB0MaxSize(pDesc), BfB0CurrentSize(pDesc),
		    BfB0CurrentFree(pDesc));
		(void)printf("free block list: ");
		while (BfB0Flink(pDesc) != 0) {
		    (void)printf("%d ", BfB0Flink(pDesc));
		    offset = BfB0Flink(pDesc) * blockSize;
		    stat = lseek((int)pDesc->fd, offset, L_SET);
		    if (stat == ERROR) {
			perror("bfInfo: error on lseek");
			retStat = ERROR;
			break;
		    }
		    else {
			stat = read((int)pDesc->fd, (char *)pDesc->pBlock0,
								(int)blockSize);
			if (stat != blockSize) {
			    (void)fprintf(stderr,
					"bfInfo: error reading block%d\n",
							offset/blockSize);
			    break;
			}
		    }
		}
		(void)printf("\n");
	    }
	}
    }


    if (pDesc->fd >= 0)
	(void)close((int)pDesc->fd);
    if (pDesc->pBlock0 != NULL)
	bfFreeBlock0(pDesc->pBlock0);
    if (pDesc != NULL)
	bfFreeDesc(pDesc);

    return retStat;
}

/*+/subr**********************************************************************
* NAME	bfOpen - open or create a block file
*
* DESCRIPTION
*	Open a block file.  The actions taken are:
*	o  if the file type doesn't match the caller's type, return with NULL.
*	   (If the caller's type is BF_TYPE_UNDEF, no type check is made.)
*	o  if the file is being opened O_RDWR, a lock file is created.  (If
*	   the lock file already exists, the open isn't done.)  The lock file
*	   has the same 'name' as the file, but with the extension of .lock
*	   added.
*	o  a block of memory is obtained to hold a file descriptor and block0
*	o  block0 is read
*	o  the file descriptor is set up
*
*	This routine can also be used to create a block file, if the open
*	mode is (O_RDWR & O_CREAT).  In this case, the 'blkSize' argument
*	must have a meaningful value.  The actions taken are:
*	o  a lock file is created, as described above
*	o  the file is created, with flags of 0664 (rw-rw-r--)
*	o  a block of memory is obtained to hold a file descriptor and block0
*	o  an 'empty' block0 is created, with no free blocks and 0 for flags
*	o  the file descriptor is set up
*
* RETURNS
*	pointer to descriptor, or
*	NULL
*
* BUGS
* o	the flags used in creating the file with O_CREAT, and the flags
*	used in creating the lock file, may be system dependent
* o	this routine should probably grab a few of the free blocks and
*	store them into the descriptor, which should have a place to 'store'
*	them.
* o	there is a potential race when locking against multiple writers.
*	Another instance of bfOpen() could check for the existance of the
*	lock file at the same time this one does, both could find the lock
*	file non-existant, and both could 'create' it.
*
* SEE ALSO
*	bfClose(), bfCreate()
*
* EXAMPLE
*	BF_DESC *pDesc;		pointer to file descriptor
*
*	pDesc = bfOpen("myFile", O_RDONLY, BF_TYPE_UNDEF, 0);
*	if (pDesc == NULL) {
*	    printf("error opening: myFile\n");
*	    exit(1);
*	}
*
*-*/
BF_DESC *
bfOpen(name, mode, type, blkSize)
char	*name;		/* I name of file */
int	mode;		/* I mode for open; O_RDWR or O_RDONLY.  O_CREAT 
			can be specified in conjunction with O_RDWR */
int	type;		/* I desired type of file; one of BF_TYPE_xxx */
int	blkSize;	/* I blockSize for new file if mode includes
			O_CREAT; ignored if mode doesn't include O_CREAT */
{
    int		stat;		/* status from calls */
    BF_DESC	*pDesc;		/* block file descriptor */
    char	buffer[BF_MIN_BLOCK_SIZE];	/* 'starter' buffer */
    long	blockSize;	/* size of blocks in this file */
    int		fd;		/* temp for fd from open */
    long	flags;		/* flags for block file descriptor */
    off_t	offset;		/* offset into file, for lseek() */
    off_t	lseek();	/* lseek function */
    char	lockName[GEN_FNAME_DIM+5];/* name of lock file for write acc.*/

    assert(type >= (int)BF_TYPE_UNDEF && type < (int)BF_TYPE_PAST);

/*----------------------------------------------------------------------------
*    first, open the file in the desired mode; if O_RDWR, set the flag
*---------------------------------------------------------------------------*/
    if (strlen(name) >= GEN_FNAME_DIM) {
	(void)fprintf(stderr, "bfOpen: file name too long %s\n", name);
	return NULL;
    }

    if ((mode & O_CREAT) != 0)
	assert(blkSize >= BF_MIN_BLOCK_SIZE && blkSize <= BF_MAX_BLOCK_SIZE);

    if (mode == O_RDONLY)
	flags = 0;
    else if (mode == O_RDWR || mode == (O_RDWR | O_CREAT) ) {
	flags = BF_DESC_WRITE;
	(void) sprintf(lockName, "%s.lock", name);
	if ((fd = open(lockName, O_RDONLY)) >= 0) {
	    (void)close(fd);
	    (void)fprintf(stderr,
			"bfOpen: %s locked against write access\n", name);
	    return NULL;
	}
	else {
	    if ((fd = open(lockName, O_CREAT, 0664)) < 0) {
		perror("bfOpen: error creating write lock file");
		return NULL;
	    }
	    (void)close(fd);
	}
    }
    else {
	(void)fprintf(stderr, "bfOpen: illegal mode\n", name);
	return NULL;
    }
    if ((fd = open(name, mode, 0664)) < 0)
	goto errorExit;

/*----------------------------------------------------------------------------
*    now, find out how big blocks are so we'll know how big to make the buffer
*---------------------------------------------------------------------------*/
    if ((mode & O_CREAT) != 0)
	blockSize = blkSize;
    else {
/*----------------------------------------------------------------------------
*	read block0 from file
*---------------------------------------------------------------------------*/
	stat = read(fd, buffer, BF_MIN_BLOCK_SIZE);
	if (stat != BF_MIN_BLOCK_SIZE) {
	    perror("bfOpen: error reading block0 preamble\n");
	    (void)close(fd);
	    goto errorExit;
	}
	blockSize = ((BF_BLOCK0 *)buffer)->fileInfo.blockSize;
    }

/*----------------------------------------------------------------------------
*    finally, malloc for the descriptor and for block0
*    set up the descriptor
*---------------------------------------------------------------------------*/
    if ((pDesc = bfMallocDesc()) == NULL) {
	(void)fprintf(stderr, "bfOpen: can't get memory for descriptor\n");
	(void)close(fd);
	goto errorExit;
    }
    else {
	if ((pDesc->pBlock0 = bfMallocBlock0()) == NULL) {
	    (void)fprintf(stderr, "bfOpen: can't get memory for block0\n");
	    bfFreeDesc(pDesc);
	    (void)close(fd);
	    goto errorExit;
	}
    }
    pDesc->fd = fd;
    pDesc->flags = flags;
    pDesc->readCount = -10;
    pDesc->readBlock = 0;
    pDesc->pReadBuf = NULL;
    pDesc->writeCount = -10;
    pDesc->writeBlock = 0;
    pDesc->pWriteBuf = NULL;
    (void)strcpy(pDesc->name, name);	/* length already checked above */

/*----------------------------------------------------------------------------
*    now, get block0 into memory.  If we're using O_CREAT, then one must be
*    created; otherwise, read it from the file.
*---------------------------------------------------------------------------*/
    if ((mode & O_CREAT) != 0) {
/*----------------------------------------------------------------------------
*	set up block 0; special fields are the caller's responsibility
*---------------------------------------------------------------------------*/
	BfB0Flink(pDesc) = 0;
	BfB0FirstByte(pDesc) = BF_BLOCK_DATA;
	BfB0LastByte(pDesc) = blockSize - 1;
	BfB0FileType(pDesc) = type;
	BfB0BlockSize(pDesc) = blockSize;
	BfB0Flags(pDesc) = 0;
	BfB0MaxSize(pDesc) = 1;
	BfB0CurrentSize(pDesc) = 1;
	BfB0CurrentFree(pDesc) = 0;
    }
    else {
/*----------------------------------------------------------------------------
*	read block0 from file
*---------------------------------------------------------------------------*/
	offset = 0;
	stat = lseek((int)pDesc->fd, offset, L_SET);
	if (stat == ERROR) {
	    perror("bfOpen: error on lseek");
	    goto errorExit0;
	}
	stat = read((int)pDesc->fd, (char *)pDesc->pBlock0, (int)blockSize);
	if (stat != blockSize) {
	    perror("bfOpen: error reading block0\n");
	    goto errorExit0;
	}
    }

/*----------------------------------------------------------------------------
*    do additional checks
*---------------------------------------------------------------------------*/
    if (type != (int)BF_TYPE_UNDEF && type != BfB0FileType(pDesc)) {
	(void)fprintf(stderr, "bfOpen: file type mismatch for %s\n", name);
	goto errorExit0;
    }

    return pDesc;

errorExit0:
    (void)close(fd);
    bfFreeBlock0(pDesc->pBlock0);
    bfFreeDesc(pDesc);
errorExit:
    if ((flags & BF_DESC_WRITE) != 0) {
	if ((stat = unlink(lockName)) != 0)
	    perror("bfOpen: error deleting lock file");
    }
    return NULL;
}

/*+/subr**********************************************************************
* NAME	bfRead - read a block from a block file
*
* DESCRIPTION
*	Reads a block from the file into the caller's buffer.  (None of the
*	read-related fields in the descriptor are updated; these fields
*	must be set by the caller, if desired.)
*
*	For files of type BF_TYPE_RING, the macro BfRingGetc(pDesc) and the
*	function bfRingFgets(pBuf, bufDim, pDesc) are available as more
*	friendly tools for reading the data from blocks.
*
* RETURNS
*	OK, or
*	ERROR
*
* EXAMPLE
*	BF_DESC *pDesc;		file descriptor
*	BF_BLOCK *pBuf;		buffer pointer
*
*	pDesc = bfOpen("dataFile", O_RDONLY, BF_TYPE_UNDEF);
*	pBuf = (BF_BLOCK *)malloc(BfB0BlockSize(pDesc));
*	stat = bfRead(pDesc, pBuf, 1);
*	printf("contents of block 1:\n");
*	for (i=BF_BLOCK_DATA; i<pBuf->lastByte; i++)
*	    putc(((char *)pBuf)[i]);
*	stat = bfClose(pDesc);
*	free((char *)pBuf);
*
*-*/
int
bfRead(pDesc, pBuf, blockNum)
BF_DESC	*pDesc;		/* IO pointer to descriptor */
BF_BLOCK  *pBuf;	/* IO buffer pointer */
BF_BLKNUM blockNum;	/* I block number */
{
    int		stat;		/* status from calls */
    off_t	offset;		/* offset into file, for lseek() */
    off_t	lseek();	/* lseek function */

    assert(pDesc != NULL);
    assert(pBuf != NULL);
    assert(blockNum >= 0 && blockNum <= BfB0CurrentSize(pDesc));

/*----------------------------------------------------------------------------
*    read the block
*---------------------------------------------------------------------------*/
    offset = blockNum * BfB0BlockSize(pDesc);
    stat = lseek((int)pDesc->fd, offset, L_SET);
    if (stat == ERROR) {
	(void)fprintf(stderr, "bfRead: error on lseek\n");
	return ERROR;
    }
    stat = read((int)pDesc->fd, (char *)pBuf, (int)BfB0BlockSize(pDesc));
    if (stat != BfB0BlockSize(pDesc)) {
	(void)fprintf(stderr, "bfRead: error reading block\n");
	return ERROR;
    }

    return OK;
}

/*+/subr**********************************************************************
* NAME	bfRelease - release a block to the free block list of a block file
*
* DESCRIPTION
*	Adds the caller's block to the head of the free block list.
*
*	The information in the caller's buffer is destroyed.
*
* RETURNS
*	OK, or
*	ERROR
*
* BUGS
* o	should provide a mechanism whereby no disk write is involved, or
*	at least provide a way to delay the write.
* o	should maintain an 'in memory' list of free blocks and replenish
*	the list as necessary.  This would avoid I/O delays on bfAcquire()
*	calls.
* o	multiple, simultaneous calls to this routine could result in various
*	kinds of scrambling of the free block list.  Callers must presently
*	cooperate to prevent simultaneous calls.
*
* SEE ALSO
*	bfAcquire()
*
*-*/
int
bfRelease(pDesc, pBuf, blockNum)
BF_DESC	*pDesc;		/* IO pointer to descriptor */
BF_BLOCK  *pBuf;	/* IO buffer pointer */
BF_BLKNUM blockNum;	/* I number of the block */
{
    int		stat;		/* status from calls */
    off_t	offset;		/* offset into file, for lseek() */
    off_t	lseek();	/* lseek function */

    assert(pDesc != NULL);
    assert(pBuf != NULL);
    assert(blockNum >= 0 && blockNum <= BfB0CurrentSize(pDesc));

    if (!BfWriteable(pDesc)) {
	(void)fprintf(stderr, "bfRelease: file is O_RDONLY\n");
	return ERROR;
    }

/*----------------------------------------------------------------------------
*    link this block into the free block list, set firstByte and lastByte
*    to 0, write the released block, and free the block's memory
*---------------------------------------------------------------------------*/
    pBuf->flink = BfB0Flink(pDesc);
    pBuf->firstByte = 0;
    pBuf->lastByte = 0;
    offset = blockNum * BfB0BlockSize(pDesc);
    stat = lseek((int)pDesc->fd, offset, L_SET);
    if (stat == ERROR) {
	perror("bfRelease: error on lseek");
	return ERROR;
    }
    stat = write((int)pDesc->fd, (char *)pBuf, (int)BfB0BlockSize(pDesc));
    if (stat != BfB0BlockSize(pDesc)) {
	(void)fprintf(stderr, "bfRelease: error writing released block\n");
	return ERROR;
    }

/*----------------------------------------------------------------------------
*    adjust the free block information in block0
*---------------------------------------------------------------------------*/
    BfB0CurrentFree(pDesc)++;
    BfB0Flink(pDesc) = blockNum;

    return OK;
}

/*+/subr**********************************************************************
* NAME	bfWrite - write a block to a block file
*
* DESCRIPTION
*	The block is written to the file, with the flink, firstByte, and
*	lastByte fields written as set by the caller.
*
*	For files of type BF_TYPE_RING, the macro BfRingPutc(c, pDesc) and the
*	function bfRingFputs(pBuf, pDesc) are available as more
*	friendly tools for writing the data to blocks.
*
* RETURNS
*	OK, or
*	ERROR
*
* EXAMPLE
*	For an example of using bfWrite, see bfAcquire().
*
*-*/
int
bfWrite(pDesc, pBuf, blockNum)
BF_DESC	*pDesc;		/* IO pointer to descriptor */
BF_BLOCK  *pBuf;	/* IO buffer pointer */
BF_BLKNUM blockNum;	/* I number of the block */
{
    int		stat;		/* status from calls */
    off_t	offset;		/* offset into file, for lseek() */
    off_t	lseek();	/* lseek function */

    assert(pDesc != NULL);
    assert(pBuf != NULL);
    assert(blockNum >= 0 && blockNum <= BfB0CurrentSize(pDesc));

    if (!BfWriteable(pDesc)) {
	(void)fprintf(stderr, "bfWrite: file is O_RDONLY\n");
	return ERROR;
    }

/*----------------------------------------------------------------------------
*    write the block and, if keepFlag isn't set, free the buffer
*---------------------------------------------------------------------------*/
    offset = blockNum * BfB0BlockSize(pDesc);
    while (1) {
	stat = lseek((int)pDesc->fd, offset, L_SET);
	if (stat == ERROR) {
	    perror("bfWrite: error on lseek");
	    return ERROR;
	}
	stat = write((int)pDesc->fd, (char *)pBuf, (int)BfB0BlockSize(pDesc));
	if (stat == BfB0BlockSize(pDesc))
	    return OK;
#ifdef vxWorks
	perror("bfWrite: error writing block\n");
	return ERROR;
#else
	if (errno != EINTR) {
	    perror("bfWrite: error writing block\n");
	    return ERROR;
	}
#endif
    }
}
