/* Compound file class
 * Copyright John Remyn 1994
 * boogyman@xs4all.hacktic.nl
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>
#include <glob.h>

#include "cfclass.h"

#define SIGLEN 12

char *cfc::cf_sigs[2] = {  // const 12 bytes signature & version number
    "CF1.0       ",
    "CF2.0       "
};

#define NROFSIGS (sizeof( cf_sigs )/sizeof( char * ))

char *cfc::errstr[9] = {
    "no error",                             // 0
    "file error",                           // 1
    "memory allocation error",              // 2
    "no compound file open",                // 3
    "memory deallocation error",            // 4
    "no more files in compound file",       // 5
    "unknown compound file signature",      // 6
    "cannot close compound file",           // 7
    "file not found in compound"            // 8
};

char *cfc::strupr( char *s ) {
	char *cp;
	cp = s;
	while( *s!='\0' ) {
		*s = toupper( *s );
		s++;
	}
	return cp;
}

void cfc::copyfnam( char *filename ) {
	strncpy( fnam, filename, 13 );
	strupr( fnam );
}

int cfc::readdata( int handle, void *target, int size ) {
	int result;
	
	result = ::read( handle, (char *)target, size );
	if( result==0 || result==-1 )
		return 0;
	return 1;
}
	

int cfc::getnroffile( char *fnam ) {
    // looks for the readnr mathing this file, fnam==null returns readnr
    // returns -1 and sets errno on not found
    // returns -1 if nr beyond file
    int nr;
    struct cf_filelist *p;
    char str[13];

    if( fnam==NULL ) {
        nr = readnr;
    }
    else {
        int i;

        i=0;
        p = file_list;
        sprintf( str, "%12s", fnam );   // produce rigtht justified filenamestr
        while( strcmp( str, (char *)(p->name) )!=0 && i++<nroff )
            p++;
        nr = i;
    }
    if( nr>=nroff ) {
        errno = 8;
        return -1;
    }
    else return( nr );
}


cfc::cfc( void ) {    // constructor
    handle = 0;
    file_list = NULL;
    readnr = 0;
    errno = 0;
}


cfc::~cfc( void ) {   // destructor
    closecf();
}


char *cfc::errorstr( void ) {
    char *s;
    s = errstr[errno];
    errno = 0;
    return( s );
}



int cfc::open( char *cfnam ) {
    // open file
    // create list
    // readnr = 0
    // return errno

    int i;
    char ver[SIGLEN+1];
    struct cf_filelist file, *lf;
    long fpointer;

    if( (handle = ::open( cfnam, O_RDONLY, 0 )) == -1 ) {
        errno = 1;
        return( 1 );
    }

    readdata( handle, (char *)ver, SIGLEN );
    ver[SIGLEN] = '\0';

    version = 0;

    while( strcmp( ver, cf_sigs[version] )!=0 && version<NROFSIGS )
    {
        version++;
    }

    if( version == NROFSIGS ) {
        closecf();
        errno = 6;
        return( errno );
    }

    readdata( handle, (void *)&nroff, sizeof( short int ) );
    if( (file_list = (cf_filelist *)malloc( nroff * sizeof( struct cf_filelist ) )) == NULL ) {
        errno = 2;
        return( errno );
    }

    lf = file_list;

    for( i=0; i<nroff; i++ ) {
        readdata( handle, (char *)&(file.name), 12 );
        file.name[12] = '\0';
        readdata( handle, (long *)&(file.storsize), sizeof( file.storsize ) );
        if( version==1 ) {    // version == 1 means CF2.0
            readdata( handle, (long *)&(file.truesize), sizeof( file.truesize ) );
        }
        else {
            file.truesize = file.storsize;
        }
        readdata( handle, (unsigned short int *)&(file.stor), sizeof( file.stor ) );
        memcpy( lf++, &file, sizeof( struct cf_filelist ) );
    }

    fpointer = lseek( handle, 0, SEEK_CUR );
    lf = file_list;
    for( i=0; i<nroff; i++ ) {
        lf->offset = fpointer;
        fpointer += lf->storsize;
        lf++;
    }

    readnr = 0;

    return( errno );
}


void cfc::closecf( void ) {
    if( handle ) {
        if( (errno = ::close( handle ))==0 ) {
            handle = 0;
        }
        else errno = 7;
    }
    if( file_list ) {
        free( file_list );
        file_list = NULL;
    }
    readnr = nroff = version = 0;
}


int cfc::close( void ) {
    // close file
    // free list
    // reset variables
    // return errno
    if( handle ) closecf();
    else errno = 3;
    return( errno );
}

int cfc::error( void ) {
    return errno;
}

// read in a file from start.
char *cfc::read( char *filename, long n, char *target ) 
	{
	return cfc::readoffset( filename, 0, n, target );
	}

// read in a file from a certain offset within that file.	
char *cfc::readoffset( char *filename, long offset, long n, char *target ) {
    // memory is always allocated on far heap
    // check if open
    // if fnam == null  if readnr behind list  error = no more files
    // else find filename, readnr =
    // find file ( readnr );
    // if n==0   n = true filesize
    // if n>true filesize  n = true filesize
    // check if target==NULL
    // yes: allocate n bytes
    // check storage type :
    //  - not compressed : load n bytes from file
    //  - compressed :  create a buffer
    //                  1:load a part of file in buffer
    //                  decompress buffer until n bytes are filled or end
    //                  of buffer is reached.
    //                  if end of buffer is reached repeat from 1:
    //                  delete buffer
    // if the read routine returns an error memory is freed and readnr is inc'd
    //  so this file will be skipped( hopefully );
    // readnr = filelist index + 1
    // on error set errno, return NULL, free memory
    // return pointer to target (allocated memory)
    struct cf_filelist fi;
    struct cf_filelist *p;
    long readlen;
    int mallocedhere = 0;


    if( !handle ) {
        errno = 3;
        return( NULL );
    }

    copyfnam( filename );

    if( tofile( fnam )==-1 ) return( NULL );
    p = file_list + readnr;

    memcpy( &fi, p, sizeof( struct cf_filelist ) );
    if( n==0 || n>(fi.storsize-offset) ) {
        readlen = fi.storsize-offset;
    }
    else {
        readlen = n;
    }
    
    if( n==0 || n>(fi.truesize-offset) ) {
        n = fi.truesize-offset;
    }
    
    if( !target ) {
        target = (char *)malloc( n );
        if( !target ) {
            errno = 2;
            return( NULL );
        }
        mallocedhere = 1;
    }

    switch( fi.stor ) {
        case( 0 ) :
        case( 1 ) : {
            lseek( handle, fi.offset + offset, SEEK_SET );
            if( !readdata( handle, target, readlen ) ) {
                errno = 1;
                if( mallocedhere ) free( target );
                readnr++;
                return( NULL );
            }
        }
    }

    readnr++;
    return( target );

}



long cfc::size( char *filename ) {
    // check if open
    // if fnam == null  if readnr behind list error = no more files
    //                  fnam = file_list[readnr]->name
    // find file
    // get true size
    // on error return -1 and set errno (cannot return 0, that's a valid size)
    // return true size

    if( filename!=NULL ) {
        copyfnam( filename );
        if( tofile( fnam )==-1 ) {
            return( -1 );
        }
    }
    if( readnr>=nroff ) {
        errno = 5;
        return( -1 );
    }
    return( (file_list+readnr)->truesize );
}


char *cfc::namestr( void ) {
    static char nstr[20];
    // check if open
    // if readnr behind filelist  errno = no more files
    // get filename
    // return filename *, NULL if error
    if( !handle ) {
        errno = 3;
        return( NULL );
    }
    if( readnr>=nroff ) {
        errno = 5;
        return( NULL );
    }
    memcpy( nstr, (file_list + readnr)->name, 12 );
    nstr[12] = '\0';
    return( nstr );
}


int cfc::nroffiles( void ) {
    // check if open
    // return nroff
    // returns -1 on error
    if( !handle ) {
        errno = 3;
        return( -1 );
    }
    return( nroff );
}


int cfc::tofile( char *filename ) {
    // check if open
    // find fnam
    // not found  return error
    // set new readnr
    int i;

    if( !handle ) {
        errno = 3;
        return( -1 );
    }
    copyfnam( filename );
    i = getnroffile( fnam );
    if( i==-1 ) {
        return( -1 );       // errno is already set by getnroffile
    }
    return( readnr = i );
}

