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
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);
+
+/**
* @}
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
+#include <sys/mman.h>
+#include <fcntl.h>
#define PATH_DELIM '/'
#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;
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 :
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
*/
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);
+ }
+}
+
+