/**********************************************************************
 * $Id$
 *
 * Name:     avc_e00read.c
 * Project:  Arc/Info vector coverage (AVC)  BIN->E00 conversion library
 * Language: ANSI C
 * Purpose:  Functions to open a binary coverage and read it as if it
 *           was an ASCII E00 file.  This file is the main entry point
 *           for the library.
 * Author:   Daniel Morissette, dmorissette@dmsolutions.ca
 *
 **********************************************************************
 * Copyright (c) 1999-2005, Daniel Morissette
 *
 * SPDX-License-Identifier: MIT
 **********************************************************************
 *
 * $Log: avc_e00read.c,v $
 * Revision 1.28  2008/07/30 19:22:18  dmorissette
 * Move detection of EXP header directly in AVCE00ReadOpenE00() and use
 * VSIFGets() instead of CPLReadLine() to avoid problem with huge one line
 * files (GDAL/OGR ticket #1989)
 *
 * Revision 1.27  2008/07/30 18:35:53  dmorissette
 * Avoid scanning the whole E00 input file in AVCE00ReadOpenE00() if the
 * file does not start with an EXP line (GDAL/OGR ticket 1989)
 *
 * Revision 1.26  2008/07/30 16:17:46  dmorissette
 * Detect compressed E00 input files and refuse to open them instead of
 * crashing (bug 1928, GDAL/OGR ticket 2513)
 *
 * Revision 1.25  2008/07/24 20:34:12  dmorissette
 * Fixed VC++ WIN32 build problems in GDAL/OGR environment
 * (GDAL/OGR ticket http://trac.osgeo.org/gdal/ticket/2500)
 *
 * Revision 1.24  2008/07/24 13:49:20  dmorissette
 * Fixed GCC compiler warning (GDAL ticket #2495)
 *
 * Revision 1.23  2006/08/17 19:51:01  dmorissette
 * #include <unistd.h> to solve warning on 64 bit platforms (bug 1461)
 *
 * Revision 1.22  2006/08/17 18:56:42  dmorissette
 * Support for reading standalone info tables (just tables, no coverage
 * data) by pointing AVCE00ReadOpen() to the info directory (bug 1549).
 *
 * Revision 1.21  2006/06/27 18:38:43  dmorissette
 * Cleaned up E00 reading (bug 1497, patch from James F.)
 *
 * Revision 1.20  2006/06/27 18:06:34  dmorissette
 * Applied patch for EOP processing from James F. (bug 1497)
 *
 * Revision 1.19  2006/06/16 11:48:11  daniel
 * New functions to read E00 files directly as opposed to translating to
 * binary coverage. Used in the implementation of E00 read support in OGR.
 * Contributed by James E. Flemer. (bug 1497)
 *
 * Revision 1.18  2006/06/14 16:31:28  daniel
 * Added support for AVCCoverPC2 type (bug 1491)
 *
 * Revision 1.17  2005/06/03 03:49:58  daniel
 * Update email address, website url, and copyright dates
 *
 * Revision 1.16  2004/07/14 18:49:50  daniel
 * Fixed leak when trying to open something that's not a coverage (bug513)
 *
 * Revision 1.15  2002/08/27 15:46:15  daniel
 * Applied fix made in GDAL/OGR by 'aubin' (moved include ctype.h after avc.h)
 *
 * Revision 1.14  2000/09/22 19:45:21  daniel
 * Switch to MIT-style license
 *
 * Revision 1.13  2000/05/29 15:31:31  daniel
 * Added Japanese DBCS support
 *
 * Revision 1.12  2000/02/14 17:21:01  daniel
 * Made more robust for corrupted or invalid files in cover directory
 *
 * Revision 1.11  2000/02/02 04:26:04  daniel
 * Support reading TX6/TX7/RXP/RPL files in weird coverages
 *
 * Revision 1.10  2000/01/10 02:56:30  daniel
 * Added read support for "weird" coverages
 *
 * Revision 1.9  2000/01/07 07:12:49  daniel
 * Added support for reading PC Coverage TXT files
 *
 * Revision 1.8  1999/12/24 07:41:08  daniel
 * Check fname length before testing for extension in AVCE00ReadFindCoverType()
 *
 * Revision 1.7  1999/12/24 07:18:34  daniel
 * Added PC Arc/Info coverages support
 *
 * Revision 1.6  1999/08/26 17:22:18  daniel
 * Use VSIFopen() instead of fopen() directly
 *
 * Revision 1.5  1999/08/23 18:21:41  daniel
 * New syntax for AVCBinReadListTables()
 *
 * Revision 1.4  1999/05/11 02:10:01  daniel
 * Free psInfo struct inside AVCE00ReadClose()
 *
 * Revision 1.3  1999/04/06 19:43:26  daniel
 * Added E00 coverage path in EXP 0  header line
 *
 * Revision 1.2  1999/02/25 04:19:01  daniel
 * Added TXT, TX6/TX7, RXP and RPL support + other minor changes
 *
 * Revision 1.1  1999/01/29 16:28:52  daniel
 * Initial revision
 *
 **********************************************************************/

#include "avc.h"

#ifdef _WIN32
#include <direct.h> /* getcwd() */
#else
#include <unistd.h> /* getcwd() */
#endif

#include <ctype.h> /* toupper() */

// Should be 80 but let's be laxer
constexpr int knMAX_CHARS_PER_LINE = 1024;

static void _AVCE00ReadScanE00(AVCE00ReadE00Ptr psRead);
static int _AVCE00ReadBuildSqueleton(AVCE00ReadPtr psInfo,
                                     char **papszCoverDir);
static AVCCoverType _AVCE00ReadFindCoverType(char **papszCoverDir);

/**********************************************************************
 *                          AVCE00ReadOpen()
 *
 * Open a Arc/Info coverage to read it as if it was an E00 file.
 *
 * You can either pass the name of the coverage directory, or the path
 * to one of the files in the coverage directory.  The name of the
 * coverage MUST be included in pszCoverPath... this means that
 * passing "." is invalid.
 * The following are all valid values for pszCoverPath:
 *               /home/data/country
 *               /home/data/country/
 *               /home/data/country/arc.adf
 * (Of course you should replace the '/' with '\\' on DOS systems!)
 *
 * Returns a new AVCE00ReadPtr handle or nullptr if the coverage could
 * not be opened or if it does not appear to be a valid Arc/Info coverage.
 *
 * The handle will eventually have to be released with AVCE00ReadClose().
 **********************************************************************/
AVCE00ReadPtr AVCE00ReadOpen(const char *pszCoverPath)
{
    AVCE00ReadPtr psInfo;
    int i, nLen, nCoverPrecision;
    VSIStatBufL sStatBuf;
    char **papszCoverDir = nullptr;

    CPLErrorReset();

    /*-----------------------------------------------------------------
     * pszCoverPath must be either a valid directory name or a valid
     * file name.
     *----------------------------------------------------------------*/
    if (pszCoverPath == nullptr || strlen(pszCoverPath) == 0 ||
        VSIStatL(pszCoverPath, &sStatBuf) == -1)
    {
        CPLError(CE_Failure, CPLE_OpenFailed, "Invalid coverage path: %s.",
                 pszCoverPath ? pszCoverPath : "(nullptr)");
        return nullptr;
    }

    /*-----------------------------------------------------------------
     * Alloc the AVCE00ReadPtr handle
     *----------------------------------------------------------------*/
    psInfo = (AVCE00ReadPtr)CPLCalloc(1, sizeof(struct AVCE00ReadInfo_t));

    /*-----------------------------------------------------------------
     * 2 possibilities about the value passed in pszCoverPath:
     * - It can be the directory name of the coverage
     * - or it can be the path to one of the files in the coverage
     *
     * If the name passed in pszCoverPath is not a directory, then we
     * need to strip the last part of the filename to keep only the
     * path, terminated by a '/' (or a '\\').
     *----------------------------------------------------------------*/
    if (VSI_ISDIR(sStatBuf.st_mode))
    {
        /*-------------------------------------------------------------
         * OK, we have a valid directory name... make sure it is
         * terminated with a '/' (or '\\')
         *------------------------------------------------------------*/
        nLen = (int)strlen(pszCoverPath);

        if (pszCoverPath[nLen - 1] == '/' || pszCoverPath[nLen - 1] == '\\')
            psInfo->pszCoverPath = CPLStrdup(pszCoverPath);
        else
        {
#ifdef _WIN32
            psInfo->pszCoverPath = CPLStrdup(CPLSPrintf("%s\\", pszCoverPath));
#else
            psInfo->pszCoverPath = CPLStrdup(CPLSPrintf("%s/", pszCoverPath));
#endif
        }
    }
    else
    {
        /*-------------------------------------------------------------
         * We are dealing with a filename.
         * Extract the coverage path component and store it.
         * The coverage path will remain terminated by a '/' or '\\' char.
         *------------------------------------------------------------*/
        psInfo->pszCoverPath = CPLStrdup(pszCoverPath);

        for (i = (int)strlen(psInfo->pszCoverPath) - 1;
             i > 0 && psInfo->pszCoverPath[i] != '/' &&
             psInfo->pszCoverPath[i] != '\\';
             i--)
        {
        }

        psInfo->pszCoverPath[i + 1] = '\0';
    }

    /*-----------------------------------------------------------------
     * Extract the coverage name from the coverage path.  Note that
     * for this the coverage path must be in the form:
     * "dir1/dir2/dir3/covername/" ... if it is not the case, then
     * we would have to use getcwd() to find the current directory name...
     * but for now we'll just produce an error if this happens.
     *----------------------------------------------------------------*/
    nLen = 0;
    for (i = (int)strlen(psInfo->pszCoverPath) - 1;
         i > 0 && psInfo->pszCoverPath[i - 1] != '/' &&
         psInfo->pszCoverPath[i - 1] != '\\' &&
         psInfo->pszCoverPath[i - 1] != ':';
         i--)
    {
        nLen++;
    }

    if (nLen > 0)
    {
        psInfo->pszCoverName = CPLStrdup(psInfo->pszCoverPath + i);
        psInfo->pszCoverName[nLen] = '\0';
    }
    else
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                 "Invalid coverage path (%s): "
                 "coverage name must be included in path.",
                 pszCoverPath);

        CPLFree(psInfo->pszCoverPath);
        CPLFree(psInfo);
        return nullptr;
    }

    /*-----------------------------------------------------------------
     * Read the coverage directory listing and try to establish the cover type
     *----------------------------------------------------------------*/
    papszCoverDir = VSIReadDir(psInfo->pszCoverPath);

    psInfo->eCoverType = _AVCE00ReadFindCoverType(papszCoverDir);

    if (psInfo->eCoverType == AVCCoverTypeUnknown)
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                 "Invalid coverage (%s): directory does not appear to "
                 "contain any supported vector coverage file.",
                 pszCoverPath);
        CPLFree(psInfo->pszCoverName);
        CPLFree(psInfo->pszCoverPath);
        CPLFree(psInfo->pszInfoPath);
        CPLFree(psInfo);
        CSLDestroy(papszCoverDir);
        return nullptr;
    }

    /*-----------------------------------------------------------------
     * INFO path: PC Coverages have all files in the same dir, and unix
     * covers have the INFO files in ../info
     *----------------------------------------------------------------*/
    if (psInfo->eCoverType == AVCCoverPC || psInfo->eCoverType == AVCCoverPC2)
    {
        psInfo->pszInfoPath = CPLStrdup(psInfo->pszCoverPath);
    }
    else
    {
        /*-------------------------------------------------------------
         * Lazy way to build the INFO path: simply add "../info/"...
         * this could probably be improved!
         *------------------------------------------------------------*/
        size_t nInfoPathLen = strlen(psInfo->pszCoverPath) + 9;
        psInfo->pszInfoPath = (char *)CPLMalloc(nInfoPathLen);
#ifdef _WIN32
#define AVC_INFOPATH "..\\info\\"
#else
#define AVC_INFOPATH "../info/"
#endif
        snprintf(psInfo->pszInfoPath, nInfoPathLen, "%s%s",
                 psInfo->pszCoverPath, AVC_INFOPATH);

        AVCAdjustCaseSensitiveFilename(psInfo->pszInfoPath);
    }

    /*-----------------------------------------------------------------
     * For Unix coverages, check that the info directory exists and
     * contains the "arc.dir".  In AVCCoverWeird, the arc.dir is
     * called "../INFO/ARCDR9".
     * PC Coverages have their info tables in the same directory as
     * the coverage files.
     *----------------------------------------------------------------*/
    if (((psInfo->eCoverType == AVCCoverV7 ||
          psInfo->eCoverType == AVCCoverV7Tables) &&
         !AVCFileExists(psInfo->pszInfoPath, "arc.dir")) ||
        (psInfo->eCoverType == AVCCoverWeird &&
         !AVCFileExists(psInfo->pszInfoPath, "arcdr9")))
    {
        CPLError(
            CE_Failure, CPLE_OpenFailed,
            "Invalid coverage (%s): 'info' directory not found or invalid.",
            pszCoverPath);
        CPLFree(psInfo->pszCoverName);
        CPLFree(psInfo->pszCoverPath);
        CPLFree(psInfo->pszInfoPath);
        CPLFree(psInfo);
        CSLDestroy(papszCoverDir);
        return nullptr;
    }

    /*-----------------------------------------------------------------
     * Make sure there was no error until now before we build skeleton.
     *----------------------------------------------------------------*/
    if (CPLGetLastErrorNo() != 0)
    {
        CPLFree(psInfo->pszCoverName);
        CPLFree(psInfo->pszCoverPath);
        CPLFree(psInfo->pszInfoPath);
        CPLFree(psInfo);
        CSLDestroy(papszCoverDir);
        return nullptr;
    }

    /*-----------------------------------------------------------------
     * Build the E00 file skeleton and be ready to return a E00 header...
     * We'll also read the coverage precision by the same way.
     *----------------------------------------------------------------*/
    nCoverPrecision = _AVCE00ReadBuildSqueleton(psInfo, papszCoverDir);

    /* Ignore warnings produced while building skeleton */
    CPLErrorReset();

    CSLDestroy(papszCoverDir);
    papszCoverDir = nullptr;

    psInfo->iCurSection = 0;
    psInfo->iCurStep = AVC_GEN_NOTSTARTED;
    psInfo->bReadAllSections = TRUE;

    /*-----------------------------------------------------------------
     * Init the E00 generator.
     *----------------------------------------------------------------*/
    psInfo->hGenInfo = AVCE00GenInfoAlloc(nCoverPrecision);

    /*-----------------------------------------------------------------
     * Init multibyte encoding info
     *----------------------------------------------------------------*/
    psInfo->psDBCSInfo = AVCAllocDBCSInfo();

    /*-----------------------------------------------------------------
     * If an error happened during the open call, cleanup and return nullptr.
     *----------------------------------------------------------------*/
    if (CPLGetLastErrorNo() != 0)
    {
        AVCE00ReadClose(psInfo);
        psInfo = nullptr;
    }

    return psInfo;
}

/**********************************************************************
 *                          AVCE00ReadOpenE00()
 *
 * Open a E00 file for reading.
 *
 * Returns a new AVCE00ReadE00Ptr handle or nullptr if the file could
 * not be opened or if it does not appear to be a valid E00 file.
 *
 * The handle will eventually have to be released with
 * AVCE00ReadCloseE00().
 **********************************************************************/
AVCE00ReadE00Ptr AVCE00ReadOpenE00(const char *pszE00FileName)
{
    AVCE00ReadE00Ptr psRead;
    VSIStatBufL sStatBuf;
    VSILFILE *fp;
    char *p;
    char szHeader[10];

    CPLErrorReset();

    /*-----------------------------------------------------------------
     * pszE00FileName must be a valid file that can be opened for
     * reading
     *----------------------------------------------------------------*/
    if (pszE00FileName == nullptr || strlen(pszE00FileName) == 0 ||
        VSIStatL(pszE00FileName, &sStatBuf) == -1 ||
        VSI_ISDIR(sStatBuf.st_mode))
    {
        CPLError(CE_Failure, CPLE_OpenFailed, "Invalid E00 file path: %s.",
                 pszE00FileName ? pszE00FileName : "(nullptr)");
        return nullptr;
    }

    if (nullptr == (fp = VSIFOpenL(pszE00FileName, "r")))
        return nullptr;

    /*-----------------------------------------------------------------
     * Make sure the file starts with a "EXP  0" or "EXP  1" header
     *----------------------------------------------------------------*/
    memset(szHeader, 0, sizeof(szHeader));
    if (VSIFReadL(szHeader, 5, 1, fp) == 0 || !STARTS_WITH_CI(szHeader, "EXP "))
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                 "This does not look like a E00 file: does not start with "
                 "a EXP header.");
        VSIFCloseL(fp);
        return nullptr;
    }
    VSIRewindL(fp);

    /*-----------------------------------------------------------------
     * Alloc the AVCE00ReadE00Ptr handle
     *----------------------------------------------------------------*/
    psRead = (AVCE00ReadE00Ptr)CPLCalloc(1, sizeof(struct AVCE00ReadInfoE00_t));

    psRead->hFile = fp;
    psRead->pszCoverPath = CPLStrdup(pszE00FileName);
    psRead->eCurFileType = AVCFileUnknown;

    /*-----------------------------------------------------------------
     * Extract the coverage name from the coverage path.
     *----------------------------------------------------------------*/
    if (nullptr != (p = strrchr(psRead->pszCoverPath, '/')) ||
        nullptr != (p = strrchr(psRead->pszCoverPath, '\\')) ||
        nullptr != (p = strrchr(psRead->pszCoverPath, ':')))
    {
        psRead->pszCoverName = CPLStrdup(p + 1);
    }
    else
    {
        psRead->pszCoverName = CPLStrdup(psRead->pszCoverPath);
    }
    if (nullptr != (p = strrchr(psRead->pszCoverName, '.')))
    {
        *p = '\0';
    }

    /*-----------------------------------------------------------------
     * Make sure there was no error until now before we scan file.
     *----------------------------------------------------------------*/
    if (CPLGetLastErrorNo() != 0)
    {
        AVCE00ReadCloseE00(psRead);
        return nullptr;
    }

    psRead->hParseInfo = AVCE00ParseInfoAlloc();

    /*-----------------------------------------------------------------
     * Scan the E00 file for sections
     *----------------------------------------------------------------*/
    _AVCE00ReadScanE00(psRead);
    if (CPLGetLastErrorNo() != 0)
    {
        AVCE00ReadCloseE00(psRead);
        return nullptr;
    }

    AVCE00ReadRewindE00(psRead);
    CPLErrorReset();

    if (psRead->numSections < 1)
    {
        AVCE00ReadCloseE00(psRead);
        return nullptr;
    }

    psRead->bReadAllSections = TRUE;

    /*-----------------------------------------------------------------
     * If an error happened during the open call, cleanup and return nullptr.
     *----------------------------------------------------------------*/
    if (CPLGetLastErrorNo() != 0)
    {
        AVCE00ReadCloseE00(psRead);
        psRead = nullptr;
    }

    return psRead;
}

/**********************************************************************
 *                          AVCE00ReadClose()
 *
 * Close a coverage and release all memory used by the AVCE00ReadPtr
 * handle.
 **********************************************************************/
void AVCE00ReadClose(AVCE00ReadPtr psInfo)
{
    CPLErrorReset();

    if (psInfo == nullptr)
        return;

    CPLFree(psInfo->pszCoverPath);
    CPLFree(psInfo->pszInfoPath);
    CPLFree(psInfo->pszCoverName);

    if (psInfo->hFile)
        AVCBinReadClose(psInfo->hFile);

    if (psInfo->hGenInfo)
        AVCE00GenInfoFree(psInfo->hGenInfo);

    if (psInfo->pasSections)
    {
        int i;
        for (i = 0; i < psInfo->numSections; i++)
        {
            CPLFree(psInfo->pasSections[i].pszName);
            CPLFree(psInfo->pasSections[i].pszFilename);
        }
        CPLFree(psInfo->pasSections);
    }

    AVCFreeDBCSInfo(psInfo->psDBCSInfo);

    CPLFree(psInfo);
}

/**********************************************************************
 *                          AVCE00ReadCloseE00()
 *
 * Close a coverage and release all memory used by the AVCE00ReadE00Ptr
 * handle.
 **********************************************************************/
void AVCE00ReadCloseE00(AVCE00ReadE00Ptr psRead)
{
    if (psRead == nullptr)
        return;

    CPLFree(psRead->pszCoverPath);
    CPLFree(psRead->pszCoverName);

    if (psRead->hFile)
    {
        VSIFCloseL(psRead->hFile);
        psRead->hFile = nullptr;
    }

    if (psRead->pasSections)
    {
        int i;
        for (i = 0; i < psRead->numSections; i++)
        {
            CPLFree(psRead->pasSections[i].pszName);
            CPLFree(psRead->pasSections[i].pszFilename);
        }
        CPLFree(psRead->pasSections);
    }

    /* These Free calls handle nullptr's */
    AVCE00ParseInfoFree(psRead->hParseInfo);
    psRead->hParseInfo = nullptr;

    CPLFree(psRead);
}

/**********************************************************************
 *                          _AVCIncreaseSectionsArray()
 *
 * Add a number of structures to the Sections array and return the
 * index of the first one that was added.  Note that the address of the
 * original array (*pasArray) is quite likely to change!
 *
 * The value of *pnumItems will be updated to reflect the new array size.
 **********************************************************************/
static int _AVCIncreaseSectionsArray(AVCE00Section **pasArray, int *pnumItems,
                                     int numToAdd)
{
    int i;

    *pasArray = (AVCE00Section *)CPLRealloc(
        *pasArray, (*pnumItems + numToAdd) * sizeof(AVCE00Section));

    for (i = 0; i < numToAdd; i++)
    {
        (*pasArray)[*pnumItems + i].eType = AVCFileUnknown;
        (*pasArray)[*pnumItems + i].pszName = nullptr;
        (*pasArray)[*pnumItems + i].pszFilename = nullptr;
        (*pasArray)[*pnumItems + i].nLineNum = 0;
        (*pasArray)[*pnumItems + i].nFeatureCount = -1;
    }

    i = *pnumItems;
    (*pnumItems) += numToAdd;

    return i;
}

/**********************************************************************
 *                         _AVCE00ReadFindCoverType()
 *
 * This functions tries to establish the coverage type by looking
 * at the coverage directory listing passed as argument.
 *
 * Returns one of AVCCoverV7 for Arc/Info V7 (Unix) coverages, or
 *                AVCCoverPC for PC Arc/Info coverages.
 *                AVCCoverWeird for an hybrid between V7 and PC
 *
 * If coverage type cannot be established then AVCCoverTypeUnknown is
 * returned.
 **********************************************************************/
static AVCCoverType _AVCE00ReadFindCoverType(char **papszCoverDir)
{
    int i, nLen;
    GBool bFoundAdfFile = FALSE, bFoundArcFile = FALSE, bFoundTableFile = FALSE,
          bFoundDbfFile = FALSE, bFoundArcDirFile = FALSE;

    /*-----------------------------------------------------------------
     * Scan the list of files, looking for well known filenames.
     * Start with the funky types first...
     *----------------------------------------------------------------*/
    for (i = 0; papszCoverDir && papszCoverDir[i]; i++)
    {
        nLen = (int)strlen(papszCoverDir[i]);
        if (nLen > 4 && EQUAL(papszCoverDir[i] + nLen - 4, ".adf"))
        {
            bFoundAdfFile = TRUE;
        }
        else if (nLen > 4 && EQUAL(papszCoverDir[i] + nLen - 4, ".dbf"))
        {
            bFoundDbfFile = TRUE;
        }
        else if (EQUAL(papszCoverDir[i], "arc") ||
                 EQUAL(papszCoverDir[i], "cnt") ||
                 EQUAL(papszCoverDir[i], "pal") ||
                 EQUAL(papszCoverDir[i], "lab") ||
                 EQUAL(papszCoverDir[i], "prj") ||
                 EQUAL(papszCoverDir[i], "tol"))
        {
            bFoundArcFile = TRUE;
        }
        else if (EQUAL(papszCoverDir[i], "aat") ||
                 EQUAL(papszCoverDir[i], "pat") ||
                 EQUAL(papszCoverDir[i], "bnd") ||
                 EQUAL(papszCoverDir[i], "tic"))
        {
            bFoundTableFile = TRUE;
        }
        else if (EQUAL(papszCoverDir[i], "arc.dir"))
        {
            bFoundArcDirFile = TRUE;
        }
    }

    /*-----------------------------------------------------------------
     * Check for PC Arc/Info coverage - variant 1.
     * These PC coverages have files with no extension (e.g. "ARC","PAL",...)
     * and their tables filenames are in the form "???.dbf"
     *----------------------------------------------------------------*/
    if (bFoundArcFile && bFoundDbfFile)
        return AVCCoverPC;

    /*-----------------------------------------------------------------
     * Check for PC Arc/Info coverage - variant 2.
     * looks like a hybrid between AVCCoverPC and AVCCoverV7
     * These PC coverages have files with .adf extension (e.g."ARC.ADF"),
     * and their tables filenames are in the form "???.dbf"
     *----------------------------------------------------------------*/
    if (bFoundAdfFile && bFoundDbfFile)
        return AVCCoverPC2;

    /*-----------------------------------------------------------------
     * Check for the weird coverages.
     * Their coverage files have no extension just like PC Coverages,
     * and their tables have 3 letters filenames with no extension
     * either (e.g. "AAT", "PAT", etc.)
     * They also have a ../info directory, but we don't really need
     * to check that (not yet!).
     *----------------------------------------------------------------*/
    if (bFoundArcFile && bFoundTableFile)
        return AVCCoverWeird;

    /*-----------------------------------------------------------------
     * V7 Coverages... they are the easiest to recognize
     * because of the ".adf" file extension
     *----------------------------------------------------------------*/
    if (bFoundAdfFile)
        return AVCCoverV7;

    /*-----------------------------------------------------------------
     * Standalone info tables.
     * We were pointed at the "info" directory. We'll treat this as
     * a coverage with just info tables.
     *----------------------------------------------------------------*/
    if (bFoundArcDirFile)
        return AVCCoverV7Tables;

    return AVCCoverTypeUnknown;
}

/**********************************************************************
 *                         _AVCE00ReadAddJabberwockySection()
 *
 * Add to the skeleton a section that contains subsections
 * for all the files with a given extension.
 *
 * Returns Updated Coverage precision
 **********************************************************************/
static int _AVCE00ReadAddJabberwockySection(
    AVCE00ReadPtr psInfo, AVCFileType eFileType, const char *pszSectionName,
    int nCoverPrecision, const char *pszFileExtension, char **papszCoverDir)
{
    int iSect, iDirEntry, nLen, nExtLen;
    GBool bFoundFiles = FALSE;
    AVCBinFile *psFile = nullptr;

    nExtLen = (int)strlen(pszFileExtension);

    /*-----------------------------------------------------------------
     * Scan the directory for files with a ".txt" extension.
     *----------------------------------------------------------------*/

    for (iDirEntry = 0; papszCoverDir && papszCoverDir[iDirEntry]; iDirEntry++)
    {
        nLen = (int)strlen(papszCoverDir[iDirEntry]);

        if (nLen > nExtLen &&
            EQUAL(papszCoverDir[iDirEntry] + nLen - nExtLen,
                  pszFileExtension) &&
            (psFile = AVCBinReadOpen(
                 psInfo->pszCoverPath, papszCoverDir[iDirEntry],
                 psInfo->eCoverType, eFileType, psInfo->psDBCSInfo)) != nullptr)
        {
            if (nCoverPrecision == AVC_DEFAULT_PREC)
                nCoverPrecision = psFile->nPrecision;
            AVCBinReadClose(psFile);

            if (bFoundFiles == FALSE)
            {
                /* Insert a "TX6 #" header before the first TX6 file
                 */
                iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                                  &(psInfo->numSections), 1);
                psInfo->pasSections[iSect].eType = AVCFileUnknown;

                psInfo->pasSections[iSect].pszName = CPLStrdup(CPLSPrintf(
                    "%s  %c", pszSectionName,
                    (nCoverPrecision == AVC_DOUBLE_PREC) ? '3' : '2'));

                bFoundFiles = TRUE;
            }

            /* Add this file to the skeleton
             */
            iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                              &(psInfo->numSections), 1);

            psInfo->pasSections[iSect].eType = eFileType;
            psInfo->pasSections[iSect].pszFilename =
                CPLStrdup(papszCoverDir[iDirEntry]);

            /* pszName will contain only the classname without the file
             * extension */
            psInfo->pasSections[iSect].pszName =
                CPLStrdup(papszCoverDir[iDirEntry]);
            psInfo->pasSections[iSect].pszName[nLen - nExtLen] = '\0';
        }
    }

    if (bFoundFiles)
    {
        /* Add a line to close the TX6 section.
         */
        iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                          &(psInfo->numSections), 1);
        psInfo->pasSections[iSect].eType = AVCFileUnknown;
        psInfo->pasSections[iSect].pszName = CPLStrdup("JABBERWOCKY");
    }

    return nCoverPrecision;
}

/**********************************************************************
 *                     _AVCE00ReadNextLineE00()
 *
 * Processes the next line of input from the E00 file.
 * (See AVCE00WriteNextLine() for similar processing.)
 *
 * Returns the next object from the E00 file, or nullptr.
 **********************************************************************/
static void *_AVCE00ReadNextLineE00(AVCE00ReadE00Ptr psRead,
                                    const char *pszLine)
{
    /* int nStatus = 0; */
    void *psObj = nullptr;

    AVCE00ParseInfo *psInfo = psRead->hParseInfo;

    CPLErrorReset();

    ++psInfo->nCurLineNum;

    if (psInfo->bForceEndOfSection)
    {
        /*-------------------------------------------------------------
         * The last call encountered an implicit end of section, so
         * we close the section now without waiting for an end-of-section
         * line (there won't be any!)... and get ready to proceed with
         * the next section.
         * This is used for TABLEs.
         *------------------------------------------------------------*/
        AVCE00ParseSectionEnd(psInfo, pszLine, TRUE);
        psRead->eCurFileType = AVCFileUnknown;
    }

    /*-----------------------------------------------------------------
     * If we're at the top level inside a supersection... check if this
     * supersection ends here.
     *----------------------------------------------------------------*/
    if (AVCE00ParseSuperSectionEnd(psInfo, pszLine) == TRUE)
    {
        /* Nothing to do... it is all been done by the call to
         * AVCE00ParseSuperSectionEnd()
         */
    }
    else if (psRead->eCurFileType == AVCFileUnknown)
    {
        /*-------------------------------------------------------------
         * We're at the top level or inside a supersection... waiting
         * to encounter a valid section or supersection header
         * (i.e. "ARC  2", etc...)
         *------------------------------------------------------------*/

        /*-------------------------------------------------------------
         * First check for a supersection header (TX6, RXP, IFO, ...)
         *------------------------------------------------------------*/
        if (AVCE00ParseSuperSectionHeader(psInfo, pszLine) == AVCFileUnknown)
        {
            /*---------------------------------------------------------
             * This was not a supersection header... check if it is a simple
             * section header
             *--------------------------------------------------------*/
            psRead->eCurFileType = AVCE00ParseSectionHeader(psInfo, pszLine);
        }
        else
        {
            /* got supersection */
        }

        if (psRead->eCurFileType == AVCFileTABLE)
        {
            /*---------------------------------------------------------
             * send the first header line to the parser and wait until
             * the whole header has been read.
             *--------------------------------------------------------*/
            AVCE00ParseNextLine(psInfo, pszLine);
        }
        else if (psRead->eCurFileType != AVCFileUnknown)
        {
            /*---------------------------------------------------------
             * found a valid section header
             *--------------------------------------------------------*/
        }
    }
    else if (psRead->eCurFileType == AVCFileTABLE && !psInfo->bTableHdrComplete)
    {
        /*-------------------------------------------------------------
         * We're reading a TABLE header... continue reading lines
         * from the header
         *
         * Note: When parsing a TABLE, the first object returned will
         * be the AVCTableDef, then data records will follow.
         *------------------------------------------------------------*/
        psObj = AVCE00ParseNextLine(psInfo, pszLine);
        if (psObj)
        {
            /* got table header */
            /* TODO: Enable return of table definition? */
            psObj = nullptr;
        }
    }
    else
    {
        /*-------------------------------------------------------------
         * We're are in the middle of a section... first check if we
         * have reached the end.
         *
         * note: The first call to AVCE00ParseSectionEnd() with FALSE will
         *       not reset the parser until we close the file... and then
         *       we call the function again to reset the parser.
         *------------------------------------------------------------*/
        if (AVCE00ParseSectionEnd(psInfo, pszLine, FALSE))
        {
            psRead->eCurFileType = AVCFileUnknown;
            AVCE00ParseSectionEnd(psInfo, pszLine, TRUE);
        }
        else
        /*-------------------------------------------------------------
         * ... not at the end yet, so continue reading objects.
         *------------------------------------------------------------*/
        {
            psObj = AVCE00ParseNextLine(psInfo, pszLine);

            if (psObj)
            {
                /* got object */
            }
        }
    }

#if 0
    if (CPLGetLastErrorNo() != 0)
        nStatus = -1;
#endif

    return psObj;
}

/**********************************************************************
 *                         _AVCE00ReadBuildSqueleton()
 *
 * Build the skeleton of the E00 file corresponding to the specified
 * coverage and set the appropriate fields in the AVCE00ReadPtr struct.
 *
 * Note that the order of the sections in the skeleton is important
 * since some software may rely on this ordering when they read E00 files.
 *
 * The function returns the coverage precision that it will read from one
 * of the file headers.
 **********************************************************************/
static int _AVCE00ReadBuildSqueleton(AVCE00ReadPtr psInfo, char **papszCoverDir)
{
    int iTable, numTables, iFile, nLen;
    char **papszTables, **papszFiles, szCWD[75] = "", *pcTmp;
    char *pszEXPPath = nullptr;
    int nCoverPrecision = AVC_DEFAULT_PREC;
    char cPrecisionCode = '2';
    const char *szFname = nullptr;
    AVCBinFile *psFile = nullptr;

    psInfo->numSections = 0;
    psInfo->pasSections = nullptr;

    /*-----------------------------------------------------------------
     * Build the absolute coverage path to include in the EXP  0 line
     * This line usually contains the full path of the E00 file that
     * is being created, but since the lib does not write the output
     * file directly, there is no simple way to get that value.  Instead,
     * we will use the absolute coverage path to which we add a .E00
     * extension.
     * We need also make sure cover path is all in uppercase.
     *----------------------------------------------------------------*/
#ifdef _WIN32
    if (psInfo->pszCoverPath[0] != '\\' &&
        !(isalpha((unsigned char)psInfo->pszCoverPath[0]) &&
          psInfo->pszCoverPath[1] == ':'))
#else
    if (psInfo->pszCoverPath[0] != '/')
#endif
    {
        if (getcwd(szCWD, 74) == nullptr)
            szCWD[0] = '\0'; /* Failed: buffer may be too small */

        nLen = (int)strlen(szCWD);

#ifdef _WIN32
        if (nLen > 0 && szCWD[nLen - 1] != '\\')
            strcat(szCWD, "\\");
#else
        if (nLen > 0 && szCWD[nLen - 1] != '/')
            strcat(szCWD, "/");
#endif
    }

    CPLString osCoverPathTruncated(psInfo->pszCoverPath);
    osCoverPathTruncated.pop_back();
    pszEXPPath = CPLStrdup(
        CPLSPrintf("EXP  0 %s%s.E00", szCWD, osCoverPathTruncated.c_str()));
    pcTmp = pszEXPPath;
    for (; *pcTmp != '\0'; pcTmp++)
        *pcTmp = (char)CPLToupper(static_cast<unsigned char>(*pcTmp));

    /*-----------------------------------------------------------------
     * EXP Header
     *----------------------------------------------------------------*/
    {
        const int iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                                    &(psInfo->numSections), 1);
        psInfo->pasSections[iSect].eType = AVCFileUnknown;
        psInfo->pasSections[iSect].pszName = pszEXPPath;
    }

    /*-----------------------------------------------------------------
     * We have to try to open each file as we go for 2 reasons:
     * - To validate the file's signature in order to detect cases like a user
     *   that places files such as "mystuff.txt" in the cover directory...
     *   this has already happened and obviously lead to problems!)
     * - We also need to find the coverage's precision from the headers
     *----------------------------------------------------------------*/

    /*-----------------------------------------------------------------
     * ARC section (arc.adf)
     *----------------------------------------------------------------*/
    szFname =
        (psInfo->eCoverType == AVCCoverV7 || psInfo->eCoverType == AVCCoverPC2)
            ? "arc.adf"
            : "arc";
    if ((iFile = CSLFindString(papszCoverDir, szFname)) != -1 &&
        (psFile =
             AVCBinReadOpen(psInfo->pszCoverPath, szFname, psInfo->eCoverType,
                            AVCFileARC, psInfo->psDBCSInfo)) != nullptr)
    {
        if (nCoverPrecision == AVC_DEFAULT_PREC)
            nCoverPrecision = psFile->nPrecision;
        AVCBinReadClose(psFile);
        const int iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                                    &(psInfo->numSections), 1);

        psInfo->pasSections[iSect].eType = AVCFileARC;
        psInfo->pasSections[iSect].pszName = CPLStrdup("ARC");
        psInfo->pasSections[iSect].pszFilename =
            CPLStrdup(papszCoverDir[iFile]);
    }

    /*-----------------------------------------------------------------
     * CNT section (cnt.adf)
     *----------------------------------------------------------------*/
    szFname =
        (psInfo->eCoverType == AVCCoverV7 || psInfo->eCoverType == AVCCoverPC2)
            ? "cnt.adf"
            : "cnt";
    if ((iFile = CSLFindString(papszCoverDir, szFname)) != -1 &&
        (psFile =
             AVCBinReadOpen(psInfo->pszCoverPath, szFname, psInfo->eCoverType,
                            AVCFileCNT, psInfo->psDBCSInfo)) != nullptr)
    {
        if (nCoverPrecision == AVC_DEFAULT_PREC)
            nCoverPrecision = psFile->nPrecision;
        AVCBinReadClose(psFile);
        const int iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                                    &(psInfo->numSections), 1);

        psInfo->pasSections[iSect].eType = AVCFileCNT;
        psInfo->pasSections[iSect].pszName = CPLStrdup("CNT");
        psInfo->pasSections[iSect].pszFilename =
            CPLStrdup(papszCoverDir[iFile]);
    }

    /*-----------------------------------------------------------------
     * LAB section (lab.adf)
     *----------------------------------------------------------------*/
    szFname =
        (psInfo->eCoverType == AVCCoverV7 || psInfo->eCoverType == AVCCoverPC2)
            ? "lab.adf"
            : "lab";
    if ((iFile = CSLFindString(papszCoverDir, szFname)) != -1 &&
        (psFile =
             AVCBinReadOpen(psInfo->pszCoverPath, szFname, psInfo->eCoverType,
                            AVCFileLAB, psInfo->psDBCSInfo)) != nullptr)
    {
        if (nCoverPrecision == AVC_DEFAULT_PREC)
            nCoverPrecision = psFile->nPrecision;
        AVCBinReadClose(psFile);
        const int iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                                    &(psInfo->numSections), 1);

        psInfo->pasSections[iSect].eType = AVCFileLAB;
        psInfo->pasSections[iSect].pszName = CPLStrdup("LAB");
        psInfo->pasSections[iSect].pszFilename =
            CPLStrdup(papszCoverDir[iFile]);
    }

    /*-----------------------------------------------------------------
     * PAL section (pal.adf)
     *----------------------------------------------------------------*/
    szFname =
        (psInfo->eCoverType == AVCCoverV7 || psInfo->eCoverType == AVCCoverPC2)
            ? "pal.adf"
            : "pal";
    if ((iFile = CSLFindString(papszCoverDir, szFname)) != -1 &&
        (psFile =
             AVCBinReadOpen(psInfo->pszCoverPath, szFname, psInfo->eCoverType,
                            AVCFilePAL, psInfo->psDBCSInfo)) != nullptr)
    {
        if (nCoverPrecision == AVC_DEFAULT_PREC)
            nCoverPrecision = psFile->nPrecision;
        AVCBinReadClose(psFile);
        const int iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                                    &(psInfo->numSections), 1);

        psInfo->pasSections[iSect].eType = AVCFilePAL;
        psInfo->pasSections[iSect].pszName = CPLStrdup("PAL");
        psInfo->pasSections[iSect].pszFilename =
            CPLStrdup(papszCoverDir[iFile]);
    }

    /*-----------------------------------------------------------------
     * TOL section (tol.adf for single precision, par.adf for double)
     *----------------------------------------------------------------*/
    szFname =
        (psInfo->eCoverType == AVCCoverV7 || psInfo->eCoverType == AVCCoverPC2)
            ? "tol.adf"
            : "tol";
    if ((iFile = CSLFindString(papszCoverDir, szFname)) != -1 &&
        (psFile =
             AVCBinReadOpen(psInfo->pszCoverPath, szFname, psInfo->eCoverType,
                            AVCFileTOL, psInfo->psDBCSInfo)) != nullptr)
    {
        if (nCoverPrecision == AVC_DEFAULT_PREC)
            nCoverPrecision = psFile->nPrecision;
        AVCBinReadClose(psFile);
        const int iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                                    &(psInfo->numSections), 1);

        psInfo->pasSections[iSect].eType = AVCFileTOL;
        psInfo->pasSections[iSect].pszName = CPLStrdup("TOL");
        psInfo->pasSections[iSect].pszFilename =
            CPLStrdup(papszCoverDir[iFile]);
    }

    szFname =
        (psInfo->eCoverType == AVCCoverV7 || psInfo->eCoverType == AVCCoverPC2)
            ? "par.adf"
            : "par";
    if ((iFile = CSLFindString(papszCoverDir, szFname)) != -1 &&
        (psFile =
             AVCBinReadOpen(psInfo->pszCoverPath, szFname, psInfo->eCoverType,
                            AVCFileTOL, psInfo->psDBCSInfo)) != nullptr)
    {
        if (nCoverPrecision == AVC_DEFAULT_PREC)
            nCoverPrecision = psFile->nPrecision;
        AVCBinReadClose(psFile);
        const int iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                                    &(psInfo->numSections), 1);

        psInfo->pasSections[iSect].eType = AVCFileTOL;
        psInfo->pasSections[iSect].pszName = CPLStrdup("TOL");
        psInfo->pasSections[iSect].pszFilename =
            CPLStrdup(papszCoverDir[iFile]);
    }

    /*-----------------------------------------------------------------
     * TXT section (txt.adf)
     *----------------------------------------------------------------*/
    szFname =
        (psInfo->eCoverType == AVCCoverV7 || psInfo->eCoverType == AVCCoverPC2)
            ? "txt.adf"
            : "txt";
    if ((iFile = CSLFindString(papszCoverDir, szFname)) != -1 &&
        (psFile =
             AVCBinReadOpen(psInfo->pszCoverPath, szFname, psInfo->eCoverType,
                            AVCFileTXT, psInfo->psDBCSInfo)) != nullptr)
    {
        if (nCoverPrecision == AVC_DEFAULT_PREC)
            nCoverPrecision = psFile->nPrecision;
        AVCBinReadClose(psFile);
        const int iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                                    &(psInfo->numSections), 1);

        psInfo->pasSections[iSect].eType = AVCFileTXT;
        psInfo->pasSections[iSect].pszName = CPLStrdup("TXT");
        psInfo->pasSections[iSect].pszFilename =
            CPLStrdup(papszCoverDir[iFile]);
    }

    /*-----------------------------------------------------------------
     * TX6 section (*.txt)
     * Scan the directory for files with a ".txt" extension.
     * Note: Never seen those in a PC Arc/Info coverage!
     * In weird coverages, the filename ends with "txt" but there is no "."
     *----------------------------------------------------------------*/
    if (psInfo->eCoverType == AVCCoverV7)
        nCoverPrecision = _AVCE00ReadAddJabberwockySection(
            psInfo, AVCFileTX6, "TX6", nCoverPrecision, ".txt", papszCoverDir);
    else if (psInfo->eCoverType == AVCCoverWeird)
        nCoverPrecision = _AVCE00ReadAddJabberwockySection(
            psInfo, AVCFileTX6, "TX6", nCoverPrecision, "txt", papszCoverDir);

    /*-----------------------------------------------------------------
     * At this point, we should have read the coverage precision... and if
     * we haven't yet then we'll just use single by default.
     * We'll need cPrecisionCode for some of the sections that follow.
     *----------------------------------------------------------------*/
    if (nCoverPrecision == AVC_DOUBLE_PREC)
        cPrecisionCode = '3';
    else
        cPrecisionCode = '2';

    /*-----------------------------------------------------------------
     * SIN  2/3 and EOX lines ... ???
     *----------------------------------------------------------------*/
    {
        int iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                              &(psInfo->numSections), 2);
        psInfo->pasSections[iSect].eType = AVCFileUnknown;
        psInfo->pasSections[iSect].pszName = CPLStrdup("SIN  X");
        psInfo->pasSections[iSect].pszName[5] = cPrecisionCode;
        iSect++;
        psInfo->pasSections[iSect].eType = AVCFileUnknown;
        psInfo->pasSections[iSect].pszName = CPLStrdup("EOX");
    }

    /*-----------------------------------------------------------------
     * LOG section (log.adf) (ends with EOL)
     *----------------------------------------------------------------*/

    /*-----------------------------------------------------------------
     * PRJ section (prj.adf) (ends with EOP)
     *----------------------------------------------------------------*/
    szFname =
        (psInfo->eCoverType == AVCCoverV7 || psInfo->eCoverType == AVCCoverPC2)
            ? "prj.adf"
            : "prj";
    if ((iFile = CSLFindString(papszCoverDir, szFname)) != -1)
    {
        const int iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                                    &(psInfo->numSections), 1);

        psInfo->pasSections[iSect].eType = AVCFilePRJ;
        psInfo->pasSections[iSect].pszName = CPLStrdup("PRJ");
        psInfo->pasSections[iSect].pszFilename =
            CPLStrdup(papszCoverDir[iFile]);
    }

    /*-----------------------------------------------------------------
     * RXP section (*.rxp)
     * Scan the directory for files with a ".rxp" extension.
     *----------------------------------------------------------------*/
    if (psInfo->eCoverType == AVCCoverV7)
        _AVCE00ReadAddJabberwockySection(
            psInfo, AVCFileRXP, "RXP", nCoverPrecision, ".rxp", papszCoverDir);
    else if (psInfo->eCoverType == AVCCoverWeird)
        _AVCE00ReadAddJabberwockySection(psInfo, AVCFileRXP, "RXP",
                                         nCoverPrecision, "rxp", papszCoverDir);

    /*-----------------------------------------------------------------
     * RPL section (*.pal)
     * Scan the directory for files with a ".rpl" extension.
     *----------------------------------------------------------------*/
    if (psInfo->eCoverType == AVCCoverV7)
        _AVCE00ReadAddJabberwockySection(
            psInfo, AVCFileRPL, "RPL", nCoverPrecision, ".pal", papszCoverDir);
    else if (psInfo->eCoverType == AVCCoverWeird)
        _AVCE00ReadAddJabberwockySection(psInfo, AVCFileRPL, "RPL",
                                         nCoverPrecision, "rpl", papszCoverDir);

    /*-----------------------------------------------------------------
     * IFO section (tables)
     *----------------------------------------------------------------*/
    papszTables = papszFiles = nullptr;
    if (psInfo->eCoverType == AVCCoverV7 ||
        psInfo->eCoverType == AVCCoverV7Tables ||
        psInfo->eCoverType == AVCCoverWeird)
    {
        /*-------------------------------------------------------------
         * Unix coverages: get tables from the ../info/arc.dir
         * Weird coverages: the arc.dir is similar but called "arcdr9"
         *------------------------------------------------------------*/
        papszTables = AVCBinReadListTables(
            psInfo->pszInfoPath, psInfo->pszCoverName, &papszFiles,
            psInfo->eCoverType, psInfo->psDBCSInfo);
    }
    else if (psInfo->eCoverType == AVCCoverPC ||
             psInfo->eCoverType == AVCCoverPC2)
    {
        /*-------------------------------------------------------------
         * PC coverages: look for "???.dbf" in the coverage directory
         *               and build the table name using the coverage name
         *               as the table basename, and the dbf file basename
         *               as the table extension.
         *------------------------------------------------------------*/
        for (iFile = 0; papszCoverDir && papszCoverDir[iFile]; iFile++)
        {
            if ((nLen = (int)strlen(papszCoverDir[iFile])) == 7 &&
                EQUAL(papszCoverDir[iFile] + nLen - 4, ".dbf"))
            {
                papszCoverDir[iFile][nLen - 4] = '\0';
                szFname = CPLSPrintf("%s.%s", psInfo->pszCoverName,
                                     papszCoverDir[iFile]);
                pcTmp = (char *)szFname;
                for (; *pcTmp != '\0'; pcTmp++)
                    *pcTmp =
                        (char)CPLToupper(static_cast<unsigned char>(*pcTmp));
                papszCoverDir[iFile][nLen - 4] = '.';

                papszTables = CSLAddString(papszTables, szFname);
                papszFiles = CSLAddString(papszFiles, papszCoverDir[iFile]);
            }
        }
    }

    if (papszTables != nullptr && (numTables = CSLCount(papszTables)) > 0)
    {
        int iSect = _AVCIncreaseSectionsArray(
            &(psInfo->pasSections), &(psInfo->numSections), numTables + 2);

        psInfo->pasSections[iSect].eType = AVCFileUnknown;
        psInfo->pasSections[iSect].pszName = CPLStrdup("IFO  X");
        psInfo->pasSections[iSect].pszName[5] = cPrecisionCode;
        iSect++;

        for (iTable = 0; iTable < numTables; iTable++)
        {
            psInfo->pasSections[iSect].eType = AVCFileTABLE;
            psInfo->pasSections[iSect].pszName = CPLStrdup(papszTables[iTable]);
            if (papszFiles)
            {
                psInfo->pasSections[iSect].pszFilename =
                    CPLStrdup(papszFiles[iTable]);
            }
            iSect++;
        }

        psInfo->pasSections[iSect].eType = AVCFileUnknown;
        psInfo->pasSections[iSect].pszName = CPLStrdup("EOI");
    }
    CSLDestroy(papszTables);
    CSLDestroy(papszFiles);

    /*-----------------------------------------------------------------
     * File ends with EOS
     *----------------------------------------------------------------*/
    const int iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                                &(psInfo->numSections), 1);
    psInfo->pasSections[iSect].eType = AVCFileUnknown;
    psInfo->pasSections[iSect].pszName = CPLStrdup("EOS");

    return nCoverPrecision;
}

/**********************************************************************
 *                     _AVCE00ReadScanE00()
 *
 * Processes an entire E00 file to find all the interesting sections.
 **********************************************************************/
static void _AVCE00ReadScanE00(AVCE00ReadE00Ptr psRead)
{
    AVCE00ParseInfo *psInfo = psRead->hParseInfo;

    const char *pszLine;
    const char *pszName = nullptr;
    void *obj;
    int iSect = 0;
    GBool bFirstLine = TRUE;

    while (CPLGetLastErrorNo() == 0 &&
           (pszLine = CPLReadLine2L(psRead->hFile, knMAX_CHARS_PER_LINE,
                                    nullptr)) != nullptr)
    {
        if (bFirstLine)
        {
            /* Look for the first non-empty line, after the EXP header,
             * trying to detect compressed E00 files. If the file is
             * compressed, the first line of data should be 79 or 80 chars
             * long and contain several '~' characters.
             */
            int nLen = (int)strlen(pszLine);
            if (nLen == 0 || STARTS_WITH_CI(pszLine, "EXP "))
                continue; /* Skip empty and EXP header lines */
            else if ((nLen == 79 || nLen == 80) &&
                     strchr(pszLine, '~') != nullptr)
            {
                /* Looks like a compressed file. Just log an error and return.
                 * The caller should reject the file because it contains 0
                 * sections
                 */
                CPLError(CE_Failure, CPLE_OpenFailed,
                         "This looks like a compressed E00 file and cannot be "
                         "processed directly. You may need to uncompress it "
                         "first using the E00compr library or the e00conv "
                         "program.");
                return;
            }

            /* All seems fine. Continue with normal processing */
            bFirstLine = FALSE;
        }

        /* coverity[tainted_data] */
        obj = _AVCE00ReadNextLineE00(psRead, pszLine);

        if (obj)
        {
            pszName = nullptr;
            switch (psInfo->eFileType)
            {
                case AVCFileARC:
                    pszName = "ARC";
                    break;

                case AVCFilePAL:
                    pszName = "PAL";
                    break;

                case AVCFileCNT:
                    pszName = "CNT";
                    break;

                case AVCFileLAB:
                    pszName = "LAB";
                    break;

                case AVCFileRPL:
                    pszName = "RPL";
                    break;

                case AVCFileTXT:
                    pszName = "TXT";
                    break;

                case AVCFileTX6:
                    pszName = "TX6";
                    break;

                case AVCFilePRJ:
                    pszName = "PRJ";
                    break;

                case AVCFileTABLE:
                    pszName = psInfo->hdr.psTableDef->szTableName;
                    break;

                default:
                    break;
            }

            if (pszName &&
                (psRead->numSections == 0 ||
                 psRead->pasSections[iSect].eType != psInfo->eFileType ||
                 !EQUAL(pszName, psRead->pasSections[iSect].pszName)))
            {
                iSect = _AVCIncreaseSectionsArray(&(psRead->pasSections),
                                                  &(psRead->numSections), 1);

                psRead->pasSections[iSect].eType = psInfo->eFileType;
                /* psRead->pasSections[iSect].pszName =
                 * CPLStrdup(psRead->pszCoverName); */
                psRead->pasSections[iSect].pszName = CPLStrdup(pszName);
                psRead->pasSections[iSect].pszFilename =
                    CPLStrdup(psRead->pszCoverPath);
                psRead->pasSections[iSect].nLineNum = psInfo->nStartLineNum;
                psRead->pasSections[iSect].nFeatureCount = 0;
            }

            if (pszName && psRead->numSections)
            {
                /* increase feature count for current layer */
                ++psRead->pasSections[iSect].nFeatureCount;
            }
        }
    }
}

/**********************************************************************
 *                         _AVCE00ReadNextTableLine()
 *
 * Return the next line of the E00 representation of a info table.
 *
 * This function is used by AVCE00ReadNextLine() to generate table
 * output... it should never be called directly.
 **********************************************************************/
static const char *_AVCE00ReadNextTableLine(AVCE00ReadPtr psInfo)
{
    const char *pszLine = nullptr;
    AVCE00Section *psSect;

    psSect = &(psInfo->pasSections[psInfo->iCurSection]);

    CPLAssert(psSect->eType == AVCFileTABLE);

    if (psInfo->iCurStep == AVC_GEN_NOTSTARTED)
    {
        /*---------------------------------------------------------
         * Open table and start returning header
         *--------------------------------------------------------*/
        if (psInfo->eCoverType == AVCCoverPC ||
            psInfo->eCoverType == AVCCoverPC2)
        {
            /*---------------------------------------------------------
             * PC Arc/Info: We pass the DBF table's full filename + the
             * Arc/Info table name (for E00 header)
             *--------------------------------------------------------*/
            char *pszFname;
            pszFname = CPLStrdup(
                CPLSPrintf("%s%s", psInfo->pszInfoPath, psSect->pszFilename));
            psInfo->hFile =
                AVCBinReadOpen(pszFname, psSect->pszName, psInfo->eCoverType,
                               psSect->eType, psInfo->psDBCSInfo);
            CPLFree(pszFname);
        }
        else
        {
            /*---------------------------------------------------------
             * AVCCoverV7 and AVCCoverWeird:
             * We pass the INFO dir's path, and the Arc/Info table name
             * will be searched in the arc.dir
             *--------------------------------------------------------*/
            psInfo->hFile = AVCBinReadOpen(psInfo->pszInfoPath, psSect->pszName,
                                           psInfo->eCoverType, psSect->eType,
                                           psInfo->psDBCSInfo);
        }

        /* For some reason the file could not be opened... abort now.
         * An error message should have already been produced by
         * AVCBinReadOpen()
         */
        if (psInfo->hFile == nullptr)
            return nullptr;

        psInfo->iCurStep = AVC_GEN_TABLEHEADER;

        pszLine = AVCE00GenTableHdr(psInfo->hGenInfo,
                                    psInfo->hFile->hdr.psTableDef, FALSE);
    }

    if (pszLine == nullptr && psInfo->iCurStep == AVC_GEN_TABLEHEADER)
    {
        /*---------------------------------------------------------
         * Continue table header
         *--------------------------------------------------------*/
        pszLine = AVCE00GenTableHdr(psInfo->hGenInfo,
                                    psInfo->hFile->hdr.psTableDef, TRUE);

        if (pszLine == nullptr)
        {
            /* Finished with table header... time to proceed with the
             * table data.
             * Reset the AVCE00GenInfo struct. so that it returns nullptr,
             * which will force reading of the first record from the
             * file on the next call to AVCE00ReadNextLine()
             */
            AVCE00GenReset(psInfo->hGenInfo);
            psInfo->iCurStep = AVC_GEN_TABLEDATA;
        }
    }

    if (pszLine == nullptr && psInfo->iCurStep == AVC_GEN_TABLEDATA)
    {
        /*---------------------------------------------------------
         * Continue with records of data
         *--------------------------------------------------------*/

        pszLine = AVCE00GenTableRec(psInfo->hGenInfo,
                                    psInfo->hFile->hdr.psTableDef->numFields,
                                    psInfo->hFile->hdr.psTableDef->pasFieldDef,
                                    psInfo->hFile->cur.pasFields, TRUE);

        if (pszLine == nullptr)
        {
            /* Current record is finished generating... we need to read
             * a new one from the file.
             */
            if (AVCBinReadNextObject(psInfo->hFile) != nullptr)
            {
                pszLine = AVCE00GenTableRec(
                    psInfo->hGenInfo, psInfo->hFile->hdr.psTableDef->numFields,
                    psInfo->hFile->hdr.psTableDef->pasFieldDef,
                    psInfo->hFile->cur.pasFields, FALSE);
            }
        }
    }

    if (pszLine == nullptr)
    {
        /*---------------------------------------------------------
         * No more lines to output for this table ... Close it.
         *--------------------------------------------------------*/
        AVCBinReadClose(psInfo->hFile);
        psInfo->hFile = nullptr;

        /*---------------------------------------------------------
         * And now proceed to the next section...
         * OK, I don't really like recursion, but it was
         * the simplest way to do this, and anyways we should never
         * have more than one level of recursion.
         *--------------------------------------------------------*/
        if (psInfo->bReadAllSections)
            psInfo->iCurSection++;
        else
            psInfo->iCurSection = psInfo->numSections;
        psInfo->iCurStep = AVC_GEN_NOTSTARTED;

        pszLine = AVCE00ReadNextLine(psInfo);
    }

    /*-----------------------------------------------------------------
     * Check for errors... if any error happened, then return nullptr.
     *----------------------------------------------------------------*/
    if (CPLGetLastErrorNo() != 0)
    {
        pszLine = nullptr;
    }

    return pszLine;
}

/**********************************************************************
 *                          AVCE00ReadNextLine()
 *
 * Returns the next line of the E00 representation of the coverage
 * or nullptr when there are no more lines to generate, or if an error happened.
 * The returned line is a null-terminated string, and it does not
 * include a newline character.
 *
 * Call CPLGetLastErrorNo() after calling AVCE00ReadNextLine() to
 * make sure that the line was generated successfully.
 *
 * Note that AVCE00ReadNextLine() returns a reference to an
 * internal buffer whose contents will
 * be valid only until the next call to this function.  The caller should
 * not attempt to free() the returned pointer.
 **********************************************************************/
const char *AVCE00ReadNextLine(AVCE00ReadPtr psInfo)
{
    const char *pszLine = nullptr;
    AVCE00Section *psSect;

    CPLErrorReset();

    /*-----------------------------------------------------------------
     * Check if we have finished generating E00 output
     *----------------------------------------------------------------*/
    if (psInfo->iCurSection >= psInfo->numSections)
        return nullptr;

    psSect = &(psInfo->pasSections[psInfo->iCurSection]);

    /*-----------------------------------------------------------------
     * For simplicity, the generation of table output is in a separate
     * function.
     *----------------------------------------------------------------*/
    if (psSect->eType == AVCFileTABLE)
    {
        return _AVCE00ReadNextTableLine(psInfo);
    }

    if (psSect->eType == AVCFileUnknown)
    {
        /*-----------------------------------------------------------------
         * Section not attached to any file, used to hold header lines
         * or section separators, etc... just return the line directly and
         * move pointer to the next section.
         *----------------------------------------------------------------*/
        pszLine = psSect->pszName;
        if (psInfo->bReadAllSections)
            psInfo->iCurSection++;
        else
            psInfo->iCurSection = psInfo->numSections;
        psInfo->iCurStep = AVC_GEN_NOTSTARTED;
    }
    /*=================================================================
     *              ARC, PAL, CNT, LAB, TOL and TXT
     *================================================================*/
    else if (psInfo->iCurStep == AVC_GEN_NOTSTARTED &&
             (psSect->eType == AVCFileARC || psSect->eType == AVCFilePAL ||
              psSect->eType == AVCFileRPL || psSect->eType == AVCFileCNT ||
              psSect->eType == AVCFileLAB || psSect->eType == AVCFileTOL ||
              psSect->eType == AVCFileTXT || psSect->eType == AVCFileTX6 ||
              psSect->eType == AVCFileRXP))
    {
        /*-----------------------------------------------------------------
         * Start processing of an ARC, PAL, CNT, LAB or TOL section:
         *   Open the file, get ready to read the first object from the
         *   file, and return the header line.
         *  If the file fails to open then we will return nullptr.
         *----------------------------------------------------------------*/
        psInfo->hFile = AVCBinReadOpen(psInfo->pszCoverPath,
                                       psSect->pszFilename, psInfo->eCoverType,
                                       psSect->eType, psInfo->psDBCSInfo);

        /*-------------------------------------------------------------
         * For some reason the file could not be opened... abort now.
         * An error message should have already been produced by
         * AVCBinReadOpen()
         *------------------------------------------------------------*/
        if (psInfo->hFile == nullptr)
            return nullptr;

        pszLine = AVCE00GenStartSection(psInfo->hGenInfo, psSect->eType,
                                        psSect->pszName);

        /*-------------------------------------------------------------
         * Reset the AVCE00GenInfo struct. so that it returns nullptr,
         * which will force reading of the first object from the
         * file on the next call to AVCE00ReadNextLine()
         *------------------------------------------------------------*/
        AVCE00GenReset(psInfo->hGenInfo);
        psInfo->iCurStep = AVC_GEN_DATA;
    }
    else if (psInfo->iCurStep == AVC_GEN_DATA &&
             (psSect->eType == AVCFileARC || psSect->eType == AVCFilePAL ||
              psSect->eType == AVCFileRPL || psSect->eType == AVCFileCNT ||
              psSect->eType == AVCFileLAB || psSect->eType == AVCFileTOL ||
              psSect->eType == AVCFileTXT || psSect->eType == AVCFileTX6 ||
              psSect->eType == AVCFileRXP))
    {
        /*-----------------------------------------------------------------
         * Return the next line of an ARC/PAL/CNT/TOL/TXT object...
         * if necessary, read the next object from the binary file.
         *----------------------------------------------------------------*/
        pszLine = AVCE00GenObject(
            psInfo->hGenInfo, psSect->eType,
            (psSect->eType == AVCFileARC   ? (void *)(psInfo->hFile->cur.psArc)
             : psSect->eType == AVCFilePAL ? (void *)(psInfo->hFile->cur.psPal)
             : psSect->eType == AVCFileRPL ? (void *)(psInfo->hFile->cur.psPal)
             : psSect->eType == AVCFileCNT ? (void *)(psInfo->hFile->cur.psCnt)
             : psSect->eType == AVCFileLAB ? (void *)(psInfo->hFile->cur.psLab)
             : psSect->eType == AVCFileTOL ? (void *)(psInfo->hFile->cur.psTol)
             : psSect->eType == AVCFileTXT ? (void *)(psInfo->hFile->cur.psTxt)
             : psSect->eType == AVCFileTX6 ? (void *)(psInfo->hFile->cur.psTxt)
             : psSect->eType == AVCFileRXP ? (void *)(psInfo->hFile->cur.psRxp)
                                           : nullptr),
            TRUE);
        if (pszLine == nullptr)
        {
            /*---------------------------------------------------------
             * Current object is finished generating... we need to read
             * a new one from the file.
             *--------------------------------------------------------*/
            if (AVCBinReadNextObject(psInfo->hFile) != nullptr)
            {
                pszLine =
                    AVCE00GenObject(psInfo->hGenInfo, psSect->eType,
                                    (psSect->eType == AVCFileARC
                                         ? (void *)(psInfo->hFile->cur.psArc)
                                     : psSect->eType == AVCFilePAL
                                         ? (void *)(psInfo->hFile->cur.psPal)
                                     : psSect->eType == AVCFileRPL
                                         ? (void *)(psInfo->hFile->cur.psPal)
                                     : psSect->eType == AVCFileCNT
                                         ? (void *)(psInfo->hFile->cur.psCnt)
                                     : psSect->eType == AVCFileLAB
                                         ? (void *)(psInfo->hFile->cur.psLab)
                                     : psSect->eType == AVCFileTOL
                                         ? (void *)(psInfo->hFile->cur.psTol)
                                     : psSect->eType == AVCFileTXT
                                         ? (void *)(psInfo->hFile->cur.psTxt)
                                     : psSect->eType == AVCFileTX6
                                         ? (void *)(psInfo->hFile->cur.psTxt)
                                     : psSect->eType == AVCFileRXP
                                         ? (void *)(psInfo->hFile->cur.psRxp)
                                         : nullptr),
                                    FALSE);
            }
        }
        if (pszLine == nullptr)
        {
            /*---------------------------------------------------------
             * Still nullptr ??? This means we finished reading this file...
             * Start returning the "end of section" line(s)...
             *--------------------------------------------------------*/
            AVCBinReadClose(psInfo->hFile);
            psInfo->hFile = nullptr;
            psInfo->iCurStep = AVC_GEN_ENDSECTION;
            pszLine =
                AVCE00GenEndSection(psInfo->hGenInfo, psSect->eType, FALSE);
        }
    }
    /*=================================================================
     *                          PRJ
     *================================================================*/
    else if (psInfo->iCurStep == AVC_GEN_NOTSTARTED &&
             psSect->eType == AVCFilePRJ)
    {
        /*-------------------------------------------------------------
         * Start processing of PRJ section... return first header line.
         *------------------------------------------------------------*/
        pszLine =
            AVCE00GenStartSection(psInfo->hGenInfo, psSect->eType, nullptr);

        psInfo->hFile = nullptr;
        psInfo->iCurStep = AVC_GEN_DATA;
    }
    else if (psInfo->iCurStep == AVC_GEN_DATA && psSect->eType == AVCFilePRJ)
    {
        /*-------------------------------------------------------------
         * Return the next line of a PRJ section
         *------------------------------------------------------------*/
        if (psInfo->hFile == nullptr)
        {
            /*---------------------------------------------------------
             * File has not been read yet...
             * Read the PRJ file, and return the first PRJ line.
             *--------------------------------------------------------*/
            psInfo->hFile = AVCBinReadOpen(
                psInfo->pszCoverPath, psSect->pszFilename, psInfo->eCoverType,
                psSect->eType, psInfo->psDBCSInfo);

            /* For some reason the file could not be opened... abort now.
             * An error message should have already been produced by
             * AVCBinReadOpen()
             */
            if (psInfo->hFile == nullptr)
                return nullptr;

            pszLine = AVCE00GenPrj(psInfo->hGenInfo,
                                   psInfo->hFile->cur.papszPrj, FALSE);
        }
        else
        {
            /*---------------------------------------------------------
             * Generate the next line of output.
             *--------------------------------------------------------*/
            pszLine = AVCE00GenPrj(psInfo->hGenInfo,
                                   psInfo->hFile->cur.papszPrj, TRUE);
        }

        if (pszLine == nullptr)
        {
            /*---------------------------------------------------------
             * Still nullptr ??? This means we finished generating this PRJ
             * section...
             * Start returning the "end of section" line(s)...
             *--------------------------------------------------------*/
            AVCBinReadClose(psInfo->hFile);
            psInfo->hFile = nullptr;
            psInfo->iCurStep = AVC_GEN_ENDSECTION;
            pszLine =
                AVCE00GenEndSection(psInfo->hGenInfo, psSect->eType, FALSE);
        }
    }
    else if (psInfo->iCurStep != AVC_GEN_ENDSECTION)
    {
        /* We should never get here! */
        CPLAssert(FALSE);
    }

    /*=================================================================
     *                End of section, for all files
     *================================================================*/

    /*-----------------------------------------------------------------
     * Finished processing of an ARC, PAL, CNT, LAB, TOL, PRJ file ...
     * continue returning the "end of section" line(s), and move the pointer
     * to the next section once we're done.
     *----------------------------------------------------------------*/
    if (psInfo->iCurStep == AVC_GEN_ENDSECTION && pszLine == nullptr)
    {
        pszLine = AVCE00GenEndSection(psInfo->hGenInfo, psSect->eType, TRUE);

        if (pszLine == nullptr)
        {
            /*---------------------------------------------------------
             * Finished returning the last lines of the section...
             * proceed to the next section...
             * OK, I don't really like recursion, but it was
             * the simplest way to do this, and anyways we should never
             * have more than one level of recursion.
             *--------------------------------------------------------*/
            if (psInfo->bReadAllSections)
                psInfo->iCurSection++;
            else
                psInfo->iCurSection = psInfo->numSections;
            psInfo->iCurStep = AVC_GEN_NOTSTARTED;

            pszLine = AVCE00ReadNextLine(psInfo);
        }
    }

    return pszLine;
}

/**********************************************************************
 *                         AVCE00ReadSectionsList()
 *
 * Returns an array of AVCE00Section structures that describe the
 * skeleton of the whole coverage.  The value of *numSect will be
 * set to the number of sections in the array.
 *
 * You can scan the returned array, and use AVCE00ReadGotoSection() to move
 * the read pointer directly to the beginning of a given section
 * of the file.
 *
 * Sections of type AVCFileUnknown correspond to lines in the
 * E00 output that are not directly linked to any coverage file, like
 * the "EXP 0" line, the "IFO X", "SIN X", etc.
 *
 * THE RETURNED ARRAY IS AN INTERNAL STRUCTURE AND SHOULD NOT BE
 * MODIFIED OR FREED BY THE CALLER... its contents will be valid
 * for as long as the coverage will remain open.
 **********************************************************************/
AVCE00Section *AVCE00ReadSectionsList(AVCE00ReadPtr psInfo, int *numSect)
{
    CPLErrorReset();

    *numSect = psInfo->numSections;
    return psInfo->pasSections;
}

/**********************************************************************
 *                         AVCE00ReadGotoSection()
 *
 * Move the read pointer to the E00 section (coverage file) described in
 * the psSect structure.  Call AVCE00ReadSectionsList() to get the list of
 * sections for the current coverage.
 *
 * if bContinue=TRUE, then reading will automatically continue with the
 * next sections of the file once the requested section is finished.
 * Otherwise, if bContinue=FALSE then reading will stop at the end
 * of this section (i.e. AVCE00ReadNextLine() will return nullptr when
 * it reaches the end of this section)
 *
 * Sections of type AVCFileUnknown returned by AVCE00ReadSectionsList()
 * correspond to lines in the E00 output that are not directly linked
 * to any coverage file, like the "EXP 0" line, the "IFO X", "SIN X", etc.
 * You can jump to these sections or any other one without problems.
 *
 * This function returns 0 on success or -1 on error.
 **********************************************************************/
int AVCE00ReadGotoSection(AVCE00ReadPtr psInfo, AVCE00Section *psSect,
                          GBool bContinue)
{
    int iSect;
    GBool bFound = FALSE;

    CPLErrorReset();

    /*-----------------------------------------------------------------
     * Locate the requested section in the array.
     *----------------------------------------------------------------*/
    for (iSect = 0; iSect < psInfo->numSections; iSect++)
    {
        if (psInfo->pasSections[iSect].eType == psSect->eType &&
            EQUAL(psInfo->pasSections[iSect].pszName, psSect->pszName))
        {
            bFound = TRUE;
            break;
        }
    }

    /*-----------------------------------------------------------------
     * Not found ... generate an error...
     *----------------------------------------------------------------*/
    if (!bFound)
    {
        CPLError(CE_Failure, CPLE_IllegalArg,
                 "Requested E00 section does not exist!");
        return -1;
    }

    /*-----------------------------------------------------------------
     * Found it ... close current section and get ready to read
     * the new one.
     *----------------------------------------------------------------*/
    if (psInfo->hFile)
    {
        AVCBinReadClose(psInfo->hFile);
        psInfo->hFile = nullptr;
    }

    psInfo->bReadAllSections = bContinue;
    psInfo->iCurSection = iSect;
    psInfo->iCurStep = AVC_GEN_NOTSTARTED;

    return 0;
}

/**********************************************************************
 *                         AVCE00ReadRewind()
 *
 * Rewinds the AVCE00ReadPtr just like the stdio rewind()
 * function would do if you were reading an ASCII E00 file.
 *
 * Returns 0 on success or -1 on error.
 **********************************************************************/
int AVCE00ReadRewind(AVCE00ReadPtr psInfo)
{
    CPLErrorReset();

    return AVCE00ReadGotoSection(psInfo, &(psInfo->pasSections[0]), TRUE);
}

/**********************************************************************
 *                         AVCE00ReadRewindE00()
 *
 * Rewinds the AVCE00ReadE00Ptr just like the stdio rewind()
 * function would do if you were reading an ASCII E00 file.
 *
 * Returns 0 on success or -1 on error.
 **********************************************************************/
int AVCE00ReadRewindE00(AVCE00ReadE00Ptr psRead)
{
    CPLErrorReset();

    psRead->bReadAllSections = TRUE;
    psRead->eCurFileType = AVCFileUnknown;

    psRead->hParseInfo->nCurLineNum = 0;
    psRead->hParseInfo->nStartLineNum = 0;
    psRead->hParseInfo->bForceEndOfSection = TRUE;
    psRead->hParseInfo->eSuperSectionType = AVCFileUnknown;
    AVCE00ParseSectionEnd(psRead->hParseInfo, nullptr, 1);

    return VSIFSeekL(psRead->hFile, 0, SEEK_SET);
}

/**********************************************************************
 *                        _AVCE00ReadSeekE00()
 *
 * Seeks to a new location in the E00 file, keeping parse state
 * appropriately.
 *
 * NOTE: This is a pretty slow implementation.
 * NOTE: The SEEK_END is not implemented.
 *
 * Returns 0 on success or -1 on error.
 **********************************************************************/
static int _AVCE00ReadSeekE00(AVCE00ReadE00Ptr psRead, int nOffset, int nWhence)
{
    const char *pszLine;
    /* void       *obj; */

    switch (nWhence)
    {
        case SEEK_CUR:
            break;

        case SEEK_SET:
            AVCE00ReadRewindE00(psRead);
            break;

        default:
            CPLAssert(nWhence == SEEK_CUR || nWhence == SEEK_SET);
            break;
    }

    while (nOffset-- && CPLGetLastErrorNo() == 0 &&
           (pszLine = CPLReadLine2L(psRead->hFile, knMAX_CHARS_PER_LINE,
                                    nullptr)) != nullptr)
    {
        /* obj = */
        /* coverity[tainted_data] */
        _AVCE00ReadNextLineE00(psRead, pszLine);
    }

    return nOffset ? -1 : 0;
}

/**********************************************************************
 *                      AVCE00ReadNextObjectE00()
 *
 * Returns the next object in an E00 file or nullptr when there are no
 * more objects, or if an error happened.  The object type can be
 * determined via the eCurFileType attribute of the
 * AVCE00ReadE00Ptr object.
 *
 * Note that AVCE00ReadNextLine() returns a reference to an internal
 * buffer whose contents will be valid only until the next call to
 * this function.  The caller should not attempt to free() the
 * returned pointer.
 **********************************************************************/
void *AVCE00ReadNextObjectE00(AVCE00ReadE00Ptr psRead)
{
    const char *pszLine;
    void *obj = nullptr;

    do
    {
        pszLine = CPLReadLine2L(psRead->hFile, knMAX_CHARS_PER_LINE, nullptr);
        if (pszLine == nullptr)
            break;
        /* coverity[tainted_data] */
        obj = _AVCE00ReadNextLineE00(psRead, pszLine);
    } while (
        obj == nullptr &&
        (psRead->bReadAllSections || psRead->eCurFileType != AVCFileUnknown) &&
        CPLGetLastErrorNo() == 0);
    return obj;
}

/**********************************************************************
 *                         AVCE00ReadSectionsListE00()
 *
 * Returns an array of AVCE00Section structures that describe the
 * sections in the E00 file.  The value of *numSect will be set to the
 * number of sections in the array.
 *
 * You can scan the returned array, and use AVCE00ReadGotoSectionE00()
 * to move the read pointer directly to the beginning of a given
 * section of the file.
 *
 * THE RETURNED ARRAY IS AN INTERNAL STRUCTURE AND SHOULD NOT BE
 * MODIFIED OR FREED BY THE CALLER... its contents will be valid
 * for as long as the coverage will remain open.
 **********************************************************************/
AVCE00Section *AVCE00ReadSectionsListE00(AVCE00ReadE00Ptr psRead, int *numSect)
{
    CPLErrorReset();

    *numSect = psRead->numSections;
    return psRead->pasSections;
}

/**********************************************************************
 *                         AVCE00ReadGotoSectionE00()
 *
 * Move the read pointer to the E00 section described in the psSect
 * structure.  Call AVCE00ReadSectionsListE00() to get the list of
 * sections for the current coverage.
 *
 * If bContinue is TRUE, then reading will automatically continue with
 * the next section of the file once the requested section is finished.
 * Otherwise, if bContinue is FALSE then reading will stop at the end
 * of this section (i.e. AVCE00ReadNextObjectE00() will return nullptr
 * when the end of this section is reached)
 *
 * This function returns 0 on success or -1 on error.
 **********************************************************************/
int AVCE00ReadGotoSectionE00(AVCE00ReadE00Ptr psRead, AVCE00Section *psSect,
                             GBool bContinue)
{
    int iSect;
    GBool bFound = FALSE;

    CPLErrorReset();

    /*-----------------------------------------------------------------
     * Locate the requested section in the array.
     *----------------------------------------------------------------*/
    for (iSect = 0; iSect < psRead->numSections; iSect++)
    {
        if (psRead->pasSections[iSect].eType == psSect->eType &&
            EQUAL(psRead->pasSections[iSect].pszName, psSect->pszName))
        {
            bFound = TRUE;
            break;
        }
    }

    /*-----------------------------------------------------------------
     * Not found ... generate an error...
     *----------------------------------------------------------------*/
    if (!bFound)
    {
        CPLError(CE_Failure, CPLE_IllegalArg,
                 "Requested E00 section does not exist!");
        return -1;
    }

    /*-----------------------------------------------------------------
     * Found it ... advance parser to line number of start of section
     *----------------------------------------------------------------*/
    _AVCE00ReadSeekE00(psRead, psRead->pasSections[iSect].nLineNum, SEEK_SET);

    psRead->bReadAllSections = bContinue;

    return 0;
}
