/* Chrysalide - Outil d'analyse de fichiers binaires * snapshot.c - prototypes gestion des instantanés de bases de données * * Copyright (C) 2019 Cyrille Bagard * * This file is part of Chrysalide. * * Chrysalide is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * Chrysalide is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Chrysalide. If not, see . */ #include "snapshot.h" #include #include #include #include #include #include #include #include "collection.h" #include "../../common/compression.h" #include "../../common/extstr.h" #include "../../common/io.h" #include "../../common/xml.h" #include "../../core/logs.h" /* ------------------------ GESTION UNITAIRE DES INSTANTANES ------------------------ */ /* Caractéristiques d'un instantané */ typedef struct _snapshot_node_t { struct _snapshot_node_t *parent; /* Parent hiérarchique */ char *name; /* Nom de l'instantané */ char *desc; /* Description associée */ char *id; /* Identifiant attribué */ char *path; /* Fichier extrait */ struct _snapshot_node_t **children; /* Sous-noeuds rattachés */ size_t count; /* Quantité de ces noeuds */ } snapshot_node_t; /* Constitue un nouveau noeud d'instantané. */ static snapshot_node_t *create_snapshot_node(const char *); /* Libère la mémoire occupée par un noeud d'instantané. */ static void destroy_snapshot_node(snapshot_node_t *); /* Définit le chemin vers une base de données pour un noeud. */ static bool setup_snapshot_node_db_path(snapshot_node_t *, const char *, const char *); /* Valide la présence d'une base de données pour chaque noeud. */ static bool check_snapshot_nodes(const snapshot_node_t *); /* Enregistre tous les éléments associés aux instantanés. */ static DBError save_snapshot_node(const snapshot_node_t *, xmlDocPtr, xmlXPathContextPtr, struct archive *); /* Recherche le noeud d'instantané lié à un identifiant. */ static snapshot_node_t *find_snapshot_node(snapshot_node_t *, const char *); /* Ajoute un instantané comme prolongement d'un instantané. */ static void add_snapshot_node(snapshot_node_t *, snapshot_node_t *); /* --------------------- MANIPULATIONS D'ENSEMBLE D'INSTANTANES --------------------- */ /* Gestionnaire d'instantanés de bases de données (instance) */ struct _GDbSnapshot { GObject parent; /* A laisser en premier */ char *tmpdir; /* Répertoire de travail */ char *hash; /* Empreinte de binaire */ snapshot_node_t *nodes; /* Instantanés présents */ snapshot_node_t *current; /* Instantané courant */ }; /* Gestionnaire d'instantanés de bases de données (classe) */ struct _GDbSnapshotClass { GObjectClass parent; /* A laisser en premier */ }; /* Initialise la classe des gestionnaires d'instantanés. */ static void g_db_snapshot_class_init(GDbSnapshotClass *); /* Initialise un gestionnaire d'instantanés de base de données. */ static void g_db_snapshot_init(GDbSnapshot *); /* Supprime toutes les références externes. */ static void g_db_snapshot_dispose(GDbSnapshot *); /* Procède à la libération totale de la mémoire. */ static void g_db_snapshot_finalize(GDbSnapshot *); /* Prépare un gestionnaire d'instantanés de bases de données. */ static GDbSnapshot *g_db_snapshot_new(const char *, const char *); /* ---------------------------------------------------------------------------------- */ /* GESTION UNITAIRE DES INSTANTANES */ /* ---------------------------------------------------------------------------------- */ /****************************************************************************** * * * Paramètres : id = éventuel identifiant prédéfini. * * * * Description : Constitue un nouveau noeud d'instantané. * * * * Retour : Structure mise en place ou NULL en cas d'échec. * * * * Remarques : - * * * ******************************************************************************/ static snapshot_node_t *create_snapshot_node(const char *id) { snapshot_node_t *result; /* Nouvel instantané à renvoyer*/ int ret; /* Bilan d'une génération */ unsigned char rand[32]; /* Tirage aléatoire */ size_t i; /* Boucle de parcours */ char _id[65]; /* Identifiant nouveau */ static char *alphabet = "0123456789abcdef"; if (id == NULL) { ret = RAND_bytes(rand, sizeof(rand)); if (ret != 1) { LOG_ERROR_OPENSSL; result = NULL; goto exit; } for (i = 0; i < sizeof(rand); i++) { _id[i * 2 + 0] = alphabet[rand[i] & 0xf]; _id[i * 2 + 1] = alphabet[(rand[i] >> 4) & 0xf]; } _id[64] = '\0'; id = _id; } result = malloc(sizeof(snapshot_node_t)); result->parent = NULL; result->name = NULL; result->desc = NULL; result->id = strdup(id); result->path = NULL; result->children = NULL; result->count = 0; exit: return result; } /****************************************************************************** * * * Paramètres : node = noeud d'instantané à traiter. * * * * Description : Libère la mémoire occupée par un noeud d'instantané. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void destroy_snapshot_node(snapshot_node_t *node) { size_t i; /* Boucle de parcours */ int ret; /* Bilan d'un appel */ for (i = 0; i < node->count; i++) destroy_snapshot_node(node->children[i]); if (node->name != NULL) free(node->name); if (node->desc != NULL) free(node->desc); free(node->id); if (node->path != NULL) { ret = unlink(node->path); if (ret != 0) LOG_ERROR_N("unlink"); free(node->path); } if (node->children != NULL) free(node->children); free(node); } /****************************************************************************** * * * Paramètres : node = noeud d'instantané à traiter. * * tmpdir = répertoire de travail temporaire. * * hash = empreinte du binaire à représenter. * * * * Description : Définit le chemin vers une base de données pour un noeud. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ static bool setup_snapshot_node_db_path(snapshot_node_t *node, const char *tmpdir, const char *hash) { bool result; /* Bilan à retourner */ int ret; /* Bilan d'une génération */ ret = asprintf(&node->path, "%s" G_DIR_SEPARATOR_S "%s_%s_db.sql", tmpdir, hash, node->id); result = (ret > 0); if (result) { ret = ensure_path_exists(node->path); result = (ret != -1); } return result; } /****************************************************************************** * * * Paramètres : node = départ du parcours de vérification. * * * * Description : Valide la présence d'une base de données pour chaque noeud. * * * * Retour : true si l'ensemble de noeuds est dans un état cohérent. * * * * Remarques : - * * * ******************************************************************************/ static bool check_snapshot_nodes(const snapshot_node_t *node) { bool result; /* Bilan à retourner */ size_t i; /* Boucle de parcours */ result = (node->path != NULL); if (!result) log_variadic_message(LMT_ERROR, _("Database is missing for snapshot '%s'"), node->id); for (i = 0; i < node->count && result; i++) result = check_snapshot_nodes(node->children[i]); return result; } /****************************************************************************** * * * Paramètres : node = départ du parcours. * * xdoc = document XML à compléter. * * context = contexte pour les recherches. * * archive = archive en cours de constitution. * * * * Description : Enregistre tous les éléments associés aux instantanés. * * * * Retour : Identifiant de l'instantané courant. * * * * Remarques : - * * * ******************************************************************************/ static DBError save_snapshot_node(const snapshot_node_t *node, xmlDocPtr xdoc, xmlXPathContextPtr context, struct archive *archive) { DBError result; /* Conclusion à retourner */ char *name; /* Désignation d'une entrée */ int ret; /* Bilan d'un appel */ CPError error; /* Bilan d'une compression */ xmlNodePtr xml_node; /* Nouveau noeud XML */ bool status; /* Bilan d'un ajout XML */ size_t i; /* Boucle de parcours */ /* Sauvegarde de la base de données */ ret = asprintf(&name, "%s.db", node->id); if (ret < 0) { result = DBE_SYS_ERROR; goto exit; } assert(node->path != NULL); error = add_file_into_archive(archive, node->path, name); free(name); switch (error) { 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; } /* Inscription dans le document XML */ xml_node = ensure_node_exist(xdoc, context, "/ChrysalideBinary/Snapshots"); if (xml_node == NULL) { result = DBE_XML_ERROR; goto exit; } xml_node = add_node_to_xpath(xdoc, context, "/ChrysalideBinary/Snapshots", "Snapshot"); if (xml_node == NULL) { result = DBE_XML_ERROR; goto exit; } status = _add_string_attribute_to_node(xml_node, "id", node->id); if (!status) { result = DBE_XML_ERROR; goto exit; } if (node->parent != NULL) { status = _add_string_attribute_to_node(xml_node, "parent", node->parent->id); if (!status) { result = DBE_XML_ERROR; goto exit; } } /* Poursuite des enregistrement */ result = DBE_NONE; for (i = 0; i < node->count && result == DBE_NONE; i++) result = save_snapshot_node(node->children[i], xdoc, context, archive); exit: return result; } /****************************************************************************** * * * Paramètres : node = départ du parcours de recherche. * * id = identifiant de l'instantané visé. * * * * Description : Recherche le noeud d'instantané lié à un identifiant. * * * * Retour : Noeud trouvé ou NULL en cas d'échec. * * * * Remarques : - * * * ******************************************************************************/ static snapshot_node_t *find_snapshot_node(snapshot_node_t *node, const char *id) { snapshot_node_t *result; /* Noeud trouvé à renvoyer */ size_t i; /* Boucle de parcours */ if (strcmp(node->id, id) == 0) result = node; else { result = NULL; for (i = 0; i < node->count && result == NULL; i++) result = find_snapshot_node(node->children[i], id); } return result; } /****************************************************************************** * * * Paramètres : node = instantané de rattachement. * * child = instantané à attacher. * * * * Description : Ajoute un instantané comme prolongement d'un instantané. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void add_snapshot_node(snapshot_node_t *node, snapshot_node_t *child) { node->children = realloc(node->children, ++node->count * sizeof(snapshot_node_t *)); node->children[node->count - 1] = child; } /* ---------------------------------------------------------------------------------- */ /* MANIPULATIONS D'ENSEMBLE D'INSTANTANES */ /* ---------------------------------------------------------------------------------- */ /* Indique le type défini pour un gestionnaire d'instantanés de bases de données. */ G_DEFINE_TYPE(GDbSnapshot, g_db_snapshot, G_TYPE_OBJECT); /****************************************************************************** * * * Paramètres : klass = classe à initialiser. * * * * Description : Initialise la classe des gestionnaires d'instantanés. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_db_snapshot_class_init(GDbSnapshotClass *klass) { GObjectClass *object; /* Autre version de la classe */ object = G_OBJECT_CLASS(klass); object->dispose = (GObjectFinalizeFunc/* ! */)g_db_snapshot_dispose; object->finalize = (GObjectFinalizeFunc)g_db_snapshot_finalize; } /****************************************************************************** * * * Paramètres : snap = instance à initialiser. * * * * Description : Initialise un gestionnaire d'instantanés de base de données. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_db_snapshot_init(GDbSnapshot *snap) { snap->tmpdir = NULL; snap->hash = NULL; snap->nodes = NULL; snap->current = NULL; } /****************************************************************************** * * * Paramètres : snap = instance d'objet GLib à traiter. * * * * Description : Supprime toutes les références externes. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_db_snapshot_dispose(GDbSnapshot *snap) { G_OBJECT_CLASS(g_db_snapshot_parent_class)->dispose(G_OBJECT(snap)); } /****************************************************************************** * * * Paramètres : snap = instance d'objet GLib à traiter. * * * * Description : Procède à la libération totale de la mémoire. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_db_snapshot_finalize(GDbSnapshot *snap) { if (snap->tmpdir != NULL) free(snap->tmpdir); if (snap->hash != NULL) free(snap->hash); if (snap->nodes != NULL) destroy_snapshot_node(snap->nodes); G_OBJECT_CLASS(g_db_snapshot_parent_class)->finalize(G_OBJECT(snap)); } /****************************************************************************** * * * Paramètres : tmpdir = répertoire de travail temporaire. * * hash = empreinte du binaire à représenter. * * * * Description : Prépare un gestionnaire d'instantanés de bases de données. * * * * Retour : Structure mise en place ou NULL en cas d'échec. * * * * Remarques : - * * * ******************************************************************************/ static GDbSnapshot *g_db_snapshot_new(const char *tmpdir, const char *hash) { GDbSnapshot *result; /* Adresse à retourner */ result = g_object_new(G_TYPE_DB_SNAPSHOT, NULL); result->tmpdir = strdup(tmpdir); result->hash = strdup(hash); return result; } /****************************************************************************** * * * Paramètres : tmpdir = répertoire de travail temporaire. * * hash = empreinte du binaire à représenter. * * collections = ensemble de modifications par catégories. * * * * Description : Prépare un gestionnaire d'instantanés de bases de données. * * * * Retour : Structure mise en place ou NULL en cas d'échec. * * * * Remarques : - * * * ******************************************************************************/ GDbSnapshot *g_db_snapshot_new_empty(const char *tmpdir, const char *hash, GList *collections) { GDbSnapshot *result; /* Adresse à retourner */ sqlite3 *db; /* Base de données à manipuler */ int ret; /* Bilan de la création */ bool status; /* Bilan d'une mise en place */ GList *iter; /* Boucle de parcours */ GDbCollection *collec; /* Collection visée manipulée */ result = g_db_snapshot_new(tmpdir, hash); result->nodes = create_snapshot_node(NULL); status = setup_snapshot_node_db_path(result->nodes, tmpdir, hash); if (!status) goto error; result->current = result->nodes; ret = sqlite3_open(result->nodes->path, &db); if (ret != SQLITE_OK) { LOG_ERROR_SQLITE(db, "sqlite3_open"); goto error_db; } for (iter = g_list_first(collections); iter != NULL; iter = g_list_next(iter)) { collec = G_DB_COLLECTION(iter->data); status = g_db_collection_create_db_table(collec, db); if (!status) goto error_db; } sqlite3_close(db); return result; error_db: sqlite3_close(db); error: g_object_unref(G_OBJECT(result)); return NULL; } /****************************************************************************** * * * Paramètres : tmpdir = répertoire de travail temporaire. * * hash = empreinte du binaire à représenter. * * xdoc = document XML à compléter. * * context = contexte pour les recherches. * * * * Description : Charge un gestionnaire d'instantanés de bases de données. * * * * Retour : Structure mise en place ou NULL en cas d'échec. * * * * Remarques : - * * * ******************************************************************************/ GDbSnapshot *g_db_snapshot_new_from_xml(const char *tmpdir, const char *hash, xmlDocPtr xdoc, xmlXPathContextPtr context) { GDbSnapshot *result; /* Adresse à retourner */ xmlXPathObjectPtr xobject; /* Cible d'une recherche */ size_t count; /* Nombre de contenus premiers */ size_t i; /* Boucle de parcours */ xmlNodePtr xml_node; /* Noeud XML avec propriétés */ char *parent_id; /* Identifiant de noeud parent */ snapshot_node_t *parent; /* Instantané parent trouvé */ char *node_id; /* Identifiant de noeud courant*/ snapshot_node_t *node; /* Instantané nouveau constitué*/ result = g_db_snapshot_new(tmpdir, hash); /* Chargement de l'ensemble des instantanés */ xobject = get_node_xpath_object(context, "/ChrysalideBinary/Snapshots/Snapshot"); count = XPATH_OBJ_NODES_COUNT(xobject); for (i = 0; i < count; i++) { xml_node = NODE_FROM_PATH_OBJ(xobject, i); parent_id = qck_get_node_prop_value(xml_node, "parent"); if (parent_id == NULL) parent = NULL; else { if (result->nodes == NULL) parent = NULL; else parent = find_snapshot_node(result->nodes, parent_id); free(parent_id); if (parent == NULL) goto bad_xml; } node_id = qck_get_node_prop_value(xml_node, "id"); if (node_id == NULL) goto bad_xml; node = create_snapshot_node(node_id); free(node_id); if (node == NULL) goto bad_xml; if (parent == NULL) { if (result->nodes != NULL) goto bad_xml; result->nodes = node; } else add_snapshot_node(parent, node); } if(xobject != NULL) xmlXPathFreeObject(xobject); /* Détermination de l'instantané courant */ node_id = get_node_text_value(context, "/ChrysalideBinary/CurrentSnapshot"); if (node_id == NULL) result->current = result->nodes; else { result->current = find_snapshot_node(result->nodes, node_id); free(node_id); } if (result->current == NULL) goto no_current; return result; bad_xml: if(xobject != NULL) xmlXPathFreeObject(xobject); no_current: g_object_unref(G_OBJECT(result)); return NULL; } /****************************************************************************** * * * Paramètres : snap = gestionnaire d'instantanés à constituer. * * archive = archive en cours de lecture. * * * * Description : Associe une base de données aux instantanés chargés. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ bool g_db_snapshot_fill(GDbSnapshot *snap, struct archive *archive) { bool result; /* Bilan à retourner */ struct archive_entry *entry; /* Elément de l'archive */ int ret; /* Bilan d'un appel */ const char *path; /* Désignation d'un fichier */ const char *dot; /* Début de l'extension */ char *node_id; /* Identifiant de noeud */ snapshot_node_t *node; /* Instantané trouvé */ result = false; for (ret = archive_read_next_header(archive, &entry); ret == ARCHIVE_OK; ret = archive_read_next_header(archive, &entry)) { path = archive_entry_pathname(entry); if (!_endswith(path, ".db", &dot)) continue; node_id = strndup(path, dot - path); node = find_snapshot_node(snap->nodes, node_id); free(node_id); if (!setup_snapshot_node_db_path(node, snap->tmpdir, snap->hash)) break; if (!dump_archive_entry_into_file(archive, entry, node->path)) break; } if (ret != ARCHIVE_EOF) goto exit; if (!check_snapshot_nodes(snap->nodes)) goto exit; result = true; exit: return result; } /****************************************************************************** * * * Paramètres : snap = gestionnaire d'instantanés à consulter. * * xdoc = document XML à compléter. * * context = contexte pour les recherches. * * archive = archive en cours de constitution. * * * * Description : Enregistre tous les éléments associés aux instantanés. * * * * Retour : Bilan de l'opération sous forme de code d'erreur. * * * * Remarques : - * * * ******************************************************************************/ DBError g_db_snapshot_save(const GDbSnapshot *snap, xmlDocPtr xdoc, xmlXPathContextPtr context, struct archive *archive) { DBError result; /* Conclusion à retourner */ bool status; /* Bilan d'un ajout XML */ assert(snap->current != NULL); status = add_content_to_node(xdoc, context, "/ChrysalideBinary/CurrentSnapshot", snap->current->id); if (!status) result = DBE_XML_ERROR; else result = save_snapshot_node(snap->nodes, xdoc, context, archive); return result; } /****************************************************************************** * * * Paramètres : snap = gestionnaire d'instantanés à consulter. * * * * Description : Fournit l'identifiant de l'instanné courant. * * * * Retour : Identifiant de l'instantané courant. * * * * Remarques : - * * * ******************************************************************************/ const char *g_db_snapshot_get_current_id(const GDbSnapshot *snap) { char *result; /* Indentifiant à retourner */ assert(snap->current != NULL); if (snap->current == NULL) result = NULL; else result = snap->current->id; return result; } /****************************************************************************** * * * Paramètres : snap = gestionnaire d'instantanés à consulter. * * id = identifiant de l'instantané visé. * * * * Description : Fournit la base de données correspondant à instanné donné. * * * * Retour : Base de données liée à l'instantané demandé ou NULL. * * * * Remarques : - * * * ******************************************************************************/ sqlite3 *g_db_snapshot_get_database(const GDbSnapshot *snap, const char *id) { 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'"), id); result = NULL; } else { ret = sqlite3_open(node->path, &result); if (ret != SQLITE_OK) { if (result != NULL) sqlite3_close(result); result = NULL; } } return result; }