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);
1005 * Note: we have just edited the JobIds into the query, so
1006 * we need to clear JobIds, or they will be added
1007 * back into JobIds with the query below, and then
1008 * restored twice. Fixes bug #2212.
1013 /* Find and insert jobid and File Index */
1014 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
1015 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
1016 rx->query, db_strerror(ua->db));
1019 ua->error_msg(_("No database record found for: %s\n"), file);
1020 // ua->error_msg("Query=%s\n", rx->query);
1027 * For a given path lookup the most recent backup in the catalog
1028 * to get the JobId and FileIndexes of all files in that directory.
1030 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
1033 strip_trailing_junk(dir);
1034 if (*rx->JobIds == 0) {
1035 ua->error_msg(_("No JobId specified cannot continue.\n"));
1038 Mmsg(rx->query, uar_jobid_fileindex_from_dir[db_get_type_index(ua->db)], rx->JobIds, dir, rx->ClientName);
1041 /* Find and insert jobid and File Index */
1042 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
1043 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
1044 rx->query, db_strerror(ua->db));
1047 ua->error_msg(_("No database record found for: %s\n"), dir);
1054 * Get the JobId and FileIndexes of all files in the specified table
1056 bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
1058 strip_trailing_junk(table);
1059 Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
1062 /* Find and insert jobid and File Index */
1063 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
1064 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
1065 rx->query, db_strerror(ua->db));
1068 ua->error_msg(_("No table found: %s\n"), table);
1074 static void split_path_and_filename(UAContext *ua, RESTORE_CTX *rx, char *name)
1078 /* Find path without the filename.
1079 * I.e. everything after the last / is a "filename".
1080 * OK, maybe it is a directory name, but we treat it like
1081 * a filename. If we don't find a / then the whole name
1082 * must be a path name (e.g. c:).
1084 for (p=f=name; *p; p++) {
1085 if (IsPathSeparator(*p)) {
1086 f = p; /* set pos of last slash */
1089 if (IsPathSeparator(*f)) { /* did we find a slash? */
1090 f++; /* yes, point to filename */
1091 } else { /* no, whole thing must be path name */
1095 /* If filename doesn't exist (i.e. root directory), we
1096 * simply create a blank name consisting of a single
1097 * space. This makes handling zero length filenames
1102 rx->fname = check_pool_memory_size(rx->fname, 2*(rx->fnl)+1);
1103 db_escape_string(ua->jcr, ua->db, rx->fname, f, rx->fnl);
1111 rx->path = check_pool_memory_size(rx->path, 2*(rx->pnl)+1);
1112 db_escape_string(ua->jcr, ua->db, rx->path, name, rx->pnl);
1118 Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
1121 static bool can_restore_all_files(UAContext *ua)
1125 lst = ua->cons->ACL_lists[Directory_ACL];
1126 /* ACL not defined, or the first entry is not *all* */
1127 /* TODO: See if we search for *all* in all the list */
1128 if (!lst || strcasecmp((char*)lst->get(0), "*all*") != 0) {
1131 if (!lst || strcasecmp((char *)lst->get(0), "*all*") != 0) {
1138 static bool ask_for_fileregex(UAContext *ua, RESTORE_CTX *rx)
1140 bool can_restore=can_restore_all_files(ua);
1142 if (can_restore && find_arg(ua, NT_("all")) >= 0) { /* if user enters all on command line */
1143 return true; /* select everything */
1146 ua->send_msg(_("\n\nFor one or more of the JobIds selected, no files were found,\n"
1147 "so file selection is not possible.\n"
1148 "Most likely your retention policy pruned the files.\n"));
1151 ua->error_msg(_("\nThe current Console has UserId or Directory restrictions. "
1152 "The full restore is not allowed.\n"));
1156 if (get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
1157 if (ua->pint32_val == 1)
1159 while (get_cmd(ua, _("\nRegexp matching files to restore? (empty to abort): "))) {
1160 if (ua->cmd[0] == '\0') {
1163 regex_t *fileregex_re = NULL;
1165 char errmsg[500] = "";
1167 fileregex_re = (regex_t *)bmalloc(sizeof(regex_t));
1168 rc = regcomp(fileregex_re, ua->cmd, REG_EXTENDED|REG_NOSUB);
1170 regerror(rc, fileregex_re, errmsg, sizeof(errmsg));
1172 regfree(fileregex_re);
1175 ua->send_msg(_("Regex compile error: %s\n"), errmsg);
1177 rx->fileregex = bstrdup(ua->cmd);
1186 /* Walk on the delta_list of a TREE_NODE item and insert all parts
1187 * TODO: Optimize for bootstrap creation, remove recursion
1188 * 6 -> 5 -> 4 -> 3 -> 2 -> 1 -> 0
1190 * 0, 1, 2, 3, 4, 5, 6
1192 static void add_delta_list_findex(RESTORE_CTX *rx, struct delta_list *lst)
1198 add_delta_list_findex(rx, lst->next);
1200 add_findex(rx->bsr_list, lst->JobId, lst->FileIndex);
1204 * This is a list of all the files (components) that the
1205 * user has requested for restore. It is requested by
1206 * the plugin (for now hard coded only for VSS).
1207 * In the future, this will be requested by a RestoreObject
1208 * and the plugin name will be sent to the FD.
1210 static bool write_component_file(UAContext *ua, RESTORE_CTX *rx, char *fname)
1213 if (!rx->component_fd) {
1214 Mmsg(rx->component_fname, "%s/%s.restore.sel.XXXXXX", working_directory, my_name);
1215 fd = mkstemp(rx->component_fname);
1218 ua->error_msg(_("Unable to create component file %s. ERR=%s\n"),
1219 rx->component_fname, be.bstrerror());
1222 rx->component_fd = fdopen(fd, "w+");
1223 if (!rx->component_fd) {
1225 ua->error_msg(_("Unable to fdopen component file %s. ERR=%s\n"),
1226 rx->component_fname, be.bstrerror());
1230 fprintf(rx->component_fd, "%s\n", fname);
1231 if (ferror(rx->component_fd)) {
1232 ua->error_msg(_("Error writing component file.\n"));
1233 fclose(rx->component_fd);
1234 unlink(rx->component_fname);
1235 rx->component_fd = NULL;
1241 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
1244 JobId_t JobId, last_JobId;
1249 memset(&tree, 0, sizeof(TREE_CTX));
1251 * Build the directory tree containing JobIds user selected
1253 tree.root = new_tree(rx->TotalFiles);
1256 tree.hardlinks_in_mem = rx->hardlinks_in_mem;
1257 tree.no_auto_parent = rx->no_auto_parent;
1259 tree.last_dir_acl = NULL;
1261 * For display purposes, the same JobId, with different volumes may
1262 * appear more than once, however, we only insert it once.
1265 tree.FileEstimate = 0;
1266 if (get_next_jobid_from_list(&p, &JobId) > 0) {
1267 /* Use first JobId as estimate of the number of files to restore */
1268 Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
1269 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1270 ua->error_msg("%s\n", db_strerror(ua->db));
1273 /* Add about 25% more than this job for over estimate */
1274 tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
1275 tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
1279 ua->info_msg(_("\nBuilding directory tree for JobId(s) %s ... "),
1282 #define new_get_file_list
1283 #ifdef new_get_file_list
1284 if (!db_get_file_list(ua->jcr, ua->db,
1285 rx->JobIds, false /* do not use md5 */,
1286 true /* get delta */,
1287 insert_tree_handler, (void *)&tree))
1289 ua->error_msg("%s", db_strerror(ua->db));
1291 if (*rx->BaseJobIds) {
1292 pm_strcat(rx->JobIds, ",");
1293 pm_strcat(rx->JobIds, rx->BaseJobIds);
1296 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1299 if (JobId == last_JobId) {
1300 continue; /* eliminate duplicate JobIds */
1304 * Find files for this JobId and insert them in the tree
1306 Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
1307 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
1308 ua->error_msg("%s", db_strerror(ua->db));
1313 * At this point, the tree is built, so we can garbage collect
1314 * any memory released by the SQL engine that RedHat has
1315 * not returned to the OS :-(
1317 garbage_collect_memory();
1320 * Look at the first JobId on the list (presumably the oldest) and
1321 * if it is marked purged, don't do the manual selection because
1322 * the Job was pruned, so the tree is incomplete.
1324 if (tree.FileCount != 0) {
1325 /* Find out if any Job is purged */
1326 Mmsg(rx->query, "SELECT SUM(PurgedFiles) FROM Job WHERE JobId IN (%s)", rx->JobIds);
1327 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1328 ua->error_msg("%s\n", db_strerror(ua->db));
1330 /* rx->JobId is the PurgedFiles flag */
1331 if (rx->found && rx->JobId > 0) {
1332 tree.FileCount = 0; /* set count to zero, no tree selection */
1335 if (tree.FileCount == 0) {
1336 OK = ask_for_fileregex(ua, rx);
1339 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1340 if (JobId == last_JobId) {
1341 continue; /* eliminate duplicate JobIds */
1343 add_findex_all(rx->bsr_list, JobId, rx->fileregex);
1349 ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
1350 edit_uint64_with_commas(tree.FileCount, ec1));
1352 ua->info_msg(_("\n%s files inserted into the tree.\n"),
1353 edit_uint64_with_commas(tree.FileCount, ec1));
1356 if (find_arg(ua, NT_("done")) < 0) {
1357 /* Let the user interact in selecting which files to restore */
1358 OK = user_select_files_from_tree(&tree);
1362 * Walk down through the tree finding all files marked to be
1363 * extracted making a bootstrap file.
1367 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
1368 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
1369 if (node->extract || node->extract_dir) {
1370 Dmsg3(400, "JobId=%lld type=%d FI=%d\n", (uint64_t)node->JobId, node->type, node->FileIndex);
1371 /* TODO: optimize bsr insertion when jobid are non sorted */
1372 add_delta_list_findex(rx, node->delta_list);
1373 add_findex(rx->bsr_list, node->JobId, node->FileIndex);
1375 * Special VSS plugin code to return selected
1376 * components. For the moment, it is hard coded
1377 * for the VSS plugin.
1379 if (fnmatch(":component_info_*", node->fname, 0) == 0) {
1380 tree_getpath(node, cwd, sizeof(cwd));
1381 if (!write_component_file(ua, rx, cwd)) {
1386 if (node->extract && node->type != TN_NEWDIR) {
1387 rx->selected_files++; /* count only saved files */
1394 delete tree.uid_acl;
1395 delete tree.gid_acl;
1396 delete tree.dir_acl;
1398 free_tree(tree.root); /* free the directory tree */
1404 * This routine is used to get the current backup or a backup
1405 * before the specified date.
1407 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
1412 char fileset_name[MAX_NAME_LENGTH];
1413 char ed1[50], ed2[50];
1414 char pool_select[MAX_NAME_LENGTH];
1417 /* Create temp tables */
1418 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1419 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1420 if (!db_sql_query(ua->db, uar_create_temp[db_get_type_index(ua->db)], NULL, NULL)) {
1421 ua->error_msg("%s\n", db_strerror(ua->db));
1423 if (!db_sql_query(ua->db, uar_create_temp1[db_get_type_index(ua->db)], NULL, NULL)) {
1424 ua->error_msg("%s\n", db_strerror(ua->db));
1427 * Select Client from the Catalog
1429 memset(&cr, 0, sizeof(cr));
1430 if (!get_client_dbr(ua, &cr, JT_BACKUP_RESTORE)) {
1433 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1438 memset(&fsr, 0, sizeof(fsr));
1439 i = find_arg_with_value(ua, "FileSet");
1441 if (i >= 0 && is_name_valid(ua->argv[i], &ua->errmsg)) {
1442 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1443 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1444 ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1445 db_strerror(ua->db));
1448 } else if (i >= 0) { /* name is invalid */
1449 ua->error_msg(_("FileSet argument: %s\n"), ua->errmsg);
1452 if (i < 0) { /* fileset not found */
1453 edit_int64(cr.ClientId, ed1);
1454 Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1455 start_prompt(ua, _("The defined FileSet resources are:\n"));
1456 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1457 ua->error_msg("%s\n", db_strerror(ua->db));
1459 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1460 fileset_name, sizeof(fileset_name)) < 0) {
1461 ua->error_msg(_("No FileSet found for client \"%s\".\n"), cr.Name);
1465 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1466 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1467 ua->warning_msg(_("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1468 ua->send_msg(_("This probably means you modified the FileSet.\n"
1469 "Continuing anyway.\n"));
1473 /* If Pool specified, add PoolId specification */
1477 memset(&pr, 0, sizeof(pr));
1478 bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1479 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1480 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
1481 edit_int64(pr.PoolId, ed1));
1483 ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1487 /* Find JobId of last Full backup for this client, fileset */
1488 edit_int64(cr.ClientId, ed1);
1489 Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1491 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1492 ua->error_msg("%s\n", db_strerror(ua->db));
1496 /* Find all Volumes used by that JobId */
1497 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1498 ua->error_msg("%s\n", db_strerror(ua->db));
1502 /* Note, this is needed because I don't seem to get the callback
1503 * from the call just above.
1506 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1507 ua->warning_msg("%s\n", db_strerror(ua->db));
1509 if (rx->JobTDate == 0) {
1510 ua->error_msg(_("No Full backup before %s found.\n"), date);
1514 /* Now find most recent Differental Job after Full save, if any */
1515 Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1516 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1517 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1518 ua->warning_msg("%s\n", db_strerror(ua->db));
1520 /* Now update JobTDate to look into Differental, if any */
1522 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1523 ua->warning_msg("%s\n", db_strerror(ua->db));
1525 if (rx->JobTDate == 0) {
1526 ua->error_msg(_("No Full backup before %s found.\n"), date);
1530 /* Now find all Incremental Jobs after Full/dif save */
1531 Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1532 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1533 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1534 ua->warning_msg("%s\n", db_strerror(ua->db));
1537 /* Get the JobIds from that list */
1538 rx->last_jobid[0] = rx->JobIds[0] = 0;
1540 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1541 ua->warning_msg("%s\n", db_strerror(ua->db));
1544 if (rx->JobIds[0] != 0) {
1545 if (find_arg(ua, NT_("copies")) > 0) {
1546 /* Display a list of all copies */
1547 db_list_copies_records(ua->jcr, ua->db, 0, rx->JobIds,
1548 prtit, ua, HORZ_LIST);
1550 /* Display a list of Jobs selected for this restore */
1551 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1,HORZ_LIST);
1555 ua->warning_msg(_("No jobs found.\n"));
1559 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1560 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1564 static int restore_count_handler(void *ctx, int num_fields, char **row)
1566 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1567 rx->JobId = str_to_int64(row[0]);
1573 * Callback handler to get JobId and FileIndex for files
1574 * can insert more than one depending on the caller.
1576 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1578 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1579 JobId_t JobId = str_to_int64(row[0]);
1581 Dmsg3(200, "JobId=%s JobIds=%s FileIndex=%s\n", row[0], rx->JobIds, row[1]);
1583 /* New JobId, add it to JobIds
1584 * The list is sorted by JobId, so we need a cache for the previous value
1586 * It will permit to find restore objects to send during the restore
1588 if (rx->JobId != JobId) {
1590 pm_strcat(rx->JobIds, ",");
1592 pm_strcat(rx->JobIds, row[0]);
1596 add_findex(rx->bsr_list, rx->JobId, str_to_int64(row[1]));
1598 rx->selected_files++;
1603 * Callback handler make list of JobIds
1605 static int jobid_handler(void *ctx, int num_fields, char **row)
1607 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1609 if (strcmp(rx->last_jobid, row[0]) == 0) {
1610 return 0; /* duplicate id */
1612 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1613 if (rx->JobIds[0] != 0) {
1614 pm_strcat(rx->JobIds, ",");
1616 pm_strcat(rx->JobIds, row[0]);
1622 * Callback handler to pickup last Full backup JobTDate
1624 static int last_full_handler(void *ctx, int num_fields, char **row)
1626 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1628 rx->JobTDate = str_to_int64(row[1]);
1633 * Callback handler build FileSet name prompt list
1635 static int fileset_handler(void *ctx, int num_fields, char **row)
1637 /* row[0] = FileSet (name) */
1639 add_prompt((UAContext *)ctx, row[0]);
1645 * Free names in the list
1647 static void free_name_list(NAME_LIST *name_list)
1649 for (int i=0; i < name_list->num_ids; i++) {
1650 free(name_list->name[i]);
1652 bfree_and_null(name_list->name);
1653 name_list->max_ids = 0;
1654 name_list->num_ids = 0;
1657 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType)
1662 Dmsg1(200, "Already have store=%s\n", rx.store->name());
1666 * Try looking up Storage by name
1669 foreach_res(store, R_STORAGE) {
1670 if (strcmp(Storage, store->name()) == 0) {
1671 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1680 /* Check if an explicit storage resource is given */
1682 int i = find_arg_with_value(ua, "storage");
1684 store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1685 if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1689 if (store && (store != rx.store)) {
1690 ua->info_msg(_("\nWarning Storage is overridden by \"%s\" on the command line.\n"),
1693 bstrncpy(rx.RestoreMediaType, MediaType, sizeof(rx.RestoreMediaType));
1694 if (strcmp(MediaType, store->media_type) != 0) {
1695 ua->info_msg(_("This may not work because of two different MediaTypes:\n"
1696 " Storage MediaType=\"%s\"\n"
1697 " Volume MediaType=\"%s\".\n\n"),
1698 store->media_type, MediaType);
1700 Dmsg2(200, "Set store=%s MediaType=%s\n", rx.store->name(), rx.RestoreMediaType);
1705 /* If no storage resource, try to find one from MediaType */
1708 foreach_res(store, R_STORAGE) {
1709 if (strcmp(MediaType, store->media_type) == 0) {
1710 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1712 Dmsg1(200, "Set store=%s\n", rx.store->name());
1713 if (Storage == NULL || Storage[0] == 0) {
1714 ua->warning_msg(_("Using Storage \"%s\" from MediaType \"%s\".\n"),
1715 store->name(), MediaType);
1717 ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1718 Storage, store->name(), MediaType);
1726 ua->warning_msg(_("\nUnable to find Storage resource for\n"
1727 "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1730 /* Take command line arg, or ask user if none */
1731 rx.store = get_storage_resource(ua, false /* don't use default */);
1733 Dmsg1(200, "Set store=%s\n", rx.store->name());