From 609c184c3edb350a0da7fe29bf449a7189080c92 Mon Sep 17 00:00:00 2001 From: Cyrille Bagard 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 ] + * [ ] + * [ 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, ©); + + 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