/*
|| ----------------------------------------------------------------------------
||
|| HETLIB.C     (c) Copyright Leland Lucius, 2000-2009
||              Released under terms of the Q Public License.
||
|| Library for managing Hercules Emulated Tapes.
||
|| ----------------------------------------------------------------------------
*/

// $Id: hetlib.c 5125 2009-01-23 12:01:44Z bernard $
//
// $Log$
// Revision 1.34  2007/11/21 23:30:41  fish
// (comment change only)
//
// Revision 1.33  2007/07/24 22:36:32  fish
// Fix tape Synchronize CCW (x'43') to do actual commit
//
// Revision 1.32  2007/06/23 00:04:10  ivan
// Update copyright notices to include current year (2007)
//
// Revision 1.31  2007/02/25 21:10:44  fish
// Fix het_locate to continue on tapemark
//
// Revision 1.30  2006/12/08 09:43:26  jj
// Add CVS message log
//

#include "hstdinc.h"

#define _HETLIB_C_
#define _HTAPE_DLL_

#include "hercules.h"
#include "hetlib.h"

#undef HETDEBUGR
#undef HETDEBUGW

/*
|| Local constant data
*/
static const char *het_errstr[] =
{
    "No error",
    "File error",
    "Tapemark read",
    "Beginning of tape",
    "End of tape",
    "BOR not found",
    "EOR not found",
    "Unexpected tapemark",
    "Buffer not big enough",
    "Premature EOF",
    "Decompression error",
    "Unknown compression method",
    "Compression error",
    "Specified length to big",
    "Write protected",
    "Bad function code passed",
    "Bad compression method",
    "Bad compression level",
    "Bad write chunk size",
    "Invalid direction specified",
    "Insufficient memory",
    "Couldn't read block header",
    "Inconsistent compression flags",
    "Invalid error code",
};
#define HET_ERRSTR_MAX ( sizeof( het_errstr) / sizeof( het_errstr[ 0 ] ) )

/*==DOC==

    NAME
            het_open - Open an HET format file

    SYNOPSIS
            #include "hetlib.h"

            int het_open( HETB **hetb, char *filename, int flags )

    DESCRIPTION
            The het_open() function opens the file indicated by the "filename"
            parameter and, if successful, places the address of an HETB at
            the location pointed to by the "hetb" parameter.

            Currently, "HETOPEN_CREATE" is the only flag available and has
            the same function as the O_CREAT flag of the open(3) function.

        @ISW@ Added flag HETOPEN_READONLY

        HETOPEN_CREATE and HETOPEN_READONLY are mutually exclusive.

        When HETOPEN_READONLY is set, the het file must exist.
        It is opened read only. Any attempt to write will fail.

    RETURN VALUE
            If no errors are detected then the return value will be >= 0
            and the address of the newly allocated HETB will be place at the
            "hetb" location.

            If an error occurs, then the return value will be < 0 and will be
            one of the following:

            HETE_NOMEM          Insufficient memory to allocate an HETB

            HETE_ERROR          File system error - check errno(3)

            For other possible errors, see:
                het_read_header()
                het_tapemark()
                het_rewind()

    NOTES
            Even if het_open() fails, you should still call het_close().

    EXAMPLE
            //
            // Create an NL tape
            //

            #include "hetlib.h"

            int main( int argc, char *argv[] )
            {
                HETB *hetb;
                int rc;

                rc = het_open( &hetb, argv[ 1 ], HETOPEN_CREATE );
                if( rc < 0 )
                {
                    printf( "het_open() returned: %d\n", rc );
                }
                else
                {
                    printf( "%s successfully created\n", argv[ 1 ] );
                }

                het_close( &hetb );

                return( 0 );
            }

    SEE ALSO
            het_read_header(), het_tapemark(), het_rewind(), het_close()

==DOC==*/

DLL_EXPORT int
het_open( HETB **hetb, char *filename, int flags )
{
    HETB *thetb;
    char *omode;
    int   rc;
    int   fd;
    int   oflags;
    char  pathname[MAX_PATH];

    /*
    || Initialize
    */
    *hetb = NULL;
    hostpath(pathname, filename, sizeof(pathname));

    /*
    || Allocate a new HETB
    */
    thetb = calloc( 1, sizeof( HETB ) );
    if( thetb == NULL )
    {
        return( HETE_NOMEM );
    }

    /*
    || Set defaults
    */
    thetb->compress   = HETDFLT_COMPRESS;
    thetb->decompress = HETDFLT_DECOMPRESS;
    thetb->method     = HETDFLT_METHOD;
    thetb->level      = HETDFLT_LEVEL;
    thetb->chksize    = HETDFLT_CHKSIZE;

    /*
    || clear HETOPEN_CREATE if HETOPEN_READONLY is specified
    */
    if(flags & HETOPEN_READONLY)
    {
        flags&=~HETOPEN_CREATE;
    }
    /*
    || Translate HET create flag to filesystem flag
    */
    oflags = ( ( flags & HETOPEN_CREATE ) ? O_CREAT : 0 );

    /*
    || Open the tape file
    */
    omode = "r+b";
    if(!(flags & HETOPEN_READONLY))
    {
        fd = open( pathname, O_RDWR | O_BINARY | oflags, S_IRUSR | S_IWUSR | S_IRGRP );
    }
    if( (flags & HETOPEN_READONLY) || (fd == -1 && (errno == EROFS || errno == EACCES) ) )
    {
        /*
        || Retry open if file resides on readonly file system
        */
        omode = "rb";
        thetb->writeprotect = TRUE;
        fd = open( pathname, O_RDONLY | O_BINARY, S_IRUSR | S_IWUSR | S_IRGRP );
    }

    /*
    || Error out if both opens failed
    */
    if( fd == -1 )
    {
        free( thetb );
        return( HETE_ERROR );
    }

    /*
    || Associate stream with file descriptor
    */
    thetb->fd = fdopen( fd, omode );
    if( thetb->fd == NULL )
    {
        rc = errno;
        close( fd );
        errno = rc;
        free( thetb );
        return( HETE_ERROR );
    }

    /*
    || If uninitialized tape, write 2 tapemarks to make it a valid NL tape
    */
    rc = het_read_header( thetb );
    if( rc < 0 && rc != HETE_TAPEMARK )
    {
        if( rc != HETE_EOT )
        {
            return( rc );
        }

        rc = het_tapemark( thetb );
        if( rc < 0 )
        {
            return( rc );
        }

        rc = het_tapemark( thetb );
        if( rc < 0 )
        {
            return( rc );
        }
    }

    /*
    || Reposition tape to load point
    */
    rc = het_rewind( thetb );
    if( rc < 0 )
    {
        return( rc );
    }

    /*
    || Give the caller the new HETB
    */
    *hetb = thetb;

    return( 0 );

}

/*==DOC==

    NAME
            het_close - Close an HET file

    SYNOPSIS
            #include "hetlib.h"

            int het_close( HETB **hetb )

    DESCRIPTION
            The het_close() function closes an HET file and releases the
            HETB.

    RETURN VALUE
            If no errors are detected then the return value will be >= 0
            and the location specified by the "hetb" parameter will be set
            to NULL.

            If an error occurs, then the return value will be < 0.  At this
            time, no errors will be returned.

    EXAMPLE
            //
            // Create an NL tape
            //

            #include "hetlib.h"

            int main( int argc, char *argv[] )
            {
                HETB *hetb;
                int rc;

                rc = het_open( &hetb, argv[ 1 ], HETOPEN_CREATE );
                if( rc < 0 )
                {
                    printf( "het_open() returned: %d\n", rc );
                }
                else
                {
                    printf( "%s successfully created\n", argv[ 1 ] );
                }

                het_close( &hetb );

                return( 0 );
            }

    SEE ALSO
            het_open()

==DOC==*/

DLL_EXPORT int
het_close( HETB **hetb )
{

    /*
    || Only free the HETB if we have one
    */
    if( *(hetb) != NULL )
    {
        /*
        || Only close the file if opened
        */
        if( (*hetb)->fd != NULL )
        {
            fclose( (*hetb)->fd );
        }
        free( *(hetb) );
    }

    /*
    || Reinitialize pointer
    */
    *hetb = NULL;

    return( 0 );
}

/*==DOC==

    NAME
            het_cntl - Control HET file behavior

    SYNOPSIS
            #include "hetlib.h"

            int het_cntl( HETB *hetb, int func, unsigned long val )

    DESCRIPTION
            The het_cntl() function allows you to get/set several values
            that control how an HET file behaves.  The value of the "val"
            parameter depends on the function code.

            The possible modes are:

            HETCNTL_GET         Should be ORed (|) with the function codes
                                to retrieve the current setting.  (Default)

            HETCNTL_SET         Should be ORed (|) with the function codes
                                to set a new value.

            The possible function codes are:

            HETCNTL_COMPRESS    val=TRUE to enable write compression (see notes)
                                Values:     FALSE (disable)
                                            TRUE (enable)
                                Default:    HETDFLT_COMPRESS (TRUE)

            HETCNTL_DECOMPRESS  val=TRUE to enable read decompression
                                Values:     FALSE (disable)
                                            TRUE (enable)
                                Default:    HETDFLT_DECOMPRESS (TRUE)

            HETCNTL_METHOD      val=Compression method to use
                                Values:     HETMETH_ZLIB (1)
                                            HETMETH_BZLIB (2)
                                Default:    HETDFLT_METHOD (HETMETH_ZLIB)

            HETCNTL_LEVEL       val=Level of compression
                                Min:        HETMIN_LEVEL (1)
                                Max:        HETMAX_LEVEL (9)
                                Default:    HETDFLT_LEVEL (4)

            HETCNTL_CHUNKSIZE   val=Size of output chunks (see notes)
                                Min:        HETMIN_CHUNKSIZE (4096)
                                Max:        HETMAX_CHUNKSIZE (65535)
                                Default:    HETDFLT_CHUNKSIZE (65535)

    RETURN VALUE
            If no errors are detected then the return value will be either
            the current setting for a "get" request or >= 0 for a "set"
            request.

            If an error occurs, then the return value will be < 0 and can be
            one of the following:

            HETE_BADMETHOD      Specified method out of range

            HETE_BADLEVEL       Specified level out of range

            HETE_BADCHUNKSIZE   Specified chunk size out of range

            HETE_BADFUNC        Unrecognized function code

    NOTES
            Each block on an HET file is made up of "HETCNTL_CHUNKSIZE" sized
            chunks.  There can be 1 or many chunks per block.

            If you wish to create an AWSTAPE compatible file, specify a chunk
            size of 4096 and disable write compression.

    EXAMPLE
            //
            // Create an NL tape and write an uncompressed string to it
            //

            #include "hetlib.h"

            char data[] = "This is a test";

            int main( int argc, char *argv[] )
            {
                HETB *hetb;
                int rc;

                rc = het_open( &hetb, argv[ 1 ], HETOPEN_CREATE );
                if( rc >= 0 )
                {
                    rc = het_cntl( hetb, HETCNTL_SET | HETCNTL_COMPRESS, FALSE );
                    if( rc >= 0 )
                    {
                        rc = het_write( hetb, data, sizeof( data ) );
                        if( rc >= 0 )
                        {
                            printf( "Data successfully written\n" );
                        }
                    }
                }

                if( rc < 0 )
                {
                    printf( "HETLIB error: %d\n", rc );
                }

                het_close( &hetb );

                return( 0 );
            }

    SEE ALSO
            het_open(), het_cntl(), het_write(), het_close()

==DOC==*/

DLL_EXPORT int
het_cntl( HETB *hetb, int func, unsigned long val )
{
    int mode;

    /*
    || Isolate the mode
    */
    mode = func & HETCNTL_SET;

    /*
    || Process the requested function
    */
    switch( func & ( ~( HETCNTL_GET | HETCNTL_SET ) ) )
    {
        case HETCNTL_COMPRESS:
            if( mode == HETCNTL_GET )
            {
                return( hetb->compress );
            }

            hetb->compress = ( val ? TRUE : FALSE );
        break;

        case HETCNTL_DECOMPRESS:
            if( mode == HETCNTL_GET )
            {
                return( hetb->decompress );
            }

            hetb->decompress = ( val ? TRUE : FALSE );
        break;

        case HETCNTL_METHOD:
            if( mode == HETCNTL_GET )
            {
                return( hetb->method );
            }

            if( val < HETMIN_METHOD || val > HETMAX_METHOD )
            {
                return( HETE_BADMETHOD );
            }

            hetb->method = val;
        break;

        case HETCNTL_LEVEL:
            if( mode == HETCNTL_GET )
            {
                return( hetb->level );
            }

            if( val < HETMIN_LEVEL || val > HETMAX_LEVEL )
            {
                return( HETE_BADLEVEL );
            }

            hetb->level = val;
        break;

        case HETCNTL_CHUNKSIZE:
            if( mode == HETCNTL_GET )
            {
                return( hetb->chksize );
            }

            if( val < HETMIN_CHUNKSIZE || val > HETMAX_CHUNKSIZE )
            {
                return( HETE_BADSIZE );
            }

            hetb->chksize = val;
        break;

        default:
            return( HETE_BADFUNC );
        break;
    }

    /*
    || Success
    */

    return( 0 );
}

/*==DOC==

    NAME
            het_read_header - Retrieve the next chunk header from an HET file

    SYNOPSIS
            #include "hetlib.h"

            int het_read_header( HETB *hetb )

    DESCRIPTION
            Retrieves the next chunk header and stores it in the HETB.

    RETURN VALUE
            If no errors are detected then the return value will be >= 0
            and the current block count will be incremented.

            If an error occurs, then the return value will be < 0 and can be
            one of the following:

            HETE_EOT            End of tape encountered

            HETE_ERROR          File system error - check errno(3)

            HETE_TAPEMARK       Tape mark encountered

    NOTES
            This function is not normally called from user programs and its
            behavior may change.

    EXAMPLE
            //
            // Read a chunk header from an HET file.
            //

            #include "hetlib.h"

            int main( int argc, char *argv[] )
            {
                HETB *hetb;
                int rc;

                rc = het_open( &hetb, argv[ 1 ], 0 );
                if( rc >= 0 )
                {
                    rc = het_read_header( hetb );
                    if( rc >= 0 )
                    {
                        printf( "Header read:\n" );
                        printf( "  Current length:  %d\n", HETHDR_CLEN( hetb ) );
                        printf( "  Previous length: %d\n", HETHDR_PLEN( hetb ) );
                        printf( "  Flags1:          %x\n", hetb->flags1 );
                        printf( "  Flags2:          %x\n", hetb->flags2 );
                    }
                }

                if( rc < 0 )
                {
                    printf( "HETLIB error: %d\n", rc );
                }

                het_close( &hetb );

                return( 0 );
            }

    SEE ALSO
            het_open(), het_close()

==DOC==*/

DLL_EXPORT int
het_read_header( HETB *hetb )
{
    int rc;

    /*
    || Read in a headers worth of data
    */
    rc = fread( &hetb->chdr, sizeof( HETHDR ), 1, hetb->fd );
    if( rc != 1 )
    {
        /*
        || Return EOT if at end of physical file
        */
        if( feof( hetb->fd ) )
        {
            return( HETE_EOT );
        }

        /*
        || Something else must've happened
        */
        return( HETE_ERROR );
    }

#if defined( HETDEBUGR )
    printf("read hdr: pl=%d, cl=%d, f1=%02x, f2=%02x\n",
        HETHDR_PLEN( hetb ), HETHDR_CLEN( hetb ),
        hetb->chdr.flags1, hetb->chdr.flags2);
#endif

    /*
    || Bump block number if done with entire block
    */
    if( hetb->chdr.flags1 & ( HETHDR_FLAGS1_EOR | HETHDR_FLAGS1_TAPEMARK ) )
    {
        hetb->cblk++;
    }

    /*
    || Check for tape marks
    */
    if( hetb->chdr.flags1 & HETHDR_FLAGS1_TAPEMARK )
    {
        return( HETE_TAPEMARK );
    }

    /*
    || Success
    */
    return( 0 );
}

/*==DOC==

    NAME
            het_read - Retrieve the next block from an HET file

    SYNOPSIS
            #include "hetlib.h"

            int het_read( HETB *hetb,  void *sbuf )

    DESCRIPTION
            Read the next block of data into the "sbuf" memory location.  The
            length of "sbuf" should be at least HETMAX_BLOCKSIZE bytes.

    RETURN VALUE
            If no errors are detected then the return value will be the
            size of the block read.  This will be either the compressed or
            uncompressed length depending on the current AWSCNTL_DECOMPRESS
            setting.

            If an error occurs, then the return value will be < 0 and can be
            one of the following:

            HETE_ERROR          File system error - check errno(3)

            HETE_BADBOR         Beginning of record expected but not found

            HETE_BADCOMPRESS    Compression mismatch between related chunks

            HETE_OVERFLOW       Record too large for buffer

            HETE_PREMEOF        Premature EOF on file

            HETE_DECERR         Decompression error (stored in errno(3))

            HETE_UNKMETH        Unknown compression method encountered

            For other possible errors, see:
                het_read_header()

    EXAMPLE
            //
            // Read a block from an HET file.
            //

            #include "hetlib.h"

            char buffer[ HETMAX_BLOCKSIZE ];

            int main( int argc, char *argv[] )
            {
                HETB *hetb;
                int rc;

                rc = het_open( &hetb, argv[ 1 ], 0 );
                if( rc >= 0 )
                {
                    rc = het_read( hetb, buffer );
                    if( rc >= 0 )
                    {
                        printf( "Block read - length: %d\n", rc );
                    }
                }

                if( rc < 0 )
                {
                    printf( "HETLIB error: %d\n", rc );
                }

                het_close( &hetb );

                return( 0 );
            }

    SEE ALSO
            het_open(), het_read_header(), het_close()

==DOC==*/

DLL_EXPORT int
het_read( HETB *hetb, void *sbuf )
{
    char *tptr;
    int rc;
    unsigned long slen;
    int flags1, flags2;
    unsigned long tlen;
    char tbuf[ HETMAX_BLOCKSIZE ];

    /*
    || Initialize
    */
    flags1 = flags2 = 0;
    tlen = 0;
    tptr = sbuf;

    /*
    || Read chunks until entire block has been read
    */
    do
    {
        /*
        || Get a header
        */
        rc = het_read_header( hetb );
        if( rc < 0 )
        {
            return( rc );
        }

        /*
        || Have we seen a BOR chunk yet?
        */
        if( !( flags1 & HETHDR_FLAGS1_BOR ) )
        {
            /*
            || Nope, so this chunk MUST have the BOR set
            */
            if( !( hetb->chdr.flags1 & HETHDR_FLAGS1_BOR ) )
            {
                return( HETE_BADBOR );
            }

            /*
            || If block is compressed (and decompression is desired), set
            || destination pointer.
            */
            if( hetb->decompress )
            {
                if( hetb->chdr.flags1 & HETHDR_FLAGS1_COMPRESS ||
                    hetb->chdr.flags2 & HETHDR_FLAGS2_COMPRESS )
                {
                    if( ( hetb->chdr.flags1 & HETHDR_FLAGS1_COMPRESS ) &&
                        ( hetb->chdr.flags2 & HETHDR_FLAGS2_COMPRESS ) )
                    {
                        return( HETE_BADCOMPRESS );
                    }
                    tptr = tbuf;
                }
            }

            /*
            || Save flags for later validation
            */
            flags1 = hetb->chdr.flags1;
            flags2 = hetb->chdr.flags2;
        }
        else
        {
            /*
            || Yep, so this chunk MUST NOT have the BOR flag set
            */
            if( hetb->chdr.flags1 & HETHDR_FLAGS1_BOR )
            {
                return( HETE_BADBOR );
            }
        }

        /*
        || Compression flags from related chunks must match
        */
        if( (            flags1 & HETHDR_FLAGS1_COMPRESS ) !=
            ( hetb->chdr.flags1 & HETHDR_FLAGS1_COMPRESS ) )
        {
            return( HETE_BADCOMPRESS );
        }
        if( (            flags2 & HETHDR_FLAGS2_COMPRESS ) !=
            ( hetb->chdr.flags2 & HETHDR_FLAGS2_COMPRESS ) )
        {
            return( HETE_BADCOMPRESS );
        }

        /*
        || Calculate running length
        */
        slen = HETHDR_CLEN( hetb );
        tlen += slen;

        /*
        || Can't be bigger than HETMAX_BLOCKSIZE
        */
        if( tlen > HETMAX_BLOCKSIZE )
        {
            return( HETE_OVERFLOW );
        }

        /*
        || Finally read in the chunk data
        */
        rc = fread( tptr, 1, slen, hetb->fd );
        if( rc != (int)slen )
        {
            if( feof( hetb->fd ) )
            {
                return( HETE_PREMEOF );
            }

            return( HETE_ERROR );
        }

        /*
        || Bump destination pointer to next possible location
        */
        tptr += slen;
    }
    while( !( hetb->chdr.flags1 & HETHDR_FLAGS1_EOR ) );

    /*
    || Save compressed length (means cblksize size and ublksize will be the
    || same for uncompressed data)
    */
    hetb->cblksize = tlen;

    /*
    || Decompress data if requested
    */
    if( hetb->decompress )
    {
        switch( hetb->chdr.flags1 & HETHDR_FLAGS1_COMPRESS )
        {
            case 0:
                switch( hetb->chdr.flags2 & HETHDR_FLAGS2_COMPRESS )
                {
                    case 0:
                    break;
#if defined( HAVE_LIBZ )
                    case HETHDR_FLAGS2_ZLIB_BUSTECH:
                        slen = HETMAX_BLOCKSIZE;

                        rc = uncompress( sbuf, &slen, (unsigned char *)tbuf, tlen );
                        if( rc != Z_OK )
                        {
                            errno = rc;
                            return( HETE_DECERR );
                        }

                        tlen = slen;
                    break;
#endif /* defined( HAVE_LIBZ ) */
                    default:
                        return( HETE_UNKMETH );
                    break;
                }
            break;

#if defined( HAVE_LIBZ )
            case HETHDR_FLAGS1_ZLIB:
                slen = HETMAX_BLOCKSIZE;

                rc = uncompress( sbuf, &slen, (unsigned char *)tbuf, tlen );
                if( rc != Z_OK )
                {
                    errno = rc;
                    return( HETE_DECERR );
                }

                tlen = slen;
            break;
#endif /* defined( HAVE_LIBZ ) */

#if defined( HET_BZIP2 )
            case HETHDR_FLAGS1_BZLIB:
                slen = HETMAX_BLOCKSIZE;

                rc = BZ2_bzBuffToBuffDecompress( sbuf,
                                                 (void *) &slen,
                                                 tbuf,
                                                 tlen,
                                                 0,
                                                 0 );
                if (rc != BZ_OK)
                {
                    errno = rc;
                    return( HETE_DECERR );
                }

                tlen = slen;
            break;
#endif /* defined( HET_BZIP2 ) */

            default:
                return( HETE_UNKMETH );
            break;
        }
    }

    /*
    || Save uncompressed length
    */
    hetb->ublksize = tlen;

    /*
    || Success
    */
    return( tlen );
}

/*==DOC==

    NAME
            het_write_header - Write a chunk header to an HET file

    SYNOPSIS
            #include "hetlib.h"

            int het_write_header( HETB *hetb, int len, int flags1, int flags2 )

    DESCRIPTION
            Constructs and writes a chunk header to an HET file.

    RETURN VALUE
            If no errors are detected then the return value will be >= 0
            and the current block count will be incremented.

            If an error occurs, then the return value will be < 0 and can be
            one of the following:

            HETE_BADLEN         "len" parameter out of range

            HETE_PROTECTED      File is write protected

            HETE_ERROR          File system error - check errno(3)

    NOTES
            This function is not normally called from user programs and its
            behavior may change.

            If this function is called and the tape is not positioned at the
            end, then the file will be truncated to the current position.  This
            means that rewriting blocks is not currently possible.

    EXAMPLE
            //
            // Write a chunk header to an HET file.
            //

            #include "hetlib.h"

            int main( int argc, char *argv[] )
            {
                HETB *hetb;
                int rc;

                rc = het_open( &hetb, argv[ 1 ], 0 );
                if( rc >= 0 )
                {
                    rc = het_write_header( hetb, 0, HETHDR_TAPEMARK, 0 );
                    if( rc >= 0 )
                    {
                        printf( "Header written\n" );
                    }
                }

                if( rc < 0 )
                {
                    printf( "HETLIB error: %d\n", rc );
                }

                het_close( &hetb );

                return( 0 );
            }

    SEE ALSO
            het_open(), het_close()

==DOC==*/

int
het_write_header( HETB *hetb, int len, int flags1, int flags2 )
{
    int    rc;
    off_t  rcoff;

#if defined( HETDEBUGW )
    printf("write hdr: pl=%d, cl=%d, f1=%02x, f2=%02x\n",
        HETHDR_PLEN( hetb ), HETHDR_CLEN( hetb ),
        hetb->chdr.flags1, hetb->chdr.flags2);
#endif

    /*
    || Validate length
    */
    if( len > HETMAX_CHUNKSIZE )
    {
        return( HETE_BADLEN );
    }

    /*
    || Can't write anything on readonly media
    */
    if( hetb->writeprotect )
    {
        return( HETE_PROTECTED );
    }

    /*
    || For tapemarks, length must be zero.
    */
    if( flags1 & HETHDR_FLAGS1_TAPEMARK )
    {
        len = 0;
    }

    /*
    || According to Linux fopen() man page, a positioning function is required
    || between reads and writes.  Is this REALLY necessary???
    */
    if( !hetb->readlast )
    {
        fseek( hetb->fd, 0, SEEK_CUR );
        hetb->readlast = FALSE;
    }

    /*
    || If this is the first write, truncate the file
    */
    if( !hetb->truncated )
    {
        rcoff = ftell( hetb->fd );
        if( rcoff == -1 )
        {
            return( HETE_ERROR );
        }

        rc = ftruncate( fileno( hetb->fd ), rcoff );
        if( rc == -1 )
        {
            return( HETE_ERROR );
        }

        hetb->truncated = TRUE;
    }

    /*
    || Construct the header
    */
    hetb->chdr.plen[ 0 ] = hetb->chdr.clen[ 0 ];
    hetb->chdr.plen[ 1 ] = hetb->chdr.clen[ 1 ];
    hetb->chdr.clen[ 0 ] = len & 0xFF;
    hetb->chdr.clen[ 1 ] = ( len >> 8 ) & 0xFF;
    hetb->chdr.flags1    = flags1;
    hetb->chdr.flags2    = flags2;

    /*
    || Write it out
    */
    rc = fwrite( &hetb->chdr, sizeof( HETHDR ), 1, hetb->fd );
    if( rc != 1 )
    {
        return( HETE_ERROR );
    }

    /*
    || Bump block count if done with entire block
    */
    if( hetb->chdr.flags1 & ( HETHDR_FLAGS1_EOR | HETHDR_FLAGS1_TAPEMARK ) )
    {
        hetb->cblk++;
    }

    /*
    || Success
    */
    return 0;
}

/*==DOC==

    NAME
            het_write - Write a block to an HET file

    SYNOPSIS
            #include "hetlib.h"

            int het_write( HETB *hetb, void *sbuf, int slen )

    DESCRIPTION
            Writes a block of data specified by "sbuf" with a length of "slen"
            to an HET file.  Depending on the current HETCNTL_COMPRESS setting,
            the data may be compressed prior to writing.

    RETURN VALUE
            If no errors are detected then the return value will be the
            size of the block written.  This will be either the compressed or
            uncompressed length depending on the current AWSCNTL_COMPRESS
            setting.

            If an error occurs, then the return value will be < 0 and can be
            one of the following:

            HETE_ERROR          File system error - check errno(3)

            HETE_BADLEN         "slen" parameter out of range

            HETE_BADCOMPRESS    Compression mismatch between related chunks

            For other possible errors, see:
                het_write_header()

    EXAMPLE
            //
            // Create an NL tape and write a string to it
            //

            #include "hetlib.h"

            char data[] = "This is a test";

            int main( int argc, char *argv[] )
            {
                HETB *hetb;
                int rc;

                rc = het_open( &hetb, argv[ 1 ], HETOPEN_CREATE );
                if( rc >= 0 )
                {
                    rc = het_write( hetb, data, sizeof( data ) );
                    if( rc >= 0 )
                    {
                        printf( "Block written - length: %d\n", rc );
                    }
                }

                if( rc < 0 )
                {
                    printf( "HETLIB error: %d\n", rc );
                }

                het_close( &hetb );

                return( 0 );
            }

    SEE ALSO
            het_open(), het_write_header(), het_close()

==DOC==*/

DLL_EXPORT int
het_write( HETB *hetb, void *sbuf, int slen )
{
    int rc;
    int flags;
    unsigned long tlen;
#if defined(HAVE_LIBZ) || defined( HET_BZIP2 )
    char tbuf[ ((((HETMAX_BLOCKSIZE * 1001) + 999) / 1000) + 12) ];
#endif

    /*
    || Validate
    */
    if( slen > HETMAX_BLOCKSIZE )
    {
        return( HETE_BADLEN );
    }

    /*
    || Initialize
    */
    flags = HETHDR_FLAGS1_BOR;

    /*
    || Save uncompressed length
    */
    hetb->ublksize = slen;

    /*
    || Compress data if requested
    */
    if( hetb->compress )
    {
        switch( hetb->method )
        {
#if defined(HAVE_LIBZ)
            case HETHDR_FLAGS1_ZLIB:
                tlen = sizeof( tbuf );

                rc = compress2( (unsigned char *)tbuf, &tlen, sbuf, slen, hetb->level );
                if( rc != Z_OK )
                {
                    errno = rc;
                    return( HETE_COMPERR );
                }

                if( (int)tlen < slen )
                {
                    sbuf = tbuf;
                    slen = tlen;
                    flags |= HETHDR_FLAGS1_ZLIB;
                }
            break;
#endif

#if defined( HET_BZIP2 )
            case HETHDR_FLAGS1_BZLIB:
                tlen = sizeof( tbuf );

                rc = BZ2_bzBuffToBuffCompress( tbuf,
                                               (void *) &tlen,
                                               sbuf,
                                               slen,
                                               hetb->level,
                                               0,
                                               0 );
                if( rc != BZ_OK )
                {
                    errno = rc;
                    return( HETE_COMPERR );
                }

                if( (int)tlen < slen )
                {
                    sbuf = tbuf;
                    slen = tlen;
                    flags |= HETHDR_FLAGS1_BZLIB;
                }
            break;
#endif /* defined( HET_BZIP2 ) */
        }
    }

    /*
    || Save compressed length
    */
    hetb->cblksize = slen;

    /*
    || Write block, breaking it into "chksize" chunks
    */
    do
    {
        /*
        || Last chunk for this block?
        */
        if( slen <= (int)hetb->chksize )
        {
            flags |= HETHDR_FLAGS1_EOR;
            tlen = slen;
        }
        else
        {
            tlen = hetb->chksize;
        }

        /*
        || Write the header
        */
        rc = het_write_header( hetb, tlen, flags, 0 );
        if( rc < 0 )
        {
            return( rc );
        }

        /*
        || Write the block
        */
        rc = fwrite( sbuf, 1, tlen, hetb->fd );
        if( rc != (int)tlen )
        {
            return( HETE_ERROR );
        }

        /*
        || Bump pointers and turn off BOR flag
        */
        {
            char    *csbuf;
            csbuf=(char *)sbuf;
            csbuf+=tlen;
            sbuf=(void *)csbuf;
        }
        slen -= tlen;
        flags &= (~HETHDR_FLAGS1_BOR);
    }
    while( slen > 0 );

    /*
    || Set new physical EOF
    */
    do rc = ftruncate( fileno( hetb->fd ), ftell( hetb->fd ) );
    while (EINTR == rc);
    if (rc != 0)
    {
        return( HETE_ERROR );
    }

    /*
    || Success
    */
    return( hetb->cblksize );
}

/*==DOC==

    NAME
            het_tapemark - Write a tape mark to an HET file

    SYNOPSIS
            #include "hetlib.h"

            int het_tapemark( HETB *hetb )

    DESCRIPTION
            Writes a special chunk header to an HET file to simulate a tape
            mark.

    RETURN VALUE
            If no errors are detected then the return value will be >= 0.

            If an error occurs, then the return value will be < 0 and will be
            the same as those returned by het_write_header().

    EXAMPLE
            //
            // Write a tapemark to an HET file
            //

            #include "hetlib.h"

            int main( int argc, char *argv[] )
            {
                HETB *hetb;
                int rc;

                rc = het_open( &hetb, argv[ 1 ], HETOPEN_CREATE );
                if( rc >= 0 )
                {
                    rc = het_tapemark( hetb );
                    if( rc >= 0 )
                    {
                        printf( "Tape mark written\n" );
                    }
                }

                if( rc < 0 )
                {
                    printf( "HETLIB error: %d\n", rc );
                }

                het_close( &hetb );

                return( 0 );
            }

    SEE ALSO
            het_open(), het_write_header(), het_close()

==DOC==*/

DLL_EXPORT int
het_tapemark( HETB *hetb )
{
    int rc;

    /*
    || Just write a tapemark header
    */
    rc = het_write_header( hetb, 0, HETHDR_FLAGS1_TAPEMARK, 0 );
    if( rc < 0 )
    {
        return( rc );
    }

    /*
    || Set new physical EOF
    */
    do rc = ftruncate( fileno( hetb->fd ), ftell( hetb->fd ) );
    while (EINTR == rc);
    if (rc != 0)
    {
        return( HETE_ERROR );
    }

    /*
    || Success
    */
    return( 0 );
}

/*==DOC==

    NAME
            het_sync - commit/flush a HET file's buffers to disk

    SYNOPSIS
            #include "hetlib.h"

            int het_sync( HETB *hetb )

    DESCRIPTION
            Calls the file system's "fdatasync" (or fsync) function to cause
            all data for the HET file to be transferred to disk by forcing a
            physical write of all data from the file's buffers or the file-
            system's cache, to the disk, thereby assuring that after a system
            crash or other failure, that all data up to the time of the call
            is thus recorded on the disk.

    RETURN VALUE
            If no errors are detected then the return value will be >= 0.

            If an error occurs, then the return value will be < 0 and will be
            one of the following:

            HETE_PROTECTED      File is write protected

            HETE_ERROR          File system error - check errno(3)

    EXAMPLE
            //
            // Flush a HET file's buffers to disk
            //

            #include "hetlib.h"

            char data[] = "This is a test";

            int main( int argc, char *argv[] )
            {
                HETB *hetb;
                int rc;

                rc = het_open( &hetb, argv[ 1 ], HETOPEN_CREATE );
                if( rc >= 0 )
                {
                    rc = het_write( hetb, data, sizeof( data ) );
                    if( rc >= 0 )
                    {
                        printf( "Block successfully written\n" );

                        rc = het_sync( &hetb );
                        if( rc >= 0 )
                        {
                            printf( "Block successfully committed\n" );
                        }
                    }
                }

                if( rc < 0 )
                {
                    printf( "HETLIB error: %d\n", rc );
                }

                het_close( &hetb );

                return( 0 );
            }

    SEE ALSO
            het_open(), het_write(), het_close()

==DOC==*/

DLL_EXPORT int
het_sync( HETB *hetb )
{
    int rc;

    /*
    || Can't sync to readonly media
    */
    if( hetb->writeprotect )
    {
        return( HETE_PROTECTED );
    }

    /*
    || Perform the sync
    */
    do rc = fdatasync( fileno( hetb->fd ) );
    while (EINTR == rc);
    if (rc != 0)
    {
        return( HETE_ERROR );
    }

    /*
    || Success
    */
    return( 0 );
}

/*==DOC==

    NAME
            het_locate - Locate a block within an HET file

    SYNOPSIS
            #include "hetlib.h"

            int het_locate( HETB *hetb, int block )

    DESCRIPTION
            Repositions the HET file to the start of the block specified by
            the "block" parameter.

    RETURN VALUE
            If no errors are detected then the return value will be >= 0 and
            represents the new current block number.

            If an error occurs, then the return value will be < 0 and will
            the same as those returned by:
                het_rewind()
                het_fsb()

    NOTES
            Block numbers start at 0.

    EXAMPLE
            //
            // Seek to block #4 in HET file
            //

            #include "hetlib.h"

            int main( int argc, char *argv[] )
            {
                HETB *hetb;
                int rc;

                rc = het_open( &hetb, argv[ 1 ], 0 );
                if( rc >= 0 )
                {
                    rc = het_locate( hetb, 4 );
                    if( rc >= 0 )
                    {
                        printf( "New tape position: %d\n", rc );
                    }
                }

                if( rc < 0 )
                {
                    printf( "HETLIB error: %d\n", rc );
                }

                het_close( &hetb );

                return( 0 );
            }

    SEE ALSO
            het_open(), het_rewind(), het_fsb(), het_close()

==DOC==*/

DLL_EXPORT int
het_locate( HETB *hetb, int block )
{
    int rc;

    /*
    || Start the search from the beginning
    */
    rc = het_rewind( hetb );
    if( rc < 0 )
    {
        return( rc );
    }

    /*
    || Forward space until we reach the desired block
    */
    while( (int)hetb->cblk < block )
    {
        rc = het_fsb( hetb );
        if( rc < 0 && HETE_TAPEMARK != rc )
        {
            return( rc );
        }
    }

    return( hetb->cblk );
}

/*==DOC==

    NAME
            het_bsb - Backspace a block in an HET file

    SYNOPSIS
            #include "hetlib.h"

            int het_bsb( HETB *hetb )

    DESCRIPTION
            Repositions the current block pointer in an HET file to the
            previous block.

    RETURN VALUE
            If no errors are detected then the return value will be >= 0 and
            will be the new block number.

            If an error occurs, then the return value will be < 0 and can be
            one of the following:

            HETE_ERROR          File system error - check errno(3)

            HETE_BOT            Beginning of tape

            HETE_TAPEMARK       Tape mark encountered

            For other possible errors, see:
                het_rewind()
                het_read_header()

    EXAMPLE
            //
            // Backspace a block in an HET file
            //

            #include "hetlib.h"

            int main( int argc, char *argv[] )
            {
                HETB *hetb;
                int rc;

                rc = het_open( &hetb, argv[ 1 ], 0 );
                if( rc >= 0 )
                {
                    rc = het_fsb( hetb );
                    if( rc >= 0 )
                    {
                        rc = het_bsb( hetb );
                        if( rc >= 0 )
                        {
                            printf( "New block number = %d\n", rc );
                        }
                    }
                }

                if( rc < 0 )
                {
                    printf( "HETLIB error: %d\n", rc );
                }

                het_close( &hetb );

                return( 0 );
            }

    SEE ALSO
            het_open(), het_rewind(), het_read_header(), het_fsb(), het_close()

==DOC==*/

DLL_EXPORT int
het_bsb( HETB *hetb )
{
    int rc;
    int newblk;
    int offset;     // (note: safe to use 'int' as offset here
                    //  since we only ever seek from SEEK_CUR)
    int tapemark = FALSE;

    /*
    || Error if at BOT
    */
    if( hetb->cblk == 0 )
    {
        return( HETE_BOT );
    }

    /*
    || Get new block number
    */
    newblk = hetb->cblk - 1;

    /*
    || If new block is first on, then just rewind
    */
    if( newblk == 0 )
    {
        return( het_rewind( hetb ) );
    }

    /*
    || Calculate offset to get back to beginning of current block
    */
    offset = -((int)( HETHDR_CLEN( hetb ) + sizeof( HETHDR ) ));

    /*
    || Search backwards an entire block.  If the block is a tapemark, we can't
    || just return to caller since we must load the chunk header preceding it
    || to maintain the chdr in the HET.
    */
    do
    {
        /*
        || Reposition to start of chunk
        */
        rc = fseek( hetb->fd,
                    offset,
                    SEEK_CUR );
        if( rc == -1 )
        {
            return( HETE_ERROR );
        }

        /*
        || Read header, ignoring tapemarks
        */
        rc = het_read_header( hetb );
        if( rc < 0 && rc != HETE_TAPEMARK )
        {
            return( rc );
        }

        /*
        || Recalculate offset
        */
        offset = -((int)( HETHDR_PLEN( hetb ) + ( sizeof( HETHDR ) * 2 ) ));
    }
    while( hetb->chdr.flags1 & !( HETHDR_FLAGS1_BOR | HETHDR_FLAGS1_TAPEMARK ) );

    /*
    || Remember whether it's a tapemark or not
    */
    tapemark = ( hetb->chdr.flags1 & HETHDR_FLAGS1_TAPEMARK );

    /*
    || Reposition to chunk header preceding this one so we can load keep the
    || chdr in the HET current.
    */
    rc = fseek( hetb->fd,
                offset,
                SEEK_CUR );
    if( rc == -1 )
    {
        return( HETE_ERROR );
    }

    /*
    || Read header (ignore tapemarks)
    */
    rc = het_read_header( hetb );
    if( rc < 0 && ( rc != HETE_TAPEMARK ) )
    {
        return( rc );
    }

    /*
    || Finally reposition back to the where we should be
    */
    rc = fseek( hetb->fd,
                HETHDR_CLEN( hetb ),
                SEEK_CUR );
    if( rc == -1 )
    {
        return( HETE_ERROR );
    }

    /*
    || Store new block number
    */
    hetb->cblk = newblk;

    /*
    || Was it a tapemark?
    */
    if( tapemark )
    {
        return( HETE_TAPEMARK );
    }

    /*
    || Reset flag to force truncation if a write occurs
    */
    hetb->truncated = FALSE;

    /*
    || Return block number
    */
    return( hetb->cblk );
}

/*==DOC==

    NAME
            het_fsb - Foward space a block in an HET file

    SYNOPSIS
            #include "hetlib.h"

            int het_fsb( HETB *hetb )

    DESCRIPTION
            Repositions the current block pointer in an HET file to the
            next block.

    RETURN VALUE
            If no errors are detected then the return value will be >= 0 and
            will be the new block number.

            If an error occurs, then the return value will be < 0 and can be
            one of the following:

            HETE_ERROR          File system error - check errno(3)

            For other possible errors, see:
                het_read_header()

    EXAMPLE
            //
            // Forward space a block in an HET file
            //

            #include "hetlib.h"

            int main( int argc, char *argv[] )
            {
                HETB *hetb;
                int rc;

                rc = het_open( &hetb, argv[ 1 ], 0 );
                if( rc >= 0 )
                {
                    rc = het_fsb( hetb );
                    if( rc >= 0 )
                    {
                        printf( "New block number = %d\n", rc );
                    }
                }

                if( rc < 0 )
                {
                    printf( "HETLIB error: %d\n", rc );
                }

                het_close( &hetb );

                return( 0 );
            }

    SEE ALSO
            het_open(), het_read_header(), het_close()

==DOC==*/

DLL_EXPORT int
het_fsb( HETB *hetb )
{
    int rc;

    /*
    || Loop until we've processed an entire block
    */
    do
    {
        /*
        || Read header to get length of next chunk
        */
        rc = het_read_header( hetb );
        if( rc < 0 )
        {
            return( rc );
        }

        /*
        || Seek to next chunk
        */
        rc = fseek( hetb->fd,
                    HETHDR_CLEN( hetb ),
                    SEEK_CUR );
        if( rc == -1 )
        {
            return( HETE_ERROR );
        }
    }
    while( !( hetb->chdr.flags1 & HETHDR_FLAGS1_EOR ) );

    /*
    || Reset flag to force truncation if a write occurs
    */
    hetb->truncated = FALSE;

    /*
    || Return block number
    */
    return( hetb->cblk );
}

/*==DOC==

    NAME
            het_bsf - Backspace a file in an HET file

    SYNOPSIS
            #include "hetlib.h"

            int het_bsf( HETB *hetb )

    DESCRIPTION
            Repositions the current block pointer in an HET file to the
            previous tapemark.

    RETURN VALUE
            If no errors are detected then the return value will be >= 0 and
            will be the new block number.

            If an error occurs, then the return value will be < 0 and will be
            the same as those returned by het_bsb() with the exception that
            HETE_TAPEMARK and HETE_BOT will not occur.

    EXAMPLE
            //
            // Backspace a file in an HET file
            //

            #include "hetlib.h"

            int main( int argc, char *argv[] )
            {
                HETB *hetb;
                int rc;

                rc = het_open( &hetb, argv[ 1 ], 0 );
                if( rc >= 0 )
                {
                    rc = het_fsf( hetb );
                    if( rc >= 0 )
                    {
                        rc = het_bsf( hetb );
                        if( rc >= 0 )
                        {
                            printf( "Backspaced (sort of :-))\n" );
                        }
                    }
                }

                if( rc < 0 )
                {
                    printf( "HETLIB error: %d\n", rc );
                }

                het_close( &hetb );

                return( 0 );
            }

    SEE ALSO
            het_open(), het_bsb(), het_fsf(), het_close()

==DOC==*/

DLL_EXPORT int
het_bsf( HETB *hetb )
{
    int rc;

    /*
    || Backspace until we hit a tapemark
    */
    do
    {
        rc = het_bsb( hetb );
    }
    while( rc >= 0 );

    /*
    || Success
    */
    if( ( rc == HETE_BOT ) || ( rc == HETE_TAPEMARK ) )
    {
        return( hetb->cblk );
    }

    /*
    || Failure
    */
    return( rc );
}

/*==DOC==

    NAME
            het_fsf - Forward space a file in an HET file

    SYNOPSIS
            #include "hetlib.h"

            int het_fsf( HETB *hetb )

    DESCRIPTION
            Repositions the current block pointer in an HET file to the
            next tapemark.

    RETURN VALUE
            If no errors are detected then the return value will be >= 0 and
            will be the new block number.

            If an error occurs, then the return value will be < 0 and will be
            the same as those returned by het_fsb() with the exception that
            HETE_TAPEMARK will not occur.

    EXAMPLE
            //
            // Forward space a file in an HET file
            //

            #include "hetlib.h"

            int main( int argc, char *argv[] )
            {
                HETB *hetb;
                int rc;

                rc = het_open( &hetb, argv[ 1 ], 0 );
                if( rc >= 0 )
                {
                    rc = het_fsf( hetb );
                    if( rc >= 0 )
                    {
                        printf( "Forward spaced\n" );
                    }
                }

                if( rc < 0 )
                {
                    printf( "HETLIB error: %d\n", rc );
                }

                het_close( &hetb );

                return( 0 );
            }

    SEE ALSO
            het_open(), het_fsb(), het_close()

==DOC==*/

DLL_EXPORT int
het_fsf( HETB *hetb )
{
    int rc;

    /*
    || Forward space until we hit a tapemark
    */
    do
    {
        rc = het_fsb( hetb );
    }
    while( rc >= 0 );

    /*
    || Success
    */
    if( rc == HETE_TAPEMARK )
    {
        return( hetb->cblk );
    }

    /*
    || Failure
    */
    return( rc );
}

/*==DOC==

    NAME
            het_rewind - Rewind an HET file

    SYNOPSIS
            #include "hetlib.h"

            int het_rewind( HETB *hetb )

    DESCRIPTION
            Repositions the current block pointer in an HET file to the
            load point.

    RETURN VALUE
            If no errors are detected then the return value will be >= 0 and
            represents the new block number (always 0).

            If an error occurs, then the return value will be < 0 and will be
            one of the following:

            HETE_ERROR          File system error - check errno(3)

    EXAMPLE
            //
            // Rewind an HET file to the load point.
            //

            #include "hetlib.h"

            int main( int argc, char *argv[] )
            {
                HETB *hetb;
                int rc;

                rc = het_open( &hetb, argv[ 1 ], 0 );
                if( rc >= 0 )
                {
                    rc = het_rewind( hetb );
                    if( rc >= 0 )
                    {
                        printf( "Tape rewound\n" );
                    }
                }

                if( rc < 0 )
                {
                    printf( "HETLIB error: %d\n", rc );
                }

                het_close( &hetb );

                return( 0 );
            }

    SEE ALSO
            het_open(), het_close()

==DOC==*/

DLL_EXPORT int
het_rewind( HETB *hetb )
{
    int rc;

    /*
    || Just seek to the beginning of the file
    */
    rc = fseek( hetb->fd,
                0,
                SEEK_SET );
    if( rc == -1 )
    {
        return( HETE_ERROR );
    }

    /*
    || Reset current block
    */
    hetb->cblk = 0;

    /*
    || Clear header for the heck of it
    */
    memset( &hetb->chdr, 0, sizeof( hetb->chdr ) );

    /*
    || Reset flag to force truncation if a write occurs
    */
    hetb->truncated = FALSE;

    /*
    || Return block number
    */
    return( hetb->cblk );
}

/*==DOC==

    NAME
            het_error - Returns a text message for an HET error code

    SYNOPSIS
            #include "hetlib.h"

            char *het_error( int rc )

    DESCRIPTION
            Simply returns a pointer to a string that describes the error
            code passed in the "rc" parameter.

    RETURN VALUE
            The return value is always valid and no errors are returned.

    EXAMPLE
            //
            // Print text of HETE_BADLEN.
            //

            #include "hetlib.h"

            int main( int argc, char *argv[] )
            {
                printf( "HETLIB error: %d = %s\n",
                    HETE_BADLEN,
                    het_error( HETE_BADLEN ) );

                return( 0 );
            }

    SEE ALSO

==DOC==*/

DLL_EXPORT const char *
het_error( int rc )
{
    /*
    || If not an error just return the "OK" string
    */
    if( rc >= 0 )
    {
        rc = 0;
    }

    /*
    || Turn it into an index
    */
    rc = -rc;

    /*
    || Within range?
    */
    if( rc >= (int)HET_ERRSTR_MAX )
    {
        rc = HET_ERRSTR_MAX - 1;
    }

    /*
    || Return string
    */
    return( het_errstr[ rc ] );
}
/*==DOC==

    NAME
            het_tell - Returns the current read/write pointer offset

    SYNOPSIS
            #include "hetlib.h"

            off_t het_tell( HETB *hetb )

    DESCRIPTION
            Returns a off_t describing the actual read/write cursor
            within the HET file

    RETURN VALUE
            >=0 The actual cursor offset
            <0 An error occured.
                Possible errors are :
                HETE_ERROR - File system error occured

    EXAMPLE
            //
            // Get the current HET pointer
            //

            #include "hetlib.h"

            int main( int argc, char *argv[] )
            {
                HETB *hetb;
                int rc;
                off_t rwptr;

                rc = het_open( &hetb, argv[ 1 ], 0 );
                if( rc >= 0 )
                {
                    rwptr = het_tell( hetb );
                    if( rwptr >= 0 )
                    {
                        printf( "Current offset is %" I64_FMT "d\n" , (U64)rwptr);
                    }
                }

                if( rc < 0 )
                {
                    printf( "HETLIB error: %d\n", rc );
                }

                het_close( &hetb );

                return( 0 );
            }

    SEE ALSO

==DOC==*/

DLL_EXPORT
off_t
het_tell( HETB *hetb )
{
    off_t rwptr = ftell( hetb->fd );
    if ( rwptr < 0 )
    {
        return HETE_ERROR;
    }
    return rwptr;
}
