From 1ebe9c962c5ef51423ff9f49c1377d9507c8b649 Mon Sep 17 00:00:00 2001 From: Joerg Steffens Date: Thu, 3 Nov 2016 17:43:08 +0100 Subject: [PATCH] added truncate command The truncate command allows to truncate purged volumes. This can be use to free some disk space. --- src/cats/cats.h | 6 +- src/cats/protos.h | 8 +- src/cats/sql.c | 11 ++ src/cats/sql_get.c | 158 ++++++++++++++++++------- src/cats/sql_list.c | 26 +++- src/dird/protos.h | 5 + src/dird/ua.h | 19 ++- src/dird/ua_acl.c | 12 ++ src/dird/ua_cmds.c | 276 ++++++++++++++++++++++++++++++++++++++++--- src/dird/ua_input.c | 21 ++++ src/dird/ua_label.c | 13 +- src/dird/ua_output.c | 25 ++++ src/dird/ua_select.c | 4 +- 13 files changed, 507 insertions(+), 77 deletions(-) diff --git a/src/cats/cats.h b/src/cats/cats.h index 036907a438e..ee70aea700b 100644 --- a/src/cats/cats.h +++ b/src/cats/cats.h @@ -66,9 +66,13 @@ class dbid_list : public SMARTALLOC { dbid_list(); /* in sql.c */ ~dbid_list(); /* in sql.c */ + + int size() const { return num_ids; } + DBId_t get(int i) const; }; -/* Job information passed to create job record and update +/* + * Job information passed to create job record and update * job record at end of job. Note, although this record * contains all the fields found in the Job database record, * it also contains fields found in the JobMedia record. diff --git a/src/cats/protos.h b/src/cats/protos.h index f004a2b5b5d..2b801763841 100644 --- a/src/cats/protos.h +++ b/src/cats/protos.h @@ -102,11 +102,13 @@ int db_get_num_pool_records(JCR *jcr, B_DB *mdb); int db_get_pool_ids(JCR *jcr, B_DB *mdb, int *num_ids, DBId_t **ids); bool db_get_client_ids(JCR *jcr, B_DB *mdb, int *num_ids, DBId_t **ids); int db_get_storage_ids(JCR *jcr, B_DB *mdb, int *num_ids, DBId_t **ids); +bool prepare_media_sql_query(JCR *jcr, B_DB *mdb, MEDIA_DBR *mr, POOL_MEM &volumes); bool db_get_media_ids(JCR *jcr, B_DB *mdb, MEDIA_DBR *mr, POOL_MEM &volumes, int *num_ids, DBId_t **ids); +bool db_get_query_dbids(JCR *jcr, B_DB *mdb, POOL_MEM &query, dbid_list &ids); +bool verify_media_ids_from_single_storage(JCR *jcr, B_DB *mdb, dbid_list &ids); int db_get_job_volume_parameters(JCR *jcr, B_DB *mdb, JobId_t JobId, VOL_PARAMS **VolParams); bool db_get_client_record(JCR *jcr, B_DB *mdb, CLIENT_DBR *cdbr); bool db_get_counter_record(JCR *jcr, B_DB *mdb, COUNTER_DBR *cr); -bool db_get_query_dbids(JCR *jcr, B_DB *mdb, POOL_MEM &query, dbid_list &ids); bool db_get_file_list(JCR *jcr, B_DB *mdb, char *jobids, bool use_md5, bool use_delta, DB_RESULT_HANDLER *result_handler, void *ctx); @@ -147,6 +149,10 @@ bool db_list_sql_query(JCR *jcr, B_DB *mdb, const char *query, bool db_list_sql_query(JCR *jcr, B_DB *mdb, const char *query, OUTPUT_FORMATTER *sendit, e_list_type type, const char *description, bool verbose = false); +bool db_list_sql_query_start(JCR *jcr, B_DB *mdb, const char *query, + OUTPUT_FORMATTER *sendit, e_list_type type, + const char *description, bool verbose = false); +void db_list_sql_query_end(JCR *jcr, B_DB *mdb); void db_list_client_records(JCR *jcr, B_DB *mdb, char *clientname, OUTPUT_FORMATTER *sendit, e_list_type type); void db_list_storage_records(JCR *jcr, B_DB *mdb, diff --git a/src/cats/sql.c b/src/cats/sql.c index 07779b22b11..1f3a74d7277 100644 --- a/src/cats/sql.c +++ b/src/cats/sql.c @@ -56,6 +56,17 @@ dbid_list::~dbid_list() free(DBId); } +DBId_t dbid_list::get(int i) const { + if (i >= size()) { + Emsg2(M_ERROR_TERM, 0, + _("Unable to access dbid_list entry %d. Only %d entries available.\n"), + i, size()); + return (DBId_t)0; + } + return DBId[i]; +} + + /* * Called here to retrieve an integer from the database */ diff --git a/src/cats/sql_get.c b/src/cats/sql_get.c index f218c8b0fd3..a0551de43cd 100644 --- a/src/cats/sql_get.c +++ b/src/cats/sql_get.c @@ -953,91 +953,138 @@ int db_get_num_media_records(JCR *jcr, B_DB *mdb) } /** - * This function returns a list of all the Media record ids for + * This function creates a sql query string at mdb->cmd to return a list of all the Media records for * the current Pool, the correct Media Type, Recyle, Enabled, StorageId, VolBytes and * volumes or VolumeName if specified. Comma separated list of volumes takes precedence * over VolumeName. The caller must free ids if non-NULL. - * - * Returns false: on failure - * true: on success */ -bool db_get_media_ids(JCR *jcr, B_DB *mdb, MEDIA_DBR *mr, POOL_MEM &volumes, int *num_ids, DBId_t **ids) +bool prepare_media_sql_query(JCR *jcr, B_DB *mdb, MEDIA_DBR *mr, POOL_MEM &volumes) { - SQL_ROW row; - int i = 0; - DBId_t *id; + bool ok = true; char ed1[50]; - bool ok = false; char esc[MAX_NAME_LENGTH * 2 + 1]; - bool have_volumes = false; POOL_MEM buf(PM_MESSAGE); - db_lock(mdb); - *ids = NULL; - - if (*volumes.c_str()) { - have_volumes = true; - } + /* + * columns we care of. + * Reduced, to be better displayable. + * Important: + * column 2: pool.name, column 3: storage.name, + * as this is used for ACL handling (counting starts at 0). + */ + const char *columns = + "Media.MediaId," + "Media.VolumeName," + "Pool.Name AS Pool," + "Storage.Name AS Storage," + "Media.MediaType," + /* "Media.DeviceId," */ + /* "Media.FirstWritten, "*/ + "Media.LastWritten," + "Media.VolFiles," + "Media.VolBytes," + "Media.VolStatus," + /* "Media.Recycle AS Recycle," */ + "Media.ActionOnPurge," + /* "Media.VolRetention," */ + "Media.Comment"; - Mmsg(mdb->cmd, "SELECT DISTINCT MediaId FROM Media WHERE Recycle=%d AND Enabled=%d ", - mr->Recycle, mr->Enabled); + Mmsg(mdb->cmd, + "SELECT DISTINCT %s FROM Media " + "LEFT JOIN Pool USING(PoolId) " + "LEFT JOIN Storage USING(StorageId) " + "WHERE Media.Recycle=%d AND Media.Enabled=%d ", + columns, mr->Recycle, mr->Enabled); if (*mr->MediaType) { db_escape_string(jcr, mdb, esc, mr->MediaType, strlen(mr->MediaType)); - Mmsg(buf, "AND MediaType='%s' ", esc); + Mmsg(buf, "AND Media.MediaType='%s' ", esc); pm_strcat(mdb->cmd, buf.c_str()); } if (mr->StorageId) { - Mmsg(buf, "AND StorageId=%s ", edit_uint64(mr->StorageId, ed1)); + Mmsg(buf, "AND Media.StorageId=%s ", edit_uint64(mr->StorageId, ed1)); pm_strcat(mdb->cmd, buf.c_str()); } if (mr->PoolId) { - Mmsg(buf, "AND PoolId=%s ", edit_uint64(mr->PoolId, ed1)); + Mmsg(buf, "AND Media.PoolId=%s ", edit_uint64(mr->PoolId, ed1)); pm_strcat(mdb->cmd, buf.c_str()); } if (mr->VolBytes) { - Mmsg(buf, "AND VolBytes > %s ", edit_uint64(mr->VolBytes, ed1)); + Mmsg(buf, "AND Media.VolBytes > %s ", edit_uint64(mr->VolBytes, ed1)); pm_strcat(mdb->cmd, buf.c_str()); } if (*mr->VolStatus) { db_escape_string(jcr, mdb, esc, mr->VolStatus, strlen(mr->VolStatus)); - Mmsg(buf, "AND VolStatus = '%s' ", esc); + Mmsg(buf, "AND Media.VolStatus = '%s' ", esc); pm_strcat(mdb->cmd, buf.c_str()); } - if (*mr->VolumeName && !have_volumes) { + if (volumes.strlen() > 0) { + /* extra list of volumes given */ + Mmsg(buf, "AND Media.VolumeName IN (%s) ", volumes.c_str()); + pm_strcat(mdb->cmd, buf.c_str()); + } else if (*mr->VolumeName) { + /* single volume given in media record */ db_escape_string(jcr, mdb, esc, mr->VolumeName, strlen(mr->VolumeName)); - Mmsg(buf, "AND VolumeName = '%s' ", esc); + Mmsg(buf, "AND Media.VolumeName = '%s' ", esc); pm_strcat(mdb->cmd, buf.c_str()); } - if (have_volumes) { - Mmsg(buf, "AND VolumeName IN (%s) ", volumes.c_str()); - pm_strcat(mdb->cmd, buf.c_str()); - } + Dmsg1(100, "query=%s\n", mdb->cmd); - Dmsg1(100, "q=%s\n", mdb->cmd); + return ok; +} - if (QUERY_DB(jcr, mdb, mdb->cmd)) { - *num_ids = sql_num_rows(mdb); - if (*num_ids > 0) { - id = (DBId_t *)malloc(*num_ids * sizeof(DBId_t)); - while ((row = sql_fetch_row(mdb)) != NULL) { - id[i++] = str_to_uint64(row[0]); - } - *ids = id; - } - sql_free_result(mdb); - ok = true; - } else { + + + +/** + * This function returns a list of all the Media record ids for + * the current Pool, the correct Media Type, Recyle, Enabled, StorageId, VolBytes and + * volumes or VolumeName if specified. Comma separated list of volumes takes precedence + * over VolumeName. The caller must free ids if non-NULL. + * + * Returns false: on failure + * true: on success + */ +bool db_get_media_ids(JCR *jcr, B_DB *mdb, MEDIA_DBR *mr, POOL_MEM &volumes, int *num_ids, DBId_t **ids) +{ + bool ok = false; + SQL_ROW row; + int i = 0; + DBId_t *id; + + db_lock(mdb); + *ids = NULL; + + if (!prepare_media_sql_query(jcr, mdb, mr, volumes)) { + Mmsg(mdb->errmsg, _("Media id select failed: invalid parameter")); + Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg); + goto bail_out; + } + + if (!QUERY_DB(jcr, mdb, mdb->cmd)) { Mmsg(mdb->errmsg, _("Media id select failed: ERR=%s\n"), sql_strerror(mdb)); Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg); - ok = false; + goto bail_out; + } + + *num_ids = sql_num_rows(mdb); + if (*num_ids > 0) { + id = (DBId_t *)malloc(*num_ids * sizeof(DBId_t)); + while ((row = sql_fetch_row(mdb)) != NULL) { + id[i++] = str_to_uint64(row[0]); + } + *ids = id; } + sql_free_result(mdb); + ok = true; + +bail_out: db_unlock(mdb); return ok; } @@ -1078,6 +1125,31 @@ bool db_get_query_dbids(JCR *jcr, B_DB *mdb, POOL_MEM &query, dbid_list &ids) return ok; } +bool verify_media_ids_from_single_storage(JCR *jcr, B_DB *mdb, dbid_list &mediaIds) +{ + MEDIA_DBR mr; + DBId_t storageId = 0; + + /* + * verify that all media use the same storage. + */ + for (int i = 0; i < mediaIds.size(); i++) { + memset(&mr, 0, sizeof(mr)); + mr.MediaId = mediaIds.get(i); + if (!db_get_media_record(jcr, mdb, &mr)) { + Mmsg1(mdb->errmsg, _("Failed to find MediaId=%lld\n"), (uint64_t)mr.MediaId); + Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg); + return false; + } else if (i == 0) { + storageId = mr.StorageId; + } else if (storageId != mr.StorageId) { + return false; + } + } + return true; +} + + /** * Get Media Record * diff --git a/src/cats/sql_list.c b/src/cats/sql_list.c index a38c717ba51..082c7f8024b 100644 --- a/src/cats/sql_list.c +++ b/src/cats/sql_list.c @@ -57,24 +57,40 @@ bool db_list_sql_query(JCR *jcr, B_DB *mdb, const char *query, { bool retval = false; + if (db_list_sql_query_start(jcr, mdb, query, sendit, type, description, verbose)) { + retval = true; + db_list_sql_query_end(jcr, mdb); + } + + return retval; +} + +bool db_list_sql_query_start(JCR *jcr, B_DB *mdb, const char *query, + OUTPUT_FORMATTER *sendit, e_list_type type, + const char *description, bool verbose) +{ db_lock(mdb); if (!sql_query(mdb, query, QF_STORE_RESULT)) { Mmsg(mdb->errmsg, _("Query failed: %s\n"), sql_strerror(mdb)); if (verbose) { sendit->decoration(mdb->errmsg); } - goto bail_out; + db_unlock(mdb); + return false; } sendit->array_start(description); list_result(jcr, mdb, sendit, type); sendit->array_end(description); - sql_free_result(mdb); - retval = true; -bail_out: + return true; +} + + +void db_list_sql_query_end(JCR *jcr, B_DB *mdb) +{ + sql_free_result(mdb); db_unlock(mdb); - return retval; } void db_list_pool_records(JCR *jcr, B_DB *mdb, POOL_DBR *pdbr, diff --git a/src/dird/protos.h b/src/dird/protos.h index bfb7de94def..135ce89ee5b 100644 --- a/src/dird/protos.h +++ b/src/dird/protos.h @@ -335,12 +335,17 @@ bool get_cmd(UAContext *ua, const char *prompt, bool subprompt = false); bool get_pint(UAContext *ua, const char *prompt); bool get_yesno(UAContext *ua, const char *prompt); bool is_yesno(char *val, bool *ret); +bool get_confirmation(UAContext *ua, const char *prompt = _("Confirm (yes/no): ")); int get_enabled(UAContext *ua, const char *val); void parse_ua_args(UAContext *ua); bool is_comment_legal(UAContext *ua, const char *name); /* ua_label.c */ bool is_volume_name_legal(UAContext *ua, const char *name); +bool send_label_request(UAContext *ua, + STORERES *store, MEDIA_DBR *mr, MEDIA_DBR *omr, POOL_DBR *pr, + bool media_record_exists, bool relabel, + drive_number_t drive, slot_number_t slot); /* ua_update.c */ void update_vol_pool(UAContext *ua, char *val, MEDIA_DBR *mr, POOL_DBR *opr); diff --git a/src/dird/ua.h b/src/dird/ua.h index 8afad6da591..c2c85f37c6b 100644 --- a/src/dird/ua.h +++ b/src/dird/ua.h @@ -31,6 +31,15 @@ #define MAX_ID_LIST_LEN 2000000 +struct ua_cmdstruct { + const char *key; /* Command */ + bool (*func)(UAContext *ua, const char *cmd); /* Handler */ + const char *help; /* Main purpose */ + const char *usage; /* All arguments to build usage */ + const bool use_in_rs; /* Can use it in Console RunScript */ + const bool audit_event; /* Log an audit event when this Command is executed */ +}; + class UAContext { public: /* @@ -55,7 +64,6 @@ class UAContext { int max_prompts; /* Max size of list */ int num_prompts; /* Current number in list */ int api; /* For programs want an API */ - int cmd_index; /* Index in command table */ bool auto_display_messages; /* If set, display messages */ bool user_notified_msg_pending; /* Set when user notified */ bool automount; /* If set, mount after label */ @@ -70,6 +78,11 @@ class UAContext { OUTPUT_FORMATTER *send; /* object instance to handle output */ private: + /* + * Members + */ + ua_cmdstruct *cmddef; /* Definition of the currently executed command */ + /* * Methods */ @@ -77,12 +90,14 @@ class UAContext { int rcode_to_acltype(int rcode); void log_audit_event_acl_failure(int acl, const char *item); void log_audit_event_acl_success(int acl, const char *item); + void set_command_definition(ua_cmdstruct *cmd) { cmddef = cmd; } public: /* * Methods */ void signal(int sig) { UA_sock->signal(sig); }; + bool execute(ua_cmdstruct *cmd); /* * ACL check method. @@ -98,6 +113,7 @@ class UAContext { RES *GetResWithName(int rcode, const char *name, bool audit_event = false, bool lock = true); POOLRES *GetPoolResWithName(const char *name, bool audit_event = true, bool lock = true); STORERES *GetStoreResWithName(const char *name, bool audit_event = true, bool lock = true); + STORERES *GetStoreResWithId(DBId_t id, bool audit_event = true, bool lock = true); CLIENTRES *GetClientResWithName(const char *name, bool audit_event = true, bool lock = true); JOBRES *GetJobResWithName(const char *name, bool audit_event = true, bool lock = true); FILESETRES *GetFileSetResWithName(const char *name, bool audit_event = true, bool lock = true); @@ -117,6 +133,7 @@ class UAContext { void error_msg(const char *fmt, ...); void warning_msg(const char *fmt, ...); void info_msg(const char *fmt, ...); + void send_cmd_usage(const char *fmt, ...); }; /* diff --git a/src/dird/ua_acl.c b/src/dird/ua_acl.c index ad8824b1af9..b8c4e58aeb3 100644 --- a/src/dird/ua_acl.c +++ b/src/dird/ua_acl.c @@ -402,6 +402,18 @@ STORERES *UAContext::GetStoreResWithName(const char *name, bool audit_event, boo return (STORERES *)GetResWithName(R_STORAGE, name, audit_event, lock); } +STORERES *UAContext::GetStoreResWithId(DBId_t id, bool audit_event, bool lock) +{ + STORAGE_DBR storage_dbr; + memset(&storage_dbr, 0, sizeof(storage_dbr)); + + storage_dbr.StorageId = id; + if (db_get_storage_record(jcr, db, &storage_dbr)) { + return GetStoreResWithName(storage_dbr.Name, audit_event, lock); + } + return NULL; +} + CLIENTRES *UAContext::GetClientResWithName(const char *name, bool audit_event, bool lock) { return (CLIENTRES *)GetResWithName(R_CLIENT, name, audit_event, lock); diff --git a/src/dird/ua_cmds.c b/src/dird/ua_cmds.c index eccfa8f0d1f..42b4b6a4b83 100644 --- a/src/dird/ua_cmds.c +++ b/src/dird/ua_cmds.c @@ -111,12 +111,13 @@ static bool mount_cmd(UAContext *ua, const char *cmd); static bool noop_cmd(UAContext *ua, const char *cmd); static bool release_cmd(UAContext *ua, const char *cmd); static bool reload_cmd(UAContext *ua, const char *cmd); +static bool resolve_cmd(UAContext *ua, const char *cmd); static bool setdebug_cmd(UAContext *ua, const char *cmd); static bool setbwlimit_cmd(UAContext *ua, const char *cmd); static bool setip_cmd(UAContext *ua, const char *cmd); static bool time_cmd(UAContext *ua, const char *cmd); -static bool resolve_cmd(UAContext *ua, const char *cmd); static bool trace_cmd(UAContext *ua, const char *cmd); +static bool truncate_cmd(UAContext *ua, const char *cmd); static bool unmount_cmd(UAContext *ua, const char *cmd); static bool use_cmd(UAContext *ua, const char *cmd); static bool var_cmd(UAContext *ua, const char *cmd); @@ -128,6 +129,7 @@ static bool delete_job_id_range(UAContext *ua, char *tok); static bool delete_volume(UAContext *ua); static bool delete_pool(UAContext *ua); static void delete_job(UAContext *ua); +static bool do_truncate(UAContext *ua, MEDIA_DBR &mr); bool quit_cmd(UAContext *ua, const char *cmd); @@ -136,15 +138,7 @@ bool quit_cmd(UAContext *ua, const char *cmd); * New commands are added after existing commands with similar letters * to prevent breakage of existing user scripts. */ -struct cmdstruct { - const char *key; /* Command */ - bool (*func)(UAContext *ua, const char *cmd); /* Handler */ - const char *help; /* Main purpose */ - const char *usage; /* All arguments to build usage */ - const bool use_in_rs; /* Can use it in Console RunScript */ - const bool audit_event; /* Log an audit event when this Command is executed */ -}; -static struct cmdstruct commands[] = { +static struct ua_cmdstruct commands[] = { { NT_("."), noop_cmd, _("no op"), NULL, true, false }, { NT_(".actiononpurge"), dot_aop_cmd, _("List possible actions on purge"), @@ -262,7 +256,7 @@ static struct cmdstruct commands[] = { "\tenable estimate exit gui label list llist\n" "\tmessages memory mount prune purge quit query\n" "\trestore relabel release reload run status\n" - "\tsetbandwidth setdebug setip show sqlquery time trace unmount\n" + "\tsetbandwidth setdebug setip show sqlquery time trace truncate unmount\n" "\tumount update use var version wait"), false, false }, { NT_("import"), import_cmd, _("Import volumes from import/export slots to normal slots"), NT_("storage= [ srcslots= dstslots= volume= scan ]"), true, true }, @@ -291,8 +285,8 @@ static struct cmdstruct commands[] = { "storages |\n" "volumes [ jobid= | ujobid= | pool= | all ] |\n" "volume= |\n" - "[ current ] | [ enabled ] | [disabled] |\n" - "[ limit= [ offset= ] ]"), true, true }, + "[current] | [enabled | disabled] |\n" + "[limit= [offset=]]"), true, true }, { NT_("llist"), llist_cmd, _("Full or long list like list command"), NT_("basefiles jobid= | basefiles ujobid= |\n" "backups client= [fileset=] [jobstatus=] [level=] [order=] [limit=] [days=] [hours=]|\n" @@ -384,6 +378,8 @@ static struct cmdstruct commands[] = { NT_(""), true, false }, { NT_("trace"), trace_cmd, _("Turn on/off trace to file"), NT_("on | off"), true, true }, + { NT_("truncate"), truncate_cmd, _("Truncate purged volumes"), + NT_("volstatus=Purged [storage=] [pool=] [volume=] [yes]"), true, true }, { NT_("unmount"), unmount_cmd, _("Unmount storage"), NT_("storage= [ drive= ]\n" "\tjobid= | job= | ujobid="), false, true }, @@ -411,7 +407,13 @@ static struct cmdstruct commands[] = { NT_("jobname= | jobid= | ujobid= | mount [timeout=]"), false, false } }; -#define comsize ((int)(sizeof(commands)/sizeof(struct cmdstruct))) +#define comsize ((int)(sizeof(commands)/sizeof(struct ua_cmdstruct))) + +bool UAContext::execute(ua_cmdstruct *cmd) +{ + set_command_definition(cmd); + return (cmd->func)(this, this->cmd); +} /* * Execute a command from the UA @@ -472,7 +474,7 @@ bool do_a_command(UAContext *ua) user->signal(BNET_CMD_BEGIN); } ua->send->set_mode(ua->api); - ok = (*commands[i].func)(ua, ua->cmd); /* go execute command */ + ok = ua->execute(&commands[i]); if (ua->api) { user->signal(ok ? BNET_CMD_OK : BNET_CMD_FAILED); } @@ -1804,6 +1806,250 @@ static bool time_cmd(UAContext *ua, const char *cmd) return true; } + + +/* + * truncate command. Truncates volumes (volume files) on the storage daemon. + * + * usage: + * truncate volstatus=Purged [storage=] [pool=] [volume=] [yes] + */ +static bool truncate_cmd(UAContext *ua, const char *cmd) +{ + bool result = false; + int i = -1; + int parsed_args = 1; /* start at 1, as command itself is also counted */ + char esc[MAX_NAME_LENGTH * 2 + 1]; + POOL_MEM tmp(PM_MESSAGE); + POOL_MEM volumes(PM_MESSAGE); + dbid_list mediaIds; + MEDIA_DBR mr; + POOL_DBR pool_dbr; + STORAGE_DBR storage_dbr; + + memset(&pool_dbr, 0, sizeof(pool_dbr)); + memset(&storage_dbr, 0, sizeof(storage_dbr)); + + /* + * Look for volumes that can be recycled, + * are enabled and have used more than the first block. + */ + memset(&mr, 0, sizeof(mr)); + mr.Recycle = 1; + mr.Enabled = VOL_ENABLED; + mr.VolBytes = (512 * 126); /* search volumes with more than 64,512 bytes (DEFAULT_BLOCK_SIZE) */ + + if (!(ua->argc > 1)) { + ua->send_cmd_usage(_("missing parameter")); + return false; + } + + /* arg: volstatus=Purged */ + i = find_arg_with_value(ua, "volstatus"); + if (i < 0) { + ua->send_cmd_usage(_("required parameter 'volstatus' missing")); + return false; + } + if (!(bstrcasecmp(ua->argv[i], "Purged"))) { + ua->send_cmd_usage(_("Invalid parameter. 'volstatus' must be 'Purged'.")); + return false; + } + parsed_args++; + + /* + * Look for Purged volumes. + */ + bstrncpy(mr.VolStatus, "Purged", sizeof(mr.VolStatus)); + + if (!open_db(ua)) { + ua->error_msg("Failed to open db.\n"); + goto bail_out; + } + + ua->send->add_acl_filter_tuple(2, Pool_ACL); + ua->send->add_acl_filter_tuple(3, Storage_ACL); + + /* + * storage parameter is only required + * if ACL forbids access to all storages. + * Otherwise the user should not be asked for this parameter. + */ + i = find_arg_with_value(ua, "storage"); + if ((i >= 0) || ua->acl_has_restrictions(Storage_ACL)) { + if (!select_storage_dbr(ua, &storage_dbr, "storage")) { + goto bail_out; + } + mr.StorageId = storage_dbr.StorageId; + parsed_args++; + } + + /* + * pool parameter is only required + * if ACL forbids access to all pools. + * Otherwise the user should not be asked for this parameter. + */ + i = find_arg_with_value(ua, "pool"); + if ((i >= 0) || ua->acl_has_restrictions(Pool_ACL)) { + if (!select_pool_dbr(ua, &pool_dbr, "pool")) { + goto bail_out; + } + mr.PoolId = pool_dbr.PoolId; + if (i >= 0) { + parsed_args++; + } + } + + /* + * parse volume parameter. + * Currently only support one volume parameter + * (multiple volume parameter have been intended before, + * but this causes problems with parsing and ACL handling). + */ + i = find_arg_with_value(ua, "volume"); + if (i >= 0) { + if (is_name_valid(ua->argv[i])) { + db_escape_string(ua->jcr, ua->db, esc, ua->argv[i], strlen(ua->argv[i])); + if (!*volumes.c_str()) { + Mmsg(tmp, "'%s'", esc); + } else { + Mmsg(tmp, ",'%s'", esc); + } + volumes.strcat(tmp.c_str()); + parsed_args++; + } + } + + if (find_arg(ua, NT_("yes")) >= 0) { + /* parameter yes is evaluated at 'get_confirmation' */ + parsed_args++; + } + + if (parsed_args != ua->argc) { + ua->send_cmd_usage(_("Invalid parameter.")); + goto bail_out; + } + + /* create sql query string (in ua->db->cmd) */ + if (!prepare_media_sql_query(ua->jcr, ua->db, &mr, volumes)) { + ua->error_msg(_("Invalid parameter (failed to create sql query).\n")); + goto bail_out; + } + + /* execute query and display result */ + db_list_sql_query(ua->jcr, ua->db, ua->db->cmd, + ua->send, HORZ_LIST, "volumes", true); + + /* + * execute query to get media ids. + * Second execution is only required, + * because function is also used in other contextes. + */ + tmp.strcpy(ua->db->cmd); + if (!db_get_query_dbids(ua->jcr, ua->db, tmp, mediaIds)) { + Dmsg0(100, "No results from db_get_query_dbids\n"); + goto bail_out; + }; + + if (!mediaIds.size()) { + Dmsg0(100, "Results are empty\n"); + goto bail_out; + } + + if (!verify_media_ids_from_single_storage(ua->jcr, ua->db, mediaIds)) { + ua->error_msg("Selected volumes are from different storages. " + "This is not supported. Please choose only volumes from a single storage.\n"); + goto bail_out; + } + + mr.MediaId = mediaIds.get(0); + if (!db_get_media_record(ua->jcr, ua->db, &mr)) { + goto bail_out; + } + + if (ua->GetStoreResWithId(mr.StorageId)->Protocol != APT_NATIVE) { + ua->warning_msg(_("Storage uses a non-native protocol. Truncate is only supported for native protocols.\n")); + goto bail_out; + } + + if (!get_confirmation(ua, _("Truncate listed volumes (yes/no)? "))) { + goto bail_out; + } + + /* + * Loop over the candidate Volumes and actually truncate them + */ + for (int i = 0; i < mediaIds.size(); i++) { + memset(&mr, 0, sizeof(mr)); + mr.MediaId = mediaIds.get(i); + if (!db_get_media_record(ua->jcr, ua->db, &mr)) { + Dmsg1(0, "Can't find MediaId=%lld\n", (uint64_t) mr.MediaId); + } else { + do_truncate(ua, mr); + } + } + +bail_out: + close_db(ua); + if (ua->jcr->store_bsock) { + ua->jcr->store_bsock->signal(BNET_TERMINATE); + ua->jcr->store_bsock->close(); + delete ua->jcr->store_bsock; + ua->jcr->store_bsock = NULL; + } + + return result; +} + +static bool do_truncate(UAContext *ua, MEDIA_DBR &mr) +{ + bool retval = false; + STORAGE_DBR storage_dbr; + POOL_DBR pool_dbr; + + memset(&storage_dbr, 0, sizeof(storage_dbr)); + memset(&pool_dbr, 0, sizeof(pool_dbr)); + + storage_dbr.StorageId = mr.StorageId; + if (!db_get_storage_record(ua->jcr, ua->db, &storage_dbr)) { + ua->error_msg("failed to determine storage for id %lld\n", mr.StorageId); + goto bail_out; + } + + pool_dbr.PoolId = mr.PoolId; + if (!db_get_pool_record(ua->jcr, ua->db, &pool_dbr)) { + ua->error_msg("failed to determine pool for id %lld\n", mr.PoolId); + goto bail_out; + } + + /* + * Choose storage + */ + ua->jcr->res.wstore = ua->GetStoreResWithName(storage_dbr.Name); + if (!ua->jcr->res.wstore) { + ua->error_msg("failed to determine storage resource by name %s\n", storage_dbr.Name); + goto bail_out; + } + + if (send_label_request(ua, ua->jcr->res.wstore, + &mr, &mr, + &pool_dbr, + /* bool media_record_exists */ + true, + /* bool relabel */ + true, + /* drive_number_t drive */ + 0, + /* slot_number_t slot */ + 0)) { + ua->send_msg(_("The volume '%s' has been truncated.\n"), mr.VolumeName); + retval = true; + } + +bail_out: + ua->jcr->res.wstore = NULL; + return retval; +} + /* * Reload the conf file */ diff --git a/src/dird/ua_input.c b/src/dird/ua_input.c index 206fb989974..583793e0e41 100644 --- a/src/dird/ua_input.c +++ b/src/dird/ua_input.c @@ -166,6 +166,27 @@ bool get_yesno(UAContext *ua, const char *prompt) } } +/* + * Checks for "yes" cmd parameter. + * If not given, display prompt and gets user input "yes" or "no". + * + * Returns: true if cmd parameter "yes" is given + * or user enters "yes" + * false otherwise + */ +bool get_confirmation(UAContext *ua, const char *prompt) +{ + if (find_arg(ua, NT_("yes")) >= 0) { + return true; + } + + if (get_yesno(ua, prompt)) { + return (ua->pint32_val == 1); + } + + return false; +} + /* * Gets an Enabled value => 0, 1, 2, yes, no, archived * Returns: 0, 1, 2 if OK diff --git a/src/dird/ua_label.c b/src/dird/ua_label.c index 0bc7d46a7a1..58217dccfeb 100644 --- a/src/dird/ua_label.c +++ b/src/dird/ua_label.c @@ -216,15 +216,10 @@ static bool generate_new_encryption_key(UAContext *ua, MEDIA_DBR *mr) return true; } -static bool send_label_request(UAContext *ua, - STORERES *store, - MEDIA_DBR *mr, - MEDIA_DBR *omr, - POOL_DBR *pr, - bool media_record_exists, - bool relabel, - drive_number_t drive, - slot_number_t slot) +bool send_label_request(UAContext *ua, + STORERES *store, MEDIA_DBR *mr, MEDIA_DBR *omr, POOL_DBR *pr, + bool media_record_exists, bool relabel, + drive_number_t drive, slot_number_t slot) { bool retval; diff --git a/src/dird/ua_output.c b/src/dird/ua_output.c index 536c3df0b60..d57b95188b8 100644 --- a/src/dird/ua_output.c +++ b/src/dird/ua_output.c @@ -1852,3 +1852,28 @@ void UAContext::info_msg(const char *fmt, ...) va_end(arg_ptr); send->message(MSG_TYPE_INFO, message); } + + +void UAContext::send_cmd_usage(const char *fmt, ...) +{ + va_list arg_ptr; + POOL_MEM message; + POOL_MEM usage; + + /* send current buffer */ + send->send_buffer(); + + va_start(arg_ptr, fmt); + message.bvsprintf(fmt, arg_ptr); + va_end(arg_ptr); + + if (cmddef) { + if (cmddef->key && cmddef->usage) { + usage.bsprintf("\nUSAGE: %s %s\n", cmddef->key, cmddef->usage); + message.strcat(usage); + } + } + + send->message(NULL, message); +} + diff --git a/src/dird/ua_select.c b/src/dird/ua_select.c index 2f31bfb6268..1185c0627ab 100644 --- a/src/dird/ua_select.c +++ b/src/dird/ua_select.c @@ -760,11 +760,11 @@ bool select_storage_dbr(UAContext *ua, STORAGE_DBR *sr, const char *argk) sr->StorageId = 0; if (!db_get_storage_ids(ua->jcr, ua->db, &num_storages, &ids)) { ua->error_msg(_("Error obtaining storage ids. ERR=%s\n"), db_strerror(ua->db)); - return 0; + return false; } if (num_storages <= 0) { - ua->error_msg(_("No storages defined. Use the \"create\" command to create one.\n")); + ua->error_msg(_("No storages defined.\n")); return false; }