/* Chrysalide - Outil d'analyse de fichiers binaires
 * storage.c - conservation hors mémoire d'objets choisis
 *
 * Copyright (C) 2020-2025 Cyrille Bagard
 *
 *  This file is part of Chrysalide.
 *
 *  Chrysalide is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Chrysalide is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
 */


#include "storage.h"


#include <assert.h>
#include <malloc.h>
#include <string.h>
#include <unistd.h>
#include <zip.h>


#include "storage-int.h"
#include "../common/cpp.h"
#include "../common/pathname.h"
#include "../core/logs.h"


/**
 * Historique du format :
 *
 *   - 09/03/25 : 1.0 (version initiale)
 *
 */
#define STORAGE_MAGIC "COBSTR"
#define STORAGE_NUMBER "\x01\x00"


/* Initialise la classe des conservations d'objets en place. */
static void g_object_storage_class_init(GObjectStorageClass *);

/* Initialise une instance de conservation d'objets en place. */
static void g_object_storage_init(GObjectStorage *);

/* Supprime toutes les références externes. */
static void g_object_storage_dispose(GObject *);

/* Procède à la libération totale de la mémoire. */
static void g_object_storage_finalize(GObject *);

/* Assure l'inexistence d'un groupe avec un nom donné. */
static bool g_object_storage_has_no_backend_named(GObjectStorage *, const char *);

/* Retrouve l'encadrement pour un nouveau groupe d'objets. */
static storage_backend_t *g_object_storage_find_backend(GObjectStorage *, const char *);

/* Ajoute le support d'un nouveau groupe d'objets construits. */
static bool g_object_storage_add_backend(GObjectStorage *, const char *, storage_backend_t **);

/* Charge un objet à partir de données rassemblées. */
static GSerializableObject *g_object_storage_load_object_unlocked(GObjectStorage *, const char *, off64_t);



/* Indique le type défini pour une conservation d'objets construits. */
G_DEFINE_TYPE(GObjectStorage, g_object_storage, G_TYPE_OBJECT);


/******************************************************************************
*                                                                             *
*  Paramètres  : klass = classe à initialiser.                                *
*                                                                             *
*  Description : Initialise la classe des conservations d'objets en place.    *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_object_storage_class_init(GObjectStorageClass *klass)
{
    GObjectClass *object;                   /* Autre version de la classe  */

    object = G_OBJECT_CLASS(klass);

    object->dispose = g_object_storage_dispose;
    object->finalize = g_object_storage_finalize;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : storage = instance à initialiser.                            *
*                                                                             *
*  Description : Initialise une instance de conservation d'objets en place.   *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_object_storage_init(GObjectStorage *storage)
{
    init_sized_binary(&storage->type);
    storage->version = 0;

    init_sized_binary(&storage->uid);

    storage->tpmem = g_type_memory_new();

    storage->backends = NULL;
    storage->count = 0;
    g_mutex_init(&storage->mutex);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : object = instance d'objet GLib à traiter.                    *
*                                                                             *
*  Description : Supprime toutes les références externes.                     *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_object_storage_dispose(GObject *object)
{
    GObjectStorage *storage;                /* Version spécialisée         */

    storage = G_OBJECT_STORAGE(object);

    g_clear_object(&storage->tpmem);

    G_OBJECT_CLASS(g_object_storage_parent_class)->dispose(object);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : object = instance d'objet GLib à traiter.                    *
*                                                                             *
*  Description : Procède à la libération totale de la mémoire.                *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_object_storage_finalize(GObject *object)
{
    GObjectStorage *storage;                /* Version spécialisée         */
    size_t i;                               /* Boucle de parcours          */
    storage_backend_t *backend;             /* Gestionnaire à manipuler    */
    int ret;                                /* Bilan d'un appel            */

    storage = G_OBJECT_STORAGE(object);

    g_mutex_lock(&storage->mutex);

    for (i = 0; i < storage->count; i++)
    {
        backend = &storage->backends[i];

        /**
         * Chargement incomplet depuis g_object_storage_load().
         */
        if (backend->name == NULL)
            break;

        if (backend->fd != -1)
            close(backend->fd);
        else
            assert(false);

        ret = access(backend->filename, W_OK);
        if (ret == 0)
        {
            ret = unlink(backend->filename);
            if (ret != 0) LOG_ERROR_N("unlink");
        }

        free(backend->name);

        free(backend->filename);

    }

    if (storage->backends != NULL)
        free(storage->backends);

    g_mutex_unlock(&storage->mutex);

    g_mutex_clear(&storage->mutex);

    exit_sized_binary(&storage->type);

    exit_sized_binary(&storage->uid);

    G_OBJECT_CLASS(g_object_storage_parent_class)->finalize(object);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : type    = type global à indiquer dans une conservation.      *
*                version = numéro de version associé.                         *
*                uid     = identifiant arbitraire mais unique pour distinguer.*
*                                                                             *
*  Description : Crée le support d'une conservation d'objets en place.        *
*                                                                             *
*  Retour      : Mécanismes mis en place.                                     *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GObjectStorage *g_object_storage_new(const char *type, uint8_t version, const char *uid)
{
    GObjectStorage *result;                 /* Structure à retourner       */

    result = g_object_new(G_TYPE_OBJECT_STORAGE, NULL);

    if (!g_object_storage_create(result, type, version, uid))
        g_clear_object(&result);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : storage = stockage d'objets à initialiser.                   *
*                type    = type global à indiquer dans une conservation.      *
*                version = numéro de version associé.                         *
*                uid     = identifiant arbitraire mais unique pour distinguer.*
*                                                                             *
*  Description : Met en place un support d'une conservation d'objets en place.*
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool g_object_storage_create(GObjectStorage *storage, const char *type, uint8_t version, const char *uid)
{
    bool result;                            /* Bilan à retourner           */

    result = true;

    dup_into_sized_binary(&storage->type, type, strlen(type));

    storage->version = version;

    dup_into_sized_binary(&storage->uid, uid, strlen(uid) + 1);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : filename = fichier de source à traiter.                      *
*                                                                             *
*  Description : Charge le support d'une conservation d'objets en place.      *
*                                                                             *
*  Retour      : Gestionnaire de conservations construit ou NULL si erreur.   *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GObjectStorage *g_object_storage_load(const char *filename)
{
    GObjectStorage *result;                 /* Structure à retourner       */
    GObjectStorage *storage;                /* Structure en construction   */
    int err;                                /* Eventuel code d'erreur      */
    zip_t *archive;                         /* Archive ZIP à manipuler     */
    zip_error_t error;                      /* Suivi des erreurs obtenues  */
    char *tpmem_filename;                   /* Chemin d'accès pour types   */
    zip_int64_t entries_count;              /* Nombre d'éléments ZIP       */
    void *data;                             /* Données (décompressées)     */
    zip_stat_t stats;                       /* Information sur les données */
    zip_file_t *file;                       /* Echantillon à extraire      */
    zip_int64_t got;                        /* Nombre d'octets lus         */
    int ret;                                /* Bilan d'un appel            */
    const void *pos;                        /* Tête de lecture             */
    const void *max;                        /* Fin des données lisibles    */
    bool status;                            /* Bilan d'une extraction      */
    char *prefix;                           /* Début de nom de fichier     */
    int fd;                                 /* Descripteur de flux ouvert  */
    off_t moved;                            /* Nouvelle position établie   */
    zip_int64_t i;                          /* Boucle de parcours          */
    storage_backend_t *backend;             /* Informations à intégrer     */
    const char *slash;                      /* Pointeur vers un caractère /*/

    result = NULL;

    storage = g_object_new(G_TYPE_OBJECT_STORAGE, NULL);

    archive = zip_open(filename, ZIP_RDONLY, &err);
    if (archive == NULL)
    {
        zip_error_init_with_code(&error, err);
        LOG_ERROR_ZIP("zip_open", &error);
        goto exit;
    }

    zip_error_init(&error);

    tpmem_filename = NULL;

    /* Validation du nombre d'entrées */

    entries_count = zip_get_num_entries(archive,  ZIP_FL_UNCHANGED);

    if (entries_count < 2)
        goto exit_with_archive;

    data = NULL;

    /* Extraction de la partie de contrôle */

    ret = zip_stat_index(archive, 0, ZIP_FL_UNCHANGED, &stats);
    if (ret != 0)
    {
        LOG_ERROR_ZIP("zip_stat_index", zip_get_error(archive));
        goto exit_with_archive;
    }

    if ((stats.valid & (ZIP_STAT_NAME | ZIP_STAT_SIZE)) != (ZIP_STAT_NAME | ZIP_STAT_SIZE))
        goto exit_with_archive;

    if (strcmp(stats.name, "control") != 0)
        goto exit_with_archive;

    if (stats.size < (6 + 2 + 1 + 1 + 1))
        goto exit_with_archive;

    file = zip_fopen_index(archive, 0, ZIP_FL_UNCHANGED);
    if (file == NULL)
    {
        LOG_ERROR_ZIP("zip_fopen_index", zip_get_error(archive));
        goto exit_with_archive;
    }

    data = malloc(stats.size);

    got = zip_fread(file, data, stats.size);

    ret = zip_fclose(file);
    if (ret != 0)
    {
        zip_error_set(&error, ret, 0);
        LOG_ERROR_ZIP("zip_fclose", &error);
        goto exit_with_data;
    }

    if (got != stats.size)
        goto exit_with_data;

    if (memcmp(data, STORAGE_MAGIC, 6) != 0)
        goto exit_with_data;

    if (memcmp(((uint8_t *)data) + 6, STORAGE_NUMBER, 2) != 0)
        goto exit_with_data;

    pos = (uint8_t *)data + 8;
    max = (uint8_t *)data + got;

    status = unpack_sized_binary(&storage->type, &pos, max);
    if (!status) goto exit_with_data;

    if (pos >= max)
        goto exit_with_data;

    storage->version = *(uint8_t *)pos;
    pos = (uint8_t *)pos + 1;

    unpack_sized_binary_as_string(&storage->uid, &pos, max);
    if (!status) goto exit_with_data;

    if (pos != max)
        goto exit_with_data;

    free(data);
    data = NULL;

    /* Extraction de la conservation des types */

    ret = zip_stat_index(archive, 1, ZIP_FL_UNCHANGED, &stats);
    if (ret != 0)
    {
        LOG_ERROR_ZIP("zip_stat_index", zip_get_error(archive));
        goto exit_with_archive;
    }

    if ((stats.valid & (ZIP_STAT_NAME | ZIP_STAT_SIZE)) != (ZIP_STAT_NAME | ZIP_STAT_SIZE))
        goto exit_with_archive;

    if (strcmp(stats.name, "types") != 0)
        goto exit_with_archive;

    file = zip_fopen_index(archive, 1, ZIP_FL_UNCHANGED);
    if (file == NULL)
    {
        LOG_ERROR_ZIP("zip_fopen_index", zip_get_error(archive));
        goto exit_with_archive;
    }

    data = malloc(stats.size);

    got = zip_fread(file, data, stats.size);

    ret = zip_fclose(file);
    if (ret != 0)
    {
        zip_error_set(&error, ret, 0);
        LOG_ERROR_ZIP("zip_fclose", &error);
        goto exit_with_data;
    }

    asprintf(&prefix, "%s-types", storage->uid.static_data);

    fd = make_tmp_file(prefix, "cache", &tpmem_filename);

    free(prefix);

    if (fd == -1)
        goto exit_with_data;

    status = safe_write(fd, data, stats.size);
    if (!status)
    {
        close(fd);
        goto exit_with_data;
    }

    moved = lseek(fd, 0, SEEK_SET);
    if (moved == ((off_t)-1))
    {
        LOG_ERROR_N("lseek");
        close(fd);
        goto exit_with_data;
    }

    status = g_type_memory_load(storage->tpmem, fd);

    close(fd);

    if (!status)
        goto exit_with_data;

    free(data);
    data = NULL;

    /* Extraction des différents objects restants */

    if (entries_count > 2)
    {
        storage->count = entries_count - 2;
        storage->backends = calloc(storage->count, sizeof(storage_backend_t));

        for (i = 2; i < entries_count; i++)
        {
            backend = &storage->backends[i - 2];

            ret = zip_stat_index(archive, i, ZIP_FL_UNCHANGED, &stats);
            if (ret != 0)
            {
                LOG_ERROR_ZIP("zip_stat_index", zip_get_error(archive));
                goto exit_with_archive;
            }

            if ((stats.valid & (ZIP_STAT_NAME | ZIP_STAT_SIZE)) != (ZIP_STAT_NAME | ZIP_STAT_SIZE))
                goto exit_with_archive;

            if (strncmp(stats.name, SL("backends/")) != 0)
                goto exit_with_archive;

            slash = strchr(stats.name, '/');

            if (slash == NULL)
                goto exit_with_archive;

            if (strchr(slash + 1, '/') != NULL)
                goto exit_with_archive;

            if (!g_object_storage_has_no_backend_named(storage, slash + 1))
                goto exit_with_archive;

            file = zip_fopen_index(archive, i, ZIP_FL_UNCHANGED);
            if (file == NULL)
            {
                LOG_ERROR_ZIP("zip_fopen_index", zip_get_error(archive));
                goto exit_with_archive;
            }

            data = malloc(stats.size);

            got = zip_fread(file, data, stats.size);

            ret = zip_fclose(file);
            if (ret != 0)
            {
                zip_error_set(&error, ret, 0);
                LOG_ERROR_ZIP("zip_fclose", &error);
                goto exit_with_data;
            }

            backend->name = strdup(slash + 1);

            asprintf(&prefix, "%s-%s", storage->uid.static_data, backend->name);

            backend->fd = make_tmp_file(prefix, "cache", &backend->filename);

            free(prefix);

            if (backend->fd == -1)
                goto exit_with_data;

            status = safe_write(backend->fd, data, stats.size);
            if (!status) goto exit_with_data;

            moved = lseek(backend->fd, 0, SEEK_SET);
            if (moved == ((off_t)-1))
            {
                LOG_ERROR_N("lseek");
                goto exit_with_data;
            }

            free(data);
            data = NULL;

        }

    }

    /* Clôture des opérations */

    result = storage;
    ref_object(storage);

 exit_with_data:

    if (data != NULL)
        free(data);

 exit_with_archive:

    ret = zip_close(archive);

    if (ret != 0)
        LOG_ERROR_ZIP("zip_close", zip_get_error(archive));

    if (tpmem_filename != NULL)
        unlink(tpmem_filename);

    zip_error_fini(&error);

 exit:

    unref_object(storage);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : storage  = gestionnaire de conservations à manipuler.        *
*                filename = fichier de destination à constituer.              *
*                                                                             *
*  Description : Sauvegarde le support d'une conservation d'objets en place.  *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool g_object_storage_store(GObjectStorage *storage, const char *filename)
{
    bool result;                            /* Bilan à retourner           */
    int err;                                /* Eventuel code d'erreur      */
    zip_t *archive;                         /* Archive ZIP à manipuler     */
    zip_error_t error;                      /* Suivi des erreurs obtenues  */
    char *tpmem_filename;                   /* Chemin d'accès pour types   */
    void *type_buf;                         /* Données pour le type        */
    size_t type_buflen;                     /* Quantité de ces données     */
    void *uid_buf;                          /* Données pour l'identifiant  */
    size_t uid_buflen;                      /* Quantité de ces données     */
    size_t control_len;                     /* Taille des premières données*/
    uint8_t *control;                       /* Premières données du fichier*/
    zip_source_t *zip_data;                 /* Données ZIP à intégrer      */
    zip_int64_t index;                      /* Nouvel index du contenu     */
    int ret;                                /* Bilan d'un appel            */
    char *prefix;                           /* Début de nom de fichier     */
    int fd;                                 /* Descripteur de flux ouvert  */
    bool status;                            /* Bilan d'une écriture        */
    size_t i;                               /* Boucle de parcours          */
    char *zip_name;                         /* Destination pour l'archive  */

    result = false;

    archive = zip_open(filename, ZIP_CREATE | ZIP_TRUNCATE, &err);
    if (archive == NULL)
    {
        zip_error_init_with_code(&error, err);
        LOG_ERROR_ZIP("zip_open", &error);
        goto exit;
    }

    zip_error_init(&error);

    tpmem_filename = NULL;

    /* Fichier de contrôle */

    type_buf = pack_sized_binary(&storage->type, &type_buflen);

    uid_buf = pack_sized_binary_as_string(&storage->uid, &uid_buflen);

    assert((sizeof(STORAGE_MAGIC) - 1 + sizeof(STORAGE_NUMBER) - 1) == 8);

    control_len = 8 + type_buflen + 1 + uid_buflen;
    control = malloc(control_len * sizeof(uint8_t));

    memcpy(control, STORAGE_MAGIC, 6);
    memcpy(control + 6, STORAGE_NUMBER, 2);

    memcpy(control + 8, type_buf, type_buflen);

    control[8 + type_buflen] = storage->version;

    memcpy(control + 8 + type_buflen + 1, uid_buf, uid_buflen);

    zip_data = zip_source_buffer_create(control, control_len, 0, &error);
    if (zip_data == NULL)
    {
        LOG_ERROR_ZIP("zip_source_buffer_create", &error);
        goto exit_with_control;
    }

    index = zip_file_add(archive, "control", zip_data, ZIP_FL_ENC_UTF_8);
    if (index == -1)
    {
        zip_source_free(zip_data);
        LOG_ERROR_ZIP("zip_file_add", zip_get_error(archive));
        goto exit_with_control;
    }

    ret = zip_set_file_compression(archive, index, ZIP_CM_STORE, 0 /* comp_flags */);
    if (ret == -1)
    {
        LOG_ERROR_ZIP("zip_set_file_compression", zip_get_error(archive));
        goto exit_with_control;
    }

    /* Composants de la conservation */

    g_mutex_lock(&storage->mutex);

    /* Conservation des types */

    asprintf(&prefix, "%s-types", storage->uid.static_data);

    fd = make_tmp_file(prefix, "cache", &tpmem_filename);

    free(prefix);

    if (fd == -1)
        goto exit_with_lock;

    status = g_type_memory_store(storage->tpmem, fd);

    close(fd);

    if (!status)
        goto exit_with_lock;

    zip_data = zip_source_file_create(tpmem_filename, 0, -1 /* ZIP_LENGTH_TO_END */, &error);
    if (zip_data == NULL)
    {
        LOG_ERROR_ZIP("zip_source_file_create", &error);
        goto exit_with_lock;
    }

    index = zip_file_add(archive, "types", zip_data, ZIP_FL_ENC_UTF_8);
    if (index == -1)
    {
        zip_source_free(zip_data);
        LOG_ERROR_ZIP("zip_file_add", zip_get_error(archive));
        goto exit_with_lock;
    }

    ret = zip_set_file_compression(archive, index, ZIP_CM_DEFLATE, 9 /* comp_flags */);
    if (ret == -1)
    {
        LOG_ERROR_ZIP("zip_set_file_compression", zip_get_error(archive));
        goto exit_with_lock;
    }

    /* Conservation des objets */

    for (i = 0; i < storage->count; i++)
    {
        zip_data = zip_source_file_create(storage->backends[i].filename, 0, -1 /* ZIP_LENGTH_TO_END */, &error);
        if (zip_data == NULL)
        {
            LOG_ERROR_ZIP("zip_source_file_create", &error);
            goto exit_with_lock;
        }

        /**
         * Pas besoin de distinguer les chemins UNIX et Windows ici.
         *
         * Cf. https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT :
         *
         * 4.4.17 file name: (Variable)
         *
         *    The path stored MUST NOT contain a drive or
         *    device letter, or a leading slash.  All slashes
         *    MUST be forward slashes '/' as opposed to
         *    backwards slashes '\' for compatibility with Amiga
         *    and UNIX file systems etc.  If input came from standard
         *    input, there is no file name field.
         *
         */

        asprintf(&zip_name, "backends/%s", storage->backends[i].name);

        index = zip_file_add(archive, zip_name, zip_data, ZIP_FL_ENC_UTF_8);

        free(zip_name);

        if (index == -1)
        {
            zip_source_free(zip_data);
            LOG_ERROR_ZIP("zip_file_add", zip_get_error(archive));
            goto exit_with_lock;
        }

        ret = zip_set_file_compression(archive, index, ZIP_CM_DEFLATE, 9 /* comp_flags */);
        if (ret == -1)
        {
            LOG_ERROR_ZIP("zip_set_file_compression", zip_get_error(archive));
            goto exit_with_lock;
        }

    }

    result = true;

    /* Clôture des opérations */

 exit_with_lock:

    g_mutex_unlock(&storage->mutex);

 exit_with_control:

    ret = zip_close(archive);

    if (ret != 0)
        LOG_ERROR_ZIP("zip_close", zip_get_error(archive));

    free(control);

    if (tpmem_filename != NULL)
        unlink(tpmem_filename);

    zip_error_fini(&error);

 exit:

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : storage = gestionnaire de conservations à consulter.         *
*                name    = désignation d'un nouveau groupe d'objets.          *
*                                                                             *
*  Description : Assure l'inexistence d'un groupe avec un nom donné.          *
*                                                                             *
*  Retour      : Bilan des recherches.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool g_object_storage_has_no_backend_named(GObjectStorage *storage, const char *name)
{
    bool result;                            /* Bilan à retourner           */
    size_t i;                               /* Boucle de parcours          */

    result = true;

    for (i = 0; i < storage->count && result; i++)
    {
        if (storage->backends[i].name == NULL)
            break;

        if (strcmp(storage->backends[i].name, name) == 0)
            result = false;

    }

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : storage = gestionnaire de conservations à compléter.         *
*                name    = désignation d'un nouveau groupe d'objets.          *
*                                                                             *
*  Description : Retrouve l'encadrement pour un nouveau groupe d'objets.      *
*                                                                             *
*  Retour      : Informations liées à un groupe ou NULL en cas d'échec.       *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static storage_backend_t *g_object_storage_find_backend(GObjectStorage *storage, const char *name)
{
    storage_backend_t *result;              /* Encadrement à retourner     */
    size_t i;                               /* Boucle de parcours          */

    assert(!g_mutex_trylock(&storage->mutex));

    for (i = 0; i < storage->count; i++)
        if (strcmp(storage->backends[i].name, name) == 0)
            break;

    if (i == storage->count)
        result = NULL;
    else
        result = &storage->backends[i];

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : storage = gestionnaire de conservations à compléter.         *
*                name    = désignation d'un nouveau groupe d'objets.          *
*                backend = support mis en place pour les enregistrements.     *
*                                                                             *
*  Description : Ajoute le support d'un nouveau groupe d'objets construits.   *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool g_object_storage_add_backend(GObjectStorage *storage, const char *name, storage_backend_t **backend)
{
    bool result;                            /* Bilan à retourner           */
    char *prefix;                           /* Début de nom de fichier     */
    char *filename;                         /* Chemin d'accès aux données  */
    int fd;                                 /* Descripteur de flux ouvert  */

    result = false;

    *backend = NULL;

    assert(!g_mutex_trylock(&storage->mutex));

    if (g_object_storage_find_backend(storage, name) != NULL)
        goto exit;

    /* Préparatifs */

    asprintf(&prefix, "%s-%s", storage->uid.static_data, name);

    fd = make_tmp_file(prefix, "cache", &filename);

    free(prefix);

    if (fd == -1)
        goto exit;

    /* Inscription en bonne et due forme */

    storage->backends = realloc(storage->backends, ++storage->count * sizeof(storage_backend_t));

    *backend = &storage->backends[storage->count - 1];

    (*backend)->name = strdup(name);

    (*backend)->filename = filename;
    (*backend)->fd = fd;

    result = true;

 exit:

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : storage = gestionnaire à manipuler.                          *
*                name    = désignation d'un groupe d'objets à consulter.      *
*                pos     = tête de lecture avant écriture.                    *
*                                                                             *
*  Description : Charge un objet à partir de données rassemblées.             *
*                                                                             *
*  Retour      : Objet restauré en mémoire ou NULL en cas d'échec.            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static GSerializableObject *g_object_storage_load_object_unlocked(GObjectStorage *storage, const char *name, off64_t pos)
{
    GSerializableObject *result;            /* Instance à retourner        */
    storage_backend_t *backend;             /* Informations à consulter    */
    off64_t new;                            /* Nouvelle position de lecture*/
    bool status;                            /* Bilan d'une opération       */

    result = NULL;

    assert(!g_mutex_trylock(&storage->mutex));

    /* Chargement */

    backend = g_object_storage_find_backend(storage, name);
    if (backend == NULL) goto exit;

    new = lseek64(backend->fd, pos, SEEK_SET);
    if (new == (off_t)-1)
    {
        LOG_ERROR_N("lseek64");
        goto exit;
    }

    assert (new == pos);

    /* Phase de conversion */

    result = G_SERIALIZABLE_OBJECT(g_type_memory_create_object_from_gtype(storage->tpmem, backend->fd));

    if (result)
    {
        status = g_serializable_object_load(result, storage, backend->fd);

        if (!status)
            g_clear_object(&result);

    }

 exit:

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : storage = gestionnaire à manipuler.                          *
*                name    = désignation d'un groupe d'objets à consulter.      *
*                pos     = tête de lecture avant écriture.                    *
*                                                                             *
*  Description : Charge un objet à partir de données rassemblées.             *
*                                                                             *
*  Retour      : Objet restauré en mémoire ou NULL en cas d'échec.            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GSerializableObject *g_object_storage_load_object(GObjectStorage *storage, const char *name, off64_t pos)
{
    GSerializableObject *result;            /* Instance à retourner        */

    g_mutex_lock(&storage->mutex);

    result = g_object_storage_load_object_unlocked(storage, name, pos);

    g_mutex_unlock(&storage->mutex);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : storage = gestionnaire à manipuler.                          *
*                fd      = flux de données de l'objet courant.                *
*                name    = désignation du groupe de l'objets à extraire.      *
*                                                                             *
*  Description : Charge un objet interne à partir d'une référence embarquée.  *
*                                                                             *
*  Retour      : Objet restauré en mémoire ou NULL en cas d'échec.            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GSerializableObject *g_object_storage_unpack_object(GObjectStorage *storage, int fd, const char *name)
{
    GSerializableObject *result;            /* Instance à retourner        */
    storage_backend_t *backend;             /* Informations à consulter    */
    uleb128_t pos;                          /* Localisation des données    */
    bool status;                            /* Bilan d'une opération       */
    off64_t saved;                          /* Sauvegarde de position      */
    off64_t new;                            /* Nouvelle position de lecture*/

    result = NULL;

    g_mutex_lock(&storage->mutex);

    /* Récupération de la position */

    backend = g_object_storage_find_backend(storage, name);
    if (backend == NULL) goto exit;

    status = load_uleb128(&pos, backend->fd);
    if (!status) goto exit;

    saved = lseek64(backend->fd, 0, SEEK_CUR);
    if (saved == (off_t)-1)
    {
        LOG_ERROR_N("lseek64");
        goto exit;
    }

    /* Chargement */

    result = g_object_storage_load_object_unlocked(storage, name, pos);

    if (result == NULL) goto exit;

    /* Restauration de la position courante */

    new = lseek64(backend->fd, saved, SEEK_SET);
    if (new == (off_t)-1)
    {
        LOG_ERROR_N("lseek64");

        g_clear_object(&result);
        goto exit;

    }

    assert (new == saved);

 exit:

    g_mutex_unlock(&storage->mutex);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : storage = gestionnaire à manipuler.                          *
*                name    = désignation d'un groupe d'objets, nouveau ou non.  *
*                object  = objet sérialisable à traiter.                      *
*                pos     = tête de lecture avant écriture. [OUT]              *
*                                                                             *
*  Description : Sauvegarde un object sous forme de données rassemblées.      *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool g_object_storage_store_object(GObjectStorage *storage, const char *name, const GSerializableObject *object, off64_t *pos)
{
    bool result;                            /* Bilan à retourner           */
    storage_backend_t *backend;             /* Informations à consulter    */
    off64_t tmp;                            /* Conservation éphémère       */

    result = false;

    g_mutex_lock(&storage->mutex);

    backend = g_object_storage_find_backend(storage, name);

    if (backend == NULL)
        g_object_storage_add_backend(storage, name, &backend);

    if (backend != NULL)
    {
        if (pos == NULL)
            pos = &tmp;

        *pos = lseek64(backend->fd, 0, SEEK_CUR);

        if (*pos != (off64_t)-1)
        {
            result = g_type_memory_store_object_gtype(storage->tpmem, G_OBJECT(object), backend->fd);

            if (result)
                result = g_serializable_object_store(object, storage, backend->fd);

        }

    }

    g_mutex_unlock(&storage->mutex);

    return result;

}