#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* ifdef HAVE_CONFIG_H */

#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

#include <Eina.h>
#include <Emile.h>

#include "Eet.h"
#include "Eet_private.h"

#ifndef O_BINARY
# define O_BINARY 0
#endif

static Eet_Version _version = { VMAJ, VMIN, VMIC, VREV };
EAPI Eet_Version *eet_version = &_version;

#ifdef HAVE_REALPATH
# undef HAVE_REALPATH
#endif /* ifdef HAVE_REALPATH */

#define EET_MAGIC_FILE        0x1ee7ff00
#define EET_MAGIC_FILE_HEADER 0x1ee7ff01

#define EET_MAGIC_FILE2       0x1ee70f42

#define EET_FILE2_HEADER_COUNT           3
#define EET_FILE2_DIRECTORY_ENTRY_COUNT  6
#define EET_FILE2_DICTIONARY_ENTRY_COUNT 5

#define EET_FILE2_HEADER_SIZE            (sizeof(int) * \
                                          EET_FILE2_HEADER_COUNT)
#define EET_FILE2_DIRECTORY_ENTRY_SIZE   (sizeof(int) * \
                                          EET_FILE2_DIRECTORY_ENTRY_COUNT)
#define EET_FILE2_DICTIONARY_ENTRY_SIZE  (sizeof(int) * \
                                          EET_FILE2_DICTIONARY_ENTRY_COUNT)

// force data alignmenmt in the eet file so direct mmap can work without
// copies and we can work with alignment
#define ALIGN 8

/* prototypes of internal calls */
static Eet_File *
eet_cache_find(const char *path,
               Eet_File  **cache,
               int         cache_num);
static void
eet_cache_add(Eet_File   *ef,
              Eet_File ***cache,
              int        *cache_num,
              int        *cache_alloc);
static void
eet_cache_del(Eet_File   *ef,
              Eet_File ***cache,
              int        *cache_num,
              int        *cache_alloc);
static int
eet_string_match(const char *s1,
                 const char *s2);
#if 0 /* Unused */
static Eet_Error
eet_flush(Eet_File *ef);
#endif /* if 0 */
static Eet_Error
 eet_flush2(Eet_File *ef, Eina_Bool sync);
static Eet_File_Node *
 find_node_by_name(Eet_File  *ef,
                  const char *name);
static Eina_Binbuf *
read_binbuf_from_disk(Eet_File      *ef,
                      Eet_File_Node *efn);

static Eet_Error
eet_internal_close(Eet_File *ef, Eina_Bool locked, Eina_Bool shutdown);

static Eina_Lock eet_cache_lock;

#define LOCK_CACHE   eina_lock_take(&eet_cache_lock)
#define UNLOCK_CACHE eina_lock_release(&eet_cache_lock)

#define INIT_FILE(File)    eina_lock_new(&File->file_lock)
#define LOCK_FILE(File)    eina_lock_take(&File->file_lock)
#define UNLOCK_FILE(File)  eina_lock_release(&File->file_lock)
#define DESTROY_FILE(File) eina_lock_free(&File->file_lock)

/* cache. i don't expect this to ever be large, so arrays will do */
static int eet_writers_num = 0;
static int eet_writers_alloc = 0;
static Eet_File **eet_writers = NULL;
static int eet_readers_num = 0;
static int eet_readers_alloc = 0;
static Eet_File **eet_readers = NULL;
static int eet_init_count = 0;

/* log domain variable */
int _eet_log_dom_global = -1;

/* Check to see its' an eet file pointer */
static inline int
eet_check_pointer(const Eet_File *ef)
{
   if ((!ef) || (ef->magic != EET_MAGIC_FILE))
     return 1;

   return 0;
}

static inline int
eet_check_header(const Eet_File *ef)
{
   if (!ef->header)
     return 1;

   if (!ef->header->directory)
     return 1;

   return 0;
}

static inline int
eet_test_close(int       test,
               Eet_File *ef)
{
   if (test)
     {
        ef->delete_me_now = 1;
        eet_internal_close(ef, EINA_TRUE, EINA_FALSE);
     }

   return test;
}

/* find an eet file in the currently in use cache */
static Eet_File *
eet_cache_find(const char *path,
               Eet_File  **cache,
               int         cache_num)
{
   int i;

   /* walk list */
   for (i = 0; i < cache_num; i++)
     {
        /* if matches real path - return it */
         if (eet_string_match(cache[i]->path, path))
           if (!cache[i]->delete_me_now)
             return cache[i];
     }

   /* not found */
   return NULL;
}

/* add to end of cache */
/* this should only be called when the cache lock is already held */
static void
eet_cache_add(Eet_File   *ef,
              Eet_File ***cache,
              int        *cache_num,
              int        *cache_alloc)
{
   Eet_File **new_cache;
   int new_cache_num;
   int new_cache_alloc;

   new_cache_num = *cache_num;
   if (new_cache_num >= 64) /* avoid fd overruns - limit to 128 (most recent) in the cache */
     {
        Eet_File *del_ef = NULL;
        int i;

        new_cache = *cache;
        for (i = 0; i < new_cache_num; i++)
          {
             if (new_cache[i]->references == 0)
               {
                  del_ef = new_cache[i];
                  break;
               }
          }

        if (del_ef)
          {
             del_ef->delete_me_now = 1;
             eet_internal_close(del_ef, EINA_TRUE, EINA_FALSE);
          }
     }

   new_cache = *cache;
   new_cache_num = *cache_num;
   new_cache_alloc = *cache_alloc;
   new_cache_num++;
   if (new_cache_num > new_cache_alloc)
     {
        new_cache_alloc += 16;
        new_cache = realloc(new_cache, new_cache_alloc * sizeof(Eet_File *));
        if (!new_cache)
          {
             CRI("BAD ERROR! Eet realloc of cache list failed. Abort");
             abort();
          }
     }

   new_cache[new_cache_num - 1] = ef;
   *cache = new_cache;
   *cache_num = new_cache_num;
   *cache_alloc = new_cache_alloc;
}

/* delete from cache */
/* this should only be called when the cache lock is already held */
static void
eet_cache_del(Eet_File   *ef,
              Eet_File ***cache,
              int        *cache_num,
              int        *cache_alloc)
{
   Eet_File **new_cache;
   int new_cache_num, new_cache_alloc;
   int i, j;

   new_cache = *cache;
   new_cache_num = *cache_num;
   new_cache_alloc = *cache_alloc;
   if (new_cache_num <= 0)
     return;

   for (i = 0; i < new_cache_num; i++)
     {
        if (new_cache[i] == ef)
          break;
     }

   if (i >= new_cache_num)
     return;

   new_cache_num--;
   for (j = i; j < new_cache_num; j++)
     new_cache[j] = new_cache[j + 1];

   if (new_cache_num <= (new_cache_alloc - 16))
     {
        new_cache_alloc -= 16;
        if (new_cache_num > 0)
          {
             new_cache = realloc(new_cache, new_cache_alloc * sizeof(Eet_File *));
             if (!new_cache)
               {
                  CRI("BAD ERROR! Eet realloc of cache list failed. Abort");
                  abort();
               }
          }
        else
          {
             free(new_cache);
             new_cache = NULL;
          }
     }

   *cache = new_cache;
   *cache_num = new_cache_num;
   *cache_alloc = new_cache_alloc;
}

/* internal string match. null friendly, catches same ptr */
static int
eet_string_match(const char *s1,
                 const char *s2)
{
   /* both null- no match */
    if ((!s1) || (!s2))
      return 0;

    if (s1 == s2)
      return 1;

    return !strcmp(s1, s2);
}

/* flush out writes to a v2 eet file */
static Eet_Error
eet_flush2(Eet_File *ef, Eina_Bool sync)
{
   Eet_File_Node *efn;
   FILE *fp;
   Eet_Error error = EET_ERROR_NONE;
   int head[EET_FILE2_HEADER_COUNT];
   int num_directory_entries = 0;
   int num_dictionary_entries = 0;
   int bytes_directory_entries = 0;
   int bytes_dictionary_entries = 0;
   int bytes_strings = 0;
   int data_offset = 0;
   int strings_offset = 0;
   int data_pad = 0;
   int pad = 0;
   int orig_data_offset = 0;
   int num;
   int i;
   int j;
   unsigned char zeros[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };

   if (eet_check_pointer(ef))
     return EET_ERROR_BAD_OBJECT;

   if (eet_check_header(ef))
     return EET_ERROR_EMPTY;

   if (!ef->writes_pending)
     return EET_ERROR_NONE;

   if ((ef->mode == EET_FILE_MODE_READ_WRITE)
       || (ef->mode == EET_FILE_MODE_WRITE))
     {
        int fd;

        /* opening for write - delete old copy of file right away */
        eina_file_unlink(ef->path);
        fd = open(ef->path, O_CREAT | O_TRUNC | O_RDWR | O_BINARY, S_IRUSR | S_IWUSR);
        if (fd < 0)
          {
             ERR("Can't write file '%s'.", ef->path);
             return EET_ERROR_NOT_WRITABLE;
          }

        fp = fdopen(fd, "wb");
        if (!fp)
          {
             ERR("Can't write file '%s'.", ef->path);
             return EET_ERROR_NOT_WRITABLE;
          }

        if (!eina_file_close_on_exec(fd, EINA_TRUE)) ERR("can't set CLOEXEC on write fd");
     }
   else
     {
        return EET_ERROR_NOT_WRITABLE;
     }

   /* calculate string base offset and data base offset */
   num = (1 << ef->header->directory->size);
   for (i = 0; i < num; ++i)
     {
        for (efn = ef->header->directory->nodes[i]; efn; efn = efn->next)
          {
             num_directory_entries++;
             bytes_strings += strlen(efn->name) + 1;
          }
     }
   if (ef->ed)
     {
        eet_dictionary_write_prepare(ef->ed);
        num_dictionary_entries = ef->ed->count;

        for (i = 0; i < num_dictionary_entries; ++i)
          bytes_strings += ef->ed->all[i].len;
     }

   /* calculate section bytes size */
   bytes_directory_entries = EET_FILE2_DIRECTORY_ENTRY_SIZE *
     num_directory_entries + EET_FILE2_HEADER_SIZE;
   bytes_dictionary_entries = EET_FILE2_DICTIONARY_ENTRY_SIZE *
     num_dictionary_entries;

   /* go thru and write the header */
   head[0] = (int)eina_htonl((unsigned int)EET_MAGIC_FILE2);
   head[1] = (int)eina_htonl((unsigned int)num_directory_entries);
   head[2] = (int)eina_htonl((unsigned int)num_dictionary_entries);

   fseek(fp, 0, SEEK_SET);
   if (fwrite(head, sizeof (head), 1, fp) != 1)
     goto write_error;

   /* calculate per entry base offset */
   strings_offset = bytes_directory_entries + bytes_dictionary_entries;
   data_offset = bytes_directory_entries + bytes_dictionary_entries +
     bytes_strings;

   data_pad = (((data_offset + (ALIGN - 1)) / ALIGN) * ALIGN) - data_offset;
   data_offset += data_pad;
   orig_data_offset = data_offset;

   /* write directories entry */
   for (i = 0; i < num; i++)
     {
        for (efn = ef->header->directory->nodes[i]; efn; efn = efn->next)
          {
             unsigned int flag;
             int ibuf[EET_FILE2_DIRECTORY_ENTRY_COUNT];

             flag = (efn->alias << 2) | (efn->ciphered << 1) | efn->compression;
             flag |= efn->compression_type << 3;

             efn->offset = data_offset;

             ibuf[0] = (int)eina_htonl((unsigned int)data_offset);
             ibuf[1] = (int)eina_htonl((unsigned int)efn->size);
             ibuf[2] = (int)eina_htonl((unsigned int)efn->data_size);
             ibuf[3] = (int)eina_htonl((unsigned int)strings_offset);
             ibuf[4] = (int)eina_htonl((unsigned int)efn->name_size);
             ibuf[5] = (int)eina_htonl((unsigned int)flag);

             strings_offset += efn->name_size;
             data_offset += efn->size;

             pad = (((data_offset + (ALIGN - 1)) / ALIGN) * ALIGN) - data_offset;
             data_offset += pad;

             if (fwrite(ibuf, sizeof(ibuf), 1, fp) != 1)
               goto write_error;
          }
     }

   /* write dictionary */
   if (ef->ed)
     {
        int offset = strings_offset;

        /* calculate dictionary strings offset */
        ef->ed->offset = strings_offset;

        for (j = 0; j < ef->ed->count; ++j)
          {
             int sbuf[EET_FILE2_DICTIONARY_ENTRY_COUNT];
             int prev = 0;

             // We still use the prev as an hint for knowing if it is the head of the hash
             if (ef->ed->hash[ef->ed->all_hash[j]] == j)
               prev = -1;

             sbuf[0] = (int)eina_htonl((unsigned int)ef->ed->all_hash[j]);
             sbuf[1] = (int)eina_htonl((unsigned int)offset);
             sbuf[2] = (int)eina_htonl((unsigned int)ef->ed->all[j].len);
             sbuf[3] = (int)eina_htonl((unsigned int)prev);
             sbuf[4] = (int)eina_htonl((unsigned int)ef->ed->all[j].next);

             offset += ef->ed->all[j].len;

             if (fwrite(sbuf, sizeof (sbuf), 1, fp) != 1)
               goto write_error;
          }
     }

   /* write directories name */
   for (i = 0; i < num; i++)
     {
        for (efn = ef->header->directory->nodes[i]; efn; efn = efn->next)
          {
             if (fwrite(efn->name, efn->name_size, 1, fp) != 1)
               goto write_error;
          }
     }

   /* write strings */
   if (ef->ed)
     for (j = 0; j < ef->ed->count; ++j)
       {
          if (fwrite(ef->ed->all[j].str, ef->ed->all[j].len, 1, fp) != 1)
            goto write_error;
       }

   if (data_pad > 0)
     {
        if (fwrite(zeros, data_pad, 1, fp) != 1)
          goto write_error;
     }

   /* write data */
   data_offset = orig_data_offset;
   pad = 0;
   for (i = 0; i < num; i++)
     {
        for (efn = ef->header->directory->nodes[i]; efn; efn = efn->next)
          {
             if (pad > 0)
               {
                  data_offset += pad;
                  if (fwrite(zeros, pad, 1, fp) != 1)
                    goto write_error;
               }
             if (fwrite(efn->data, efn->size, 1, fp) != 1)
               goto write_error;

             data_offset += efn->size;
             pad = (((data_offset + (ALIGN - 1)) / ALIGN) * ALIGN) - data_offset;
          }
     }

   /* flush all write to the file. */
   fflush(fp);

   /* append signature if required */
   if (ef->key)
     {
        error = eet_identity_sign(fp, ef->key);
        if (error != EET_ERROR_NONE)
          goto sign_error;
     }

   /* no more writes pending */
   ef->writes_pending = 0;

#ifndef _WIN32
   if (sync) fdatasync(fileno(fp));
#endif
   fclose(fp);

   return EET_ERROR_NONE;

write_error:
   if (ferror(fp))
     {
        ERR("Error during write on '%s'.", ef->path);
        switch (errno)
          {
           case EFBIG: error = EET_ERROR_WRITE_ERROR_FILE_TOO_BIG; break;

           case EIO: error = EET_ERROR_WRITE_ERROR_IO_ERROR; break;

           case ENOSPC: error = EET_ERROR_WRITE_ERROR_OUT_OF_SPACE; break;

           case EPIPE: error = EET_ERROR_WRITE_ERROR_FILE_CLOSED; break;

           default: error = EET_ERROR_WRITE_ERROR; break;
          }
     }

sign_error:
   fclose(fp);
   return error;
}

EAPI int
eet_init(void)
{
   if (++eet_init_count != 1)
     return eet_init_count;

   if (!eina_init())
     return --eet_init_count;

   _eet_log_dom_global = eina_log_domain_register("eet", EET_DEFAULT_LOG_COLOR);
   if (_eet_log_dom_global < 0)
     {
        EINA_LOG_ERR("Eet Can not create a general log domain.");
        goto shutdown_eina;
     }

   eina_lock_new(&eet_cache_lock);

   if (!eet_mempool_init())
     {
        EINA_LOG_ERR("Eet: Eet_Node mempool creation failed");
        goto unregister_log_domain;
     }

   if (!eet_node_init())
     {
        EINA_LOG_ERR("Eet: Eet_Node mempool creation failed");
        goto shutdown_mempool;
     }

   if (!emile_init())
     {
        EINA_LOG_ERR("Emile: failed to initialize");
        goto shutdown_emile;
     }

   eina_log_timing(_eet_log_dom_global,
                   EINA_LOG_STATE_STOP,
                   EINA_LOG_STATE_INIT);

   return eet_init_count;

shutdown_emile:
   eet_node_shutdown();
shutdown_mempool:
   eet_mempool_shutdown();
unregister_log_domain:
   eina_log_domain_unregister(_eet_log_dom_global);
   _eet_log_dom_global = -1;
shutdown_eina:
   eina_shutdown();
   return --eet_init_count;
}

EAPI int
eet_shutdown(void)
{
   if (eet_init_count <= 0)
     {
        ERR("Init count not greater than 0 in shutdown.");
        return 0;
     }
   if (--eet_init_count != 0)
     return eet_init_count;

   eina_log_timing(_eet_log_dom_global,
                   EINA_LOG_STATE_START,
                   EINA_LOG_STATE_SHUTDOWN);

   eet_clearcache();

   if (eet_writers_num || eet_readers_num)
     {
        Eet_File **closelist = NULL;
        int num = 0;
        int i;

        closelist = alloca((eet_writers_num + eet_readers_num)
                           * sizeof(Eet_File *));
        for (i = 0; i < eet_writers_num; i++)
          {
             closelist[num++] = eet_writers[i];
             eet_writers[i]->delete_me_now = 1;
          }

        for (i = 0; i < eet_readers_num; i++)
          {
             closelist[num++] = eet_readers[i];
             eet_readers[i]->delete_me_now = 1;
          }

        for (i = 0; i < num; i++)
          {
             ERR("File '%s' is still open %i times !", closelist[i]->path, closelist[i]->references);
             eet_internal_close(closelist[i], EINA_TRUE, EINA_TRUE);
          }
     }
   eet_node_shutdown();
   eet_mempool_shutdown();

   eina_lock_free(&eet_cache_lock);

   emile_shutdown();

   eina_log_domain_unregister(_eet_log_dom_global);
   _eet_log_dom_global = -1;
   eina_shutdown();

   return eet_init_count;
}

EAPI Eet_Error
eet_sync(Eet_File *ef)
{
   Eet_Error ret;

   if (eet_check_pointer(ef))
     return EET_ERROR_BAD_OBJECT;

   if ((ef->mode != EET_FILE_MODE_WRITE) &&
       (ef->mode != EET_FILE_MODE_READ_WRITE))
     return EET_ERROR_NOT_WRITABLE;

   if (!ef->writes_pending)
     return EET_ERROR_NONE;

   LOCK_FILE(ef);

   ret = eet_flush2(ef, EINA_FALSE);

   UNLOCK_FILE(ef);
   return ret;
}

EAPI Eet_Error
eet_sync_sync(Eet_File *ef)
{
   Eet_Error ret;

   if (eet_check_pointer(ef))
     return EET_ERROR_BAD_OBJECT;

   if ((ef->mode != EET_FILE_MODE_WRITE) &&
       (ef->mode != EET_FILE_MODE_READ_WRITE))
     return EET_ERROR_NOT_WRITABLE;

   if (!ef->writes_pending)
     return EET_ERROR_NONE;

   LOCK_FILE(ef);

   ret = eet_flush2(ef, EINA_TRUE);

   UNLOCK_FILE(ef);
   return ret;
}

EAPI void
eet_clearcache(void)
{
   int num = 0;
   int i;

   /*
    * We need to compute the list of eet file to close separately from the cache,
    * due to eet_close removing them from the cache after each call.
    */
   LOCK_CACHE;
   for (i = 0; i < eet_writers_num; i++)
     {
        if (eet_writers[i]->references <= 0)
          num++;
     }

   for (i = 0; i < eet_readers_num; i++)
     {
        if (eet_readers[i]->references <= 0)
          num++;
     }

   if (num > 0)
     {
        Eet_File **closelist = NULL;

        closelist = alloca(num * sizeof(Eet_File *));
        num = 0;
        for (i = 0; i < eet_writers_num; i++)
          {
             if (eet_writers[i]->references <= 0)
               {
                  closelist[num] = eet_writers[i];
                  eet_writers[i]->delete_me_now = 1;
                  num++;
               }
          }

        for (i = 0; i < eet_readers_num; i++)
          {
             if (eet_readers[i]->references <= 0)
               {
                  closelist[num] = eet_readers[i];
                  eet_readers[i]->delete_me_now = 1;
                  num++;
               }
          }

        for (i = 0; i < num; i++)
          {
             eet_internal_close(closelist[i], EINA_TRUE, EINA_FALSE);
          }
     }

   UNLOCK_CACHE;
}

/* FIXME: MMAP race condition in READ_WRITE_MODE */
static Eet_File *
eet_internal_read2(Eet_File *ef)
{
   const int *data = (const int *)ef->data;
   const char *start = (const char *)ef->data;
   int idx = 0;
   unsigned long int bytes_directory_entries;
   unsigned long int bytes_dictionary_entries;
   unsigned long int signature_base_offset;
   unsigned long int num_directory_entries;
   unsigned long int num_dictionary_entries;
   unsigned int i;

   idx += sizeof(int);
   if (eet_test_close((int)eina_ntohl(*data) != EET_MAGIC_FILE2, ef))
     return NULL;

   data++;

#define GET_INT(Value, Pointer, Index) \
  {                                    \
     Value = eina_ntohl(*Pointer);          \
     Pointer++;                        \
     Index += sizeof(int);             \
  }

   /* get entries count and byte count */
   GET_INT(num_directory_entries, data, idx);
   /* get dictionary count and byte count */
   GET_INT(num_dictionary_entries, data, idx);

   bytes_directory_entries = EET_FILE2_DIRECTORY_ENTRY_SIZE *
     num_directory_entries + EET_FILE2_HEADER_SIZE;
   bytes_dictionary_entries = EET_FILE2_DICTIONARY_ENTRY_SIZE *
     num_dictionary_entries;

   /* we can't have > 0x7fffffff values here - invalid */
   if (eet_test_close((num_directory_entries > 0x7fffffff), ef))
     return NULL;

   /* we can't have more bytes directory and bytes in dictionaries than the size of the file */
   if (eet_test_close((bytes_directory_entries + bytes_dictionary_entries) >
                      ef->data_size, ef))
     return NULL;

   /* allocate header */
   ef->header = eet_file_header_calloc(1);
   if (eet_test_close(!ef->header, ef))
     return NULL;

   ef->header->magic = EET_MAGIC_FILE_HEADER;

   /* allocate directory block in ram */
   ef->header->directory = eet_file_directory_calloc(1);
   if (eet_test_close(!ef->header->directory, ef))
     return NULL;

   /* 8 bit hash table (256 buckets) */
   ef->header->directory->size = 8;
   /* allocate base hash table */
   ef->header->directory->nodes =
     calloc(1, sizeof(Eet_File_Node *) * (1 << ef->header->directory->size));
   if (eet_test_close(!ef->header->directory->nodes, ef))
     return NULL;

   signature_base_offset = 0;
   if (num_directory_entries == 0)
     {
        signature_base_offset = ef->data_size;
     }

   /* actually read the directory block - all of it, into ram */
   for (i = 0; i < num_directory_entries; ++i)
     {
        const char *name;
        Eet_File_Node *efn;
        unsigned long int name_offset;
        unsigned long int name_size;
        int hash;
        int flag;

        /* out directory block is inconsistent - we have overrun our */
        /* dynamic block buffer before we finished scanning dir entries */
        efn = eet_file_node_malloc(1);
        if (eet_test_close(!efn, ef))
          {
             if (efn) eet_file_node_mp_free(efn);  /* yes i know - we only get here if
                                   * efn is null/0 -> trying to shut up
                                   * warning tools like cppcheck */
             return NULL;
          }

        /* get entrie header */
        GET_INT(efn->offset, data, idx);
        GET_INT(efn->size, data, idx);
        GET_INT(efn->data_size, data, idx);
        GET_INT(name_offset, data, idx);
        GET_INT(name_size, data, idx);
        GET_INT(flag, data, idx);

        efn->compression = flag & 0x1 ? 1 : 0;
        efn->ciphered = flag & 0x2 ? 1 : 0;
        efn->alias = flag & 0x4 ? 1 : 0;
        efn->compression_type = (flag >> 3) & 0xff;

#define EFN_TEST(Test, Ef, Efn) \
  if (eet_test_close(Test, Ef)) \
    {                           \
       eet_file_node_mp_free(Efn);               \
       return NULL;             \
    }

        /* check data pointer position */
        EFN_TEST(!((efn->size > 0)
                   && (efn->offset + efn->size <= ef->data_size)
                   && (efn->offset > bytes_dictionary_entries +
                       bytes_directory_entries)), ef, efn);

        /* check name position */
        EFN_TEST(!((name_size > 0)
                   && (name_offset + name_size < ef->data_size)
                   && (name_offset >= bytes_dictionary_entries +
                       bytes_directory_entries)), ef, efn);

        name = start + name_offset;

        /* check '\0' at the end of name string */
        EFN_TEST(name[name_size - 1] != '\0', ef, efn);

        efn->free_name = 0;
        efn->name = (char *)name;
        efn->name_size = name_size;

        hash = _eet_hash_gen(efn->name, ef->header->directory->size);
        efn->next = ef->header->directory->nodes[hash];
        ef->header->directory->nodes[hash] = efn;

        /* read-only mode, so currently we have no data loaded */
        if (ef->mode == EET_FILE_MODE_READ)
          efn->data = NULL;  /* read-write mode - read everything into ram */
        else
          {
             efn->data = malloc(efn->size);
             if (efn->data)
               {
                  memcpy(efn->data, ef->data + efn->offset, efn->size);
                  ef->header->directory->free_count++;
               }
          }

        /* compute the possible position of a signature */
        if (signature_base_offset < (efn->offset + efn->size))
          signature_base_offset = efn->offset + efn->size;
     }

   ef->ed = NULL;

   if (num_dictionary_entries)
     {
        const int *dico = (const int *)ef->data +
          EET_FILE2_DIRECTORY_ENTRY_COUNT * num_directory_entries +
          EET_FILE2_HEADER_COUNT;
        int j;

        if (eet_test_close((num_dictionary_entries *
                            (int)EET_FILE2_DICTIONARY_ENTRY_SIZE + idx) >
                           (bytes_dictionary_entries + bytes_directory_entries),
                           ef))
          return NULL;

        ef->ed = eet_dictionary_add();
        if (eet_test_close(!ef->ed, ef))
          return NULL;

        INF("loading dictionary for '%s' with %lu entries of size %zu",
            ef->path, num_dictionary_entries, sizeof(Eet_String));

        ef->ed->all = calloc(1, num_dictionary_entries * sizeof(Eet_String));
        if (eet_test_close(!ef->ed->all, ef))
          return NULL;

	ef->ed->all_hash = calloc(1, num_dictionary_entries * sizeof (unsigned char));
	if (eet_test_close(!ef->ed->all_hash, ef))
	  return NULL;

	ef->ed->all_allocated = calloc(1, ((num_dictionary_entries >> 3) + 1) * sizeof (unsigned char));
	if (eet_test_close(!ef->ed->all_allocated, ef))
	  return NULL;

        ef->ed->count = num_dictionary_entries;
        ef->ed->total = num_dictionary_entries;
        ef->ed->start = start + bytes_dictionary_entries +
          bytes_directory_entries;
        ef->ed->end = ef->ed->start;

        for (j = 0; j < ef->ed->count; ++j)
          {
             unsigned int offset;
	     int prev;
             int hash;

             GET_INT(hash, dico, idx);
             GET_INT(offset, dico, idx);
             GET_INT(ef->ed->all[j].len, dico, idx);
             GET_INT(prev, dico, idx); // Let's ignore prev link for dictionary, use it only as an hint to head
             GET_INT(ef->ed->all[j].next, dico, idx);

             /* Hash value could be stored on 8bits data, but this will break alignment of all the others data.
                So stick to int and check the value. */
             if (eet_test_close(hash & 0xFFFFFF00, ef))
               return NULL;

             /* Check string position */
             if (eet_test_close(!((ef->ed->all[j].len > 0)
                                  && (offset >
                                      (bytes_dictionary_entries +
                                       bytes_directory_entries))
                                  && (offset + ef->ed->all[j].len <
                                      ef->data_size)), ef))
               return NULL;

             ef->ed->all[j].str = start + offset;

             if (ef->ed->all[j].str + ef->ed->all[j].len > ef->ed->end)
               ef->ed->end = ef->ed->all[j].str + ef->ed->all[j].len;

             /* Check '\0' at the end of the string */
             if (eet_test_close(ef->ed->all[j].str[ef->ed->all[j].len - 1] !=
                                '\0', ef))
               return NULL;

             ef->ed->all_hash[j] = hash;
             if (prev == -1)
               ef->ed->hash[hash] = j;

             /* compute the possible position of a signature */
             if (signature_base_offset < offset + ef->ed->all[j].len)
               signature_base_offset = offset + ef->ed->all[j].len;
          }
     }

   /* Check if the file is signed */
   ef->x509_der = NULL;
   ef->x509_length = 0;
   ef->signature = NULL;
   ef->signature_length = 0;

   if (signature_base_offset < ef->data_size)
     {
#ifdef HAVE_SIGNATURE
        const unsigned char *buffer = ((const unsigned char *)ef->data) +
          signature_base_offset;
        unsigned long int sig_size = ef->data_size - signature_base_offset;

        /* check that the signature is a sane size to bother even checking */
        if (sig_size >= (3 * sizeof(int)))
          {
             int head[3];

             /* check the signature has the magic number and sig + cert len
              * + magic is sane */
             memcpy(head, buffer, 3 * sizeof(int));
             head[0] = eina_ntohl(head[0]);
             head[1] = eina_ntohl(head[1]);
             head[2] = eina_ntohl(head[2]);
             if ((head[0] == EET_MAGIC_SIGN) && (head[1] > 0) && (head[2] > 0))
               {
                  /* there appears to be an actual valid identity at the end
                   * so now actually check it */
                  ef->x509_der = eet_identity_check(ef->data,
                                                    signature_base_offset,
                                                    &ef->sha1,
                                                    &ef->sha1_length,
                                                    buffer,
                                                    sig_size,
                                                    &ef->signature,
                                                    &ef->signature_length,
                                                    &ef->x509_length);

                  if (eet_test_close(!ef->x509_der, ef)) return NULL;
               }
          }

#else /* ifdef HAVE_SIGNATURE */
        ERR(
          "This file could be signed but you didn't compile the necessary code to check the signature.");
#endif /* ifdef HAVE_SIGNATURE */
     }

   /* At this stage we have a valid eet file, let's tell the system we are likely to need most of its data */
   if (ef->readfp && ef->ed)
     {
        unsigned long int offset;

        offset = (unsigned char*) ef->ed->start - (unsigned char*) ef->data;
        eina_file_map_populate(ef->readfp, EINA_FILE_WILLNEED, ef->data,
                               offset, ef->data_size - offset);
     }

   return ef;
}

#if EET_OLD_EET_FILE_FORMAT
static Eet_File *
eet_internal_read1(Eet_File *ef)
{
   const unsigned char *dyn_buf = NULL;
   const unsigned char *p = NULL;
   unsigned long int byte_entries;
   unsigned long int num_entries;
   unsigned int i;
   int idx = 0;

   WRN(
     "EET file format of '%s' is deprecated. You should just open it one time with mode == EET_FILE_MODE_READ_WRITE to solve this issue.",
     ef->path);

   /* build header table if read mode */
   /* geat header */
   idx += sizeof(int);
   if (eet_test_close((int)eina_ntohl(*((int *)ef->data)) != EET_MAGIC_FILE, ef))
     return NULL;

#define EXTRACT_INT(Value, Pointer, Index)       \
  {                                              \
     int tmp;                                    \
     memcpy(&tmp, Pointer + Index, sizeof(int)); \
     Value = eina_ntohl(tmp);                         \
     Index += sizeof(int);                       \
  }

   /* get entries count and byte count */
   EXTRACT_INT(num_entries, ef->data, idx);
   EXTRACT_INT(byte_entries, ef->data, idx);

   /* we can't have <= 0 values here - invalid */
   if (eet_test_close((num_entries > 0x7fffffff) ||
                      (byte_entries > 0x7fffffff), ef))
     return NULL;

   /* we can't have more entries than minimum bytes for those! invalid! */
   if (eet_test_close((num_entries * 20) > byte_entries, ef))
     return NULL;

   /* check we will not outrun the file limit */
   if (eet_test_close(((byte_entries + (int)(sizeof(int) * 3)) >
                       ef->data_size), ef))
     return NULL;

   /* allocate header */
   ef->header = eet_file_header_calloc(1);
   if (eet_test_close(!ef->header, ef))
     return NULL;

   ef->header->magic = EET_MAGIC_FILE_HEADER;

   /* allocate directory block in ram */
   ef->header->directory = eet_file_directory_calloc(1);
   if (eet_test_close(!ef->header->directory, ef))
     return NULL;

   /* 8 bit hash table (256 buckets) */
   ef->header->directory->size = 8;
   /* allocate base hash table */
   ef->header->directory->nodes =
     calloc(1, sizeof(Eet_File_Node *) * (1 << ef->header->directory->size));
   if (eet_test_close(!ef->header->directory->nodes, ef))
     return NULL;

   /* actually read the directory block - all of it, into ram */
   dyn_buf = ef->data + idx;

   /* parse directory block */
   p = dyn_buf;

   for (i = 0; i < num_entries; i++)
     {
        Eet_File_Node *efn;
        void *data = NULL;
        int indexn = 0;
        int name_size;
        int hash;
        int k;

#define HEADER_SIZE (sizeof(int) * 5)

        /* out directory block is inconsistent - we have overrun our */
        /* dynamic block buffer before we finished scanning dir entries */
        if (eet_test_close(p + HEADER_SIZE >= (dyn_buf + byte_entries), ef))
          return NULL;

        /* allocate all the ram needed for this stored node accounting */
        efn = eet_file_node_malloc(1);
        if (eet_test_close(!efn, ef))
          {
             if (efn) eet_file_node_mp_free(efn);  /* yes i know - we only get here if
                                   * efn is null/0 -> trying to shut up
                                   * warning tools like cppcheck */
             return NULL;
          }

        /* get entrie header */
        EXTRACT_INT(efn->offset, p, indexn);
        EXTRACT_INT(efn->compression, p, indexn);
        EXTRACT_INT(efn->size, p, indexn);
        EXTRACT_INT(efn->data_size, p, indexn);
        EXTRACT_INT(name_size, p, indexn);

        efn->name_size = name_size;
        efn->ciphered = 0;
        efn->alias = 0;

        /* invalid size */
        if (eet_test_close(efn->size <= 0, ef))
          {
             eet_file_node_mp_free(efn);
             return NULL;
          }

        /* invalid name_size */
        if (eet_test_close(name_size == 0, ef))
          {
             eet_file_node_mp_free(efn);
             return NULL;
          }

        /* reading name would mean falling off end of dyn_buf - invalid */
        if (eet_test_close((p + 16 + name_size) > (dyn_buf + byte_entries), ef))
          {
             eet_file_node_mp_free(efn);
             return NULL;
          }

        /* This code is useless if we dont want backward compatibility */
        for (k = name_size;
             k > 0 && ((unsigned char)*(p + HEADER_SIZE + k)) != 0; --k)
          ;

        efn->free_name = ((unsigned char)*(p + HEADER_SIZE + k)) != 0;

        if (efn->free_name)
          {
             efn->name = malloc(sizeof(char) * name_size + 1);
             if (eet_test_close(!efn->name, ef))
               {
                  eet_file_node_mp_free(efn);
                  return NULL;
               }

             strncpy(efn->name, (char *)p + HEADER_SIZE, name_size);
             efn->name[name_size] = 0;
             ef->header->directory->free_count++;

             WRN(
               "File: %s is not up to date for key \"%s\" - needs rebuilding sometime",
               ef->path,
               efn->name);
          }
        else
          /* The only really useful peace of code for efn->name (no backward compatibility) */
          efn->name = (char *)((unsigned char *)(p + HEADER_SIZE));

        /* get hash bucket it should go in */
        hash = _eet_hash_gen(efn->name, ef->header->directory->size);
        efn->next = ef->header->directory->nodes[hash];
        ef->header->directory->nodes[hash] = efn;

        /* read-only mode, so currently we have no data loaded */
        if (ef->mode == EET_FILE_MODE_READ)
          efn->data = NULL;  /* read-write mode - read everything into ram */
        else
          {
             data = malloc(efn->size);
             if (data)
               {
                  memcpy(data, ef->data + efn->offset, efn->size);
                  ef->header->directory->free_count++;
               }

             efn->data = data;
          }

        /* advance */
        p += HEADER_SIZE + name_size;
     }
   return ef;
}

#endif /* if EET_OLD_EET_FILE_FORMAT */

/*
 * this should only be called when the cache lock is already held
 * (We could drop this restriction if we add a parameter to eet_test_close
 * that indicates if the lock is held or not.  For now it is easiest
 * to just require that it is always held.)
 */
static Eet_File *
eet_internal_read(Eet_File *ef)
{
   const int *data = (const int *)ef->data;

   if (eet_test_close((ef->data == (void *)-1) || (!ef->data), ef))
     return NULL;

   if (eet_test_close(ef->data_size < (int)sizeof(int) * 3, ef))
     return NULL;

   switch (eina_ntohl(*data))
     {
#if EET_OLD_EET_FILE_FORMAT
      case EET_MAGIC_FILE:
        return eet_internal_read1(ef);

#endif /* if EET_OLD_EET_FILE_FORMAT */
      case EET_MAGIC_FILE2:
        return eet_internal_read2(ef);

      default:
        ef->delete_me_now = 1;
        eet_internal_close(ef, EINA_TRUE, EINA_FALSE);
        break;
     }

   return NULL;
}

static Eet_Error
eet_internal_close(Eet_File *ef,
                   Eina_Bool locked, Eina_Bool shutdown)
{
   Eet_Error err = EET_ERROR_NONE;

   /* check to see its' an eet file pointer */
   if (eet_check_pointer(ef))
     {
        ERR("Bad file descriptor '%p'\n", ef);
        return EET_ERROR_BAD_OBJECT;
     }

   if (!locked)
     LOCK_CACHE;

   /* deref */
   ef->references--;
   /* if its still referenced - dont go any further */
   if (ef->references > 0)
     {
        /* flush any writes */
         if ((ef->mode == EET_FILE_MODE_WRITE) ||
             (ef->mode == EET_FILE_MODE_READ_WRITE))
           eet_sync(ef);
         goto on_error;
     }

   err = eet_flush2(ef, EINA_FALSE);

   eet_identity_unref(ef->key);
   ef->key = NULL;

   /* if not urgent to delete it - dont free it - leave it in cache */
   if ((!ef->delete_me_now) && (ef->mode == EET_FILE_MODE_READ))
     goto on_error;

   /* remove from cache */
   if (ef->mode == EET_FILE_MODE_READ)
     eet_cache_del(ef, &eet_readers, &eet_readers_num, &eet_readers_alloc);
   else if ((ef->mode == EET_FILE_MODE_WRITE) ||
            (ef->mode == EET_FILE_MODE_READ_WRITE))
     eet_cache_del(ef, &eet_writers, &eet_writers_num, &eet_writers_alloc);

   /* we can unlock the cache now */
   if (!locked)
     UNLOCK_CACHE;

   DESTROY_FILE(ef);

   /* free up data */
   if (ef->header)
     {
        if (ef->header->directory)
          {
             if (ef->header->directory->nodes)
               {
                  int i, num;

                  num = (1 << ef->header->directory->size);
                  for (i = 0; i < num && ef->header->directory->free_count; i++)
                    {
                       Eet_File_Node *efn;

                       while ((efn = ef->header->directory->nodes[i]))
                         {
                            if (efn->data)
                              {
                                 free(efn->data);
                                 ef->header->directory->free_count--;
                              }

                            ef->header->directory->nodes[i] = efn->next;

                            if (efn->free_name)
                              {
                                 free(efn->name);
                                 ef->header->directory->free_count--;
                              }

                            if (shutdown)
                              {
                                 if (!ef->header->directory->free_count) break;
                              }
                            else
                              eet_file_node_mp_free(efn);
                         }
                    }
                  free(ef->header->directory->nodes);
               }

             if (!shutdown)
               eet_file_directory_mp_free(ef->header->directory);
          }

        if (!shutdown)
          eet_file_header_mp_free(ef->header);
     }

   eet_dictionary_free(ef->ed);

   if (ef->sha1)
     free(ef->sha1);

   if (ef->readfp && ef->readfp_owned)
     {
        if (ef->data)
          eina_file_map_free(ef->readfp, (void *)ef->data);

        eina_file_close(ef->readfp);
     }

   /* zero out ram for struct - caution tactic against stale memory use */
   memset(ef, 0, sizeof(Eet_File));

   /* free it */
   eina_stringshare_del(ef->path);
   if (!shutdown)
     eet_file_mp_free(ef);
   return err;

on_error:
   if (!locked)
     UNLOCK_CACHE;

   return EET_ERROR_NONE;
}

EAPI Eet_File *
eet_memopen_read(const void *data,
                 size_t      size)
{
   Eet_File *ef;

   if (!data || size == 0)
     return NULL;

   ef = eet_file_malloc(1);
   if (!ef)
     return NULL;

   /* eet_internal_read expects the cache lock to be held when it is called */
   LOCK_CACHE;

   INIT_FILE(ef);
   ef->ed = NULL;
   ef->path = NULL;
   ef->key = NULL;
   ef->magic = EET_MAGIC_FILE;
   ef->references = 1;
   ef->mode = EET_FILE_MODE_READ;
   ef->header = NULL;
   ef->delete_me_now = 1;
   ef->readfp = NULL;
   ef->data = data;
   ef->data_size = size;
   ef->sha1 = NULL;
   ef->sha1_length = 0;
   ef->readfp_owned = EINA_FALSE;

   ef = eet_internal_read(ef);
   UNLOCK_CACHE;
   return ef;
}

EAPI const char *
eet_file_get(Eet_File *ef)
{
   if (eet_check_pointer(ef)) return NULL;
   return ef->path;
}

EAPI Eet_File *
eet_mmap(const Eina_File *file)
{
   Eet_File *ef = NULL;
   const char *path;

   path = eina_file_filename_get(file);

   LOCK_CACHE;
   ef = eet_cache_find(path, eet_writers, eet_writers_num);
   if (ef)
     {
        eet_sync(ef);
        ef->references++;
        ef->delete_me_now = 1;
        eet_internal_close(ef, EINA_TRUE, EINA_FALSE);
     }

   ef = eet_cache_find(path, eet_readers, eet_readers_num);
   if (ef && ef->readfp == file)
     {
        ef->references++;
        goto done;
     }

   /* Allocate struct for eet file and have it zero'd out */
   ef = eet_file_malloc(1);
   if (!ef) goto on_error;

   /* fill some of the members */
   INIT_FILE(ef);
   ef->ed = NULL;
   ef->key = NULL;
   ef->readfp = eina_file_dup(file);
   ef->path = eina_stringshare_add(path);
   ef->magic = EET_MAGIC_FILE;
   ef->references = 1;
   ef->mode = EET_FILE_MODE_READ;
   ef->header = NULL;
   ef->writes_pending = 0;
   ef->delete_me_now = 0;
   ef->data = NULL;
   ef->data_size = 0;
   ef->sha1 = NULL;
   ef->sha1_length = 0;
   ef->readfp_owned = EINA_TRUE;

   ef->data_size = eina_file_size_get(ef->readfp);
   ef->data = eina_file_map_all(ef->readfp, EINA_FILE_SEQUENTIAL);
   if (eet_test_close((ef->data == NULL), ef))
     goto on_error;

   ef = eet_internal_read(ef);
   if (!ef)
     goto on_error;

   if (ef->mode == EET_FILE_MODE_READ)
     eet_cache_add(ef, &eet_readers, &eet_readers_num, &eet_readers_alloc);

 done:
   UNLOCK_CACHE;
   return ef;

 on_error:
   UNLOCK_CACHE;
   return NULL;
}

EAPI Eet_File *
eet_open(const char   *file,
         Eet_File_Mode mode)
{
   Eina_File *fp;
   Eet_File *ef;
   int file_len, ret;
   unsigned long int size;

   if (!file)
     return NULL;

   /* find the current file handle in cache*/
   ef = NULL;
   LOCK_CACHE;
   if (mode == EET_FILE_MODE_READ)
     {
        ef = eet_cache_find((char *)file, eet_writers, eet_writers_num);
        if (ef)
          {
             eet_sync(ef);
             ef->references++;
             ef->delete_me_now = 1;
             eet_internal_close(ef, EINA_TRUE, EINA_FALSE);
          }

        ef = eet_cache_find((char *)file, eet_readers, eet_readers_num);
     }
   else if ((mode == EET_FILE_MODE_WRITE) ||
            (mode == EET_FILE_MODE_READ_WRITE))
     {
        ef = eet_cache_find((char *)file, eet_readers, eet_readers_num);
        if (ef)
          {
             ef->delete_me_now = 1;
             ef->references++;
             eet_internal_close(ef, EINA_TRUE, EINA_FALSE);
          }

        ef = eet_cache_find((char *)file, eet_writers, eet_writers_num);
     }

   /* try open the file based on mode */
   if ((mode == EET_FILE_MODE_READ) || (mode == EET_FILE_MODE_READ_WRITE))
     {
        if (mode == EET_FILE_MODE_READ_WRITE)
          {
             /* do not use eina_file_access() here */
             ret = access(file, W_OK);
             if ((ret != 0) && (errno != ENOENT)) return NULL;
          }
        /* Prevent garbage in futur comparison. */
         fp = eina_file_open(file, EINA_FALSE);
         if (!fp)
           {
              size = 0;
              goto open_error;
           }

         size = eina_file_size_get(fp);

         if (size < ((int)sizeof(int) * 3))
           {
              eina_file_close(fp);
              fp = NULL;

              size = 0;

              goto open_error;
           }

open_error:
         if (!fp && mode == EET_FILE_MODE_READ)
           goto on_error;
     }
   else
     {
        if (mode != EET_FILE_MODE_WRITE)
          {
            UNLOCK_CACHE;
            return NULL;
          }

        size = 0;

        fp = NULL;
        /* do not use eina_file_access() here */
        ret = access(file, W_OK);
        if ((ret != 0) && (errno != ENOENT)) return NULL;
     }

   /* We found one */
   if (ef && ef->readfp != fp)
     {
        ef->delete_me_now = 1;
        ef->references++;
        eet_internal_close(ef, EINA_TRUE, EINA_FALSE);
        ef = NULL;
     }

   if (ef)
     {
        /* reference it up and return it */
         if (fp)
           eina_file_close(fp);

         ef->references++;
         UNLOCK_CACHE;
         return ef;
     }

   file_len = strlen(file) + 1;

   /* Allocate struct for eet file and have it zero'd out */
   ef = eet_file_malloc(1);
   if (!ef)
     goto on_error;

   /* fill some of the members */
   INIT_FILE(ef);
   ef->key = NULL;
   ef->readfp = fp;
   ef->path = eina_stringshare_add_length(file, file_len);
   ef->magic = EET_MAGIC_FILE;
   ef->references = 1;
   ef->mode = mode;
   ef->header = NULL;
   ef->writes_pending = 0;
   ef->delete_me_now = 0;
   ef->data = NULL;
   ef->data_size = 0;
   ef->sha1 = NULL;
   ef->sha1_length = 0;
   ef->readfp_owned = EINA_TRUE;

   ef->ed = (mode == EET_FILE_MODE_WRITE)
     || (!ef->readfp && mode == EET_FILE_MODE_READ_WRITE) ?
     eet_dictionary_add() : NULL;

   if (!ef->readfp &&
       (mode == EET_FILE_MODE_READ_WRITE || mode == EET_FILE_MODE_WRITE))
     goto empty_file;

   /* if we can't open - bail out */
   if (eet_test_close(!ef->readfp, ef))
     goto on_error;

   /* if we opened for read or read-write */
   if ((mode == EET_FILE_MODE_READ) || (mode == EET_FILE_MODE_READ_WRITE))
     {
        ef->data_size = size;
        ef->data = eina_file_map_all(fp, EINA_FILE_SEQUENTIAL);
        if (eet_test_close((ef->data == NULL), ef))
          goto on_error;

        ef = eet_internal_read(ef);
        if (!ef)
          goto on_error;
     }

empty_file:
   /* add to cache */
   if (ef->references == 1)
     {
        if (ef->mode == EET_FILE_MODE_READ)
          eet_cache_add(ef, &eet_readers, &eet_readers_num, &eet_readers_alloc);
        else if ((ef->mode == EET_FILE_MODE_WRITE) ||
                 (ef->mode == EET_FILE_MODE_READ_WRITE))
          eet_cache_add(ef, &eet_writers, &eet_writers_num, &eet_writers_alloc);
     }

   UNLOCK_CACHE;
   return ef;

on_error:
   UNLOCK_CACHE;
   return NULL;
}

EAPI Eet_File_Mode
eet_mode_get(Eet_File *ef)
{
   /* check to see its' an eet file pointer */
    if ((!ef) || (ef->magic != EET_MAGIC_FILE))
      return EET_FILE_MODE_INVALID;
    else
      return ef->mode;
}

static const char *_b64_table =
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

static Eina_Bool
_b64_is(char c)
{
   const char *p;

   if (!c) return EINA_FALSE;
   p = strchr(_b64_table, c);
   if (p >= _b64_table) return EINA_TRUE;
   return EINA_FALSE;
}

static unsigned char
_b64_val(char c)
{
   const char *p = strchr(_b64_table, c);
   if (p) return p - _b64_table;
   return 0;
}

static int
_b64_dec(unsigned char *dst, const char *src, int len)
{
   unsigned char *p = dst;
   *dst = 0;

   if (!*src) return 0;
   do
     {
        unsigned char a = _b64_val(src[0]);
        unsigned char b = _b64_val(src[1]);
        unsigned char c = _b64_val(src[2]);
        unsigned char d = _b64_val(src[3]);

        *p++ = (a << 2) | (b >> 4);
        *p++ = (b << 4) | (c >> 2);
        *p++ = (c << 6) | d;

        if (!_b64_is(src[1]))
          {
             p -= 2;
             break;
          }
        else if (!_b64_is(src[2]))
          {
             p -= 2;
             break;
          }
        else if (!_b64_is(src[3]))
          {
             p--;
             break;
          }
        src += 4;
        while (*src && ((*src == 13) || (*src == 10))) src++;
     }
   while ((len -= 4));
   *p = 0;
   return (int)(p - dst);
}

static unsigned char *
_base64_dec(const char *file, int *size_ret)
{
   char buf[4096], *p, *end;
   unsigned char *data = NULL;
   Eina_Binbuf *binbuf;
   FILE *f;

   f = fopen(file, "rb");
   if (!f) return NULL;
   binbuf = eina_binbuf_new();
   if (!binbuf)
     {
        fclose(f);
        return NULL;
     }
   while (fgets(buf, sizeof(buf) - 1, f))
     {
        buf[sizeof(buf) - 1] = 0;
        // check where first invalid char in a line is
        for (p = buf; *p; p++)
          {
             // this is the first invalid char
             if ((*p != '=') && (!_b64_is(*p))) break;
          }
        end = p;
        // go from line start to (but not including) first invalid char
        if (((end - buf) > 0) &&
            ((end - buf) < 0x1fffffff) && // not too long
            (((end - buf) % 4) == 0))
          {
             unsigned char *tmp = malloc((end - buf + 4) * 2);

             if (tmp)
               {
                  size_t len = _b64_dec(tmp, buf, end - buf);
                  char *str = malloc(end - buf + 1);
                  strncpy(str, buf, end - buf);
                  str[end - buf] = 0;
                  free(str);
                  eina_binbuf_append_length(binbuf, tmp, len);
                  free(tmp);
               }
          }
     }
   fclose(f);
   // as long as data is less than a mb - we have a cert that is possibly ok
   if (eina_binbuf_length_get(binbuf) < (1 * 1024 * 1024))
     {
        *size_ret = eina_binbuf_length_get(binbuf);
        data = eina_binbuf_string_steal(binbuf);
     }
   eina_binbuf_free(binbuf);
   return data;
}

EAPI Eina_Bool
eet_identity_verify(Eet_File   *ef,
                    const char *certificate_file)
{
   unsigned char *cert;
   int cert_len;

   if (eet_check_pointer(ef))
     return EINA_FALSE;

   if (!ef->x509_der)
     return EINA_FALSE;

   cert = _base64_dec(certificate_file, &cert_len);
   if (!cert)
     return EINA_FALSE;

   if (cert_len != ef->x509_length)
     {
        free(cert);
        return EINA_FALSE;
     }
   if (memcmp(ef->x509_der, cert, cert_len))
     {
        free(cert);
        return EINA_FALSE;
     }
   free(cert);
   return EINA_TRUE;
}

EAPI const void *
eet_identity_x509(Eet_File *ef,
                  int      *der_length)
{
   if (eet_check_pointer(ef))
     return NULL;

   if (!ef->x509_der)
     return NULL;

   if (der_length)
     *der_length = ef->x509_length;

   return ef->x509_der;
}

EAPI const void *
eet_identity_signature(Eet_File *ef,
                       int      *signature_length)
{
   if (eet_check_pointer(ef))
     return NULL;

   if (!ef->signature)
     return NULL;

   if (signature_length)
     *signature_length = ef->signature_length;

   return ef->signature;
}

EAPI const void *
eet_identity_sha1(Eet_File *ef,
                  int      *sha1_length)
{
   if (eet_check_pointer(ef))
     return NULL;

   if (!ef->sha1)
     ef->sha1 = eet_identity_compute_sha1(ef->data,
                                          ef->data_size,
                                          &ef->sha1_length);

   if (sha1_length)
     *sha1_length = ef->sha1_length;

   return ef->sha1;
}

EAPI Eet_Error
eet_identity_set(Eet_File *ef,
                 Eet_Key  *key)
{
   Eet_Key *tmp;

   if (!ef)
     return EET_ERROR_BAD_OBJECT;

   tmp = ef->key;
   ef->key = key;
   eet_identity_ref(ef->key);
   eet_identity_unref(tmp);

   /* flags that writes are pending */
   ef->writes_pending = 1;

   return EET_ERROR_NONE;
}

EAPI Eet_Error
eet_close(Eet_File *ef)
{
   return eet_internal_close(ef, EINA_FALSE, EINA_FALSE);
}

EAPI void *
eet_read_cipher(Eet_File   *ef,
                const char *name,
                int        *size_ret,
                const char *cipher_key)
{
   Eet_File_Node *efn;
   Eina_Binbuf *in = NULL;
   unsigned char *data = NULL;

   if (size_ret)
     *size_ret = 0;

   /* check to see its' an eet file pointer */
   if (eet_check_pointer(ef))
     return NULL;

   if (!name)
     return NULL;

   if ((ef->mode != EET_FILE_MODE_READ) &&
       (ef->mode != EET_FILE_MODE_READ_WRITE))
     return NULL;

   /* no header, return NULL */
   if (eet_check_header(ef))
     return NULL;

   LOCK_FILE(ef);

   /* hunt hash bucket */
   efn = find_node_by_name(ef, name);
   if (!efn)
     goto on_error;

   /* Requested decryption but file not encrypted -> integrity violation */
   if (!efn->ciphered && cipher_key)
     goto on_error;

   /* Get a binbuf attached to this efn */
   in = read_binbuf_from_disk(ef, efn);
   if (!in) goto on_error;

   /* First uncipher data */
   if (efn->ciphered && cipher_key)
     {
        Eina_Binbuf *out;

        out = emile_binbuf_decipher(EMILE_AES256_CBC, in,
                                    cipher_key, strlen(cipher_key));

        eina_binbuf_free(in);
        if (!out) goto on_error;

        in = out;
     }

   if (efn->compression)
     {
        Eina_Binbuf *out;

        out = emile_decompress(in,
                               eet_2_emile_compressor(efn->compression_type),
                               efn->data_size);

        eina_binbuf_free(in);
        if (!out) goto on_error;

        in = out;
     }

   UNLOCK_FILE(ef);

   if (size_ret)
     *size_ret = eina_binbuf_length_get(in);
   data = eina_binbuf_string_steal(in);
   eina_binbuf_free(in);

   /* handle alias */
   if (efn->alias)
     {
        void *tmp;

        if (data[efn->data_size - 1] != '\0')
          goto on_error;

        tmp = eet_read_cipher(ef, (char*) data, size_ret, cipher_key);

        free(data);

        data = tmp;
     }

   return data;

on_error:
   UNLOCK_FILE(ef);
   free(data);
   return NULL;
}

EAPI void *
eet_read(Eet_File   *ef,
         const char *name,
         int        *size_ret)
{
   return eet_read_cipher(ef, name, size_ret, NULL);
}

EAPI const void *
eet_read_direct(Eet_File   *ef,
                const char *name,
                int        *size_ret)
{
   Eet_File_Node *efn;
   const char *data = NULL;
   int size = 0;

   if (size_ret)
     *size_ret = 0;

   /* check to see its' an eet file pointer */
   if (eet_check_pointer(ef))
     return NULL;

   if (!name)
     return NULL;

   if ((ef->mode != EET_FILE_MODE_READ) &&
       (ef->mode != EET_FILE_MODE_READ_WRITE))
     return NULL;

   /* no header, return NULL */
   if (eet_check_header(ef))
     return NULL;

   LOCK_FILE(ef);

   /* hunt hash bucket */
   efn = find_node_by_name(ef, name);
   if (!efn) goto on_error;

   /* trick to detect data in memory instead of mmaped from disk */
   if (((efn->offset + efn->size) > ef->data_size) && !efn->data)
     goto on_error;

   /* get size (uncompressed, if compressed at all) */
   size = efn->data_size;

   /* handle alias case */
   if (efn->alias)
     {
        if (efn->compression)
          {
             Eina_Binbuf *in;
             Eina_Binbuf *out;
             const unsigned char *tmp;
             const char *retptr;

             in = read_binbuf_from_disk(ef, efn);
             if (!in) goto on_error;

             out = emile_decompress(in,
                                    eet_2_emile_compressor(efn->compression_type),
                                    efn->data_size);
             eina_binbuf_free(in);
             if (!out) goto on_error;

             tmp = eina_binbuf_string_get(out);
             if (tmp[eina_binbuf_length_get(out) - 1] != '\0')
               {
                  eina_binbuf_free(out);
                  goto on_error;
               }

             UNLOCK_FILE(ef);

             retptr = eet_read_direct(ef, (const char *) tmp, size_ret);

             eina_binbuf_free(out);
             return retptr;
          }

        data = efn->data ? efn->data : ef->data + efn->offset;
        if (!data) goto on_error;

        if (data[size - 1] != '\0')
          goto on_error;

        UNLOCK_FILE(ef);

        return eet_read_direct(ef, data, size_ret);
     }
   else
   /* uncompressed data */
   if ((efn->compression == 0) && (efn->ciphered == 0))
     data = efn->data ? efn->data : ef->data + efn->offset;  /* compressed data */
   else
     data = NULL;

   /* fill in return values */
   if (size_ret)
     *size_ret = size;

   UNLOCK_FILE(ef);

   return data;

on_error:
   UNLOCK_FILE(ef);
   return NULL;
}

EAPI const char *
eet_alias_get(Eet_File   *ef,
              const char *name)
{
   Eet_File_Node *efn;
   const char *data = NULL;
   int size = 0;

   /* check to see its' an eet file pointer */
   if (eet_check_pointer(ef))
     return NULL;

   if (!name)
     return NULL;

   if ((ef->mode != EET_FILE_MODE_READ) &&
       (ef->mode != EET_FILE_MODE_READ_WRITE))
     return NULL;

   /* no header, return NULL */
   if (eet_check_header(ef))
     return NULL;

   LOCK_FILE(ef);

   /* hunt hash bucket */
   efn = find_node_by_name(ef, name);
   if (!efn)
     goto on_error;

   /* trick to detect data in memory instead of mmaped from disk */
   if (efn->offset > ef->data_size && !efn->data)
     goto on_error;

   /* get size (uncompressed, if compressed at all) */
   size = efn->data_size;

   if (!efn->alias) goto on_error;
   data = efn->data ? efn->data : ef->data + efn->offset;

   /* handle alias case */
   if (efn->compression)
     {
        Eina_Binbuf *in;
        Eina_Binbuf *out;
        const unsigned char *tmp;
        const char *retptr;

        in = read_binbuf_from_disk(ef, efn);
        if (!in) goto on_error;

        out = emile_decompress(in,
                               eet_2_emile_compressor(efn->compression_type),
                               efn->data_size);
        eina_binbuf_free(in);
        if (!out) goto on_error;

        tmp = eina_binbuf_string_get(out);
        if (tmp[eina_binbuf_length_get(out) - 1] != '\0')
          {
             eina_binbuf_free(out);
             goto on_error;
          }

        UNLOCK_FILE(ef);

        retptr = eina_stringshare_add((const char *) tmp);

        eina_binbuf_free(out);
        return retptr;
     }

   if (!data)
     goto on_error;

   if (data[size - 1] != '\0')
     goto on_error;

   UNLOCK_FILE(ef);

   return eina_stringshare_add(data);

on_error:
   UNLOCK_FILE(ef);
   return NULL;
}

static void
eet_define_data(Eet_File *ef, Eet_File_Node *efn, Eina_Binbuf *data, int original_size, int comp, Eina_Bool ciphered)
{
   free(efn->data);
   efn->alias = 0;
   efn->ciphered = ciphered;
   efn->compression = !!comp;
   efn->compression_type = comp;
   efn->size = eina_binbuf_length_get(data);
   efn->data_size = original_size;
   efn->data = efn->size ? eina_binbuf_string_steal(data) : NULL;
   /* Put the offset above the limit to avoid direct access */
   efn->offset = ef->data_size + 1;
}

EAPI Eina_Bool
eet_alias(Eet_File   *ef,
          const char *name,
          const char *destination,
          int         comp)
{
   Eet_File_Node *efn;
   Eina_Binbuf *in;
   Eina_Bool exists_already = EINA_FALSE;
   int hash;
   Eina_Bool success = EINA_FALSE;

   /* check to see its' an eet file pointer */
   if (eet_check_pointer(ef))
     return EINA_FALSE;

   if ((!name) || (!destination))
     return EINA_FALSE;

   if ((ef->mode != EET_FILE_MODE_WRITE) &&
       (ef->mode != EET_FILE_MODE_READ_WRITE))
     return EINA_FALSE;

   LOCK_FILE(ef);

   if (!ef->header)
     {
        /* allocate header */
         ef->header = eet_file_header_calloc(1);
         if (!ef->header)
           goto on_error;

         ef->header->magic = EET_MAGIC_FILE_HEADER;
         /* allocate directory block in ram */
         ef->header->directory = eet_file_directory_calloc(1);
         if (!ef->header->directory)
           {
              eet_file_header_mp_free(ef->header);
              ef->header = NULL;
              goto on_error;
           }

         /* 8 bit hash table (256 buckets) */
         ef->header->directory->size = 8;
         /* allocate base hash table */
         ef->header->directory->nodes =
           calloc(1, sizeof(Eet_File_Node *) *
                  (1 << ef->header->directory->size));
         if (!ef->header->directory->nodes)
           {
              eet_file_directory_mp_free(ef->header->directory);
              ef->header = NULL;
              goto on_error;
           }
     }

   /* figure hash bucket */
   hash = _eet_hash_gen(name, ef->header->directory->size);

   in = eina_binbuf_manage_new((unsigned char*) destination, strlen(destination) + 1, EINA_TRUE);
   if (!in) goto on_error;

   /* if we want to compress */
   if (comp)
     {
        Eina_Binbuf *out;

        out = emile_compress(in,
                             eet_2_emile_compressor(comp),
                             EMILE_COMPRESSOR_BEST);
        eina_binbuf_free(in);
        if (!out) goto on_error;

        in = out;
     }

   /* Does this node already exist? */
   for (efn = ef->header->directory->nodes[hash]; efn; efn = efn->next)
     {
        /* if it matches */
         if ((efn->name) && (eet_string_match(efn->name, name)))
           {
              eet_define_data(ef, efn, in, strlen(destination) + 1, comp, 0);
              exists_already = EINA_TRUE;
              break;
           }
     }
   if (!exists_already)
     {
        efn = eet_file_node_malloc(1);
        if (!efn)
          {
             eina_binbuf_free(in);
             goto on_error;
          }

        efn->name = strdup(name);
        efn->name_size = strlen(efn->name) + 1;
        efn->free_name = 1;
        ef->header->directory->free_count++;
        efn->data = NULL;

        efn->next = ef->header->directory->nodes[hash];
        ef->header->directory->nodes[hash] = efn;

        eet_define_data(ef, efn, in, strlen(destination) + 1, comp, 0);
        ef->header->directory->free_count++;
     }

   efn->alias = 1;
   eina_binbuf_free(in);

   /* flags that writes are pending */
   ef->writes_pending = 1;

   success = EINA_TRUE;
on_error:

   UNLOCK_FILE(ef);
   return success;
}

EAPI int
eet_write_cipher(Eet_File   *ef,
                 const char *name,
                 const void *data,
                 int         size,
                 int         comp,
                 const char *cipher_key)
{
   Eina_Binbuf *in;
   Eet_File_Node *efn;
   int exists_already = 0;
   int hash;

   /* check to see its' an eet file pointer */
   if (eet_check_pointer(ef))
     return 0;

   if ((!name) || (!data) || (size <= 0))
     return 0;

   if ((ef->mode != EET_FILE_MODE_WRITE) &&
       (ef->mode != EET_FILE_MODE_READ_WRITE))
     return 0;

   LOCK_FILE(ef);

   if (!ef->header)
     {
        /* allocate header */
         ef->header = eet_file_header_calloc(1);
         if (!ef->header)
           goto on_error;

         ef->header->magic = EET_MAGIC_FILE_HEADER;
         /* allocate directory block in ram */
         ef->header->directory = eet_file_directory_calloc(1);
         if (!ef->header->directory)
           {
              eet_file_header_mp_free(ef->header);
              ef->header = NULL;
              goto on_error;
           }

         /* 8 bit hash table (256 buckets) */
         ef->header->directory->size = 8;
         /* allocate base hash table */
         ef->header->directory->nodes =
           calloc(1, sizeof(Eet_File_Node *) *
                  (1 << ef->header->directory->size));
         if (!ef->header->directory->nodes)
           {
              eet_file_directory_mp_free(ef->header->directory);
              ef->header = NULL;
              goto on_error;
           }
     }

   /* figure hash bucket */
   hash = _eet_hash_gen(name, ef->header->directory->size);

   UNLOCK_FILE(ef);

   in = eina_binbuf_manage_new(data, size, EINA_TRUE);
   if (comp)
     {
        Eina_Binbuf *out;

        out = emile_compress(in, eet_2_emile_compressor(comp), EMILE_COMPRESSOR_BEST);
        if (out)
          {
             if (eina_binbuf_length_get(out) < eina_binbuf_length_get(in))
               {
                  eina_binbuf_free(in);
                  in = out;
               }
             else
               {
                  eina_binbuf_free(out);
                  comp = 0;
               }
          }
        else
          {
             // There is a change of behavior here, in case of memory pressure,
             // we will try to keep the uncompressed buffer.
             comp = 0;
          }
     }

   if (cipher_key)
     {
        Eina_Binbuf *out;

        out = emile_binbuf_cipher(EMILE_AES256_CBC, in,
                                  cipher_key, strlen(cipher_key));
        // Old behaviour was to not fail if the cipher where not built in
        if (out)
          {
             eina_binbuf_free(in);
             in = out;
          }
     }

   LOCK_FILE(ef);
   /* Does this node already exist? */
   for (efn = ef->header->directory->nodes[hash]; efn; efn = efn->next)
     {
        /* if it matches */
        if ((efn->name) && (eet_string_match(efn->name, name)))
          {
             eet_define_data(ef, efn, in, size, comp, !!cipher_key);
             exists_already = 1;
             break;
          }
     }
   if (!exists_already)
     {
        efn = eet_file_node_malloc(1);
        if (!efn)
          {
             eina_binbuf_free(in);
             goto on_error;
          }

        efn->name = strdup(name);
        efn->name_size = strlen(efn->name) + 1;
        efn->free_name = 1;
        ef->header->directory->free_count++;
        efn->data = NULL;

        efn->next = ef->header->directory->nodes[hash];
        ef->header->directory->nodes[hash] = efn;

        eet_define_data(ef, efn, in, size, comp, !!cipher_key);
        ef->header->directory->free_count++;
     }

   /* flags that writes are pending */
   ef->writes_pending = 1;
   UNLOCK_FILE(ef);

   eina_binbuf_free(in);

   return efn->size;

on_error:
   UNLOCK_FILE(ef);
   return 0;
}

EAPI int
eet_write(Eet_File   *ef,
          const char *name,
          const void *data,
          int         size,
          int         comp)
{
   return eet_write_cipher(ef, name, data, size, comp, NULL);
}

EAPI int
eet_delete(Eet_File   *ef,
           const char *name)
{
   Eet_File_Node *efn;
   Eet_File_Node *pefn;
   int hash;
   int exists_already = 0;

   /* check to see its' an eet file pointer */
   if (eet_check_pointer(ef))
     return 0;

   if (!name)
     return 0;

   /* deleting keys is only possible in RW or WRITE mode */
   if (ef->mode == EET_FILE_MODE_READ)
     return 0;

   if (eet_check_header(ef))
     return 0;

   LOCK_FILE(ef);

   /* figure hash bucket */
   hash = _eet_hash_gen(name, ef->header->directory->size);

   /* Does this node already exist? */
   for (pefn = NULL, efn = ef->header->directory->nodes[hash];
        efn;
        pefn = efn, efn = efn->next)
     {
        /* if it matches */
         if (eet_string_match(efn->name, name))
           {
              if (efn->data)
                free(efn->data);

              if (!pefn)
                ef->header->directory->nodes[hash] = efn->next;
              else
                pefn->next = efn->next;

              if (efn->free_name)
                free(efn->name);

              eet_file_node_mp_free(efn);
              exists_already = 1;
              break;
           }
     }
   /* flags that writes are pending */
   if (exists_already)
     ef->writes_pending = 1;

   UNLOCK_FILE(ef);

   /* update access time */
   return exists_already;
}

EAPI Eet_Dictionary *
eet_dictionary_get(Eet_File *ef)
{
   if (eet_check_pointer(ef))
     return NULL;

   return ef->ed;
}

EAPI char **
eet_list(Eet_File   *ef,
         const char *glob,
         int        *count_ret)
{
   Eet_File_Node *efn;
   char **list_ret = NULL;
   int list_count = 0;
   int list_count_alloc = 0;
   int i, num;

   /* check to see its' an eet file pointer */
   if (eet_check_pointer(ef) || eet_check_header(ef) ||
       (!glob) ||
       ((ef->mode != EET_FILE_MODE_READ) &&
        (ef->mode != EET_FILE_MODE_READ_WRITE)))
     {
        if (count_ret)
          *count_ret = 0;

        return NULL;
     }

   if (!strcmp(glob, "*"))
     glob = NULL;

   LOCK_FILE(ef);

   /* loop through all entries */
   num = (1 << ef->header->directory->size);
   for (i = 0; i < num; i++)
     {
        for (efn = ef->header->directory->nodes[i]; efn; efn = efn->next)
          {
             /* if the entry matches the input glob
              * check for * explicitly, because on some systems, * isn't well
              * supported
              */
               if ((!glob) || eina_fnmatch(glob, efn->name, 0))
                 {
     /* add it to our list */
                     list_count++;

     /* only realloc in 32 entry chunks */
                     if (list_count > list_count_alloc)
                       {
                          char **new_list = NULL;

                          list_count_alloc += 64;
                          new_list =
                            realloc(list_ret, list_count_alloc * (sizeof(char *)));
                          if (!new_list)
                            {
                               free(list_ret);

                               goto on_error;
                            }

                          list_ret = new_list;
                       }

     /* put pointer of name string in */
                     list_ret[list_count - 1] = efn->name;
                 }
          }
     }

   UNLOCK_FILE(ef);

   /* return count and list */
   if (count_ret)
     *count_ret = list_count;

   return list_ret;

on_error:
   UNLOCK_FILE(ef);

   if (count_ret)
     *count_ret = 0;

   return NULL;
}

EAPI int
eet_num_entries(Eet_File *ef)
{
   int i, num, ret = 0;
   Eet_File_Node *efn;

   /* check to see its' an eet file pointer */
   if (eet_check_pointer(ef) || eet_check_header(ef) ||
       ((ef->mode != EET_FILE_MODE_READ) &&
        (ef->mode != EET_FILE_MODE_READ_WRITE)))
     return -1;

   LOCK_FILE(ef);

   /* loop through all entries */
   num = (1 << ef->header->directory->size);
   for (i = 0; i < num; i++)
     {
        for (efn = ef->header->directory->nodes[i]; efn; efn = efn->next)
          ret++;
     }

   UNLOCK_FILE(ef);

   return ret;
}

typedef struct _Eet_Entries_Iterator Eet_Entries_Iterator;
struct _Eet_Entries_Iterator
{
   Eina_Iterator iterator;

   Eet_File *ef;
   Eet_File_Node *efn;
   int index;

   Eet_Entry entry;

   Eina_Bool locked;
};

Eina_Bool
_eet_entries_iterator_next(Eet_Entries_Iterator *it, void **data)
{
   if (it->efn == NULL)
     {
        int num;

        num = (1 << it->ef->header->directory->size);

        do
          {
             it->index++;

             if (!(it->index < num))
               return EINA_FALSE;

             it->efn = it->ef->header->directory->nodes[it->index];
          }
        while (!it->efn);
     }

   /* copy info in public header */
   it->entry.name = it->efn->name;
   it->entry.offset = it->efn->offset;
   it->entry.size = it->efn->size;
   it->entry.data_size = it->efn->data_size;
   it->entry.compression = it->efn->compression;
   it->entry.ciphered = it->efn->ciphered;
   it->entry.alias = it->efn->alias;

   *data = &it->entry;
   it->efn = it->efn->next;
   return EINA_TRUE;
}

void *
_eet_entries_iterator_container(Eet_Entries_Iterator *it)
{
   return it->ef;
}

void
_eet_entries_iterator_free(Eet_Entries_Iterator *it)
{
   if (it->locked)
     {
        CRI("Iterator still LOCKED !");
        UNLOCK_FILE(it->ef);
     }
}

Eina_Bool
_eet_entries_iterator_lock(Eet_Entries_Iterator *it)
{
   if (it->locked)
     {
        CRI("Iterator already LOCKED !");
        return EINA_TRUE;
     }

   LOCK_FILE(it->ef);
   it->locked = EINA_TRUE;
   return EINA_TRUE;
}

Eina_Bool
_eet_entries_iterator_unlock(Eet_Entries_Iterator *it)
{
   if (!it->locked)
     {
        CRI("Iterator already UNLOCKED !");
        return EINA_TRUE;
     }

   UNLOCK_FILE(it->ef);
   it->locked = EINA_FALSE;
   return EINA_TRUE;
}

EAPI Eina_Iterator *
eet_list_entries(Eet_File *ef)
{
   Eet_Entries_Iterator *it;

   it = malloc(sizeof (Eet_Entries_Iterator));
   if (!it) return NULL;

   EINA_MAGIC_SET(&it->iterator, EINA_MAGIC_ITERATOR);
   it->ef = ef;
   it->efn = NULL;
   it->index = -1;
   it->locked = EINA_FALSE;
   it->iterator.version = EINA_ITERATOR_VERSION;
   it->iterator.next = FUNC_ITERATOR_NEXT(_eet_entries_iterator_next);
   it->iterator.get_container = FUNC_ITERATOR_GET_CONTAINER(_eet_entries_iterator_container);
   it->iterator.free = FUNC_ITERATOR_FREE(_eet_entries_iterator_free);
   it->iterator.lock = FUNC_ITERATOR_LOCK(_eet_entries_iterator_lock);
   it->iterator.unlock = FUNC_ITERATOR_LOCK(_eet_entries_iterator_unlock);

   return &it->iterator;
}

static Eet_File_Node *
find_node_by_name(Eet_File   *ef,
                  const char *name)
{
   Eet_File_Node *efn;
   int hash;

   /* get hash bucket this should be in */
   hash = _eet_hash_gen(name, ef->header->directory->size);

   for (efn = ef->header->directory->nodes[hash]; efn; efn = efn->next)
     {
        if (eet_string_match(efn->name, name))
          return efn;
     }

   return NULL;
}

static Eina_Binbuf *
read_binbuf_from_disk(Eet_File      *ef,
                      Eet_File_Node *efn)
{
   if (efn->data)
     return eina_binbuf_manage_new(efn->data, efn->size, EINA_TRUE);

   if (efn->offset > ef->data_size)
     return 0;

   if (!ef->data)
     return 0;

   if ((efn->offset + efn->size) > ef->data_size)
     return 0;

   return eina_binbuf_manage_new(ef->data + efn->offset, efn->size, EINA_TRUE);
}
