From 609c184c3edb350a0da7fe29bf449a7189080c92 Mon Sep 17 00:00:00 2001
From: Cyrille Bagard <nocbos@gmail.com>
Date: Sun, 27 Oct 2019 23:33:11 +0100
Subject: Implemented snapshot related management features.

---
 plugins/pychrysalide/analysis/db/client.c | 168 ++++++++++++
 src/analysis/db/cdb.c                     | 287 ++++++++++++++++----
 src/analysis/db/client.c                  |  86 +++++-
 src/analysis/db/client.h                  |   2 +-
 src/analysis/db/protocol.h                |  30 +++
 src/analysis/db/snapshot.c                | 419 ++++++++++++++++++++++++++++--
 src/analysis/db/snapshot.h                |  11 +-
 src/common/packed.c                       |   8 +
 src/common/sqlite.c                       |  60 +++++
 src/common/sqlite.h                       |   3 +
 src/gui/dialogs/snapshots.c               |  15 +-
 11 files changed, 992 insertions(+), 97 deletions(-)

diff --git a/plugins/pychrysalide/analysis/db/client.c b/plugins/pychrysalide/analysis/db/client.c
index 80e15b8..68699b9 100644
--- a/plugins/pychrysalide/analysis/db/client.c
+++ b/plugins/pychrysalide/analysis/db/client.c
@@ -62,6 +62,15 @@ static PyObject *py_hub_client_set_snapshot_name(PyObject *, PyObject *);
 /* Définit la désignation d'un instantané donné. */
 static PyObject *py_hub_client_set_snapshot_desc(PyObject *, PyObject *);
 
+/* Restaure un ancien instantané. */
+static PyObject *py_hub_client_restore_snapshot(PyObject *, PyObject *);
+
+/* Crée un nouvel instantané à partir d'un autre. */
+static PyObject *py_hub_client_create_snapshot(PyObject *, PyObject *);
+
+/* Supprime un ancien instantané. */
+static PyObject *py_hub_client_remove_snapshot(PyObject *, PyObject *);
+
 /* Fournit la liste des instantanés existants. */
 static PyObject *py_hub_client_get_snapshots(PyObject *, void *);
 
@@ -465,6 +474,162 @@ static PyObject *py_hub_client_set_snapshot_desc(PyObject *self, PyObject *args)
 
 /******************************************************************************
 *                                                                             *
+*  Paramètres  : self = client à manipuler.                                   *
+*                args = arguments d'appel à consulter.                        *
+*                                                                             *
+*  Description : Restaure un ancien instantané.                               *
+*                                                                             *
+*  Retour      : True si la commande a bien été envoyée, False sinon.         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_hub_client_restore_snapshot(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Bilan à retourner           */
+    const char *raw_id;                     /* Identifiant brut            */
+    int ret;                                /* Bilan de lecture des args.  */
+    snapshot_id_t id;                       /* Identifiant utilisable      */
+    bool status;                            /* Bilan d'opération           */
+    GHubClient *client;                     /* Version native du serveur   */
+
+#define HUB_CLIENT_RESTORE_SNAPSHOT_METHOD PYTHON_METHOD_DEF                \
+(                                                                           \
+    restore_snapshot, "$self, id, /",                                       \
+    METH_VARARGS, py_hub_client,                                            \
+    "Ask the server for restoring a given snapshot using"                   \
+    " its identifier and returns the status of the request transmission."   \
+    "\n"                                                                    \
+    "A 'snapshot-changed' signal is emitted once the request has been"      \
+    " processed with success."                                              \
+)
+
+    ret = PyArg_ParseTuple(args, "s", &raw_id);
+    if (!ret) return NULL;
+
+    status = init_snapshot_id_from_text(&id, raw_id);
+    if (!status)
+    {
+        PyErr_SetString(PyExc_TypeError, _("provided value is not a valid snapshot identifier."));
+        return NULL;
+    }
+
+    client = G_HUB_CLIENT(pygobject_get(self));
+
+    status = g_hub_client_restore_snapshot(client, &id);
+
+    result = status ? Py_True : Py_False;
+    Py_INCREF(result);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = client à manipuler.                                   *
+*                args = arguments d'appel à consulter.                        *
+*                                                                             *
+*  Description : Crée un nouvel instantané à partir d'un autre.               *
+*                                                                             *
+*  Retour      : True si la commande a bien été envoyée, False sinon.         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_hub_client_create_snapshot(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Bilan à retourner           */
+    GHubClient *client;                     /* Version native du serveur   */
+    bool status;                            /* Bilan d'opération           */
+
+#define HUB_CLIENT_CREATE_SNAPSHOT_METHOD PYTHON_METHOD_DEF                 \
+(                                                                           \
+    create_snapshot, "$self, /",                                            \
+    METH_NOARGS, py_hub_client,                                             \
+    "Ask the server for creating a new snapshot of the current state"       \
+    " and returns the status of the request transmission."                  \
+    "\n"                                                                    \
+    "A 'snapshots-updated' signal is emitted once the request has been"     \
+    " processed with success."                                              \
+)
+
+    client = G_HUB_CLIENT(pygobject_get(self));
+
+    status = g_hub_client_create_snapshot(client);
+
+    result = status ? Py_True : Py_False;
+    Py_INCREF(result);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = client à manipuler.                                   *
+*                args = arguments d'appel à consulter.                        *
+*                                                                             *
+*  Description : Supprime un ancien instantané.                               *
+*                                                                             *
+*  Retour      : True si la commande a bien été envoyée, False sinon.         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_hub_client_remove_snapshot(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Bilan à retourner           */
+    const char *raw_id;                     /* Identifiant brut            */
+    int rec;                                /* Indicateur de récursivité   */
+    int ret;                                /* Bilan de lecture des args.  */
+    snapshot_id_t id;                       /* Identifiant utilisable      */
+    bool status;                            /* Bilan d'opération           */
+    GHubClient *client;                     /* Version native du serveur   */
+
+#define HUB_CLIENT_REMOVE_SNAPSHOT_METHOD PYTHON_METHOD_DEF                 \
+(                                                                           \
+    remove_snapshot, "$self, id, recursive, /",                             \
+    METH_VARARGS, py_hub_client,                                            \
+    "Ask the server for removing a given snapshot using"                    \
+    " its identifier and returns the status of the request transmission."   \
+    "\n"                                                                    \
+    "If this removal has not to be recursive, all children snapshots get"   \
+    " reassigned to the parent snapshot of the target."                     \
+    "\n"                                                                    \
+    "A 'snapshots-updated' signal is emitted once the request has been"     \
+    " processed with success."                                              \
+)
+
+    ret = PyArg_ParseTuple(args, "sp", &raw_id, &rec);
+    if (!ret) return NULL;
+
+    status = init_snapshot_id_from_text(&id, raw_id);
+    if (!status)
+    {
+        PyErr_SetString(PyExc_TypeError, _("provided value is not a valid snapshot identifier."));
+        return NULL;
+    }
+
+    client = G_HUB_CLIENT(pygobject_get(self));
+
+    status = g_hub_client_remove_snapshot(client, &id, rec);
+
+    result = status ? Py_True : Py_False;
+    Py_INCREF(result);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
 *  Paramètres  : self    = objet Python concerné par l'appel.                 *
 *                closure = non utilisé ici.                                   *
 *                                                                             *
@@ -711,6 +876,9 @@ PyTypeObject *get_python_hub_client_type(void)
         HUB_CLIENT_SET_LAST_ACTIVE_METHOD,
         HUB_CLIENT_SET_SNAPSHOT_NAME_METHOD,
         HUB_CLIENT_SET_SNAPSHOT_DESC_METHOD,
+        HUB_CLIENT_RESTORE_SNAPSHOT_METHOD,
+        HUB_CLIENT_CREATE_SNAPSHOT_METHOD,
+        HUB_CLIENT_REMOVE_SNAPSHOT_METHOD,
         { NULL }
     };
 
diff --git a/src/analysis/db/cdb.c b/src/analysis/db/cdb.c
index 125f69d..92624f6 100644
--- a/src/analysis/db/cdb.c
+++ b/src/analysis/db/cdb.c
@@ -147,6 +147,15 @@ static void on_collection_extended(GDbCollection *, GDbItem *, GCdbArchive *);
 /* Assure le traitement des requêtes de clients. */
 static void *g_cdb_archive_process(GCdbArchive *);
 
+/* Envoie un paquet de données constitué à tous les clients. */
+static void g_cdb_archive_send_reply_to_all_clients(GCdbArchive *, packed_buffer *);
+
+/* Envoie à tous les clients la nouvelle liste d'instantanés. */
+static bool g_cdb_archive_send_snapshot_update(GCdbArchive *, packed_buffer *);
+
+/* Envoie à tous les clients le nouvel instantané courant. */
+static bool g_cdb_archive_send_snapshot_change(GCdbArchive *, packed_buffer *);
+
 /* Dissocie un utilisateur de l'archive. */
 static void g_cdb_archive_remove_client(GCdbArchive *, size_t);
 
@@ -255,12 +264,18 @@ static void g_cdb_archive_dispose(GCdbArchive *archive)
 
 static void g_cdb_archive_finalize(GCdbArchive *archive)
 {
+#ifndef NDEBUG
     int ret;                                /* Bilan d'un appel            */
+#endif
 
     if (archive->db != NULL)
     {
+#ifndef NDEBUG
         ret = sqlite3_close(archive->db);
         assert(ret == SQLITE_OK);
+#else
+        sqlite3_close(archive->db);
+#endif
     }
 
     if (archive->xml_desc != NULL)
@@ -299,10 +314,6 @@ GCdbArchive *g_cdb_archive_new(const char *basedir, const char *tmpdir, const rl
     GCdbArchive *result;                    /* Adresse à retourner         */
     int ret;                                /* Retour d'un appel           */
     struct stat finfo;                      /* Information sur l'archive   */
-    snapshot_id_t id;                       /* Identifiant d'instantané    */
-#ifndef NDEBUG
-    bool status;                            /* Validité de l'identifiant   */
-#endif
 
     result = g_object_new(G_TYPE_CDB_ARCHIVE, NULL);
 
@@ -348,14 +359,7 @@ GCdbArchive *g_cdb_archive_new(const char *basedir, const char *tmpdir, const rl
 
         /* Récupération de la base courante */
 
-#ifndef NDEBUG
-        status = g_db_snapshot_get_current_id(result->snapshot, &id);
-        assert(status);
-#else
-        g_db_snapshot_get_current_id(result->snapshot, &id);
-#endif
-
-        result->db = g_db_snapshot_get_database(result->snapshot, &id);
+        result->db = g_db_snapshot_get_database(result->snapshot);
 
         if (result->db == NULL)
         {
@@ -381,14 +385,7 @@ GCdbArchive *g_cdb_archive_new(const char *basedir, const char *tmpdir, const rl
 
         /* Récupération de la base courante */
 
-#ifndef NDEBUG
-        status = g_db_snapshot_get_current_id(result->snapshot, &id);
-        assert(status);
-#else
-        g_db_snapshot_get_current_id(result->snapshot, &id);
-#endif
-
-        result->db = g_db_snapshot_get_database(result->snapshot, &id);
+        result->db = g_db_snapshot_get_database(result->snapshot);
 
         if (result->db == NULL)
         {
@@ -843,7 +840,7 @@ static void *g_cdb_archive_process(GCdbArchive *archive)
     DBError error;                          /* Bilan d'une opération       */
     packed_buffer out_pbuf;                 /* Tampon d'émission           */
     GDbCollection *collec;                  /* Collection visée au final   */
-    snapshot_id_t id;                       /* Identifiant d'instantané    */
+    bool reload;                            /* Besoin de rechargement      */
     char *msg;                              /* Erreur à faire remonter     */
 
     void interrupt_poll_with_sigusr1(int sig) { };
@@ -1007,19 +1004,8 @@ static void *g_cdb_archive_process(GCdbArchive *archive)
 
                     case DBC_GET_SNAPSHOTS:
 
- force_snapshots_update:
-
-                        init_packed_buffer(&out_pbuf);
-
-                        status = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_SNAPSHOTS_UPDATED },
-                                                      sizeof(uint32_t), true);
-                        if (!status) goto gcap_bad_reply;
-
-                        status = g_db_snapshot_pack_all(archive->snapshot, &out_pbuf);
-                        if (!status) goto gcap_bad_reply;
-
-                        status = extend_packed_buffer(&out_pbuf, SNAPSHOT_END_MARK, SNAP_ID_HEX_SZ, false);
-                        if (!status) goto gcap_bad_reply;
+                        if (!g_cdb_archive_send_snapshot_update(archive, &out_pbuf))
+                            goto critical_error;
 
                         status = ssl_send_packed_buffer(&out_pbuf, archive->clients[i].ssl_fd);
                         if (!status) goto gcap_bad_reply;
@@ -1030,21 +1016,8 @@ static void *g_cdb_archive_process(GCdbArchive *archive)
 
                     case DBC_GET_CUR_SNAPSHOT:
 
-                        init_packed_buffer(&out_pbuf);
-
-                        status = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_CUR_SNAPSHOT_UPDATED },
-                                                      sizeof(uint32_t), true);
-                        if (!status) goto gcap_bad_reply;
-
-#ifndef NDEBUG
-                        status = g_db_snapshot_get_current_id(archive->snapshot, &id);
-                        assert(status);
-#else
-                        g_db_snapshot_get_current_id(archive->snapshot, &id);
-#endif
-
-                        status = pack_snapshot_id(&id, &out_pbuf);
-                        if (!status) goto gcap_bad_reply;
+                        if (!g_cdb_archive_send_snapshot_change(archive, &out_pbuf))
+                            goto critical_error;
 
                         status = ssl_send_packed_buffer(&out_pbuf, archive->clients[i].ssl_fd);
                         if (!status) goto gcap_bad_reply;
@@ -1061,8 +1034,34 @@ static void *g_cdb_archive_process(GCdbArchive *archive)
 
                     case DBC_SET_CUR_SNAPSHOT:
 
+                        error = g_db_snapshot_restore(archive->snapshot, &in_pbuf, &reload);
 
+                        if (error == DBE_NONE)
+                        {
+#ifndef NDEBUG
+                            ret = sqlite3_close(archive->db);
+                            assert(ret == SQLITE_OK);
+#else
+                            sqlite3_close(archive->db);
+#endif
+
+                            archive->db = g_db_snapshot_get_database(archive->snapshot);
+
+                            if (archive->db == NULL)
+                            {
+                                error = DBE_SNAPSHOT_RESTORE_FAILURE;
+                            }
 
+                            else
+                            {
+                                if (!g_cdb_archive_send_snapshot_change(archive, NULL))
+                                    goto critical_error;
+                            }
+
+                        }
+
+                        else if (error == DBE_BAD_EXCHANGE)
+                            goto gcap_bad_exchange;
 
                         break;
 
@@ -1071,7 +1070,10 @@ static void *g_cdb_archive_process(GCdbArchive *archive)
                         error = g_db_snapshot_set_name(archive->snapshot, &in_pbuf);
 
                         if (error == DBE_NONE)
-                            goto force_snapshots_update;
+                        {
+                            if (!g_cdb_archive_send_snapshot_update(archive, NULL))
+                                goto critical_error;
+                        }
 
                         else if (error == DBE_BAD_EXCHANGE)
                             goto gcap_bad_exchange;
@@ -1083,7 +1085,47 @@ static void *g_cdb_archive_process(GCdbArchive *archive)
                         error = g_db_snapshot_set_desc(archive->snapshot, &in_pbuf);
 
                         if (error == DBE_NONE)
-                            goto force_snapshots_update;
+                        {
+                            if (!g_cdb_archive_send_snapshot_update(archive, NULL))
+                                goto critical_error;
+                        }
+
+                        else if (error == DBE_BAD_EXCHANGE)
+                            goto gcap_bad_exchange;
+
+                        break;
+
+                    case DBC_CREATE_SNAPSHOT:
+
+                        error = g_db_snapshot_create(archive->snapshot, archive->db);
+
+                        if (error == DBE_NONE)
+                        {
+                            if (!g_cdb_archive_send_snapshot_update(archive, NULL))
+                                goto critical_error;
+                        }
+
+                        else if (error == DBE_BAD_EXCHANGE)
+                            goto gcap_bad_exchange;
+
+                        break;
+
+                    case DBC_REMOVE_SNAPSHOT:
+
+                        error = g_db_snapshot_remove(archive->snapshot, &in_pbuf, &reload);
+
+                        if (error == DBE_NONE)
+                        {
+                            if (!g_cdb_archive_send_snapshot_update(archive, NULL))
+                                goto critical_error;
+
+                            if (reload)
+                            {
+                                if (!g_cdb_archive_send_snapshot_change(archive, NULL))
+                                    goto critical_error;
+                            }
+
+                        }
 
                         else if (error == DBE_BAD_EXCHANGE)
                             goto gcap_bad_exchange;
@@ -1122,6 +1164,14 @@ static void *g_cdb_archive_process(GCdbArchive *archive)
 
                 g_cdb_archive_remove_client(archive, i);
 
+                continue;
+
+ critical_error:
+
+                LOG_ERROR(LMT_ERROR, _("Internal critical error"));
+
+                assert(0);
+
             }
 
         }
@@ -1150,6 +1200,141 @@ static void *g_cdb_archive_process(GCdbArchive *archive)
 /******************************************************************************
 *                                                                             *
 *  Paramètres  : archive = archive à connecter avec un utilisateur.           *
+*                pbuf    = paquet de données à émettre.                       *
+*                                                                             *
+*  Description : Envoie un paquet de données constitué à tous les clients.    *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_cdb_archive_send_reply_to_all_clients(GCdbArchive *archive, packed_buffer *pbuf)
+{
+    size_t i;                               /* Boucle de parcours          */
+    bool status;                            /* Bilan d'une émission        */
+
+    for (i = 0; i < archive->count; i++)
+    {
+        status = ssl_send_packed_buffer(pbuf, archive->clients[i].ssl_fd);
+        if (!status)
+        {
+            log_variadic_message(LMT_ERROR, _("Error while replying to client %zu"), i);
+
+            g_cdb_archive_remove_client(archive, i);
+            i--;
+
+        }
+
+    }
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : archive = archive à connecter avec un utilisateur.           *
+*                pbuf    = paquet à consituer pour un envoi unique. [OUT]     *
+*                                                                             *
+*  Description : Envoie à tous les clients la nouvelle liste d'instantanés.   *
+*                                                                             *
+*  Retour      : Bilan de constitution de la réponse.                         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static bool g_cdb_archive_send_snapshot_update(GCdbArchive *archive, packed_buffer *pbuf)
+{
+    bool result;                            /* Bilan à retourner           */
+    bool do_send;                           /* Réalisation de l'émission   */
+    packed_buffer out_pbuf;                 /* Tampon d'émission           */
+
+    do_send = (pbuf == NULL);
+
+    if (pbuf == NULL)
+        pbuf = &out_pbuf;
+
+    init_packed_buffer(pbuf);
+
+    result = extend_packed_buffer(pbuf, (uint32_t []) { DBC_SNAPSHOTS_UPDATED },
+                                  sizeof(uint32_t), true);
+    if (!result) goto bad_reply;
+
+    result = g_db_snapshot_pack_all(archive->snapshot, pbuf);
+    if (!result) goto bad_reply;
+
+    result = extend_packed_buffer(pbuf, SNAPSHOT_END_MARK, SNAP_ID_HEX_SZ, false);
+    if (!result) goto bad_reply;
+
+    if (do_send)
+        g_cdb_archive_send_reply_to_all_clients(archive, pbuf);
+
+ bad_reply:
+
+    if (do_send || !result)
+        exit_packed_buffer(pbuf);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : archive = archive à connecter avec un utilisateur.           *
+*                pbuf    = paquet à consituer pour un envoi unique. [OUT]     *
+*                                                                             *
+*  Description : Envoie à tous les clients le nouvel instantané courant.      *
+*                                                                             *
+*  Retour      : Bilan de constitution de la réponse.                         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static bool g_cdb_archive_send_snapshot_change(GCdbArchive *archive, packed_buffer *pbuf)
+{
+    bool result;                            /* Bilan à retourner           */
+    bool do_send;                           /* Réalisation de l'émission   */
+    packed_buffer out_pbuf;                 /* Tampon d'émission           */
+    snapshot_id_t id;                       /* Identifiant d'instantané    */
+
+    do_send = (pbuf == NULL);
+
+    if (pbuf == NULL)
+        pbuf = &out_pbuf;
+
+    init_packed_buffer(pbuf);
+
+    result = extend_packed_buffer(pbuf, (uint32_t []) { DBC_CUR_SNAPSHOT_UPDATED },
+                                  sizeof(uint32_t), true);
+    if (!result) goto bad_reply;
+
+    result = g_db_snapshot_get_current_id(archive->snapshot, &id);
+    assert(result);
+    if (!result) goto bad_reply;
+
+    result = pack_snapshot_id(&id, pbuf);
+    if (!result) goto bad_reply;
+
+    if (do_send)
+        g_cdb_archive_send_reply_to_all_clients(archive, pbuf);
+
+ bad_reply:
+
+    if (do_send || !result)
+        exit_packed_buffer(pbuf);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : archive = archive à connecter avec un utilisateur.           *
 *                fd      = canal de communication réseau ouvert.              *
 *                                                                             *
 *  Description : Associe un nouvel utilisateur à l'archive.                   *
diff --git a/src/analysis/db/client.c b/src/analysis/db/client.c
index b4e856f..c1bcd28 100644
--- a/src/analysis/db/client.c
+++ b/src/analysis/db/client.c
@@ -908,6 +908,8 @@ static void *g_hub_client_update(GHubClient *client)
                 case DBC_SET_CUR_SNAPSHOT:
                 case DBC_SET_SNAPSHOT_NAME:
                 case DBC_SET_SNAPSHOT_DESC:
+                case DBC_CREATE_SNAPSHOT:
+                case DBC_REMOVE_SNAPSHOT:
                     log_variadic_message(LMT_INFO,
                                          _("This command is not available on this side: 0x%08x"), command);
                     goto gdcu_bad_exchange;
@@ -1586,9 +1588,32 @@ bool g_hub_client_set_snapshot_desc(GHubClient *client, const snapshot_id_t *id,
 
 bool g_hub_client_restore_snapshot(GHubClient *client, const snapshot_id_t *id)
 {
-    bool result;                            /* Bilan à retourner           */
+    bool result;                            /* Bilan partiel à remonter    */
+    packed_buffer out_pbuf;                 /* Tampon d'émission           */
+    SSL *tls_fd;                            /* Canal de communication SSL  */
+
+    init_packed_buffer(&out_pbuf);
+
+    tls_fd = g_hub_client_get_ssl_fd(client);
+
+    if (tls_fd == NULL)
+        result = false;
 
-    result = false;
+    else
+    {
+        result = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_SET_CUR_SNAPSHOT }, sizeof(uint32_t), true);
+
+        if (result)
+            result = pack_snapshot_id(id, &out_pbuf);
+
+        if (result)
+            result = ssl_send_packed_buffer(&out_pbuf, tls_fd);
+
+        g_hub_client_put_ssl_fd(client, tls_fd);
+
+    }
+
+    exit_packed_buffer(&out_pbuf);
 
     return result;
 
@@ -1598,7 +1623,6 @@ bool g_hub_client_restore_snapshot(GHubClient *client, const snapshot_id_t *id)
 /******************************************************************************
 *                                                                             *
 *  Paramètres  : client = client pour les accès distants à manipuler.         *
-*                id     = identifiant d'instantané à traiter.                 *
 *                                                                             *
 *  Description : Crée un nouvel instantané à partir d'un autre.               *
 *                                                                             *
@@ -1608,11 +1632,31 @@ bool g_hub_client_restore_snapshot(GHubClient *client, const snapshot_id_t *id)
 *                                                                             *
 ******************************************************************************/
 
-bool g_hub_client_create_snapshot(GHubClient *client, const snapshot_id_t *id)
+bool g_hub_client_create_snapshot(GHubClient *client)
 {
-    bool result;                            /* Bilan à retourner           */
+    bool result;                            /* Bilan partiel à remonter    */
+    packed_buffer out_pbuf;                 /* Tampon d'émission           */
+    SSL *tls_fd;                            /* Canal de communication SSL  */
 
-    result = false;
+    init_packed_buffer(&out_pbuf);
+
+    tls_fd = g_hub_client_get_ssl_fd(client);
+
+    if (tls_fd == NULL)
+        result = false;
+
+    else
+    {
+        result = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_CREATE_SNAPSHOT }, sizeof(uint32_t), true);
+
+        if (result)
+            result = ssl_send_packed_buffer(&out_pbuf, tls_fd);
+
+        g_hub_client_put_ssl_fd(client, tls_fd);
+
+    }
+
+    exit_packed_buffer(&out_pbuf);
 
     return result;
 
@@ -1635,9 +1679,35 @@ bool g_hub_client_create_snapshot(GHubClient *client, const snapshot_id_t *id)
 
 bool g_hub_client_remove_snapshot(GHubClient *client, const snapshot_id_t *id, bool rec)
 {
-    bool result;                            /* Bilan à retourner           */
+    bool result;                            /* Bilan partiel à remonter    */
+    packed_buffer out_pbuf;                 /* Tampon d'émission           */
+    SSL *tls_fd;                            /* Canal de communication SSL  */
+
+    init_packed_buffer(&out_pbuf);
+
+    tls_fd = g_hub_client_get_ssl_fd(client);
+
+    if (tls_fd == NULL)
+        result = false;
+
+    else
+    {
+        result = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_REMOVE_SNAPSHOT }, sizeof(uint32_t), true);
+
+        if (result)
+            result = pack_snapshot_id(id, &out_pbuf);
+
+        if (result)
+            result = extend_packed_buffer(&out_pbuf, (uint8_t []) { rec ? 0x1 : 0x0 }, sizeof(uint8_t), false);
 
-    result = false;
+        if (result)
+            result = ssl_send_packed_buffer(&out_pbuf, tls_fd);
+
+        g_hub_client_put_ssl_fd(client, tls_fd);
+
+    }
+
+    exit_packed_buffer(&out_pbuf);
 
     return result;
 
diff --git a/src/analysis/db/client.h b/src/analysis/db/client.h
index af47b5c..0323349 100644
--- a/src/analysis/db/client.h
+++ b/src/analysis/db/client.h
@@ -93,7 +93,7 @@ bool g_hub_client_set_snapshot_desc(GHubClient *, const snapshot_id_t *, const c
 bool g_hub_client_restore_snapshot(GHubClient *, const snapshot_id_t *);
 
 /* Crée un nouvel instantané à partir d'un autre. */
-bool g_hub_client_create_snapshot(GHubClient *, const snapshot_id_t *);
+bool g_hub_client_create_snapshot(GHubClient *);
 
 /* Supprime un ancien instantané. */
 bool g_hub_client_remove_snapshot(GHubClient *, const snapshot_id_t *, bool);
diff --git a/src/analysis/db/protocol.h b/src/analysis/db/protocol.h
index b57c25c..f66d8fc 100644
--- a/src/analysis/db/protocol.h
+++ b/src/analysis/db/protocol.h
@@ -277,6 +277,34 @@ typedef enum _DBCommand
 
     DBC_SET_SNAPSHOT_DESC,                  /* Description de l'instantané */
 
+    /**
+     * Gestion de la commande 'DBC_CREATE_SNAPSHOT'.
+     *
+     * Le client connecté envoie un paquet de la forme suivante :
+     *
+     *    [ Gestion d'instantané : DBC_CREATE_SNAPSHOT          ]
+     *
+     * Le serveur renvoie ensuite automatiquement un paquet
+     * de type 'DBC_SNAPSHOTS_UPDATED'.
+     */
+
+    DBC_CREATE_SNAPSHOT,                    /* Création d'instantané       */
+
+    /**
+     * Gestion de la commande 'DBC_REMOVE_SNAPSHOT'.
+     *
+     * Le client connecté envoie un paquet de la forme suivante :
+     *
+     *    [ Gestion d'instantané : DBC_REMOVE_SNAPSHOT          ]
+     *    [ <identifiant d'instantané>                          ]
+     *    [ indicateur de récursivité : octet 0x1 ou 0x0        ]
+     *
+     * Le serveur renvoie ensuite automatiquement un paquet
+     * de type 'DBC_SNAPSHOTS_UPDATED'.
+     */
+
+    DBC_REMOVE_SNAPSHOT,                    /* Suppression d'instantané    */
+
     DBC_COUNT
 
 } DBCommand;
@@ -303,6 +331,8 @@ typedef enum _DBError
 
     DBE_XML_ERROR,                          /* Erreur lors d'une définition*/
     DBE_SNAPSHOT_NOT_FOUND,                 /* Instantané non trouvé       */
+    DBE_SNAPSHOT_RESTORE_FAILURE,           /* Echec d'une restauration    */
+    DBE_SNAPSHOT_ROOT_REMOVAL,              /* Tentative de suppression    */
 
     DBE_COUNT
 
diff --git a/src/analysis/db/snapshot.c b/src/analysis/db/snapshot.c
index 2cd50f6..e07129e 100644
--- a/src/analysis/db/snapshot.c
+++ b/src/analysis/db/snapshot.c
@@ -38,6 +38,7 @@
 #include "../../common/compression.h"
 #include "../../common/extstr.h"
 #include "../../common/io.h"
+#include "../../common/sqlite.h"
 #include "../../common/xml.h"
 #include "../../core/logs.h"
 
@@ -79,9 +80,15 @@ static DBError save_snapshot_node(const snapshot_node_t *, xmlDocPtr, xmlXPathCo
 /* Recherche le noeud d'instantané lié à un identifiant. */
 static snapshot_node_t *find_snapshot_node(snapshot_node_t *, const snapshot_id_t *);
 
+/* Détermine si un instantané est compris dans une branche. */
+static bool contain_snapshot_node(const snapshot_node_t *, const snapshot_node_t *);
+
 /* Ajoute un instantané comme prolongement d'un instantané. */
 static void add_snapshot_node(snapshot_node_t *, snapshot_node_t *);
 
+/* Fait disparaître un instantané dans une arborescence. */
+static void remove_snapshot_node(snapshot_node_t *, bool);
+
 /* Collecte les descriptions d'une arborescence d'instantanés. */
 static bool pack_snapshot_node(const snapshot_node_t *, packed_buffer *);
 
@@ -101,6 +108,8 @@ struct _GDbSnapshot
     snapshot_node_t *nodes;                 /* Instantanés présents        */
     snapshot_node_t *current;               /* Instantané courant          */
 
+    char *current_db;                       /* Base de données SQLite      */
+
 };
 
 /* Gestionnaire d'instantanés de bases de données (classe) */
@@ -487,6 +496,34 @@ static snapshot_node_t *find_snapshot_node(snapshot_node_t *node, const snapshot
 
 /******************************************************************************
 *                                                                             *
+*  Paramètres  : node   = départ du parcours de recherche.                    *
+*                target = instantané recherché.                               *
+*                                                                             *
+*  Description : Détermine si un instantané est compris dans une branche.     *
+*                                                                             *
+*  Retour      : Noeud trouvé ou NULL en cas d'échec.                         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static bool contain_snapshot_node(const snapshot_node_t *node, const snapshot_node_t *target)
+{
+    bool result;                            /* Bilan à faire remonter      */
+    size_t i;                               /* Boucle de parcours          */
+
+    result = (node == target);
+
+    for (i = 0; i < node->count && !result; i++)
+        result = contain_snapshot_node(node->children[i], target);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
 *  Paramètres  : node  = instantané de rattachement.                          *
 *                child = instantané à attacher.                               *
 *                                                                             *
@@ -507,6 +544,8 @@ static void add_snapshot_node(snapshot_node_t *node, snapshot_node_t *child)
 
     node->children[node->count - 1] = child;
 
+    child->parent = node;
+
     src = get_snapshot_info_id(&node->info);
     dest = get_snapshot_info_parent_id(&child->info);
 
@@ -517,6 +556,78 @@ static void add_snapshot_node(snapshot_node_t *node, snapshot_node_t *child)
 
 /******************************************************************************
 *                                                                             *
+*  Paramètres  : node = instantané à traiter.                                 *
+*                rec  = précise si les enfants sont à rattacher au parent.    *
+*                                                                             *
+*  Description : Fait disparaître un instantané dans une arborescence.        *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void remove_snapshot_node(snapshot_node_t *node, bool rec)
+{
+    snapshot_node_t *parent;                /* Accès direct                */
+    size_t i;                               /* Boucle de parcours          */
+
+    parent = node->parent;
+
+    assert(parent != NULL);
+
+    /* Coupe de la branche */
+
+    assert(parent->count > 0);
+
+    if (parent->count == 1)
+    {
+        free(parent->children);
+
+        parent->children = NULL;
+        parent->count = 0;
+
+    }
+
+    else
+    {
+        for (i = 0; i < parent->count; i++)
+            if (parent->children[i] == node)
+                break;
+
+        assert(i < parent->count);
+
+        if ((i + 1) < parent->count)
+            memmove(&parent->children[i], &parent->children[i + 1],
+                    (parent->count - i - 1) * sizeof(snapshot_node_t *));
+
+        parent->children = realloc(parent->children, --parent->count * sizeof(snapshot_node_t *));
+
+    }
+
+    /* Rattachement des enfants ? */
+
+    if (!rec)
+    {
+        for (i = 0; i < node->count; i++)
+            add_snapshot_node(parent, node->children[i]);
+
+        free(node->children);
+
+        node->children = NULL;
+        node->count = 0;
+
+    }
+
+    /* Suppression */
+
+    destroy_snapshot_node(node);
+
+}
+
+
+/******************************************************************************
+*                                                                             *
 *  Paramètres  : node = définition d'instantané à consulter.                  *
 *                pbuf = paquet de données où venir inscrire des infos.        *
 *                                                                             *
@@ -595,9 +706,10 @@ static void g_db_snapshot_init(GDbSnapshot *snap)
     snap->hash = NULL;
 
     snap->nodes = NULL;
-
     snap->current = NULL;
 
+    snap->current_db = NULL;
+
 }
 
 
@@ -643,6 +755,9 @@ static void g_db_snapshot_finalize(GDbSnapshot *snap)
     if (snap->nodes != NULL)
         destroy_snapshot_node(snap->nodes);
 
+    if (snap->current_db != NULL)
+        free(snap->current_db);
+
     G_OBJECT_CLASS(g_db_snapshot_parent_class)->finalize(G_OBJECT(snap));
 
 }
@@ -664,12 +779,30 @@ static void g_db_snapshot_finalize(GDbSnapshot *snap)
 static GDbSnapshot *g_db_snapshot_new(const char *tmpdir, const char *hash)
 {
     GDbSnapshot *result;                    /* Adresse à retourner         */
+    int ret;                                /* Bilan d'une génération      */
+    bool status;                            /* Bilan de la création        */
 
     result = g_object_new(G_TYPE_DB_SNAPSHOT, NULL);
 
     result->tmpdir = strdup(tmpdir);
     result->hash = strdup(hash);
 
+    ret = asprintf(&result->current_db, "%s" G_DIR_SEPARATOR_S "%s_current_db.sql", tmpdir, hash);
+
+    status = (ret > 0);
+
+    if (status)
+    {
+        ret = ensure_path_exists(result->current_db);
+        status = (ret != -1);
+    }
+
+    if (!status)
+    {
+        g_object_unref(G_OBJECT(result));
+        result = NULL;
+    }
+
     return result;
 
 }
@@ -699,6 +832,7 @@ GDbSnapshot *g_db_snapshot_new_empty(const char *tmpdir, const char *hash, GList
     GDbCollection *collec;                  /* Collection visée manipulée  */
 
     result = g_db_snapshot_new(tmpdir, hash);
+    if (result == NULL) goto exit;
 
     result->nodes = create_snapshot_node(NULL, 0, NULL, NULL);
 
@@ -728,6 +862,11 @@ GDbSnapshot *g_db_snapshot_new_empty(const char *tmpdir, const char *hash, GList
 
     sqlite3_close(db);
 
+    status = copy_file(result->current_db, result->nodes->path);
+
+    if (!status)
+        goto error;
+
     return result;
 
  error_db:
@@ -738,6 +877,8 @@ GDbSnapshot *g_db_snapshot_new_empty(const char *tmpdir, const char *hash, GList
 
     g_object_unref(G_OBJECT(result));
 
+ exit:
+
     return NULL;
 
 }
@@ -776,6 +917,7 @@ GDbSnapshot *g_db_snapshot_new_from_xml(const char *tmpdir, const char *hash, xm
     snapshot_id_t node_id;                  /* Identifiant de noeud courant*/
 
     result = g_db_snapshot_new(tmpdir, hash);
+    if (result == NULL) goto exit;
 
     /* Chargement de l'ensemble des instantanés */
 
@@ -890,6 +1032,8 @@ GDbSnapshot *g_db_snapshot_new_from_xml(const char *tmpdir, const char *hash, xm
 
     g_object_unref(G_OBJECT(result));
 
+ exit:
+
     return NULL;
 
 }
@@ -931,6 +1075,15 @@ bool g_db_snapshot_fill(GDbSnapshot *snap, struct archive *archive)
         if (!_endswith(path, ".db", &dot))
             continue;
 
+        if (strcmp(path, "current.db") == 0)
+        {
+            if (!dump_archive_entry_into_file(archive, entry, snap->current_db))
+                break;
+
+            continue;
+
+        }
+
         raw_id = strndup(path, dot - path);
 
         status = init_snapshot_id_from_text(&node_id, raw_id);
@@ -988,6 +1141,7 @@ DBError g_db_snapshot_save(const GDbSnapshot *snap, xmlDocPtr xdoc, xmlXPathCont
     DBError result;                         /* Conclusion à retourner      */
     const snapshot_id_t *id;                /* Identifiant attribué        */
     bool status;                            /* Bilan d'un ajout XML        */
+    CPError ret;                            /* Bilan d'une compression     */
 
     assert(snap->current != NULL);
 
@@ -999,8 +1153,32 @@ DBError g_db_snapshot_save(const GDbSnapshot *snap, xmlDocPtr xdoc, xmlXPathCont
         result = DBE_XML_ERROR;
 
     else
+    {
+        ret = add_file_into_archive(archive, snap->current_db, "current.db");
+
+        switch (ret)
+        {
+            case CPE_NO_ERROR:
+                break;
+
+            case CPE_SYSTEM_ERROR:
+                result = DBE_SYS_ERROR;
+                goto exit;
+                break;
+
+            case CPE_ARCHIVE_ERROR:
+                result = DBE_ARCHIVE_ERROR;
+                goto exit;
+                break;
+
+        }
+
         result = save_snapshot_node(snap->nodes, xdoc, context, archive);
 
+    }
+
+ exit:
+
     return result;
 
 }
@@ -1042,7 +1220,6 @@ bool g_db_snapshot_get_current_id(const GDbSnapshot *snap, snapshot_id_t *id)
 /******************************************************************************
 *                                                                             *
 *  Paramètres  : snap = gestionnaire d'instantanés à consulter.               *
-*                id   = identifiant de l'instantané visé.                     *
 *                                                                             *
 *  Description : Fournit la base de données correspondant à instanné donné.   *
 *                                                                             *
@@ -1052,32 +1229,19 @@ bool g_db_snapshot_get_current_id(const GDbSnapshot *snap, snapshot_id_t *id)
 *                                                                             *
 ******************************************************************************/
 
-sqlite3 *g_db_snapshot_get_database(const GDbSnapshot *snap, const snapshot_id_t *id)
+sqlite3 *g_db_snapshot_get_database(const GDbSnapshot *snap)
 {
     sqlite3 *result;                        /* Base SQLite à retourner     */
-    snapshot_node_t *node;                  /* Instantané trouvé           */
     int ret;                                /* Bilan d'un appel            */
 
-    node = find_snapshot_node(snap->nodes, id);
-
-    if (node == NULL)
-    {
-        log_variadic_message(LMT_ERROR, _("Snapshot not found for id '%s'"), snapshot_id_as_string(id));
-        result = NULL;
-    }
+    ret = sqlite3_open(snap->current_db, &result);
 
-    else
+    if (ret != SQLITE_OK)
     {
-        ret = sqlite3_open(node->path, &result);
-
-        if (ret != SQLITE_OK)
-        {
-            if (result != NULL)
-                sqlite3_close(result);
-
-            result = NULL;
+        if (result != NULL)
+            sqlite3_close(result);
 
-        }
+        result = NULL;
 
     }
 
@@ -1112,7 +1276,7 @@ bool g_db_snapshot_pack_all(const GDbSnapshot *snap, packed_buffer *pbuf)
 
 /******************************************************************************
 *                                                                             *
-*  Paramètres  : snap    = gestionnaire d'instantanés à consulter.            *
+*  Paramètres  : snap = gestionnaire d'instantanés à consulter.               *
 *                pbuf = paquet de données où venir puiser les infos.          *
 *                                                                             *
 *  Description : Actualise la désignation d'un instantané donné.              *
@@ -1177,7 +1341,7 @@ DBError g_db_snapshot_set_name(const GDbSnapshot *snap, packed_buffer *pbuf)
 
 /******************************************************************************
 *                                                                             *
-*  Paramètres  : snap    = gestionnaire d'instantanés à consulter.            *
+*  Paramètres  : snap = gestionnaire d'instantanés à consulter.               *
 *                pbuf = paquet de données où venir puiser les infos.          *
 *                                                                             *
 *  Description : Actualise la description d'un instantané donné.              *
@@ -1238,3 +1402,212 @@ DBError g_db_snapshot_set_desc(const GDbSnapshot *snap, packed_buffer *pbuf)
     return result;
 
 }
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : snap   = gestionnaire d'instantanés à consulter.             *
+*                pbuf   = paquet de données où venir puiser les infos.        *
+*                reload = indique un besoin de rechargement de la base. [OUT] *
+*                                                                             *
+*  Description : Restaure un instantané de l'arborescence.                    *
+*                                                                             *
+*  Retour      : Bilan de l'opération sous forme de code d'erreur.            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+DBError g_db_snapshot_restore(GDbSnapshot *snap, packed_buffer *pbuf, bool *reload)
+{
+    DBError result;                         /* Conclusion à retourner      */
+    snapshot_id_t id;                       /* Identifiant d'instantané    */
+    bool status;                            /* Bilan d'une récupération    */
+    snapshot_node_t *node;                  /* Instantané trouvé           */
+
+    result = DBE_NONE;
+
+    /* Lecture des arguments */
+
+    setup_empty_snapshot_id(&id);
+
+    status = unpack_snapshot_id(&id, pbuf);
+    if (!status)
+    {
+        result = DBE_BAD_EXCHANGE;
+        goto bad_exchange;
+    }
+
+    /* Traitement */
+
+    node = find_snapshot_node(snap->nodes, &id);
+
+    if (node == NULL)
+    {
+        log_variadic_message(LMT_ERROR, _("Snapshot not found for id '%s'"), snapshot_id_as_string(&id));
+        result = DBE_SNAPSHOT_NOT_FOUND;
+    }
+
+    else if (node == snap->current)
+    {
+        log_simple_message(LMT_WARNING, _("No need to restore the current snapshot"));
+        *reload = false;
+    }
+
+    else
+    {
+        status = copy_file(snap->current_db, node->path);
+
+        if (!status)
+        {
+            log_variadic_message(LMT_ERROR, _("Failed to restore snapshot from '%s' to '%s'"),
+                                 node->path, snap->current_db);
+            result = DBE_SNAPSHOT_RESTORE_FAILURE;
+        }
+
+        else
+        {
+            snap->current = node;
+            *reload = true;
+        }
+
+    }
+
+ bad_exchange:
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : snap = gestionnaire d'instantanés à consulter.               *
+*                db   = base de données courante.                             *
+*                                                                             *
+*  Description : Crée un nouvel instantanés dans l'arborescence.              *
+*                                                                             *
+*  Retour      : Bilan de l'opération sous forme de code d'erreur.            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+DBError g_db_snapshot_create(GDbSnapshot *snap, sqlite3 *db)
+{
+    DBError result;                         /* Conclusion à retourner      */
+    snapshot_node_t *new;                   /* Nouvel instantané           */
+    bool status;                            /* Bilan d'une récupération    */
+
+    result = DBE_NONE;
+
+    new = create_snapshot_node(NULL, 0, NULL, NULL);
+
+    status = setup_snapshot_node_db_path(new, snap->tmpdir, snap->hash);
+    if (!status)
+    {
+        result = DBE_SYS_ERROR;
+        destroy_snapshot_node(new);
+        goto error;
+    }
+
+    status = backup_db(db, new->path);
+    if (!status)
+    {
+        result = DBE_SYS_ERROR;
+        destroy_snapshot_node(new);
+        goto error;
+    }
+
+    add_snapshot_node(snap->current, new);
+
+    snap->current = new;
+
+ error:
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : snap    = gestionnaire d'instantanés à consulter.            *
+*                pbuf    = paquet de données où venir puiser les infos.       *
+*                changed = indique si l'instantané courant a bougé. [OUT]     *
+*                                                                             *
+*  Description : Supprime un instantané dans l'arborescence.                  *
+*                                                                             *
+*  Retour      : Bilan de l'opération sous forme de code d'erreur.            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+DBError g_db_snapshot_remove(GDbSnapshot *snap, packed_buffer *pbuf, bool *changed)
+{
+    DBError result;                         /* Conclusion à retourner      */
+    snapshot_id_t id;                       /* Identifiant d'instantané    */
+    bool status;                            /* Bilan d'une récupération    */
+    uint8_t tmp;                            /* Stockage temporaire         */
+    bool rec;                               /* Indicateur de récursivité   */
+    snapshot_node_t *node;                  /* Instantané trouvé           */
+
+    result = DBE_NONE;
+
+    *changed = false;
+
+    /* Lecture des arguments */
+
+    setup_empty_snapshot_id(&id);
+
+    status = unpack_snapshot_id(&id, pbuf);
+    if (!status)
+    {
+        result = DBE_BAD_EXCHANGE;
+        goto bad_exchange;
+    }
+
+    status = extract_packed_buffer(pbuf, &tmp, sizeof(uint8_t), false);
+    if (!status)
+    {
+        result = DBE_BAD_EXCHANGE;
+        goto bad_exchange;
+    }
+
+    rec = (tmp == 0x1);
+
+    /* Traitement */
+
+    node = find_snapshot_node(snap->nodes, &id);
+
+    if (node == NULL)
+    {
+        log_variadic_message(LMT_ERROR, _("Snapshot not found for id '%s'"), snapshot_id_as_string(&id));
+        result = DBE_SNAPSHOT_NOT_FOUND;
+    }
+
+    else if (node == snap->nodes)
+    {
+        log_simple_message(LMT_ERROR, _("Root snapshot can not be removed"));
+        result = DBE_SNAPSHOT_ROOT_REMOVAL;
+    }
+
+    else
+    {
+        /* Réassignation éventuelle */
+        if ((rec && contain_snapshot_node(node, snap->current)) || (!rec && node == snap->current))
+        {
+            snap->current = node->parent;
+            *changed = true;
+        }
+
+        remove_snapshot_node(node, rec);
+
+    }
+
+ bad_exchange:
+
+    return result;
+
+}
diff --git a/src/analysis/db/snapshot.h b/src/analysis/db/snapshot.h
index 457e8c2..8737f8c 100644
--- a/src/analysis/db/snapshot.h
+++ b/src/analysis/db/snapshot.h
@@ -72,7 +72,7 @@ DBError g_db_snapshot_save(const GDbSnapshot *, xmlDocPtr, xmlXPathContextPtr, s
 bool g_db_snapshot_get_current_id(const GDbSnapshot *, snapshot_id_t *);
 
 /* Fournit la base de données correspondant à instanné donné. */
-sqlite3 *g_db_snapshot_get_database(const GDbSnapshot *, const snapshot_id_t *);
+sqlite3 *g_db_snapshot_get_database(const GDbSnapshot *);
 
 /* Collecte les descriptions de l'ensemble des instantanés. */
 bool g_db_snapshot_pack_all(const GDbSnapshot *, packed_buffer *);
@@ -83,6 +83,15 @@ DBError g_db_snapshot_set_name(const GDbSnapshot *, packed_buffer *);
 /* Actualise la description d'un instantané donné. */
 DBError g_db_snapshot_set_desc(const GDbSnapshot *, packed_buffer *);
 
+/* Restaure un instantané de l'arborescence. */
+DBError g_db_snapshot_restore(GDbSnapshot *, packed_buffer *, bool *);
+
+/* Crée un nouvel instantanés dans l'arborescence. */
+DBError g_db_snapshot_create(GDbSnapshot *, sqlite3 *);
+
+/* Supprime un instantané dans l'arborescence. */
+DBError g_db_snapshot_remove(GDbSnapshot *, packed_buffer *, bool *);
+
 
 
 #endif  /* _ANALYSIS_DB_SNAPSHOT_H */
diff --git a/src/common/packed.c b/src/common/packed.c
index a10155d..0c4a222 100644
--- a/src/common/packed.c
+++ b/src/common/packed.c
@@ -97,8 +97,16 @@ void reset_packed_buffer(packed_buffer *pbuf)
 
 void exit_packed_buffer(packed_buffer *pbuf)
 {
+#ifndef NDEBUG
+    assert(pbuf->data != NULL);
+#endif
+
     free(pbuf->data);
 
+#ifndef NDEBUG
+    pbuf->data = NULL;
+#endif
+
 }
 
 
diff --git a/src/common/sqlite.c b/src/common/sqlite.c
index 3a94623..41f99ac 100644
--- a/src/common/sqlite.c
+++ b/src/common/sqlite.c
@@ -583,3 +583,63 @@ bool update_db_values(sqlite3 *db, const char *table, const bound_value *values,
     return result;
 
 }
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : db       = base de données à sauvegarder.                    *
+*                filename = fichier de destination pour la sauvegarde.        *
+*                                                                             *
+*  Description : Effectue une copie d'une base de données en cours d'usage.   *
+*                                                                             *
+*  Retour      : Bilan de l'exécution de l'opération.                         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool backup_db(sqlite3 *db, const char *filename)
+{
+    bool result;                            /* Conclusion à faire remonter */
+    sqlite3 *copy;                          /* Copie de la base de données */
+    int ret;                                /* Bilan d'un appel à SQLite   */
+    sqlite3_backup *backup;                 /* Gestionnaire de sauvegarde  */
+
+    /**
+     * Cf. https://www.sqlite.org/backup.html
+     */
+
+    ret = sqlite3_open(filename, &copy);
+
+    if (ret != SQLITE_OK)
+    {
+        if (copy != NULL)
+            sqlite3_close(copy);
+
+        result = false;
+
+        goto exit;
+
+    }
+
+    backup = sqlite3_backup_init(copy, "main", db, "main");
+
+    if (backup == NULL)
+        result = false;
+
+    else
+    {
+        sqlite3_backup_step(backup, -1);
+        sqlite3_backup_finish(backup);
+
+        ret = sqlite3_errcode(copy);
+
+        result = (ret == SQLITE_OK);
+
+    }
+
+ exit:
+
+    return result;
+
+}
diff --git a/src/common/sqlite.h b/src/common/sqlite.h
index 99de12d..addc16d 100644
--- a/src/common/sqlite.h
+++ b/src/common/sqlite.h
@@ -87,6 +87,9 @@ bool store_db_values(sqlite3 *, const char *, const bound_value *, size_t);
 /* Met à jour une série de valeurs dans une base de données. */
 bool update_db_values(sqlite3 *, const char *, const bound_value *, size_t, const bound_value *, size_t);
 
+/* Effectue une copie d'une base de données en cours d'usage. */
+bool backup_db(sqlite3 *, const char *);
+
 
 
 #endif  /* _COMMON_SQLITE_H */
diff --git a/src/gui/dialogs/snapshots.c b/src/gui/dialogs/snapshots.c
index 1fcea67..9f256c6 100644
--- a/src/gui/dialogs/snapshots.c
+++ b/src/gui/dialogs/snapshots.c
@@ -720,22 +720,11 @@ static void restore_old_snapshot(GtkToolButton *button, GtkBuilder *builder)
 
 static void create_new_snapshot(GtkToolButton *button, GtkBuilder *builder)
 {
-    const char *raw;                        /* Identifiant brut            */
-    snapshot_id_t id;                       /* Identifiant utilisable      */
-    bool status;                            /* Bilan d'une conversion      */
     GHubClient *client;                     /* Cible des interactions      */
 
-    raw = g_object_get_data(G_OBJECT(builder), "selected");
-
-    status = init_snapshot_id_from_text(&id, raw);
-
-    if (status)
-    {
-        client = G_HUB_CLIENT(g_object_get_data(G_OBJECT(builder), "client"));
-
-        g_hub_client_create_snapshot(client, &id);
+    client = G_HUB_CLIENT(g_object_get_data(G_OBJECT(builder), "client"));
 
-    }
+    g_hub_client_create_snapshot(client);
 
 }
 
-- 
cgit v0.11.2-87-g4458