/* SPDX-License-Identifier: Apache-2.0 */ /** * Copyright 2026 Thorsten Töpper * * The database contains those tables: * - filenames * -> id INTEGER PRIMARY KEY * -> name TEXT * - paths * -> id INTEGER PRIMARY KEY * -> pathname TEXT * - fileinfo * -> id INTEGER PRIMARY KEY * -> p_id INTEGER * -> fn_id INTEGER * -> h_id INTEGER * -> size INTEGER * -> last_seen INTEGER * -> stat_bin BLOB * - hashes * -> id INTEGER PRIMARY KEY * -> blake2 TEXT * -> sha256 TEXT * -> sha512 TEXT * * @file database_interaction.c * * vim:ts=4:sw=4:expandtab */ #include #include #include #include #include #include #include #include #include #include "options.h" #include "database_interaction.h" #include "trace_macros.h" #include "file_processor.h" /*=========== DEFINES, CONSTANTS AND TYPES ===========*/ /*=========== GLOBAL VARIABLES ===========*/ sqlite3 *dbconn = NULL; /* The statements will be wrapped via a function dbi_STATEMENTNAME() for the outside */ sqlite3_stmt *select_filename_by_id, *select_filename_by_name, *select_path_by_id, *select_path_by_pathname, *select_hashes_by_id, *select_hashes_by_strings, *select_fileinfo_by_id, *select_fileinfo_by_id_resolved, *select_fileinfo_by_path_id, *select_fileinfo_by_filename_id, *select_fileinfo_by_path_filename_ids, *select_fileinfo_by_hash_path_filename_ids, *select_fileinfo_by_hash_id, *select_fileinfo_complete_table, *select_fileinfo_complete_table_resolved; sqlite3_stmt *insert_filename, *insert_pathname, *insert_hashes, *insert_fileinfo; sqlite3_stmt *update_fileinfo_last_seen, *update_fileinfo_complete; sqlite3_stmt *delete_fileinfo_by_id; /*=========== FUNCTIONS ===========*/ void create_tables(); int prepare_statements(); char *select_string_by_int(sqlite3_stmt *st, int64_t id); /* Writing this block way too often */ #define DBCONN_CHECK(x) \ if (dbconn==NULL){ LOGERR("ERROR: No database connection.\n");\ return x; } bool dbi_open(char *filename) { if (filename == NULL || filename[0] == '\0') { LOGERR("ERROR: No valid filename given.\n"); return false; } if (dbconn != NULL) { LOGERR("ERROR: There's already an open database\n"); return false; } if (sqlite3_open(filename, &dbconn) != SQLITE_OK) { LOGERR("ERROR: Failed to open database: %s\n", sqlite3_errmsg(dbconn)); sqlite3_close(dbconn); dbconn = NULL; return false; } create_tables(); if (prepare_statements() != 0) { return false; } return true; } void dbi_close() { /* TODO: sqlite3_finalize for all prepared statements */ #define LOCAL_FINALIZE(x) { sqlite3_finalize(x); x=NULL; } DBCONN_CHECK(); LOCAL_FINALIZE(select_filename_by_id); LOCAL_FINALIZE(select_filename_by_name); LOCAL_FINALIZE(select_path_by_id); LOCAL_FINALIZE(select_path_by_pathname); LOCAL_FINALIZE(select_hashes_by_id); LOCAL_FINALIZE(select_hashes_by_strings); LOCAL_FINALIZE(select_fileinfo_by_id); LOCAL_FINALIZE(select_fileinfo_by_id_resolved); LOCAL_FINALIZE(select_fileinfo_by_path_id); LOCAL_FINALIZE(select_fileinfo_by_filename_id); LOCAL_FINALIZE(select_fileinfo_by_path_filename_ids); LOCAL_FINALIZE(select_fileinfo_by_hash_id); LOCAL_FINALIZE(insert_filename); LOCAL_FINALIZE(insert_pathname); LOCAL_FINALIZE(insert_hashes); LOCAL_FINALIZE(insert_fileinfo); LOCAL_FINALIZE(update_fileinfo_last_seen); LOCAL_FINALIZE(update_fileinfo_complete); LOCAL_FINALIZE(delete_fileinfo_by_id); #undef LOCAL_FINALIZE sqlite3_close(dbconn); dbconn = NULL; } /** * Create the later used tables if they don't exist yet */ inline void create_tables() { char *err = NULL; sqlite3_exec(dbconn, "CREATE TABLE IF NOT EXISTS filenames ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE);", NULL, NULL, &err); if (err != NULL) { LOGERR("ERROR: Creation of table filenames failed: %s\n", err); sqlite3_free(err); err = NULL; } sqlite3_exec(dbconn, "CREATE TABLE IF NOT EXISTS paths ( id INTEGER PRIMARY KEY AUTOINCREMENT, pathname TEXT UNIQUE);", NULL, NULL, &err); if (err != NULL) { LOGERR("ERROR: Creation of table pathss failed: %s\n", err); sqlite3_free(err); err = NULL; } /* no UNIQUE here, as even for the rare case of a hash collission in a single algorithm, all three won't collide at the same time. */ sqlite3_exec(dbconn, "CREATE TABLE IF NOT EXISTS hashes ( id INTEGER PRIMARY KEY AUTOINCREMENT, blake2 TEXT, sha256 TEXT, sha512 TEXT );", NULL, NULL, &err); if (err != NULL) { LOGERR("ERROR: Creation of table hashes failed: %s\n", err); sqlite3_free(err); err = NULL; } sqlite3_exec(dbconn, "CREATE TABLE IF NOT EXISTS fileinfo ( id INTEGER PRIMARY KEY, p_id INTEGER, " "fn_id INTEGER, h_id INTEGER, size INTEGER, last_seen INTEGER, stat_struct BLOB, " "FOREIGN KEY(p_id) REFERENCES paths(id), FOREIGN KEY(fn_id) REFERENCES filenames(id), " "FOREIGN KEY(h_id) REFERENCES hashes(id));", NULL, NULL, &err); if (err != NULL) { LOGERR("ERROR: Creation of table fileinfo failed: %s\n", err); sqlite3_free(err); err = NULL; } } int prepare_statements() { int counter = 0; /* Error handling in KISS. */ #define LOCAL_PREP_STMT(q, s) { counter++; \ if ((sqlite3_prepare_v2(dbconn, q, -1, s, NULL)) != SQLITE_OK) { \ LOGERR("ERROR: Failed to prepare statement %d '%s': %s\n", \ counter, q, sqlite3_errmsg(dbconn)); return -1; } \ } /* SELECT */ LOCAL_PREP_STMT("SELECT name FROM filenames WHERE id = ? ;", &select_filename_by_id); LOCAL_PREP_STMT("SELECT id FROM filenames WHERE name = ? ;", &select_filename_by_name); LOCAL_PREP_STMT("SELECT pathname FROM paths WHERE id = ? ;", &select_path_by_id); LOCAL_PREP_STMT("SELECT id FROM paths WHERE pathname = ? ;", &select_path_by_pathname); LOCAL_PREP_STMT("SELECT blake2, sha256, sha512 FROM hashes WHERE id = ? ;", &select_hashes_by_id); LOCAL_PREP_STMT("SELECT id FROM hashes WHERE blake2 = ? AND sha256 = ? AND sha512 = ? ;", &select_hashes_by_strings); LOCAL_PREP_STMT("SELECT * FROM fileinfo WHERE id = ? ;", &select_fileinfo_by_id); LOCAL_PREP_STMT("SELECT * FROM fileinfo WHERE p_id = ? ;", &select_fileinfo_by_path_id); LOCAL_PREP_STMT("SELECT * FROM fileinfo WHERE fn_id = ? ;", &select_fileinfo_by_filename_id); LOCAL_PREP_STMT("SELECT * FROM fileinfo WHERE p_id = ? AND fn_id = ? ;", &select_fileinfo_by_path_filename_ids); LOCAL_PREP_STMT("SELECT * FROM fileinfo WHERE h_id = ? AND p_id = ? AND fn_id = ? ;", &select_fileinfo_by_hash_path_filename_ids); LOCAL_PREP_STMT("SELECT * FROM fileinfo WHERE h_id = ? ;", &select_fileinfo_by_hash_id); /* TODO: so far the only query with JOINs or masking it in another way? * Many years since */ LOCAL_PREP_STMT("SELECT paths.path_name, filenames.name, hashes.blake2, hashes.sha256, hashes.512, fileinfo.size, fileinfo.last_seen, fileinfo.stat_struct FROM fileinfo INNER JOIN paths ON fileinfo.p_id = paths.id INNER JOIN filenames ON fileinfo.fn_id = filenames.id INNER JOIN hashes ON fileinfo.h_id = hashes.id WHERE fileinfo.id = ? ;", &select_fileinfo_by_id_resolved); LOCAL_PREP_STMT("SELECT paths.path_name, filenames.name, hashes.blake2, hashes.sha256, hashes.512, fileinfo.size, fileinfo.last_seen, fileinfo.stat_struct FROM fileinfo INNER JOIN paths ON fileinfo.p_id = paths.id INNER JOIN filenames ON fileinfo.fn_id = filenames.id INNER JOIN hashes ON fileinfo.h_id = hashes.id ;", &select_fileinfo_complete_table_resolved); LOCAL_PREP_STMT("SELECT p_id, fn_id, h_id, size, last_seen, stat_struct FROM fileinfo ;", &select_fileinfo_complete_table); /* INSERT */ LOCAL_PREP_STMT("INSERT INTO filenames (name) VALUES (?);", &insert_filename); LOCAL_PREP_STMT("INSERT INTO paths (pathname) VALUES (?);", &insert_pathname); LOCAL_PREP_STMT("INSERT INTO hashes (blake2, sha256, sha512) VALUES (?, ?, ?);", &insert_hashes); LOCAL_PREP_STMT("INSERT INTO fileinfo (p_id, fn_id, h_id, size, last_seen, stat_struct) " "VALUES (?, ?, ?, ?, ?, ?);", &insert_fileinfo); /* UPDATE */ LOCAL_PREP_STMT("UPDATE fileinfo SET last_seen = @time WHERE id = @id ;", &update_fileinfo_last_seen); LOCAL_PREP_STMT("UPDATE fileinfo SET p_id = @pid , fn_id = @fnid , h_id = @hid , " "size = @sz , last_seen = @ls, stat_struct = @stat WHERE id = @id ;", &update_fileinfo_complete); /* DELETE */ LOCAL_PREP_STMT("DELETE FROM fileinfo WHERE id = ? ;", &delete_fileinfo_by_id); #undef LOCAL_PREP_STMT return 0; } /** * To be wrapped for simple SELECT text ... WHERE PK = id; statements. * @param st A prepared statement * @param id A 64 bit integer used as primary key in the query. * @return NULL in case of error * copy of the database content */ char *select_string_by_int(sqlite3_stmt *st, int64_t id) { char *result = NULL; int strc = 0; const char *text; DBCONN_CHECK(NULL); if (st == NULL) { LOGERR("ERROR: No prepared statement.\n"); return NULL; } if (id < 1) { //LOGERR("ERROR: Invalid id %" PRId64 "\n", id); LOGERR("ERROR: Invalid id %ld\n", id); return NULL; } if (sqlite3_bind_int64(st, 1, id) != SQLITE_OK) { // LOGERR("ERROR: Failed to bind id %" PRId64 " to prepared statement: %s\n", sqlite3_errmsg(dbconn)); LOGERR("ERROR: Failed to bind id %ld to prepared statement: %s\n", id, sqlite3_errmsg(dbconn)); return NULL; } strc = sqlite3_step(st); /* Dont' forget: the sqlite3_reset() call must be executed! */ if (strc == SQLITE_ROW) { text = (const char *)sqlite3_column_text(st, 0); if ((result = calloc((strlen(text)+1), sizeof(char))) == NULL) { LOGERR("ERROR: Failed to allocate memory for copy of query result.\n"); sqlite3_reset(st); return NULL; } memcpy(result, text, strlen(text)); } else if (strc != SQLITE_DONE) { LOGERR("ERROR: Failed step: %s\n", sqlite3_errmsg(dbconn)); } sqlite3_clear_bindings(st); sqlite3_reset(st); return result; } /** * * @return 0 if ok, <0 in case of error */ int insert_text(sqlite3_stmt *st, int64_t (*check_function)(const char*), const char *text) { int strc = 0; DBCONN_CHECK(-1); if (st == NULL) { LOGERR("ERROR: No prepared statement.\n"); return -1; } if (text == NULL) { LOGERR("ERROR: No content to insert.\n"); return -1; } /* CHECK WHETHER THE ENTRY ALREADY EXISTS! */ if ((check_function != NULL) && (*check_function)(text) > 0) { return 0; } if (sqlite3_bind_text(st, 1, text, -1, SQLITE_TRANSIENT) != SQLITE_OK) { LOGERR("ERROR: Failed to bind text '%s' to statement: %s\n", text, sqlite3_errmsg(dbconn)); return -1; } strc = sqlite3_step(st); if (strc != SQLITE_DONE) { LOGERR("ERROR: Failed to insert text '%s' into db: %s\n", text, sqlite3_errmsg(dbconn)); } sqlite3_clear_bindings(st); sqlite3_reset(st); return (strc == SQLITE_DONE) ? 0 : -1; } /** * To be wrapped for simple SELECT text ... WHERE COL = string; statements COL being UNIQUE. * @param st A prepared statement * @param id a string bound to the WHERE in the statement * @return < -1 in case of error * 0 if not found * >0 the id in the database */ int64_t select_int_by_string(sqlite3_stmt *st, const char *s) { int64_t result = -1; int strc = 0; DBCONN_CHECK(-2); if (st == NULL) { LOGERR("ERROR: No prepared statement.\n"); return -2; } if (s == NULL || strlen(s)==0) { LOGERR("ERROR: Invalid string %s\n", s); return -2; } sqlite3_clear_bindings(st); sqlite3_reset(st); if (sqlite3_bind_text(st, 1, s, -1, SQLITE_TRANSIENT) != SQLITE_OK) { LOGERR("ERROR: Failed to bind string %s to prepared statement: %s\n", s, sqlite3_errmsg(dbconn)); return -2; } strc = sqlite3_step(st); /* Dont' forget: the sqlite3_reset() call must be executed! */ if (strc == SQLITE_ROW) { result = (int64_t) sqlite3_column_int64(st, 0); } else if (strc != SQLITE_DONE) { LOGERR("ERROR: Failed step: %s\n", sqlite3_errmsg(dbconn)); result = -2; } else { /* SQLITE_DONE => EMPTY */ DBGTRC("DEBUG: Combination not found in db\n"); result = 0; } sqlite3_clear_bindings(st); sqlite3_reset(st); return result; } char *dbi_select_filename_by_id(int64_t id) { return select_string_by_int(select_filename_by_id, id); } char *dbi_select_path_by_id(int64_t id) { return select_string_by_int(select_path_by_id, id); } int64_t dbi_select_filename_by_name(const char *name) { return select_int_by_string(select_filename_by_name, name); } int64_t dbi_select_path_by_pathname(const char *pathname) { return select_int_by_string(select_path_by_pathname, pathname); } int dbi_insert_filename(const char *filename) { return insert_text(insert_filename, dbi_select_filename_by_name, filename); } int dbi_insert_pathname(const char *path) { return insert_text(insert_pathname, dbi_select_path_by_pathname, path); } int64_t dbi_select_hashes_by_strings(const char *blake2, const char *sha256, const char *sha512) { int64_t result = 0; int strc = 0; sqlite3_stmt *st = select_hashes_by_strings; DBCONN_CHECK(-2); if (blake2 == NULL || sha256 == NULL || sha512 == NULL || strlen(blake2)==0 || strlen(sha256)==0 || strlen(sha512)==0) { LOGERR("ERROR: Invalid argument: blake2=%s sha256=%s sha512=%s\n", blake2, sha256, sha512); return -2; } /* Avoid conflict with earlier calls */ sqlite3_clear_bindings(st); sqlite3_reset(st); if (sqlite3_bind_text(st, 1, blake2, -1, SQLITE_TRANSIENT) != SQLITE_OK) { LOGERR("ERROR: Failed to bind field blake2 to prepared statement: %s\n", sqlite3_errmsg(dbconn)); return -2; } if (sqlite3_bind_text(st, 2, sha256, -1, SQLITE_TRANSIENT) != SQLITE_OK) { LOGERR("ERROR: Failed to bind field sha256 to prepared statement: %s\n", sqlite3_errmsg(dbconn)); return -2; } if (sqlite3_bind_text(st, 3, sha512, -1, SQLITE_TRANSIENT) != SQLITE_OK) { LOGERR("ERROR: Failed to bind field sha512 to prepared statement: %s\n", sqlite3_errmsg(dbconn)); return -2; } strc = sqlite3_step(st); /* Dont' forget: the sqlite3_reset() call must be executed! */ if (strc == SQLITE_ROW) { result = (int64_t) sqlite3_column_int64(st, 0); } else if (strc != SQLITE_DONE) { LOGERR("ERROR: Failed step: %s\n", sqlite3_errmsg(dbconn)); result = -2; } else { /* SQLITE_DONE => EMPTY */ DBGTRC("DEBUG: Combination not found in db\n"); result = 0; } sqlite3_clear_bindings(st); sqlite3_reset(st); return result; } int dbi_insert_hashes(const char *blake2, const char *sha256, const char *sha512) { int rc = 0; int64_t strc = 0; sqlite3_stmt *st = insert_hashes; DBCONN_CHECK(-2); if (blake2 == NULL || sha256 == NULL || sha512 == NULL || strlen(blake2)==0 || strlen(sha256)==0 || strlen(sha512)==0) { LOGERR("ERROR: Invalid argument: blake2=%s sha256=%s sha512=%s\n", blake2, sha256, sha512); return -2; } if (dbi_select_hashes_by_strings(blake2, sha256, sha512) > 0) { return 0; } /* Avoid conflict with earlier calls */ sqlite3_clear_bindings(st); sqlite3_reset(st); if (sqlite3_bind_text(st, 1, blake2, -1, SQLITE_TRANSIENT) != SQLITE_OK) { LOGERR("ERROR: Failed to bind field blake2 to prepared statement: %s\n", sqlite3_errmsg(dbconn)); return -2; } if (sqlite3_bind_text(st, 2, sha256, -1, SQLITE_TRANSIENT) != SQLITE_OK) { LOGERR("ERROR: Failed to bind field sha256 to prepared statement: %s\n", sqlite3_errmsg(dbconn)); return -2; } if (sqlite3_bind_text(st, 3, sha512, -1, SQLITE_TRANSIENT) != SQLITE_OK) { LOGERR("ERROR: Failed to bind field sha512 to prepared statement: %s\n", sqlite3_errmsg(dbconn)); return -2; } strc = sqlite3_step(st); if (strc != SQLITE_DONE) { LOGERR("ERROR: Failed to insert hashes (blake2=%s, sha256=%s, sha512=%s) into db: %s\n", blake2, sha256, sha512, sqlite3_errmsg(dbconn)); rc = -2; } else { rc = 0; } sqlite3_clear_bindings(st); sqlite3_reset(st); return rc; } struct df_hashstrings *dbi_select_hashes_by_id(int64_t id) { struct df_hashstrings *result = NULL; int strc = 0; sqlite3_stmt *st = select_hashes_by_id; const char *text; DBCONN_CHECK(NULL); if (id < 1) { LOGERR("ERROR: invalid id %lld\n", (long long int)id); /* TODO: Macro resolve not ok */ return NULL; } if (sqlite3_bind_int64(st, 1, id) != SQLITE_OK) { // LOGERR("ERROR: Failed to bind id %" PRId64 " to prepared statement: %s\n", sqlite3_errmsg(dbconn)); LOGERR("ERROR: Failed to bind id %ld to prepared statement: %s\n", id, sqlite3_errmsg(dbconn)); return NULL; } strc = sqlite3_step(st); /* Dont' forget: the sqlite3_reset() call must be executed! */ if (strc == SQLITE_ROW) { if ((result = calloc(1, sizeof(struct df_hashstrings))) == NULL) { LOGERR("ERROR: Failed to allocate memory for copy of query result.\n"); sqlite3_reset(st); return NULL; } text = (const char *)sqlite3_column_text(st, 0); memcpy(result->blake2, text, strlen(text)); text = (const char *)sqlite3_column_text(st, 1); memcpy(result->sha256, text, strlen(text)); text = (const char *)sqlite3_column_text(st, 2); memcpy(result->sha512, text, strlen(text)); } else if (strc != SQLITE_DONE) { LOGERR("ERROR: Failed step: %s\n", sqlite3_errmsg(dbconn)); } sqlite3_clear_bindings(st); sqlite3_reset(st); return result; } int64_t dbi_select_fileinfo_by_hash_path_filename_ids(int64_t hash_id, int64_t path_id, int64_t filename_id) { int64_t result = 0; int strc = 0; sqlite3_stmt *st = select_fileinfo_by_hash_path_filename_ids; DBCONN_CHECK(-2); sqlite3_clear_bindings(st); sqlite3_reset(st); if ( hash_id < 1 || path_id < 1 || filename_id < 1 ) { LOGERR("ERROR: At least one invalid id: hashes %ld | path %ld | filename %ld\n", hash_id, path_id, filename_id); return -2; } if (sqlite3_bind_int64(st, 1, hash_id) != SQLITE_OK) { LOGERR("ERROR: Failed to bind hash_id %ld to prepared statement: %s\n", hash_id, sqlite3_errmsg(dbconn)); return -2; } if (sqlite3_bind_int64(st, 2, path_id) != SQLITE_OK) { LOGERR("ERROR: Failed to bind path_id %ld to prepared statement: %s\n", path_id, sqlite3_errmsg(dbconn)); return -2; } if (sqlite3_bind_int64(st, 3, filename_id) != SQLITE_OK) { LOGERR("ERROR: Failed to bind filename_id %ld to prepared statement: %s\n", filename_id, sqlite3_errmsg(dbconn)); return -2; } strc = sqlite3_step(st); /* Dont' forget: the sqlite3_reset() call must be executed! */ if (strc == SQLITE_ROW) { result = (int64_t) sqlite3_column_int64(st, 0); } else if (strc != SQLITE_DONE) { LOGERR("ERROR: Failed step: %s\n", sqlite3_errmsg(dbconn)); result = -2; } else { /* SQLITE_DONE => EMPTY */ DBGTRC("DEBUG: Combination not found in db\n"); result = 0; } sqlite3_clear_bindings(st); sqlite3_reset(st); return result; } int64_t dbi_select_fileinfo_by_path_filename_ids(int64_t pname_id, int64_t fname_id) { int64_t result = 0; int strc = 0; sqlite3_stmt *st = select_fileinfo_by_path_filename_ids; DBCONN_CHECK(-2); sqlite3_clear_bindings(st); sqlite3_reset(st); if ( pname_id < 1 || fname_id < 1 ) { LOGERR("ERROR: At least one invalid id: path %ld | filename %ld\n", pname_id, fname_id); return -2; } if (sqlite3_bind_int64(st, 1, pname_id) != SQLITE_OK) { LOGERR("ERROR: Failed to bind pname_id %ld to prepared statement: %s\n", pname_id, sqlite3_errmsg(dbconn)); return -2; } if (sqlite3_bind_int64(st, 2, fname_id) != SQLITE_OK) { LOGERR("ERROR: Failed to bind fname_id %ld to prepared statement: %s\n", fname_id, sqlite3_errmsg(dbconn)); return -2; } strc = sqlite3_step(st); /* Dont' forget: the sqlite3_reset() call must be executed! */ if (strc == SQLITE_ROW) { result = (int64_t) sqlite3_column_int64(st, 0); } else if (strc != SQLITE_DONE) { LOGERR("ERROR: Failed step: %s\n", sqlite3_errmsg(dbconn)); result = -2; } else { /* SQLITE_DONE => EMPTY */ DBGTRC("DEBUG: Combination not found in db\n"); result = 0; } sqlite3_clear_bindings(st); sqlite3_reset(st); return result; } int dbi_update_fileinfo_last_seen(int64_t id) { int rc = -1, strc = 0; time_t ts = time(NULL); sqlite3_stmt *st = update_fileinfo_last_seen; DBCONN_CHECK( -2 ); if (id < 1) { LOGERR("ERROR: Invalid id.\n"); return -1; } sqlite3_clear_bindings(st); sqlite3_reset(st); if (sqlite3_bind_int64(st, 1, ts) != SQLITE_OK) { LOGERR("ERROR: Failed to bind last_seen timestamp to prepared statement: %s\n", sqlite3_errmsg(dbconn)); return -2; } if (sqlite3_bind_int64(st, 2, id) != SQLITE_OK) { LOGERR("ERROR: Failed to bind last_seen timestamp to prepared statement: %s\n", sqlite3_errmsg(dbconn)); return -2; } strc = sqlite3_step(st); if (strc != SQLITE_DONE) { LOGERR("ERROR: Failed to update last_seen timestamp for entry %ld: %s\n", id, sqlite3_errmsg(dbconn)); rc = -2; } else { rc = 0; } sqlite3_clear_bindings(st); sqlite3_reset(st); return rc; } int update_fileinfo_function(struct df_fileinfo *fi, int64_t existing_id, int64_t pname_id, int64_t fname_id, int64_t hashes_id) { int rc = -1, strc = 0; time_t ts = time(NULL); sqlite3_stmt *st = update_fileinfo_complete; DBCONN_CHECK( -2 ); if (fi == NULL) { LOGERR("ERROR: Invalid argument.\n"); return -2; } if (existing_id < 1) { /* TODO: ALL possible situations need to be checked */ if (fname_id < 1) { fname_id = dbi_select_filename_by_name(fi->name); } if (pname_id < 1) { pname_id = dbi_select_path_by_pathname(fi->path); } } else { LOGERR("ERROR: No entry given.\n"); return -2; } /* Possibly new hashes so always INSERT and use the return which was given */ if (hashes_id < 1) { if (dbi_insert_hashes(fi->hashes.blake2, fi->hashes.sha256, fi->hashes.sha512) < 0) { LOGERR("ERROR: abort due to previous error.\n"); return -2; } hashes_id = dbi_select_hashes_by_strings(fi->hashes.blake2, fi->hashes.sha256, fi->hashes.sha512); } sqlite3_clear_bindings(st); sqlite3_reset(st); if (sqlite3_bind_int64(st, 1, pname_id) != SQLITE_OK) { LOGERR("ERROR: Failed to bind path_id to prepared statement: %s\n", sqlite3_errmsg(dbconn)); return -2; } if (sqlite3_bind_int64(st, 2, fname_id) != SQLITE_OK) { LOGERR("ERROR: Failed to bind filename_id to prepared statement: %s\n", sqlite3_errmsg(dbconn)); return -2; } if (sqlite3_bind_int64(st, 3, hashes_id) != SQLITE_OK) { LOGERR("ERROR: Failed to bind filename_id to prepared statement: %s\n", sqlite3_errmsg(dbconn)); return -2; } if (sqlite3_bind_int64(st, 4, fi->statbuf.st_size) != SQLITE_OK) { LOGERR("ERROR: Failed to bind size to prepared statement: %s\n", sqlite3_errmsg(dbconn)); return -2; } if (sqlite3_bind_int64(st, 5, ts) != SQLITE_OK) { LOGERR("ERROR: Failed to bind last_seen timestamp to prepared statement: %s\n", sqlite3_errmsg(dbconn)); return -2; } if (sqlite3_bind_blob(st, 6, &(fi->statbuf), sizeof(struct stat), SQLITE_TRANSIENT) != SQLITE_OK) { LOGERR("ERROR: Failed to bind last_seen timestamp to prepared statement: %s\n", sqlite3_errmsg(dbconn)); return -2; } strc = sqlite3_step(st); if (strc != SQLITE_DONE) { LOGERR("ERROR: Failed to completely update entry %ld: %s\n", existing_id, sqlite3_errmsg(dbconn)); rc = -2; } else { rc = 0; } sqlite3_clear_bindings(st); sqlite3_reset(st); return rc; } int dbi_update_fileinfo_complete(struct df_fileinfo *fi, int64_t existing_id) { return update_fileinfo_function(fi, existing_id, -1, -1, -1); } /** * Wrapper function around several other inserts */ int dbi_insert_fileinfo(struct df_fileinfo *fi) { int rc = 0, strc = 0; int64_t fname_id, pname_id, hashes_id, existing_entry = 0; time_t ts = 0; sqlite3_stmt *st = insert_fileinfo; DBCONN_CHECK(-2); if (fi == NULL) { LOGERR("ERROR: No fileinfo given.\n"); return -2; } if (dbi_insert_filename(fi->name) < 0) { LOGERR("ERROR: abort due to previous error.\n"); return -2; } fname_id = dbi_select_filename_by_name(fi->name); if (dbi_insert_pathname(fi->path) < 0) { LOGERR("ERROR: abort due to previous error.\n"); return -2; } pname_id = dbi_select_path_by_pathname(fi->path); /* TODO: Take some time and decide whether it shall stay like this or hand over the struct */ if (dbi_insert_hashes(fi->hashes.blake2, fi->hashes.sha256, fi->hashes.sha512) < 0) { LOGERR("ERROR: abort due to previous error.\n"); return -2; } hashes_id = dbi_select_hashes_by_strings(fi->hashes.blake2, fi->hashes.sha256, fi->hashes.sha512); /* Any problems with the selects? */ if (fname_id <0 || pname_id <0 || hashes_id <0) { LOGERR("ERROR: abort due to previous error.\n"); return -2; } ts = time(NULL); /* TODO: There also belongs a query whether the fullpath already has an entry, if so and hash_id * is different, an UPDATE and not an insert is required. */ existing_entry = dbi_select_fileinfo_by_hash_path_filename_ids(hashes_id, pname_id, fname_id); if (existing_entry > 0) { return dbi_update_fileinfo_last_seen(existing_entry); } /* fullpath entry exists, but the hashes mismatch. */ existing_entry = dbi_select_fileinfo_by_path_filename_ids(pname_id, fname_id); if (existing_entry > 0) { return update_fileinfo_function(fi, existing_entry, pname_id, fname_id, hashes_id); } sqlite3_clear_bindings(st); sqlite3_reset(st); if (sqlite3_bind_int64(st, 1, pname_id) != SQLITE_OK) { LOGERR("ERROR: Failed to bind path_id to prepared statement: %s\n", sqlite3_errmsg(dbconn)); return -2; } if (sqlite3_bind_int64(st, 2, fname_id) != SQLITE_OK) { LOGERR("ERROR: Failed to bind filename_id to prepared statement: %s\n", sqlite3_errmsg(dbconn)); return -2; } if (sqlite3_bind_int64(st, 3, hashes_id) != SQLITE_OK) { LOGERR("ERROR: Failed to bind filename_id to prepared statement: %s\n", sqlite3_errmsg(dbconn)); return -2; } if (sqlite3_bind_int64(st, 4, fi->statbuf.st_size) != SQLITE_OK) { LOGERR("ERROR: Failed to bind size to prepared statement: %s\n", sqlite3_errmsg(dbconn)); return -2; } if (sqlite3_bind_int64(st, 5, ts) != SQLITE_OK) { LOGERR("ERROR: Failed to bind last_seen timestamp to prepared statement: %s\n", sqlite3_errmsg(dbconn)); return -2; } if (sqlite3_bind_blob(st, 6, &(fi->statbuf), sizeof(struct stat), SQLITE_TRANSIENT) != SQLITE_OK) { LOGERR("ERROR: Failed to bind last_seen timestamp to prepared statement: %s\n", sqlite3_errmsg(dbconn)); return -2; } strc = sqlite3_step(st); if (strc != SQLITE_DONE) { LOGERR("ERROR: Failed to insert fileinfo for %s/%s into db: %s\n", fi->path, fi->name, sqlite3_errmsg(dbconn)); rc = -2; } else { rc = 0; } sqlite3_clear_bindings(st); sqlite3_reset(st); return rc; }; #if 0 *select_fileinfo_by_id, *select_fileinfo_by_id_resolved, *select_fileinfo_by_path_id, *select_fileinfo_by_filename_id, *select_fileinfo_by_path_filename_ids, *select_fileinfo_by_hash_id; *select_fileinfo_complete_table, *select_fileinfo_complete_table_resolved; sqlite3_stmt *delete_fileinfo_by_id; #endif