text: /******************************************************************************
   *
   * Project:  ISO 8211 Access
   * Purpose:  Implements the DDFModule class.
   * Author:   Frank Warmerdam, warmerdam@pobox.com
   *
   ******************************************************************************
   * Copyright (c) 1999, Frank Warmerdam
   * Copyright (c) 2011-2013, Even Rouault <even dot rouault at mines-paris dot org>
   *
   * 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.
   ****************************************************************************/
  
  #include "cpl_port.h"
  #include "iso8211.h"
  
  #include <cstdio>
  #include <cstring>
  #if HAVE_FCNTL_H
  #  include <fcntl.h>
  #endif
  
  #include "cpl_conv.h"
  #include "cpl_error.h"
  #include "cpl_vsi.h"
  
  CPL_CVSID("$Id$")
  
  /************************************************************************/
  /*                             DDFModule()                              */
  /************************************************************************/
  
  /**
   * The constructor.
   */
  
  DDFModule::DDFModule() :
      fpDDF(nullptr),
      bReadOnly(TRUE),
      nFirstRecordOffset(0),
      _interchangeLevel('\0'),
      _inlineCodeExtensionIndicator('\0'),
      _versionNumber('\0'),
      _appIndicator('\0'),
      _fieldControlLength(9),
      _recLength(0),
      _leaderIden('L'),
      _fieldAreaStart(0),
      _sizeFieldLength(0),
      _sizeFieldPos(0),
      _sizeFieldTag(0),
      nFieldDefnCount(0),
      papoFieldDefns(nullptr),
      poRecord(nullptr),
      nCloneCount(0),
      nMaxCloneCount(0),
      papoClones(nullptr)
  {
      strcpy( _extendedCharSet, " ! " );
  }
  
  /************************************************************************/
  /*                             ~DDFModule()                             */
  /************************************************************************/
  
  /**
   * The destructor.
   */
  
  DDFModule::~DDFModule()
  
  {
      Close();
  }
  
  /************************************************************************/
  /*                               Close()                                */
  /*                                                                      */
  /*      Note that closing a file also destroys essentially all other    */
  /*      module datastructures.                                          */
  /************************************************************************/
  
  /**
   * Close an ISO 8211 file.
   */
  
  void DDFModule::Close()
  
  {
  /* -------------------------------------------------------------------- */
  /*      Close the file.                                                 */
  /* -------------------------------------------------------------------- */
      if( fpDDF != nullptr )
      {
          CPL_IGNORE_RET_VAL(VSIFCloseL( fpDDF ));
          fpDDF = nullptr;
      }
  
  /* -------------------------------------------------------------------- */
  /*      Cleanup the working record.                                     */
  /* -------------------------------------------------------------------- */
      if( poRecord != nullptr )
      {
          delete poRecord;
          poRecord = nullptr;
      }
  
  /* -------------------------------------------------------------------- */
  /*      Cleanup the clones.                                             */
  /* -------------------------------------------------------------------- */
      for( int i = 0; i < nCloneCount; i++ )
      {
          papoClones[i]->RemoveIsCloneFlag();
          delete papoClones[i];
      }
      nCloneCount = 0;
      nMaxCloneCount = 0;
      CPLFree( papoClones );
      papoClones = nullptr;
  
  /* -------------------------------------------------------------------- */
  /*      Cleanup the field definitions.                                  */
  /* -------------------------------------------------------------------- */
      for( int i = 0; i < nFieldDefnCount; i++ )
          delete papoFieldDefns[i];
      CPLFree( papoFieldDefns );
      papoFieldDefns = nullptr;
      nFieldDefnCount = 0;
  }
  
  /************************************************************************/
  /*                                Open()                                */
  /*                                                                      */
  /*      Open an ISO 8211 file, and read the DDR record to build the     */
  /*      field definitions.                                              */
  /************************************************************************/
  
  /**
   * Open a ISO 8211 (DDF) file for reading.
   *
   * If the open succeeds the data descriptive record (DDR) will have been
   * read, and all the field and subfield definitions will be available.
   *
   * @param pszFilename   The name of the file to open.
   * @param bFailQuietly If FALSE a CPL Error is issued for non-8211 files,
   * otherwise quietly return NULL.
   *
   * @return FALSE if the open fails or TRUE if it succeeds.  Errors messages
   * are issued internally with CPLError().
   */
  
  int DDFModule::Open( const char * pszFilename, int bFailQuietly )
  
  {
      constexpr int nLeaderSize = 24;
  
  /* -------------------------------------------------------------------- */
  /*      Close the existing file if there is one.                        */
  /* -------------------------------------------------------------------- */
      if( fpDDF != nullptr )
          Close();
  
  /* -------------------------------------------------------------------- */
  /*      Open the file.                                                  */
  /* -------------------------------------------------------------------- */
      fpDDF = VSIFOpenL( pszFilename, "rb" );
  
      if( fpDDF == nullptr )
      {
          if( !bFailQuietly )
              CPLError( CE_Failure, CPLE_OpenFailed,
                        "Unable to open DDF file `%s'.",
                        pszFilename );
          return FALSE;
      }
  
  /* -------------------------------------------------------------------- */
  /*      Read the 24 byte leader.                                        */
  /* -------------------------------------------------------------------- */
      char        achLeader[nLeaderSize];
  
      if( (int)VSIFReadL( achLeader, 1, nLeaderSize, fpDDF ) != nLeaderSize )
      {
          CPL_IGNORE_RET_VAL(VSIFCloseL( fpDDF ));
          fpDDF = nullptr;
  
          if( !bFailQuietly )
              CPLError( CE_Failure, CPLE_FileIO,
                        "Leader is short on DDF file `%s'.",
                        pszFilename );
  
          return FALSE;
      }
  
  /* -------------------------------------------------------------------- */
  /*      Verify that this appears to be a valid DDF file.                */
  /* -------------------------------------------------------------------- */
      int         i, bValid = TRUE;
  
      for( i = 0; i < nLeaderSize; i++ )
      {
          if( achLeader[i] < 32 || achLeader[i] > 126 )
              bValid = FALSE;
      }
  
      if( achLeader[5] != '1' && achLeader[5] != '2' && achLeader[5] != '3' )
          bValid = FALSE;
  
      if( achLeader[6] != 'L' )
          bValid = FALSE;
      if( achLeader[8] != '1' && achLeader[8] != ' ' )
          bValid = FALSE;
  
  /* -------------------------------------------------------------------- */
  /*      Extract information from leader.                                */
  /* -------------------------------------------------------------------- */
  
      if( bValid )
      {
          _recLength                        = DDFScanInt( achLeader+0, 5 );
          _interchangeLevel                 = achLeader[5];
          _leaderIden                   = achLeader[6];
          _inlineCodeExtensionIndicator = achLeader[7];
          _versionNumber                = achLeader[8];
          _appIndicator                 = achLeader[9];
          _fieldControlLength           = DDFScanInt(achLeader+10,2);
          _fieldAreaStart               = DDFScanInt(achLeader+12,5);
          _extendedCharSet[0]           = achLeader[17];
          _extendedCharSet[1]           = achLeader[18];
          _extendedCharSet[2]           = achLeader[19];
          _extendedCharSet[3]           = '\0';
          _sizeFieldLength              = DDFScanInt(achLeader+20,1);
          _sizeFieldPos                 = DDFScanInt(achLeader+21,1);
          _sizeFieldTag                 = DDFScanInt(achLeader+23,1);
  
          if( _recLength < nLeaderSize || _fieldControlLength <= 0
              || _fieldAreaStart < 24 || _sizeFieldLength <= 0
              || _sizeFieldPos <= 0 || _sizeFieldTag <= 0 )
          {
              bValid = FALSE;
          }
      }
  
  /* -------------------------------------------------------------------- */
  /*      If the header is invalid, then clean up, report the error       */
  /*      and return.                                                     */
  /* -------------------------------------------------------------------- */
      if( !bValid )
      {
          CPL_IGNORE_RET_VAL(VSIFCloseL( fpDDF ));
          fpDDF = nullptr;
  
          if( !bFailQuietly )
              CPLError( CE_Failure, CPLE_AppDefined,
                        "File `%s' does not appear to have\n"
                        "a valid ISO 8211 header.\n",
                        pszFilename );
          return FALSE;
      }
  
  /* -------------------------------------------------------------------- */
  /*      Read the whole record info memory.                              */
  /* -------------------------------------------------------------------- */
      char *pachRecord = (char *) CPLMalloc(_recLength);
      memcpy( pachRecord, achLeader, nLeaderSize );
  
      if( (int)VSIFReadL( pachRecord+nLeaderSize, 1, _recLength-nLeaderSize, fpDDF )
          != _recLength - nLeaderSize )
      {
          if( !bFailQuietly )
              CPLError( CE_Failure, CPLE_FileIO,
                        "Header record is short on DDF file `%s'.",
                        pszFilename );
  
          CPLFree( pachRecord );
          return FALSE;
      }
  
  /* -------------------------------------------------------------------- */
  /*      First make a pass counting the directory entries.               */
  /* -------------------------------------------------------------------- */
      int         nFieldEntryWidth, nFDCount = 0;
  
      nFieldEntryWidth = _sizeFieldLength + _sizeFieldPos + _sizeFieldTag;
  
      for( i = nLeaderSize; i + nFieldEntryWidth <= _recLength; i += nFieldEntryWidth )
      {
          if( pachRecord[i] == DDF_FIELD_TERMINATOR )
              break;
  
          nFDCount++;
      }
  
  /* -------------------------------------------------------------------- */
  /*      Allocate, and read field definitions.                           */
  /* -------------------------------------------------------------------- */
      for( i = 0; i < nFDCount; i++ )
      {
          char    szTag[128];
          int     nEntryOffset = nLeaderSize + i*nFieldEntryWidth;
          int     nFieldLength, nFieldPos;
  
          strncpy( szTag, pachRecord+nEntryOffset, _sizeFieldTag );
          szTag[_sizeFieldTag] = '\0';
  
          nEntryOffset += _sizeFieldTag;
          nFieldLength = DDFScanInt( pachRecord+nEntryOffset, _sizeFieldLength );
  
          nEntryOffset += _sizeFieldLength;
          nFieldPos = DDFScanInt( pachRecord+nEntryOffset, _sizeFieldPos );
  
          if (nFieldPos < 0 ||
              nFieldPos > INT_MAX - _fieldAreaStart ||
              nFieldLength < 2 || // DDFFieldDefn::Initialize() assumes at least 2 bytes
              _recLength - (_fieldAreaStart+nFieldPos) < nFieldLength)
          {
              if( !bFailQuietly )
                  CPLError( CE_Failure, CPLE_FileIO,
                          "Header record invalid on DDF file `%s'.",
                          pszFilename );
  
              CPLFree( pachRecord );
              return FALSE;
          }
  
          DDFFieldDefn *poFDefn = new DDFFieldDefn();
          if( poFDefn->Initialize( this, szTag, nFieldLength,
                                   pachRecord+_fieldAreaStart+nFieldPos ) )
              AddField( poFDefn );
          else
              delete poFDefn;
      }
  
      CPLFree( pachRecord );
  
  /* -------------------------------------------------------------------- */
  /*      Record the current file offset, the beginning of the first      */
  /*      data record.                                                    */
  /* -------------------------------------------------------------------- */
      nFirstRecordOffset = (long)VSIFTellL( fpDDF );
  
      return TRUE;
  }
  
  /************************************************************************/
  /*                             Initialize()                             */
  /************************************************************************/
  
  int DDFModule::Initialize( char chInterchangeLevel,
                             char chLeaderIden,
                             char chCodeExtensionIndicator,
                             char chVersionNumber,
                             char chAppIndicator,
                             const char *pszExtendedCharSet,
                             int nSizeFieldLength,
                             int nSizeFieldPos,
                             int nSizeFieldTag )
  
  {
      _interchangeLevel = chInterchangeLevel;
      _leaderIden = chLeaderIden;
      _inlineCodeExtensionIndicator = chCodeExtensionIndicator;
      _versionNumber = chVersionNumber;
      _appIndicator = chAppIndicator;
      snprintf( _extendedCharSet, sizeof(_extendedCharSet), "%s", pszExtendedCharSet );
      _sizeFieldLength = nSizeFieldLength;
      _sizeFieldPos = nSizeFieldPos;
      _sizeFieldTag = nSizeFieldTag;
  
      return TRUE;
  }
  
  /************************************************************************/
  /*                               Create()                               */
  /************************************************************************/
  
  int DDFModule::Create( const char *pszFilename )
  
  {
      CPLAssert( fpDDF == nullptr );
  
  /* -------------------------------------------------------------------- */
  /*      Create the file on disk.                                        */
  /* -------------------------------------------------------------------- */
      fpDDF = VSIFOpenL( pszFilename, "wb+" );
      if( fpDDF == nullptr )
      {
          CPLError( CE_Failure, CPLE_OpenFailed,
                    "Failed to create file %s, check path and permissions.",
                    pszFilename );
          return FALSE;
      }
  
      bReadOnly = FALSE;
  
  /* -------------------------------------------------------------------- */
  /*      Prepare all the field definition information.                   */
  /* -------------------------------------------------------------------- */
      int iField;
  
      _recLength = 24
          + nFieldDefnCount * (_sizeFieldLength+_sizeFieldPos+_sizeFieldTag)
          + 1;
  
      _fieldAreaStart = _recLength;
  
      for( iField=0; iField < nFieldDefnCount; iField++ )
      {
          int nLength;
  
          papoFieldDefns[iField]->GenerateDDREntry( this, nullptr, &nLength );
          _recLength += nLength;
      }
  
  /* -------------------------------------------------------------------- */
  /*      Setup 24 byte leader.                                           */
  /* -------------------------------------------------------------------- */
      char achLeader[25];
  
      snprintf( achLeader+0, sizeof(achLeader)-0, "%05d", (int) _recLength );
      achLeader[5] = _interchangeLevel;
      achLeader[6] = _leaderIden;
      achLeader[7] = _inlineCodeExtensionIndicator;
      achLeader[8] = _versionNumber;
      achLeader[9] = _appIndicator;
      snprintf( achLeader+10, sizeof(achLeader)-10, "%02d", (int) _fieldControlLength );
      snprintf( achLeader+12, sizeof(achLeader)-12, "%05d", (int) _fieldAreaStart );
      memcpy( achLeader+17, _extendedCharSet, 3 );
      snprintf( achLeader+20, sizeof(achLeader)-20, "%1d", (int) _sizeFieldLength );
      snprintf( achLeader+21, sizeof(achLeader)-21, "%1d", (int) _sizeFieldPos );
      achLeader[22] = '0';
      snprintf( achLeader+23, sizeof(achLeader)-23, "%1d", (int) _sizeFieldTag );
      int bRet = VSIFWriteL( achLeader, 24, 1, fpDDF ) > 0;
  
  /* -------------------------------------------------------------------- */
  /*      Write out directory entries.                                    */
  /* -------------------------------------------------------------------- */
      int nOffset = 0;
      for( iField=0; iField < nFieldDefnCount; iField++ )
      {
          char achDirEntry[255];
          char szFormat[32];
          int nLength;
  
          CPLAssert(_sizeFieldLength + _sizeFieldPos + _sizeFieldTag < (int)sizeof(achDirEntry));
  
          papoFieldDefns[iField]->GenerateDDREntry( this, nullptr, &nLength );
  
          CPLAssert( (int)strlen(papoFieldDefns[iField]->GetName()) == _sizeFieldTag );
          strcpy( achDirEntry, papoFieldDefns[iField]->GetName() );
          snprintf(szFormat, sizeof(szFormat), "%%0%dd", (int)_sizeFieldLength);
          snprintf( achDirEntry + _sizeFieldTag, sizeof(achDirEntry) - _sizeFieldTag,
                    szFormat, nLength );
          snprintf(szFormat, sizeof(szFormat), "%%0%dd", (int)_sizeFieldPos);
          snprintf( achDirEntry + _sizeFieldTag + _sizeFieldLength,
                    sizeof(achDirEntry) - _sizeFieldTag - _sizeFieldLength,
                    szFormat, nOffset );
          nOffset += nLength;
  
          bRet &= VSIFWriteL( achDirEntry, _sizeFieldLength + _sizeFieldPos + _sizeFieldTag, 1, fpDDF ) > 0;
      }
  
      char chUT = DDF_FIELD_TERMINATOR;
      bRet &= VSIFWriteL( &chUT, 1, 1, fpDDF ) > 0;
  
  /* -------------------------------------------------------------------- */
  /*      Write out the field descriptions themselves.                    */
  /* -------------------------------------------------------------------- */
      for( iField = 0; iField < nFieldDefnCount; iField++ )
      {
          char *pachData = nullptr;
          int nLength = 0;
  
          papoFieldDefns[iField]->GenerateDDREntry( this, &pachData, &nLength );
          bRet &= VSIFWriteL( pachData, nLength, 1, fpDDF ) > 0;
          CPLFree( pachData );
      }
  
      return bRet ? TRUE : FALSE;
  }
  
  /************************************************************************/
  /*                                Dump()                                */
  /************************************************************************/
  
  /**
   * Write out module info to debugging file.
   *
   * A variety of information about the module is written to the debugging
   * file.  This includes all the field and subfield definitions read from
   * the header.
   *
   * @param fp The standard IO file handle to write to.  i.e. stderr.
   */
  
  void DDFModule::Dump( FILE * fp )
  
  {
      fprintf( fp, "DDFModule:\n" );
      fprintf( fp, "    _recLength = %d\n", _recLength );
      fprintf( fp, "    _interchangeLevel = %c\n", _interchangeLevel );
      fprintf( fp, "    _leaderIden = %c\n", _leaderIden );
      fprintf( fp, "    _inlineCodeExtensionIndicator = %c\n",
               _inlineCodeExtensionIndicator );
      fprintf( fp, "    _versionNumber = %c\n", _versionNumber );
      fprintf( fp, "    _appIndicator = %c\n", _appIndicator );
      fprintf( fp, "    _extendedCharSet = `%s'\n", _extendedCharSet );
      fprintf( fp, "    _fieldControlLength = %d\n", _fieldControlLength );
      fprintf( fp, "    _fieldAreaStart = %d\n", _fieldAreaStart );
      fprintf( fp, "    _sizeFieldLength = %d\n", _sizeFieldLength );
      fprintf( fp, "    _sizeFieldPos = %d\n", _sizeFieldPos );
      fprintf( fp, "    _sizeFieldTag = %d\n", _sizeFieldTag );
  
      for( int i = 0; i < nFieldDefnCount; i++ )
      {
          papoFieldDefns[i]->Dump( fp );
      }
  }
  
  /************************************************************************/
  /*                           FindFieldDefn()                            */
  /************************************************************************/
  
  /**
   * Fetch the definition of the named field.
   *
   * This function will scan the DDFFieldDefn's on this module, to find
   * one with the indicated field name.
   *
   * @param pszFieldName The name of the field to search for.  The comparison is
   *                     case insensitive.
   *
   * @return A pointer to the request DDFFieldDefn object is returned, or NULL
   * if none matching the name are found.  The return object remains owned by
   * the DDFModule, and should not be deleted by application code.
   */
  
  DDFFieldDefn *DDFModule::FindFieldDefn( const char *pszFieldName )
  
  {
      int         i;
  
  /* -------------------------------------------------------------------- */
  /*      This pass tries to reduce the cost of comparing strings by      */
  /*      first checking the first character, and by using strcmp()       */
  /* -------------------------------------------------------------------- */
      for( i = 0; i < nFieldDefnCount; i++ )
      {
          const char *pszThisName = papoFieldDefns[i]->GetName();
  
          if( *pszThisName == *pszFieldName && *pszFieldName != '\0'
              && strcmp( pszFieldName+1, pszThisName+1) == 0 )
              return papoFieldDefns[i];
      }
  
  /* -------------------------------------------------------------------- */
  /*      Now do a more general check.  Application code may not          */
  /*      always use the correct name case.                               */
  /* -------------------------------------------------------------------- */
      for( i = 0; i < nFieldDefnCount; i++ )
      {
          if( EQUAL(pszFieldName, papoFieldDefns[i]->GetName()) )
              return papoFieldDefns[i];
      }
  
      return nullptr;
  }
  
  /************************************************************************/
  /*                             ReadRecord()                             */
  /*                                                                      */
  /*      Read one record from the file, and return to the                */
  /*      application.  The returned record is owned by the module,       */
  /*      and is reused from call to call in order to preserve headers    */
  /*      when they aren't being re-read from record to record.           */
  /************************************************************************/
  
  /**
   * Read one record from the file.
   *
   * @return A pointer to a DDFRecord object is returned, or NULL if a read
   * error, or end of file occurs.  The returned record is owned by the
   * module, and should not be deleted by the application.  The record is
   * only valid until the next ReadRecord() at which point it is overwritten.
   */
  
  DDFRecord *DDFModule::ReadRecord()
  
  {
      if( poRecord == nullptr )
          poRecord = new DDFRecord( this );
  
      if( poRecord->Read() )
          return poRecord;
      else
          return nullptr;
  }
  
  /************************************************************************/
  /*                              AddField()                              */
  /************************************************************************/
  
  /**
   * Add new field definition.
   *
   * Field definitions may only be added to DDFModules being used for
   * writing, not those being used for reading.  Ownership of the
   * DDFFieldDefn object is taken by the DDFModule.
   *
   * @param poNewFDefn definition to be added to the module.
   */
  
  void DDFModule::AddField( DDFFieldDefn *poNewFDefn )
  
  {
      nFieldDefnCount++;
      papoFieldDefns = (DDFFieldDefn **)
          CPLRealloc(papoFieldDefns, sizeof(void*)*nFieldDefnCount);
      papoFieldDefns[nFieldDefnCount-1] = poNewFDefn;
  }
  
  /************************************************************************/
  /*                              GetField()                              */
  /************************************************************************/
  
  /**
   * Fetch a field definition by index.
   *
   * @param i (from 0 to GetFieldCount() - 1.
   * @return the returned field pointer or NULL if the index is out of range.
   */
  
  DDFFieldDefn *DDFModule::GetField(int i)
  
  {
      if( i < 0 || i >= nFieldDefnCount )
          return nullptr;
      else
          return papoFieldDefns[i];
  }
  
  /************************************************************************/
  /*                           AddCloneRecord()                           */
  /*                                                                      */
  /*      We want to keep track of cloned records, so we can clean        */
  /*      them up when the module is destroyed.                           */
  /************************************************************************/
  
  void DDFModule::AddCloneRecord( DDFRecord * poRecordIn )
  
  {
  /* -------------------------------------------------------------------- */
  /*      Do we need to grow the container array?                         */
  /* -------------------------------------------------------------------- */
      if( nCloneCount == nMaxCloneCount )
      {
          nMaxCloneCount = nCloneCount*2 + 20;
          papoClones = (DDFRecord **) CPLRealloc(papoClones,
                                                 nMaxCloneCount * sizeof(void*));
      }
  
  /* -------------------------------------------------------------------- */
  /*      Add to the list.                                                */
  /* -------------------------------------------------------------------- */
      papoClones[nCloneCount++] = poRecordIn;
  }
  
  /************************************************************************/
  /*                         RemoveCloneRecord()                          */
  /************************************************************************/
  
  void DDFModule::RemoveCloneRecord( DDFRecord * poRecordIn )
  
  {
      int         i;
  
      for( i = 0; i < nCloneCount; i++ )
      {
          if( papoClones[i] == poRecordIn )
          {
              papoClones[i] = papoClones[nCloneCount-1];
              nCloneCount--;
              return;
          }
      }
  
      CPLAssert( false );
  }
  
  /************************************************************************/
  /*                               Rewind()                               */
  /************************************************************************/
  
  /**
   * Return to first record.
   *
   * The next call to ReadRecord() will read the first data record in the file.
   *
   * @param nOffset the offset in the file to return to.  By default this is
   * -1, a special value indicating that reading should return to the first
   * data record.  Otherwise it is an absolute byte offset in the file.
   */
  
  void DDFModule::Rewind( long nOffset )
  
  {
      if( nOffset == -1 )
          nOffset = nFirstRecordOffset;
  
      if( fpDDF == nullptr )
          return;
  
      if( VSIFSeekL( fpDDF, nOffset, SEEK_SET ) < 0 )
          return;
  
      if( nOffset == nFirstRecordOffset && poRecord != nullptr )
          poRecord->Clear();
  }
  