eina: add Eina_File API.

NOTE: the purpose of this API is to replace mmap user in the
EFL, share cache and more code across them. The potential user
are eet, evas, efreet, eio and enlil. More patch are needed for
them to use this infra. Help welcome :-)

NOTE2: this API also need more test and is waiting for some
more pthread infra before being thread safe. But at the end
it will be thread safe if eina thread safety is requested.


SVN revision: 58637
This commit is contained in:
Cedric BAIL 2011-04-13 16:15:30 +00:00
parent b5d014dcb7
commit 8a8153bf2d
4 changed files with 494 additions and 1 deletions

View File

@ -47,3 +47,8 @@
* Add eina_inlist_sort.
* Add eina_mempool_repack.
* Add Eina_Object API.
2011-05-13 Cedric Bail & Vincent Torri
* Add Eina_File API.

View File

@ -81,6 +81,15 @@ typedef enum {
EINA_FILE_WHT /**< Whiteout file type (unused on Windows). */
} Eina_File_Type;
typedef struct _Eina_File Eina_File;
typedef enum {
EINA_FILE_RANDOM, /**< Advise random memory access to the mapped memory. */
EINA_FILE_SEQUENTIAL, /**< Advise sequential memory access to the mapped memory. */
EINA_FILE_WILLNEED, /**< Advise need for all the mapped memory. */
EINA_FILE_POPULATE /**< Request all the mapped memory. */
} Eina_File_Populate;
/* Why do this? Well PATH_MAX may vary from when eina itself is compiled
* to when the app using eina is compiled. exposing the path buffer below
* cant safely and portably vary based on how/when you compile. it should
@ -238,6 +247,82 @@ EAPI Eina_Iterator *eina_file_stat_ls(const char *dir) EINA_WARN_UNUSED_RESULT E
*/
EAPI Eina_Iterator *eina_file_direct_ls(const char *dir) EINA_WARN_UNUSED_RESULT EINA_ARG_NONNULL(1) EINA_MALLOC;
/**
* @brief Get a read-only handler to a file.
*
* @param name Filename to open
* @param shared Requested a shm
*
* The file are only open in read only mode. Name should be an absolute path to
* prevent cache mistake. A handler could be shared among multiple instance and
* will be correctly refcounted. File are automatically closed on exec.
*/
EAPI Eina_File *eina_file_open(const char *name, Eina_Bool shared) EINA_WARN_UNUSED_RESULT EINA_ARG_NONNULL(1) EINA_MALLOC;
/**
* @brief Unref file handler.
*
* @param file File handler to unref.
*
* This doesn't close the file, it will remain open until it leave the cache.
*/
EAPI void eina_file_close(Eina_File *file);
/**
* @brief Get file size at open time.
*
* @param file The file handler to request the size from.
* @return The length of the file.
*/
EAPI unsigned long int eina_file_size_get(Eina_File *file);
/**
* @brief Get the last modification time of an open file.
*
* @param file The file handler to request the modification time from.
* @return The last modification time.
*/
EAPI time_t eina_file_mtime_get(Eina_File *file);
/**
* @brief Get the filename of an open file.
*
* @param file The file handler to request the name from.
* @return Stringshared filename of the file.
*/
EAPI const char *eina_file_filename_get(Eina_File *file);
/**
* @brief Map all the file to a buffer.
*
* @param file The file handler to map in memory
* @param rule The rule to apply to the mapped memory
* @return A pointer to a buffer that map all the file content. @c NULL if it fail.
*/
EAPI void *eina_file_map_all(Eina_File *file, Eina_File_Populate rule);
/**
* @brief Map a part of the file.
*
* @param file The file handler to map in memory
* @param rule The rule to apply to the mapped memory
* @param offset The offset inside the file
* @param length The length of the memory to map
* @return A valid pointer to the system memory with @p length valid byte in it. And @c NULL if not inside the file or anything else goes wrong.
*
* This does handle refcounting so it will share map that target the same memory area.
*/
EAPI void *eina_file_map_new(Eina_File *file, Eina_File_Populate rule,
unsigned long int offset, unsigned long int length);
/**
* @brief Unref and unmap memory if possible.
*
* @param file The file handler to unmap memory from.
* @param map Memory map to unref and unmap.
*/
EAPI void eina_file_map_free(Eina_File *file, void *map);
/**
* @}
*/

View File

@ -44,6 +44,8 @@ void *alloca (size_t);
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#define PATH_DELIM '/'
@ -60,15 +62,38 @@ void *alloca (size_t);
#include "eina_safety_checks.h"
#include "eina_file.h"
#include "eina_stringshare.h"
#include "eina_hash.h"
#include "eina_list.h"
/*============================================================================*
* Local *
*============================================================================*/
#ifndef EINA_LOG_COLOR_DEFAULT
#define EINA_LOG_COLOR_DEFAULT EINA_COLOR_CYAN
#endif
#ifdef ERR
#undef ERR
#endif
#define ERR(...) EINA_LOG_DOM_ERR(_eina_file_log_dom, __VA_ARGS__)
#ifdef WRN
#undef WRN
#endif
#define WRN(...) EINA_LOG_DOM_WARN(_eina_file_log_dom, __VA_ARGS__)
#ifdef DBG
#undef DBG
#endif
#define DBG(...) EINA_LOG_DOM_DBG(_eina_file_log_dom, __VA_ARGS__)
/**
* @cond LOCAL
*/
typedef struct _Eina_File_Iterator Eina_File_Iterator;
typedef struct _Eina_File_Map Eina_File_Map;
struct _Eina_File_Iterator
{
Eina_Iterator iterator;
@ -79,6 +104,42 @@ struct _Eina_File_Iterator
char dir[1];
};
struct _Eina_File
{
const char *filename;
Eina_Hash *map;
Eina_Hash *rmap;
void *global_map;
unsigned long length;
time_t mtime;
int refcount;
int global_refcount;
int fd;
Eina_Bool shared : 1;
Eina_Bool delete_me : 1;
};
struct _Eina_File_Map
{
void *map;
unsigned long int offset;
unsigned long int length;
int refcount;
};
static Eina_Hash *_eina_file_cache = NULL;
static Eina_List *_eina_file_cache_lru = NULL;
static Eina_List *_eina_file_cache_delete = NULL;
static int _eina_file_log_dom = -1;
/*
* This complex piece of code is needed due to possible race condition.
* The code and description of the issue can be found at :
@ -310,6 +371,121 @@ _eina_file_stat_ls_iterator_next(Eina_File_Direct_Iterator *it, void **data)
return EINA_TRUE;
}
static void
_eina_file_real_close(Eina_File *file)
{
eina_hash_free(file->rmap);
eina_hash_free(file->map);
if (file->global_map != MAP_FAILED)
munmap(file->global_map, file->length);
close(file->fd);
eina_stringshare_del(file->filename);
free(file);
}
static void
_eina_file_map_close(Eina_File_Map *map)
{
munmap(map->map, map->length);
free(map);
}
static unsigned int
_eina_file_map_key_length(__UNUSED__ const void *key)
{
return sizeof (unsigned long int) * 2;
}
static int
_eina_file_map_key_cmp(const unsigned long int *key1, __UNUSED__ int key1_length,
const unsigned long int *key2, __UNUSED__ int key2_length)
{
if (key1[0] - key2[0] == 0) return key1[1] - key2[1];
return key1[0] - key2[0];
}
static int
_eina_file_map_key_hash(const unsigned long int *key, __UNUSED__ int key_length)
{
return eina_hash_int64(&key[0], sizeof (unsigned long int))
^ eina_hash_int64(&key[1], sizeof (unsigned long int));
}
static void
_eina_file_map_rule_apply(Eina_File_Populate rule, void *addr, unsigned long int size)
{
int flag;
switch (rule)
{
case EINA_FILE_RANDOM: flag = MADV_RANDOM; break;
case EINA_FILE_SEQUENTIAL: flag = MADV_SEQUENTIAL; break;
case EINA_FILE_WILLNEED:
case EINA_FILE_POPULATE:
flag = MADV_WILLNEED;
break;
}
madvise(addr, size, flag);
}
Eina_Bool
eina_file_init(void)
{
_eina_file_log_dom = eina_log_domain_register("eina_file",
EINA_LOG_COLOR_DEFAULT);
if (_eina_file_log_dom < 0)
{
EINA_LOG_ERR("Could not register log domain: eina_file");
return EINA_FALSE;
}
_eina_file_cache = eina_hash_string_djb2_new(EINA_FREE_CB(_eina_file_real_close));
if (!_eina_file_cache)
{
ERR("Could not create cache.");
eina_log_domain_unregister(_eina_file_log_dom);
_eina_file_log_dom = -1;
return EINA_FALSE;
}
return EINA_TRUE;
}
Eina_Bool
eina_file_shutdown(void)
{
Eina_File *f;
Eina_List *l;
EINA_LIST_FREE(_eina_file_cache_delete, f)
_eina_file_real_close(f);
EINA_LIST_FOREACH(_eina_file_cache_lru, l, f)
eina_hash_del(_eina_file_cache, f->filename, f);
if (eina_hash_population(_eina_file_cache) > 0)
{
Eina_Iterator *it;
const char *key;
it = eina_hash_iterator_key_new(_eina_file_cache);
EINA_ITERATOR_FOREACH(it, key)
ERR("File [%s] still open !", key);
eina_iterator_free(it);
}
eina_hash_free(_eina_file_cache);
eina_log_domain_unregister(_eina_file_log_dom);
_eina_file_log_dom = -1;
return EINA_FALSE;
}
/**
* @endcond
*/
@ -527,3 +703,228 @@ eina_file_stat_ls(const char *dir)
return &it->iterator;
}
EAPI Eina_File *
eina_file_open(const char *filename, Eina_Bool shared)
{
Eina_File *file;
Eina_File *n;
struct stat file_stat;
int fd;
Eina_Bool create = EINA_FALSE;
/* FIXME: always open absolute path (need to fix filename according to current
directory) */
if (shared)
fd = shm_open(filename, O_RDONLY | O_CLOEXEC, ACCESSPERMS);
else
fd = open(filename, O_RDONLY | O_CLOEXEC, ACCESSPERMS);
if (fd < 0) return NULL;
if (fstat(fd, &file_stat))
{
close(fd);
return NULL;
}
file = eina_hash_find(_eina_file_cache, filename);
if (file && (file->mtime != file_stat.st_mtime
|| file->length != file_stat.st_size))
{
create = EINA_TRUE;
if (file->refcount == 0)
{
_eina_file_cache_lru = eina_list_prepend(_eina_file_cache_lru, file);
eina_hash_del(_eina_file_cache, file->filename, file);
file = NULL;
}
else if (!file->delete_me)
{
file->delete_me = EINA_TRUE;
_eina_file_cache_delete = eina_list_prepend(_eina_file_cache_delete, file);
}
}
if (!file || create)
{
n = malloc(sizeof (Eina_File));
if (!n) goto on_error;
n->filename = eina_stringshare_add(filename);
n->map = eina_hash_new(EINA_KEY_LENGTH(_eina_file_map_key_length),
EINA_KEY_CMP(_eina_file_map_key_cmp),
EINA_KEY_HASH(_eina_file_map_key_hash),
EINA_FREE_CB(_eina_file_map_close),
3);
n->rmap = eina_hash_pointer_new(NULL);
n->global_map = MAP_FAILED;
n->length = file_stat.st_size;
n->mtime = file_stat.st_mtime;
n->refcount = 0;
n->fd = fd;
n->shared = shared;
n->delete_me = EINA_FALSE;
eina_hash_set(_eina_file_cache, filename, n);
}
else
{
close(fd);
n = file;
if (n->refcount == 0)
_eina_file_cache_lru = eina_list_remove(_eina_file_cache_lru, n);
}
n->refcount++;
return n;
on_error:
close(fd);
return NULL;
}
EAPI void
eina_file_close(Eina_File *file)
{
file->refcount--;
if (file->refcount != 0) return ;
if (file->delete_me)
{
_eina_file_cache_delete = eina_list_remove(_eina_file_cache_delete, file);
_eina_file_real_close(file);
}
else
{
_eina_file_cache_lru = eina_list_prepend(_eina_file_cache_lru, file);
}
}
EAPI unsigned long int
eina_file_size_get(Eina_File *file)
{
return file->length;
}
EAPI time_t
eina_file_mtime_get(Eina_File *file)
{
return file->mtime;
}
EAPI const char *
eina_file_filename_get(Eina_File *file)
{
return file->filename;
}
EAPI void *
eina_file_map_all(Eina_File *file, Eina_File_Populate rule)
{
int flags = MAP_SHARED;
if (rule == EINA_FILE_POPULATE) flags |= MAP_POPULATE;
if (file->global_map == MAP_FAILED)
file->global_map = mmap(NULL, file->length, PROT_READ, flags, file->fd, 0);
if (file->global_map != MAP_FAILED)
{
_eina_file_map_rule_apply(rule, file->global_map, file->length);
file->global_refcount++;
return file->global_map;
}
return NULL;
}
EAPI void *
eina_file_map_new(Eina_File *file, Eina_File_Populate rule,
unsigned long int offset, unsigned long int length)
{
Eina_File_Map *map;
unsigned long int key[2];
if (offset > file->length)
return NULL;
if (offset + length > file->length)
return NULL;
if (offset == 0 && length == file->length)
return eina_file_map_all(file, rule);
key[0] = offset;
key[1] = length;
map = eina_hash_find(file->map, &key);
if (!map)
{
int flags = MAP_SHARED;
if (rule == EINA_FILE_POPULATE) flags |= MAP_POPULATE;
map = malloc(sizeof (Eina_File_Map));
if (!map) return NULL;
map->map = mmap(NULL, length, PROT_READ, flags, file->fd, offset);
map->offset = offset;
map->length = length;
map->refcount = 0;
if (map->map == MAP_FAILED)
{
free(map);
return NULL;
}
eina_hash_add(file->map, &key, map);
eina_hash_direct_add(file->rmap, map->map, map);
}
map->refcount++;
_eina_file_map_rule_apply(rule, map->map, length);
return map->map;
}
EAPI void
eina_file_map_free(Eina_File *file, void *map)
{
if (file->global_map == map)
{
file->global_refcount--;
if (file->global_refcount > 0) return ;
munmap(file->global_map, file->length);
file->global_map = MAP_FAILED;
}
else
{
Eina_File_Map *em;
unsigned long int key[2];
em = eina_hash_find(file->rmap, &map);
if (!em) return ;
em->refcount--;
if (em->refcount > 0) return ;
key[0] = em->offset;
key[1] = em->length;
eina_hash_del(file->rmap, &map, em);
eina_hash_del(file->map, &key, em);
}
}

View File

@ -124,6 +124,7 @@ static HANDLE _mutex = NULL;
S(ustrbuf);
S(quadtree);
S(simple_xml);
S(file);
#undef S
struct eina_desc_setup
@ -156,7 +157,8 @@ static const struct eina_desc_setup _eina_desc_setup[] = {
S(strbuf),
S(ustrbuf),
S(quadtree),
S(simple_xml)
S(simple_xml),
S(file)
#undef S
};
static const size_t _eina_desc_setup_len = sizeof(_eina_desc_setup) /