2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2017 Kern Sibbald
6 The original author of Bacula is Kern Sibbald, with contributions
7 from many others, a complete list can be found in the file AUTHORS.
9 You may use this file and others of this release according to the
10 license defined in the LICENSE file, which includes the Affero General
11 Public License, v3.0 ("AGPLv3") and some additional permissions and
12 terms pursuant to its AGPLv3 Section 7.
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
17 Bacula(R) is a registered trademark of Kern Sibbald.
20 * Bacula Director -- User Agent Database restore Command
21 * Creates a bootstrap file for restoring files and
22 * starts the restore job.
24 * Tree handling routines split into ua_tree.c July MMIII.
25 * BSR (bootstrap record) handling routines split into
28 * Kern Sibbald, July MMII
35 /* Imported functions */
36 extern void print_bsr(UAContext *ua, RBSR *bsr);
39 /* Forward referenced functions */
40 static int last_full_handler(void *ctx, int num_fields, char **row);
41 static int jobid_handler(void *ctx, int num_fields, char **row);
42 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx);
43 static int fileset_handler(void *ctx, int num_fields, char **row);
44 static void free_name_list(NAME_LIST *name_list);
45 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date);
46 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx);
47 static void split_path_and_filename(UAContext *ua, RESTORE_CTX *rx, char *fname);
48 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row);
49 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
51 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
53 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir);
54 static int get_client_name(UAContext *ua, RESTORE_CTX *rx);
55 static int get_restore_client_name(UAContext *ua, RESTORE_CTX &rx);
56 static bool get_date(UAContext *ua, char *date, int date_len);
57 static int restore_count_handler(void *ctx, int num_fields, char **row);
58 static void get_and_display_basejobs(UAContext *ua, RESTORE_CTX *rx);
60 void new_rx(RESTORE_CTX *rx)
63 memset(rx, 0, sizeof(*rx));
64 rx->path = get_pool_memory(PM_FNAME);
67 rx->fname = get_pool_memory(PM_FNAME);
70 rx->JobIds = get_pool_memory(PM_FNAME);
73 rx->component_fname = get_pool_memory(PM_FNAME);
74 rx->component_fname[0] = 0;
76 rx->BaseJobIds = get_pool_memory(PM_FNAME);
77 rx->BaseJobIds[0] = 0;
79 rx->query = get_pool_memory(PM_FNAME);
82 rx->bsr_list = New(rblist(bsr, &bsr->link));
83 rx->hardlinks_in_mem = true;
91 int restore_cmd(UAContext *ua, const char *cmd)
93 RESTORE_CTX rx; /* restore context */
98 char *escaped_bsr_name = NULL;
99 char *escaped_where_name = NULL;
100 char *strip_prefix, *add_prefix, *add_suffix, *regexp;
101 strip_prefix = add_prefix = add_suffix = regexp = NULL;
103 new_rx(&rx); /* Initialize RESTORE_CTX */
105 if (!open_new_client_db(ua)) {
109 for (i = 0; i < ua->argc ; i++) {
110 if (strcasecmp(ua->argk[i], "fdcalled") == 0) {
113 } else if (strcasecmp(ua->argk[i], "noautoparent") == 0) {
114 rx.no_auto_parent = true;
117 continue; /* skip if no value given */
119 if (strcasecmp(ua->argk[i], "comment") == 0) {
120 rx.comment = ua->argv[i];
121 if (!is_comment_legal(ua, rx.comment)) {
125 } else if (strcasecmp(ua->argk[i], "where") == 0) {
126 rx.where = ua->argv[i];
128 } else if (strcasecmp(ua->argk[i], "when") == 0) {
129 rx.when = ua->argv[i];
131 } else if (strcasecmp(ua->argk[i], "replace") == 0) {
132 rx.replace = ua->argv[i];
134 } else if (strcasecmp(ua->argk[i], "strip_prefix") == 0) {
135 strip_prefix = ua->argv[i];
137 } else if (strcasecmp(ua->argk[i], "add_prefix") == 0) {
138 add_prefix = ua->argv[i];
140 } else if (strcasecmp(ua->argk[i], "add_suffix") == 0) {
141 add_suffix = ua->argv[i];
143 } else if (strcasecmp(ua->argk[i], "regexwhere") == 0) {
144 rx.RegexWhere = ua->argv[i];
146 } else if (strcasecmp(ua->argk[i], "optimizespeed") == 0) {
147 if (strcasecmp(ua->argv[i], "0") || strcasecmp(ua->argv[i], "no") ||
148 strcasecmp(ua->argv[i], "false")) {
149 rx.hardlinks_in_mem = false;
154 if (strip_prefix || add_suffix || add_prefix) {
155 int len = bregexp_get_build_where_size(strip_prefix, add_prefix, add_suffix);
156 regexp = (char *)bmalloc(len * sizeof(char));
158 bregexp_build_where(regexp, len, strip_prefix, add_prefix, add_suffix);
159 rx.RegexWhere = regexp;
162 /* TODO: add acl for regexwhere ? */
165 if (!acl_access_ok(ua, Where_ACL, rx.RegexWhere)) {
166 ua->error_msg(_("\"RegexWhere\" specification not authorized.\n"));
172 if (!acl_access_ok(ua, Where_ACL, rx.where)) {
173 ua->error_msg(_("\"where\" specification not authorized.\n"));
178 /* Ensure there is at least one Restore Job */
180 foreach_res(job, R_JOB) {
181 if (job->JobType == JT_RESTORE) {
182 if (!rx.restore_job) {
183 rx.restore_job = job;
189 if (!rx.restore_jobs) {
191 "No Restore Job Resource found in bacula-dir.conf.\n"
192 "You must create at least one before running this command.\n"));
197 * Request user to select JobIds or files by various different methods
198 * last 20 jobs, where File saved, most recent backup, ...
199 * In the end, a list of files are pumped into
202 switch (user_select_jobids_or_files(ua, &rx)) {
205 case 1: /* selected by jobid */
206 get_and_display_basejobs(ua, &rx);
207 if (!build_directory_tree(ua, &rx)) {
208 ua->send_msg(_("Restore not done.\n"));
212 case 2: /* selected by filename, no tree needed */
216 if (rx.bsr_list->size() > 0) {
218 if (!complete_bsr(ua, rx.bsr_list)) { /* find Vol, SessId, SessTime from JobIds */
219 ua->error_msg(_("Unable to construct a valid BSR. Cannot continue.\n"));
222 if (!(rx.selected_files = write_bsr_file(ua, rx))) {
223 ua->warning_msg(_("No files selected to be restored.\n"));
227 ua->send_msg(_("Bootstrap records written to %s\n"), ua->jcr->RestoreBootstrap);
228 display_bsr_info(ua, rx); /* display vols needed, etc */
230 if (rx.selected_files==1) {
231 ua->info_msg(_("\n1 file selected to be restored.\n\n"));
233 ua->info_msg(_("\n%s files selected to be restored.\n\n"),
234 edit_uint64_with_commas(rx.selected_files, ed1));
237 ua->warning_msg(_("No files selected to be restored.\n"));
241 if (rx.restore_jobs == 1) {
242 job = rx.restore_job;
244 job = get_restore_job(ua);
250 get_client_name(ua, &rx);
251 if (!rx.ClientName[0]) {
252 ua->error_msg(_("No Client resource found!\n"));
255 get_restore_client_name(ua, rx);
257 escaped_bsr_name = escape_filename(jcr->RestoreBootstrap);
260 "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
261 " bootstrap=\"%s\" files=%u catalog=\"%s\"",
262 job->name(), rx.ClientName, rx.RestoreClientName,
263 rx.store?rx.store->name():"",
264 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
265 rx.selected_files, ua->catalog->name());
267 /* Build run command */
269 if (rx.RestoreMediaType[0]) {
270 Mmsg(buf, " mediatype=\"%s\"", rx.RestoreMediaType);
271 pm_strcat(ua->cmd, buf);
275 escaped_where_name = escape_filename(rx.RegexWhere);
276 Mmsg(buf, " regexwhere=\"%s\"",
277 escaped_where_name ? escaped_where_name : rx.RegexWhere);
279 } else if (rx.where) {
280 escaped_where_name = escape_filename(rx.where);
281 Mmsg(buf," where=\"%s\"",
282 escaped_where_name ? escaped_where_name : rx.where);
284 pm_strcat(ua->cmd, buf);
287 Mmsg(buf, " replace=%s", rx.replace);
288 pm_strcat(ua->cmd, buf);
292 pm_strcat(ua->cmd, " fdcalled=yes");
296 Mmsg(buf, " when=\"%s\"", rx.when);
297 pm_strcat(ua->cmd, buf);
301 Mmsg(buf, " comment=\"%s\"", rx.comment);
302 pm_strcat(ua->cmd, buf);
305 if (escaped_bsr_name != NULL) {
306 bfree(escaped_bsr_name);
309 if (escaped_where_name != NULL) {
310 bfree(escaped_where_name);
317 if (find_arg(ua, NT_("yes")) > 0) {
318 pm_strcat(ua->cmd, " yes"); /* pass it on to the run command */
320 Dmsg1(200, "Submitting: %s\n", ua->cmd);
322 * Transfer jobids, component stuff to jcr to
323 * pass to run_cmd(). Note, these are fields and
324 * other things that are not passed on the command
327 /* ***FIXME*** pass jobids on command line */
329 free_pool_memory(jcr->JobIds);
331 jcr->JobIds = rx.JobIds;
333 jcr->component_fname = rx.component_fname;
334 rx.component_fname = NULL;
335 jcr->component_fd = rx.component_fd;
336 rx.component_fd = NULL;
338 run_cmd(ua, ua->cmd);
340 garbage_collect_memory(); /* release unused memory */
344 if (escaped_bsr_name != NULL) {
345 bfree(escaped_bsr_name);
348 if (escaped_where_name != NULL) {
349 bfree(escaped_where_name);
356 /* Free the plugin config if needed, we don't want to re-use
357 * this part of the next try
359 free_plugin_config_items(jcr->plugin_config);
360 jcr->plugin_config = NULL;
363 garbage_collect_memory(); /* release unused memory */
369 * Fill the rx->BaseJobIds and display the list
371 static void get_and_display_basejobs(UAContext *ua, RESTORE_CTX *rx)
375 if (!db_get_used_base_jobids(ua->jcr, ua->db, rx->JobIds, &jobids)) {
376 ua->warning_msg("%s", db_strerror(ua->db));
381 Mmsg(q, uar_print_jobs, jobids.list);
382 ua->send_msg(_("The restore will use the following job(s) as Base\n"));
383 db_list_sql_query(ua->jcr, ua->db, q.c_str(), prtit, ua, 1, HORZ_LIST);
385 pm_strcpy(rx->BaseJobIds, jobids.list);
388 void free_rx(RESTORE_CTX *rx)
390 free_bsr(rx->bsr_list);
392 free_and_null_pool_memory(rx->JobIds);
393 free_and_null_pool_memory(rx->BaseJobIds);
394 free_and_null_pool_memory(rx->fname);
395 free_and_null_pool_memory(rx->path);
396 free_and_null_pool_memory(rx->query);
399 rx->fileregex = NULL;
401 if (rx->component_fd) {
402 fclose(rx->component_fd);
403 rx->component_fd = NULL;
405 if (rx->component_fname) {
406 unlink(rx->component_fname);
408 free_and_null_pool_memory(rx->component_fname);
409 free_name_list(&rx->name_list);
412 static bool has_value(UAContext *ua, int i)
415 ua->error_msg(_("Missing value for keyword: %s\n"), ua->argk[i]);
422 * This gets the client name from which the backup was made
424 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
426 /* If no client name specified yet, get it now */
427 if (!rx->ClientName[0]) {
429 /* try command line argument */
430 int i = find_arg_with_value(ua, NT_("client"));
432 i = find_arg_with_value(ua, NT_("backupclient"));
435 if (!is_name_valid(ua->argv[i], &ua->errmsg)) {
436 ua->error_msg("%s argument: %s", ua->argk[i], ua->errmsg);
439 bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
442 memset(&cr, 0, sizeof(cr));
443 /* We want the name of the client where the backup was made */
444 if (!get_client_dbr(ua, &cr, JT_BACKUP_RESTORE)) {
447 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
453 * This is where we pick up a client name to restore to.
455 static int get_restore_client_name(UAContext *ua, RESTORE_CTX &rx)
457 /* Start with same name as backup client */
458 bstrncpy(rx.RestoreClientName, rx.ClientName, sizeof(rx.RestoreClientName));
460 /* try command line argument */
461 int i = find_arg_with_value(ua, NT_("restoreclient"));
463 if (!is_name_valid(ua->argv[i], &ua->errmsg)) {
464 ua->error_msg("%s argument: %s", ua->argk[i], ua->errmsg);
467 bstrncpy(rx.RestoreClientName, ua->argv[i], sizeof(rx.RestoreClientName));
476 * The first step in the restore process is for the user to
477 * select a list of JobIds from which he will subsequently
478 * select which files are to be restored.
480 * Returns: 2 if filename list made
481 * 1 if jobid list made
484 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
487 char date[MAX_TIME_LENGTH];
488 bool have_date = false;
489 /* Include current second if using current time */
490 utime_t now = time(NULL) + 1;
492 JOB_DBR jr = { (JobId_t)-1 };
495 const char *list[] = {
496 _("List last 20 Jobs run"),
497 _("List Jobs where a given File is saved"),
498 _("Enter list of comma separated JobIds to select"),
499 _("Enter SQL list command"),
500 _("Select the most recent backup for a client"),
501 _("Select backup for a client before a specified time"),
502 _("Enter a list of files to restore"),
503 _("Enter a list of files to restore before a specified time"),
504 _("Find the JobIds of the most recent backup for a client"),
505 _("Find the JobIds for a backup for a client before a specified time"),
506 _("Enter a list of directories to restore for found JobIds"),
507 _("Select full restore to a specified Job date"),
512 /* These keywords are handled in a for loop */
522 /* The keyword below are handled by individual arg lookups */
528 "bootstrap", /* 13 */
530 "strip_prefix", /* 15 */
531 "add_prefix", /* 16 */
532 "add_suffix", /* 17 */
533 "regexwhere", /* 18 */
534 "restoreclient", /* 19 */
537 "restorejob", /* 22 */
539 "xxxxxxxxx", /* 24 */
542 "noautoparent", /* 27 */
548 for (i=1; i<ua->argc; i++) { /* loop through arguments */
549 bool found_kw = false;
550 for (j=0; kw[j]; j++) { /* loop through keywords */
551 if (strcasecmp(kw[j], ua->argk[i]) == 0) {
558 ua->error_msg(_("Unknown keyword: %s\n"), ua->argk[i]);
561 /* Found keyword in kw[] list, process it */
564 if (!has_value(ua, i)) {
567 if (*rx->JobIds != 0) {
568 pm_strcat(rx->JobIds, ",");
570 pm_strcat(rx->JobIds, ua->argv[i]);
573 case 1: /* current */
575 * Note, we add one second here just to include any job
576 * that may have finished within the current second,
577 * which happens a lot in scripting small jobs.
579 bstrutime(date, sizeof(date), now);
583 if (have_date || !has_value(ua, i)) {
586 if (str_to_utime(ua->argv[i]) == 0) {
587 ua->error_msg(_("Improper date format: %s\n"), ua->argv[i]);
590 bstrncpy(date, ua->argv[i], sizeof(date));
595 if (!has_value(ua, i)) {
599 bstrutime(date, sizeof(date), now);
601 if (!get_client_name(ua, rx)) {
604 pm_strcpy(ua->cmd, ua->argv[i]);
605 insert_one_file_or_dir(ua, rx, date, j==4);
609 bstrutime(date, sizeof(date), now);
611 if (!select_backups_before_date(ua, rx, date)) {
616 case 6: /* pool specified */
617 if (!has_value(ua, i)) {
620 rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
622 ua->error_msg(_("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
625 if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
627 ua->error_msg(_("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
631 case 7: /* all specified */
635 * All keywords 7 or greater are ignored or handled by a select prompt
643 ua->send_msg(_("\nFirst you select one or more JobIds that contain files\n"
644 "to be restored. You will be presented several methods\n"
645 "of specifying the JobIds. Then you will be allowed to\n"
646 "select which files from those JobIds are to be restored.\n\n"));
649 /* If choice not already made above, prompt */
656 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
657 for (int i=0; list[i]; i++) {
658 add_prompt(ua, list[i]);
661 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
662 case -1: /* error or cancel */
664 case 0: /* list last 20 Jobs run */
665 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
666 ua->error_msg(_("SQL query not authorized.\n"));
669 gui_save = ua->jcr->gui;
671 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
672 ua->jcr->gui = gui_save;
675 case 1: /* list where a file is saved */
676 if (!get_client_name(ua, rx)) {
679 if (!get_cmd(ua, _("Enter Filename (no path):"))) {
682 len = strlen(ua->cmd);
683 fname = (char *)malloc(len * 2 + 1);
684 db_escape_string(ua->jcr, ua->db, fname, ua->cmd, len);
685 Mmsg(rx->query, uar_file[db_get_type_index(ua->db)], rx->ClientName, fname);
687 gui_save = ua->jcr->gui;
689 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
690 ua->jcr->gui = gui_save;
693 case 2: /* enter a list of JobIds */
694 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
697 pm_strcpy(rx->JobIds, ua->cmd);
699 case 3: /* Enter an SQL list command */
700 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
701 ua->error_msg(_("SQL query not authorized.\n"));
704 if (!get_cmd(ua, _("Enter SQL list command: "))) {
707 gui_save = ua->jcr->gui;
709 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
710 ua->jcr->gui = gui_save;
713 case 4: /* Select the most recent backups */
715 bstrutime(date, sizeof(date), now);
717 if (!select_backups_before_date(ua, rx, date)) {
721 case 5: /* select backup at specified time */
723 if (!get_date(ua, date, sizeof(date))) {
727 if (!select_backups_before_date(ua, rx, date)) {
731 case 6: /* Enter files */
733 bstrutime(date, sizeof(date), now);
735 if (!get_client_name(ua, rx)) {
738 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
739 "containing a list of file names with paths, and terminate\n"
740 "them with a blank line.\n"));
742 if (!get_cmd(ua, _("Enter full filename: "))) {
745 len = strlen(ua->cmd);
749 insert_one_file_or_dir(ua, rx, date, false);
752 case 7: /* enter files backed up before specified time */
754 if (!get_date(ua, date, sizeof(date))) {
758 if (!get_client_name(ua, rx)) {
761 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
762 "containing a list of file names with paths, and terminate\n"
763 "them with a blank line.\n"));
765 if (!get_cmd(ua, _("Enter full filename: "))) {
768 len = strlen(ua->cmd);
772 insert_one_file_or_dir(ua, rx, date, false);
776 case 8: /* Find JobIds for current backup */
778 bstrutime(date, sizeof(date), now);
780 if (!select_backups_before_date(ua, rx, date)) {
786 case 9: /* Find JobIds for give date */
788 if (!get_date(ua, date, sizeof(date))) {
792 if (!select_backups_before_date(ua, rx, date)) {
798 case 10: /* Enter directories */
799 if (*rx->JobIds != 0) {
800 ua->send_msg(_("You have already selected the following JobIds: %s\n"),
802 } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
803 if (*rx->JobIds != 0 && *ua->cmd) {
804 pm_strcat(rx->JobIds, ",");
806 pm_strcat(rx->JobIds, ua->cmd);
808 if (*rx->JobIds == 0 || *rx->JobIds == '.') {
810 return 0; /* nothing entered, return */
813 bstrutime(date, sizeof(date), now);
815 if (!get_client_name(ua, rx)) {
818 ua->send_msg(_("Enter full directory names or start the name\n"
819 "with a < to indicate it is a filename containing a list\n"
820 "of directories and terminate them with a blank line.\n"));
822 if (!get_cmd(ua, _("Enter directory name: "))) {
825 len = strlen(ua->cmd);
829 /* Add trailing slash to end of directory names */
830 if (ua->cmd[0] != '<' && !IsPathSeparator(ua->cmd[len-1])) {
831 strcat(ua->cmd, "/");
833 insert_one_file_or_dir(ua, rx, date, true);
837 case 11: /* Choose a jobid and select jobs */
838 if (!get_cmd(ua, _("Enter JobId to get the state to restore: ")) ||
839 !is_an_integer(ua->cmd))
844 memset(&jr, 0, sizeof(JOB_DBR));
845 jr.JobId = str_to_int64(ua->cmd);
846 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
847 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
848 ua->cmd, db_strerror(ua->db));
851 ua->send_msg(_("Selecting jobs to build the Full state at %s\n"),
853 jr.JobLevel = L_INCREMENTAL; /* Take Full+Diff+Incr */
854 if (!db_get_accurate_jobids(ua->jcr, ua->db, &jr, &jobids)) {
857 pm_strcpy(rx->JobIds, jobids.list);
858 Dmsg1(30, "Item 12: jobids = %s\n", rx->JobIds);
860 case 12: /* Cancel or quit */
865 memset(&jr, 0, sizeof(JOB_DBR));
866 POOLMEM *JobIds = get_pool_memory(PM_FNAME);
870 * Find total number of files to be restored, and filter the JobId
871 * list to contain only ones permitted by the ACL conditions.
873 for (p=rx->JobIds; ; ) {
875 int stat = get_next_jobid_from_list(&p, &JobId);
877 ua->error_msg(_("Invalid JobId in list.\n"));
878 free_pool_memory(JobIds);
884 if (jr.JobId == JobId) {
885 continue; /* duplicate of last JobId */
887 memset(&jr, 0, sizeof(JOB_DBR));
889 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
890 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
891 edit_int64(JobId, ed1), db_strerror(ua->db));
892 free_pool_memory(JobIds);
895 if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
896 ua->error_msg(_("Access to JobId=%s (Job \"%s\") not authorized. Not selected.\n"),
897 edit_int64(JobId, ed1), jr.Name);
901 pm_strcat(JobIds, ",");
903 pm_strcat(JobIds, edit_int64(JobId, ed1));
904 rx->TotalFiles += jr.JobFiles;
906 pm_strcpy(rx->JobIds, JobIds); /* Set ACL filtered list */
907 free_pool_memory(JobIds);
908 if (*rx->JobIds == 0) {
909 ua->warning_msg(_("No Jobs selected.\n"));
913 if (strchr(rx->JobIds,',')) {
914 ua->info_msg(_("You have selected the following JobIds: %s\n"), rx->JobIds);
916 ua->info_msg(_("You have selected the following JobId: %s\n"), rx->JobIds);
924 static bool get_date(UAContext *ua, char *date, int date_len)
926 ua->send_msg(_("The restored files will the most current backup\n"
927 "BEFORE the date you specify below.\n\n"));
929 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
932 if (str_to_utime(ua->cmd) != 0) {
935 ua->error_msg(_("Improper date format.\n"));
937 bstrncpy(date, ua->cmd, date_len);
942 * Insert a single file, or read a list of files from a file
944 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
954 if ((ffd = bfopen(p, "rb")) == NULL) {
956 ua->error_msg(_("Cannot open file %s: ERR=%s\n"),
960 while (fgets(file, sizeof(file), ffd)) {
963 if (!insert_dir_into_findex_list(ua, rx, file, date)) {
964 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
967 if (!insert_file_into_findex_list(ua, rx, file, date)) {
968 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
976 insert_table_into_findex_list(ua, rx, p);
980 insert_dir_into_findex_list(ua, rx, ua->cmd, date);
982 insert_file_into_findex_list(ua, rx, ua->cmd, date);
989 * For a given file (path+filename), split into path and file, then
990 * lookup the most recent backup in the catalog to get the JobId
991 * and FileIndex, then insert them into the findex list.
993 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
996 strip_trailing_newline(file);
997 split_path_and_filename(ua, rx, file);
998 if (*rx->JobIds == 0) {
999 Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname,
1002 Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
1003 rx->path, rx->fname, rx->ClientName);
1006 /* Find and insert jobid and File Index */
1007 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
1008 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
1009 rx->query, db_strerror(ua->db));
1012 ua->error_msg(_("No database record found for: %s\n"), file);
1013 // ua->error_msg("Query=%s\n", rx->query);
1020 * For a given path lookup the most recent backup in the catalog
1021 * to get the JobId and FileIndexes of all files in that directory.
1023 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
1026 strip_trailing_junk(dir);
1027 if (*rx->JobIds == 0) {
1028 ua->error_msg(_("No JobId specified cannot continue.\n"));
1031 Mmsg(rx->query, uar_jobid_fileindex_from_dir[db_get_type_index(ua->db)], rx->JobIds, dir, rx->ClientName);
1034 /* Find and insert jobid and File Index */
1035 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
1036 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
1037 rx->query, db_strerror(ua->db));
1040 ua->error_msg(_("No database record found for: %s\n"), dir);
1047 * Get the JobId and FileIndexes of all files in the specified table
1049 bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
1051 strip_trailing_junk(table);
1052 Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
1055 /* Find and insert jobid and File Index */
1056 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
1057 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
1058 rx->query, db_strerror(ua->db));
1061 ua->error_msg(_("No table found: %s\n"), table);
1067 static void split_path_and_filename(UAContext *ua, RESTORE_CTX *rx, char *name)
1071 /* Find path without the filename.
1072 * I.e. everything after the last / is a "filename".
1073 * OK, maybe it is a directory name, but we treat it like
1074 * a filename. If we don't find a / then the whole name
1075 * must be a path name (e.g. c:).
1077 for (p=f=name; *p; p++) {
1078 if (IsPathSeparator(*p)) {
1079 f = p; /* set pos of last slash */
1082 if (IsPathSeparator(*f)) { /* did we find a slash? */
1083 f++; /* yes, point to filename */
1084 } else { /* no, whole thing must be path name */
1088 /* If filename doesn't exist (i.e. root directory), we
1089 * simply create a blank name consisting of a single
1090 * space. This makes handling zero length filenames
1095 rx->fname = check_pool_memory_size(rx->fname, 2*(rx->fnl)+1);
1096 db_escape_string(ua->jcr, ua->db, rx->fname, f, rx->fnl);
1104 rx->path = check_pool_memory_size(rx->path, 2*(rx->pnl)+1);
1105 db_escape_string(ua->jcr, ua->db, rx->path, name, rx->pnl);
1111 Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
1114 static bool can_restore_all_files(UAContext *ua)
1118 lst = ua->cons->ACL_lists[Directory_ACL];
1119 /* ACL not defined, or the first entry is not *all* */
1120 /* TODO: See if we search for *all* in all the list */
1121 if (!lst || strcasecmp((char*)lst->get(0), "*all*") != 0) {
1124 if (!lst || strcasecmp((char *)lst->get(0), "*all*") != 0) {
1131 static bool ask_for_fileregex(UAContext *ua, RESTORE_CTX *rx)
1133 bool can_restore=can_restore_all_files(ua);
1135 if (can_restore && find_arg(ua, NT_("all")) >= 0) { /* if user enters all on command line */
1136 return true; /* select everything */
1139 ua->send_msg(_("\n\nFor one or more of the JobIds selected, no files were found,\n"
1140 "so file selection is not possible.\n"
1141 "Most likely your retention policy pruned the files.\n"));
1144 ua->error_msg(_("\nThe current Console has UserId or Directory restrictions. "
1145 "The full restore is not allowed.\n"));
1149 if (get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
1150 if (ua->pint32_val == 1)
1152 while (get_cmd(ua, _("\nRegexp matching files to restore? (empty to abort): "))) {
1153 if (ua->cmd[0] == '\0') {
1156 regex_t *fileregex_re = NULL;
1158 char errmsg[500] = "";
1160 fileregex_re = (regex_t *)bmalloc(sizeof(regex_t));
1161 rc = regcomp(fileregex_re, ua->cmd, REG_EXTENDED|REG_NOSUB);
1163 regerror(rc, fileregex_re, errmsg, sizeof(errmsg));
1165 regfree(fileregex_re);
1168 ua->send_msg(_("Regex compile error: %s\n"), errmsg);
1170 rx->fileregex = bstrdup(ua->cmd);
1179 /* Walk on the delta_list of a TREE_NODE item and insert all parts
1180 * TODO: Optimize for bootstrap creation, remove recursion
1181 * 6 -> 5 -> 4 -> 3 -> 2 -> 1 -> 0
1183 * 0, 1, 2, 3, 4, 5, 6
1185 static void add_delta_list_findex(RESTORE_CTX *rx, struct delta_list *lst)
1191 add_delta_list_findex(rx, lst->next);
1193 add_findex(rx->bsr_list, lst->JobId, lst->FileIndex);
1197 * This is a list of all the files (components) that the
1198 * user has requested for restore. It is requested by
1199 * the plugin (for now hard coded only for VSS).
1200 * In the future, this will be requested by a RestoreObject
1201 * and the plugin name will be sent to the FD.
1203 static bool write_component_file(UAContext *ua, RESTORE_CTX *rx, char *fname)
1206 if (!rx->component_fd) {
1207 Mmsg(rx->component_fname, "%s/%s.restore.sel.XXXXXX", working_directory, my_name);
1208 fd = mkstemp(rx->component_fname);
1211 ua->error_msg(_("Unable to create component file %s. ERR=%s\n"),
1212 rx->component_fname, be.bstrerror());
1215 rx->component_fd = fdopen(fd, "w+");
1216 if (!rx->component_fd) {
1218 ua->error_msg(_("Unable to fdopen component file %s. ERR=%s\n"),
1219 rx->component_fname, be.bstrerror());
1223 fprintf(rx->component_fd, "%s\n", fname);
1224 if (ferror(rx->component_fd)) {
1225 ua->error_msg(_("Error writing component file.\n"));
1226 fclose(rx->component_fd);
1227 unlink(rx->component_fname);
1228 rx->component_fd = NULL;
1234 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
1237 JobId_t JobId, last_JobId;
1242 memset(&tree, 0, sizeof(TREE_CTX));
1244 * Build the directory tree containing JobIds user selected
1246 tree.root = new_tree(rx->TotalFiles);
1249 tree.hardlinks_in_mem = rx->hardlinks_in_mem;
1250 tree.no_auto_parent = rx->no_auto_parent;
1252 tree.last_dir_acl = NULL;
1254 * For display purposes, the same JobId, with different volumes may
1255 * appear more than once, however, we only insert it once.
1258 tree.FileEstimate = 0;
1259 if (get_next_jobid_from_list(&p, &JobId) > 0) {
1260 /* Use first JobId as estimate of the number of files to restore */
1261 Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
1262 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1263 ua->error_msg("%s\n", db_strerror(ua->db));
1266 /* Add about 25% more than this job for over estimate */
1267 tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
1268 tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
1272 ua->info_msg(_("\nBuilding directory tree for JobId(s) %s ... "),
1275 #define new_get_file_list
1276 #ifdef new_get_file_list
1277 if (!db_get_file_list(ua->jcr, ua->db,
1278 rx->JobIds, false /* do not use md5 */,
1279 true /* get delta */,
1280 insert_tree_handler, (void *)&tree))
1282 ua->error_msg("%s", db_strerror(ua->db));
1284 if (*rx->BaseJobIds) {
1285 pm_strcat(rx->JobIds, ",");
1286 pm_strcat(rx->JobIds, rx->BaseJobIds);
1289 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1292 if (JobId == last_JobId) {
1293 continue; /* eliminate duplicate JobIds */
1297 * Find files for this JobId and insert them in the tree
1299 Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
1300 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
1301 ua->error_msg("%s", db_strerror(ua->db));
1306 * At this point, the tree is built, so we can garbage collect
1307 * any memory released by the SQL engine that RedHat has
1308 * not returned to the OS :-(
1310 garbage_collect_memory();
1313 * Look at the first JobId on the list (presumably the oldest) and
1314 * if it is marked purged, don't do the manual selection because
1315 * the Job was pruned, so the tree is incomplete.
1317 if (tree.FileCount != 0) {
1318 /* Find out if any Job is purged */
1319 Mmsg(rx->query, "SELECT SUM(PurgedFiles) FROM Job WHERE JobId IN (%s)", rx->JobIds);
1320 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1321 ua->error_msg("%s\n", db_strerror(ua->db));
1323 /* rx->JobId is the PurgedFiles flag */
1324 if (rx->found && rx->JobId > 0) {
1325 tree.FileCount = 0; /* set count to zero, no tree selection */
1328 if (tree.FileCount == 0) {
1329 OK = ask_for_fileregex(ua, rx);
1332 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1333 if (JobId == last_JobId) {
1334 continue; /* eliminate duplicate JobIds */
1336 add_findex_all(rx->bsr_list, JobId, rx->fileregex);
1342 ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
1343 edit_uint64_with_commas(tree.FileCount, ec1));
1345 ua->info_msg(_("\n%s files inserted into the tree.\n"),
1346 edit_uint64_with_commas(tree.FileCount, ec1));
1349 if (find_arg(ua, NT_("done")) < 0) {
1350 /* Let the user interact in selecting which files to restore */
1351 OK = user_select_files_from_tree(&tree);
1355 * Walk down through the tree finding all files marked to be
1356 * extracted making a bootstrap file.
1360 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
1361 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
1362 if (node->extract || node->extract_dir) {
1363 Dmsg3(400, "JobId=%lld type=%d FI=%d\n", (uint64_t)node->JobId, node->type, node->FileIndex);
1364 /* TODO: optimize bsr insertion when jobid are non sorted */
1365 add_delta_list_findex(rx, node->delta_list);
1366 add_findex(rx->bsr_list, node->JobId, node->FileIndex);
1368 * Special VSS plugin code to return selected
1369 * components. For the moment, it is hard coded
1370 * for the VSS plugin.
1372 if (fnmatch(":component_info_*", node->fname, 0) == 0) {
1373 tree_getpath(node, cwd, sizeof(cwd));
1374 if (!write_component_file(ua, rx, cwd)) {
1379 if (node->extract && node->type != TN_NEWDIR) {
1380 rx->selected_files++; /* count only saved files */
1387 delete tree.uid_acl;
1388 delete tree.gid_acl;
1389 delete tree.dir_acl;
1391 free_tree(tree.root); /* free the directory tree */
1397 * This routine is used to get the current backup or a backup
1398 * before the specified date.
1400 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
1405 char fileset_name[MAX_NAME_LENGTH];
1406 char ed1[50], ed2[50];
1407 char pool_select[MAX_NAME_LENGTH];
1410 /* Create temp tables */
1411 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1412 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1413 if (!db_sql_query(ua->db, uar_create_temp[db_get_type_index(ua->db)], NULL, NULL)) {
1414 ua->error_msg("%s\n", db_strerror(ua->db));
1416 if (!db_sql_query(ua->db, uar_create_temp1[db_get_type_index(ua->db)], NULL, NULL)) {
1417 ua->error_msg("%s\n", db_strerror(ua->db));
1420 * Select Client from the Catalog
1422 memset(&cr, 0, sizeof(cr));
1423 if (!get_client_dbr(ua, &cr, JT_BACKUP_RESTORE)) {
1426 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1431 memset(&fsr, 0, sizeof(fsr));
1432 i = find_arg_with_value(ua, "FileSet");
1434 if (i >= 0 && is_name_valid(ua->argv[i], &ua->errmsg)) {
1435 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1436 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1437 ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1438 db_strerror(ua->db));
1441 } else if (i >= 0) { /* name is invalid */
1442 ua->error_msg(_("FileSet argument: %s\n"), ua->errmsg);
1445 if (i < 0) { /* fileset not found */
1446 edit_int64(cr.ClientId, ed1);
1447 Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1448 start_prompt(ua, _("The defined FileSet resources are:\n"));
1449 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1450 ua->error_msg("%s\n", db_strerror(ua->db));
1452 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1453 fileset_name, sizeof(fileset_name)) < 0) {
1454 ua->error_msg(_("No FileSet found for client \"%s\".\n"), cr.Name);
1458 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1459 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1460 ua->warning_msg(_("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1461 ua->send_msg(_("This probably means you modified the FileSet.\n"
1462 "Continuing anyway.\n"));
1466 /* If Pool specified, add PoolId specification */
1470 memset(&pr, 0, sizeof(pr));
1471 bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1472 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1473 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
1474 edit_int64(pr.PoolId, ed1));
1476 ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1480 /* Find JobId of last Full backup for this client, fileset */
1481 edit_int64(cr.ClientId, ed1);
1482 Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1484 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1485 ua->error_msg("%s\n", db_strerror(ua->db));
1489 /* Find all Volumes used by that JobId */
1490 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1491 ua->error_msg("%s\n", db_strerror(ua->db));
1495 /* Note, this is needed because I don't seem to get the callback
1496 * from the call just above.
1499 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1500 ua->warning_msg("%s\n", db_strerror(ua->db));
1502 if (rx->JobTDate == 0) {
1503 ua->error_msg(_("No Full backup before %s found.\n"), date);
1507 /* Now find most recent Differental Job after Full save, if any */
1508 Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1509 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1510 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1511 ua->warning_msg("%s\n", db_strerror(ua->db));
1513 /* Now update JobTDate to look into Differental, if any */
1515 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1516 ua->warning_msg("%s\n", db_strerror(ua->db));
1518 if (rx->JobTDate == 0) {
1519 ua->error_msg(_("No Full backup before %s found.\n"), date);
1523 /* Now find all Incremental Jobs after Full/dif save */
1524 Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1525 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1526 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1527 ua->warning_msg("%s\n", db_strerror(ua->db));
1530 /* Get the JobIds from that list */
1531 rx->last_jobid[0] = rx->JobIds[0] = 0;
1533 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1534 ua->warning_msg("%s\n", db_strerror(ua->db));
1537 if (rx->JobIds[0] != 0) {
1538 if (find_arg(ua, NT_("copies")) > 0) {
1539 /* Display a list of all copies */
1540 db_list_copies_records(ua->jcr, ua->db, 0, rx->JobIds,
1541 prtit, ua, HORZ_LIST);
1543 /* Display a list of Jobs selected for this restore */
1544 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1,HORZ_LIST);
1548 ua->warning_msg(_("No jobs found.\n"));
1552 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1553 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1557 static int restore_count_handler(void *ctx, int num_fields, char **row)
1559 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1560 rx->JobId = str_to_int64(row[0]);
1566 * Callback handler to get JobId and FileIndex for files
1567 * can insert more than one depending on the caller.
1569 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1571 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1572 JobId_t JobId = str_to_int64(row[0]);
1574 Dmsg3(200, "JobId=%s JobIds=%s FileIndex=%s\n", row[0], rx->JobIds, row[1]);
1576 /* New JobId, add it to JobIds
1577 * The list is sorted by JobId, so we need a cache for the previous value
1579 * It will permit to find restore objects to send during the restore
1581 if (rx->JobId != JobId) {
1583 pm_strcat(rx->JobIds, ",");
1585 pm_strcat(rx->JobIds, row[0]);
1589 add_findex(rx->bsr_list, rx->JobId, str_to_int64(row[1]));
1591 rx->selected_files++;
1596 * Callback handler make list of JobIds
1598 static int jobid_handler(void *ctx, int num_fields, char **row)
1600 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1602 if (strcmp(rx->last_jobid, row[0]) == 0) {
1603 return 0; /* duplicate id */
1605 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1606 if (rx->JobIds[0] != 0) {
1607 pm_strcat(rx->JobIds, ",");
1609 pm_strcat(rx->JobIds, row[0]);
1615 * Callback handler to pickup last Full backup JobTDate
1617 static int last_full_handler(void *ctx, int num_fields, char **row)
1619 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1621 rx->JobTDate = str_to_int64(row[1]);
1626 * Callback handler build FileSet name prompt list
1628 static int fileset_handler(void *ctx, int num_fields, char **row)
1630 /* row[0] = FileSet (name) */
1632 add_prompt((UAContext *)ctx, row[0]);
1638 * Free names in the list
1640 static void free_name_list(NAME_LIST *name_list)
1642 for (int i=0; i < name_list->num_ids; i++) {
1643 free(name_list->name[i]);
1645 bfree_and_null(name_list->name);
1646 name_list->max_ids = 0;
1647 name_list->num_ids = 0;
1650 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType)
1655 Dmsg1(200, "Already have store=%s\n", rx.store->name());
1659 * Try looking up Storage by name
1662 foreach_res(store, R_STORAGE) {
1663 if (strcmp(Storage, store->name()) == 0) {
1664 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1673 /* Check if an explicit storage resource is given */
1675 int i = find_arg_with_value(ua, "storage");
1677 store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1678 if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1682 if (store && (store != rx.store)) {
1683 ua->info_msg(_("\nWarning Storage is overridden by \"%s\" on the command line.\n"),
1686 bstrncpy(rx.RestoreMediaType, MediaType, sizeof(rx.RestoreMediaType));
1687 if (strcmp(MediaType, store->media_type) != 0) {
1688 ua->info_msg(_("This may not work because of two different MediaTypes:\n"
1689 " Storage MediaType=\"%s\"\n"
1690 " Volume MediaType=\"%s\".\n\n"),
1691 store->media_type, MediaType);
1693 Dmsg2(200, "Set store=%s MediaType=%s\n", rx.store->name(), rx.RestoreMediaType);
1698 /* If no storage resource, try to find one from MediaType */
1701 foreach_res(store, R_STORAGE) {
1702 if (strcmp(MediaType, store->media_type) == 0) {
1703 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1705 Dmsg1(200, "Set store=%s\n", rx.store->name());
1706 if (Storage == NULL || Storage[0] == 0) {
1707 ua->warning_msg(_("Using Storage \"%s\" from MediaType \"%s\".\n"),
1708 store->name(), MediaType);
1710 ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1711 Storage, store->name(), MediaType);
1719 ua->warning_msg(_("\nUnable to find Storage resource for\n"
1720 "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1723 /* Take command line arg, or ask user if none */
1724 rx.store = get_storage_resource(ua, false /* don't use default */);
1726 Dmsg1(200, "Set store=%s\n", rx.store->name());