You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2836 lines
72 KiB

#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 <fnmatch.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);
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)
{
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)
{
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;
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);
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); \