# /*##########################################################################
# Copyright (C) 1995-2018 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ############################################################################*/
/************************************************************************
 *
 *   File:          sfinit.c
 *
 *   Project:       SpecFile library
 *
 *   Description:   Initialization routines ( open/update/close )
 *
 *   Author:        V.Rey
 *
 *   Date:          $Date: 2005/05/25 13:01:32 $
 *
 ************************************************************************/
/*
 *   Log: $Log: sfinit.c,v $
 *   Log: Revision 1.5  2005/05/25 13:01:32  sole
 *   Log: Back to revision 1.3
 *   Log:
 *   Log: Revision 1.3  2004/05/12 16:57:32  sole
 *   Log: windows support
 *   Log:
 *   Log: Revision 1.2  2002/11/12 13:23:43  sole
 *   Log: Version with added support for the new sf->updating flag
 *   Log:
 *   Log: Revision 3.0  2000/12/20 14:17:19  rey
 *   Log: Python version available
 *   Log:
 *   Log: Revision 2.2  2000/12/20 12:12:08  rey
 *   Log: bug corrected with SfAllMotors
 *   Log:
 * Revision 2.1  2000/07/31  19:04:42  19:04:42  rey (Vicente Rey-Bakaikoa)
 * SfUpdate and bug corrected in ReadIndex
 *
 * Revision 2.0  2000/04/13  13:28:54  13:28:54  rey (Vicente Rey-Bakaikoa)
 * New version of the library. Complete rewrite
 * Adds support for MCA
 */
/*
 *   File:         sfinit.c
 *
 *   Description:  This file implements basic routines on SPEC datafiles
 *                 SfOpen / SfClose / SfError
 *
 *                 SfUpdate is kept but it is obsolete
 *
 *   Version:      2.0
 *
 *   Date:         March 2000
 *
 *   Author:       Vicente REY
 *
 *   Copyright:    E.S.R.F. European Synchrotron Radiation Facility (c) 2000
 */
/*
 *   $Log: sfinit.c,v $
 *   Revision 1.5  2005/05/25 13:01:32  sole
 *   Back to revision 1.3
 *
 *   Revision 1.3  2004/05/12 16:57:32  sole
 *   windows support
 *
 *   Revision 1.2  2002/11/12 13:23:43  sole
 *   Version with added support for the new sf->updating flag
 *
 *   Revision 3.0  2000/12/20 14:17:19  rey
 *   Python version available
 *
 *   Revision 2.2  2000/12/20 12:12:08  rey
 *   bug corrected with SfAllMotors
 *
 * Revision 2.1  2000/07/31  19:04:42  19:04:42  rey (Vicente Rey-Bakaikoa)
 * SfUpdate and bug corrected in ReadIndex
 *
 * Revision 2.0  2000/04/13  13:27:19  13:27:19  rey (Vicente Rey-Bakaikoa)
 * New version of the library. Complete rewrite
 * Adds support for MCA
 *
 *
 *********************************************************************/
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <ctype.h>

#ifdef WIN32
#include <stdio.h>
#include <stdlib.h>
#else
#include <unistd.h>
#endif

#include <SpecFile.h>
#include <SpecFileP.h>

/*
 * Defines
 */

#define ANY          0
#define NEWLINE      1
#define COMMENT      2

#define SF_ISFX      ".sfI"

#define SF_INIT      0
#define SF_READY     1
#define SF_MODIFIED  2

/*
 * Function declaration
 */

DllExport SpecFile * SfOpen   ( char *name,int *error);
DllExport SpecFile * SfOpen2  ( int fd, char *name,int *error);
DllExport int        SfClose  ( SpecFile *sf);
DllExport short      SfUpdate ( SpecFile *sf, int *error);
DllExport char     * SfError  ( int error);


#ifdef linux
char SF_SIGNATURE[] =  "Linux 2ruru Sf2.0";
#else
char SF_SIGNATURE[] =  "2ruru Sf2.0";
#endif

/*
 * Internal functions
 */
static short statusEnd     ( char c2, char c1);
static void  sfStartBuffer ( SpecFile *sf, SfCursor *cursor, short status,char c0, char c1,int *error);
static void  sfNewLine     ( SpecFile *sf, SfCursor *cursor, char c0,char c1,int *error);
static void  sfHeaderLine  ( SpecFile *sf, SfCursor *cursor, char c,int *error);
static void  sfNewBlock    ( SpecFile *sf, SfCursor *cursor, short how,int *error);
static void  sfSaveScan    ( SpecFile *sf, SfCursor *cursor, int *error);
static void  sfAssignScanNumbers (SpecFile *sf);
static void  sfReadFile    ( SpecFile *sf, SfCursor *cursor, int *error);
static void  sfResumeRead  ( SpecFile *sf, SfCursor *cursor, int *error);
#ifdef SPECFILE_USE_INDEX_FILE
static short sfOpenIndex   ( SpecFile *sf, SfCursor *cursor, int *error);
static short sfReadIndex   ( int sfi, SpecFile *sf, SfCursor *cursor, int *error);
static void  sfWriteIndex  ( SpecFile *sf, SfCursor *cursor, int *error);
#endif

/*
 * errors
 */
typedef struct _errors {
     int       code;
     char      *message;
} sf_errors ;

static
sf_errors errors[]={
{ SF_ERR_MEMORY_ALLOC     , "Memory allocation error ( SpecFile )"  },
{ SF_ERR_FILE_OPEN        , "File open error ( SpecFile )"      },
{ SF_ERR_FILE_CLOSE       , "File close error ( SpecFile )"         },
{ SF_ERR_FILE_READ        , "File read error ( SpecFile )"      },
{ SF_ERR_FILE_WRITE       , "File write error ( SpecFile )"      },
{ SF_ERR_LINE_NOT_FOUND   , "Line not found error ( SpecFile )"      },
{ SF_ERR_SCAN_NOT_FOUND   , "Scan not found error ( SpecFile )"      },
{ SF_ERR_HEADER_NOT_FOUND , "Header not found error ( SpecFile )"      },
{ SF_ERR_LABEL_NOT_FOUND  , "Label not found error ( SpecFile )"      },
{ SF_ERR_MOTOR_NOT_FOUND  , "Motor not found error ( SpecFile )"      },
{ SF_ERR_POSITION_NOT_FOUND    , "Position not found error ( SpecFile )" },
{ SF_ERR_LINE_EMPTY       , "Line empty or wrong data error ( SpecFile )"},
{ SF_ERR_USER_NOT_FOUND   , "User not found error ( SpecFile )"       },
{ SF_ERR_COL_NOT_FOUND    , "Column not found error ( SpecFile )"      },
{ SF_ERR_MCA_NOT_FOUND    , "Mca not found ( SpecFile )"      },
/* MUST be always the last one : */
{ SF_ERR_NO_ERRORS        , "OK ( SpecFile )"              },
};





/*********************************************************************
 *   Function:          SpecFile *SfOpen( name, error)
 *
 *   Description:       Opens connection to Spec data file.
 *                      Creates index list in memory.
 *
 *   Parameters:
 *              Input :
 *                      (1) Filename
 *              Output:
 *                      (2) error number
 *   Returns:
 *                      SpecFile pointer.
 *                      NULL if not successful.
 *
 *   Possible errors:
 *                      SF_ERR_FILE_OPEN
 *                      SF_ERR_MEMORY_ALLOC
 *
 *********************************************************************/

DllExport SpecFile *
SfOpen(char *name, int *error) {

   int         fd;
   fd   = open(name,SF_OPENFLAG);
   return (SfOpen2(fd, name, error));
}



/*********************************************************************
 *   Function:          SpecFile *SfOpen2( fd, name, error)
 *
 *   Description:       Opens connection to Spec data file.
 *                      Creates index list in memory.
 *
 *   Parameters:
 *              Input :
 *                      (1) Integer file handle
 *                      (2) Filename
 *              Output:
 *                      (3) error number
 *   Returns:
 *                      SpecFile pointer.
 *                      NULL if not successful.
 *
 *   Possible errors:
 *                      SF_ERR_FILE_OPEN
 *                      SF_ERR_MEMORY_ALLOC
 *
 *********************************************************************/

DllExport SpecFile *
SfOpen2(int fd, char *name,int *error) {
   SpecFile   *sf;
   short       idxret;
   SfCursor      cursor;
   struct stat mystat;

   if ( fd == -1 ) {
      *error = SF_ERR_FILE_OPEN;
      return ( (SpecFile *) NULL );
   }

  /*
   * Init specfile strucure
   */
#ifdef _WINDOWS
   static HANDLE hglb;
   hglb = GlobalAlloc(GPTR,sizeof(SpecFile));
   sf   = (SpecFile * ) GlobalLock(hglb);
#else
   sf = (SpecFile *) malloc ( sizeof(SpecFile ));
#endif
   stat(name,&mystat);

   sf->fd     = fd;
   sf->m_time = mystat.st_mtime;
   sf->sfname = (char *)strdup(name);

   sf->list.first      = (ObjectList *)NULL;
   sf->list.last       = (ObjectList *)NULL;
   sf->no_scans        = 0;
   sf->current         = (ObjectList *)NULL;
   sf->scanbuffer      = (char *)NULL;
   sf->scanheadersize  = 0;
   sf->filebuffer      = (char *)NULL;
   sf->filebuffersize  = 0;

   sf->no_labels       = -1;
   sf->labels          = (char **)NULL;
   sf->no_motor_names  = -1;
   sf->motor_names     = (char **)NULL;
   sf->no_motor_pos    = -1;
   sf->motor_pos       = (double *)NULL;
   sf->data            = (double **)NULL;
   sf->data_info       = (long *)NULL;
   sf->updating        = 0;

  /*
   * Init cursor
   */
   cursor.bytecnt      = 0;
   cursor.cursor       = 0;
   cursor.scanno       = 0;
   cursor.hdafoffset   = -1;
   cursor.dataoffset   = -1;
   cursor.mcaspectra   = 0;
   cursor.what         = 0;
   cursor.data         = 0;
   cursor.file_header  = 0;


#ifdef SPECFILE_USE_INDEX_FILE
  /*
   * Check if index file
   *   open it and continue from there
   */
   idxret = sfOpenIndex(sf,&cursor,error);
#else
   idxret = SF_INIT;
#endif

   switch(idxret) {
      case SF_MODIFIED:
          sfResumeRead(sf,&cursor,error);
          sfReadFile(sf,&cursor,error);
          break;

      case SF_INIT:
          sfReadFile(sf,&cursor,error);
          break;

      case SF_READY:
          break;

      default:
          break;
   }

   sf->cursor = cursor;

  /*
   * Once is all done assign scan numbers and orders
   */
   sfAssignScanNumbers(sf);

#ifdef SPECFILE_USE_INDEX_FILE
   if (idxret != SF_READY) sfWriteIndex(sf,&cursor,error);
#endif
   return(sf);
}




/*********************************************************************
 *
 *   Function:		int SfClose( sf )
 *
 *   Description:	Closes a file previously opened with SfOpen()
 *			and frees all memory .
 *   Parameters:
 *		Input:
 *			File pointer
 *   Returns:
 *			0 :  close successful
 *		       -1 :  errors occured
 *
 *********************************************************************/
DllExport int
SfClose( SpecFile *sf )
{
     register ObjectList  *ptr;
     register ObjectList  *prevptr;

     freeAllData(sf);

     for( ptr=sf->list.last ; ptr ; ptr=prevptr ) {
          free( (SpecScan *)ptr->contents );
          prevptr = ptr->prev;
	  free( (ObjectList *)ptr );
     }

     free ((char *)sf->sfname);
     if (sf->scanbuffer != NULL)
        free ((char *)sf->scanbuffer);

     if (sf->filebuffer != NULL)
        free ((char *)sf->filebuffer);

     if( close(sf->fd) ) {
	  return( -1 ) ;
     }

     free (  sf );
     sf    = (SpecFile *)NULL;

     return   (   0 );
}


/*********************************************************************
 *
 *   Function:          short SfUpdate( sf, error )
 *
 *   Description:       Updates connection to Spec data file .
 *                      Appends to index list in memory.
 *
 *   Parameters:
 *              Input :
 *                      (1) sf (pointer to the index list in memory)
 *              Output:
 *                      (2) error number
 *   Returns:
 *                      ( 0 ) => Nothing done.
 *                      ( 1 ) => File was updated
 *
 *   Possible errors:
 *                      SF_ERR_FILE_OPEN
 *                      SF_ERR_MEMORY_ALLOC
 *
 *********************************************************************/
DllExport short
SfUpdate ( SpecFile *sf, int *error )
{
    struct stat mystat;
    long   mtime;
   /*printf("In SfUpdate\n");
   __asm("int3");*/
    stat(sf->sfname,&mystat);

    mtime = mystat.st_mtime;

    if (sf->m_time != mtime)  {
       sfResumeRead (sf,&(sf->cursor),error);
       sfReadFile   (sf,&(sf->cursor),error);

       sf->m_time = mtime;
       sfAssignScanNumbers(sf);
#ifdef SPECFILE_USE_INDEX_FILE
       sfWriteIndex (sf,&(sf->cursor),error);
#endif
       return(1);
    }else{
       return(0);
    }
}


/*********************************************************************
 *
 *   Function:		char *SfError( code )
 *
 *   Description:	Returns the message associated with error 'code'.
 *
 *   Parameters:
 *		Input :	error code
 *
 *********************************************************************/
DllExport char *
SfError(int code ) {
     int	i;

     for ( i=0 ; errors[i].code!=0 ; i++ ) {
	     if ( errors[i].code == code ) break;
     }
     return( errors[i].message );
}


static void
sfReadFile(SpecFile *sf,SfCursor *cursor,int *error) {

   int         fd;

   char  *buffer,*ptr;

   long  size,bytesread;

   short  status;

   fd   = sf->fd;

   size = 1024*1024;


   if ( (buffer = (char *) malloc(size)) == NULL ) {
        /*
         * Try smaller buffer
         */
         size = 128 * 128;
         if ( (buffer = (char *) malloc(size)) == NULL ) {
              /*
               * Uhmmm
               */
              *error = SF_ERR_MEMORY_ALLOC;
              free(sf->sfname);
              free(sf);
              sf = (SpecFile *)NULL;
              return;
         }
   }

   status              = NEWLINE;
   while ((bytesread = read(fd,buffer,size)) > 0 ) {

      sfStartBuffer(sf,cursor,status,buffer[0],buffer[1],error);

      cursor->bytecnt++;
      for (ptr=buffer+1;ptr < buffer + bytesread -1; ptr++,cursor->bytecnt++) {
          if (*(ptr-1) == '\n' ) {
             sfNewLine(sf,cursor,*ptr,*(ptr+1),error);
          }
      }

      cursor->bytecnt++;
      status = statusEnd(buffer[bytesread-2],buffer[bytesread-1]);
  }

  free(buffer);

  sf->no_scans = cursor->scanno;
  if (sf->no_scans > 0) {
     /*
      * Save last
      */
      sfSaveScan(sf,cursor,error);
  }
  return;

}


static void
sfResumeRead  ( SpecFile *sf, SfCursor *cursor, int *error) {
    cursor->bytecnt      = cursor->cursor;
    cursor->what         = 0;
    cursor->hdafoffset   = -1;
    cursor->dataoffset   = -1;
    cursor->mcaspectra   = 0;
    cursor->data         = 0;
    cursor->scanno--;
    sf->updating = 1;
    lseek(sf->fd,cursor->bytecnt,SEEK_SET);
    return;
}


#ifdef SPECFILE_USE_INDEX_FILE
static short
sfOpenIndex ( SpecFile *sf, SfCursor *cursor, int *error) {
    char *idxname;
    short namelength;
    int   sfi;

    namelength = strlen(sf->sfname) + strlen(SF_ISFX) + 1;

    idxname = (char *)malloc(sizeof(char) * namelength);

    sprintf(idxname,"%s%s",sf->sfname,SF_ISFX);

    if ((sfi = open(idxname,SF_OPENFLAG)) == -1) {
        free(idxname);
        return(SF_INIT);
    } else {
        free(idxname);
        return(sfReadIndex(sfi,sf,cursor,error));
    }
}


static short
sfReadIndex   ( int sfi, SpecFile *sf, SfCursor *cursor, int *error) {
    SfCursor   filecurs;
    char       buffer[200];
    long       bytesread,i=0;
    SpecScan   scan;
    short      modif = 0;
    long       mtime;

   /*
    * read signature
    */
    bytesread = read(sfi,buffer,sizeof(SF_SIGNATURE));
    if (strcmp(buffer,SF_SIGNATURE) || bytesread == 0 ) {
        return(SF_INIT);
    }

   /*
    * read cursor and specfile structure
    */
    if ( read(sfi,&mtime,   sizeof(long)) == 0)   return(SF_INIT);
    if ( read(sfi,&filecurs, sizeof(SfCursor)) == 0) return(SF_INIT);

    if (sf->m_time != mtime)  modif = 1;

    while(read(sfi,&scan, sizeof(SpecScan))) {
        addToList(&(sf->list), (void *)&scan, (long)sizeof(SpecScan));
        i++;
    }
    sf->no_scans = i;

    memcpy(cursor,&filecurs,sizeof(SfCursor));

    if (modif) return(SF_MODIFIED);

    return(SF_READY);
}


static void
sfWriteIndex  ( SpecFile *sf, SfCursor *cursor, int *error) {

    int         fdi;
    char       *idxname;
    short       namelength;
    ObjectList *obj;
    long        mtime;

    namelength = strlen(sf->sfname) + strlen(SF_ISFX) + 1;

    idxname = (char *)malloc(sizeof(char) * namelength);

    sprintf(idxname,"%s%s",sf->sfname,SF_ISFX);

    /* if ((fdi = open(idxname,SF_WRITEFLAG,SF_UMASK)) == -1) { */
    if ((fdi = open(idxname,O_CREAT | O_WRONLY,SF_UMASK)) == -1) {
        printf("    - cannot open. Error: (%d)\n",errno);
        free(idxname);
        return;
    } else {
        mtime = sf->m_time;
        write(fdi,SF_SIGNATURE,sizeof(SF_SIGNATURE));
 /*
  * Swap bytes for linux
  */
        write(fdi, (void *) &mtime, sizeof(long));
        write(fdi, (void *) cursor, sizeof(SfCursor));
        for( obj = sf->list.first; obj ; obj = obj->next)
           write(fdi,(void *) obj->contents, sizeof(SpecScan));
        close(fdi);
        free(idxname);
        return;
    }
}
#endif


/*****************************************************************************
 *
 *    Function:   static void sfStartBuffer()
 *
 *    Description:  start analyzing file buffer and takes into account the last
 *                  bytes of previous reading as defined in variable status
 *
 *****************************************************************************/
static void
sfStartBuffer(SpecFile *sf,SfCursor *cursor,short status,char c0,char c1,int *error) {

    if ( status == ANY ) {
        return;
    } else if ( status == NEWLINE ) {
        sfNewLine(sf,cursor,c0,c1,error);
    } else if ( status == COMMENT ) {
        cursor->bytecnt--;
        sfHeaderLine(sf,cursor,c0,error);
        cursor->bytecnt++;
    }

}


/*******************************************************************************
 *
 *    Function:   static void statusEnd()
 *
 *    Description:  ends analysis of file buffer and returns a variable
 *                  indicating staus ( last character is COMMENT,NEWLINE of ANY )
 *
 *******************************************************************************/
static short
statusEnd(char c2,char c1) {

      if (c2=='\n' && c1=='#') {
            return(COMMENT);
      } else if (c1=='\n') {
            return(NEWLINE);
      } else {
            return(ANY);
      }
}


static void
sfNewLine(SpecFile *sf,SfCursor *cursor,char c0,char c1,int *error) {
     if (c0 == '#') {
          sfHeaderLine(sf,cursor,c1,error);
     } else if (c0 == '@') {
          if ( cursor->data == 0 ) {
             cursor->dataoffset = cursor->bytecnt;
             cursor->data = 1;
          }
          cursor->mcaspectra++;
     } else if ( isdigit(c0) || c0 == '-' || c0 == '+' || c0 == ' ' || c0 == '\t') {
          if ( cursor->data == 0 ) {
              cursor->dataoffset = cursor->bytecnt;
              cursor->data = 1;
          }
     }
}


static void
sfHeaderLine(SpecFile *sf,SfCursor *cursor,char c,int *error) {
   if ( c == 'S') {
        sfNewBlock(sf,cursor,SCAN,error);
   } else if ( c == 'F') {
        sfNewBlock(sf,cursor,FILE_HEADER,error);
   } else {
        if (cursor->data && cursor->hdafoffset == -1 )
              cursor->hdafoffset = cursor->bytecnt;
   }
}


static void
sfNewBlock(SpecFile *sf,SfCursor *cursor,short newblock,int *error) {

  /*
   * Dispatch opened block
   */
   if (cursor->what == SCAN) {
        sfSaveScan(sf,cursor,error);
   } else if ( cursor->what == FILE_HEADER) {
        cursor->fileh_size = cursor->bytecnt - cursor->cursor + 1;
   }

  /*
   * Open new block
   */
   if (newblock == SCAN) {
        cursor->scanno++;
        cursor->what = SCAN;
   } else {
        cursor->file_header = cursor->bytecnt;
   }
   cursor->what         = newblock;
   cursor->hdafoffset   = -1;
   cursor->dataoffset   = -1;
   cursor->mcaspectra   = 0;
   cursor->data         = 0;
   cursor->cursor       = cursor->bytecnt;
}


static void
sfSaveScan(SpecFile *sf, SfCursor *cursor,int *error) {
    SpecScan  scan;
    SpecScan  *oldscan;
    register	ObjectList	*ptr;


    scan.index                 = cursor->scanno;
    scan.offset                = cursor->cursor;
    scan.size                  = cursor->bytecnt - cursor->cursor;
    scan.last                  = cursor->bytecnt - 1;
    scan.data_offset           = cursor->dataoffset;
    scan.hdafter_offset        = cursor->hdafoffset;
    scan.mcaspectra            = cursor->mcaspectra;
    scan.file_header           = cursor->file_header;

    if(sf->updating == 1){
        ptr = sf->list.last;
        oldscan=(SpecScan *)(ptr->contents);
        oldscan->index=scan.index;
        oldscan->offset=scan.offset;
        oldscan->size=scan.size;
        oldscan->last=scan.last;
        oldscan->data_offset=scan.data_offset;
        oldscan->hdafter_offset=scan.hdafter_offset;
        oldscan->mcaspectra=scan.mcaspectra;
        oldscan->file_header=scan.file_header;
        sf->updating=0;
    }else{
        addToList( &(sf->list), (void *)&scan, (long) sizeof(SpecScan));
    }
}


static void
sfAssignScanNumbers(SpecFile *sf) {

  int i;
  char *ptr;
  char buffer[50];
  char buffer2[50];

  register   ObjectList *object,
                        *object2;
  SpecScan              *scan,
                        *scan2;

  for ( object = (sf->list).first; object; object=object->next) {
        scan = (SpecScan *) object->contents;

        lseek(sf->fd,scan->offset,SEEK_SET);
        read(sf->fd,buffer,sizeof(buffer));
        buffer[49] = '\0';

        for ( ptr = buffer+3,i=0; *ptr != ' ';ptr++,i++) buffer2[i] = *ptr;

        buffer2[i] = '\0';

        scan->scan_no = atol(buffer2);
        scan->order   = 1;
        for ( object2 = (sf->list).first; object2 != object; object2=object2->next) {
            scan2 = (SpecScan *) object2->contents;
            if (scan2->scan_no == scan->scan_no) scan->order++;
        }
  }
}

void
printCursor(SfCursor *cursor) {
   printf("<Cursor>\n");
   printf("   - Bytecnt:     %ld\n",cursor->bytecnt);
   printf("   - Cursor:      %ld\n",cursor->cursor);
   printf("   - Scanno:      %ld\n",cursor->scanno);
}
