From: Michal Rakowski Date: Wed, 14 Oct 2020 14:25:41 +0000 (+0200) Subject: dird: Add support for Object Management X-Git-Tag: Release-11.3.2~998 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d3b15f5b579980b741accbacea1c06b8e3fd962c;p=thirdparty%2Fbacula.git dird: Add support for Object Management --- diff --git a/bacula/src/dird/catreq.c b/bacula/src/dird/catreq.c index 3663f6381..4a4e12063 100644 --- a/bacula/src/dird/catreq.c +++ b/bacula/src/dird/catreq.c @@ -640,13 +640,13 @@ static void update_attribute(JCR *jcr, char *msg, int32_t msglen) } else if (Stream == STREAM_PLUGIN_OBJECT) { OBJECT_DBR obj_r; + obj_r.parse_plugin_object_string(&p); - skip_nonspaces(&p); /* skip FileIndex */ - skip_spaces(&p); - skip_nonspaces(&p); /* skip FileType */ - skip_spaces(&p); - - parse_plugin_object_string(&p, &obj_r); + if (jcr->wjcr) { + obj_r.JobId = jcr->wjcr->JobId; + } else { + obj_r.JobId = jcr->JobId; + } if (!db_create_object_record(jcr, jcr->db, &obj_r)) { Jmsg1(jcr, M_FATAL, 0, _("Plugin object create error. %s"), db_strerror(jcr->db)); diff --git a/bacula/src/dird/protos.h b/bacula/src/dird/protos.h index 7fdbabce6..adf26a0af 100644 --- a/bacula/src/dird/protos.h +++ b/bacula/src/dird/protos.h @@ -328,6 +328,7 @@ void list_dir_status_header(UAContext *ua); /* ua_tree.c */ bool user_select_files_from_tree(TREE_CTX *tree); +bool user_select_files_from_tree_plugin_obj(TREE_CTX *tree); int insert_tree_handler(void *ctx, int num_fields, char **row); bool check_directory_acl(char **last_dir, alist *dir_acl, const char *path); diff --git a/bacula/src/dird/ua_cmds.c b/bacula/src/dird/ua_cmds.c index 0f225dfb7..bb74722c8 100644 --- a/bacula/src/dird/ua_cmds.c +++ b/bacula/src/dird/ua_cmds.c @@ -141,7 +141,8 @@ static struct cmdstruct commands[] = { /* C NT_("jobs [client=] [jobid=] [ujobid= [tag=] [job=] [joberrors] [jobstatus=] [level=] [jobtype=] [order=] [limit=]|\n" "\tjobtotals | pools | volume | media | files jobid= | copies jobid= |\n" "\tjoblog jobid= | pluginrestoreconf jobid= restoreobjectid= | snapshot |\n" - "\tfilemedia jobid= fileindex= | clients\n"), false}, + "\tfilemedia jobid= fileindex= | clients\n" + "\tobject [jobid= client= type= [order=] [limit=\n"), false}, { NT_("messages"), messagescmd, _("Display pending messages"), NT_(""), false}, { NT_("memory"), memory_cmd, _("Print current memory usage"), NT_(""), true}, @@ -162,7 +163,8 @@ static struct cmdstruct commands[] = { /* C { NT_("restore"), restore_cmd, _("Restore files"), NT_("where= client= storage= bootstrap= " "restorejob= restoreclient= noautoparent" - "\n\tcomment= jobid= jobuser= jobgroup= copies done select all"), false}, + "\n\tcomment= jobid= jobuser= jobgroup= copies done select all" + "\n\tobjectid="), false}, { NT_("relabel"), relabel_cmd, _("Relabel a tape"), NT_("storage= oldvolume=\n\tvolume= pool="), false}, diff --git a/bacula/src/dird/ua_output.c b/bacula/src/dird/ua_output.c index 24dd58172..28e1a923b 100644 --- a/bacula/src/dird/ua_output.c +++ b/bacula/src/dird/ua_output.c @@ -326,7 +326,7 @@ bail_out: * list nextvol job=xx - list the next vol to be used by job * list nextvolume job=xx - same as above. * list copies jobid=x,y,z - * list objects [type=objecttype] [job_id=n objectid=m] - list plugin objects + * list objects [type=objecttype job_id=id clientname=n] - list plugin objects * list pluginrestoreconf jobid=x,y,z [id=k] * list filemedia jobid=x fileindex=z * @@ -662,32 +662,48 @@ static int do_list_cmd(UAContext *ua, const char *cmd, e_list_type llist) db_free_restoreobject_record(ua->jcr, &rr); return 1; + /* List PLUGIN OBJECTS */ } else if (strcasecmp(ua->argk[i], NT_("object")) == 0 || strcasecmp(ua->argk[i], NT_("objects")) == 0) { - class OBJECT_DBR obj_r; - j = find_arg_with_value(ua, NT_("type")); - if (j >= 0) { - bstrncpy(obj_r.ObjectType, ua->argv[j], sizeof(obj_r.ObjectType)); - } + OBJECT_DBR obj_r; for (j=i+1; jargc; j++) { - //TODO job_id arg should be handled different probably because of the collision with the 'list jobid=nn' cmd' - if (strcasecmp(ua->argk[j], NT_("job_id")) == 0 && ua->argv[j]) { - if (is_a_number(ua->argv[j])) { + if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) { + if (is_a_number(ua->argv[j]) && acl_access_jobid_ok(ua, ua->argv[j])) { obj_r.JobId = str_to_uint64(ua->argv[j]); - } else { + } else { ua->error_msg(_("Invalid jobid argument\n")); return 1; } + } else if ((strcasecmp(ua->argk[j], NT_("objectid")) == 0) && - ua->argv[j]) - { + ua->argv[j]) { if (is_a_number(ua->argv[j])) { obj_r.ObjectId = str_to_uint64(ua->argv[j]); } else { ua->error_msg(_("Invalid objectid argument\n")); return 1; } + + } else if (strcasecmp(ua->argk[j], NT_("client")) == 0) { + if (!acl_access_ok(ua, Client_ACL, ua->argk[j])) { + ua->error_msg(_("Access to Client=%s not authorized.\n"), ua->argk[j]); + return 0; + } + bstrncpy(obj_r.ClientName, ua->argv[j], sizeof(obj_r.ClientName)); + + } else if (strcasecmp(ua->argk[j], NT_("name")) == 0) { + bstrncpy(obj_r.ObjectName, ua->argv[j], sizeof(obj_r.ObjectName)); + + } else if (strcasecmp(ua->argk[j], NT_("type")) == 0) { + bstrncpy(obj_r.ObjectType, ua->argv[j], sizeof(obj_r.ObjectType)); + + } else if (strcasecmp(ua->argk[j], NT_("limit")) == 0 && ua->argv[j]) { + obj_r.limit = atoi(ua->argv[j]); + + } else if (strcasecmp(ua->argk[j], NT_("order")) == 0 && ua->argv[j]) { + /* Other order are tested before */ + obj_r.order = bstrcasecmp(ua->argv[j], "DESC") == 0; } } @@ -696,12 +712,8 @@ static int do_list_cmd(UAContext *ua, const char *cmd, e_list_type llist) return 1; } - int k = find_arg_with_value(ua, NT_("type")); - if (k >= 0) { - bstrncpy(obj_r.ObjectType, ua->argv[k], sizeof(obj_r.ObjectType)); - } - db_list_plugin_objects(ua->jcr, ua->db, &obj_r, prtit, ua, llist); + return 1; /* List MEDIA or VOLUMES */ } else if (strcasecmp(ua->argk[i], NT_("media")) == 0 || @@ -841,7 +853,10 @@ static int do_list_cmd(UAContext *ua, const char *cmd, e_list_type llist) || strcasecmp(ua->argk[i], NT_("long")) == 0 || strcasecmp(ua->argk[i], NT_("start")) == 0 || strcasecmp(ua->argk[i], NT_("end")) == 0 - || strcasecmp(ua->argk[i], NT_("name")) == 0 + || strcasecmp(ua->argk[i], NT_("objecttype")) == 0 + || strcasecmp(ua->argk[i], NT_("objectid")) == 0 + || strcasecmp(ua->argk[i], NT_("clientname")) == 0 + || strcasecmp(ua->argk[i], NT_("jobid")) == 0 ) { /* Ignore it */ } else if (strcasecmp(ua->argk[i], NT_("snapshot")) == 0 || diff --git a/bacula/src/dird/ua_restore.c b/bacula/src/dird/ua_restore.c index 8b38921bb..f271304d9 100644 --- a/bacula/src/dird/ua_restore.c +++ b/bacula/src/dird/ua_restore.c @@ -269,6 +269,7 @@ int restore_cmd(UAContext *ua, const char *cmd) break; } + if (rx.bsr_list->size() > 0) { char ed1[50]; if (!complete_bsr(ua, rx.bsr_list)) { /* find Vol, SessId, SessTime from JobIds */ @@ -547,6 +548,76 @@ static int get_restore_client_name(UAContext *ua, RESTORE_CTX &rx, char * Restor } +static int select_files_from_plugin_obj(UAContext *ua, OBJECT_DBR *obj_r, RESTORE_CTX *rx) +{ + if (!db_get_plugin_object_record(ua->jcr, ua->db, obj_r)) { + ua->error_msg(_("Failed to get plugin object for specified object parameters\n")); + return 0; + } + + JOB_DBR jr; + memset(&jr, 0, sizeof(JOB_DBR)); + jr.JobId = obj_r->JobId; + if (!db_get_job_record(ua->jcr, ua->db, &jr)) { + ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"), + ua->cmd, db_strerror(ua->db)); + return 0; + } + + if (!acl_access_ok(ua, Job_ACL, jr.Name)) { + ua->error_msg(_("Access to JobId=%d (Job \"%s\") not authorized.\n"), + jr.JobId, jr.Name); + return 0; + } + + CLIENT_DBR cr; + cr.ClientId = jr.ClientId; + if (!db_get_client_record(ua->jcr, ua->db, &cr)) { + ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"), + ua->cmd, db_strerror(ua->db)); + return 0; + } + + if (!acl_access_ok(ua, Client_ACL, cr.Name)) { + ua->error_msg(_("Access to ClientId=%d not authorized.\n"), + jr.ClientId); + return 0; + } + + bool dir = obj_r->Filename[0] != 0 ? false : true; + if (dir) { + pm_strcpy(ua->cmd, obj_r->Path); + } else { + Mmsg(ua->cmd, "%s%s", obj_r->Path, obj_r->Filename); + } + + db_list_ctx jobids; + if (!db_get_accurate_jobids(ua->jcr, ua->db, &jr, &jobids)) { + return 0; + } + pm_strcpy(rx->JobIds, jobids.list); + + if (!get_client_name(ua, rx)) { + return 0; + } + + insert_one_file_or_dir(ua, rx, jr.cStartTime, dir); + + return 1; +} + +static int object_type_hander(void *ctx, int num_fields, char **row) +{ + if (num_fields != 2) { + return 1; + } + alist *val = (alist *)ctx; + + val[0].append(bstrdup(row[0])); + val[1].append(bstrdup(row[1])); + + return 0; +} /* * The first step in the restore process is for the user to @@ -581,6 +652,7 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx) _("Find the JobIds for a backup for a client before a specified time"), _("Enter a list of directories to restore for found JobIds"), _("Select full restore to a specified Job date"), + _("Select object to restore"), _("Cancel"), NULL }; @@ -619,6 +691,7 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx) "jobuser", /* 28 */ "jobgroup", /* 29 */ + "objectid", /* 30 */ NULL }; @@ -713,12 +786,22 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx) /* * All keywords 7 or greater are ignored or handled by a select prompt */ + case 30: + { + OBJECT_DBR obj_r; + obj_r.ObjectId = str_to_int64(ua->argv[i]); + if (!select_files_from_plugin_obj(ua, &obj_r, rx)) { + return 0; + } + return 2; + } default: break; } } if (!done) { + //TODO Cleanup msg ua->send_msg(_("\nFirst you select one or more JobIds that contain files\n" "to be restored. You will be presented several methods\n" "of specifying the JobIds. Then you will be allowed to\n" @@ -936,7 +1019,100 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx) pm_strcpy(rx->JobIds, jobids.list); Dmsg1(30, "Item 12: jobids = %s\n", rx->JobIds); break; - case 12: /* Cancel or quit */ + + case 12: /* Select Object to restore */ + { + POOL_MEM query, esc(PM_MESSAGE); + gui_save = ua->jcr->gui; + ua->jcr->gui = true; + + // Array in form of ObjectCategories (at [0]) and corresponding ObjectTypes (at [1]) + alist types[2]; + Mmsg(query, "SELECT DISTINCT ObjectCategory, ObjectType FROM Object ORDER BY ObjectCategory, ObjectType ASC"); + if (!db_sql_query(ua->db, query.c_str(), object_type_hander, types)) { + ua->error_msg(_("SQL Query failed: %s\n"), db_strerror(ua->db)); + return 0; + } + + char *t; + POOL_MEM tmp(PM_MESSAGE); + start_prompt(ua, _("List of the Object Types:\n")); + for (int i=0; i < types[0].size(); i++) { + // Build prompt "ObjectType ObjectCategory" - e.g. "PostgreSQL Database" + Mmsg(tmp, "%s %s", types[1].get(i), types[0].get(i)); + add_prompt(ua, tmp.c_str()); + } + + int idx = do_prompt(ua, "", _("Select item:"), NULL, 0); + if (idx < 0) { + return 0; + } + + alist object_list; + alist *alist_ptr = &object_list; + + POOL_MEM esc_cat, esc_type; + char *cat = (char*)types[0].get(idx); + char *type = (char*)types[1].get(idx); + esc_cat.check_size(strlen(cat)*2+1); + esc_type.check_size(strlen(type)*2+1); + + /* TODO: Check if we need db_lock() */ + db_escape_string(ua->jcr, ua->db, esc_cat.c_str(), cat, strlen(cat)); + db_escape_string(ua->jcr, ua->db, esc_type.c_str(), type, strlen(type)); + + Mmsg(query, "SELECT DISTINCT ObjectName FROM Object WHERE ObjectCategory='%s' AND ObjectType='%s' ORDER BY ObjectName ASC", + esc_cat.c_str(), esc_type.c_str()); + + if (!db_sql_query(ua->db, query.c_str(), db_string_list_handler, &alist_ptr)) { + ua->error_msg(_("SQL Query failed: %s\n"), db_strerror(ua->db)); + return 0; + } + + Mmsg(tmp, "List of the %s %s Objects:\n", types[1].get(idx), types[0].get(idx)); + start_prompt(ua, tmp.c_str()); + foreach_alist(t, &object_list) { + add_prompt(ua, t); + } + + idx = do_prompt(ua, "", _("Select Object:"), NULL, 0); + if (idx < 0) { + return 0; + } + + ua->info_msg(_("Objects available:\n")); + esc.check_size(strlen((char*)object_list.get(idx))*2+1); + db_escape_string(ua->jcr, ua->db, esc.c_str(), + (char*)object_list.get(idx), strlen((char*)object_list.get(idx))); + Mmsg(query, "SELECT Object.ObjectId AS ObjectId, Object.ObjectName AS ObjectName, Client.Name as Client, " + "Object.ObjectSource AS ObjectSource, " + "Job.StartTime AS StartTime, Object.ObjectSize AS ObjectSize " + "FROM Object JOIN Job USING (JobId) " + "JOIN Client ON (Job.ClientId = Client.ClientId) " + "WHERE Object.ObjectName='%s' AND Object.ObjectCategory='%s' AND Object.ObjectType='%s'", + esc.c_str(), esc_cat.c_str(), esc_type.c_str()); + + if (!db_list_sql_query(ua->jcr, ua->db, query.c_str(), prtit, ua, 0, HORZ_LIST)) { + ua->error_msg(_("SQL Query failed: %s\n"), db_strerror(ua->db)); + return 0; + } + + //TODO Validation if id entered matches one from list above would be nice... + if (!get_pint(ua, "Enter ID of Object to be restored: ")) { + ua->info_msg(_("Selection aborted, nothing done.\n")); + break; + } + + OBJECT_DBR obj_r; + obj_r.ObjectId = ua->pint32_val; + if (!select_files_from_plugin_obj(ua, &obj_r, rx)) { + return 0; + } + + ua->jcr->gui = gui_save; + return 2; + } + case 13: /* Cancel or quit */ return 0; } } diff --git a/bacula/src/dird/ua_tree.c b/bacula/src/dird/ua_tree.c index cb23f9a31..088268c5d 100644 --- a/bacula/src/dird/ua_tree.c +++ b/bacula/src/dird/ua_tree.c @@ -60,6 +60,8 @@ static int dot_lscmd(UAContext *ua, TREE_CTX *tree); static int dot_helpcmd(UAContext *ua, TREE_CTX *tree); static int dot_lsmarkcmd(UAContext *ua, TREE_CTX *tree); +static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extract); + struct cmdstruct { const char *key; int (*func)(UAContext *ua, TREE_CTX *tree); const char *help; }; static struct cmdstruct commands[] = { { NT_("add"), markcmd, _("add dir/file to be restored recursively, wildcards allowed")},