diff --git a/include/database.h b/include/database.h index 6e22d90..09989c4 100644 --- a/include/database.h +++ b/include/database.h @@ -4,39 +4,26 @@ #include #include +#include #include #define HASH_LEN 32 enum lm_query_index { - QUERY_CREATE_ENTRY_TABLE = 0, - QUERY_INSERT_ENTRY_SINGLE = 1, - QUERY_SELECT_ENTRY_SINGLE_1 = 2, - QUERY_SELECT_ENTRY_SINGLE_2 = 3, - QUERY_DELETE_ENTRY_SINGLE = 4, - QUERY_SELECT_ENTRY_ALL = 5, - - QUERY_CREATE_FILE_TABLE = 6, - QUERY_INSERT_FILE_SINGLE = 7, - QUERY_DELETE_FILE_ALL = 8, - QUERY_DELETE_FILE_SINGLE = 9, - QUERY_SELECT_FILE_ALL = 10, - QUERY_SELECT_FILE_SINGLE = 11, - QUERY_UPDATE_FILE_1 = 12, - QUERY_UPDATE_FILE_2 = 13, + QUERY_CREATE_TABLE = 0, + QUERY_INSERT_SINGLE = 1, + QUERY_SELECT_SINGLE_1 = 2, + QUERY_SELECT_SINGLE_2 = 3, + QUERY_DELETE_SINGLE = 4, + QUERY_SELECT_ALL = 5, }; enum lm_columns { - FILES_COLUMN_PATH = 1, - FILES_COLUMN_HASH = 2, - FILES_COLUMN_KEEP = 3, - FILES_COLUMN_ENTRY = 4, - - ENTRIES_COLUMN_NAME = 1, - ENTRIES_COLUMN_VERSION = 2, - ENTRIES_COLUMN_DESC = 3, - ENTRIES_COLUMN_SIZE = 4, - ENTRIES_COLUMN_DEPENDS = 5, + COLUMN_NAME = 1, + COLUMN_VERSION = 2, + COLUMN_DESC = 3, + COLUMN_SIZE = 4, + COLUMN_DEPENDS = 5, }; extern char *queries[]; @@ -44,13 +31,10 @@ extern char *queries[]; typedef lm_pkg_data_t lm_entry_t; typedef struct lm_database { - sqlite3 *entries_db; - sqlite3_stmt *entries_st; - - sqlite3 *files_db; - sqlite3_stmt *files_st; - - char *dir; + sqlite3 *sql; + sqlite3_stmt *st; + FILE *filesp; + char *dir; } lm_database_t; void lm_entry_init(lm_entry_t *entry); @@ -79,8 +63,9 @@ bool lm_database_files_next( lm_database_t *db, lm_entry_t *entry, char **path, char **hash, bool *keep); // gets the next file of the entry bool lm_database_files_add( lm_database_t *db, lm_entry_t *entry, char *path, char *hash); // adds a file to the files database -bool lm_database_files_del(lm_database_t *db, lm_entry_t *entry); // dels all files of belonging to a entry -bool lm_database_files_del_single(lm_database_t *db, char *path); +bool lm_database_files_del( + lm_database_t *db, lm_entry_t *entry); // dels files belonging to an entry that is not set as KEEP +bool lm_database_files_del_all(lm_database_t *db, lm_entry_t *entry); // dels all files of belonging to an entry void lm_database_files_next_free(lm_database_t *db, lm_entry_t *entry, char **path, char **hash, bool *keep); // frees resources used for lm_database_files_next diff --git a/include/error.h b/include/error.h index d86f6b6..c9a2296 100644 --- a/include/error.h +++ b/include/error.h @@ -81,7 +81,7 @@ typedef enum lm_error { LM_ERR_DbFilesOpenFail = 80, LM_ERR_DbFilesDirFail = 81, LM_ERR_DbFilesUnlinkFail = 82, - LM_ERR_DbFilesWriteFail = 83, + LM_ERR_DbFilesRenameFail = 83, LM_ERR_DbKeepsNotFound = 84, LM_ERR_DbKeepsOpenFail = 85, LM_ERR_DbKeepsDirFail = 86, diff --git a/locale/tr/LC_MESSAGES/libmp.po b/locale/tr/LC_MESSAGES/libmp.po index a4cb59e..8540f74 100644 --- a/locale/tr/LC_MESSAGES/libmp.po +++ b/locale/tr/LC_MESSAGES/libmp.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-11 01:42+0300\n" +"POT-Creation-Date: 2024-08-15 02:14+0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -352,23 +352,27 @@ msgid "pool directory sub-paths are not accessible" msgstr "" #: src/error.c:100 -msgid "package file list not found in the database" +msgid "file list not found for the package" msgstr "" #: src/error.c:101 -msgid "failed to open package file list in the database" +#, c-format +msgid "failed to rename the file list for the package: %s" msgstr "" #: src/error.c:102 -msgid "failed to access package file list database directory" +#, c-format +msgid "failed to open the package file list: %s" msgstr "" #: src/error.c:103 -msgid "failed to remove package file list from the database" +#, c-format +msgid "failed to open the database directory: %s" msgstr "" #: src/error.c:104 -msgid "failed to write to the file list in the database" +#, c-format +msgid "failed to remove package file list: %s" msgstr "" #: src/error.c:105 diff --git a/src/ctx/check.c b/src/ctx/check.c index 1409e41..542e563 100644 --- a/src/ctx/check.c +++ b/src/ctx/check.c @@ -47,7 +47,8 @@ bool lm_ctx_check(lm_ctx_t *ctx, lm_entry_t *entry, lm_ctx_check_callback_t call if(S_ISLNK(st.st_mode)){ pdebug(__func__, "%s seems to be a link, no hash verification to do", fp); - if(hash[0] != '\0'){ + // hashes for links are just "#" chars + if(hash[0] != '#'){ lm_error_set(LM_ERR_FileNotLink, fp); goto end; } diff --git a/src/ctx/install.c b/src/ctx/install.c index 98b8680..06ed6cc 100644 --- a/src/ctx/install.c +++ b/src/ctx/install.c @@ -137,14 +137,14 @@ bool __lm_ctx_extract_files(lm_ctx_t *ctx, lm_pkg_t *pkg, char *files, lm_ctx_in current += archive_entry_size(entry); type = archive_entry_filetype(entry); - if(exists(entry_path, NULL) && lm_database_files_iskeep(ctx->db, entry_path)){ + if(exists(entry_path, NULL) && lm_package_data_keep_contains(&pkg->data, entry_path)){ pdebug(__func__, "not extracting %s, file is set as KEEP", entry_path); continue; } switch (type) { case AE_IFLNK: - if(!lm_database_files_add(ctx->db, &pkg->data, entry_path, "")){ + if(!lm_database_files_add(ctx->db, &pkg->data, "################################", entry_path)){ char *suberr = lm_strerror_dup(); pdebug(__func__, "failed to add link to the database for %s: %s", pkg->data.name, suberr); lm_error_set(LM_ERR_PkgFilesAddFail, entry_path, suberr); @@ -270,6 +270,8 @@ bool lm_ctx_install(lm_ctx_t *ctx, lm_pkg_t *pkg, bool run_install, lm_ctx_insta lm_error_set(LM_ERR_PkgDataNotMatch); goto end; } + + pdebug(__func__, "updating changes file for %s", pkg->data.name); if(!lm_database_changes_update(ctx->db, &pkg->data, files->changes_file)){ char *suberr = lm_strerror_dup(); @@ -284,6 +286,13 @@ bool lm_ctx_install(lm_ctx_t *ctx, lm_pkg_t *pkg, bool run_install, lm_ctx_insta goto end; } + pdebug(__func__, "deleting file list for %s", pkg->data.name); + + if(!lm_database_files_del_all(ctx->db, &pkg->data)){ + pdebug(__func__, "failed to remove file list for %s: %s", pkg->data.name, lm_error()); + goto end; + } + while((line_len = getline(&line, (size_t*)&line_len, hashes)) > 0){ if(NULL == line) goto next; @@ -306,12 +315,7 @@ bool lm_ctx_install(lm_ctx_t *ctx, lm_pkg_t *pkg, bool run_install, lm_ctx_insta pdebug(__func__, "(%lu) %s => %s", line_len, file, hash); - if(!lm_database_files_del_single(ctx->db, file)){ - pdebug(__func__, "failed to remove file from the database for %s: %s", pkg->data.name, lm_error()); - goto end; - } - - if(!lm_database_files_add(ctx->db, &pkg->data, file, hash)){ + if(!lm_database_files_add(ctx->db, &pkg->data, hash, file)){ char *suberr = lm_strerror_dup(); pdebug(__func__, "failed to add file to the database for %s: %s", pkg->data.name, suberr); lm_error_set(LM_ERR_PkgFilesAddFail, file, suberr); @@ -324,6 +328,8 @@ bool lm_ctx_install(lm_ctx_t *ctx, lm_pkg_t *pkg, bool run_install, lm_ctx_insta line = NULL; line_len = 0; } + + pdebug(__func__, "extracting files for %s", pkg->data.name); if(!__lm_ctx_extract_files(ctx, pkg, files->files_archive, callback, data)){ char *suberr = lm_strerror_dup(); @@ -332,6 +338,8 @@ bool lm_ctx_install(lm_ctx_t *ctx, lm_pkg_t *pkg, bool run_install, lm_ctx_insta free(suberr); goto end; } + + pdebug(__func__, "adding an entry for %s", pkg->data.name); if(!lm_database_entry_add(ctx->db, &pkg->data)){ char *suberr = lm_strerror_dup(); @@ -341,6 +349,11 @@ bool lm_ctx_install(lm_ctx_t *ctx, lm_pkg_t *pkg, bool run_install, lm_ctx_insta goto end; } + if(run_install) + pdebug(__func__, "running the install script for %s", pkg->data.name); + else + pdebug(__func__, "saving the install script for %s", pkg->data.name); + if(run_install && !__lm_ctx_run_install(ctx->root, files->install_file)){ char *suberr = lm_strerror_dup(); pdebug(__func__, "failed to run install script: %s", lm_strerror()); diff --git a/src/ctx/remove.c b/src/ctx/remove.c index 603d318..fc25a32 100644 --- a/src/ctx/remove.c +++ b/src/ctx/remove.c @@ -55,13 +55,16 @@ bool lm_ctx_remove(lm_ctx_t *ctx, lm_entry_t *entry, lm_ctx_remove_callback_t ca size_t total = 0, current = 0; total = lm_database_files_count(ctx->db, entry); + pdebug(__func__, "removing %lu files", total); while(lm_database_files_next(ctx->db, entry, &path, &hash, &in_keep)){ - if(in_keep) + if(in_keep){ + pdebug(__func__, "not removing file because it is set as keep: %s", path); goto next; + } fpath = join_alloc(ctx->root, path); - pdebug(__func__, "removing file %s (%s)", fpath, entry->name); + pdebug(__func__, "removing file %s (%s)", fpath, hash); if(!exists(fpath, NULL)){ pdebug(__func__, "found file in database, but its not on the file system: %s", fpath); diff --git a/src/database/changes.c b/src/database/changes.c index 11c558b..5c89336 100644 --- a/src/database/changes.c +++ b/src/database/changes.c @@ -8,18 +8,31 @@ #include #include +size_t __lm_database_changes_get(lm_database_t *db, lm_entry_t *entry, char *path){ + size_t changes_size = strlen(entry->name)+15; + size_t full_size = changes_size + strlen(db->dir); + + if(NULL != path){ + char changes_file[changes_size]; + snprintf(changes_file, changes_size, "%s_changes", entry->name); + join(path, db->dir, changes_file); + } + + return full_size; +} + +#define __lm_changes_size() __lm_database_changes_get(db, entry, NULL) +#define __lm_changes_path(p) __lm_database_changes_get(db, entry, p) + bool lm_database_changes_update(lm_database_t *db, lm_entry_t *entry, char *file){ if(NULL == db || NULL == entry || NULL == file){ lm_error_set(LM_ERR_ArgNULL); return false; } - char changes_file[strlen(entry->name)+20]; - sprintf(changes_file, "%s_changes", entry->name); - - char changes_path[strlen(db->dir)+sizeof(changes_file)]; - join(changes_path, db->dir, changes_file); - + char changes_path[__lm_changes_size()]; + __lm_changes_path(changes_path); + if(!copy_file(changes_path, file)) return false; @@ -37,11 +50,8 @@ bool lm_database_changes_del(lm_database_t *db, lm_entry_t *entry){ return false; } - char changes_file[strlen(entry->name)+20]; - sprintf(changes_file, "%s_changes", entry->name); - - char changes_path[strlen(db->dir)+sizeof(changes_file)]; - join(changes_path, db->dir, changes_file); + char changes_path[__lm_changes_size()]; + __lm_changes_path(changes_path); if(unlink(changes_path) < 0 && errno != ENOENT){ lm_error_set(LM_ERR_DbChangesUnlinkFail); @@ -52,15 +62,16 @@ bool lm_database_changes_del(lm_database_t *db, lm_entry_t *entry){ } char *lm_database_changes_get(lm_database_t *db, lm_entry_t *entry){ - char changes_file[strlen(entry->name)+20]; - sprintf(changes_file, "%s_changes", entry->name); + if(NULL == db || NULL == entry){ + lm_error_set(LM_ERR_ArgNULL); + return false; + } - char *changes_path = malloc(strlen(db->dir)+sizeof(changes_file)); - join(changes_path, db->dir, changes_file); + char* changes_path = malloc(__lm_changes_size()); + __lm_changes_path(changes_path); if(!exists(changes_path, NULL)){ lm_error_set(LM_ERR_DbChangesNotExists); - free(changes_path); return NULL; } diff --git a/src/database/database.c b/src/database/database.c index 5a8958a..12acb62 100644 --- a/src/database/database.c +++ b/src/database/database.c @@ -31,34 +31,6 @@ char *queries[] = { // QUERY_SELECT_PACKAGE_ALL "SELECT * FROM packages", - - // QUERY_CREATE_FILE_TABLE - "CREATE TABLE IF NOT EXISTS files (" \ - " path TEXT PRIMARY KEY NOT NULL," \ - " hash TEXT NOT NULL," \ - " keep INT NOT NULL," \ - " package TEXT NOT NULL);", - - // QUERY_INSERT_FILE_SINGLE - "INSERT INTO files VALUES (?, ?, ?, ?)", - - // QUERY_DELETE_FILE_ALL - "DELETE FROM files WHERE package = ? AND keep = 0", - - // QUERY_DELETE_FILE_SINGLE - "DELETE FROM files WHERE path = ?", - - // QUERY_SELECT_FILE_ALL - "SELECT * FROM files WHERE package = ?", - - // QUERY_SELECT_FILE_SINGLE - "SELECT * FROM files WHERE path = ?", - - // QUERY_UPDATE_FILE_1 - "UPDATE files SET keep = 1 WHERE path = ?", - - // QUERY_UPDATE_FILE_2 - "UPDATE files SET keep = 0 WHERE path = ?", }; lm_database_t *lm_database_new(char *path){ @@ -77,35 +49,20 @@ lm_database_t *lm_database_new(char *path){ } size_t pathlen = strlen(path); - char packagesdb[pathlen+20], filesdb[pathlen+20]; - + char packagesdb[pathlen+15]; join(packagesdb, path, "packages.db"); - join(filesdb, path, "files.db"); - if(sqlite3_open(packagesdb, &db->entries_db)){ - pdebug(__func__, "(%s) failed to open databse: %s", packagesdb, sqlite3_errmsg(db->entries_db)); + if(sqlite3_open(packagesdb, &db->sql)){ + pdebug(__func__, "(%s) failed to open databse: %s", packagesdb, sqlite3_errmsg(db->sql)); lm_error_set(LM_ERR_DbSqlOpenFail); return NULL; } - if(sqlite3_open(filesdb, &db->files_db)){ - pdebug(__func__, "(%s) failed to open databse: %s", filesdb, sqlite3_errmsg(db->files_db)); - lm_error_set(LM_ERR_DbSqlOpenFail); - return NULL; - } - - if(sqlite3_exec(db->entries_db, queries[QUERY_CREATE_ENTRY_TABLE], NULL, 0, &err) != SQLITE_OK){ + if(sqlite3_exec(db->sql, queries[QUERY_CREATE_TABLE], NULL, 0, &err) != SQLITE_OK){ pdebug(__func__, "(%s) failed to create packages table: %s", packagesdb, err); lm_error_set(LM_ERR_DbSqlCreateFail); sqlite3_free(err); - db->entries_db = NULL; - } - - if(sqlite3_exec(db->files_db, queries[QUERY_CREATE_FILE_TABLE], NULL, 0, &err) != SQLITE_OK){ - pdebug(__func__, "(%s) failed to create files table: %s", filesdb, err); - lm_error_set(LM_ERR_DbSqlCreateFail); - sqlite3_free(err); - db->files_db = NULL; + db->sql = NULL; } db->dir = strdup(path); @@ -113,15 +70,13 @@ lm_database_t *lm_database_new(char *path){ } void lm_database_free(lm_database_t *db){ - if(NULL != db->entries_st) - sqlite3_finalize(db->entries_st); + if(NULL != db->st) + sqlite3_finalize(db->st); - if(NULL != db->files_st) - sqlite3_finalize(db->files_st); - - sqlite3_close(db->entries_db); - sqlite3_close(db->files_db); + if(NULL != db->filesp) + fclose(db->filesp); + sqlite3_close(db->sql); free(db->dir); free(db); } diff --git a/src/database/entry.c b/src/database/entry.c index 54cfbdd..93d547d 100644 --- a/src/database/entry.c +++ b/src/database/entry.c @@ -28,35 +28,35 @@ bool lm_database_entry_add(lm_database_t *db, lm_entry_t *entry){ char depends[lm_package_data_depend_strlen(entry)]; bool ret = false; - if(sqlite3_prepare(db->entries_db, queries[QUERY_INSERT_ENTRY_SINGLE], strlen(queries[QUERY_INSERT_ENTRY_SINGLE]), &db->entries_st, NULL) != SQLITE_OK){ - pdebug(__func__, "failed to prepare statement for inserting %s: %s", entry->name, sqlite3_errmsg(db->entries_db)); + if(sqlite3_prepare(db->sql, queries[QUERY_INSERT_SINGLE], strlen(queries[QUERY_INSERT_SINGLE]), &db->st, NULL) != SQLITE_OK){ + pdebug(__func__, "failed to prepare statement for inserting %s: %s", entry->name, sqlite3_errmsg(db->sql)); lm_error_set(LM_ERR_DbSqlPrepareFail); goto end; } - sqlite3_bind_text(db->entries_st, ENTRIES_COLUMN_NAME, entry->name, strlen(entry->name), SQLITE_STATIC); - sqlite3_bind_text(db->entries_st, ENTRIES_COLUMN_DESC, entry->desc, strlen(entry->desc), SQLITE_STATIC); - sqlite3_bind_text(db->entries_st, ENTRIES_COLUMN_VERSION, entry->version, strlen(entry->version), SQLITE_STATIC); - sqlite3_bind_int64(db->entries_st, ENTRIES_COLUMN_SIZE, entry->size); + sqlite3_bind_text(db->st, COLUMN_NAME, entry->name, strlen(entry->name), SQLITE_STATIC); + sqlite3_bind_text(db->st, COLUMN_DESC, entry->desc, strlen(entry->desc), SQLITE_STATIC); + sqlite3_bind_text(db->st, COLUMN_VERSION, entry->version, strlen(entry->version), SQLITE_STATIC); + sqlite3_bind_int64(db->st, COLUMN_SIZE, entry->size); if(!lm_package_data_depend_tostr(entry, depends)){ pdebug(__func__, "failed to convert depends to string for inserting %s: %s", entry->name, lm_strerror()); goto end; } pdebug(__func__, "depend list for %s: %s", entry->name, depends); - sqlite3_bind_text(db->entries_st, ENTRIES_COLUMN_DEPENDS, depends, strlen(depends), SQLITE_STATIC); + sqlite3_bind_text(db->st, COLUMN_DEPENDS, depends, strlen(depends), SQLITE_STATIC); - if(!lm_database_step_all(db->entries_st)){ - pdebug(__func__, "failed to execute insert statement for inserting %s: %s", entry->name, sqlite3_errmsg(db->entries_db)); + if(!lm_database_step_all(db->st)){ + pdebug(__func__, "failed to execute insert statement for inserting %s: %s", entry->name, sqlite3_errmsg(db->sql)); lm_error_set(LM_ERR_DbSqlInsertFail); goto end; } ret = true; end: - if(NULL != db->entries_st){ - sqlite3_finalize(db->entries_st); - db->entries_st = NULL; + if(NULL != db->st){ + sqlite3_finalize(db->st); + db->st = NULL; } return ret; } @@ -71,26 +71,26 @@ bool lm_database_entry_find(lm_database_t *db, lm_entry_t *entry, char *name, ch char *query = NULL; if(NULL == version) - query = queries[QUERY_SELECT_ENTRY_SINGLE_1]; + query = queries[QUERY_SELECT_SINGLE_1]; else - query = queries[QUERY_SELECT_ENTRY_SINGLE_2]; + query = queries[QUERY_SELECT_SINGLE_2]; // package pointer should already be free'd // we are initing it just in case the caller didn't if(NULL != entry) lm_entry_init(entry); - if(sqlite3_prepare(db->entries_db, query, strlen(query), &db->entries_st, NULL) != SQLITE_OK){ - pdebug(__func__, "failed to prepare statement for finding %s: %s", name, sqlite3_errmsg(db->entries_db)); + if(sqlite3_prepare(db->sql, query, strlen(query), &db->st, NULL) != SQLITE_OK){ + pdebug(__func__, "failed to prepare statement for finding %s: %s", name, sqlite3_errmsg(db->sql)); lm_error_set(LM_ERR_DbSqlPrepareFail); goto end; } - sqlite3_bind_text(db->entries_st, ENTRIES_COLUMN_NAME, name, strlen(name), SQLITE_STATIC); + sqlite3_bind_text(db->st, COLUMN_NAME, name, strlen(name), SQLITE_STATIC); if(NULL != version) - sqlite3_bind_text(db->entries_st, ENTRIES_COLUMN_VERSION, version, strlen(version), SQLITE_STATIC); + sqlite3_bind_text(db->st, COLUMN_VERSION, version, strlen(version), SQLITE_STATIC); - if(sqlite3_step(db->entries_st) != SQLITE_ROW){ + if(sqlite3_step(db->st) != SQLITE_ROW){ pdebug(__func__, "got no rows for %s", name); lm_error_set(LM_ERR_DbSqlNotFound); goto end; @@ -101,12 +101,12 @@ bool lm_database_entry_find(lm_database_t *db, lm_entry_t *entry, char *name, ch goto end; } - entry->name = strdup((char*)sqlite3_column_text(db->entries_st, ENTRIES_COLUMN_NAME-1)); - entry->desc = strdup((char*)sqlite3_column_text(db->entries_st, ENTRIES_COLUMN_DESC-1)); - entry->version = strdup((char*)sqlite3_column_text(db->entries_st, ENTRIES_COLUMN_VERSION-1)); - entry->size = sqlite3_column_int64(db->entries_st, ENTRIES_COLUMN_SIZE-1); + entry->name = strdup((char*)sqlite3_column_text(db->st, COLUMN_NAME-1)); + entry->desc = strdup((char*)sqlite3_column_text(db->st, COLUMN_DESC-1)); + entry->version = strdup((char*)sqlite3_column_text(db->st, COLUMN_VERSION-1)); + entry->size = sqlite3_column_int64(db->st, COLUMN_SIZE-1); - char *depends = (char*)sqlite3_column_text(db->entries_st, ENTRIES_COLUMN_DEPENDS-1); + char *depends = (char*)sqlite3_column_text(db->st, COLUMN_DEPENDS-1); if(!lm_package_data_depend_fromstr(entry, depends)){ pdebug(__func__, "failed to load depends for finding %s: %s", entry->name, lm_strerror()); // error is set by the function @@ -115,9 +115,9 @@ bool lm_database_entry_find(lm_database_t *db, lm_entry_t *entry, char *name, ch ret = true; end: - if(NULL != db->entries_st){ - sqlite3_finalize(db->entries_st); - db->entries_st = NULL; + if(NULL != db->st){ + sqlite3_finalize(db->st); + db->st = NULL; } return ret; } @@ -130,25 +130,25 @@ bool lm_database_entry_del(lm_database_t *db, lm_entry_t *entry){ bool ret = false; - if(sqlite3_prepare(db->entries_db, queries[QUERY_DELETE_ENTRY_SINGLE], strlen(queries[QUERY_DELETE_ENTRY_SINGLE]), &db->entries_st, NULL) != SQLITE_OK){ - pdebug(__func__, "failed to prepare statement for deleting %s: %s", entry->name, sqlite3_errmsg(db->entries_db)); + if(sqlite3_prepare(db->sql, queries[QUERY_DELETE_SINGLE], strlen(queries[QUERY_DELETE_SINGLE]), &db->st, NULL) != SQLITE_OK){ + pdebug(__func__, "failed to prepare statement for deleting %s: %s", entry->name, sqlite3_errmsg(db->sql)); lm_error_set(LM_ERR_DbSqlPrepareFail); goto end; } - sqlite3_bind_text(db->entries_st, ENTRIES_COLUMN_NAME, entry->name, strlen(entry->name), SQLITE_STATIC); + sqlite3_bind_text(db->st, COLUMN_NAME, entry->name, strlen(entry->name), SQLITE_STATIC); - if(sqlite3_step(db->entries_st) != SQLITE_DONE){ - pdebug(__func__, "failed to execute delete statement for deleting %s: %s", entry->name, sqlite3_errmsg(db->entries_db)); + if(sqlite3_step(db->st) != SQLITE_DONE){ + pdebug(__func__, "failed to execute delete statement for deleting %s: %s", entry->name, sqlite3_errmsg(db->sql)); lm_error_set(LM_ERR_DbSqlDeleteFail); goto end; } ret = true; end: - if(NULL != db->entries_st){ - sqlite3_finalize(db->entries_st); - db->entries_st = NULL; + if(NULL != db->st){ + sqlite3_finalize(db->st); + db->st = NULL; } return ret; } @@ -159,33 +159,33 @@ bool lm_database_entry_next(lm_database_t *db, lm_entry_t *entry){ return false; } - if(NULL != db->entries_st) + if(NULL != db->st) lm_entry_free(entry); - else if(NULL == db->entries_st && - sqlite3_prepare(db->entries_db, queries[QUERY_SELECT_ENTRY_ALL], strlen(queries[QUERY_SELECT_ENTRY_ALL]), &db->entries_st, NULL) != SQLITE_OK){ - pdebug(__func__, "failed to prepare statement for selecting all: %s", sqlite3_errmsg(db->entries_db)); + else if(NULL == db->st && + sqlite3_prepare(db->sql, queries[QUERY_SELECT_ALL], strlen(queries[QUERY_SELECT_ALL]), &db->st, NULL) != SQLITE_OK){ + pdebug(__func__, "failed to prepare statement for selecting all: %s", sqlite3_errmsg(db->sql)); lm_error_set(LM_ERR_DbSqlPrepareFail); return false; } lm_entry_init(entry); - if(sqlite3_step(db->entries_st) != SQLITE_ROW){ - sqlite3_finalize(db->entries_st); - db->entries_st = NULL; + if(sqlite3_step(db->st) != SQLITE_ROW){ + sqlite3_finalize(db->st); + db->st = NULL; return false; } - entry->name = strdup((char*)sqlite3_column_text(db->entries_st, ENTRIES_COLUMN_NAME-1)); - entry->desc = strdup((char*)sqlite3_column_text(db->entries_st, ENTRIES_COLUMN_DESC-1)); - entry->version = strdup((char*)sqlite3_column_text(db->entries_st, ENTRIES_COLUMN_VERSION-1)); - entry->size = sqlite3_column_int64(db->entries_st, ENTRIES_COLUMN_SIZE-1); + entry->name = strdup((char*)sqlite3_column_text(db->st, COLUMN_NAME-1)); + entry->desc = strdup((char*)sqlite3_column_text(db->st, COLUMN_DESC-1)); + entry->version = strdup((char*)sqlite3_column_text(db->st, COLUMN_VERSION-1)); + entry->size = sqlite3_column_int64(db->st, COLUMN_SIZE-1); - char *depends = (char*)sqlite3_column_text(db->entries_st, ENTRIES_COLUMN_DEPENDS-1); + char *depends = (char*)sqlite3_column_text(db->st, COLUMN_DEPENDS-1); if(!lm_package_data_depend_fromstr(entry, depends)){ pdebug(__func__, "failed to load depends for finding %s: %s", entry->name, lm_strerror()); - sqlite3_finalize(db->entries_st); + sqlite3_finalize(db->st); return false; } @@ -198,9 +198,9 @@ void lm_database_entry_next_free(lm_database_t *db, lm_entry_t *entry){ return; } - if(NULL != db->entries_st){ - sqlite3_finalize(db->entries_st); - db->entries_st = NULL; + if(NULL != db->st){ + sqlite3_finalize(db->st); + db->st = NULL; } if(NULL != entry) diff --git a/src/database/files.c b/src/database/files.c index dffa480..6c75406 100644 --- a/src/database/files.c +++ b/src/database/files.c @@ -4,238 +4,316 @@ #include "../../include/util.h" #include -#include -#include #include #include #include #include +#include +#include +#include -ssize_t lm_database_files_count(lm_database_t *db, lm_entry_t *entry){ - ssize_t count = 0; +#define LINE_MIN 36 // keep (1) + colon (1) + MD5 hash (32) + colon (1) + path (min 1) +#define KEEP_INDEX 0 +#define HASH_INDEX 2 +#define PATH_INDEX 35 +size_t __lm_database_files_get(lm_database_t *db, lm_entry_t *entry, char *path){ + size_t name_size = strlen(entry->name)+20; + size_t full_size = name_size + strlen(db->dir); + + if(NULL != path){ + char files_name[name_size]; + snprintf(files_name, name_size, "%s_files", entry->name); + join(path, db->dir, files_name); + } + + return full_size; +} + +#define __lm_files_size() __lm_database_files_get(db, entry, NULL) +#define __lm_files_path(p) __lm_database_files_get(db, entry, p) + +FILE *__lm_database_files_open(lm_database_t *db, lm_entry_t *entry, char *perms){ if(NULL == db || NULL == entry){ lm_error_set(LM_ERR_ArgNULL); + return false; + } + + char files_path[__lm_files_size()]; + FILE *filesp = NULL; + + __lm_files_path(files_path); + + if(!exists(files_path, NULL)){ + lm_error_set(LM_ERR_DbFilesNotFound); + return NULL; + } + + if(NULL == (filesp = fopen(files_path, perms))){ + lm_error_set(LM_ERR_DbFilesOpenFail, files_path); + return NULL; + } + + return filesp; +} + +char *__lm_database_files_line_single(FILE *filesp, char *path) { + ssize_t line_len = 0; + char *line = NULL; + + while ((line_len = getline(&line, (size_t*)&line_len, filesp)) >= LINE_MIN) { + // keep : hash : path + // 1 : 32 : ? + + // ignore empty line + if(line[0] == 0) + goto next; + + // remove newline + else if (line[line_len-1] == '\n') + line[line_len-1] = 0; + + if(eq((line + PATH_INDEX), path)) + return line; + + next: + free(line); + line = NULL; + line_len = 0; + } + + return line; +} + +char *__lm_database_files_line_all(lm_database_t *db, char *path) { + size_t dirlen = strlen(db->dir); + struct dirent *ent = NULL; + FILE* filesp = NULL; + DIR *ddir = NULL; + char *ret = NULL; + + if(NULL == (ddir = opendir(db->dir))){ + lm_error_set(LM_ERR_DbFilesDirFail, db->dir); goto end; } - if(sqlite3_prepare(db->files_db, queries[QUERY_SELECT_FILE_ALL], strlen(queries[QUERY_SELECT_FILE_ALL]), &db->files_st, NULL) != SQLITE_OK){ - pdebug(__func__, "failed to prepare statement for selecting all: %s", sqlite3_errmsg(db->files_db)); - lm_error_set(LM_ERR_DbSqlPrepareFail); - return false; - } - - sqlite3_bind_text(db->files_st, 1, entry->name, strlen(entry->name), SQLITE_STATIC); + while(NULL != (ent = readdir(ddir))){ + size_t entlen = strlen(ent->d_name); + char entpath[entlen+dirlen+10]; - while(sqlite3_step(db->files_st) == SQLITE_ROW) - count++; -end: - if(NULL != db->files_st){ - sqlite3_finalize(db->files_st); - db->files_st = NULL; + join(entpath, db->dir, ent->d_name); + + if(NULL == (filesp = fopen(entpath, "r"))){ + lm_error_set(LM_ERR_DbFilesOpenFail, entpath); + goto end; + } + + if(NULL == (ret = __lm_database_files_line_single(filesp, path))) + goto end; } + +end: + if(NULL != ddir) + closedir(ddir); + + if(NULL != filesp) + fclose(filesp); + + return ret; +} + +ssize_t lm_database_files_count(lm_database_t *db, lm_entry_t *entry){ + if(NULL == db || NULL == entry){ + lm_error_set(LM_ERR_ArgNULL); + return 0; + } + + FILE *filesp = NULL; + ssize_t count = 0; + char c = 0; + + if((filesp = __lm_database_files_open(db, entry, "r")) == NULL) + return 0; // error set by function + + while((c = fgetc(filesp)) != EOF) + if(c == '\n') + count ++; + + fclose(filesp); return count; } bool lm_database_files_contains(lm_database_t *db, lm_entry_t *entry, char *path){ - char *package = NULL; - bool ret = false; - - if(NULL == db || NULL == path){ + if(NULL == db || NULL == entry || NULL == path){ lm_error_set(LM_ERR_ArgNULL); - goto end; - } - - if(sqlite3_prepare(db->files_db, queries[QUERY_SELECT_FILE_SINGLE], strlen(queries[QUERY_SELECT_FILE_SINGLE]), &db->files_st, NULL) != SQLITE_OK){ - pdebug(__func__, "failed to prepare statement for selecting %s: %s", path, sqlite3_errmsg(db->files_db)); - lm_error_set(LM_ERR_DbSqlPrepareFail); - goto end; + return false; } - sqlite3_bind_text(db->files_st, FILES_COLUMN_PATH, path, strlen(path), SQLITE_STATIC); + FILE *filesp = NULL; + char *line = NULL; + bool ret = false; + + if((filesp = __lm_database_files_open(db, entry, "r")) == NULL) + return false; // error set by function + + if((line = __lm_database_files_line_single(filesp, path)) != NULL) + ret = true; + + free(line); + + if(NULL != filesp) + fclose(filesp); - if(sqlite3_step(db->files_st) != SQLITE_ROW){ - pdebug(__func__, "failed to execute select statement for selecting %s: %s", path, sqlite3_errmsg(db->files_db)); - lm_error_set(LM_ERR_DbSqlSelectFail); - goto end; - } - - package = (char*)sqlite3_column_text(db->files_st, FILES_COLUMN_ENTRY-1); - ret = eq(package, entry->name); -end: - if(NULL != db->files_st){ - sqlite3_finalize(db->files_st); - db->files_st = NULL; - } return ret; } bool lm_database_files_matches(lm_database_t *db, char *path, char *hash){ - char *hashdb = NULL; - bool ret = false; - if(NULL == db || NULL == path || NULL == hash){ lm_error_set(LM_ERR_ArgNULL); - goto end; - } - - if(sqlite3_prepare(db->files_db, queries[QUERY_SELECT_FILE_SINGLE], strlen(queries[QUERY_SELECT_FILE_SINGLE]), &db->files_st, NULL) != SQLITE_OK){ - pdebug(__func__, "failed to prepare statement for selecting %s: %s", path, sqlite3_errmsg(db->files_db)); - lm_error_set(LM_ERR_DbSqlPrepareFail); - goto end; + return false; } - sqlite3_bind_text(db->files_st, 1, path, strlen(path), SQLITE_STATIC); + char *line = NULL, *line_hash = NULL; + bool ret = false; - if(sqlite3_step(db->files_st) != SQLITE_ROW){ - pdebug(__func__, "failed to execute select statement for selecting %s: %s", path, sqlite3_errmsg(db->files_db)); - lm_error_set(LM_ERR_DbSqlSelectFail); - goto end; - } + if(NULL == (line = __lm_database_files_line_all(db, path))) + return false; - hashdb = (char*)sqlite3_column_text(db->files_st, FILES_COLUMN_HASH-1); - ret = eq(hashdb, hash); + line_hash = line+2; + line[35] = 0; -end: - if(NULL != db->files_st){ - sqlite3_finalize(db->files_st); - db->files_st = NULL; - } + if(eq(line_hash, hash)) + ret = true; + + free(line); return ret; } bool lm_database_files_iskeep(lm_database_t *db, char *path){ - bool ret = false; - int iskeep = 0; - if(NULL == db || NULL == path){ lm_error_set(LM_ERR_ArgNULL); - goto end; + return false; } - if(sqlite3_prepare(db->files_db, queries[QUERY_SELECT_FILE_SINGLE], strlen(queries[QUERY_SELECT_FILE_SINGLE]), &db->files_st, NULL) != SQLITE_OK){ - pdebug(__func__, "failed to prepare statement for selecting %s: %s", path, sqlite3_errmsg(db->files_db)); - lm_error_set(LM_ERR_DbSqlPrepareFail); - goto end; - } - - sqlite3_bind_text(db->files_st, 1, path, strlen(path), SQLITE_STATIC); - - if(!lm_database_step_all(db->files_st)){ - pdebug(__func__, "failed to execute select statement for selecting %s: %s", path, sqlite3_errmsg(db->files_db)); - lm_error_set(LM_ERR_DbSqlSelectFail); - goto end; - } - - iskeep = sqlite3_column_int64(db->files_st, FILES_COLUMN_KEEP-1); - ret = iskeep == 1; - -end: - if(NULL != db->files_st){ - sqlite3_finalize(db->files_st); - db->files_st = NULL; - } - return ret; - -} - -bool lm_database_files_add(lm_database_t *db, lm_entry_t *entry, char *path, char *hash){ + char *line = NULL; bool ret = false; - if(NULL == db || NULL == entry || NULL == path || NULL == hash){ - lm_error_set(LM_ERR_ArgNULL); - goto end; - } + if(NULL == (line = __lm_database_files_line_all(db, path))) + return false; - if(sqlite3_prepare(db->files_db, queries[QUERY_INSERT_FILE_SINGLE], strlen(queries[QUERY_INSERT_FILE_SINGLE]), &db->files_st, NULL) != SQLITE_OK){ - pdebug(__func__, "failed to prepare statement for inserting %s: %s", path, sqlite3_errmsg(db->files_db)); - lm_error_set(LM_ERR_DbSqlPrepareFail); - goto end; - } + if(line[0] == '1') + ret = true; - sqlite3_bind_text(db->files_st, FILES_COLUMN_PATH, path, strlen(path), SQLITE_STATIC); - sqlite3_bind_text(db->files_st, FILES_COLUMN_HASH, hash, strlen(hash), SQLITE_STATIC); - if(lm_package_data_keep_contains(entry, path)) - sqlite3_bind_int(db->files_st, FILES_COLUMN_KEEP, 1); - else - sqlite3_bind_int(db->files_st, FILES_COLUMN_KEEP, 0); - sqlite3_bind_text(db->files_st, FILES_COLUMN_ENTRY, entry->name, strlen(entry->name), SQLITE_STATIC); - - if(!lm_database_step_all(db->files_st)){ - pdebug(__func__, "failed to execute insert statement for inserting %s: %s", entry->name, sqlite3_errmsg(db->files_db)); - lm_error_set(LM_ERR_DbSqlInsertFail); - goto end; - } - - ret = true; -end: - if(NULL != db->files_st){ - sqlite3_finalize(db->files_st); - db->files_st = NULL; - } + free(line); return ret; } +bool lm_database_files_add(lm_database_t *db, lm_entry_t *entry, char *hash, char *path){ + if(NULL == db || NULL == entry || NULL == hash || NULL == path){ + lm_error_set(LM_ERR_ArgNULL); + return false; + } + + char files_path[__lm_files_size()]; + FILE *filesp = NULL; + + __lm_files_path(files_path); + + if(NULL == (filesp = fopen(files_path, "a"))){ + lm_error_set(LM_ERR_DbFilesOpenFail, files_path); + return false; + } + + fwrite(lm_package_data_keep_contains(entry, path) ? "1:" : "0:", 1, 2, filesp); + fwrite(hash, 1, strlen(hash), filesp); + fwrite(":", 1, 1, filesp); + fwrite(path, 1, strlen(path), filesp); + fwrite("\n", 1, 1, filesp); + + fclose(filesp); + return true; +} + bool lm_database_files_del(lm_database_t *db, lm_entry_t *entry){ if(NULL == db || NULL == entry){ lm_error_set(LM_ERR_ArgNULL); return false; } - bool ret = false; + FILE *original = NULL, *temp = NULL; + char temp_name[strlen(entry->name)+15]; + char temp_path[__lm_files_size()+10]; + char real_path[__lm_files_size()], *line = NULL; + bool ret = false, empty = true; + ssize_t line_len = 0; - if(sqlite3_prepare(db->files_db, queries[QUERY_DELETE_FILE_ALL], strlen(queries[QUERY_DELETE_FILE_ALL]), &db->files_st, NULL) != SQLITE_OK){ - pdebug(__func__, "failed to prepare statement for deleting %s: %s", entry->name, sqlite3_errmsg(db->files_db)); - lm_error_set(LM_ERR_DbSqlPrepareFail); + __lm_files_path(real_path); + sprintf(temp_name, "%s_files.temp", entry->name); + join(temp_path, db->dir, temp_name); + + if(NULL == (original = fopen(real_path, "r"))){ + lm_error_set(LM_ERR_DbFilesOpenFail); + goto end; + } + + if(NULL == (temp = fopen(temp_path, "a"))){ + lm_error_set(LM_ERR_DbFilesOpenFail); goto end; } - sqlite3_bind_text(db->files_st, 1, entry->name, strlen(entry->name), SQLITE_STATIC); + while ((line_len = getline(&line, (size_t*)&line_len, original)) >= LINE_MIN){ + if(line[0] == '1'){ + fwrite(line, 1, line_len, temp); + empty = false; + } - if(!lm_database_step_all(db->files_st)){ - pdebug(__func__, "failed to execute delete statement for deleting %s: %s", entry->name, sqlite3_errmsg(db->files_db)); - lm_error_set(LM_ERR_DbSqlDeleteFail); - goto end; + free(line); + line = NULL; + line_len = 0; } ret = true; end: - if(NULL != db->files_st){ - sqlite3_finalize(db->files_st); - db->files_st = NULL; + if(NULL != original) + fclose(original); + + if(NULL != temp) + fclose(temp); + + free(line); + + if(ret && unlink(real_path) != 0){ + lm_error_set(LM_ERR_DbFilesUnlinkFail, real_path); + ret = false; } + + if(empty || !ret){ + unlink(temp_path); + } + + else if(ret && rename(temp_path, real_path) != 0){ + lm_error_set(LM_ERR_DbFilesRenameFail, real_path); + ret = false; + } + return ret; } -bool lm_database_files_del_single(lm_database_t *db, char *path){ - if(NULL == db || NULL == path){ +bool lm_database_files_del_all(lm_database_t *db, lm_entry_t *entry){ + if(NULL == db || NULL == entry){ lm_error_set(LM_ERR_ArgNULL); return false; } + + char files_path[__lm_files_size()]; + __lm_files_path(files_path); - bool ret = false; - - if(sqlite3_prepare(db->files_db, queries[QUERY_DELETE_FILE_SINGLE], strlen(queries[QUERY_DELETE_FILE_SINGLE]), &db->files_st, NULL) != SQLITE_OK){ - pdebug(__func__, "failed to prepare statement for deleting %s: %s", path, sqlite3_errmsg(db->files_db)); - lm_error_set(LM_ERR_DbSqlPrepareFail); - goto end; + if(exists(files_path, NULL) && unlink(files_path) != 0){ + lm_error_set(LM_ERR_DbFilesUnlinkFail, files_path); + return false; } - sqlite3_bind_text(db->files_st, 1, path, strlen(path), SQLITE_STATIC); - - if(!lm_database_step_all(db->files_st)){ - pdebug(__func__, "failed to execute delete statement for deleting %s: %s", path, sqlite3_errmsg(db->files_db)); - lm_error_set(LM_ERR_DbSqlDeleteFail); - goto end; - } - - ret = true; -end: - if(NULL != db->files_st){ - sqlite3_finalize(db->files_st); - db->files_st = NULL; - } - return ret; + return true; } bool lm_database_files_next(lm_database_t *db, lm_entry_t *entry, char **path, char **hash, bool *keep){ @@ -244,33 +322,38 @@ bool lm_database_files_next(lm_database_t *db, lm_entry_t *entry, char **path, c return false; } - if(NULL == db->files_st){ - if(sqlite3_prepare(db->files_db, queries[QUERY_SELECT_FILE_ALL], strlen(queries[QUERY_SELECT_FILE_ALL]), &db->files_st, NULL) != SQLITE_OK){ - pdebug(__func__, "failed to prepare statement for selecting all: %s", sqlite3_errmsg(db->files_db)); - lm_error_set(LM_ERR_DbSqlPrepareFail); - return false; - } - sqlite3_bind_text(db->files_st, 1, entry->name, strlen(entry->name), SQLITE_STATIC); - } + if(NULL == db->filesp && NULL == (db->filesp = __lm_database_files_open(db, entry, "r"))) + return false; // error set by function - else if(NULL != db->files_st){ - free(*path); - free(*hash); - *path = NULL; - *hash = NULL; - *keep = false; - } + ssize_t line_len = 0; + char *line = NULL; - if(sqlite3_step(db->files_st) != SQLITE_ROW){ - sqlite3_finalize(db->files_st); - db->files_st = NULL; - return false; - } + if(NULL != *hash) + free(*hash-HASH_INDEX); + + if((line_len = getline(&line, (size_t*)&line_len, db->filesp)) <= LINE_MIN) + goto eof; + + if(line[line_len-1] == '\n') + line[line_len-1] = 0; // replace newline with null terminator + line[PATH_INDEX-1] = 0; // replace colon with null terminator + + *keep = line[0] == '1'; + *hash = line + HASH_INDEX; + *path = line + PATH_INDEX; - *path = strdup((char*)sqlite3_column_text(db->files_st, FILES_COLUMN_PATH-1)); - *hash = strdup((char*)sqlite3_column_text(db->files_st, FILES_COLUMN_HASH-1)); - *keep = sqlite3_column_int(db->files_st, FILES_COLUMN_KEEP-1) == 1; return true; + +eof: + fclose(db->filesp); + free(line); + + db->filesp = NULL; + *keep = false; + *path = NULL; + *hash = NULL; + + return false; } void lm_database_files_next_free(lm_database_t *db, lm_entry_t *entry, char **path, char **hash, bool *keep){ @@ -279,16 +362,14 @@ void lm_database_files_next_free(lm_database_t *db, lm_entry_t *entry, char **pa return; } - if(NULL != db->files_st){ - sqlite3_finalize(db->files_st); - db->files_st = NULL; - } - - if(NULL != path) - free(*path); + if(NULL != db->filesp) + fclose(db->filesp); - if(NULL != hash) - free(*hash); + if(NULL != *hash) + free(*hash-HASH_INDEX); + db->filesp = NULL; *keep = false; + *path = NULL; + *hash = NULL; } diff --git a/src/error.c b/src/error.c index 8a4f69b..66e430d 100644 --- a/src/error.c +++ b/src/error.c @@ -97,11 +97,11 @@ void lm_error_set(lm_error_t code, ...) { {.code = LM_ERR_PoolUrlEmpty, .desc = _("pool URL is empty or invalid") }, {.code = LM_ERR_PoolBadDir, .desc = _("pool directory path is not accessible") }, {.code = LM_ERR_PoolBadPaths, .desc = _("pool directory sub-paths are not accessible") }, - {.code = LM_ERR_DbFilesNotFound, .desc = _("package file list not found in the database") }, - {.code = LM_ERR_DbFilesOpenFail, .desc = _("failed to open package file list in the database") }, - {.code = LM_ERR_DbFilesDirFail, .desc = _("failed to access package file list database directory") }, - {.code = LM_ERR_DbFilesUnlinkFail, .desc = _("failed to remove package file list from the database") }, - {.code = LM_ERR_DbFilesWriteFail, .desc = _("failed to write to the file list in the database") }, + {.code = LM_ERR_DbFilesNotFound, .desc = _("file list not found for the package") }, + {.code = LM_ERR_DbFilesRenameFail, .desc = _("failed to rename the file list for the package: %s") }, + {.code = LM_ERR_DbFilesOpenFail, .desc = _("failed to open the package file list: %s") }, + {.code = LM_ERR_DbFilesDirFail, .desc = _("failed to open the database directory: %s") }, + {.code = LM_ERR_DbFilesUnlinkFail, .desc = _("failed to remove package file list: %s") }, {.code = LM_ERR_DbKeepsNotFound, .desc = _("package keep list not found in the database") }, {.code = LM_ERR_DbKeepsOpenFail, .desc = _("failed to open package keep list in the database") }, {.code = LM_ERR_DbKeepsDirFail, .desc = _("failed to access package keep list database directory") },