/*******************************************************************************
* xmount Copyright (c) 2024 by SITS Sarl                                       *
*                                                                              *
* Author(s):                                                                   *
*   Guy Voncken <deve‍lop@f‍aert.n‍et>                                            *
*                                                                              *
* This program is free software: you can redistribute it and/or modify it      *
* under the terms of the GNU General Public License as published by the Free   *
* Software Foundation, either version 3 of the License, or (at your option)    *
* any later version.                                                           *
*                                                                              *
* This program is distributed in the hope that it will be useful, but WITHOUT  *
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or        *
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for     *
* more details.                                                                *
*                                                                              *
* You should have received a copy of the GNU General Public License along with *
* this program. If not, see <http://www.gnu.org/licenses/>.                    *
*******************************************************************************/

// Aewf has been written in order to reduce xmount's memory footprint when
// operating on large EWF images. Before Aewf, xmount exclusively relied on
// libewf for accessing EWF images, resulting in enormous memory consumption.
//
// Aewf uses 2 main structures for handling image access: pAewf->pSegmentArr
// contains everything about the image files (segments) and pAewf->pTableArr
// handles the EWF chunk offset tables.
//
// At the same time, those structures serve as caches for the two most vital
// ressouces, namely the number of segment files opened in parallel and the
// memory consumed by the chunk offset tables.
//
// The max. values for both are configurable, see pAewf->MaxOpenSegments and
// pAewf->MaxTableCache.

// Please don't touch source code formatting!

#ifdef LINTING
//   #define _LARGEFILE_SOURCE
//   #define _FILE_OFFSET_BITS 64
   #define AEWF_STANDALONE
#endif

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <limits.h>
#include <time.h>       //lint !e537 !e451  Include file messages
#include <zlib.h>
#include <unistd.h>     //lint !e537
#include <wchar.h>      //lint !e537 !e451
#include <stdarg.h>     //lint !e537 !e451
#include <errno.h>
#include <pthread.h>

#include "../libxmount_input.h"

#include "libxmount_input_aewf.h"

#ifdef AEWF_STANDALONE
   #define CREATE_REVERSE_FILE
//   #define REVERSE_FILE_USES_SEPARATE_HANDLE
   #define LOG_STDOUT TRUE
#endif

//#ifdef AEWF_STANDALONE
//  #define _GNU_SOURCE
//#endif

#define SAFE_FREE_1(pBuf) \
{                         \
   if (pBuf)              \
   {                      \
      free (pBuf);        \
      pBuf = NULL;        \
   }                      \
}

#define SAFE_FREE_2(pBuf, Size) \
{                               \
   if (pBuf)                    \
   {                            \
      memset (pBuf,0,Size);     \
      free (pBuf);              \
      pBuf = NULL;              \
   }                            \
}

#define CHOOSE_MACRO(_1,_2,NAME,...) NAME
#define SAFE_FREE(...) CHOOSE_MACRO(__VA_ARGS__, SAFE_FREE_2, SAFE_FREE_1)(__VA_ARGS__)


#define AEWF_OPTION_TABLECACHE      "aewfmaxmem"
#define AEWF_OPTION_MAXOPENSEGMENTS "aewfmaxfiles"
#define AEWF_OPTION_STATS           "aewfstats"
#define AEWF_OPTION_STATSREFRESH    "aewfrefresh"
#define AEWF_OPTION_LOG             "aewflog"
#define AEWF_OPTION_THREADS         "aewfthreads"

static int         AewfClose           (void *pHandle);
static const char* AewfGetErrorMessage (int ErrNum);

const uint64_t AEWF_DEFAULT_TABLECACHE      = 10;  // MiB
const uint64_t AEWF_DEFAULT_MAXOPENSEGMENTS = 10;
const uint64_t AEWF_DEFAULT_STATSREFRESH    = 10;

// ----------------------------
//  Error handling and logging
// ----------------------------

#define LOG_HEADER_LEN 80

// CHK requires existance of pAewf handle

#ifdef AEWF_STANDALONE
   #define LOG_ERRORS_ON_STDOUT TRUE
#else
   #define LOG_ERRORS_ON_STDOUT pAewf->LogStdout
#endif

#define CHK(ChkVal)                                                             \
{                                                                               \
   int ChkValRc;                                                                \
   if ((ChkValRc=(ChkVal)) != AEWF_OK)                                          \
   {                                                                            \
      const char *pErr = AewfGetErrorMessage (ChkValRc);                        \
      LogEntry (pAewf->pLogPath, LOG_ERRORS_ON_STDOUT, __FILE__, __FUNCTION__, __LINE__, "Error %d (%s) occured", ChkValRc, pErr); \
      return ChkValRc;                                                          \
   }                                                                            \
}

// CHK_LEAVE requires 
//    - label Leave at function end where the cleanup work is done
//    - int variable rc that has to be returned to caller

#define CHK_LEAVE(ChkVal)                 \
{                                         \
   int ChkValRc;                          \
   if ((ChkValRc=(ChkVal)) != AEWF_OK)    \
   {                                      \
      rc = ChkValRc;                      \
      goto Leave;                         \
   }                                      \
}

#define LOG(...) \
   LogEntry (pAewf->pLogPath, pAewf->LogStdout, __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__);


int LogvEntry (const char *pLogPath, uint8_t LogStdout, const char *pFileName, const char *pFunctionName, int LineNr, const char *pFormat, va_list pArguments)
{
   time_t       NowT;
   struct tm  *pNowTM;
   FILE       *pFile;
   int          wr;
   char       *pFullLogFileName = NULL;
   const char *pBase;
   char         LogLineHeader[1024];
   pid_t        OwnPID;
   va_list     pArguments0;
   int          rc;

   if (!LogStdout && (pLogPath==NULL))
      CHK_LEAVE (AEWF_OK)

   time (&NowT);
   pNowTM = localtime (&NowT);
   OwnPID = getpid();  // pthread_self()
   wr  = (int) strftime (&LogLineHeader[0] , sizeof(LogLineHeader)   , "%a %d.%b.%Y %H:%M:%S ", pNowTM); //lint !e713
   wr += snprintf       (&LogLineHeader[wr], sizeof(LogLineHeader)-wr, "%5d ", OwnPID);                  //lint !e737

   if (pFileName && pFunctionName)
   {
      pBase = strrchr(pFileName, '/');
      if (pBase)
         pFileName = pBase+1;
      wr += snprintf (&LogLineHeader[wr], sizeof(LogLineHeader)-wr, "%s %s %d ", pFileName, pFunctionName, LineNr); //lint !e737
   }

//   while (wr < LOG_HEADER_LEN)
//      LogLineHeader[wr++] = ' ';

   if (pLogPath)
   {
      wr = asprintf (&pFullLogFileName, "%s/log_%d", pLogPath, OwnPID);
      if ((wr <= 0) || (pFullLogFileName == NULL))
      {
         if (LogStdout)
            printf ("\nLog file error: Can't build filename");
         CHK_LEAVE (AEWF_MEMALLOC_FAILED)
      }
      else
      {
         pFile = fopen64 (pFullLogFileName, "a");
         if (pFile == NULL)
         {
            if (LogStdout)
               printf ("\nLog file error: Can't be opened");
            CHK_LEAVE (AEWF_CANNOT_OPEN_LOGFILE)
         }
         else
         {
            fprintf  (pFile, "%-*s", LOG_HEADER_LEN, &LogLineHeader[0]);
            va_copy (pArguments0, pArguments);
            vfprintf (pFile, pFormat, pArguments0);
            fprintf  (pFile, "\n");
            fclose   (pFile);
         }
      }
   }
   if (LogStdout)
   {
      printf  ("%s", &LogLineHeader[0]);
      va_copy (pArguments0, pArguments);
      vprintf (pFormat, pArguments0);
      printf  ("\n");
   }
   rc = AEWF_OK;

Leave:
   SAFE_FREE (pFullLogFileName);

   return rc;
}

int LogEntry (const char *pLogPath, uint8_t LogStdout, const char *pFileName, const char *pFunctionName, int LineNr, const char *pFormat, ...)
{
   va_list VaList;
   int     rc;

   if (!LogStdout && (pLogPath==NULL))
      return AEWF_OK;

   va_start (VaList, pFormat); //lint !e530 Symbol 'VaList' not initialized
   rc = LogvEntry (pLogPath, LogStdout, pFileName, pFunctionName, LineNr, pFormat, VaList);
   va_end(VaList);
   return rc;
}

// AewfCheckError is called before exiting AewfRead. It should not
// be called elsewehere or else the statistics would become wrong.
static void AewfCheckError (t_pAewf pAewf, int Ret, int *pErrno)
{
   *pErrno = 0;
   if (Ret != AEWF_OK)
   {
      pAewf->Errors++;
      pAewf->LastError = Ret;

      if      ((Ret >= AEWF_ERROR_ENOMEM_START) && (Ret <= AEWF_ERROR_ENOMEM_END)) *pErrno = ENOMEM;
      else if ((Ret >= AEWF_ERROR_EINVAL_START) && (Ret <= AEWF_ERROR_EINVAL_END)) *pErrno = EINVAL;
      else                                                                         *pErrno = EIO;    // all other errors
   }
}

// ------------------------------------------
//  Wrappers for functions using silly types
// ------------------------------------------

static int wrap_uncompress (void *pDst, uint32_t *pDstLen, const void *pSrc, uint32_t SrcLen)
{
   int rc;
   uLongf DstLen0 = (uLongf) *pDstLen;
   rc = uncompress ((Bytef *) pDst, &DstLen0, (const Bytef *)pSrc, (uLong) SrcLen);
   *pDstLen = (uint32_t) DstLen0;
   
   return rc;
}


// ------------------------------------
//         Internal functions
// ------------------------------------

static int OpenFile (FILE **ppFile, const char *pFilename, uint64_t *pFileSize)
{
   *ppFile = fopen (pFilename, "r");
   if (*ppFile == NULL)
      return AEWF_FILE_OPEN_FAILED;

   if (pFileSize)
   {
      fseek(*ppFile, 0 , SEEK_END);
      *pFileSize = ftell(*ppFile);
      fseek(*ppFile, 0 , SEEK_SET);
   }
   return AEWF_OK;
}

static int CloseFile (FILE **ppFile)
{
   if (fclose (*ppFile))
      return AEWF_FILE_CLOSE_FAILED;
   *ppFile = NULL;

   return AEWF_OK;
}

#define NO_SEEK ULLONG_MAX

static int ReadFilePos (t_pAewf pAewf, FILE *pFile, void *pMem, uint64_t Size, uint64_t Pos)
{
   if (Size == 0)
      return AEWF_OK;

   if (Pos != NO_SEEK)
   {
      if (fseeko64 (pFile, Pos, SEEK_SET))
         return AEWF_FILE_SEEK_FAILED;
   }
   if (fread (pMem, Size, 1UL, pFile) != 1)
      return AEWF_FILE_READ_FAILED;

   return AEWF_OK;
}

static int ReadFileAllocPos (t_pAewf pAewf, FILE *pFile, void **ppMem, uint64_t Size, uint64_t Pos)
{
   *ppMem = NULL;

   if (Size)
   {
      *ppMem = (void*) malloc (Size);
      if (*ppMem == NULL)
         return AEWF_MEMALLOC_FAILED;

      CHK (ReadFilePos (pAewf, pFile, *ppMem, Size, Pos))
   }
   return AEWF_OK;
}

static int ReadFileAlloc (t_pAewf pAewf, FILE *pFile, void **ppMem, uint64_t Size)
{
   CHK (ReadFileAllocPos (pAewf, pFile, ppMem, Size, NO_SEEK))

   return AEWF_OK;
}

static int QsortCompareSegments (const void *pA, const void *pB)
{
   const t_pSegment pSegmentA = ((const t_pSegment)pA); //lint !e1773 Attempt to cast way const
   const t_pSegment pSegmentB = ((const t_pSegment)pB); //lint !e1773 Attempt to cast way const
   return (int)pSegmentA->Number - (int)pSegmentB->Number;
}

static int CreateInfoData (t_pAewf pAewf, t_pAewfSectionVolume pVolume,
                                          char *pHeader , uint64_t HeaderLen,
                                          char *pHeader2, uint64_t Header2Len,
                                          t_pAewfSectionHash pMD5)
{
   char           *pInfo1 = NULL;
   char           *pInfo2 = NULL;
   char           *pInfo3 = NULL;
   char           *pInfo4 = NULL;
   char           *pInfo5 = NULL;
   char           *pInfo6 = NULL;
   char           *pHdr   = NULL;
   uint32_t         HdrLen= 0;
   char           *pText  = NULL;
   char           *pCurrent;
   char           *pDesc  = NULL;
   char           *pData  = NULL;
   char           *pEnd;
   uint32_t         DstLen0;
   int             zrc;
   const uint32_t  MaxTextSize = 65536;
   uint32_t        UncompressedLen;
   int             rc = AEWF_OK;

   #define ASPRINTF(...)                  \
   {                                      \
      if (asprintf(__VA_ARGS__) < 0)      \
         CHK_LEAVE (AEWF_ASPRINTF_FAILED) \
   }

   ASPRINTF(&pInfo1, "Image size               %" PRIu64 " (%0.2f GiB)\n"
                     "Bytes per sector         %u\n"
                     "Sector count             %" PRIu64 "\n"
                     "Sectors per chunk        %u\n"
                     "Chunk count              %u\n"
                     "Error block size         %u\n"
                     "Compression level        %u\n"
                     "Media type               %02X\n"
                     "Cylinders/Heads/Sectors  %u/%u/%u\n"
                     "Media flags              %02X\n"
                     "Palm volume start sector %u\n"
                     "Smart logs start sector  %u\n",
                     pAewf->ImageSize, pAewf->ImageSize / (1024.0 * 1024.0* 1024.0),
                     pVolume->BytesPerSector,
                     pVolume->SectorCount,
                     pVolume->SectorsPerChunk,
                     pVolume->ChunkCount,
                     pVolume->ErrorBlockSize,
                     pVolume->CompressionLevel,
                     pVolume->MediaType,
                     pVolume->CHS_Cylinders, pVolume->CHS_Heads, pVolume->CHS_Sectors,
                     pVolume->MediaFlags,
                     pVolume->PalmVolumeStartSector,
                     pVolume->SmartLogsStartSector);
   ASPRINTF (&pInfo2, "AcquirySystemGUID        %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n",
             pVolume->AcquirySystemGUID[ 0], pVolume->AcquirySystemGUID[ 1], pVolume->AcquirySystemGUID[ 2], pVolume->AcquirySystemGUID[ 3],
             pVolume->AcquirySystemGUID[ 4], pVolume->AcquirySystemGUID[ 4], pVolume->AcquirySystemGUID[ 6], pVolume->AcquirySystemGUID[ 7],
             pVolume->AcquirySystemGUID[ 8], pVolume->AcquirySystemGUID[ 9], pVolume->AcquirySystemGUID[10], pVolume->AcquirySystemGUID[11],
             pVolume->AcquirySystemGUID[12], pVolume->AcquirySystemGUID[13], pVolume->AcquirySystemGUID[14], pVolume->AcquirySystemGUID[15]);

   if      (pHeader2) { pHdr = pHeader2; HdrLen = Header2Len; }  // 64 to 32 bit conversion -> not a problem,
   else if (pHeader ) { pHdr = pHeader;  HdrLen = HeaderLen;  }  // since size checks have been done before
   if (pHdr && HdrLen)
   {
      pText = (char *) malloc (MaxTextSize);
      if (pText == NULL)
         CHK_LEAVE (AEWF_MEMALLOC_FAILED)
      DstLen0 = MaxTextSize;
      zrc = wrap_uncompress (pText, &DstLen0, pHdr, HdrLen);
      UncompressedLen = DstLen0;
      if (zrc != Z_OK)
         CHK_LEAVE (AEWF_UNCOMPRESS_HEADER_FAILED)
      if (pHeader2)                                         // We must convert from silly Windows 2 byte wchar_t to
      {                                                     // correct Unix 4 byte wchar_t, before we can convert to UTF8
         wchar_t *pTemp  = (wchar_t*) malloc ((UncompressedLen/2)*sizeof(wchar_t));
         wchar_t *pStart = pTemp;

         if (pTemp == NULL)
            CHK_LEAVE (AEWF_MEMALLOC_FAILED)
         for (uint32_t i=0; i<(UncompressedLen/2); i++)
            pTemp[i] =   (wchar_t) (((unsigned char*)pText)[2*i  ])  |
                       (((wchar_t) (((unsigned char*)pText)[2*i+1])) << 8);

         if (*pStart == 0xFEFF)    // Skip BOM if it exists, or else
            pStart++;              // conversion to UTF8 might fail.
         (void) wcstombs (pText, pStart, UncompressedLen/2);
         SAFE_FREE (pTemp);
      }

      // Extract descriptor and data lines
      // ---------------------------------
      pCurrent = pText;
      while (pCurrent)
      {
          if (strcasestr(pCurrent, "main") == pCurrent)  // The header line is the one that
             break;                                      // follows the line beginning with "main"
          pCurrent = strstr (pCurrent, "\n");
          if (pCurrent)
             pCurrent++;
      }
      if (pCurrent)
      {
         pDesc = strstr (pCurrent, "\n");
         if (pDesc)
         {
            *pDesc++ = '\0';
            pData = strstr (pDesc, "\n");
            if (pData)
            {
               *pData++ = '\0';
               pEnd = strstr (pData, "\n");
               if (pEnd)
                  *pEnd = '\0';
            }
         }
      }

      // Scan descriptor and data lines
      // ------------------------------
      char       *pCurDesc = pDesc;
      char       *pCurData = pData;
      const char *pField;
      char       *pTabDesc;
      char       *pTabData;
      char       *pValue;
      int          wr = 0;
      time_t       Time;
      struct tm  *pTM;
      char         TimeBuff[64];
      int          Info3Len;

      if (pCurDesc && pCurData)
      {
         Info3Len = strlen (pCurData) + 4096;
         pInfo3 = (char *) malloc (Info3Len);
         if (pInfo3 == NULL)
            CHK_LEAVE (AEWF_MEMALLOC_FAILED)

         while (*pCurDesc && *pCurData && (wr<Info3Len))
         {
            pTabDesc = strstr (pCurDesc, "\t");
            pTabData = strstr (pCurData, "\t");
            if (pTabDesc) *pTabDesc = '\0';
            if (pTabData) *pTabData = '\0';
            if      (strcasecmp(pCurDesc, "a" ) == 0) pField = "Description";
            else if (strcasecmp(pCurDesc, "c" ) == 0) pField = "Case";
            else if (strcasecmp(pCurDesc, "n" ) == 0) pField = "Evidence";
            else if (strcasecmp(pCurDesc, "e" ) == 0) pField = "Examiner";
            else if (strcasecmp(pCurDesc, "t" ) == 0) pField = "Notes";
            else if (strcasecmp(pCurDesc, "md") == 0) pField = "Model";
            else if (strcasecmp(pCurDesc, "sn") == 0) pField = "Serial number";
            else if (strcasecmp(pCurDesc, "av") == 0) pField = "Imager version";
            else if (strcasecmp(pCurDesc, "ov") == 0) pField = "OS version";
            else if (strcasecmp(pCurDesc, "m" ) == 0) pField = "Acquired time";
            else if (strcasecmp(pCurDesc, "u" ) == 0) pField = "System time";
            else if (strcasecmp(pCurDesc, "p" ) == 0) pField = NULL;
            else if (strcasecmp(pCurDesc, "dc") == 0) pField = NULL;
            else                                      pField = "--";
            if (pField)
            {
               pValue = pCurData;
               if (strstr (pField, "time"))
               {
                  size_t w;

                  Time = atoll (pCurData);
                  pTM = localtime (&Time);
                  pValue = &TimeBuff[0];
                  w = strftime (pValue, sizeof(TimeBuff)  , "%Y-%m-%d %H:%M:%S (%z)", pTM);
                  snprintf (&pValue[w], sizeof(TimeBuff)-w, " (epoch %s)", pCurData);
               }
               wr += snprintf (&pInfo3[wr], Info3Len-wr, "%-17s %s\n", pField, pValue);
            }
            if (!pTabDesc || !pTabData)
               break;
            pCurDesc = pTabDesc+1;
            pCurData = pTabData+1;
         }
      }
   }

   if (pAewf->Segments == 1)
        ASPRINTF (&pInfo4, "%"PRIu64" segment file: %s\n",
                  pAewf->Segments,
                  pAewf->pSegmentArr[0].pName)
   else ASPRINTF (&pInfo4, "%"PRIu64" segment files\n   First: %s\n   Last:  %s\n",
                  pAewf->Segments,
                  pAewf->pSegmentArr[0                ].pName,
                  pAewf->pSegmentArr[pAewf->Segments-1].pName);

   ASPRINTF (&pInfo5, "%"PRIu64" tables\n", pAewf->Tables);

   if (pMD5)
      ASPRINTF  (&pInfo6, "MD5 stored in image: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
                          pMD5->MD5[0], pMD5->MD5[1], pMD5->MD5[2 ], pMD5->MD5[3 ], pMD5->MD5[4 ], pMD5->MD5[5 ], pMD5->MD5[6 ], pMD5->MD5[7 ],
                          pMD5->MD5[8], pMD5->MD5[9], pMD5->MD5[10], pMD5->MD5[11], pMD5->MD5[12], pMD5->MD5[13], pMD5->MD5[14], pMD5->MD5[15])
   else
      ASPRINTF  (&pInfo6, "MD5 stored in image: none")

   if (pInfo3)
        ASPRINTF (&pAewf->pInfo, "%s%s\n%s\n%s%s\n%s", pInfo1, pInfo2, pInfo3, pInfo4, pInfo5, pInfo6)
   else ASPRINTF (&pAewf->pInfo, "%s%s%s%s\n%s"      , pInfo1, pInfo2,         pInfo4, pInfo5, pInfo6)

   #undef ASPRINTF
   rc = AEWF_OK;

Leave:
   SAFE_FREE (pText );
   SAFE_FREE (pInfo1);
   SAFE_FREE (pInfo2);
   SAFE_FREE (pInfo3);
   SAFE_FREE (pInfo4);
   SAFE_FREE (pInfo5);
   SAFE_FREE (pInfo6);

   return rc;
}

static int AewfOpenSegment (t_pAewf pAewf, t_pTable pTable)
{
   t_pSegment pOldestSegment;

   if (pTable->pSegment->pFile != NULL) // is already opened ?
   {
      pAewf->SegmentCacheHits++;
      return AEWF_OK;
   }
   pAewf->SegmentCacheMisses++;

   // Check if another segment file must be closed first
   // --------------------------------------------------
   while (pAewf->OpenSegments >= pAewf->MaxOpenSegments)
   {
      pOldestSegment = NULL;

      for (uint64_t i=0; i<pAewf->Segments; i++)
      {
         if (pAewf->pSegmentArr[i].pFile == NULL)
            continue;
         if (pOldestSegment == NULL)
         {
            pOldestSegment = &pAewf->pSegmentArr[i];
         }
         else
         {
            if (pAewf->pSegmentArr[i].LastUsed < pOldestSegment->LastUsed)
               pOldestSegment = &pAewf->pSegmentArr[i];
         }
      }
      if (pOldestSegment == NULL)
         break;

      LOG ("Closing %s", pOldestSegment->pName);
      CHK (CloseFile (&pOldestSegment->pFile))
      pAewf->OpenSegments--;
   }

   // Open the desired segment file
   // -----------------------------
   uint64_t FileSize;
   LOG ("Opening %s", pTable->pSegment->pName);
   CHK (OpenFile(&pTable->pSegment->pFile, pTable->pSegment->pName, &FileSize))
   if (FileSize != pTable->pSegment->FileSize)
      return AEWF_FILESIZE_CHANGED;
   pAewf->OpenSegments++;

   return AEWF_OK;
}

static int AewfLoadEwfTable (t_pAewf pAewf, t_pTable pTable)
{
   t_pTable pOldestTable = NULL;

   if (pTable->pEwfTable != NULL) // is already loaded?
   {
      pAewf->TableCacheHits++;
      return AEWF_OK;
   }
   pAewf->TableCacheMisses++;

   // Check if another pEwfTable must be given up first
   // -------------------------------------------------
   while ((pAewf->TableCache + pTable->Size) > pAewf->MaxTableCache)
   {
      pOldestTable = NULL;

      for (uint64_t i=0; i<pAewf->Tables; i++)
      {
         if (pAewf->pTableArr[i].pEwfTable == NULL)
            continue;
         if (pOldestTable == NULL)
         {
            pOldestTable = &pAewf->pTableArr[i];
         }
         else
         {
            if (pAewf->pTableArr[i].LastUsed < pOldestTable->LastUsed)
               pOldestTable = &pAewf->pTableArr[i];
         }
      }
      if (pOldestTable == NULL)
         break;
      pAewf->TableCache -= pOldestTable->Size;
      SAFE_FREE (pOldestTable->pEwfTable, pOldestTable->Size);
      LOG ("Releasing table %" PRIu64 " (%lu bytes)", pOldestTable->Nr, pOldestTable->Size);
   }

   // Read the desired table into RAM
   // -------------------------------
   LOG ("Loading table %" PRIu64 " (%lu bytes)", pTable->Nr, pTable->Size);
   CHK (AewfOpenSegment (pAewf, pTable));
   CHK (ReadFileAllocPos (pAewf, pTable->pSegment->pFile, (void**) &pTable->pEwfTable, pTable->Size, pTable->Offset))
   pAewf->TableCache += pTable->Size;
   pAewf->TablesReadFromImage += pTable->Size;

   return AEWF_OK;
}

static int UpdateStats (t_pAewf pAewf, int Force)
{
   time_t   NowT;
   pid_t    pid;
   FILE   *pFile           = NULL;
   char   *pFilename       = NULL;
   char   *pCurrentWorkDir = NULL;
   int      rc;

   if (pAewf->pStatsPath)
   {
      time (&NowT);
      if (((NowT - pAewf->LastStatsUpdate) >= (int)pAewf->StatsRefresh) || Force)
      {
         pAewf->LastStatsUpdate = NowT;

         pid = getpid ();
         if (asprintf (&pFilename, "%s/stats_%d", pAewf->pStatsPath, pid) < 0)
            CHK_LEAVE (AEWF_MEMALLOC_FAILED)
         pFile = fopen (pFilename, "w");
         if (pFile == NULL) // May be the file is temporarly locked by someone else, let's retry in 1 second
         {
            pAewf->LastStatsUpdate = NowT - pAewf->StatsRefresh + 1;
            CHK_LEAVE (AEWF_OK)
         }

         fprintf (pFile, "Image segment files     %6"PRIu64"\n" , pAewf->Segments);
         fprintf (pFile, "Image tables            %6"PRIu64"\n" , pAewf->Tables);
         fprintf (pFile, "\n");
         fprintf (pFile, "Cache         hits      misses  ratio\n");
         fprintf (pFile, "--------------------------------------\n");
         fprintf (pFile, "Segment %10" PRIu64 "  %10" PRIu64 "  %5.1f%%\n", pAewf->SegmentCacheHits, pAewf->SegmentCacheMisses, (100.0*pAewf->SegmentCacheHits)/(pAewf->SegmentCacheHits+pAewf->SegmentCacheMisses));
         fprintf (pFile, "Table   %10" PRIu64 "  %10" PRIu64 "  %5.1f%%\n", pAewf->TableCacheHits  , pAewf->TableCacheMisses  , (100.0*pAewf->TableCacheHits)  /(pAewf->TableCacheHits  +pAewf->TableCacheMisses  ));
         fprintf (pFile, "Chunk   %10" PRIu64 "  %10" PRIu64 "  %5.1f%%\n", pAewf->ChunkCacheHits  , pAewf->ChunkCacheMisses  , (100.0*pAewf->ChunkCacheHits)  /(pAewf->ChunkCacheHits  +pAewf->ChunkCacheMisses  ));
         fprintf (pFile, "\n");
         fprintf (pFile, "Read operations          %10" PRIu64 "\n", pAewf->ReadOperations);
         fprintf (pFile, "Errors                   %10" PRIu64 "\n", pAewf->Errors);
         fprintf (pFile, "Open segment files       %10" PRIu64"\n" , pAewf->OpenSegments);
         fprintf (pFile, "Last error               %10d (%s)\n"         , pAewf->LastError, AewfGetErrorMessage (pAewf->LastError));
         fprintf (pFile, "Data read from image     %10.1f MiB (compressed)\n", pAewf->DataReadFromImage    / (1024.0*1024.0));
         fprintf (pFile, "Data read from image     %10.1f MiB (raw)\n"       , pAewf->DataReadFromImageRaw / (1024.0*1024.0));
         fprintf (pFile, "Data requested by caller %10.1f MiB\n"             , pAewf->DataRequestedByCaller/ (1024.0*1024.0));
         fprintf (pFile, "Tables read from image   %10.1f MiB\n"             , pAewf->TablesReadFromImage  / (1024.0*1024.0));
         fprintf (pFile, "RAM used as table cache  %10.1f MiB\n"             , pAewf->TableCache           / (1024.0*1024.0));
         fprintf (pFile, "Size of all image tables %10.1f MiB\n"             , pAewf->TotalTableSize       / (1024.0*1024.0));
         fprintf (pFile, "\n");
         fprintf (pFile, "Histogram of read request sizes\n");
         fprintf (pFile, "-------------------------------\n");
         fprintf (pFile, "  0  < Size <= 32K  %10" PRIu64"\n", pAewf->ReadSizesArr[READSIZE_32K]);
         fprintf (pFile, " 32K < Size <= 64K  %10" PRIu64"\n", pAewf->ReadSizesArr[READSIZE_64K]);
         fprintf (pFile, " 64K < Size <= 128K %10" PRIu64"\n", pAewf->ReadSizesArr[READSIZE_128K]);
         fprintf (pFile, "128K < Size <= 256K %10" PRIu64"\n", pAewf->ReadSizesArr[READSIZE_256K]);
         fprintf (pFile, "256K < Size <= 512K %10" PRIu64"\n", pAewf->ReadSizesArr[READSIZE_512K]);
         fprintf (pFile, "512K < Size <= 1M   %10" PRIu64"\n", pAewf->ReadSizesArr[READSIZE_1M]);
         fprintf (pFile, "       Size >  1M   %10" PRIu64"\n", pAewf->ReadSizesArr[READSIZE_ABOVE_1M]);

         pCurrentWorkDir = getcwd (NULL, 0);
         if (pCurrentWorkDir == NULL)
            CHK_LEAVE (AEWF_MEMALLOC_FAILED)
         fprintf (pFile, "\nCurrent working directory: %s\n", pCurrentWorkDir);
      }
   }
   rc = AEWF_OK;

Leave:
   if (pFile)
      (void) fclose (pFile);
   SAFE_FREE (pCurrentWorkDir);
   SAFE_FREE (pFilename);

   return rc;
}

// -----------------------------------------------------------------------------
//  Legacy functions - Single threaded read function from former xmount version
// -----------------------------------------------------------------------------

// AewfReadChunkLegacy0 reads exactly one chunk. It expects the EWF table be present
// in memory and the required segment be opened.

static int AewfReadChunkLegacy0 (t_pAewf pAewf, t_pTable pTable, uint64_t AbsoluteChunk, uint64_t TableChunk)
{
   int                  Compressed;
   uint64_t             SeekPos;
   t_pAewfSectionTable pEwfTable;
   uint32_t             Offset;
   uint32_t             ReadLen;
   uLongf               DstLen0;
   int                  zrc;
   uint32_t             CalcCRC;
   uint32_t           *pStoredCRC;
   uint64_t             ChunkSize;
   int                  Ret = AEWF_OK;

   pEwfTable  = pTable->pEwfTable;
   if (pEwfTable == NULL)
      return AEWF_ERROR_EWF_TABLE_NOT_READY;
   if (pTable->pSegment->pFile == NULL)
      return AEWF_ERROR_EWF_SEGMENT_NOT_READY;

   Compressed = pEwfTable->OffsetArray[TableChunk] &  AEWF_COMPRESSED;
   Offset     = pEwfTable->OffsetArray[TableChunk] & ~AEWF_COMPRESSED;
   SeekPos    = pEwfTable->TableBaseOffset + Offset;

   if (TableChunk < (pEwfTable->ChunkCount-1))
        ReadLen = (pEwfTable->OffsetArray[TableChunk+1] & ~AEWF_COMPRESSED) - Offset;
   else ReadLen = pTable->SectionSectorsSize - (SeekPos - pTable->SectionSectorsPos);
//   else ReadLen = pAewf->ChunkBuffSize;  // This also  works! It looks as if uncompress is able to find out by itself the real size of the input data. But this line could lead to reading beyond EOF...

   if (ReadLen > pAewf->ChunkBuffSize)
   {
      LOG ("Chunk too big %u / %u", ReadLen, pAewf->ChunkBuffSize);
      return AEWF_CHUNK_TOO_BIG;
   }

   ChunkSize = pAewf->ChunkSize;
   if (AbsoluteChunk == (pAewf->Chunks-1))   // The very last chunk of the image may be smaller than the default
   {                                         // chunk size if the image isn't a multiple of the chunk size.
      ChunkSize = pAewf->ImageSize % pAewf->ChunkSize;
      if (ChunkSize == 0)
         ChunkSize = pAewf->ChunkSize;
   }

   if (Compressed)
   {
      CHK (ReadFilePos (pAewf, pTable->pSegment->pFile, pAewf->pChunkBuffCompressed, ReadLen, SeekPos))
      DstLen0 = pAewf->ChunkBuffSize;
      zrc = uncompress ((unsigned char*)pAewf->pChunkBuffUncompressed, &DstLen0, (const Bytef*)pAewf->pChunkBuffCompressed, ReadLen);
      if (zrc != Z_OK)
         Ret = AEWF_UNCOMPRESS_FAILED;
      if (DstLen0 != ChunkSize)
         Ret = AEWF_BAD_UNCOMPRESSED_LENGTH;
   }
   else
   {
      CHK (ReadFilePos (pAewf, pTable->pSegment->pFile, pAewf->pChunkBuffUncompressed, ReadLen, SeekPos))
      CalcCRC    =  adler32 (1, (const Bytef *) pAewf->pChunkBuffUncompressed, ChunkSize);
      pStoredCRC = (uint32_t *) (pAewf->pChunkBuffUncompressed + ChunkSize);  //lint !e826 Suspicious pointer-to-pointer conversion (area too small)
      if (CalcCRC != *pStoredCRC)
         Ret = AEWF_CHUNK_CRC_ERROR;
   }

   pAewf->DataReadFromImage    += ReadLen;
   pAewf->DataReadFromImageRaw += ChunkSize;
   if (Ret == AEWF_OK)
   {
      pAewf->ChunkInBuff                  = AbsoluteChunk;
      pAewf->ChunkBuffUncompressedDataLen = ChunkSize;
   }
   else
   {
      pAewf->ChunkInBuff                  = AEWF_NONE;
      pAewf->ChunkBuffUncompressedDataLen = 0;
   }
   return Ret;
}

static int AewfReadChunkLegacy (t_pAewf pAewf, uint64_t AbsoluteChunk, char **ppBuffer, uint64_t *pLen)
{
   t_pTable  pTable;
   int        Found=FALSE;
   uint64_t   TableChunk;
   uint64_t   TableNr;

   *ppBuffer = pAewf->pChunkBuffUncompressed;
   *pLen     = 0;

   if (pAewf->ChunkInBuff == AbsoluteChunk)
   {
      *pLen = pAewf->ChunkBuffUncompressedDataLen;
      pAewf->ChunkCacheHits++;
      return AEWF_OK;
   }
   pAewf->ChunkCacheMisses++;

   // Find table containing desired chunk
   // -----------------------------------
   for (TableNr=0; TableNr<pAewf->Tables; TableNr++)
   {
      pTable = &pAewf->pTableArr[TableNr];
      Found  = (AbsoluteChunk >= pTable->ChunkFrom) &&
               (AbsoluteChunk <= pTable->ChunkTo);
      if (Found)
         break;
   }
   if (!Found)
      CHK (AEWF_CHUNK_NOT_FOUND)

   // Load corresponding table and get chunk
   // --------------------------------------
   pTable->LastUsed = time(NULL);                  //lint !e771 pTable' (line 640) conceivably not initialized
   pTable->pSegment->LastUsed = pTable->LastUsed;  // Update LastUsed here, in order not to remove the required data from cache

   CHK (AewfLoadEwfTable (pAewf, pTable))
   CHK (AewfOpenSegment  (pAewf, pTable));
   if ((AbsoluteChunk - pTable->ChunkFrom) > UINT_MAX)
      CHK (AEWF_ERROR_IN_CHUNK_NUMBER)
   TableChunk = AbsoluteChunk - pTable->ChunkFrom;
//   LOG ("table %d / entry %" PRIu64 " (%s)", TableNr, TableChunk, pTable->pSegment->pName)
   CHK (AewfReadChunkLegacy0 (pAewf, pTable, AbsoluteChunk, TableChunk))
   *pLen = pAewf->ChunkBuffUncompressedDataLen;

   return AEWF_OK;
}

static int AewfReadLegacy (t_pAewf pAewf, char *pBuf, uint64_t Seek64, size_t Count, size_t *pRead, int *pErrno)
{
   char    *pChunkBuffer;
   uint64_t  Chunk;
   uint64_t  Remaining;
   uint64_t  ChunkLen, Ofs, ToCopy;

   Ofs       = Seek64 % pAewf->ChunkSize;
   Chunk     = Seek64 / pAewf->ChunkSize;
   Remaining = Count;

   while (Remaining)
   {
      CHK (AewfReadChunkLegacy (pAewf, Chunk, &pChunkBuffer, &ChunkLen))
      if (ChunkLen == 0)
         return AEWF_CHUNK_LENGTH_ZERO;

      ToCopy = GETMIN (ChunkLen-Ofs, Remaining);
      memcpy (pBuf, pChunkBuffer+Ofs, ToCopy);
      Remaining -= ToCopy;
      pBuf      += ToCopy;
      *pRead    += ToCopy;
      Ofs        = 0;
      Chunk++;
   }

   return AEWF_OK;
}

// ------------------------------------------------------------------------------------
//  MT functions - Read function with multi-threaded decompression and CRC calculation
// ------------------------------------------------------------------------------------

// AewfThreadUncompress is run whenever compressed data chunk are encountered. It uncompresses
// the data and copies it to the correct destination.
static void* AewfThreadUncompress (void *pArg)
{
   t_pAewfThread pThread = (t_pAewfThread) pArg;
   uLongf         DstLen0;
   int            zrc;

   pThread->ReturnCode = AEWF_OK;
   DstLen0 = pThread->pAewf->ChunkBuffSize;
   zrc = uncompress ((unsigned char*)pThread->pChunkBuffUncompressed, &DstLen0,
                       (const Bytef*)pThread->pChunkBuffCompressed  ,
                                     pThread->ChunkBuffCompressedDataLen);
   if (zrc != Z_OK)
      pThread->ReturnCode = AEWF_UNCOMPRESS_FAILED;
   else if (DstLen0 != pThread->ChunkBuffUncompressedDataLen)
      pThread->ReturnCode = AEWF_BAD_UNCOMPRESSED_LENGTH;
   else
      memcpy (pThread->pBuf, pThread->pChunkBuffUncompressed+pThread->Ofs, pThread->Len);

   return NULL;
}

// AewfThreadCRC is called for uncompressed data chunks. It verifies the CRC and
// copies the data to the correct destination.
static void* AewfThreadCRC (void *pArg)
{
   t_pAewfThread  pThread = (t_pAewfThread) pArg;
   uint32_t      *pStoredCRC;
   uint32_t        CalcCRC;

   pThread->ReturnCode = AEWF_OK;
   CalcCRC    =  adler32 (1, (const Bytef *) pThread->pChunkBuffUncompressed, pThread->ChunkBuffUncompressedDataLen);
   pStoredCRC = (uint32_t *) (pThread->pChunkBuffUncompressed + pThread->ChunkBuffUncompressedDataLen);  //lint !e826 Suspicious pointer-to-pointer conversion (area too small)
   if (CalcCRC != *pStoredCRC)
      pThread->ReturnCode = AEWF_CHUNK_CRC_ERROR;
   memcpy (pThread->pBuf, pThread->pChunkBuffUncompressed+pThread->Ofs, pThread->Len);

   return NULL;
}

// AewfThreadCopy is used in case of a cache hit. It simply copies the data from the
// uncompressed buffer to the destination.
static void* AewfThreadCopy (void *pArg)
{
   t_pAewfThread pThread = (t_pAewfThread) pArg;

   memcpy (pThread->pBuf, pThread->pChunkBuffUncompressed+pThread->Ofs, pThread->Len);
   pThread->ReturnCode = AEWF_OK;

   return NULL;
}


// AewfReadChunkMT0 reads exactly one chunk. It expects the EWF table be present
// in memory and the required segment be opened.
static int AewfReadChunkMT0 (t_pAewf pAewf, t_pTable pTable, uint64_t AbsoluteChunk, uint64_t TableChunk, char *pBuf, uint64_t Ofs, uint64_t Len)
{
   int                  Compressed;
   uint64_t             SeekPos;
   t_pAewfSectionTable pEwfTable;
   uint32_t             Offset;
   uint32_t             ReadLen;
   int                  prc;
   uint64_t             ChunkSize;
   int                  Ret = AEWF_OK;

//   LOG ("Called - AbsoluteChunk=%'" PRIu64, AbsoluteChunk);

   pEwfTable = pTable->pEwfTable;
   if (pEwfTable == NULL)
      return AEWF_ERROR_EWF_TABLE_NOT_READY;
   if (pTable->pSegment->pFile == NULL)
      return AEWF_ERROR_EWF_SEGMENT_NOT_READY;

   Compressed = pEwfTable->OffsetArray[TableChunk] &  AEWF_COMPRESSED;
   Offset     = pEwfTable->OffsetArray[TableChunk] & ~AEWF_COMPRESSED;
   SeekPos    = pEwfTable->TableBaseOffset + Offset;

   if (TableChunk < (pEwfTable->ChunkCount-1))
        ReadLen = (pEwfTable->OffsetArray[TableChunk+1] & ~AEWF_COMPRESSED) - Offset;
   else ReadLen = pTable->SectionSectorsSize - (SeekPos - pTable->SectionSectorsPos);
//   else ReadLen = pAewf->ChunkBuffSize;  // This also  works! It looks as if uncompress is able to find out by itself the real size of the input data. But this line could lead to reading beyond EOF...

   if (ReadLen > pAewf->ChunkBuffSize)
   {
      LOG ("Chunk too big %u / %u", ReadLen, pAewf->ChunkBuffSize);
      CHK (AEWF_CHUNK_TOO_BIG)
   }

   ChunkSize = pAewf->ChunkSize;
   if (AbsoluteChunk == (pAewf->Chunks-1))   // The very last chunk of the image may be smaller than the default
   {                                         // chunk size if the image isn't a multiple of the chunk size.
      ChunkSize = pAewf->ImageSize % pAewf->ChunkSize;
      if (ChunkSize == 0)
         ChunkSize = pAewf->ChunkSize;
   }

   for (uint64_t i=0; i<pAewf->Threads; i++)  // Search for first free thread for doing the job
   {
      t_pAewfThread pThread = &(pAewf->pThreadArr[i]);
      if (pThread->State == AEWF_IDLE)
      {
         pThread->ChunkBuffCompressedDataLen   = ReadLen;
         pThread->ChunkBuffUncompressedDataLen = ChunkSize;  // uncompress should return this size (if it's a compressed chunk)
         pThread->ChunkInBuff                  = AbsoluteChunk;

         pThread->pBuf                         = pBuf; // These 3 parameters specify which part
         pThread->Ofs                          = Ofs;  // of the resulting chunk data should be
         pThread->Len                          = Len;  // copied to which location.
         if (Compressed)
         {
            CHK (ReadFilePos (pAewf, pTable->pSegment->pFile, pThread->pChunkBuffCompressed, ReadLen, SeekPos))
            prc = pthread_create (&pThread->ID, NULL, AewfThreadUncompress, pThread);
         }
         else
         {
            CHK (ReadFilePos (pAewf, pTable->pSegment->pFile, pThread->pChunkBuffUncompressed, ReadLen, SeekPos))
            prc = pthread_create (&pThread->ID, NULL, AewfThreadCRC, pThread);
         }
         if (prc != 0)
            Ret = AEWF_ERROR_PTHREAD;
         pThread->State = AEWF_LAUNCHED;  // Only switch to LAUNCHED now that we know that pthread_create was successful
         break;
      }
   }

   pAewf->DataReadFromImage    += ReadLen;
   pAewf->DataReadFromImageRaw += ChunkSize;

   return Ret;
}

static int AewfReadChunkMT (t_pAewf pAewf, uint64_t AbsoluteChunk, char *pBuf, uint64_t Ofs, uint64_t Len)
{
   t_pTable  pTable;
   int        Found=FALSE;
   uint64_t   TableChunk;
   uint64_t   TableNr;
   int        rc;

//   LOG ("Called - AbsoluteChunk=%'" PRIu64, AbsoluteChunk);

   // Check if chunk already is in cache
   // ----------------------------------
   for (uint32_t i=0; i<pAewf->Threads; i++)
   {
      t_pAewfThread pThread = &(pAewf->pThreadArr[i]);
      if (pThread->ChunkInBuff == AbsoluteChunk)
      {
         pThread->pBuf  = pBuf;
         pThread->Ofs   = Ofs;
         pThread->Len   = Len;
         rc = pthread_create (&pThread->ID, NULL, AewfThreadCopy, pThread);
         if (rc != 0)
            return AEWF_ERROR_PTHREAD;
         pThread->State = AEWF_LAUNCHED;
         pAewf->ChunkCacheHits++;

         return AEWF_OK;
      }
   }
   pAewf->ChunkCacheMisses++;

   // Find table containing desired chunk
   // -----------------------------------
   for (TableNr=0; TableNr<pAewf->Tables; TableNr++)
   {
      pTable = &pAewf->pTableArr[TableNr];
      Found  = (AbsoluteChunk >= pTable->ChunkFrom) &&
               (AbsoluteChunk <= pTable->ChunkTo);
      if (Found)
         break;
   }
   if (!Found)
      CHK (AEWF_CHUNK_NOT_FOUND)

   // Load corresponding table and get chunk
   // --------------------------------------
   pTable->LastUsed = time(NULL);                  //lint !e771 pTable' (line 640) conceivably not initialized
   pTable->pSegment->LastUsed = pTable->LastUsed;  // Update LastUsed here, in order not to remove the required data from cache

   CHK (AewfLoadEwfTable (pAewf, pTable))
   CHK (AewfOpenSegment  (pAewf, pTable));
   TableChunk = AbsoluteChunk - pTable->ChunkFrom;
   if (TableChunk > UINT_MAX)
      CHK (AEWF_ERROR_IN_CHUNK_NUMBER)
//   LOG ("table %d / entry %" PRIu64 " (%s)", TableNr, TableChunk, pTable->pSegment->pName)
   CHK (AewfReadChunkMT0 (pAewf, pTable, AbsoluteChunk, TableChunk, pBuf, Ofs, Len))

   return AEWF_OK;
}

static int AewfReadMT0 (t_pAewf pAewf, char *pBuf, uint64_t Seek64, size_t Count, size_t *pRead, int *pErrno)
{
   uint64_t       AbsoluteChunk;
   uint64_t       Remaining;
   uint64_t       Len, Ofs;
   t_pAewfThread pThread;
   int            RcRead;
   int            RcThread;
   int            rc;

   Ofs           = Seek64 % pAewf->ChunkSize;
   AbsoluteChunk = Seek64 / pAewf->ChunkSize;
   Remaining     = Count;
   *pRead        = 0;

   // Launch all read/decompress jobs
   // -------------------------------
   while (Remaining)
   {
      Len = GETMIN (pAewf->ChunkSize - Ofs, Remaining);
      RcRead = AewfReadChunkMT (pAewf, AbsoluteChunk, pBuf, Ofs, Len);
      if (RcRead)
         break;
      Remaining -= Len;
      pBuf      += Len;
      Ofs        = 0;
      AbsoluteChunk++;
   }

   // Wait for threads
   // ----------------
   RcThread=0;
   for (uint32_t i=0; i<pAewf->Threads; i++)
   {
      pThread = &(pAewf->pThreadArr[i]);
//      LOG ("Checking thread %d -> %d", i, pThread->State);
      if (pThread->State == AEWF_LAUNCHED)
      {
         pthread_join (pThread->ID, NULL);
         pThread->State = AEWF_IDLE;
         rc = pThread->ReturnCode;
         if (rc)
            RcThread = rc;
         *pRead += pThread->Len;

      }
   }
   CHK (RcRead)
   CHK (RcThread)

   return AEWF_OK;
}

static int AewfReadMT (t_pAewf pAewf, char *pBuf, uint64_t Seek64, size_t Count, size_t *pRead, int *pErrno)
{
   uint64_t ToRead;
   uint64_t MaxPerLoop;
   size_t   Read;

   MaxPerLoop = pAewf->Threads * pAewf->ChunkSize;
   while (Count)
   {
      ToRead = GETMIN (MaxPerLoop, Count);
      Read   = 0;
      CHK (AewfReadMT0 (pAewf, pBuf, Seek64, ToRead, &Read, pErrno))
      *pRead += Read;
      pBuf   += Read;
      Seek64 += Read;
      Count  -= Read;
   }

   return AEWF_OK;
}

static uint32_t GetCPUs (t_pAewf pAewf)
{
   int32_t CPUs;

   CPUs = sysconf( _SC_NPROCESSORS_ONLN );
   if (CPUs < 0)
   {
      if (pAewf)
         LOG ("Number of CPUs could not be determined, assume 4 CPUs");
      CPUs = 4;
   }
   return (uint32_t)CPUs;
}

// ---------------
//  API functions
// ---------------

static int AewfInit(void **pp_init_handle)
{
    *pp_init_handle = NULL;

    return AEWF_OK;
}

static int AewfDeInit(void **pp_init_handle)
{
    return AEWF_OK;
}

static inline int AewfCheckHandle (void *pHandle)
{
   t_pAewf pAewf = (t_pAewf) pHandle;

   if (pAewf == NULL)
      return AEWF_HANDLE_IS_NULL;

   if (pAewf->Magic != AEWF_MAGIC)
      return AEWF_MAGIC_BROKEN;

   return AEWF_OK;
}

static int AewfInitHandle (void *pHandle)
{
   t_pAewf pAewf = (t_pAewf) pHandle;

   CHK (AewfCheckHandle (pHandle))

   pAewf->ChunkInBuff           = AEWF_NONE;
   pAewf->pErrorText            = NULL;
   pAewf->SegmentCacheHits      = 0;
   pAewf->SegmentCacheMisses    = 0;
   pAewf->TableCacheHits        = 0;
   pAewf->TableCacheMisses      = 0;
   pAewf->ChunkCacheHits        = 0;
   pAewf->ChunkCacheMisses      = 0;
   pAewf->ReadOperations        = 0;
   pAewf->DataReadFromImage     = 0;
   pAewf->DataReadFromImageRaw  = 0;
   pAewf->DataRequestedByCaller = 0;
   pAewf->TablesReadFromImage   = 0;
   pAewf->ChunksRead            = 0;
   pAewf->BytesRead             = 0;
   memset (pAewf->ReadSizesArr, 0, sizeof (pAewf->ReadSizesArr));
   pAewf->Errors                = 0;
   pAewf->LastError             = AEWF_OK;
   pAewf->pInfo                 = NULL;
   pAewf->pThreadArr            = NULL;

   return AEWF_OK;
}

static int AewfCreateHandle (void **ppHandle, void *p_init_handle, const char *pFormat, uint8_t Debug)
{
   t_pAewf pAewf;

   *ppHandle = NULL;

   // Create handle and clear it
   // --------------------------
   pAewf = (t_pAewf) malloc (sizeof(t_Aewf));
   if (pAewf == NULL)
      return AEWF_MEMALLOC_FAILED;
   memset(pAewf,0,sizeof(t_Aewf));
   pAewf->Magic = AEWF_MAGIC;
   CHK (AewfInitHandle (pAewf));

   pAewf->LogStdout       = Debug;
   // Values below may be overwritten by AewfOptionsParse
   pAewf->MaxTableCache   = AEWF_DEFAULT_TABLECACHE * 1024*1024;
   pAewf->MaxOpenSegments = AEWF_DEFAULT_MAXOPENSEGMENTS;
   pAewf->StatsRefresh    = AEWF_DEFAULT_STATSREFRESH;
   pAewf->Threads         = GetCPUs(pAewf);
   pAewf->pStatsPath      = NULL;
   pAewf->pLogPath        = NULL;

   *ppHandle = (void*) pAewf;

   return AEWF_OK;
}

int AewfDestroyHandle(void **ppHandle)
{
   t_pAewf pAewf = (t_pAewf) *ppHandle;

   LOG ("Called");
   LOG ("Remark: 'Ret' won't be logged"); // Handle gets destroyed, 'ret' logging not possible

   CHK (AewfCheckHandle (*ppHandle))

   if (pAewf->Open)
      (void) AewfClose (*ppHandle);

   SAFE_FREE (pAewf->pLogPath  );
   SAFE_FREE (pAewf->pStatsPath);
   SAFE_FREE (pAewf->pStatsPath);

   memset (pAewf, 0, sizeof(t_Aewf));
   free (pAewf);
   *ppHandle = NULL;

   return AEWF_OK;
}

int AewfOpen (void *pHandle, const char **ppFilenameArr, uint64_t FilenameArrLen)
{
   t_pAewf                 pAewf = (t_pAewf) pHandle;
   t_AewfFileHeader         FileHeader;
   t_AewfSection            Section;
   FILE                   *pFile;
   t_pSegment              pSegment;
   t_pSegment              pPrevSegment;
   t_pTable                pTable;
   uint64_t                 Pos;
   t_pAewfSectionTable     pEwfTable   = NULL;
   t_pAewfSectionVolume    pVolume     = NULL;
   t_pAewfSectionHash      pMD5        = NULL;
   char                   *pHeader     = NULL;
   char                   *pHeader2    = NULL;
   int                      LastSection;
   uint64_t                 SectionSectorsPos;
   uint64_t                 SectionSectorsSize;
   uint64_t                 HeaderLen  = 0;
   uint64_t                 Header2Len = 0;
   uint32_t                 SectionsCount;
   uint64_t                 FileSize;
   int                      rc;

   LOG ("Called - Files=%" PRIu64, FilenameArrLen);

   CHK_LEAVE (AewfCheckHandle (pHandle))
   if (pAewf->Open)
      CHK_LEAVE (AEWF_ALREADY_OPEN)

   CHK_LEAVE (AewfInitHandle  (pAewf));   // Re-init vital structures
   pAewf->Open = TRUE;

   // Create pSegmentArr and put the segment files in it
   // --------------------------------------------------
   size_t SegmentArrLen = FilenameArrLen * sizeof(t_Segment);
   pAewf->pSegmentArr   = (t_pSegment) malloc (SegmentArrLen);
   pAewf->Segments      = FilenameArrLen;
   if (pAewf->pSegmentArr == NULL)
      CHK_LEAVE (AEWF_MEMALLOC_FAILED)
   memset (pAewf->pSegmentArr, 0, SegmentArrLen);

   for (uint64_t i=0; i<FilenameArrLen; i++)
   {
      pSegment = &pAewf->pSegmentArr[i];
      pSegment->pName = realpath (ppFilenameArr[i], NULL); // realpath allocates a buffer of the necessary length

      LOG ("Opening segment %s", ppFilenameArr[i]);
      CHK_LEAVE (OpenFile (&pFile, pSegment->pName, &pSegment->FileSize))
      CHK_LEAVE (ReadFilePos (pAewf, pFile, (void*)&FileHeader, sizeof(FileHeader), 0))

      if (memcmp (FileHeader.Signature, AEWF_SIGNATURE, sizeof (AEWF_SIGNATURE)) != 0 )
      {
         LOG ("Error: Bad file signature in %s", pSegment->pName);
         CHK_LEAVE (AEWF_BAD_FILE_SIGNATURE)
      }

      pSegment->Number   = FileHeader.SegmentNumber;
      pSegment->LastUsed = 0;
      pSegment->pFile    = NULL;

      CHK_LEAVE (CloseFile (&pFile))
   }

   // Put segment array into correct sequence and check if segment numbers are correct
   // --------------------------------------------------------------------------------
   qsort (pAewf->pSegmentArr, pAewf->Segments, sizeof (t_Segment), &QsortCompareSegments);
   pPrevSegment = NULL;
   for (uint64_t i=0; i<pAewf->Segments; i++)
   {
      pSegment = &(pAewf->pSegmentArr[i]);
      if (pPrevSegment)
      {
         if (pSegment->Number == pPrevSegment->Number)
         {
            LOG ("Error: Duplicate segment numbers");
            LOG ("Segment files %s and %s have both segment number %u", pPrevSegment->pName, pSegment->pName, pSegment->Number);
            CHK_LEAVE (AEWF_DUPLICATE_SEGMENT_NUMBER)
         }
      }
      if (pSegment->Number != (i+1))
      {
         LOG ("Error: Missing segment number(s)");
         if (pPrevSegment)
              LOG ("Previous segment file %s has segment number %u", pPrevSegment->pName, pPrevSegment->Number)
         else LOG ("No previous segment file.")
         LOG ("Following segment file %s has segment number %u", pSegment->pName    , pSegment->Number    );
         CHK_LEAVE (AEWF_MISSING_SEGMENT_NUMBER)
      }
      pPrevSegment = pSegment;
   }

   // Find all tables in the segment files
   // ------------------------------------
   pAewf->pTableArr      = NULL;
   pAewf->Tables         = 0;
   pAewf->Chunks         = 0;
   pAewf->TotalTableSize = 0;
   SectionSectorsPos     = 0;
   SectionSectorsSize    = 0;

   LOG ("Reading tables");
   for (uint64_t i=0; i<pAewf->Segments; i++)
   {
      pSegment = &pAewf->pSegmentArr[i];
      CHK_LEAVE (OpenFile (&pFile, pSegment->pName, &FileSize))
      if (FileSize != pSegment->FileSize)
         CHK_LEAVE (AEWF_FILESIZE_CHANGED)

      CHK_LEAVE (ReadFilePos (pAewf, pFile, &FileHeader, sizeof(FileHeader), 0))
      Pos = sizeof (FileHeader);
      LOG ("Segment %s ", pSegment->pName);
      SectionsCount = 0;
      // Search for the important sections
      do
      {
         if (++SectionsCount > AEWF_MAX_SECTION_COUNT)
         {
            LOG ("Error: Segment file has unusual high number of sections (%s)", pSegment->pName);
            CHK_LEAVE (AEWF_TOO_MANY_SECTIONS)
         }

         CHK_LEAVE (ReadFilePos (pAewf, pFile, &Section, sizeof (t_AewfSection), Pos))
         if ((Pos + Section.Size) > pSegment->FileSize)
            CHK_LEAVE (AEWF_SECTION_BEYOND_EOF)

         int IsHeader  = strcasecmp ((char *)Section.Type, "header"  ) == 0;
         int IsHeader2 = strcasecmp ((char *)Section.Type, "header2" ) == 0;

         if (IsHeader || IsHeader2)
         {
            void     **ppHdr   = IsHeader ? (void **) &pHeader : (void **) &pHeader2;
            uint64_t  *pHdrLen = IsHeader ? &HeaderLen         : &Header2Len;

            if (Section.Size <= sizeof (t_AewfSection))  // There must be at least 1 byte of header data
               CHK_LEAVE (AEWF_SECTION_HEADER_WRONG_SIZE)

            if (*ppHdr == NULL)
            {
               *pHdrLen = Section.Size - sizeof(t_AewfSection);
               if (*pHdrLen > AEWF_MAX_HEADER_LEN)
                  CHK_LEAVE (AEWF_SECTION_HEADER_WRONG_SIZE)
               CHK_LEAVE (ReadFileAlloc (pAewf, pFile, ppHdr, *pHdrLen))
            }
         }
         else if (strcasecmp ((char *)Section.Type, "sectors") == 0)
         {
            if (Section.Size < sizeof (t_AewfSection))
               CHK_LEAVE (AEWF_SECTION_SECTORS_WRONG_SIZE)
            SectionSectorsPos  = Pos;
            SectionSectorsSize = Section.Size;
         }
         else if (strcasecmp ((char *)Section.Type, "table") == 0)
         {
            if (pVolume == NULL)                               CHK_LEAVE (AEWF_VOLUME_MUST_PRECEDE_TABLES)
            if (SectionSectorsSize == 0)                       CHK_LEAVE (AEWF_SECTORS_MUST_PRECEDE_TABLES)
            if (Section.Size < (sizeof (t_AewfSection) + 
                                sizeof (t_AewfSectionTable)))  CHK_LEAVE (AEWF_SECTION_TABLE_WRONG_SIZE)

            CHK_LEAVE (ReadFileAlloc (pAewf, pFile, (void**) &pEwfTable, sizeof(t_AewfSectionTable))) // No need to read the part that contains the chunk offsets (array at structure end)
            if (pEwfTable->ChunkCount)  // Disregard tables having zero chunks
            {
               pAewf->Tables++;
               pAewf->pTableArr = (t_pTable) realloc (pAewf->pTableArr, pAewf->Tables * sizeof (t_Table));
               pTable = &pAewf->pTableArr[pAewf->Tables-1];
               pTable->Nr                 = pAewf->Tables-1;
               pTable->pSegment           = pSegment;
               pTable->Offset             = Pos          + sizeof (t_AewfSection);
               pTable->Size               = Section.Size - sizeof (t_AewfSection); 
               pTable->ChunkCount         = pEwfTable->ChunkCount;
               pTable->LastUsed           = 0;
               pTable->pEwfTable          = NULL;
               pTable->ChunkFrom          = pAewf->Chunks;
               pTable->SectionSectorsPos  = SectionSectorsPos;
               pTable->SectionSectorsSize = SectionSectorsSize;
               pAewf->TotalTableSize     += pTable->Size;
               pAewf->Chunks             += pTable->ChunkCount;
               pTable->ChunkTo            = pAewf->Chunks-1;
               SectionSectorsPos  = 0;
               SectionSectorsSize = 0;

               // Is the offset array big enough for the number of chunks?
               // Is the file long enough for holding the table?
               uint64_t MaxArrayEntries = (pTable->Size - sizeof(t_AewfSectionTable)) / sizeof(uint32_t);
               if (MaxArrayEntries                 < pTable->ChunkCount)   CHK_LEAVE (AEWF_SECTION_TABLE_WRONG_SIZE)
               if ((pTable->Offset + pTable->Size) > pSegment->FileSize)   CHK_LEAVE (AEWF_SECTION_TABLE_BEYOND_EOF)
            }
            SAFE_FREE (pEwfTable, sizeof(t_AewfSectionTable));
         }

         else if ( ((strcasecmp ((char *)Section.Type, "volume") == 0) || // Guymager works with the volume section. Others use different names
                    (strcasecmp ((char *)Section.Type, "disk"  ) == 0) || // for it, but it all is the same. See Joachim Metz' EWF documentation
                    (strcasecmp ((char *)Section.Type, "data"  ) == 0))
                 && (pVolume==NULL))
         {
            if (Section.Size < (sizeof (t_AewfSection)+sizeof(t_AewfSectionVolume)))
               CHK_LEAVE (AEWF_SECTION_VOLUME_WRONG_SIZE)

            CHK_LEAVE (ReadFileAlloc (pAewf, pFile, (void**) &pVolume, sizeof(t_AewfSectionVolume)))
            pAewf->Sectors    = pVolume->SectorCount;
            pAewf->SectorSize = pVolume->BytesPerSector;
            pAewf->ChunkSize  = pVolume->SectorsPerChunk * (uint64_t) pVolume->BytesPerSector;
            pAewf->ImageSize  = pAewf->Sectors * pAewf->SectorSize;
         }
         if (strcasecmp ((char *)Section.Type, "hash") == 0)
         {
            if (Section.Size < (sizeof (t_AewfSection) + sizeof(t_AewfSectionHash)))
               CHK_LEAVE (AEWF_SECTION_HASH_WRONG_SIZE)

            CHK_LEAVE (ReadFileAlloc (pAewf, pFile, (void**) &pMD5, sizeof(t_AewfSectionHash)))
         }
//         LOG ("Section %s", Section.Type)

         LastSection = (Pos == Section.OffsetNextSection) ||  // This is the official marker for the last section
                       (Pos == 0);                            // This is an error - normally - but let's be fault tolerant
         if (!LastSection)
         {
            if ( (Section.OffsetNextSection        <= Pos) ||         // Test works even if unsigned substraction 
                ((Section.OffsetNextSection - Pos) <  Section.Size))  // would result would in integer wrap around
            {
               LOG ("Error: Next section must lie beyond current section (%s %"PRIu64" / %"PRIu64")", pSegment->pName, Section.OffsetNextSection, Pos)
               CHK_LEAVE (AEWF_SECTION_STARTPOS_ERROR)
            }
            Pos = Section.OffsetNextSection;
         }
      } while (!LastSection);
      CHK_LEAVE (CloseFile (&pFile))
   }
   if (pVolume == NULL)
      CHK_LEAVE (AEWF_VOLUME_MISSING)

   if (pAewf->Chunks != pVolume->ChunkCount)
   {
      LOG ("Error: Wrong chunk count: %"PRIu64" / %"PRIu64, pAewf->Chunks, pVolume->ChunkCount);
      LOG ("Maybe some segment files are missing. Perhaps you specified E01 instead of E?? or the segments continue beyond extension .EZZ.");
      CHK_LEAVE (AEWF_WRONG_CHUNK_COUNT)
   }

   // Check for reasonable limits in order to detect corrupt images 
   // -------------------------------------------------------------
   if (pAewf->Sectors == 0) 
   {
      LOG ("Error: Image contains zero sectors.");
      CHK_LEAVE (AEWF_ZERO_SECTORS)
   }
   if ((pAewf->SectorSize == 0) || 
       (pAewf->SectorSize > AEWF_MAX_SECTOR_SIZE))
   {
      LOG ("Error: Image has invalid number of bytes per sector: %"PRIu32, pVolume->BytesPerSector);
      CHK_LEAVE (AEWF_INVALID_SECTOR_SIZE)

   }
   if ((pAewf->ChunkSize == 0) || 
       (pAewf->ChunkSize > AEWF_MAX_CHUNK_SIZE))
   {
      LOG ("Error: Image has invalid chunk size: %"PRIu64, pAewf->ChunkSize);
      CHK_LEAVE (AEWF_INVALID_CHUNK_SIZE)

   }
   if (pAewf->ImageSize == 0) 
   {
      LOG ("Error: Image size is zero.");
      CHK_LEAVE (AEWF_ZERO_IMAGE_SIZE)
   }
   if ((pAewf->Tables == 0) || (pAewf->Tables > AEWF_MAX_TABLES))
   {
      LOG ("Error: Number of AEWF tables is %"PRIu64, pAewf->Tables);
      CHK_LEAVE (AEWF_NUMBER_OF_TABLES)
   }

   pAewf->ChunkBuffSize = pAewf->ChunkSize + 4096; // reserve some extra space (for CRC and as compressed data might be slightly larger than uncompressed data with some imagers)
   pAewf->pChunkBuffCompressed   = (char *) malloc (pAewf->ChunkBuffSize);
   pAewf->pChunkBuffUncompressed = (char *) malloc (pAewf->ChunkBuffSize);
   if ((pAewf->pChunkBuffCompressed   == NULL) ||
       (pAewf->pChunkBuffUncompressed == NULL))
      CHK_LEAVE (AEWF_MEMALLOC_FAILED)

   pAewf->TableCache      = 0;
   pAewf->OpenSegments    = 0;

   CHK_LEAVE (CreateInfoData (pAewf, pVolume, pHeader, HeaderLen, pHeader2, Header2Len, pMD5))

   // Allocate thread structures
   // --------------------------
   if (pAewf->Threads > 1)
   {
      pAewf->pThreadArr = (t_pAewfThread) malloc (pAewf->Threads * sizeof (t_AewfThread));
      if (pAewf->pThreadArr == NULL)
            CHK_LEAVE (AEWF_MEMALLOC_FAILED)
      for (uint32_t i=0; i<pAewf->Threads; i++)
      {
         t_pAewfThread pThread = &pAewf->pThreadArr[i];
         memset (pThread, 0, sizeof(t_AewfThread));
         pThread->pAewf                  = pAewf;
         pThread->pChunkBuffCompressed   = (char *) malloc (pAewf->ChunkBuffSize);
         pThread->pChunkBuffUncompressed = (char *) malloc (pAewf->ChunkBuffSize);
         pThread->ChunkInBuff            = AEWF_NONE;
         pThread->State                  = AEWF_IDLE;
         if ((pThread->pChunkBuffCompressed   == NULL) ||
             (pThread->pChunkBuffUncompressed == NULL))
            CHK_LEAVE (AEWF_MEMALLOC_FAILED)
      }
   }

   rc = AEWF_OK;

Leave:
   SAFE_FREE (pVolume  , sizeof(t_AewfSectionVolume));
   SAFE_FREE (pHeader  , HeaderLen);
   SAFE_FREE (pHeader2 , Header2Len);
   SAFE_FREE (pMD5     , sizeof (t_AewfSectionHash));
   SAFE_FREE (pEwfTable, sizeof(t_AewfSectionTable));

   LOG ("Ret");
   return rc;
}

static int AewfClose (void *pHandle)
{
   t_pAewf    pAewf = (t_pAewf) pHandle;
   t_pTable   pTable;
   t_pSegment pSegment;

   LOG ("Called");
   CHK (AewfCheckHandle (pHandle))

   if (pAewf->Open == FALSE)
      return AEWF_NOT_OPEN;

   CHK (UpdateStats (pAewf,TRUE))

   // Check if all threads ended and free structures
   // ----------------------------------------------
   if (pAewf->pThreadArr)
   {
      for (uint32_t i=0; i<pAewf->Threads; i++)
      {
         t_pAewfThread pThread = &pAewf->pThreadArr[i];
         if (pThread->State != AEWF_IDLE)
            CHK (AEWF_THREADS_STILL_RUNNING)
         SAFE_FREE (pThread->pChunkBuffCompressed  , pAewf->ChunkBuffSize);
         SAFE_FREE (pThread->pChunkBuffUncompressed, pAewf->ChunkBuffSize);
      }
      SAFE_FREE (pAewf->pThreadArr, pAewf->Threads * sizeof (t_AewfThread));
   }

   for (uint64_t i=0; i<pAewf->Tables; i++)
   {
      pTable = &pAewf->pTableArr[i];
      SAFE_FREE (pTable->pEwfTable, pTable->Size)
   }
   pAewf->Tables = 0;

   for (uint64_t i=0; i<pAewf->Segments; i++)
   {
      pSegment = &pAewf->pSegmentArr[i];
      if (pSegment->pFile)
         CHK (CloseFile (&pSegment->pFile));
      SAFE_FREE (pSegment->pName);
   }
   pAewf->Segments = 0;

   SAFE_FREE (pAewf->pTableArr  , pAewf->Tables   * sizeof (t_Table));
   SAFE_FREE (pAewf->pSegmentArr, pAewf->Segments * sizeof (t_Segment));
   SAFE_FREE (pAewf->pChunkBuffCompressed  , pAewf->ChunkBuffSize);
   SAFE_FREE (pAewf->pChunkBuffUncompressed, pAewf->ChunkBuffSize);
   SAFE_FREE (pAewf->pInfo);

   pAewf->Open = FALSE;

   LOG ("Ret");
   return AEWF_OK;
}

static int AewfSize (void *pHandle, uint64_t *pSize)
{
   t_pAewf pAewf = (t_pAewf) pHandle;

   LOG ("Called");
   CHK (AewfCheckHandle (pHandle))

   *pSize = pAewf->ImageSize;

   LOG ("Ret - Size=%" PRIu64, *pSize);
   return AEWF_OK;
}

static int AewfRead (void *pHandle, char *pBuf, off_t Seek, size_t Count, size_t *pRead, int *pErrno)
{
   t_pAewf  pAewf = (t_pAewf) pHandle;
   uint64_t  Seek64;
   int       rc = AEWF_OK;

   LOG ("Called - Seek=%'" PRIu64 ",Count=%'" PRIu64, Seek, Count);
   CHK_LEAVE (AewfCheckHandle (pHandle))

   *pRead  = 0;
   *pErrno = 0;

   if (Seek < 0)
      CHK_LEAVE (AEWF_NEGATIVE_SEEK)
   Seek64 = Seek;

   pAewf->ReadOperations++;
   pAewf->DataRequestedByCaller += Count;
   if       (Count <=   32*1024) pAewf->ReadSizesArr[READSIZE_32K]++;
   else  if (Count <=   64*1024) pAewf->ReadSizesArr[READSIZE_64K]++;
   else  if (Count <=  128*1024) pAewf->ReadSizesArr[READSIZE_128K]++;
   else  if (Count <=  256*1024) pAewf->ReadSizesArr[READSIZE_256K]++;
   else  if (Count <=  512*1024) pAewf->ReadSizesArr[READSIZE_512K]++;
   else  if (Count <= 1024*1024) pAewf->ReadSizesArr[READSIZE_1M]++;
   else                          pAewf->ReadSizesArr[READSIZE_ABOVE_1M]++;

   if (Seek64 >= pAewf->ImageSize)        // If calling function asks
      CHK_LEAVE (AEWF_OK);                // for data beyond end of
   if ((Seek64+Count) > pAewf->ImageSize) // image simply return what
      Count = pAewf->ImageSize - Seek64;  // is possible.

   if (pAewf->Threads == 1)
        rc = AewfReadLegacy (pAewf, pBuf, Seek64, Count, pRead, pErrno);
   else rc = AewfReadMT     (pAewf, pBuf, Seek64, Count, pRead, pErrno);

   rc = AEWF_OK;

Leave:
   AewfCheckError (pAewf, rc, pErrno);
   CHK (UpdateStats (pAewf, (rc != AEWF_OK)))
   LOG ("Ret %d - Read=%" PRIu32, rc, *pRead);
   return rc;
}

static int AewfOptionsHelp (const char **ppHelp)
{
   char *pHelp=NULL;
   int    wr;

   wr = asprintf (&pHelp, "    %-12s : Maximum amount of RAM cache, in MiB, for image offset tables. Default: %"PRIu64" MiB\n"
                          "    %-12s : Maximum number of concurrently opened image segment files. Default: %"PRIu64"\n"
                          "    %-12s : Output statistics at regular intervals to this directory (must exist).\n"
                          "                   The files created in this directory will be named stats_<pid>.\n"
                          "    %-12s : The update interval, in seconds, for the statistics (%s must be set). Default: %"PRIu64"s.\n"
                          "    %-12s : Path for writing log file (must exist).\n"
                          "                   The files created in this directory will be named log_<pid>.\n"
                          "    %-12s : Max. number of threads for parallelized decompression. Default: System CPUs (%"PRIu32")\n"
                          "                   A value of 1 switches back to old, single-threaded legacy functions.\n",
                          AEWF_OPTION_TABLECACHE,      AEWF_DEFAULT_TABLECACHE,
                          AEWF_OPTION_MAXOPENSEGMENTS, AEWF_DEFAULT_MAXOPENSEGMENTS,
                          AEWF_OPTION_STATS,
                          AEWF_OPTION_STATSREFRESH, AEWF_OPTION_STATS, AEWF_DEFAULT_STATSREFRESH,
                          AEWF_OPTION_LOG,
                          AEWF_OPTION_THREADS, GetCPUs(NULL));
   if ((pHelp == NULL) || (wr<=0))
      return AEWF_MEMALLOC_FAILED;

   *ppHelp = pHelp;

   return AEWF_OK;
}

static int AewfOptionsParse (void *pHandle, uint32_t OptionCount, const pts_LibXmountOptions *ppOptions, const char **ppError)
{
   pts_LibXmountOptions pOption;
   t_pAewf              pAewf  = (t_pAewf) pHandle;
   const char          *pError = NULL;
   int                   rc    = AEWF_OK;
   int                   Ok;

   LOG ("Called - OptionCount=%" PRIu32, OptionCount);
   CHK (AewfCheckHandle (pHandle))

   *ppError = NULL;

   #define TEST_OPTION_UINT64(Opt,DestField)                      \
      if (strcmp (pOption->p_key, Opt) == 0)                      \
      {                                                           \
         pAewf->DestField = StrToUint64 (pOption->p_value, &Ok);  \
         if (!Ok)                                                 \
         {                                                        \
            pError = "Error in option %s: Invalid value";         \
            break;                                                \
         }                                                        \
         LOG ("Option %s set to %" PRIu64, Opt, pAewf->DestField) \
      }

   for (uint32_t i=0; i<OptionCount; i++)
   {
      pOption = ppOptions[i];
      if (strcmp (pOption->p_key, AEWF_OPTION_LOG) == 0)
      {
         pAewf->pLogPath = realpath (pOption->p_value, NULL);
         if (pAewf->pLogPath == NULL)
         {
            pError = "The given log path does not exist";
            LOG ("Log path %s not found", pOption->p_value);
            break;
         }
         rc = LOG ("Logging for libxmount_input_aewf started")
         if (rc != AEWF_OK)
         {
            pError = "Write test to log file failed";
            break;
         }
         pOption->valid = TRUE;
         LOG ("Option %s set to %s (full path %s)", AEWF_OPTION_LOG, pOption->p_value, pAewf->pLogPath);
      }
      if (strcmp (pOption->p_key, AEWF_OPTION_STATS) == 0)
      {
         pAewf->pStatsPath = realpath (pOption->p_value, NULL);
         if (pAewf->pStatsPath == NULL)
         {
            pError = "The given stats path does not exist";
            LOG ("Stats path %s not found", pOption->p_value);
            break;
         }
         pOption->valid = TRUE;
         LOG ("Option %s set to %s (full path %s)", AEWF_OPTION_STATS, pOption->p_value, pAewf->pLogPath);
      }

      else TEST_OPTION_UINT64 (AEWF_OPTION_MAXOPENSEGMENTS, MaxOpenSegments)
      else TEST_OPTION_UINT64 (AEWF_OPTION_TABLECACHE     , MaxTableCache)
      else TEST_OPTION_UINT64 (AEWF_OPTION_STATSREFRESH   , StatsRefresh)
      else TEST_OPTION_UINT64 (AEWF_OPTION_THREADS        , Threads)
   }
   #undef TEST_OPTION_UINT64

   if (pError)
   {
      *ppError = strdup (pError);
      rc = AEWF_OPTIONS_ERROR;
   }
   LOG ("Ret - rc=%d, error=%s", rc, *ppError);
   return rc;
}

static int AewfGetInfofileContent (void *pHandle, const char **ppInfoBuf)
{
   t_pAewf  pAewf  = (t_pAewf) pHandle;
   char    *pInfo;

   LOG ("Called");
   CHK (AewfCheckHandle (pHandle))

   pInfo = strdup (pAewf->pInfo);
   if (pInfo == NULL)
      return AEWF_MEMALLOC_FAILED;
   *ppInfoBuf = pInfo;

   LOG ("Ret - %d bytes of info", strlen(pInfo)+1);
   return AEWF_OK;
}

static const char* AewfGetErrorMessage (int ErrNum)
{
   const char *pMsg;
   #define ADD_ERR(AewfErrCode)              \
      case AewfErrCode: pMsg = #AewfErrCode; \
      break;

   switch (ErrNum)
   {
      ADD_ERR (AEWF_OK)
      ADD_ERR (AEWF_MEMALLOC_FAILED)
      ADD_ERR (AEWF_READ_BEYOND_END_OF_IMAGE)
      ADD_ERR (AEWF_OPTIONS_ERROR)
      ADD_ERR (AEWF_CANNOT_OPEN_LOGFILE)
      ADD_ERR (AEWF_FILE_OPEN_FAILED)
      ADD_ERR (AEWF_FILE_CLOSE_FAILED)
      ADD_ERR (AEWF_FILE_SEEK_FAILED)
      ADD_ERR (AEWF_FILE_READ_FAILED)
      ADD_ERR (AEWF_READFILE_BAD_MEM)
      case AEWF_BAD_FILE_SIGNATURE:
         pMsg = "Bad file signature. The file is not in EWF format.";
         break;
      case AEWF_MISSING_SEGMENT_NUMBER:
         pMsg = "Missing segment number. The list of EWF segment files is incomplete. One or "
                "more segment numbers are missing.";
         break;
      case AEWF_DUPLICATE_SEGMENT_NUMBER:
         pMsg = "Duplicate segment number. The list of EWF segment files contains duplicate segment "
                "numbers. Maybe you accidentally specified the segment files of more than just one EWF image.";
         break;
      ADD_ERR (AEWF_WRONG_SEGMENT_FILE_COUNT)
      ADD_ERR (AEWF_VOLUME_MUST_PRECEDE_TABLES)
      ADD_ERR (AEWF_SECTORS_MUST_PRECEDE_TABLES)
      case AEWF_WRONG_CHUNK_COUNT:
         pMsg = "Wrong chunk count. Some segment files seem to be missing. Perhaps you specified .E01 "
                "instead of .E?? or the segment files continue beyond extension .EZZ.";
         break;
      ADD_ERR (AEWF_CHUNK_NOT_FOUND)
      ADD_ERR (AEWF_VOLUME_MISSING)
      ADD_ERR (AEWF_ERROR_EWF_TABLE_NOT_READY)
      ADD_ERR (AEWF_ERROR_EWF_SEGMENT_NOT_READY)
      ADD_ERR (AEWF_CHUNK_TOO_BIG)
      ADD_ERR (AEWF_UNCOMPRESS_FAILED)
      ADD_ERR (AEWF_BAD_UNCOMPRESSED_LENGTH)
      ADD_ERR (AEWF_CHUNK_CRC_ERROR)
      ADD_ERR (AEWF_ERROR_IN_CHUNK_NUMBER)
      ADD_ERR (AEWF_UNCOMPRESS_HEADER_FAILED)
      ADD_ERR (AEWF_ASPRINTF_FAILED)
      ADD_ERR (AEWF_CHUNK_LENGTH_ZERO)
      ADD_ERR (AEWF_NEGATIVE_SEEK)
      ADD_ERR (AEWF_ERROR_EIO_END)
      ADD_ERR (AEWF_ERROR_PTHREAD)
      ADD_ERR (AEWF_WRONG_CHUNK_CALCULATION)
      ADD_ERR (AEWF_ZERO_SECTORS)
      ADD_ERR (AEWF_INVALID_SECTOR_SIZE)
      ADD_ERR (AEWF_INVALID_CHUNK_SIZE)
      ADD_ERR (AEWF_ZERO_IMAGE_SIZE)
      ADD_ERR (AEWF_NUMBER_OF_TABLES)
      ADD_ERR (AEWF_SECTION_STARTPOS_ERROR)
      ADD_ERR (AEWF_TOO_MANY_SECTIONS)
      ADD_ERR (AEWF_HANDLE_IS_NULL)
      ADD_ERR (AEWF_THREADS_STILL_RUNNING)
      ADD_ERR (AEWF_FILESIZE_CHANGED)
      ADD_ERR (AEWF_SECTION_BEYOND_EOF)
      ADD_ERR (AEWF_SECTION_SECTORS_WRONG_SIZE)
      ADD_ERR (AEWF_SECTION_TABLE_WRONG_SIZE)
      ADD_ERR (AEWF_SECTION_TABLE_BEYOND_EOF)
      ADD_ERR (AEWF_SECTION_HEADER_WRONG_SIZE)
      ADD_ERR (AEWF_SECTION_VOLUME_WRONG_SIZE)
      ADD_ERR (AEWF_SECTION_HASH_WRONG_SIZE)

      default:
         pMsg = "Unknown error";
   }
   #undef ARR_ERR
   return pMsg;
}

static int AewfFreeBuffer (void *pBuf)
{
   free (pBuf);

   return AEWF_OK;
}


// ------------------------------------
//  LibXmount_Input API implementation
// ------------------------------------

uint8_t LibXmount_Input_GetApiVersion ()
{
   return LIBXMOUNT_INPUT_API_VERSION;
}

const char* LibXmount_Input_GetSupportedFormats ()
{
  return "aewf\0\0"; //lint !e840 Use of nul character in a string literal
}

void LibXmount_Input_GetFunctions (ts_LibXmountInputFunctions *pFunctions)
{
   pFunctions->Init               = &AewfInit;
   pFunctions->DeInit             = &AewfDeInit;
   pFunctions->CreateHandle       = &AewfCreateHandle;
   pFunctions->DestroyHandle      = &AewfDestroyHandle;
   pFunctions->Open               = &AewfOpen;
   pFunctions->Close              = &AewfClose;
   pFunctions->Size               = &AewfSize;
   pFunctions->Read               = &AewfRead;
   pFunctions->OptionsHelp        = &AewfOptionsHelp;
   pFunctions->OptionsParse       = &AewfOptionsParse;
   pFunctions->GetInfofileContent = &AewfGetInfofileContent;
   pFunctions->GetErrorMessage    = &AewfGetErrorMessage;
   pFunctions->FreeBuffer         = &AewfFreeBuffer;
}

// -----------------------------------------------------
//            Small main routine for testing
//            It converts an EWF file into dd
// -----------------------------------------------------

#ifdef AEWF_STANDALONE

#define PRINT_ERROR_AND_EXIT(...) \
{                                 \
   printf (__VA_ARGS__);          \
   exit (1);                      \
}

int ParseOptions (t_pAewf pAewf, char *pOptions)
{
   pts_LibXmountOptions   pOptionArr;
   pts_LibXmountOptions *ppOptionArr;
   int                     OptionCount;
   char                  *pSep;
   char                  *pEqual;
   char                  *pTmp;
   const char            *pError;
   char                  *pOpt;
   int                     rc;

   if (pOptions == NULL)
      return AEWF_OK;

   if (*pOptions == '\0')
      return AEWF_OK;

   if (*pOptions == ',')
      return AEWF_OPTIONS_ERROR;

   if (pOptions[strlen(pOptions)-1] == ',')
      return AEWF_OPTIONS_ERROR;

   pOpt = strdup (pOptions);

   // Count number of comma separated options
   OptionCount = 1;
   pTmp        = pOpt;
   while ((pTmp = strchr (pTmp, ',')) != NULL)
   {
      OptionCount++;
      pTmp++;
   }

   // Create and fill option array
   pOptionArr  = (pts_LibXmountOptions) malloc (OptionCount * sizeof(ts_LibXmountOptions));
   if (pOptionArr == NULL)
      PRINT_ERROR_AND_EXIT ("Cannot allocate pOptionArr");
   memset (pOptionArr, 0, OptionCount * sizeof(ts_LibXmountOptions));
   pTmp = pOpt;
   for (int i=0; i<OptionCount; i++)
   {
      pOptionArr[i].p_key = pTmp;
      pSep = strchr (pTmp, ',');
      if (pSep)
         *pSep ='\0';
      pEqual = strchr (pTmp, '=');
      if (pEqual)
      {
         *pEqual = '\0';
         pOptionArr[i].p_value = pEqual+1;
      }
      if (pSep != NULL)
         pTmp = pSep+1;
   }

   // Create pointer array and call AEWF parse function
   ppOptionArr = (pts_LibXmountOptions *)malloc (OptionCount*sizeof (pts_LibXmountOptions));
   if (ppOptionArr == NULL)
      PRINT_ERROR_AND_EXIT ("Cannot allocate ppOptionArr");
   for (int i=0; i<OptionCount; i++)
      ppOptionArr[i] = &pOptionArr[i];

   rc = AewfOptionsParse ((void*) pAewf, OptionCount, ppOptionArr, &pError);

   SAFE_FREE (ppOptionArr);
   SAFE_FREE ( pOptionArr);
   SAFE_FREE ( pOpt);
   if (pError)
      PRINT_ERROR_AND_EXIT ("Error while setting options: %s", pError);
   CHK (rc)

   return AEWF_OK;
}

int main(int argc, const char *argv[])
{
   t_pAewf       pAewf=NULL;
   uint64_t       TotalSize;
   uint64_t       Remaining;
   uint64_t       ToRead;
   uint64_t       Read;
   uint64_t       Pos;
   unsigned int   BuffSize = 13*65536; // A multiple of chunk size for good performance
   char           Buff[BuffSize];
   FILE         *pFile;
   int            Percent;
   int            PercentOld;
   int            rc;
   int            Errno;
   char         *pOptions = NULL;
   const char   *pHelp;
   const char   *pInfoBuff;

   #ifdef CREATE_REVERSE_FILE
      FILE      *pFileRev;
      uint64_t    PosRev;
      #ifdef REVERSE_FILE_USES_SEPARATE_HANDLE
         t_pAewf pAewfRev;
      #else
         #define pAewfRev pAewf
      #endif
   #endif

   setbuf(stdout, NULL);
   setbuf(stderr, NULL);
   (void) setlocale (LC_ALL, "");

   printf ("EWF to DD converter - result file is named dd\n");
   printf ("   Result file is named dd");
   #ifdef CREATE_REVERSE_FILE
      printf ("; Also creates a backwards read file named rev");
      #ifdef REVERSE_FILE_USES_SEPARATE_HANDLE
         printf ("; Uses separate AEWF handle for reverse file");
      #else
         printf ("; Uses the same AEWF handle for reverse file");
      #endif
   #endif
   printf ("\n");


   if (argc < 2)
   {
      (void) AewfOptionsHelp (&pHelp);
      printf ("Usage: %s <EWF segment file 1> <EWF segment file 2> <...> [-comma_separated_options]\n", argv[0]);
      printf ("Possible options:\n%s\n", pHelp);
      printf ("The output file will be named dd.\n");
      CHK (AewfFreeBuffer ((void*) pHelp))
      exit (1);
   }

   if (argv[argc-1][0] == '-')
   {
      pOptions = strdup (&(argv[argc-1][1]));
      argc--;
   }
   rc = AewfCreateHandle ((void**) &pAewf, "aewf", LOG_STDOUT);
   if (rc != AEWF_OK)
      PRINT_ERROR_AND_EXIT ("Cannot create handle, rc=%d\n", rc)

   if (pOptions)
      CHK (ParseOptions(pAewf, pOptions))
   rc = AewfOpen (pAewf, &argv[1], argc-1);
   if (rc != AEWF_OK)
      PRINT_ERROR_AND_EXIT ("Cannot open EWF files, rc=%d\n", rc)

   #if defined(CREATE_REVERSE_FILE) && defined(REVERSE_FILE_USES_SEPARATE_HANDLE)
      rc = AewfCreateHandle ((void**) &pAewfRev, "aewf", LOG_STDOUT);
      if (rc != AEWF_OK)
         PRINT_ERROR_AND_EXIT ("Cannot create reverse handle, rc=%d\n", rc)
      if (pOptions)
         CHK (ParseOptions (pAewfRev, pOptions))
      rc = AewfOpen (pAewfRev, &argv[1], argc-1);
      if (rc != AEWF_OK)
         PRINT_ERROR_AND_EXIT ("Cannot open EWF files, rc=%d\n", rc)
   #endif

   CHK (AewfGetInfofileContent ((void*) pAewf, &pInfoBuff))
   if (pInfoBuff)
      printf ("Contents of info buffer:\n%s\n", pInfoBuff);
   CHK (AewfFreeBuffer ((void*) pInfoBuff))

   CHK (AewfSize (pAewf, &TotalSize))
   printf ("Total size: %" PRIu64 " bytes\n", TotalSize);

   pFile = fopen ("dd", "w");
   if (pFile == NULL)
      PRINT_ERROR_AND_EXIT("Cannot open destination file\n");

   #ifdef CREATE_REVERSE_FILE
      pFileRev = fopen ("rev", "w");
      if (pFileRev == NULL)
         PRINT_ERROR_AND_EXIT("Cannot open reverse destination file\n");
      PosRev = TotalSize;
   #endif

   Remaining  = TotalSize;
   Pos        = 0;
   PercentOld = -1;
   Errno      = 0;
   while (Remaining)
   {
//      LOG ("Pos %" PRIu64 " -- Remaining %" PRIu64 " ", Pos, Remaining);
      ToRead = GETMIN (Remaining, BuffSize);

     rc = AewfRead ((void*) pAewf, &Buff[0], Pos, ToRead, &Read, &Errno);
      if ((rc != AEWF_OK) || (Errno != 0))
         PRINT_ERROR_AND_EXIT("Error %d while calling AewfRead (Errno %d)\n", rc, Errno);
      if (Read != ToRead)
         PRINT_ERROR_AND_EXIT("Only %" PRIu64 " out of %" PRIu64 " bytes read\n", Read, ToRead);

      if (fwrite (Buff, Read, 1, pFile) != 1)
         PRINT_ERROR_AND_EXIT("Could not write to destination file\n");

      Remaining -= ToRead;
      Pos       += ToRead;

      #ifdef CREATE_REVERSE_FILE
         PosRev -= ToRead;
         rc = AewfRead ((void*) pAewfRev, &Buff[0], PosRev, ToRead, &Read, &Errno);
         if ((rc != AEWF_OK) || (Errno != 0))
            PRINT_ERROR_AND_EXIT("Error %d while reverse calling AewfRead (Errno %d)\n", rc, Errno);
         if (Read != ToRead)
            PRINT_ERROR_AND_EXIT("Only %" PRIu64 " out of %" PRIu64 " bytes read from rev file\n", Read, ToRead);

         if (fseeko (pFileRev, PosRev, SEEK_SET))
            return AEWF_FILE_SEEK_FAILED;

         if (fwrite (Buff, Read, 1, pFileRev) != 1)
            PRINT_ERROR_AND_EXIT("Could not write to reverse destination file\n");
      #endif


      Percent = (100*Pos) / TotalSize;
      if (Percent != PercentOld)
      {
         printf ("\r%d%% done...", Percent);
         PercentOld = Percent;
      }
   }
   if (fclose (pFile))
      PRINT_ERROR_AND_EXIT ("Error while closing destination file\n");

   if (AewfClose (pAewf))
      PRINT_ERROR_AND_EXIT("Error while closing AEWF files\n");
   if (AewfDestroyHandle ((void**)&pAewf))
      PRINT_ERROR_AND_EXIT("Error while destroying AEWF handle\n");

   #ifdef CREATE_REVERSE_FILE
      #ifdef REVERSE_FILE_USES_SEPARATE_HANDLE
         if (AewfClose (pAewfRev))
            PRINT_ERROR_AND_EXIT("Error while closing reverse AEWF files\n");
         if (AewfDestroyHandle ((void**)&pAewfRev))
            PRINT_ERROR_AND_EXIT("Error while destroying reverse AEWF handle\n");
      #endif
      if (fclose (pFileRev))
         PRINT_ERROR_AND_EXIT ("Error while closing reverse destination file\n");
   #endif

   printf ("\n");
   return 0;
}

#endif

