/* Chrysalide - Outil d'analyse de fichiers binaires
 * storage.c - conservation hors mémoire vive des instructions désassemblées
 *
 * Copyright (C) 2018 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 <fcntl.h>
#include <malloc.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>


#include "instruction.h"
#include "operands/target.h"
#include "../common/compression.h"
#include "../common/extstr.h"
#include "../common/pathname.h"
#include "../common/xdg.h"
#include "../core/global.h"
#include "../core/logs.h"
#include "../core/queue.h"
#include "../glibext/delayed-int.h"



/* ----------------- CONSERVATION EXTERNE DES INSTRUCTIONS CHARGEES ----------------- */


#define G_TYPE_INS_CACHING            g_ins_caching_get_type()
#define G_INS_CACHING(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), G_TYPE_INS_CACHING, GInsCaching))
#define G_IS_INS_CACHING(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), G_TYPE_INS_CACHING))
#define G_INS_CACHING_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), G_TYPE_INS_CACHING, GInsCachingClass))
#define G_IS_INS_CACHING_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), G_TYPE_INS_CACHING))
#define G_INS_CACHING_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), G_TYPE_INS_CACHING, GInsCachingClass))


/* Ensembles binaires à désassembler (instance) */
typedef struct _GInsCaching
{
    GDelayedWork parent;                    /* A laisser en premier        */

    GArchProcessor *proc;                   /* Ensemble à traiter          */
    GAsmStorage *storage;                   /* Cache de destinartion       */

    GBinFormat *format;                     /* Nature de l'opération       */

    bool status;                            /* Bilan de l'opération        */

} GInsCaching;

/* Ensembles binaires à désassembler (classe) */
typedef struct _GInsCachingClass
{
    GDelayedWorkClass parent;               /* A laisser en premier        */

} GInsCachingClass;


/* Indique le type défini pour les tâches d'enregistrement des instructions. */
GType g_ins_caching_get_type(void);

/* initialise la classe des tâches de cache d'instructions. */
static void g_ins_caching_class_init(GInsCachingClass *);

/* Initialise une tâche de cache d'instructions. */
static void g_ins_caching_init(GInsCaching *);

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

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

/* Crée une tâche de mise en cache de toutes les instructions. */
static GInsCaching *g_ins_caching_new(GArchProcessor *, GAsmStorage *, GBinFormat *);

/* Assure la conservation ou le chargement d'instructions. */
static void g_ins_caching_process(GInsCaching *, GtkStatusStack *);

/* Assure le chargement d'instructions en différé. */
static void g_ins_caching_process_load(GInsCaching *, GtkStatusStack *);

/* Assure la conservation d'instructions en différé. */
static void g_ins_caching_process_store(GInsCaching *, GtkStatusStack *);

/* Fournit le bilan des traitements d'instructions en différé. */
static bool g_ins_caching_get_status(const GInsCaching *);



/* ------------------- MECANISME DE SAUVEGARDE ET DE RESTAURATION ------------------- */


/* Conservation d'une référence sur un type */
typedef struct _gtype_ref_info_t
{
    GType gtype;                            /* Type pour la GLib           */
    gpointer gclass;                        /* Lien vers sa classe         */

} gtype_ref_info_t;

/* Définition d'une conservation d'instructions d'assemblage (instance) */
struct _GAsmStorage
{
    GObject parent;                         /* A laisser en premier        */

    char *id;                               /* Identifiant de contenu      */

    char *idx_filename;                     /* Fichier pour l'indexage     */
    char *ins_filename;                     /* Fichier pour instructions   */
    char *op_filename;                      /* Fichier pour les opérandes  */
    char *reg_filename;                     /* Fichier pour les registres  */
    char *tp_filename;                      /* Fichier pour les types      */

    int idx_fd;                             /* Flux pour l'indexage        */
    int ins_fd;                             /* Flux pour les instructions  */
    int op_fd;                              /* Flux pour les opérandes     */
    int reg_fd;                             /* Flux pour les registres     */
    int tp_fd;                              /* Flux pour les types         */

    /**
     * La GLib n'est pas très claire sur la taille de GType :
     *
     *    #if     GLIB_SIZEOF_SIZE_T != GLIB_SIZEOF_LONG || !defined __cplusplus
     *    typedef gsize                           GType;
     *    #else   // for historic reasons, C++ links against gulong GTypes
     *    typedef gulong                          GType;
     *    #endif
     *
     * Et :
     *
     *    typedef unsigned $glib_size_type_define gsize;
     *
     * On prend le parti de réduire à 65536 types possibles dans l'enregistrement
     * des objets instanciés, et on conserve ces types en tant qu'unsigned short.
     */

    gtype_ref_info_t *gtypes;               /* Types des objets reconnus   */
    size_t gtp_count;                       /* Quantité de ces objets      */
    GMutex gtp_mutex;                       /* Contrôle d'accès à la liste */

    GArchProcessor *proc;                   /* Ensemble à traiter          */

    GArchInstruction **collected;           /* Liste d'instructions        */
    off64_t length;                         /* Taille de cette liste       */
    size_t count;                           /* Nombre de présences         */

};

/* Définition d'une conservation d'instructions d'assemblage (classe) */
struct _GAsmStorageClass
{
    GObjectClass parent;                    /* A laisser en premier        */

    /* Signaux */

    void (* saved) (GAsmStorage *);

};


/* Initialise la classe des conservations d'instructions. */
static void g_asm_storage_class_init(GAsmStorageClass *);

/* Initialise une instance de conservation d'instructions. */
static void g_asm_storage_init(GAsmStorage *);

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

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

/* Indique le chemin d'accès à l'archive finale. */
static char *g_asm_storage_get_archive_filename(const GAsmStorage *);

/* Décompresse les fichiers de cache d'instructions. */
static bool g_asm_storage_decompress(const GAsmStorage *);

/* Compresse les fichiers de cache d'instructions. */
static bool g_asm_storage_compress(const GAsmStorage *);

/* Apprend tous les types mémorisés dans un fichier. */
static bool g_asm_storage_read_types(GAsmStorage *);

/* Enregistre tous les types mémorisés dans un fichier. */
static bool g_asm_storage_write_types(GAsmStorage *);

/* Ouvre tous les fichiers nécessaires à une opération. */
static bool g_asm_storage_open_files(GAsmStorage *, int );

/* Acquitte la fin d'une tâche de sauvegarde complète. */
static void on_cache_saving_completed(GInsCaching *, GAsmStorage *);



/* ---------------------------------------------------------------------------------- */
/*                   CONSERVATION EXTERNE DES INSTRUCTIONS CHARGEES                   */
/* ---------------------------------------------------------------------------------- */


/* Indique le type défini pour les tâches d'enregistrement des instructions. */
G_DEFINE_TYPE(GInsCaching, g_ins_caching, G_TYPE_DELAYED_WORK);


/******************************************************************************
*                                                                             *
*  Paramètres  : klass = classe à initialiser.                                *
*                                                                             *
*  Description : Initialise la classe des tâches de cache d'instructions.     *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_ins_caching_class_init(GInsCachingClass *klass)
{
    GObjectClass *object;                   /* Autre version de la classe  */
    GDelayedWorkClass *work;                /* Version en classe parente   */

    object = G_OBJECT_CLASS(klass);

    object->dispose = (GObjectFinalizeFunc/* ! */)g_ins_caching_dispose;
    object->finalize = (GObjectFinalizeFunc)g_ins_caching_finalize;

    work = G_DELAYED_WORK_CLASS(klass);

    work->run = (run_task_fc)g_ins_caching_process;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : caching = instance à initialiser.                            *
*                                                                             *
*  Description : Initialise une tâche de cache d'instructions.                *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_ins_caching_init(GInsCaching *caching)
{
    caching->status = true;

}


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

static void g_ins_caching_dispose(GInsCaching *caching)
{
    g_object_unref(G_OBJECT(caching->proc));

    g_object_unref(G_OBJECT(caching->storage));

    if (caching->format != NULL)
        g_object_unref(G_OBJECT(caching->format));

    G_OBJECT_CLASS(g_ins_caching_parent_class)->dispose(G_OBJECT(caching));

}


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

static void g_ins_caching_finalize(GInsCaching *caching)
{
    G_OBJECT_CLASS(g_ins_caching_parent_class)->finalize(G_OBJECT(caching));

}


/******************************************************************************
*                                                                             *
*  Paramètres  : proc    = gestionnaire de l'ensemble d'instructions visées.  *
*                storage = gestionnaire de la conservation à venir.           *
*                format  = format binaire chargé associé à l'architecture.    *
*                                                                             *
*  Description : Crée une tâche de mise en cache de toutes les instructions.  *
*                                                                             *
*  Retour      : Tâche créée.                                                 *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static GInsCaching *g_ins_caching_new(GArchProcessor *proc, GAsmStorage *storage, GBinFormat *format)
{
    GInsCaching *result;            /* Tâche à retourner           */

    result = g_object_new(G_TYPE_INS_CACHING, NULL);

    result->proc = proc;
    g_object_ref(G_OBJECT(result->proc));

    result->storage = storage;
    g_object_ref(G_OBJECT(result->storage));

    result->format = format;

    if (format != NULL)
        g_object_ref(G_OBJECT(format));

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : caching = opération d'enregistrement à mener.                *
*                status  = barre de statut à tenir informée.                  *
*                                                                             *
*  Description : Assure la conservation ou le chargement d'instructions.      *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_ins_caching_process(GInsCaching *caching, GtkStatusStack *status)
{
    if (caching->format != NULL)
        g_ins_caching_process_load(caching, status);

    else
        g_ins_caching_process_store(caching, status);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : caching = opération d'enregistrement à mener.                *
*                status  = barre de statut à tenir informée.                  *
*                                                                             *
*  Description : Assure le chargement d'instructions en différé.              *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_ins_caching_process_load(GInsCaching *caching, GtkStatusStack *status)
{
    GAsmStorage *storage;                   /* Cache de destinartion       */
    packed_buffer_t pbuf;                   /* Tampon des données à écrire */
    off64_t i;                              /* Boucle de parcours          */
    off64_t pos;                            /* Position courante           */
    GArchInstruction *instr;                /* Instruction à traiter       */
    off64_t target;                         /* Position dans le flux       */

    storage = caching->storage;

    init_packed_buffer(&pbuf);

    for (i = 0; i < storage->length && caching->status; i++)
    {
        /* Des données sont-elles présentes à cette position ? */

        pos = lseek64(storage->idx_fd, i * sizeof(off64_t), SEEK_SET);

        if (pos != (i * sizeof(off64_t)))
        {
            perror("lseek64");
            caching->status = false;
            break;
        }

        caching->status = safe_read(storage->idx_fd, &target, sizeof(off64_t));

        if (!caching->status)
            break;

        if (target == (off64_t)-1)
            continue;

        /* Chargement de l'instruction */

        instr = g_asm_storage_get_instruction_at(storage, caching->format, i, &pbuf);

        if (instr == NULL)
            caching->status = false;

        else
            g_object_unref(G_OBJECT(instr));

    }

    exit_packed_buffer(&pbuf);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : caching = opération d'enregistrement à mener.                *
*                status  = barre de statut à tenir informée.                  *
*                                                                             *
*  Description : Assure la conservation d'instructions en différé.            *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_ins_caching_process_store(GInsCaching *caching, GtkStatusStack *status)
{
    GArchProcessor *proc;                   /* Ensemble à traiter          */
    GAsmStorage *storage;                   /* Cache de destinartion       */
    packed_buffer_t pbuf;                   /* Tampon des données à écrire */
    size_t count;                           /* Quantité d'instructions     */
    phys_t last_phys;                       /* Dernière position physique  */
    size_t i;                               /* Boucle de parcours #1       */
    GArchInstruction *instr;                /* Instruction à traiter       */
    off64_t pos;                            /* Position dans le flux       */
    const mrange_t *irange;                 /* Emplacement de l'instruction*/
    phys_t cur_phys;                        /* Position physique courante  */
    phys_t k;                               /* Boucle de parcours #2       */

    proc = caching->proc;
    storage = caching->storage;

    init_packed_buffer(&pbuf);

    g_arch_processor_lock(proc);

    count = g_arch_processor_count_instructions(proc);

    last_phys = VMPA_NO_PHYSICAL;

    for (i = 0; i < count && caching->status; i++)
    {
        /* Enregistrement de l'instruction */

        instr = g_arch_processor_get_instruction(proc, i);

        caching->status = false;//g_arch_instruction_store__old(instr, storage, &pbuf);

        if (caching->status)
            caching->status = g_asm_storage_store_instruction_data(storage, &pbuf, &pos);

        /* Enregistrement de la position */

        if (caching->status)
        {
            irange = g_arch_instruction_get_range(instr);

            cur_phys = get_phy_addr(get_mrange_addr(irange));

            assert((last_phys == VMPA_NO_PHYSICAL && cur_phys == 0) || (cur_phys > 0 && last_phys < cur_phys));

            if (last_phys != VMPA_NO_PHYSICAL)
                for (k = last_phys; k < (cur_phys - 1) && caching->status; k++)
                    caching->status = safe_write(storage->idx_fd, (off64_t []) { -1 }, sizeof(off64_t));

            caching->status = safe_write(storage->idx_fd, &pos, sizeof(off64_t));

            last_phys = cur_phys;

        }

        g_object_unref(G_OBJECT(instr));

    }

    g_arch_processor_unlock(proc);

    exit_packed_buffer(&pbuf);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : caching = opération d'enregistrement à mener.                *
*                                                                             *
*  Description : Fournit le bilan des traitements d'instructions en différé.  *
*                                                                             *
*  Retour      : Bilan des opérations.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool g_ins_caching_get_status(const GInsCaching *caching)
{
    bool result;                            /* Bilan à retourner           */

    result = caching->status;

    return result;

}



/* ---------------------------------------------------------------------------------- */
/*                     MECANISME DE SAUVEGARDE ET DE RESTAURATION                     */
/* ---------------------------------------------------------------------------------- */


/* Indique le type défini pour une conservation d'instructions d'assemblage. */
G_DEFINE_TYPE(GAsmStorage, g_asm_storage, G_TYPE_OBJECT);


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

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

    object = G_OBJECT_CLASS(klass);

    object->dispose = (GObjectFinalizeFunc/* ! */)g_asm_storage_dispose;
    object->finalize = (GObjectFinalizeFunc)g_asm_storage_finalize;

    g_signal_new("saved",
                 G_TYPE_ASM_STORAGE,
                 G_SIGNAL_RUN_LAST,
                 G_STRUCT_OFFSET(GAsmStorageClass, saved),
                 NULL, NULL,
                 g_cclosure_marshal_VOID__VOID,
                 G_TYPE_NONE, 0);

}


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

static void g_asm_storage_init(GAsmStorage *storage)
{
    storage->idx_filename = NULL;
    storage->ins_filename = NULL;
    storage->op_filename = NULL;
    storage->reg_filename = NULL;
    storage->tp_filename = NULL;

    storage->idx_fd = -1;
    storage->ins_fd = -1;
    storage->op_fd = -1;
    storage->reg_fd = -1;
    storage->tp_fd = -1;

    storage->gtypes = NULL;
    storage->gtp_count = 0;
    g_mutex_init(&storage->gtp_mutex);

    storage->proc = NULL;

    storage->collected = NULL;
    storage->length = 0;
    storage->count = 0;

}


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

static void g_asm_storage_dispose(GAsmStorage *storage)
{
    size_t i;                               /* Boucle de parcours          */

    g_mutex_lock(&storage->gtp_mutex);

    for (i = 0; i < storage->gtp_count; i++)
        if (storage->gtypes[i].gclass != NULL)
            g_type_class_unref(storage->gtypes[i].gclass);

    g_mutex_unlock(&storage->gtp_mutex);

    g_mutex_clear(&storage->gtp_mutex);

    if (storage->proc != NULL)
        g_object_unref(G_OBJECT(storage->proc));

    for (i = 0; i < storage->length; i++)
        if (storage->collected[i] != NULL)
            g_object_unref(G_OBJECT(storage->collected[i]));

    G_OBJECT_CLASS(g_asm_storage_parent_class)->dispose(G_OBJECT(storage));

}


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

static void g_asm_storage_finalize(GAsmStorage *storage)
{
    int ret;                                /* Bilan d'un appel            */

    free(storage->id);

#define finalize_storage_file(f)                \
    if (f != NULL)                              \
    {                                           \
        ret = access(f, W_OK);                  \
        if (ret == 0)                           \
        {                                       \
            ret = unlink(f);                    \
            if (ret != 0) perror("unlink");     \
        }                                       \
        free(f);                                \
    }

    finalize_storage_file(storage->idx_filename);
    finalize_storage_file(storage->ins_filename);
    finalize_storage_file(storage->op_filename);
    finalize_storage_file(storage->reg_filename);
    finalize_storage_file(storage->tp_filename);

    if (storage->idx_fd != -1)
        close(storage->idx_fd);

    if (storage->ins_fd != -1)
        close(storage->ins_fd);

    if (storage->op_fd != -1)
        close(storage->op_fd);

    if (storage->reg_fd != -1)
        close(storage->reg_fd);

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

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

    G_OBJECT_CLASS(g_asm_storage_parent_class)->finalize(G_OBJECT(storage));

}


/******************************************************************************
*                                                                             *
*  Paramètres  : proc = gestionnaire de l'ensemble d'instructions visées.     *
*                id   = identifiant pour la zone d'enregistrements.           *
*                                                                             *
*  Description : Crée le support d'une conservation d'instructions.           *
*                                                                             *
*  Retour      : Mécanismes mis en place.                                     *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GAsmStorage *g_asm_storage_new_compressed(GArchProcessor *proc, const gchar *id)
{
    GAsmStorage *result;                    /* Structure à retourner       */
    char *suffix;                           /* Fin du nom de fichier       */
    char *basedir;                          /* Chemin d'accès              */
    bool status;                            /* Assurance de validité       */

    result = g_object_new(G_TYPE_ASM_STORAGE, NULL);

    result->id = strdup(id);

    result->proc = proc;
    g_object_ref(G_OBJECT(proc));

    suffix = strdup("chrysalide");
    suffix = stradd(suffix, G_DIR_SEPARATOR_S);
    suffix = stradd(suffix, "cache");
    suffix = stradd(suffix, G_DIR_SEPARATOR_S);

    basedir = get_xdg_config_dir(suffix);

    free(suffix);

    status = mkpath(basedir);
    if (!status) goto gasn_base_error;

    asprintf(&result->idx_filename, "%s.%s-%s", basedir, id, "index.bin");
    asprintf(&result->ins_filename, "%s.%s-%s", basedir, id, "instructions.bin");
    asprintf(&result->op_filename, "%s.%s-%s", basedir, id, "operands.bin");
    asprintf(&result->reg_filename, "%s.%s-%s", basedir, id, "registers.bin");
    asprintf(&result->tp_filename, "%s.%s-%s", basedir, id, "types.bin");

    free(basedir);

    return result;

 gasn_base_error:

    g_object_unref(G_OBJECT(result));

    free(basedir);

    return NULL;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : storage = gestionnaire à consulter.                          *
*                                                                             *
*  Description : Indique le chemin d'accès à l'archive finale.                *
*                                                                             *
*  Retour      : Nom de fichier à libérer, ou NULL en cas d'erreur.           *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static char *g_asm_storage_get_archive_filename(const GAsmStorage *storage)
{
    char *result;                           /* Chemin d'accès à retourner  */
    char *suffix;                           /* Fin du nom de fichier       */

    suffix = strdup("chrysalide");
    suffix = stradd(suffix, G_DIR_SEPARATOR_S);
    suffix = stradd(suffix, "cache");
    suffix = stradd(suffix, G_DIR_SEPARATOR_S);
    suffix = stradd(suffix, storage->id);
    suffix = stradd(suffix, ".idb.tar.xz");

    result = get_xdg_config_dir(suffix);

    free(suffix);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : storage = gestionnaire à consulter.                          *
*                                                                             *
*  Description : Détermine si un cache d'instructions complet existe.         *
*                                                                             *
*  Retour      : Bilan de la détermination.                                   *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool g_asm_storage_has_cache(const GAsmStorage *storage)
{
    bool result;                            /* Bilan à faire remonter      */
    char *filename;                         /* Chemin d'accès à l'archive  */
    int ret;                                /* Résultat d'un test d'accès  */

    filename = g_asm_storage_get_archive_filename(storage);

    ret = access(filename, R_OK);

    result = (ret == 0);

    free(filename);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : storage = gestionnaire à manipuler.                          *
*                                                                             *
*  Description : Décompresse les fichiers de cache d'instructions.            *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool g_asm_storage_decompress(const GAsmStorage *storage)
{
    bool result;                            /* Bilan à retourner           */
    char *filename;                         /* Chemin d'accès à l'archive  */
    struct archive *in;                     /* Archive à consulter         */
    int ret;                                /* Bilan d'un appel            */
    struct archive_entry *entry;            /* Elément de l'archive        */
    const char *path;                       /* Désignation d'un fichier    */

    result = false;

    filename = g_asm_storage_get_archive_filename(storage);

    in = archive_read_new();
    archive_read_support_filter_all(in);
    archive_read_support_format_all(in);

    ret = archive_read_open_filename(in, filename, 10240 /* ?! */);
    if (ret != ARCHIVE_OK) goto gasd_bad_archive;

    for (ret = archive_read_next_header(in, &entry);
         ret == ARCHIVE_OK;
         ret = archive_read_next_header(in, &entry))
    {
        path = archive_entry_pathname(entry);

        if (strcmp(path, "index.bin") == 0)
        {
            if (!dump_archive_entry_into_file(in, entry, storage->idx_filename))
                goto gasd_exit;
        }
        else if (strcmp(path, "instructions.bin") == 0)
        {
            if (!dump_archive_entry_into_file(in, entry, storage->ins_filename))
                goto gasd_exit;
        }
        else if (strcmp(path, "operands.bin") == 0)
        {
            if (!dump_archive_entry_into_file(in, entry, storage->op_filename))
                goto gasd_exit;
        }
        else if (strcmp(path, "registers.bin") == 0)
        {
            if (!dump_archive_entry_into_file(in, entry, storage->reg_filename))
                goto gasd_exit;
        }
        else if (strcmp(path, "types.bin") == 0)
        {
            if (!dump_archive_entry_into_file(in, entry, storage->tp_filename))
                goto gasd_exit;
        }

    }

    if (ret != ARCHIVE_EOF)
        goto gasd_exit;

    result = true;

 gasd_exit:

 gasd_bad_archive:

    archive_read_close(in);
    archive_read_free(in);

    free(filename);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : storage = gestionnaire à manipuler.                          *
*                                                                             *
*  Description : Compresse les fichiers de cache d'instructions.              *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool g_asm_storage_compress(const GAsmStorage *storage)
{
    bool result;                            /* Bilan à retourner           */
    char *filename;                         /* Chemin d'accès à l'archive  */
    struct archive *out;                    /* Archive à constituer        */
    int ret;                                /* Bilan d'une création        */
    CPError status;                         /* Bilan d'une compression     */

    result = false;

    filename = g_asm_storage_get_archive_filename(storage);

    out = archive_write_new();
    archive_write_add_filter_xz(out);
    archive_write_set_format_gnutar(out);

    ret = archive_write_open_filename(out, filename);
    if (ret != ARCHIVE_OK) goto gasc_exit;

    status = add_file_into_archive(out, storage->idx_filename, "index.bin");
    if (status != CPE_NO_ERROR) goto gasc_exit;

    status = add_file_into_archive(out, storage->ins_filename, "instructions.bin");
    if (status != CPE_NO_ERROR) goto gasc_exit;

    status = add_file_into_archive(out, storage->op_filename, "operands.bin");
    if (status != CPE_NO_ERROR) goto gasc_exit;

    status = add_file_into_archive(out, storage->reg_filename, "registers.bin");
    if (status != CPE_NO_ERROR) goto gasc_exit;

    status = add_file_into_archive(out, storage->tp_filename, "types.bin");
    if (status != CPE_NO_ERROR) goto gasc_exit;

    result = true;

 gasc_exit:

    archive_write_close(out);
    archive_write_free(out);

    free(filename);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : storage = gestionnaire à compléter.                          *
*                                                                             *
*  Description : Apprend tous les types mémorisés dans un fichier.            *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool g_asm_storage_read_types(GAsmStorage *storage)
{
    bool result;                            /* Bilan à enregistrer         */
    packed_buffer_t pbuf;                   /* Tampon des données à lire   */
    size_t i;                               /* Boucle de parcours          */
    unsigned char len;                      /* Taille d'un nom de type     */
    char *name;                             /* Désignation d'un type       */

    init_packed_buffer(&pbuf);

    result = read_packed_buffer(&pbuf, storage->tp_fd);

    if (result)
    {
        g_mutex_lock(&storage->gtp_mutex);

        result = extract_packed_buffer(&pbuf, &storage->gtp_count, sizeof(size_t), true);

        if (result)
            storage->gtypes = (gtype_ref_info_t *)calloc(storage->gtp_count, sizeof(gtype_ref_info_t));

        for (i = 0; i < storage->gtp_count && result; i++)
        {
            result = extract_packed_buffer(&pbuf, &len, sizeof(unsigned char), false);

            if (result)
            {
                name = (char *)malloc(len);

                result = extract_packed_buffer(&pbuf, name, len, false);

                if (result)
                {
                    storage->gtypes[i].gtype = g_type_from_name(name);
                    result = (storage->gtypes[i].gtype != 0);

                    if (!result)
                        log_variadic_message(LMT_ERROR, "Unknown type: '%s'", name);

                }

                if (result)
                    storage->gtypes[i].gclass = g_type_class_ref(storage->gtypes[i].gtype);

                free(name);

            }

        }

        g_mutex_unlock(&storage->gtp_mutex);

    }

    exit_packed_buffer(&pbuf);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : storage = gestionnaire à manipuler.                          *
*                pbuf    = zone tampon à venir lire.                          *
*                                                                             *
*  Description : Crée une nouvelle instance d'objet à partir de son type.     *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GObject *g_asm_storage_create_object(GAsmStorage *storage, packed_buffer_t *pbuf)
{
    GObject *result;                        /* Nouvelle instance à renvoyer*/
    size_t index;                           /* Indice du point d'insertion */
    bool status;                            /* Bilan d'une récupération    */

    result = NULL;

    status = extract_packed_buffer(pbuf, &index, sizeof(size_t), true);

    if (status)
    {
        g_mutex_lock(&storage->gtp_mutex);

        if (index < storage->gtp_count)
            result = g_object_new(storage->gtypes[index].gtype, NULL);

        g_mutex_unlock(&storage->gtp_mutex);

    }

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : storage = gestionnaire à manipuler.                          *
*                obj     = instance dont le type est à mémoriser.             *
*                pbuf    = zone tampon à remplir.                             *
*                                                                             *
*  Description : Sauvegarde le type d'un objet instancié.                     *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool g_asm_storage_store_object_gtype(GAsmStorage *storage, GObject *obj, packed_buffer_t *pbuf)
{
    bool result;                            /* Bilan à retourner           */
    GType gtype;                            /* Type à enregistrer          */
    size_t index;                           /* Indice du point d'insertion */

    gtype = G_TYPE_FROM_INSTANCE(obj);

    /**
     * Pour quelques explications sur l'esquive suivante, se rapporter aux
     * commentaires de g_target_operand_unserialize().
     *
     * Dans la situation présente, on ne doit pas enregistrer le type dans le tampon,
     * car l'opérande va relancer l'opération entière (avec un opérande temporaire),
     * ce qui conduirait à l'enregistrement de deux types successifs dans les données.
     */

    if (gtype == G_TYPE_TARGET_OPERAND)
        result = true;

    else
    {
        g_mutex_lock(&storage->gtp_mutex);

        for (index = 0; index < storage->gtp_count; index++)
            if (storage->gtypes[index].gtype == gtype)
                break;

        if (index == storage->gtp_count)
        {
            storage->gtypes = (gtype_ref_info_t *)realloc(storage->gtypes,
                                                          ++storage->gtp_count * sizeof(gtype_ref_info_t));

            assert(storage->gtp_count > 0);

            storage->gtypes[index].gtype = gtype;
            storage->gtypes[index].gclass = g_type_class_ref(gtype);

        }

        g_mutex_unlock(&storage->gtp_mutex);

        result = extend_packed_buffer(pbuf, &index, sizeof(size_t), true);

    }

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : storage = gestionnaire à consulter.                          *
*                                                                             *
*  Description : Enregistre tous les types mémorisés dans un fichier.         *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool g_asm_storage_write_types(GAsmStorage *storage)
{
    bool result;                            /* Bilan à enregistrer         */
    packed_buffer_t pbuf;                   /* Tampon des données à écrire */
    size_t i;                               /* Boucle de parcours          */
    const gchar *name;                      /* Désignation d'un type       */
    size_t len;                             /* Taille de ce nom            */

    init_packed_buffer(&pbuf);

    g_mutex_lock(&storage->gtp_mutex);

    result = extend_packed_buffer(&pbuf, &storage->gtp_count, sizeof(size_t), true);

    for (i = 0; i < storage->gtp_count && result; i++)
    {
        name = g_type_name(storage->gtypes[i].gtype);
        len = strlen(name) + 1;

        if (len > (2 << (sizeof(unsigned char) * 8 - 1)))
        {
            log_variadic_message(LMT_ERROR, "Type name too long: '%s' (%zu bytes)", name, len);
            result = false;
            break;
        }

        result = extend_packed_buffer(&pbuf, (unsigned char []) { len }, sizeof(unsigned char), false);

        if (result)
            result = extend_packed_buffer(&pbuf, name, len, false);

    }

    if (result)
        result = write_packed_buffer(&pbuf, storage->tp_fd);

    g_mutex_unlock(&storage->gtp_mutex);

    exit_packed_buffer(&pbuf);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : storage = gestionnaire à manipuler.                          *
*                type    = type du fichier de destination.                    *
*                pbuf    = zone tampon à remplir.                             *
*                pos     = tête de lecture avant écriture.                    *
*                                                                             *
*  Description : Charge des données rassemblées.                              *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool _g_asm_storage_load_data(const GAsmStorage *storage, StorageFileType type, packed_buffer_t *pbuf, off64_t pos)
{
    bool result;                            /* Bilan à retourner           */
    int fd;                                 /* Flux ciblé                  */
    off64_t new;                            /* Nouvelle position de lecture*/

    switch (type)
    {
        case SFT_INSTRUCTION:
            fd = storage->ins_fd;
            break;
        case SFT_OPERAND:
            fd = storage->op_fd;
            break;
        case SFT_REGISTER:
            fd = storage->reg_fd;
            break;
        default:
            fd = -1;
            break;
    }

    if (fd == -1)
    {
        result = false;
        goto type_error;
    }

    new = lseek64(fd, pos, SEEK_SET);

    if (new != pos)
        result = false;

    else
    {
        reset_packed_buffer(pbuf);
        result = read_packed_buffer(pbuf, fd);
    }

 type_error:

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : storage = gestionnaire à manipuler.                          *
*                type    = type du fichier de destination.                    *
*                pbuf    = zone tampon à lire.                                *
*                pos     = tête de lecture avant écriture. [OUT]              *
*                                                                             *
*  Description : Sauvegarde des données rassemblées.                          *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool _g_asm_storage_store_data(const GAsmStorage *storage, StorageFileType type, packed_buffer_t *pbuf, off64_t *pos)
{
    bool result;                            /* Bilan à retourner           */
    int fd;                                 /* Flux ciblé                  */

    switch (type)
    {
        case SFT_INSTRUCTION:
            fd = storage->ins_fd;
            break;
        case SFT_OPERAND:
            fd = storage->op_fd;
            break;
        case SFT_REGISTER:
            fd = storage->reg_fd;
            break;
        default:
            fd = -1;
            break;
    }

    if (fd == -1)
    {
        result = false;
        goto type_error;
    }

    *pos = lseek64(fd, 0, SEEK_CUR);

    if (*pos == (off64_t)-1)
        result = false;

    else
    {
        result = write_packed_buffer(pbuf, fd);
        reset_packed_buffer(pbuf);
    }

 type_error:

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : storage = gestionnaire à manipuler.                          *
*                flags   = options d'ouverture supplémentaires.               *
*                                                                             *
*  Description : Ouvre tous les fichiers nécessaires à une opération.         *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool g_asm_storage_open_files(GAsmStorage *storage, int flags)
{
    bool result;                            /* Bilan à retourner           */

#define open_file(filename, fd)                             \
    ({                                                      \
        bool __status;                                      \
        fd = open(filename, flags | O_LARGEFILE, 0600);     \
        if (fd == -1)                                       \
        {                                                   \
            perror("open");                                 \
            __status = false;                               \
        }                                                   \
        else                                                \
            __status = true;                                \
        __status;                                           \
    })

    result = open_file(storage->idx_filename, storage->idx_fd);

    if (result)
        result = open_file(storage->ins_filename, storage->ins_fd);

    if (result)
        result = open_file(storage->op_filename, storage->op_fd);

    if (result)
        result = open_file(storage->reg_filename, storage->reg_fd);

    if (result)
        result = open_file(storage->tp_filename, storage->tp_fd);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : storage = gestionnaire à manipuler.                          *
*                format  = format binaire chargé associé à l'architecture.    *
*                gid     = groupe de travail dédié.                           *
*                                                                             *
*  Description : Lance une restauration complète d'unsauvegarde compressée.   *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool g_asm_storage_open(GAsmStorage *storage, GBinFormat *format, wgroup_id_t gid)
{
    bool result;                            /* Bilan à retourner           */
    GInsCaching *caching;                   /* Tâche à faire exécuter      */
    GWorkQueue *queue;                      /* Gestionnaire des tâches     */
    GArchInstruction **list;                /* Instructions rechargées     */
    size_t i;                               /* Boucle de parcours #1       */
    size_t k;                               /* Boucle de parcours #2       */

    result = g_asm_storage_decompress(storage);

    if (result)
        result = g_asm_storage_open_files(storage, O_RDONLY);

    if (result)
        result = g_asm_storage_read_types(storage);

    if (result)
    {
        storage->length = lseek64(storage->idx_fd, 0, SEEK_END);

        if (storage->length == (off64_t)-1)
        {
            perror("lseek64");
            result = false;
            goto gaso_exit;
        }

        result = (storage->length % sizeof(off64_t) == 0);

        storage->length /= sizeof(off64_t);

    }

    if (!result)
    {
        log_simple_message(LMT_ERROR, "Instruction cache seems corrupted...");
        goto gaso_exit;
    }

    storage->collected = (GArchInstruction **)calloc(storage->length, sizeof(GArchInstruction *));

    /**
     * Cette méthode ne peut être appelée que pour un objet construit
     * à partir du constructeur g_asm_storage_new_compressed().
     */
    assert(storage->proc != NULL);

    caching = g_ins_caching_new(storage->proc, storage, format);
    g_object_ref(G_OBJECT(caching));

    queue = get_work_queue();

    g_work_queue_schedule_work(queue, G_DELAYED_WORK(caching), gid);

    g_work_queue_wait_for_completion(queue, gid);

    result = g_ins_caching_get_status(caching);

    g_object_unref(G_OBJECT(caching));


    if (result)
    {
        log_simple_message(LMT_INFO, "Successfully restored all instructions from cache!");

        list = (GArchInstruction **)malloc(storage->count * sizeof(GArchInstruction *));

        for (i = 0, k = 0; i < storage->length; i++)
            if (storage->collected[i] != NULL)
            {
                list[k] = storage->collected[i];
                g_object_ref(G_OBJECT(list[k++]));
            }

        assert(k == storage->count);

        g_arch_processor_set_instructions(storage->proc, list, storage->count);

    }

    else
        log_simple_message(LMT_ERROR, "Failed to restore all instructions from cache!");

 gaso_exit:

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : storage = gestionnaire à manipuler.                          *
*                format  = format binaire chargé associé à l'architecture.    *
*                index   = position physique de l'instruction recherchée.     *
*                pbuf    = tampon de lecture à disposition pour l'opération.  *
*                                                                             *
*  Description : Fournit l'instruction correspondant à une position indicée.  *
*                                                                             *
*  Retour      : Instruction rechargée ou NULL en cas d'erreur.               *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GArchInstruction *g_asm_storage_get_instruction_at(GAsmStorage *storage, GBinFormat *format, off64_t index, packed_buffer_t *pbuf)
{
    GArchInstruction *result;               /* Instruction à renvoyer      */
    off64_t pos;                            /* Position dans le cache      */
    off64_t new;                            /* Nouvelle position de lecture*/
    off64_t target;                         /* Emplacement du cache ciblé  */
    bool status;                            /* Bilan d'une lecture         */
#ifndef NDEBUG
    const mrange_t *irange;                 /* Emplacement de l'instruction*/
#endif

    assert(index < storage->length);

    pos = index * sizeof(off64_t);

    if (storage->collected[index] == NULL)
    {
        new = lseek64(storage->idx_fd, pos, SEEK_SET);

        if (new == pos)
        {
            status = safe_read(storage->idx_fd, &target, sizeof(off64_t));

            if (status)
                status = g_asm_storage_load_instruction_data(storage, pbuf, target);

            if (status)
                storage->collected[index] = NULL;//g_arch_instruction_load__old(storage, format, pbuf);

            if (storage->collected[index] != NULL)
            {
                storage->count++;

#ifndef NDEBUG
                irange = g_arch_instruction_get_range(storage->collected[index]);

                assert(index == get_phy_addr(get_mrange_addr(irange)));
#endif

            }

        }

    }

    result = storage->collected[index];

    if (result != NULL)
        g_object_ref(G_OBJECT(result));

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : storage = gestionnaire à manipuler.                          *
*                                                                             *
*  Description : Programme une sauvegarde complète et compressée.             *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void g_asm_storage_save(GAsmStorage *storage)
{
    bool status;                            /* Statut des préparatifs      */
    GInsCaching *caching;                   /* Tâche à faire exécuter      */
    GWorkQueue *queue;                      /* Gestionnaire des tâches     */

    status = g_asm_storage_open_files(storage, O_WRONLY | O_CREAT | O_TRUNC);

    if (!status)
        log_simple_message(LMT_ERROR, "Unable to setup files for instructions caching!");

    else
    {
        /**
         * Cette méthode ne peut être appelée que pour un objet construit
         * à partir du constructeur g_asm_storage_new_compressed().
         */
        assert(storage->proc != NULL);

        caching = g_ins_caching_new(storage->proc, storage, NULL);

        g_signal_connect(caching, "work-completed", G_CALLBACK(on_cache_saving_completed), storage);

        queue = get_work_queue();

        g_work_queue_schedule_work(queue, G_DELAYED_WORK(caching), STORAGE_WORK_GROUP);

    }

}


/******************************************************************************
*                                                                             *
*  Paramètres  : caching = tâche de sauvegarde menée à son terme.             *
*                storage = gestionnaire de conservation à la réception.       *
*                                                                             *
*  Description : Acquitte la fin d'une tâche de sauvegarde complète.          *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void on_cache_saving_completed(GInsCaching *caching, GAsmStorage *storage)
{
    bool status;                            /* Bilan des enregistrements   */

    status = g_ins_caching_get_status(caching);

    if (status)
        log_simple_message(LMT_INFO, "Successfully cached all instructions!");
    else
        log_simple_message(LMT_ERROR, "Failed to cache all instructions!");

    if (status)
        status = g_asm_storage_write_types(storage);

    if (status)
    {
        status = g_asm_storage_compress(storage);

        if (!status)
            log_simple_message(LMT_ERROR, "Failed to compress instruction cache!");

    }

    g_signal_emit_by_name(storage, "saved");

}