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) {
115 continue; /* skip if no value given */
117 if (strcasecmp(ua->argk[i], "comment") == 0) {
118 rx.comment = ua->argv[i];
119 if (!is_comment_legal(ua, rx.comment)) {
123 } else if (strcasecmp(ua->argk[i], "where") == 0) {
124 rx.where = ua->argv[i];
126 } else if (strcasecmp(ua->argk[i], "when") == 0) {
127 rx.when = ua->argv[i];
129 } else if (strcasecmp(ua->argk[i], "replace") == 0) {
130 rx.replace = ua->argv[i];
132 } else if (strcasecmp(ua->argk[i], "strip_prefix") == 0) {
133 strip_prefix = ua->argv[i];
135 } else if (strcasecmp(ua->argk[i], "add_prefix") == 0) {
136 add_prefix = ua->argv[i];
138 } else if (strcasecmp(ua->argk[i], "add_suffix") == 0) {
139 add_suffix = ua->argv[i];
141 } else if (strcasecmp(ua->argk[i], "regexwhere") == 0) {
142 rx.RegexWhere = ua->argv[i];
144 } else if (strcasecmp(ua->argk[i], "optimizespeed") == 0) {
145 if (strcasecmp(ua->argv[i], "0") || strcasecmp(ua->argv[i], "no") ||
146 strcasecmp(ua->argv[i], "false")) {
147 rx.hardlinks_in_mem = false;
152 if (strip_prefix || add_suffix || add_prefix) {
153 int len = bregexp_get_build_where_size(strip_prefix, add_prefix, add_suffix);
154 regexp = (char *)bmalloc(len * sizeof(char));
156 bregexp_build_where(regexp, len, strip_prefix, add_prefix, add_suffix);
157 rx.RegexWhere = regexp;
160 /* TODO: add acl for regexwhere ? */
163 if (!acl_access_ok(ua, Where_ACL, rx.RegexWhere)) {
164 ua->error_msg(_("\"RegexWhere\" specification not authorized.\n"));
170 if (!acl_access_ok(ua, Where_ACL, rx.where)) {
171 ua->error_msg(_("\"where\" specification not authorized.\n"));
176 /* Ensure there is at least one Restore Job */
178 foreach_res(job, R_JOB) {
179 if (job->JobType == JT_RESTORE) {
180 if (!rx.restore_job) {
181 rx.restore_job = job;
187 if (!rx.restore_jobs) {
189 "No Restore Job Resource found in bacula-dir.conf.\n"
190 "You must create at least one before running this command.\n"));
195 * Request user to select JobIds or files by various different methods
196 * last 20 jobs, where File saved, most recent backup, ...
197 * In the end, a list of files are pumped into
200 switch (user_select_jobids_or_files(ua, &rx)) {
203 case 1: /* selected by jobid */
204 get_and_display_basejobs(ua, &rx);
205 if (!build_directory_tree(ua, &rx)) {
206 ua->send_msg(_("Restore not done.\n"));
210 case 2: /* selected by filename, no tree needed */
214 if (rx.bsr_list->size() > 0) {
216 if (!complete_bsr(ua, rx.bsr_list)) { /* find Vol, SessId, SessTime from JobIds */
217 ua->error_msg(_("Unable to construct a valid BSR. Cannot continue.\n"));
220 if (!(rx.selected_files = write_bsr_file(ua, rx))) {
221 ua->warning_msg(_("No files selected to be restored.\n"));
225 ua->send_msg(_("Bootstrap records written to %s\n"), ua->jcr->RestoreBootstrap);
226 display_bsr_info(ua, rx); /* display vols needed, etc */
228 if (rx.selected_files==1) {
229 ua->info_msg(_("\n1 file selected to be restored.\n\n"));
231 ua->info_msg(_("\n%s files selected to be restored.\n\n"),
232 edit_uint64_with_commas(rx.selected_files, ed1));
235 ua->warning_msg(_("No files selected to be restored.\n"));
239 if (rx.restore_jobs == 1) {
240 job = rx.restore_job;
242 job = get_restore_job(ua);
248 get_client_name(ua, &rx);
249 if (!rx.ClientName) {
250 ua->error_msg(_("No Client resource found!\n"));
253 get_restore_client_name(ua, rx);
255 escaped_bsr_name = escape_filename(jcr->RestoreBootstrap);
258 "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
259 " bootstrap=\"%s\" files=%u catalog=\"%s\"",
260 job->name(), rx.ClientName, rx.RestoreClientName,
261 rx.store?rx.store->name():"",
262 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
263 rx.selected_files, ua->catalog->name());
265 /* Build run command */
267 if (rx.RestoreMediaType[0]) {
268 Mmsg(buf, " mediatype=\"%s\"", rx.RestoreMediaType);
269 pm_strcat(ua->cmd, buf);
273 escaped_where_name = escape_filename(rx.RegexWhere);
274 Mmsg(buf, " regexwhere=\"%s\"",
275 escaped_where_name ? escaped_where_name : rx.RegexWhere);
277 } else if (rx.where) {
278 escaped_where_name = escape_filename(rx.where);
279 Mmsg(buf," where=\"%s\"",
280 escaped_where_name ? escaped_where_name : rx.where);
282 pm_strcat(ua->cmd, buf);
285 Mmsg(buf, " replace=%s", rx.replace);
286 pm_strcat(ua->cmd, buf);
290 pm_strcat(ua->cmd, " fdcalled=yes");
294 Mmsg(buf, " when=\"%s\"", rx.when);
295 pm_strcat(ua->cmd, buf);
299 Mmsg(buf, " comment=\"%s\"", rx.comment);
300 pm_strcat(ua->cmd, buf);
303 if (escaped_bsr_name != NULL) {
304 bfree(escaped_bsr_name);
307 if (escaped_where_name != NULL) {
308 bfree(escaped_where_name);
315 if (find_arg(ua, NT_("yes")) > 0) {
316 pm_strcat(ua->cmd, " yes"); /* pass it on to the run command */
318 Dmsg1(200, "Submitting: %s\n", ua->cmd);
320 * Transfer jobids, component stuff to jcr to
321 * pass to run_cmd(). Note, these are fields and
322 * other things that are not passed on the command
325 /* ***FIXME*** pass jobids on command line */
327 free_pool_memory(jcr->JobIds);
329 jcr->JobIds = rx.JobIds;
331 jcr->component_fname = rx.component_fname;
332 rx.component_fname = NULL;
333 jcr->component_fd = rx.component_fd;
334 rx.component_fd = NULL;
336 run_cmd(ua, ua->cmd);
338 garbage_collect_memory(); /* release unused memory */
342 if (escaped_bsr_name != NULL) {
343 bfree(escaped_bsr_name);
346 if (escaped_where_name != NULL) {
347 bfree(escaped_where_name);
354 /* Free the plugin config if needed, we don't want to re-use
355 * this part of the next try
357 free_plugin_config_items(jcr->plugin_config);
358 jcr->plugin_config = NULL;
361 garbage_collect_memory(); /* release unused memory */
367 * Fill the rx->BaseJobIds and display the list
369 static void get_and_display_basejobs(UAContext *ua, RESTORE_CTX *rx)
373 if (!db_get_used_base_jobids(ua->jcr, ua->db, rx->JobIds, &jobids)) {
374 ua->warning_msg("%s", db_strerror(ua->db));
379 Mmsg(q, uar_print_jobs, jobids.list);
380 ua->send_msg(_("The restore will use the following job(s) as Base\n"));
381 db_list_sql_query(ua->jcr, ua->db, q.c_str(), prtit, ua, 1, HORZ_LIST);
383 pm_strcpy(rx->BaseJobIds, jobids.list);
386 void free_rx(RESTORE_CTX *rx)
388 free_bsr(rx->bsr_list);
390 free_and_null_pool_memory(rx->JobIds);
391 free_and_null_pool_memory(rx->BaseJobIds);
392 free_and_null_pool_memory(rx->fname);
393 free_and_null_pool_memory(rx->path);
394 free_and_null_pool_memory(rx->query);
397 rx->fileregex = NULL;
399 if (rx->component_fd) {
400 fclose(rx->component_fd);
401 rx->component_fd = NULL;
403 if (rx->component_fname) {
404 unlink(rx->component_fname);
406 free_and_null_pool_memory(rx->component_fname);
407 free_name_list(&rx->name_list);
410 static bool has_value(UAContext *ua, int i)
413 ua->error_msg(_("Missing value for keyword: %s\n"), ua->argk[i]);
420 * This gets the client name from which the backup was made
422 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
424 /* If no client name specified yet, get it now */
425 if (!rx->ClientName[0]) {
427 /* try command line argument */
428 int i = find_arg_with_value(ua, NT_("client"));
430 i = find_arg_with_value(ua, NT_("backupclient"));
433 if (!is_name_valid(ua->argv[i], &ua->errmsg)) {
434 ua->error_msg("%s argument: %s", ua->argk[i], ua->errmsg);
437 bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
440 memset(&cr, 0, sizeof(cr));
441 /* We want the name of the client where the backup was made */
442 if (!get_client_dbr(ua, &cr, JT_BACKUP_RESTORE)) {
445 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
451 * This is where we pick up a client name to restore to.
453 static int get_restore_client_name(UAContext *ua, RESTORE_CTX &rx)
455 /* Start with same name as backup client */
456 bstrncpy(rx.RestoreClientName, rx.ClientName, sizeof(rx.RestoreClientName));
458 /* try command line argument */
459 int i = find_arg_with_value(ua, NT_("restoreclient"));
461 if (!is_name_valid(ua->argv[i], &ua->errmsg)) {
462 ua->error_msg("%s argument: %s", ua->argk[i], ua->errmsg);
465 bstrncpy(rx.RestoreClientName, ua->argv[i], sizeof(rx.RestoreClientName));
474 * The first step in the restore process is for the user to
475 * select a list of JobIds from which he will subsequently
476 * select which files are to be restored.
478 * Returns: 2 if filename list made
479 * 1 if jobid list made
482 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
485 char date[MAX_TIME_LENGTH];
486 bool have_date = false;
487 /* Include current second if using current time */
488 utime_t now = time(NULL) + 1;
490 JOB_DBR jr = { (JobId_t)-1 };
493 const char *list[] = {
494 _("List last 20 Jobs run"),
495 _("List Jobs where a given File is saved"),
496 _("Enter list of comma separated JobIds to select"),
497 _("Enter SQL list command"),
498 _("Select the most recent backup for a client"),
499 _("Select backup for a client before a specified time"),
500 _("Enter a list of files to restore"),
501 _("Enter a list of files to restore before a specified time"),
502 _("Find the JobIds of the most recent backup for a client"),
503 _("Find the JobIds for a backup for a client before a specified time"),
504 _("Enter a list of directories to restore for found JobIds"),
505 _("Select full restore to a specified Job date"),
510 /* These keywords are handled in a for loop */
520 /* The keyword below are handled by individual arg lookups */
526 "bootstrap", /* 13 */
528 "strip_prefix", /* 15 */
529 "add_prefix", /* 16 */
530 "add_suffix", /* 17 */
531 "regexwhere", /* 18 */
532 "restoreclient", /* 19 */
535 "restorejob", /* 22 */
537 "xxxxxxxxx", /* 24 */
546 for (i=1; i<ua->argc; i++) { /* loop through arguments */
547 bool found_kw = false;
548 for (j=0; kw[j]; j++) { /* loop through keywords */
549 if (strcasecmp(kw[j], ua->argk[i]) == 0) {
556 ua->error_msg(_("Unknown keyword: %s\n"), ua->argk[i]);
559 /* Found keyword in kw[] list, process it */
562 if (!has_value(ua, i)) {
565 if (*rx->JobIds != 0) {
566 pm_strcat(rx->JobIds, ",");
568 pm_strcat(rx->JobIds, ua->argv[i]);
571 case 1: /* current */
573 * Note, we add one second here just to include any job
574 * that may have finished within the current second,
575 * which happens a lot in scripting small jobs.
577 bstrutime(date, sizeof(date), now);
581 if (have_date || !has_value(ua, i)) {
584 if (str_to_utime(ua->argv[i]) == 0) {
585 ua->error_msg(_("Improper date format: %s\n"), ua->argv[i]);
588 bstrncpy(date, ua->argv[i], sizeof(date));
593 if (!has_value(ua, i)) {
597 bstrutime(date, sizeof(date), now);
599 if (!get_client_name(ua, rx)) {
602 pm_strcpy(ua->cmd, ua->argv[i]);
603 insert_one_file_or_dir(ua, rx, date, j==4);
607 bstrutime(date, sizeof(date), now);
609 if (!select_backups_before_date(ua, rx, date)) {
614 case 6: /* pool specified */
615 if (!has_value(ua, i)) {
618 rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
620 ua->error_msg(_("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
623 if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
625 ua->error_msg(_("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
629 case 7: /* all specified */
633 * All keywords 7 or greater are ignored or handled by a select prompt
641 ua->send_msg(_("\nFirst you select one or more JobIds that contain files\n"
642 "to be restored. You will be presented several methods\n"
643 "of specifying the JobIds. Then you will be allowed to\n"
644 "select which files from those JobIds are to be restored.\n\n"));
647 /* If choice not already made above, prompt */
654 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
655 for (int i=0; list[i]; i++) {
656 add_prompt(ua, list[i]);
659 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
660 case -1: /* error or cancel */
662 case 0: /* list last 20 Jobs run */
663 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
664 ua->error_msg(_("SQL query not authorized.\n"));
667 gui_save = ua->jcr->gui;
669 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
670 ua->jcr->gui = gui_save;
673 case 1: /* list where a file is saved */
674 if (!get_client_name(ua, rx)) {
677 if (!get_cmd(ua, _("Enter Filename (no path):"))) {
680 len = strlen(ua->cmd);
681 fname = (char *)malloc(len * 2 + 1);
682 db_escape_string(ua->jcr, ua->db, fname, ua->cmd, len);
683 Mmsg(rx->query, uar_file[db_get_type_index(ua->db)], rx->ClientName, fname);
685 gui_save = ua->jcr->gui;
687 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
688 ua->jcr->gui = gui_save;
691 case 2: /* enter a list of JobIds */
692 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
695 pm_strcpy(rx->JobIds, ua->cmd);
697 case 3: /* Enter an SQL list command */
698 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
699 ua->error_msg(_("SQL query not authorized.\n"));
702 if (!get_cmd(ua, _("Enter SQL list command: "))) {
705 gui_save = ua->jcr->gui;
707 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
708 ua->jcr->gui = gui_save;
711 case 4: /* Select the most recent backups */
713 bstrutime(date, sizeof(date), now);
715 if (!select_backups_before_date(ua, rx, date)) {
719 case 5: /* select backup at specified time */
721 if (!get_date(ua, date, sizeof(date))) {
725 if (!select_backups_before_date(ua, rx, date)) {
729 case 6: /* Enter files */
731 bstrutime(date, sizeof(date), now);
733 if (!get_client_name(ua, rx)) {
736 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
737 "containing a list of file names with paths, and terminate\n"
738 "them with a blank line.\n"));
740 if (!get_cmd(ua, _("Enter full filename: "))) {
743 len = strlen(ua->cmd);
747 insert_one_file_or_dir(ua, rx, date, false);
750 case 7: /* enter files backed up before specified time */
752 if (!get_date(ua, date, sizeof(date))) {
756 if (!get_client_name(ua, rx)) {
759 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
760 "containing a list of file names with paths, and terminate\n"
761 "them with a blank line.\n"));
763 if (!get_cmd(ua, _("Enter full filename: "))) {
766 len = strlen(ua->cmd);
770 insert_one_file_or_dir(ua, rx, date, false);
774 case 8: /* Find JobIds for current backup */
776 bstrutime(date, sizeof(date), now);
778 if (!select_backups_before_date(ua, rx, date)) {
784 case 9: /* Find JobIds for give date */
786 if (!get_date(ua, date, sizeof(date))) {
790 if (!select_backups_before_date(ua, rx, date)) {
796 case 10: /* Enter directories */
797 if (*rx->JobIds != 0) {
798 ua->send_msg(_("You have already selected the following JobIds: %s\n"),
800 } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
801 if (*rx->JobIds != 0 && *ua->cmd) {
802 pm_strcat(rx->JobIds, ",");
804 pm_strcat(rx->JobIds, ua->cmd);
806 if (*rx->JobIds == 0 || *rx->JobIds == '.') {
808 return 0; /* nothing entered, return */
811 bstrutime(date, sizeof(date), now);
813 if (!get_client_name(ua, rx)) {
816 ua->send_msg(_("Enter full directory names or start the name\n"
817 "with a < to indicate it is a filename containing a list\n"
818 "of directories and terminate them with a blank line.\n"));
820 if (!get_cmd(ua, _("Enter directory name: "))) {
823 len = strlen(ua->cmd);
827 /* Add trailing slash to end of directory names */
828 if (ua->cmd[0] != '<' && !IsPathSeparator(ua->cmd[len-1])) {
829 strcat(ua->cmd, "/");
831 insert_one_file_or_dir(ua, rx, date, true);
835 case 11: /* Choose a jobid and select jobs */
836 if (!get_cmd(ua, _("Enter JobId to get the state to restore: ")) ||
837 !is_an_integer(ua->cmd))
842 memset(&jr, 0, sizeof(JOB_DBR));
843 jr.JobId = str_to_int64(ua->cmd);
844 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
845 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
846 ua->cmd, db_strerror(ua->db));
849 ua->send_msg(_("Selecting jobs to build the Full state at %s\n"),
851 jr.JobLevel = L_INCREMENTAL; /* Take Full+Diff+Incr */
852 if (!db_get_accurate_jobids(ua->jcr, ua->db, &jr, &jobids)) {
855 pm_strcpy(rx->JobIds, jobids.list);
856 Dmsg1(30, "Item 12: jobids = %s\n", rx->JobIds);
858 case 12: /* Cancel or quit */
863 memset(&jr, 0, sizeof(JOB_DBR));
864 POOLMEM *JobIds = get_pool_memory(PM_FNAME);
868 * Find total number of files to be restored, and filter the JobId
869 * list to contain only ones permitted by the ACL conditions.
871 for (p=rx->JobIds; ; ) {
873 int stat = get_next_jobid_from_list(&p, &JobId);
875 ua->error_msg(_("Invalid JobId in list.\n"));
876 free_pool_memory(JobIds);
882 if (jr.JobId == JobId) {
883 continue; /* duplicate of last JobId */
885 memset(&jr, 0, sizeof(JOB_DBR));
887 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
888 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
889 edit_int64(JobId, ed1), db_strerror(ua->db));
890 free_pool_memory(JobIds);
893 if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
894 ua->error_msg(_("Access to JobId=%s (Job \"%s\") not authorized. Not selected.\n"),
895 edit_int64(JobId, ed1), jr.Name);
899 pm_strcat(JobIds, ",");
901 pm_strcat(JobIds, edit_int64(JobId, ed1));
902 rx->TotalFiles += jr.JobFiles;
904 pm_strcpy(rx->JobIds, JobIds); /* Set ACL filtered list */
905 free_pool_memory(JobIds);
906 if (*rx->JobIds == 0) {
907 ua->warning_msg(_("No Jobs selected.\n"));
911 if (strchr(rx->JobIds,',')) {
912 ua->info_msg(_("You have selected the following JobIds: %s\n"), rx->JobIds);
914 ua->info_msg(_("You have selected the following JobId: %s\n"), rx->JobIds);
922 static bool get_date(UAContext *ua, char *date, int date_len)
924 ua->send_msg(_("The restored files will the most current backup\n"
925 "BEFORE the date you specify below.\n\n"));
927 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
930 if (str_to_utime(ua->cmd) != 0) {
933 ua->error_msg(_("Improper date format.\n"));
935 bstrncpy(date, ua->cmd, date_len);
940 * Insert a single file, or read a list of files from a file
942 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
952 if ((ffd = bfopen(p, "rb")) == NULL) {
954 ua->error_msg(_("Cannot open file %s: ERR=%s\n"),
958 while (fgets(file, sizeof(file), ffd)) {
961 if (!insert_dir_into_findex_list(ua, rx, file, date)) {
962 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
965 if (!insert_file_into_findex_list(ua, rx, file, date)) {
966 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
974 insert_table_into_findex_list(ua, rx, p);
978 insert_dir_into_findex_list(ua, rx, ua->cmd, date);
980 insert_file_into_findex_list(ua, rx, ua->cmd, date);
987 * For a given file (path+filename), split into path and file, then
988 * lookup the most recent backup in the catalog to get the JobId
989 * and FileIndex, then insert them into the findex list.
991 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
994 strip_trailing_newline(file);
995 split_path_and_filename(ua, rx, file);
996 if (*rx->JobIds == 0) {
997 Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname,
1000 Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
1001 rx->path, rx->fname, rx->ClientName);
1004 /* Find and insert jobid and File Index */
1005 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
1006 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
1007 rx->query, db_strerror(ua->db));
1010 ua->error_msg(_("No database record found for: %s\n"), file);
1011 // ua->error_msg("Query=%s\n", rx->query);
1018 * For a given path lookup the most recent backup in the catalog
1019 * to get the JobId and FileIndexes of all files in that directory.
1021 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
1024 strip_trailing_junk(dir);
1025 if (*rx->JobIds == 0) {
1026 ua->error_msg(_("No JobId specified cannot continue.\n"));
1029 Mmsg(rx->query, uar_jobid_fileindex_from_dir[db_get_type_index(ua->db)], rx->JobIds, dir, rx->ClientName);
1032 /* Find and insert jobid and File Index */
1033 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
1034 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
1035 rx->query, db_strerror(ua->db));
1038 ua->error_msg(_("No database record found for: %s\n"), dir);
1045 * Get the JobId and FileIndexes of all files in the specified table
1047 bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
1049 strip_trailing_junk(table);
1050 Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
1053 /* Find and insert jobid and File Index */
1054 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
1055 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
1056 rx->query, db_strerror(ua->db));
1059 ua->error_msg(_("No table found: %s\n"), table);
1065 static void split_path_and_filename(UAContext *ua, RESTORE_CTX *rx, char *name)
1069 /* Find path without the filename.
1070 * I.e. everything after the last / is a "filename".
1071 * OK, maybe it is a directory name, but we treat it like
1072 * a filename. If we don't find a / then the whole name
1073 * must be a path name (e.g. c:).
1075 for (p=f=name; *p; p++) {
1076 if (IsPathSeparator(*p)) {
1077 f = p; /* set pos of last slash */
1080 if (IsPathSeparator(*f)) { /* did we find a slash? */
1081 f++; /* yes, point to filename */
1082 } else { /* no, whole thing must be path name */
1086 /* If filename doesn't exist (i.e. root directory), we
1087 * simply create a blank name consisting of a single
1088 * space. This makes handling zero length filenames
1093 rx->fname = check_pool_memory_size(rx->fname, 2*(rx->fnl)+1);
1094 db_escape_string(ua->jcr, ua->db, rx->fname, f, rx->fnl);
1102 rx->path = check_pool_memory_size(rx->path, 2*(rx->pnl)+1);
1103 db_escape_string(ua->jcr, ua->db, rx->path, name, rx->pnl);
1109 Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
1112 static bool can_restore_all_files(UAContext *ua)
1116 lst = ua->cons->ACL_lists[Directory_ACL];
1117 /* ACL not defined, or the first entry is not *all* */
1118 /* TODO: See if we search for *all* in all the list */
1119 if (!lst || strcasecmp((char*)lst->get(0), "*all*") != 0) {
1122 if (!lst || strcasecmp((char *)lst->get(0), "*all*") != 0) {
1129 static bool ask_for_fileregex(UAContext *ua, RESTORE_CTX *rx)
1131 bool can_restore=can_restore_all_files(ua);
1133 if (can_restore && find_arg(ua, NT_("all")) >= 0) { /* if user enters all on command line */
1134 return true; /* select everything */
1137 ua->send_msg(_("\n\nFor one or more of the JobIds selected, no files were found,\n"
1138 "so file selection is not possible.\n"
1139 "Most likely your retention policy pruned the files.\n"));
1142 ua->error_msg(_("\nThe current Console has UserId or Directory restrictions. "
1143 "The full restore is not allowed.\n"));
1147 if (get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
1148 if (ua->pint32_val == 1)
1150 while (get_cmd(ua, _("\nRegexp matching files to restore? (empty to abort): "))) {
1151 if (ua->cmd[0] == '\0') {
1154 regex_t *fileregex_re = NULL;
1156 char errmsg[500] = "";
1158 fileregex_re = (regex_t *)bmalloc(sizeof(regex_t));
1159 rc = regcomp(fileregex_re, ua->cmd, REG_EXTENDED|REG_NOSUB);
1161 regerror(rc, fileregex_re, errmsg, sizeof(errmsg));
1163 regfree(fileregex_re);
1166 ua->send_msg(_("Regex compile error: %s\n"), errmsg);
1168 rx->fileregex = bstrdup(ua->cmd);
1177 /* Walk on the delta_list of a TREE_NODE item and insert all parts
1178 * TODO: Optimize for bootstrap creation, remove recursion
1179 * 6 -> 5 -> 4 -> 3 -> 2 -> 1 -> 0
1181 * 0, 1, 2, 3, 4, 5, 6
1183 static void add_delta_list_findex(RESTORE_CTX *rx, struct delta_list *lst)
1189 add_delta_list_findex(rx, lst->next);
1191 add_findex(rx->bsr_list, lst->JobId, lst->FileIndex);
1195 * This is a list of all the files (components) that the
1196 * user has requested for restore. It is requested by
1197 * the plugin (for now hard coded only for VSS).
1198 * In the future, this will be requested by a RestoreObject
1199 * and the plugin name will be sent to the FD.
1201 static bool write_component_file(UAContext *ua, RESTORE_CTX *rx, char *fname)
1204 if (!rx->component_fd) {
1205 Mmsg(rx->component_fname, "%s/%s.restore.sel.XXXXXX", working_directory, my_name);
1206 fd = mkstemp(rx->component_fname);
1209 ua->error_msg(_("Unable to create component file %s. ERR=%s\n"),
1210 rx->component_fname, be.bstrerror());
1213 rx->component_fd = fdopen(fd, "w+");
1214 if (!rx->component_fd) {
1216 ua->error_msg(_("Unable to fdopen component file %s. ERR=%s\n"),
1217 rx->component_fname, be.bstrerror());
1221 fprintf(rx->component_fd, "%s\n", fname);
1222 if (ferror(rx->component_fd)) {
1223 ua->error_msg(_("Error writing component file.\n"));
1224 fclose(rx->component_fd);
1225 unlink(rx->component_fname);
1226 rx->component_fd = NULL;
1232 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
1235 JobId_t JobId, last_JobId;
1240 memset(&tree, 0, sizeof(TREE_CTX));
1242 * Build the directory tree containing JobIds user selected
1244 tree.root = new_tree(rx->TotalFiles);
1247 tree.hardlinks_in_mem = rx->hardlinks_in_mem;
1249 tree.last_dir_acl = NULL;
1251 * For display purposes, the same JobId, with different volumes may
1252 * appear more than once, however, we only insert it once.
1255 tree.FileEstimate = 0;
1256 if (get_next_jobid_from_list(&p, &JobId) > 0) {
1257 /* Use first JobId as estimate of the number of files to restore */
1258 Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
1259 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1260 ua->error_msg("%s\n", db_strerror(ua->db));
1263 /* Add about 25% more than this job for over estimate */
1264 tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
1265 tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
1269 ua->info_msg(_("\nBuilding directory tree for JobId(s) %s ... "),
1272 #define new_get_file_list
1273 #ifdef new_get_file_list
1274 if (!db_get_file_list(ua->jcr, ua->db,
1275 rx->JobIds, false /* do not use md5 */,
1276 true /* get delta */,
1277 insert_tree_handler, (void *)&tree))
1279 ua->error_msg("%s", db_strerror(ua->db));
1281 if (*rx->BaseJobIds) {
1282 pm_strcat(rx->JobIds, ",");
1283 pm_strcat(rx->JobIds, rx->BaseJobIds);
1286 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1289 if (JobId == last_JobId) {
1290 continue; /* eliminate duplicate JobIds */
1294 * Find files for this JobId and insert them in the tree
1296 Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
1297 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
1298 ua->error_msg("%s", db_strerror(ua->db));
1303 * At this point, the tree is built, so we can garbage collect
1304 * any memory released by the SQL engine that RedHat has
1305 * not returned to the OS :-(
1307 garbage_collect_memory();
1310 * Look at the first JobId on the list (presumably the oldest) and
1311 * if it is marked purged, don't do the manual selection because
1312 * the Job was pruned, so the tree is incomplete.
1314 if (tree.FileCount != 0) {
1315 /* Find out if any Job is purged */
1316 Mmsg(rx->query, "SELECT SUM(PurgedFiles) FROM Job WHERE JobId IN (%s)", rx->JobIds);
1317 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1318 ua->error_msg("%s\n", db_strerror(ua->db));
1320 /* rx->JobId is the PurgedFiles flag */
1321 if (rx->found && rx->JobId > 0) {
1322 tree.FileCount = 0; /* set count to zero, no tree selection */
1325 if (tree.FileCount == 0) {
1326 OK = ask_for_fileregex(ua, rx);
1329 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1330 if (JobId == last_JobId) {
1331 continue; /* eliminate duplicate JobIds */
1333 add_findex_all(rx->bsr_list, JobId, rx->fileregex);
1339 ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
1340 edit_uint64_with_commas(tree.FileCount, ec1));
1342 ua->info_msg(_("\n%s files inserted into the tree.\n"),
1343 edit_uint64_with_commas(tree.FileCount, ec1));
1346 if (find_arg(ua, NT_("done")) < 0) {
1347 /* Let the user interact in selecting which files to restore */
1348 OK = user_select_files_from_tree(&tree);
1352 * Walk down through the tree finding all files marked to be
1353 * extracted making a bootstrap file.
1357 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
1358 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
1359 if (node->extract || node->extract_dir) {
1360 Dmsg3(400, "JobId=%lld type=%d FI=%d\n", (uint64_t)node->JobId, node->type, node->FileIndex);
1361 /* TODO: optimize bsr insertion when jobid are non sorted */
1362 add_delta_list_findex(rx, node->delta_list);
1363 add_findex(rx->bsr_list, node->JobId, node->FileIndex);
1365 * Special VSS plugin code to return selected
1366 * components. For the moment, it is hard coded
1367 * for the VSS plugin.
1369 if (fnmatch(":component_info_*", node->fname, 0) == 0) {
1370 tree_getpath(node, cwd, sizeof(cwd));
1371 if (!write_component_file(ua, rx, cwd)) {
1376 if (node->extract && node->type != TN_NEWDIR) {
1377 rx->selected_files++; /* count only saved files */
1384 delete tree.uid_acl;
1385 delete tree.gid_acl;
1386 delete tree.dir_acl;
1388 free_tree(tree.root); /* free the directory tree */
1394 * This routine is used to get the current backup or a backup
1395 * before the specified date.
1397 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
1402 char fileset_name[MAX_NAME_LENGTH];
1403 char ed1[50], ed2[50];
1404 char pool_select[MAX_NAME_LENGTH];
1407 /* Create temp tables */
1408 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1409 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1410 if (!db_sql_query(ua->db, uar_create_temp[db_get_type_index(ua->db)], NULL, NULL)) {
1411 ua->error_msg("%s\n", db_strerror(ua->db));
1413 if (!db_sql_query(ua->db, uar_create_temp1[db_get_type_index(ua->db)], NULL, NULL)) {
1414 ua->error_msg("%s\n", db_strerror(ua->db));
1417 * Select Client from the Catalog
1419 memset(&cr, 0, sizeof(cr));
1420 if (!get_client_dbr(ua, &cr, JT_BACKUP_RESTORE)) {
1423 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1428 memset(&fsr, 0, sizeof(fsr));
1429 i = find_arg_with_value(ua, "FileSet");
1431 if (i >= 0 && is_name_valid(ua->argv[i], &ua->errmsg)) {
1432 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1433 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1434 ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1435 db_strerror(ua->db));
1438 } else if (i >= 0) { /* name is invalid */
1439 ua->error_msg(_("FileSet argument: %s\n"), ua->errmsg);
1442 if (i < 0) { /* fileset not found */
1443 edit_int64(cr.ClientId, ed1);
1444 Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1445 start_prompt(ua, _("The defined FileSet resources are:\n"));
1446 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1447 ua->error_msg("%s\n", db_strerror(ua->db));
1449 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1450 fileset_name, sizeof(fileset_name)) < 0) {
1451 ua->error_msg(_("No FileSet found for client \"%s\".\n"), cr.Name);
1455 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1456 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1457 ua->warning_msg(_("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1458 ua->send_msg(_("This probably means you modified the FileSet.\n"
1459 "Continuing anyway.\n"));
1463 /* If Pool specified, add PoolId specification */
1467 memset(&pr, 0, sizeof(pr));
1468 bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1469 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1470 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
1471 edit_int64(pr.PoolId, ed1));
1473 ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1477 /* Find JobId of last Full backup for this client, fileset */
1478 edit_int64(cr.ClientId, ed1);
1479 Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1481 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1482 ua->error_msg("%s\n", db_strerror(ua->db));
1486 /* Find all Volumes used by that JobId */
1487 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1488 ua->error_msg("%s\n", db_strerror(ua->db));
1492 /* Note, this is needed because I don't seem to get the callback
1493 * from the call just above.
1496 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1497 ua->warning_msg("%s\n", db_strerror(ua->db));
1499 if (rx->JobTDate == 0) {
1500 ua->error_msg(_("No Full backup before %s found.\n"), date);
1504 /* Now find most recent Differental Job after Full save, if any */
1505 Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1506 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1507 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1508 ua->warning_msg("%s\n", db_strerror(ua->db));
1510 /* Now update JobTDate to look into Differental, if any */
1512 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1513 ua->warning_msg("%s\n", db_strerror(ua->db));
1515 if (rx->JobTDate == 0) {
1516 ua->error_msg(_("No Full backup before %s found.\n"), date);
1520 /* Now find all Incremental Jobs after Full/dif save */
1521 Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1522 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1523 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1524 ua->warning_msg("%s\n", db_strerror(ua->db));
1527 /* Get the JobIds from that list */
1528 rx->last_jobid[0] = rx->JobIds[0] = 0;
1530 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1531 ua->warning_msg("%s\n", db_strerror(ua->db));
1534 if (rx->JobIds[0] != 0) {
1535 if (find_arg(ua, NT_("copies")) > 0) {
1536 /* Display a list of all copies */
1537 db_list_copies_records(ua->jcr, ua->db, 0, rx->JobIds,
1538 prtit, ua, HORZ_LIST);
1540 /* Display a list of Jobs selected for this restore */
1541 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1,HORZ_LIST);
1545 ua->warning_msg(_("No jobs found.\n"));
1549 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1550 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1554 static int restore_count_handler(void *ctx, int num_fields, char **row)
1556 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1557 rx->JobId = str_to_int64(row[0]);
1563 * Callback handler to get JobId and FileIndex for files
1564 * can insert more than one depending on the caller.
1566 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1568 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1569 JobId_t JobId = str_to_int64(row[0]);
1571 Dmsg3(200, "JobId=%s JobIds=%s FileIndex=%s\n", row[0], rx->JobIds, row[1]);
1573 /* New JobId, add it to JobIds
1574 * The list is sorted by JobId, so we need a cache for the previous value
1576 * It will permit to find restore objects to send during the restore
1578 if (rx->JobId != JobId) {
1580 pm_strcat(rx->JobIds, ",");
1582 pm_strcat(rx->JobIds, row[0]);
1586 add_findex(rx->bsr_list, rx->JobId, str_to_int64(row[1]));
1588 rx->selected_files++;
1593 * Callback handler make list of JobIds
1595 static int jobid_handler(void *ctx, int num_fields, char **row)
1597 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1599 if (strcmp(rx->last_jobid, row[0]) == 0) {
1600 return 0; /* duplicate id */
1602 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1603 if (rx->JobIds[0] != 0) {
1604 pm_strcat(rx->JobIds, ",");
1606 pm_strcat(rx->JobIds, row[0]);
1612 * Callback handler to pickup last Full backup JobTDate
1614 static int last_full_handler(void *ctx, int num_fields, char **row)
1616 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1618 rx->JobTDate = str_to_int64(row[1]);
1623 * Callback handler build FileSet name prompt list
1625 static int fileset_handler(void *ctx, int num_fields, char **row)
1627 /* row[0] = FileSet (name) */
1629 add_prompt((UAContext *)ctx, row[0]);
1635 * Free names in the list
1637 static void free_name_list(NAME_LIST *name_list)
1639 for (int i=0; i < name_list->num_ids; i++) {
1640 free(name_list->name[i]);
1642 bfree_and_null(name_list->name);
1643 name_list->max_ids = 0;
1644 name_list->num_ids = 0;
1647 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType)
1652 Dmsg1(200, "Already have store=%s\n", rx.store->name());
1656 * Try looking up Storage by name
1659 foreach_res(store, R_STORAGE) {
1660 if (strcmp(Storage, store->name()) == 0) {
1661 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1670 /* Check if an explicit storage resource is given */
1672 int i = find_arg_with_value(ua, "storage");
1674 store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1675 if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1679 if (store && (store != rx.store)) {
1680 ua->info_msg(_("\nWarning Storage is overridden by \"%s\" on the command line.\n"),
1683 bstrncpy(rx.RestoreMediaType, MediaType, sizeof(rx.RestoreMediaType));
1684 if (strcmp(MediaType, store->media_type) != 0) {
1685 ua->info_msg(_("This may not work because of two different MediaTypes:\n"
1686 " Storage MediaType=\"%s\"\n"
1687 " Volume MediaType=\"%s\".\n\n"),
1688 store->media_type, MediaType);
1690 Dmsg2(200, "Set store=%s MediaType=%s\n", rx.store->name(), rx.RestoreMediaType);
1695 /* If no storage resource, try to find one from MediaType */
1698 foreach_res(store, R_STORAGE) {
1699 if (strcmp(MediaType, store->media_type) == 0) {
1700 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1702 Dmsg1(200, "Set store=%s\n", rx.store->name());
1703 if (Storage == NULL || Storage[0] == 0) {
1704 ua->warning_msg(_("Using Storage \"%s\" from MediaType \"%s\".\n"),
1705 store->name(), MediaType);
1707 ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1708 Storage, store->name(), MediaType);
1716 ua->warning_msg(_("\nUnable to find Storage resource for\n"
1717 "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1720 /* Take command line arg, or ask user if none */
1721 rx.store = get_storage_resource(ua, false /* don't use default */);
1723 Dmsg1(200, "Set store=%s\n", rx.store->name());