diff --git a/CHANGELOG.md b/CHANGELOG.md index 67b7a4bdd77..ee844de12aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ and since Bareos version 20 this project adheres to [Semantic Versioning](https: - fix director crash on "update slots" when there is a parsing issue with the autochanger or tape devices [PR #919] - [Issue #1232]: bareos logrotate errors, reintroduce su directive in logrotate [PR #918] - fix scheduler running disabled jobs after executing the disable command [PR #924] +- [Issue #1334]: After deleting storage from the configuration, it still persists in the catalog db [PR #912] ### Added - systemtests: allows multiple subtests per systemtest [PR #857] @@ -72,6 +73,8 @@ and since Bareos version 20 this project adheres to [Semantic Versioning](https: - packages: Build also for Fedora_34 [PR #869] - packages: Build also for Debian_11 [PR #914] - add job name in End Job Session output in bls tool [PR #916] +- added check for orphaned storages in dbcheck [PR #912] +- added option to delete selected storage in bconsole if it is orphaned [PR #912] ### Changed - core: systemd service: change daemon type from forking to simple and start daemons in foreground [PR #824] @@ -204,6 +207,7 @@ and since Bareos version 20 this project adheres to [Semantic Versioning](https: [PR #903]: https://github.com/bareos/bareos/pull/903 [PR #907]: https://github.com/bareos/bareos/pull/907 [PR #910]: https://github.com/bareos/bareos/pull/910 +[PR #912]: https://github.com/bareos/bareos/pull/912 [PR #914]: https://github.com/bareos/bareos/pull/914 [PR #918]: https://github.com/bareos/bareos/pull/918 [PR #919]: https://github.com/bareos/bareos/pull/919 diff --git a/core/src/dird/CMakeLists.txt b/core/src/dird/CMakeLists.txt index dac5a41b067..75eb33caddb 100644 --- a/core/src/dird/CMakeLists.txt +++ b/core/src/dird/CMakeLists.txt @@ -34,6 +34,7 @@ set(DIRD_OBJECTS_SRCS catreq.cc check_catalog.cc consolidate.cc + dbcheck_utils.cc dird_globals.cc dir_plugins.cc dird_conf.cc @@ -118,8 +119,8 @@ if(HAVE_WIN32) ) endif() -set(DBCHKSRCS dbcheck.cc dird_conf.cc dird_globals.cc ua_acl.cc ua_audit.cc - run_conf.cc inc_conf.cc +set(DBCHKSRCS dbcheck.cc dbcheck_utils.cc dird_conf.cc dird_globals.cc + ua_acl.cc ua_audit.cc run_conf.cc inc_conf.cc ) if(HAVE_WIN32) list(APPEND DBCHKSRCS ../win32/dird/dbcheckres.rc) diff --git a/core/src/dird/dbcheck.cc b/core/src/dird/dbcheck.cc index 880d0da0876..4dac290dd32 100644 --- a/core/src/dird/dbcheck.cc +++ b/core/src/dird/dbcheck.cc @@ -3,7 +3,7 @@ Copyright (C) 2002-2011 Free Software Foundation Europe e.V. Copyright (C) 2011-2016 Planets Communications B.V. - Copyright (C) 2013-2020 Bareos GmbH & Co. KG + Copyright (C) 2013-2021 Bareos GmbH & Co. KG This program is Free Software; you can redistribute it and/or modify it under the terms of version three of the GNU Affero General Public @@ -33,26 +33,12 @@ #include "lib/parse_conf.h" #include "lib/util.h" +#include "dbcheck_utils.h" + using namespace directordaemon; extern bool ParseDirConfig(const char* configfile, int exit_code); -typedef struct s_id_ctx { - int64_t* Id; /* ids to be modified */ - int num_ids; /* ids stored */ - int max_ids; /* size of array */ - int num_del; /* number deleted */ - int tot_ids; /* total to process */ -} ID_LIST; - -typedef struct s_name_ctx { - char** name; /* list of names */ - int num_ids; /* ids stored */ - int max_ids; /* size of array */ - int num_del; /* number deleted */ - int tot_ids; /* total to process */ -} NameList; - // Global variables static bool fix = false; static bool batch = false; @@ -66,8 +52,6 @@ static const char* idx_tmp_name; static const char* backend_directory = PATH_BAREOS_BACKENDDIR; #endif -#define MAX_ID_LIST_LEN 10000000 - // Forward referenced functions static void set_quit(); static void toggle_modify(); @@ -80,6 +64,7 @@ static void eliminate_orphaned_fileset_records(); static void eliminate_orphaned_client_records(); static void eliminate_orphaned_job_records(); static void eliminate_admin_records(); +static void eliminate_orphaned_storage_records(); static void eliminate_restore_records(); static void repair_bad_paths(); static void repair_bad_filenames(); @@ -107,6 +92,8 @@ static struct dbcheck_cmdstruct commands[] = { {eliminate_orphaned_client_records, "Check for orphaned Client records", true}, {eliminate_orphaned_job_records, "Check for orphaned Job records", true}, + {eliminate_orphaned_storage_records, "Check for orphaned storage records", + true}, {eliminate_admin_records, "Check for all Admin records", true}, {eliminate_restore_records, "Check for all Restore records", true}, {run_all_commands, "Run ALL checks", false}, @@ -147,10 +134,10 @@ static char* GetCmd(const char* prompt) printf("%s", prompt); fflush(stdout); - if (fgets(cmd, sizeof(cmd), stdin) == NULL) { + if (fgets(cmd, sizeof(cmd), stdin) == nullptr) { printf("\n"); quit = true; - return NULL; + return nullptr; } StripTrailingJunk(cmd); return cmd; @@ -196,7 +183,7 @@ static void PrintCatalogDetails(CatalogResource* catalog, POOLMEM* catalog_details = GetPoolMemory(PM_MESSAGE); // Instantiate a BareosDb class and see what db_type gets assigned to it. - db = db_init_database(NULL, catalog->db_driver, catalog->db_name, + db = db_init_database(nullptr, catalog->db_driver, catalog->db_name, catalog->db_user, catalog->db_password.value, catalog->db_address, catalog->db_port, catalog->db_socket, catalog->mult_db_connections, @@ -205,59 +192,10 @@ static void PrintCatalogDetails(CatalogResource* catalog, if (db) { printf("%sdb_type=%s\nworking_dir=%s\n", catalog->display(catalog_details), db->GetType(), working_directory); - db->CloseDatabase(NULL); + db->CloseDatabase(nullptr); } FreePoolMemory(catalog_details); } - -static int PrintNameHandler(void* ctx, int num_fields, char** row) -{ - if (row[0]) { printf("%s\n", row[0]); } - return 0; -} - -static int GetNameHandler(void* ctx, int num_fields, char** row) -{ - POOLMEM* name = (POOLMEM*)ctx; - - if (row[0]) { PmStrcpy(name, row[0]); } - return 0; -} - -static int PrintJobHandler(void* ctx, int num_fields, char** row) -{ - printf(_("JobId=%s Name=\"%s\" StartTime=%s\n"), NPRT(row[0]), NPRT(row[1]), - NPRT(row[2])); - return 0; -} - -static int PrintJobmediaHandler(void* ctx, int num_fields, char** row) -{ - printf(_("Orphaned JobMediaId=%s JobId=%s Volume=\"%s\"\n"), NPRT(row[0]), - NPRT(row[1]), NPRT(row[2])); - return 0; -} - -static int PrintFileHandler(void* ctx, int num_fields, char** row) -{ - printf(_("Orphaned FileId=%s JobId=%s Volume=\"%s\"\n"), NPRT(row[0]), - NPRT(row[1]), NPRT(row[2])); - return 0; -} - -static int PrintFilesetHandler(void* ctx, int num_fields, char** row) -{ - printf(_("Orphaned FileSetId=%s FileSet=\"%s\" MD5=%s\n"), NPRT(row[0]), - NPRT(row[1]), NPRT(row[2])); - return 0; -} - -static int PrintClientHandler(void* ctx, int num_fields, char** row) -{ - printf(_("Orphaned ClientId=%s Name=\"%s\"\n"), NPRT(row[0]), NPRT(row[1])); - return 0; -} - /** * database index handling functions * @@ -275,12 +213,12 @@ typedef struct s_idx_list { // Drop temporary index static bool DropTmpIdx(const char* idx_name, const char* table_name) { - if (idx_tmp_name != NULL) { + if (idx_tmp_name != nullptr) { printf(_("Drop temporary index.\n")); fflush(stdout); Bsnprintf(buf, sizeof(buf), "DROP INDEX %s ON %s", idx_name, table_name); if (verbose) { printf("%s\n", buf); } - if (!db->SqlQuery(buf, NULL, NULL)) { + if (!db->SqlQuery(buf, nullptr, nullptr)) { printf("%s\n", db->strerror()); return false; } else { @@ -288,44 +226,10 @@ static bool DropTmpIdx(const char* idx_name, const char* table_name) } fflush(stdout); } - idx_tmp_name = NULL; + idx_tmp_name = nullptr; return true; } - -// Called here with each id to be added to the list -static int IdListHandler(void* ctx, int num_fields, char** row) -{ - ID_LIST* lst = (ID_LIST*)ctx; - - if (lst->num_ids == MAX_ID_LIST_LEN) { return 1; } - if (lst->num_ids == lst->max_ids) { - if (lst->max_ids == 0) { - lst->max_ids = 10000; - lst->Id = (int64_t*)malloc(sizeof(int64_t) * lst->max_ids); - } else { - lst->max_ids = (lst->max_ids * 3) / 2; - lst->Id = (int64_t*)realloc(lst->Id, sizeof(int64_t) * lst->max_ids); - } - } - lst->Id[lst->num_ids++] = str_to_int64(row[0]); - return 0; -} - -// Construct record id list -static int MakeIdList(const char* query, ID_LIST* id_list) -{ - id_list->num_ids = 0; - id_list->num_del = 0; - id_list->tot_ids = 0; - - if (!db->SqlQuery(query, IdListHandler, (void*)id_list)) { - printf("%s", db->strerror()); - return 0; - } - return 1; -} - // Delete all entries in the list static int DeleteIdList(const char* query, ID_LIST* id_list) { @@ -334,59 +238,11 @@ static int DeleteIdList(const char* query, ID_LIST* id_list) for (int i = 0; i < id_list->num_ids; i++) { Bsnprintf(buf, sizeof(buf), query, edit_int64(id_list->Id[i], ed1)); if (verbose) { printf(_("Deleting: %s\n"), buf); } - db->SqlQuery(buf, NULL, NULL); - } - return 1; -} - -// Called here with each name to be added to the list -static int NameListHandler(void* ctx, int num_fields, char** row) -{ - NameList* name = (NameList*)ctx; - - if (name->num_ids == MAX_ID_LIST_LEN) { return 1; } - if (name->num_ids == name->max_ids) { - if (name->max_ids == 0) { - name->max_ids = 10000; - name->name = (char**)malloc(sizeof(char*) * name->max_ids); - } else { - name->max_ids = (name->max_ids * 3) / 2; - name->name = (char**)realloc(name->name, sizeof(char*) * name->max_ids); - } - } - name->name[name->num_ids++] = strdup(row[0]); - return 0; -} - -// Construct name list -static int MakeNameList(const char* query, NameList* name_list) -{ - name_list->num_ids = 0; - name_list->num_del = 0; - name_list->tot_ids = 0; - - if (!db->SqlQuery(query, NameListHandler, (void*)name_list)) { - printf("%s", db->strerror()); - return 0; + db->SqlQuery(buf, nullptr, nullptr); } return 1; } -// Print names in the list -static void PrintNameList(NameList* name_list) -{ - for (int i = 0; i < name_list->num_ids; i++) { - printf("%s\n", name_list->name[i]); - } -} - -// Free names in the list -static void FreeNameList(NameList* name_list) -{ - for (int i = 0; i < name_list->num_ids; i++) { free(name_list->name[i]); } - name_list->num_ids = 0; -} - static void eliminate_duplicate_paths() { const char* query; @@ -400,7 +256,7 @@ static void eliminate_duplicate_paths() = "SELECT Path, count(Path) as Count FROM Path " "GROUP BY Path HAVING count(Path) > 1"; - if (!MakeNameList(query, &name_list)) { exit(1); } + if (!MakeNameList(db, query, &name_list)) { exit(1); } printf(_("Found %d duplicate Path records.\n"), name_list.num_ids); fflush(stdout); if (name_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) { @@ -411,12 +267,12 @@ static void eliminate_duplicate_paths() // Loop through list of duplicate names for (int i = 0; i < name_list.num_ids; i++) { // Get all the Ids of each name - db->EscapeString(NULL, esc_name, name_list.name[i], + db->EscapeString(nullptr, esc_name, name_list.name[i], strlen(name_list.name[i])); Bsnprintf(buf, sizeof(buf), "SELECT PathId FROM Path WHERE Path='%s'", esc_name); if (verbose > 1) { printf("%s\n", buf); } - if (!MakeIdList(buf, &id_list)) { exit(1); } + if (!MakeIdList(db, buf, &id_list)) { exit(1); } if (verbose) { printf(_("Found %d for: %s\n"), id_list.num_ids, name_list.name[i]); } @@ -427,10 +283,10 @@ static void eliminate_duplicate_paths() edit_int64(id_list.Id[0], ed1), edit_int64(id_list.Id[j], ed2)); if (verbose > 1) { printf("%s\n", buf); } - db->SqlQuery(buf, NULL, NULL); + db->SqlQuery(buf, nullptr, nullptr); Bsnprintf(buf, sizeof(buf), "DELETE FROM Path WHERE PathId=%s", ed2); if (verbose > 2) { printf("%s\n", buf); } - db->SqlQuery(buf, NULL, NULL); + db->SqlQuery(buf, nullptr, nullptr); } } fflush(stdout); @@ -449,7 +305,7 @@ static void eliminate_orphaned_jobmedia_records() printf(_("Checking for orphaned JobMedia entries.\n")); fflush(stdout); - if (!MakeIdList(query, &id_list)) { exit(1); } + if (!MakeIdList(db, query, &id_list)) { exit(1); } // Loop doing 300000 at a time while (id_list.num_ids != 0) { printf(_("Found %d orphaned JobMedia records.\n"), id_list.num_ids); @@ -462,7 +318,7 @@ static void eliminate_orphaned_jobmedia_records() "JobMedia,Media " "WHERE JobMedia.JobMediaId=%s AND Media.MediaId=JobMedia.MediaId", edit_int64(id_list.Id[i], ed1)); - if (!db->SqlQuery(buf, PrintJobmediaHandler, NULL)) { + if (!db->SqlQuery(buf, PrintJobmediaHandler, nullptr)) { printf("%s\n", db->strerror()); } } @@ -475,7 +331,7 @@ static void eliminate_orphaned_jobmedia_records() } else { break; /* get out if not updating db */ } - if (!MakeIdList(query, &id_list)) { exit(1); } + if (!MakeIdList(db, query, &id_list)) { exit(1); } } fflush(stdout); } @@ -490,7 +346,7 @@ static void eliminate_orphaned_file_records() printf(_("Checking for orphaned File entries. This may take some time!\n")); if (verbose > 1) { printf("%s\n", query); } fflush(stdout); - if (!MakeIdList(query, &id_list)) { exit(1); } + if (!MakeIdList(db, query, &id_list)) { exit(1); } // Loop doing 300000 at a time while (id_list.num_ids != 0) { printf(_("Found %d orphaned File records.\n"), id_list.num_ids); @@ -501,7 +357,7 @@ static void eliminate_orphaned_file_records() "SELECT File.FileId,File.JobId,File.Name FROM File " "WHERE File.FileId=%s", edit_int64(id_list.Id[i], ed1)); - if (!db->SqlQuery(buf, PrintFileHandler, NULL)) { + if (!db->SqlQuery(buf, PrintFileHandler, nullptr)) { printf("%s\n", db->strerror()); } } @@ -513,7 +369,7 @@ static void eliminate_orphaned_file_records() } else { break; /* get out if not updating db */ } - if (!MakeIdList(query, &id_list)) { exit(1); } + if (!MakeIdList(db, query, &id_list)) { exit(1); } } fflush(stdout); } @@ -524,14 +380,14 @@ static void eliminate_orphaned_path_records() PoolMem query(PM_MESSAGE); lctx.count = 0; - idx_tmp_name = NULL; + idx_tmp_name = nullptr; db->FillQuery(query, BareosDb::SQL_QUERY::get_orphaned_paths_0); printf(_("Checking for orphaned Path entries. This may take some time!\n")); if (verbose > 1) { printf("%s\n", query.c_str()); } fflush(stdout); - if (!MakeIdList(query.c_str(), &id_list)) { exit(1); } + if (!MakeIdList(db, query.c_str(), &id_list)) { exit(1); } // Loop doing 300000 at a time while (id_list.num_ids != 0) { printf(_("Found %d orphaned Path records.\n"), id_list.num_ids); @@ -541,7 +397,7 @@ static void eliminate_orphaned_path_records() char ed1[50]; Bsnprintf(buf, sizeof(buf), "SELECT Path FROM Path WHERE PathId=%s", edit_int64(id_list.Id[i], ed1)); - db->SqlQuery(buf, PrintNameHandler, NULL); + db->SqlQuery(buf, PrintNameHandler, nullptr); } fflush(stdout); } @@ -553,7 +409,7 @@ static void eliminate_orphaned_path_records() } else { break; /* get out if not updating db */ } - if (!MakeIdList(query.c_str(), &id_list)) { exit(1); } + if (!MakeIdList(db, query.c_str(), &id_list)) { exit(1); } } } @@ -568,7 +424,7 @@ static void eliminate_orphaned_fileset_records() "WHERE Job.FileSetId IS NULL"; if (verbose > 1) { printf("%s\n", query); } fflush(stdout); - if (!MakeIdList(query, &id_list)) { exit(1); } + if (!MakeIdList(db, query, &id_list)) { exit(1); } printf(_("Found %d orphaned FileSet records.\n"), id_list.num_ids); fflush(stdout); if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) { @@ -578,7 +434,7 @@ static void eliminate_orphaned_fileset_records() "SELECT FileSetId,FileSet,MD5 FROM FileSet " "WHERE FileSetId=%s", edit_int64(id_list.Id[i], ed1)); - if (!db->SqlQuery(buf, PrintFilesetHandler, NULL)) { + if (!db->SqlQuery(buf, PrintFilesetHandler, nullptr)) { printf("%s\n", db->strerror()); } } @@ -611,7 +467,7 @@ static void eliminate_orphaned_client_records() "WHERE Job.ClientId IS NULL"; if (verbose > 1) { printf("%s\n", query); } fflush(stdout); - if (!MakeIdList(query, &id_list)) { exit(1); } + if (!MakeIdList(db, query, &id_list)) { exit(1); } printf(_("Found %d orphaned Client records.\n"), id_list.num_ids); if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) { for (int i = 0; i < id_list.num_ids; i++) { @@ -620,7 +476,7 @@ static void eliminate_orphaned_client_records() "SELECT ClientId,Name FROM Client " "WHERE ClientId=%s", edit_int64(id_list.Id[i], ed1)); - if (!db->SqlQuery(buf, PrintClientHandler, NULL)) { + if (!db->SqlQuery(buf, PrintClientHandler, nullptr)) { printf("%s\n", db->strerror()); } } @@ -653,7 +509,7 @@ static void eliminate_orphaned_job_records() "WHERE Client.Name IS NULL"; if (verbose > 1) { printf("%s\n", query); } fflush(stdout); - if (!MakeIdList(query, &id_list)) { exit(1); } + if (!MakeIdList(db, query, &id_list)) { exit(1); } printf(_("Found %d orphaned Job records.\n"), id_list.num_ids); fflush(stdout); if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) { @@ -663,7 +519,7 @@ static void eliminate_orphaned_job_records() "SELECT JobId,Name,StartTime FROM Job " "WHERE JobId=%s", edit_int64(id_list.Id[i], ed1)); - if (!db->SqlQuery(buf, PrintJobHandler, NULL)) { + if (!db->SqlQuery(buf, PrintJobHandler, nullptr)) { printf("%s\n", db->strerror()); } } @@ -683,6 +539,39 @@ static void eliminate_orphaned_job_records() } } +static void eliminate_orphaned_storage_records() +{ + printf(_("Checking for orphaned Storage entries.\n")); + fflush(stdout); + + std::vector orphaned_storage_names_list + = get_orphaned_storages_names(db); + + printf(_("Found %zu orphaned Storage records.\n"), + orphaned_storage_names_list.size()); + + std::vector storages_to_be_deleted; + + if (orphaned_storage_names_list.size() > 0) { + if (verbose && yes_no(_("Print orhpaned storages? (yes/no): "))) { + for (auto const& storage : orphaned_storage_names_list) { + printf("'%s'\n", storage.c_str()); + } + fflush(stdout); + } + + storages_to_be_deleted + = get_deletable_storageids(db, orphaned_storage_names_list); + } + if (quit) { return; } + if (fix && storages_to_be_deleted.size() > 0) { + printf(_("Deleting %zu orphaned storage records.\n"), + storages_to_be_deleted.size()); + fflush(stdout); + delete_storages(db, storages_to_be_deleted); + } +} + static void eliminate_admin_records() { const char* query; @@ -693,7 +582,7 @@ static void eliminate_admin_records() "WHERE Job.Type='D'"; if (verbose > 1) { printf("%s\n", query); } fflush(stdout); - if (!MakeIdList(query, &id_list)) { exit(1); } + if (!MakeIdList(db, query, &id_list)) { exit(1); } printf(_("Found %d Admin Job records.\n"), id_list.num_ids); if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) { for (int i = 0; i < id_list.num_ids; i++) { @@ -702,7 +591,7 @@ static void eliminate_admin_records() "SELECT JobId,Name,StartTime FROM Job " "WHERE JobId=%s", edit_int64(id_list.Id[i], ed1)); - if (!db->SqlQuery(buf, PrintJobHandler, NULL)) { + if (!db->SqlQuery(buf, PrintJobHandler, nullptr)) { printf("%s\n", db->strerror()); } } @@ -726,7 +615,7 @@ static void eliminate_restore_records() "WHERE Job.Type='R'"; if (verbose > 1) { printf("%s\n", query); } fflush(stdout); - if (!MakeIdList(query, &id_list)) { exit(1); } + if (!MakeIdList(db, query, &id_list)) { exit(1); } printf(_("Found %d Restore Job records.\n"), id_list.num_ids); if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) { for (int i = 0; i < id_list.num_ids; i++) { @@ -735,7 +624,7 @@ static void eliminate_restore_records() "SELECT JobId,Name,StartTime FROM Job " "WHERE JobId=%s", edit_int64(id_list.Id[i], ed1)); - if (!db->SqlQuery(buf, PrintJobHandler, NULL)) { + if (!db->SqlQuery(buf, PrintJobHandler, nullptr)) { printf("%s\n", db->strerror()); } } @@ -760,14 +649,14 @@ static void repair_bad_filenames() "WHERE Name LIKE '%/'"; if (verbose > 1) { printf("%s\n", query); } fflush(stdout); - if (!MakeIdList(query, &id_list)) { exit(1); } + if (!MakeIdList(db, query, &id_list)) { exit(1); } printf(_("Found %d bad Filename records.\n"), id_list.num_ids); if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) { for (i = 0; i < id_list.num_ids; i++) { char ed1[50]; Bsnprintf(buf, sizeof(buf), "SELECT Name FROM File WHERE FileId=%s", edit_int64(id_list.Id[i], ed1)); - if (!db->SqlQuery(buf, PrintNameHandler, NULL)) { + if (!db->SqlQuery(buf, PrintNameHandler, nullptr)) { printf("%s\n", db->strerror()); } } @@ -796,12 +685,12 @@ static void repair_bad_filenames() esc_name[1] = 0; } else { name[len - 1] = 0; - db->EscapeString(NULL, esc_name, name, len); + db->EscapeString(nullptr, esc_name, name, len); } Bsnprintf(buf, sizeof(buf), "UPDATE File SET Name='%s' WHERE FileId=%s", esc_name, edit_int64(id_list.Id[i], ed1)); if (verbose > 1) { printf("%s\n", buf); } - db->SqlQuery(buf, NULL, NULL); + db->SqlQuery(buf, nullptr, nullptr); } FreePoolMemory(name); } @@ -817,7 +706,7 @@ static void repair_bad_paths() db->FillQuery(query, BareosDb::SQL_QUERY::get_bad_paths_0); if (verbose > 1) { printf("%s\n", query.c_str()); } fflush(stdout); - if (!MakeIdList(query.c_str(), &id_list)) { exit(1); } + if (!MakeIdList(db, query.c_str(), &id_list)) { exit(1); } printf(_("Found %d bad Path records.\n"), id_list.num_ids); fflush(stdout); if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) { @@ -825,7 +714,7 @@ static void repair_bad_paths() char ed1[50]; Bsnprintf(buf, sizeof(buf), "SELECT Path FROM Path WHERE PathId=%s", edit_int64(id_list.Id[i], ed1)); - if (!db->SqlQuery(buf, PrintNameHandler, NULL)) { + if (!db->SqlQuery(buf, PrintNameHandler, nullptr)) { printf("%s\n", db->strerror()); } } @@ -851,11 +740,11 @@ static void repair_bad_paths() } // Add trailing slash len = PmStrcat(name, "/"); - db->EscapeString(NULL, esc_name, name, len); + db->EscapeString(nullptr, esc_name, name, len); Bsnprintf(buf, sizeof(buf), "UPDATE Path SET Path='%s' WHERE PathId=%s", esc_name, edit_int64(id_list.Id[i], ed1)); if (verbose > 1) { printf("%s\n", buf); } - db->SqlQuery(buf, NULL, NULL); + db->SqlQuery(buf, nullptr, nullptr); } fflush(stdout); FreePoolMemory(name); @@ -924,12 +813,12 @@ static void do_interactive_mode() int main(int argc, char* argv[]) { int ch; - const char* db_driver = NULL; + const char* db_driver = nullptr; const char *user, *password, *db_name, *dbhost; int dbport = 0; bool print_catalog = false; - char* configfile = NULL; - char* catalogname = NULL; + char* configfile = nullptr; + char* catalogname = nullptr; char* endptr; #if defined(HAVE_DYNAMIC_CATS_BACKENDS) std::vector backend_directories; @@ -941,7 +830,7 @@ int main(int argc, char* argv[]) textdomain("bareos"); MyNameIs(argc, argv, "dbcheck"); - InitMsg(NULL, NULL); /* setup message handler */ + InitMsg(nullptr, nullptr); /* setup message handler */ memset(&id_list, 0, sizeof(id_list)); memset(&name_list, 0, sizeof(name_list)); @@ -989,7 +878,7 @@ int main(int argc, char* argv[]) OSDependentInit(); if (configfile || (argc == 0)) { - CatalogResource* catalog = NULL; + CatalogResource* catalog = nullptr; int found = 0; if (argc > 0) { Pmsg0(0, _("Warning skipping the additional parameters for working " @@ -1023,7 +912,7 @@ int main(int argc, char* argv[]) exit(1); } else { LockRes(my_config); - me = (DirectorResource*)my_config->GetNextRes(R_DIRECTOR, NULL); + me = (DirectorResource*)my_config->GetNextRes(R_DIRECTOR, nullptr); my_config->own_resource_ = me; UnlockRes(my_config); if (!me) { @@ -1047,7 +936,7 @@ int main(int argc, char* argv[]) password = catalog->db_password.value; dbhost = catalog->db_address; db_driver = catalog->db_driver; - if (dbhost && dbhost[0] == 0) { dbhost = NULL; } + if (dbhost && dbhost[0] == 0) { dbhost = nullptr; } dbport = catalog->db_port; } } else { @@ -1061,7 +950,7 @@ int main(int argc, char* argv[]) db_name = "bareos"; user = db_name; password = ""; - dbhost = NULL; + dbhost = nullptr; if (argc == 2) { db_name = argv[1]; @@ -1101,9 +990,9 @@ int main(int argc, char* argv[]) } // Open database - db = db_init_database(NULL, db_driver, db_name, user, password, dbhost, - dbport, NULL, false, false, false, false); - if (!db->OpenDatabase(NULL)) { + db = db_init_database(nullptr, db_driver, db_name, user, password, dbhost, + dbport, nullptr, false, false, false, false); + if (!db->OpenDatabase(nullptr)) { Emsg1(M_FATAL, 0, "%s", db->strerror()); return 1; } @@ -1120,9 +1009,9 @@ int main(int argc, char* argv[]) // Drop temporary index idx_tmp_name DropTmpIdx("idxPIchk", "File"); - db->CloseDatabase(NULL); + db->CloseDatabase(nullptr); DbFlushBackends(); - CloseMsg(NULL); + CloseMsg(nullptr); TermMsg(); return 0; diff --git a/core/src/dird/dbcheck_utils.cc b/core/src/dird/dbcheck_utils.cc new file mode 100644 index 00000000000..39745bb4a44 --- /dev/null +++ b/core/src/dird/dbcheck_utils.cc @@ -0,0 +1,268 @@ +/* + BAREOS® - Backup Archiving REcovery Open Sourced + + Copyright (C) 2021-2021 Bareos GmbH & Co. KG + + This program is Free Software; you can redistribute it and/or + modify it under the terms of version three of the GNU Affero General Public + License as published by the Free Software Foundation, which is + listed in the file LICENSE. + + This program 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 + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#include "dbcheck_utils.h" + +using namespace directordaemon; + +int PrintNameHandler(void* ctx, int num_fields, char** row) +{ + if (row[0]) { printf("%s\n", row[0]); } + return 0; +} + +int GetNameHandler(void* ctx, int num_fields, char** row) +{ + POOLMEM* name = (POOLMEM*)ctx; + + if (row[0]) { PmStrcpy(name, row[0]); } + return 0; +} + +int PrintJobHandler(void* ctx, int num_fields, char** row) +{ + printf(_("JobId=%s Name=\"%s\" StartTime=%s\n"), NPRT(row[0]), NPRT(row[1]), + NPRT(row[2])); + return 0; +} + +int PrintJobmediaHandler(void* ctx, int num_fields, char** row) +{ + printf(_("Orphaned JobMediaId=%s JobId=%s Volume=\"%s\"\n"), NPRT(row[0]), + NPRT(row[1]), NPRT(row[2])); + return 0; +} + +int PrintFileHandler(void* ctx, int num_fields, char** row) +{ + printf(_("Orphaned FileId=%s JobId=%s Volume=\"%s\"\n"), NPRT(row[0]), + NPRT(row[1]), NPRT(row[2])); + return 0; +} + +int PrintFilesetHandler(void* ctx, int num_fields, char** row) +{ + printf(_("Orphaned FileSetId=%s FileSet=\"%s\" MD5=%s\n"), NPRT(row[0]), + NPRT(row[1]), NPRT(row[2])); + return 0; +} + +int PrintClientHandler(void* ctx, int num_fields, char** row) +{ + printf(_("Orphaned ClientId=%s Name=\"%s\"\n"), NPRT(row[0]), NPRT(row[1])); + return 0; +} + +// Called here with each id to be added to the list +int IdListHandler(void* ctx, int num_fields, char** row) +{ + ID_LIST* lst = (ID_LIST*)ctx; + + if (lst->num_ids == MAX_ID_LIST_LEN) { return 1; } + if (lst->num_ids == lst->max_ids) { + if (lst->max_ids == 0) { + lst->max_ids = 10000; + lst->Id = (int64_t*)malloc(sizeof(int64_t) * lst->max_ids); + } else { + lst->max_ids = (lst->max_ids * 3) / 2; + lst->Id = (int64_t*)realloc(lst->Id, sizeof(int64_t) * lst->max_ids); + } + } + lst->Id[lst->num_ids++] = str_to_int64(row[0]); + return 0; +} + +// Construct record id list +int MakeIdList(BareosDb* db, const char* query, ID_LIST* id_list) +{ + id_list->num_ids = 0; + id_list->num_del = 0; + id_list->tot_ids = 0; + + if (!db->SqlQuery(query, IdListHandler, (void*)id_list)) { + printf("%s", db->strerror()); + return 0; + } + return 1; +} + +// Called here with each name to be added to the list +int NameListHandler(void* ctx, int num_fields, char** row) +{ + NameList* name = (NameList*)ctx; + + if (name->num_ids == MAX_ID_LIST_LEN) { return 1; } + if (name->num_ids == name->max_ids) { + if (name->max_ids == 0) { + name->max_ids = 10000; + name->name = (char**)malloc(sizeof(char*) * name->max_ids); + } else { + name->max_ids = (name->max_ids * 3) / 2; + name->name = (char**)realloc(name->name, sizeof(char*) * name->max_ids); + } + } + name->name[name->num_ids++] = strdup(row[0]); + return 0; +} + +// Construct name list +int MakeNameList(BareosDb* db, const char* query, NameList* name_list) +{ + name_list->num_ids = 0; + name_list->num_del = 0; + name_list->tot_ids = 0; + + if (!db->SqlQuery(query, NameListHandler, (void*)name_list)) { + printf("%s", db->strerror()); + return 0; + } + return 1; +} + +// Print names in the list +void PrintNameList(NameList* name_list) +{ + for (int i = 0; i < name_list->num_ids; i++) { + printf("%s\n", name_list->name[i]); + } +} + +// Free names in the list +void FreeNameList(NameList* name_list) +{ + for (int i = 0; i < name_list->num_ids; i++) { free(name_list->name[i]); } + name_list->num_ids = 0; +} + +std::vector get_deletable_storageids( + BareosDb* db, + std::vector orphaned_storage_names_list) +{ + std::string query = "SELECT storageid FROM storage WHERE Name in ("; + for (const auto& orphaned_element : orphaned_storage_names_list) { + query += "'" + orphaned_element + "',"; + } + query.pop_back(); + query += ")"; + + ID_LIST orphaned_storage_ids_list{}; + if (!MakeIdList(db, query.c_str(), &orphaned_storage_ids_list)) { exit(1); } + + std::vector storage_ids_to_delete; + NameList volume_names = {}; + + for (int orphaned_storage_id = 0; + orphaned_storage_id < orphaned_storage_ids_list.num_ids; + ++orphaned_storage_id) { + std::string media_query + = "SELECT volumename FROM media WHERE storageid=" + + std::to_string(orphaned_storage_ids_list.Id[orphaned_storage_id]); + + if (!MakeNameList(db, media_query.c_str(), &volume_names)) { exit(1); } + + if (volume_names.num_ids > 0) { + for (int volumename = 0; volumename < volume_names.num_ids; + ++volumename) { + Emsg3(M_WARNING, 0, + _("Orphaned storage '%s' is being used by volume '%s'. " + "An orphaned storage will only be removed when it is " + "no longer referenced.\n"), + orphaned_storage_names_list[orphaned_storage_id].c_str(), + volume_names.name[volumename]); + } + } + + NameList device_names{}; + std::string device_query + = "SELECT name FROM device WHERE storageid=" + + std::to_string(orphaned_storage_ids_list.Id[orphaned_storage_id]); + + if (!MakeNameList(db, device_query.c_str(), &device_names)) { exit(1); } + + if (device_names.num_ids > 0) { + for (int devicename = 0; devicename < device_names.num_ids; + ++devicename) { + Emsg3(M_WARNING, 0, + _("Orphaned storage '%s' is being used by device '%s'. " + "An orphaned storage will only be removed when it is " + "no longer referenced.\n"), + orphaned_storage_names_list[orphaned_storage_id].c_str(), + device_names.name[devicename]); + } + } + + if (volume_names.num_ids == 0 && device_names.num_ids == 0) { + storage_ids_to_delete.push_back( + orphaned_storage_ids_list.Id[orphaned_storage_id]); + } + } + return storage_ids_to_delete; +} + +static std::vector get_configuration_storages() +{ + std::vector config_storage_names_list; + BareosResource* storage_ressource{nullptr}; + + foreach_res (storage_ressource, R_STORAGE) { + config_storage_names_list.push_back(storage_ressource->resource_name_); + } + + return config_storage_names_list; +} + +std::vector get_orphaned_storages_names(BareosDb* db) +{ + std::vector config_storage_names_list + = get_configuration_storages(); + + std::string query = "SELECT name FROM storage"; + + NameList database_storage_names_list{}; + if (!MakeNameList(db, query.c_str(), &database_storage_names_list)) { + exit(1); + } + + std::vector orphaned_storage_names_list; + + for (int i = 0; i < database_storage_names_list.num_ids; ++i) { + if (std::find(config_storage_names_list.begin(), + config_storage_names_list.end(), + std::string(database_storage_names_list.name[i])) + == config_storage_names_list.end()) { + orphaned_storage_names_list.push_back( + database_storage_names_list.name[i]); + } + } + + FreeNameList(&database_storage_names_list); + return orphaned_storage_names_list; +} + +void delete_storages(BareosDb* db, std::vector storages_to_be_deleted) +{ + for (auto const& storageid : storages_to_be_deleted) { + std::string delete_query + = "DELETE FROM storage WHERE storageid=" + std::to_string(storageid); + + db->SqlQuery(delete_query.c_str(), nullptr, nullptr); + } +} diff --git a/core/src/dird/dbcheck_utils.h b/core/src/dird/dbcheck_utils.h new file mode 100644 index 00000000000..786d6bbf2a7 --- /dev/null +++ b/core/src/dird/dbcheck_utils.h @@ -0,0 +1,80 @@ +/* + BAREOS® - Backup Archiving REcovery Open Sourced + + Copyright (C) 2021-2021 Bareos GmbH & Co. KG + + This program is Free Software; you can redistribute it and/or + modify it under the terms of version three of the GNU Affero General Public + License as published by the Free Software Foundation, which is + listed in the file LICENSE. + + This program 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 + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#ifndef BAREOS_DIRD_DBCHECK_UTILS_H_ +#define BAREOS_DIRD_DBCHECK_UTILS_H_ + +#include "include/bareos.h" +#include "cats/cats.h" +#include "cats/cats_backends.h" +#include "lib/runscript.h" +#include "dird/dird_conf.h" +#include "dird/dird_globals.h" +#include "lib/edit.h" +#include "lib/parse_conf.h" +#include "lib/util.h" +#include + +using namespace directordaemon; + +extern bool ParseDirConfig(const char* configfile, int exit_code); + +typedef struct s_id_ctx { + int64_t* Id; /* ids to be modified */ + int num_ids; /* ids stored */ + int max_ids; /* size of array */ + int num_del; /* number deleted */ + int tot_ids; /* total to process */ +} ID_LIST; + +typedef struct s_name_ctx { + char** name; /* list of names */ + int num_ids; /* ids stored */ + int max_ids; /* size of array */ + int num_del; /* number deleted */ + int tot_ids; /* total to process */ +} NameList; + +#define MAX_ID_LIST_LEN 10000000 + +// Helper functions +int PrintNameHandler(void* ctx, int num_fields, char** row); +int GetNameHandler(void* ctx, int num_fields, char** row); +int PrintJobHandler(void* ctx, int num_fields, char** row); +int PrintJobmediaHandler(void* ctx, int num_fields, char** row); +int PrintFileHandler(void* ctx, int num_fields, char** row); +int PrintFilesetHandler(void* ctx, int num_fields, char** row); +int PrintClientHandler(void* ctx, int num_fields, char** row); +int IdListHandler(void* ctx, int num_fields, char** row); +int MakeIdList(BareosDb* db, const char* query, ID_LIST* id_list); +int NameListHandler(void* ctx, int num_fields, char** row); +int MakeNameList(BareosDb* db, const char* query, s_name_ctx* name_list); +void PrintNameList(s_name_ctx* name_list); +void FreeNameList(s_name_ctx* name_list); + +std::vector get_deletable_storageids( + BareosDb* db, + std::vector orphaned_storage_names_list); +std::vector get_orphaned_storages_names(BareosDb* db); +void delete_storages(BareosDb* db, std::vector storages_to_be_deleted); + + +#endif // BAREOS_DIRD_DBCHECK_UTILS_H_ diff --git a/core/src/dird/ua_cmds.cc b/core/src/dird/ua_cmds.cc index 2df4c013db6..077c526a143 100644 --- a/core/src/dird/ua_cmds.cc +++ b/core/src/dird/ua_cmds.cc @@ -54,6 +54,8 @@ #include "lib/parse_conf.h" #include "lib/util.h" +#include "dird/dbcheck_utils.h" + namespace directordaemon { /* Imported subroutines */ @@ -154,6 +156,7 @@ static bool DeleteJobIdRange(UaContext* ua, char* tok); static bool DeleteVolume(UaContext* ua); static bool DeletePool(UaContext* ua); static void DeleteJob(UaContext* ua); +static void DeleteStorage(UaContext* ua); static bool DoTruncate(UaContext* ua, MediaDbRecord& mr, drive_number_t drive_number); @@ -185,7 +188,7 @@ static struct ua_cmdstruct commands[] = { {NT_(".users"), DotUsersCmd, _("List all user resources"), NULL, true, false}, {NT_(".defaults"), DotDefaultsCmd, _("Get default settings"), - NT_("job= | client= | storage= | client= | storage= | " "pool="), false, false}, #ifdef DEVELOPER @@ -286,8 +289,8 @@ static struct ua_cmdstruct commands[] = { NT_("pool="), false, true}, {NT_("delete"), DeleteCmd, _("Delete volume, pool or job"), NT_("[volume= [yes] | volume pool= [yes] | " - "pool= | jobid= | jobid= | " - "jobid=]"), + "storage storage= | pool= | jobid= | " + "jobid= | jobid=]"), true, true}, {NT_("disable"), DisableCmd, _("Disable a job/client/schedule"), NT_("job= client= schedule="), true, @@ -2252,7 +2255,7 @@ static bool ReloadCmd(UaContext* ua, const char* cmd) static bool DeleteCmd(UaContext* ua, const char* cmd) { static const char* keywords[] - = {NT_("volume"), NT_("pool"), NT_("jobid"), NULL}; + = {NT_("volume"), NT_("pool"), NT_("jobid"), NT_("storage"), NULL}; if (!OpenClientDb(ua, true)) { return true; } ua->send->ObjectStart("deleted"); @@ -2276,6 +2279,9 @@ static bool DeleteCmd(UaContext* ua, const char* cmd) case 2: DeleteJob(ua); break; + case 3: + DeleteStorage(ua); + break; default: ua->WarningMsg(_("Nothing done.\n")); break; @@ -2284,6 +2290,52 @@ static bool DeleteCmd(UaContext* ua, const char* cmd) return true; } +static void DeleteStorage(UaContext* ua) +{ + std::string given_storagename; + if (FindArgWithValue(ua, NT_("storage")) <= 0) { + if (GetCmd(ua, _("Enter storage to delete: "))) { + given_storagename = ua->cmd; + } + } else { + given_storagename = ua->argv[FindArgWithValue(ua, NT_("storage"))]; + } + + std::vector orphaned_storages + = get_orphaned_storages_names(ua->db); + + ua->SendMsg(_("Found %zu orphaned Storage records.\n"), + orphaned_storages.size()); + + if (std::find(orphaned_storages.begin(), orphaned_storages.end(), + given_storagename) + == orphaned_storages.end()) { + ua->ErrorMsg( + _("The given storage '%s' either does not exist at all, or still " + "exists in the configuration. In order to remove a storage " + "from the catalog, its configuration must be removed first.\n"), + given_storagename.c_str()); + return; + } + + std::vector storages_to_be_deleted; + if (orphaned_storages.size() > 0) { + for (int i = orphaned_storages.size() - 1; i >= 0; i--) { + if (orphaned_storages[i] != given_storagename) { + orphaned_storages.erase(orphaned_storages.begin() + i); + } + } + storages_to_be_deleted + = get_deletable_storageids(ua->db, orphaned_storages); + } + + if (storages_to_be_deleted.size() > 0) { + ua->SendMsg(_("Deleting %zu orphaned storage record(s).\n"), + storages_to_be_deleted.size()); + delete_storages(ua->db, storages_to_be_deleted); + } +} + /** * DeleteJob has been modified to parse JobID lists like the following: * delete JobID=3,4,6,7-11,14 diff --git a/docs/manuals/source/Appendix/BareosPrograms.rst b/docs/manuals/source/Appendix/BareosPrograms.rst index cda83ef26d2..332d434bd05 100644 --- a/docs/manuals/source/Appendix/BareosPrograms.rst +++ b/docs/manuals/source/Appendix/BareosPrograms.rst @@ -1148,27 +1148,26 @@ If the :strong:`-b` option is specified, :command:`bareos-dbcheck` will run in b .. code-block:: shell-session - Hello, this is the database check/correct program. - Modify database is off. Verbose is off. - Please select the function you want to perform. - 1) Toggle modify database flag - 2) Toggle verbose flag - 3) Repair bad Filename records - 4) Repair bad Path records - 5) Eliminate duplicate Filename records - 6) Eliminate duplicate Path records - 7) Eliminate orphaned Jobmedia records - 8) Eliminate orphaned File records - 9) Eliminate orphaned Path records - 10) Eliminate orphaned Filename records - 11) Eliminate orphaned FileSet records - 12) Eliminate orphaned Client records - 13) Eliminate orphaned Job records - 14) Eliminate all Admin records - 15) Eliminate all Restore records - 16) All (3-15) - 17) Quit - Select function number: + Hello, this is the Bareos database check/correct program. + Modify database is off. Verbose is off. + Please select the function you want to perform. + 0) Quit + 1) Toggle modify database flag + 2) Toggle verbose flag + 3) Check for bad Filename records + 4) Check for bad Path records + 5) Check for duplicate Path records + 6) Check for orphaned Jobmedia records + 7) Check for orphaned File records + 8) Check for orphaned Path records + 9) Check for orphaned FileSet records + 10) Check for orphaned Client records + 11) Check for orphaned Job records + 12) Check for orphaned storage records + 13) Check for all Admin records + 14) Check for all Restore records + 15) Run ALL checks + Select function number: By entering 1 or 2, you can toggle the modify database flag (:strong:`-f` option) and the verbose flag (:strong:`-v`). It can be helpful and reassuring to turn off the modify database flag, then select one or more of the consistency checks (items 3 through 13) to see what will be done, then toggle the modify flag on and re-run the check. @@ -1199,6 +1198,8 @@ The inconsistencies examined are the following: - Orphaned Job records. If no client is defined for a job or you do not run a job for a long time, you can accumulate old job records. This option allow you to remove jobs that are not attached to any client (and thus useless). +- Orphaned storage records. If you delete a storage configuration file from the bareos configurations folder, you end up with unused storages in the database that can cause certain visual inconsistencies. This option allows you to delete these orphaned storages, but you have to make sure first that they are not used by any Media or Device. + - All Admin records. This command will remove all Admin records, regardless of their age. - All Restore records. This command will remove all Restore records, regardless of their age. diff --git a/docs/manuals/source/TasksAndConcepts/BareosConsole.rst b/docs/manuals/source/TasksAndConcepts/BareosConsole.rst index 312ef58c5b3..45c43033978 100644 --- a/docs/manuals/source/TasksAndConcepts/BareosConsole.rst +++ b/docs/manuals/source/TasksAndConcepts/BareosConsole.rst @@ -202,7 +202,7 @@ catalog catalogs Used in the show command. Takes no arguments. -client | fd +client | fd Used to specify a client (or filedaemon). clients @@ -220,7 +220,7 @@ days devices Used in the show command. Takes no arguments. -director | dir | directors +director | dir | directors Used in the show and status command. Takes no arguments. directory @@ -264,7 +264,7 @@ jobid JobId can be used on the :bcommand:`rerun` command to select all jobs failed after and including the given jobid for rerunning. -job | jobname +job | jobname The Job or Jobname keyword refers to the name you specified in the Job resource, and hence it refers to any number of Jobs that ran. It is typically useful if you want to list all jobs of a particular name. level @@ -282,7 +282,7 @@ messages media Used in the list and llist commands. Takes no arguments. -nextvolume | nextvol +nextvolume | nextvol Used in the list and llist commands. Takes no arguments. on @@ -306,7 +306,7 @@ limit schedules Used in the show command. Takes no arguments. -storage | store | sd +storage | store | sd Used to specify the name of a storage daemon. storages @@ -510,7 +510,7 @@ configure delete :index:`\ `\ The delete command is used to delete a Volume, Pool or Job record from the Catalog as well as all associated catalog Volume records that were created. This command operates only on the Catalog database and has no effect on the actual data written to a Volume. This command can be dangerous and we strongly recommend that you do not use it unless you know what you are doing. - If the keyword Volume appears on the command line, the named Volume will be deleted from the catalog, if the keyword Pool appears on the command line, a Pool will be deleted, and if the keyword Job appears on the command line, a Job and all its associated records (File and JobMedia) will be deleted from the catalog. + If the keyword Volume appears on the command line, the named Volume will be deleted from the catalog. If the keyword Pool appears on the command line, a Pool will be deleted. If the keyword Job appears on the command line, a Job and all its associated records (File and JobMedia) will be deleted from the catalog. If the keyword storage appears on the command line, the orphaned storage with the selected name will be deleted. The full form of this command is: @@ -521,8 +521,9 @@ delete delete volume= pool= delete JobId= JobId= ... delete Job JobId=n,m,o-r,t ... + delete storage= - The first form deletes a Pool record from the catalog database. The second form deletes a Volume record from the specified pool in the catalog database. The third form deletes the specified Job record from the catalog database. The last form deletes JobId records for JobIds n, m, o, p, q, r, and t. Where each one of the n,m,... is, of course, a number. That is a "delete jobid" accepts lists and ranges of jobids. + The first form deletes a Pool record from the catalog database. The second form deletes a Volume record from the specified pool in the catalog database. The third form deletes the specified Job record from the catalog database. The fourth form deletes JobId records for JobIds n, m, o, p, q, r, and t. Where each one of the n,m,... is, of course, a number. That is a "delete jobid" accepts lists and ranges of jobids. The last form deletes the selected storage from the database, only if it is orphaned, meaning if still persists in the database even though its configuration has been removed and there are no volumes or devices associated with it anymore. disable :index:`\ `\ This command permits you to disable a Job for automatic scheduling. The job may have been previously enabled with the Job resource Enabled directive or using the console enable command. The next time the Director is reloaded or restarted, the Enable/Disable state will be set to the value in the Job resource (default enabled) as defined in the |dir| configuration. diff --git a/systemtests/tests/multiplied-device/etc/bareos/bareos-dir.d/storage/fakestorage1.conf.in b/systemtests/tests/multiplied-device/etc/bareos/bareos-dir.d/storage/fakestorage1.conf.in new file mode 100644 index 00000000000..ca66a781b20 --- /dev/null +++ b/systemtests/tests/multiplied-device/etc/bareos/bareos-dir.d/storage/fakestorage1.conf.in @@ -0,0 +1,8 @@ +Storage { + Name = fakestorage1 + Address = @hostname@ + Password = "@sd_password@" + Device = FileStorage + Media Type = File + SD Port = @sd_port@ +} diff --git a/systemtests/tests/multiplied-device/etc/bareos/bareos-dir.d/storage/fakestorage2.conf.in b/systemtests/tests/multiplied-device/etc/bareos/bareos-dir.d/storage/fakestorage2.conf.in new file mode 100644 index 00000000000..590d3e4a2de --- /dev/null +++ b/systemtests/tests/multiplied-device/etc/bareos/bareos-dir.d/storage/fakestorage2.conf.in @@ -0,0 +1,8 @@ +Storage { + Name = fakestorage2 + Address = @hostname@ + Password = "@sd_password@" + Device = FileStorage + Media Type = File + SD Port = @sd_port@ +} diff --git a/systemtests/tests/multiplied-device/etc/bareos/bareos-sd.d/device/FileStorage.conf b/systemtests/tests/multiplied-device/etc/bareos/bareos-sd.d/device/FileStorage.conf index 48290d23ab2..1a9fa89e7b1 100644 --- a/systemtests/tests/multiplied-device/etc/bareos/bareos-sd.d/device/FileStorage.conf +++ b/systemtests/tests/multiplied-device/etc/bareos/bareos-sd.d/device/FileStorage.conf @@ -1,5 +1,5 @@ Device { - Name = MultiFileStorage + Name = FileStorage Media Type = File Archive Device = storage LabelMedia = yes; # lets Bareos label unlabeled media @@ -7,8 +7,6 @@ Device { AutomaticMount = yes; # when device opened, read it RemovableMedia = no; AlwaysOpen = no; - Description = "File device. Will be multiplied 3 times" - Count = 3 MaximumConcurrentJobs = 1 autoselect = no #needed to test the setdevice command (see testrunner) } diff --git a/systemtests/tests/multiplied-device/etc/bareos/bareos-sd.d/device/MultiFileStorage.conf b/systemtests/tests/multiplied-device/etc/bareos/bareos-sd.d/device/MultiFileStorage.conf new file mode 100644 index 00000000000..48290d23ab2 --- /dev/null +++ b/systemtests/tests/multiplied-device/etc/bareos/bareos-sd.d/device/MultiFileStorage.conf @@ -0,0 +1,14 @@ +Device { + Name = MultiFileStorage + Media Type = File + Archive Device = storage + LabelMedia = yes; # lets Bareos label unlabeled media + Random Access = yes; + AutomaticMount = yes; # when device opened, read it + RemovableMedia = no; + AlwaysOpen = no; + Description = "File device. Will be multiplied 3 times" + Count = 3 + MaximumConcurrentJobs = 1 + autoselect = no #needed to test the setdevice command (see testrunner) +} diff --git a/systemtests/tests/multiplied-device/testrunner b/systemtests/tests/multiplied-device/testrunner index e921fd03c74..47af06ebec4 100755 --- a/systemtests/tests/multiplied-device/testrunner +++ b/systemtests/tests/multiplied-device/testrunner @@ -67,8 +67,31 @@ END_OF_DATA run_bareos "$@" check_for_zombie_jobs storage=File + +# rename the storages instead of deleting them +mv ./etc/bareos/bareos-dir.d/storage/fakestorage1.conf ./etc/bareos/bareos-dir.d/storage/fakestorage1.conf_backup +mv ./etc/bareos/bareos-dir.d/storage/fakestorage2.conf ./etc/bareos/bareos-dir.d/storage/fakestorage2.conf_backup + +cat <"$tmp/bconcmds" +@$out $tmp/log4.out +label volume=fakevolume storage=fakestorage1 pool=Full +reload +delete storage=File +delete storage=fakestorage1 +messages +delete storage=fakestorage2 +messages +quit +END_OF_DATA + +run_bconsole stop_bareos +# rename files to be able to run the test again later +mv ./etc/bareos/bareos-dir.d/storage/fakestorage1.conf_backup ./etc/bareos/bareos-dir.d/storage/fakestorage1.conf +mv ./etc/bareos/bareos-dir.d/storage/fakestorage2.conf_backup ./etc/bareos/bareos-dir.d/storage/fakestorage2.conf + + check_two_logs check_restore_diff ${BackupDirectory} @@ -122,4 +145,34 @@ for i in 1 2 3; do estat=3 fi done + +# check if the relabel went correctly +grep "3000 OK label. VolBytes=... Volume=\"fakevolume\" Device=\"FileStorage\" (storage)" "${tmp}"/log4.out >/dev/null 2>&1 +if test $? -ne 0 ; then + echo "The fakevolume relabel failed." + estat=1; +fi + +# make sure exact number of orphaned storages is found +grep "Found 2 orphaned Storage records" "${tmp}"/log4.out >/dev/null 2>&1 +if test $? -ne 0 ; then + echo "The delete command failed to find the exact number of fakestorages." + estat=1; +fi + +# make sure storage 'File' cannot be deleted +grep "The given storage 'File' either does not exist at all, or still exists in the configuration." "${tmp}"/log4.out >/dev/null 2>&1 +if test $? -ne 0 ; then + echo "The delete command failed for an existing storage." + estat=1; +fi + + +# make sure extra storages fakestorage1 cannot be deleted +grep "Orphaned storage 'fakestorage1' is being used by volume 'fakevolume'" "${tmp}"/log4.out >/dev/null 2>&1 +if test $? -ne 0 ; then + echo "The delete command failed to detect fakestorages related to other medias." + estat=1; +fi + end_test