]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
Correct editing in Verify output that caused a seg fault
[bacula/bacula] / bacula / src / dird / ua_restore.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2002-2007 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version two of the GNU General Public
10    License as published by the Free Software Foundation plus additions
11    that are listed in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of John Walker.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 /*
29  *
30  *   Bacula Director -- User Agent Database restore Command
31  *      Creates a bootstrap file for restoring files and
32  *      starts the restore job.
33  *
34  *      Tree handling routines split into ua_tree.c July MMIII.
35  *      BSR (bootstrap record) handling routines split into
36  *        bsr.c July MMIII
37  *
38  *     Kern Sibbald, July MMII
39  *
40  *   Version $Id$
41  */
42
43
44 #include "bacula.h"
45 #include "dird.h"
46
47 /* Imported functions */
48 extern void print_bsr(UAContext *ua, RBSR *bsr);
49
50
51
52 /* Forward referenced functions */
53 static int last_full_handler(void *ctx, int num_fields, char **row);
54 static int jobid_handler(void *ctx, int num_fields, char **row);
55 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx);
56 static int fileset_handler(void *ctx, int num_fields, char **row);
57 static void free_name_list(NAME_LIST *name_list);
58 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date);
59 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx);
60 static void free_rx(RESTORE_CTX *rx);
61 static void split_path_and_filename(RESTORE_CTX *rx, char *fname);
62 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row);
63 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
64                                          char *date);
65 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
66                                         char *date);
67 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir);
68 static int get_client_name(UAContext *ua, RESTORE_CTX *rx);
69 static int get_date(UAContext *ua, char *date, int date_len);
70 static int restore_count_handler(void *ctx, int num_fields, char **row);
71 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table);
72
73 /*
74  *   Restore files
75  *
76  */
77 int restore_cmd(UAContext *ua, const char *cmd)
78 {
79    RESTORE_CTX rx;                    /* restore context */
80    JOB *job;
81    int i;
82    JCR *jcr = ua->jcr;
83    char *escaped_bsr_name = NULL;
84    char *escaped_where_name = NULL;
85    char *strip_prefix, *add_prefix, *add_suffix, *regexp;
86    strip_prefix = add_prefix = add_suffix = regexp = NULL;
87
88    memset(&rx, 0, sizeof(rx));
89    rx.path = get_pool_memory(PM_FNAME);
90    rx.fname = get_pool_memory(PM_FNAME);
91    rx.JobIds = get_pool_memory(PM_FNAME);
92    rx.query = get_pool_memory(PM_FNAME);
93    rx.bsr = new_bsr();
94
95    i = find_arg_with_value(ua, "where");
96    if (i >= 0) {
97       rx.where = ua->argv[i];
98    }
99
100    i = find_arg_with_value(ua, "strip_prefix");
101    if (i >= 0) {
102       strip_prefix = ua->argv[i];
103    }
104
105    i = find_arg_with_value(ua, "add_prefix");
106    if (i >= 0) {
107       add_prefix = ua->argv[i];
108    }
109
110    i = find_arg_with_value(ua, "add_suffix");
111    if (i >= 0) {
112       add_suffix = ua->argv[i];
113    }
114
115    i = find_arg_with_value(ua, "regexwhere");
116    if (i >= 0) {
117       rx.RegexWhere = ua->argv[i];
118    }
119
120    if (strip_prefix || add_suffix || add_prefix) {
121       int len = bregexp_get_build_where_size(strip_prefix, add_prefix, add_suffix);
122       regexp = (char *) bmalloc (len * sizeof(char));
123
124       bregexp_build_where(regexp, len, strip_prefix, add_prefix, add_suffix);
125       rx.RegexWhere = regexp;
126    }
127
128    /* TODO: add acl for regexwhere ? */
129
130    if (rx.RegexWhere) {
131       if (!acl_access_ok(ua, Where_ACL, rx.RegexWhere)) {
132          ua->error_msg(_("\"RegexWhere\" specification not authorized.\n"));
133          goto bail_out;
134       }
135    }
136
137    if (rx.where) {
138       if (!acl_access_ok(ua, Where_ACL, rx.where)) {
139          ua->error_msg(_("\"where\" specification not authorized.\n"));
140          goto bail_out;
141       }
142    }
143
144    if (!open_client_db(ua)) {
145       goto bail_out;
146    }
147
148    /* Ensure there is at least one Restore Job */
149    LockRes();
150    foreach_res(job, R_JOB) {
151       if (job->JobType == JT_RESTORE) {
152          if (!rx.restore_job) {
153             rx.restore_job = job;
154          }
155          rx.restore_jobs++;
156       }
157    }
158    UnlockRes();
159    if (!rx.restore_jobs) {
160       ua->error_msg(_(
161          "No Restore Job Resource found in bacula-dir.conf.\n"
162          "You must create at least one before running this command.\n"));
163       goto bail_out;
164    }
165
166    /*
167     * Request user to select JobIds or files by various different methods
168     *  last 20 jobs, where File saved, most recent backup, ...
169     *  In the end, a list of files are pumped into
170     *  add_findex()
171     */
172    switch (user_select_jobids_or_files(ua, &rx)) {
173    case 0:                            /* error */
174       goto bail_out;
175    case 1:                            /* selected by jobid */
176       if (!build_directory_tree(ua, &rx)) {
177          ua->send_msg(_("Restore not done.\n"));
178          goto bail_out;
179       }
180       break;
181    case 2:                            /* selected by filename, no tree needed */
182       break;
183    }
184
185    if (rx.bsr->JobId) {
186       uint32_t selected_files;
187       char ed1[50];
188       if (!complete_bsr(ua, rx.bsr)) {   /* find Vol, SessId, SessTime from JobIds */
189          ua->error_msg(_("Unable to construct a valid BSR. Cannot continue.\n"));
190          goto bail_out;
191       }
192       if (!(selected_files = write_bsr_file(ua, rx))) {
193          ua->warning_msg(_("No files selected to be restored.\n"));
194          goto bail_out;
195       }
196       /* If no count of files, use bsr generated value (often wrong) */
197       if (rx.selected_files == 0) {
198          rx.selected_files = selected_files;
199       }
200       if (rx.selected_files==1) {
201          ua->info_msg(_("\n1 file selected to be restored.\n\n"));
202       }
203       else {
204          ua->info_msg(_("\n%s files selected to be restored.\n\n"), 
205             edit_uint64_with_commas(rx.selected_files, ed1));
206       }
207    } else {
208       ua->warning_msg(_("No files selected to be restored.\n"));
209       goto bail_out;
210    }
211
212    if (rx.restore_jobs == 1) {
213       job = rx.restore_job;
214    } else {
215       job = select_restore_job_resource(ua);
216    }
217    if (!job) {
218       goto bail_out;
219    }
220
221    get_client_name(ua, &rx);
222    if (!rx.ClientName) {
223       ua->error_msg(_("No Client resource found!\n"));
224       goto bail_out;
225    }
226
227    escaped_bsr_name = escape_filename(jcr->RestoreBootstrap);
228
229    /* Build run command */
230    if (rx.RegexWhere) {
231       escaped_where_name = escape_filename(rx.RegexWhere);
232       Mmsg(ua->cmd,
233           "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s\""
234           " regexwhere=\"%s\" files=%d catalog=\"%s\"",
235           job->name(), rx.ClientName, rx.store?rx.store->name():"",
236           escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
237           escaped_where_name ? escaped_where_name : rx.RegexWhere,
238           rx.selected_files, ua->catalog->name());
239
240    } else if (rx.where) {
241       escaped_where_name = escape_filename(rx.where);
242       Mmsg(ua->cmd,
243           "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s\""
244           " where=\"%s\" files=%d catalog=\"%s\"",
245           job->name(), rx.ClientName, rx.store?rx.store->name():"",
246           escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
247           escaped_where_name ? escaped_where_name : rx.where,
248           rx.selected_files, ua->catalog->name());
249
250    } else {
251       Mmsg(ua->cmd,
252           "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s\""
253           " files=%d catalog=\"%s\"",
254           job->name(), rx.ClientName, rx.store?rx.store->name():"",
255           escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
256           rx.selected_files, ua->catalog->name());
257    }
258
259    if (escaped_bsr_name != NULL) {
260       bfree(escaped_bsr_name);
261    }
262
263    if (escaped_where_name != NULL) {
264       bfree(escaped_where_name);
265    }
266    
267    if (regexp) {
268       bfree(regexp);
269    }
270
271    if (find_arg(ua, NT_("yes")) > 0) {
272       pm_strcat(ua->cmd, " yes");    /* pass it on to the run command */
273    }
274    Dmsg1(200, "Submitting: %s\n", ua->cmd);
275    parse_ua_args(ua);
276    run_cmd(ua, ua->cmd);
277    free_rx(&rx);
278    return 1;
279
280 bail_out:
281    if (escaped_bsr_name != NULL) {
282       bfree(escaped_bsr_name);
283    }
284
285    if (escaped_where_name != NULL) {
286       bfree(escaped_where_name);
287    }
288
289    if (regexp) {
290       bfree(regexp);
291    }
292
293    free_rx(&rx);
294    return 0;
295
296 }
297
298 static void free_rx(RESTORE_CTX *rx)
299 {
300    free_bsr(rx->bsr);
301    rx->bsr = NULL;
302    if (rx->JobIds) {
303       free_pool_memory(rx->JobIds);
304       rx->JobIds = NULL;
305    }
306    if (rx->fname) {
307       free_pool_memory(rx->fname);
308       rx->fname = NULL;
309    }
310    if (rx->path) {
311       free_pool_memory(rx->path);
312       rx->path = NULL;
313    }
314    if (rx->query) {
315       free_pool_memory(rx->query);
316       rx->query = NULL;
317    }
318    free_name_list(&rx->name_list);
319 }
320
321 static bool has_value(UAContext *ua, int i)
322 {
323    if (!ua->argv[i]) {
324       ua->error_msg(_("Missing value for keyword: %s\n"), ua->argk[i]);
325       return false;
326    }
327    return true;
328 }
329
330 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
331 {
332    /* If no client name specified yet, get it now */
333    if (!rx->ClientName[0]) {
334       CLIENT_DBR cr;
335       /* try command line argument */
336       int i = find_arg_with_value(ua, NT_("client"));
337       if (i >= 0) {
338          if (!has_value(ua, i)) {
339             return 0;
340          }
341          bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
342          return 1;
343       }
344       memset(&cr, 0, sizeof(cr));
345       if (!get_client_dbr(ua, &cr)) {
346          return 0;
347       }
348       bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
349    }
350    return 1;
351 }
352
353
354 /*
355  * The first step in the restore process is for the user to
356  *  select a list of JobIds from which he will subsequently
357  *  select which files are to be restored.
358  *
359  *  Returns:  2  if filename list made
360  *            1  if jobid list made
361  *            0  on error
362  */
363 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
364 {
365    char *p;
366    char date[MAX_TIME_LENGTH];
367    bool have_date = false;
368    /* Include current second if using current time */
369    utime_t now = time(NULL) + 1;
370    JobId_t JobId;
371    JOB_DBR jr = { (JobId_t)-1 };
372    bool done = false;
373    int i, j;
374    const char *list[] = {
375       _("List last 20 Jobs run"),
376       _("List Jobs where a given File is saved"),
377       _("Enter list of comma separated JobIds to select"),
378       _("Enter SQL list command"),
379       _("Select the most recent backup for a client"),
380       _("Select backup for a client before a specified time"),
381       _("Enter a list of files to restore"),
382       _("Enter a list of files to restore before a specified time"),
383       _("Find the JobIds of the most recent backup for a client"),
384       _("Find the JobIds for a backup for a client before a specified time"),
385       _("Enter a list of directories to restore for found JobIds"),
386       _("Cancel"),
387       NULL };
388
389    const char *kw[] = {
390        /* These keywords are handled in a for loop */
391       "jobid",       /* 0 */
392       "current",     /* 1 */
393       "before",      /* 2 */
394       "file",        /* 3 */
395       "directory",   /* 4 */
396       "select",      /* 5 */
397       "pool",        /* 6 */
398       "all",         /* 7 */
399
400       /* The keyword below are handled by individual arg lookups */
401       "client",       /* 8 */
402       "storage",      /* 9 */
403       "fileset",      /* 10 */
404       "where",        /* 11 */
405       "yes",          /* 12 */
406       "bootstrap",    /* 13 */
407       "done",         /* 14 */
408       "strip_prefix", /* 15 */
409       "add_prefix",   /* 16 */
410       "add_suffix",   /* 17 */
411       "regexwhere",   /* 18 */
412       NULL
413    };
414
415    *rx->JobIds = 0;
416
417    for (i=1; i<ua->argc; i++) {       /* loop through arguments */
418       bool found_kw = false;
419       for (j=0; kw[j]; j++) {         /* loop through keywords */
420          if (strcasecmp(kw[j], ua->argk[i]) == 0) {
421             found_kw = true;
422             break;
423          }
424       }
425       if (!found_kw) {
426          ua->error_msg(_("Unknown keyword: %s\n"), ua->argk[i]);
427          return 0;
428       }
429       /* Found keyword in kw[] list, process it */
430       switch (j) {
431       case 0:                            /* jobid */
432          if (!has_value(ua, i)) {
433             return 0;
434          }
435          if (*rx->JobIds != 0) {
436             pm_strcat(rx->JobIds, ",");
437          }
438          pm_strcat(rx->JobIds, ua->argv[i]);
439          done = true;
440          break;
441       case 1:                            /* current */
442          /*
443           * Note, we add one second here just to include any job
444           *  that may have finished within the current second,
445           *  which happens a lot in scripting small jobs.
446           */
447          bstrutime(date, sizeof(date), now);
448          have_date = true;
449          break;
450       case 2:                            /* before */
451          if (!has_value(ua, i)) {
452             return 0;
453          }
454          if (str_to_utime(ua->argv[i]) == 0) {
455             ua->error_msg(_("Improper date format: %s\n"), ua->argv[i]);
456             return 0;
457          }
458          bstrncpy(date, ua->argv[i], sizeof(date));
459          have_date = true;
460          break;
461       case 3:                            /* file */
462       case 4:                            /* dir */
463          if (!has_value(ua, i)) {
464             return 0;
465          }
466          if (!have_date) {
467             bstrutime(date, sizeof(date), now);
468          }
469          if (!get_client_name(ua, rx)) {
470             return 0;
471          }
472          pm_strcpy(ua->cmd, ua->argv[i]);
473          insert_one_file_or_dir(ua, rx, date, j==4);
474          return 2;
475       case 5:                            /* select */
476          if (!have_date) {
477             bstrutime(date, sizeof(date), now);
478          }
479          if (!select_backups_before_date(ua, rx, date)) {
480             return 0;
481          }
482          done = true;
483          break;
484       case 6:                            /* pool specified */
485          if (!has_value(ua, i)) {
486             return 0;
487          }
488          rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
489          if (!rx->pool) {
490             ua->error_msg(_("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
491             return 0;
492          }
493          if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
494             rx->pool = NULL;
495             ua->error_msg(_("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
496             return 0;
497          }
498          break;
499       case 7:                         /* all specified */
500          rx->all = true;
501          break;
502       /*
503        * All keywords 7 or greater are ignored or handled by a select prompt
504        */
505       default:
506          break;
507       }
508    }
509
510    if (!done) {
511       ua->send_msg(_("\nFirst you select one or more JobIds that contain files\n"
512                   "to be restored. You will be presented several methods\n"
513                   "of specifying the JobIds. Then you will be allowed to\n"
514                   "select which files from those JobIds are to be restored.\n\n"));
515    }
516
517    /* If choice not already made above, prompt */
518    for ( ; !done; ) {
519       char *fname;
520       int len;
521       bool gui_save;
522
523       start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
524       for (int i=0; list[i]; i++) {
525          add_prompt(ua, list[i]);
526       }
527       done = true;
528       switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
529       case -1:                        /* error or cancel */
530          return 0;
531       case 0:                         /* list last 20 Jobs run */
532          if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
533             ua->error_msg(_("SQL query not authorized.\n"));
534             return 0;
535          }
536          gui_save = ua->jcr->gui;
537          ua->jcr->gui = true;
538          db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
539          ua->jcr->gui = gui_save;
540          done = false;
541          break;
542       case 1:                         /* list where a file is saved */
543          if (!get_client_name(ua, rx)) {
544             return 0;
545          }
546          if (!get_cmd(ua, _("Enter Filename (no path):"))) {
547             return 0;
548          }
549          len = strlen(ua->cmd);
550          fname = (char *)malloc(len * 2 + 1);
551          db_escape_string(fname, ua->cmd, len);
552          Mmsg(rx->query, uar_file, rx->ClientName, fname);
553          free(fname);
554          gui_save = ua->jcr->gui;
555          ua->jcr->gui = true;
556          db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
557          ua->jcr->gui = gui_save;
558          done = false;
559          break;
560       case 2:                         /* enter a list of JobIds */
561          if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
562             return 0;
563          }
564          pm_strcpy(rx->JobIds, ua->cmd);
565          break;
566       case 3:                         /* Enter an SQL list command */
567          if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
568             ua->error_msg(_("SQL query not authorized.\n"));
569             return 0;
570          }
571          if (!get_cmd(ua, _("Enter SQL list command: "))) {
572             return 0;
573          }
574          gui_save = ua->jcr->gui;
575          ua->jcr->gui = true;
576          db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
577          ua->jcr->gui = gui_save;
578          done = false;
579          break;
580       case 4:                         /* Select the most recent backups */
581          bstrutime(date, sizeof(date), now);
582          if (!select_backups_before_date(ua, rx, date)) {
583             return 0;
584          }
585          break;
586       case 5:                         /* select backup at specified time */
587          if (!get_date(ua, date, sizeof(date))) {
588             return 0;
589          }
590          if (!select_backups_before_date(ua, rx, date)) {
591             return 0;
592          }
593          break;
594       case 6:                         /* Enter files */
595          bstrutime(date, sizeof(date), now);
596          if (!get_client_name(ua, rx)) {
597             return 0;
598          }
599          ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
600                         "containing a list of file names with paths, and terminate\n"
601                         "them with a blank line.\n"));
602          for ( ;; ) {
603             if (!get_cmd(ua, _("Enter full filename: "))) {
604                return 0;
605             }
606             len = strlen(ua->cmd);
607             if (len == 0) {
608                break;
609             }
610             insert_one_file_or_dir(ua, rx, date, false);
611          }
612          return 2;
613        case 7:                        /* enter files backed up before specified time */
614          if (!get_date(ua, date, sizeof(date))) {
615             return 0;
616          }
617          if (!get_client_name(ua, rx)) {
618             return 0;
619          }
620          ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
621                         "containing a list of file names with paths, and terminate\n"
622                         "them with a blank line.\n"));
623          for ( ;; ) {
624             if (!get_cmd(ua, _("Enter full filename: "))) {
625                return 0;
626             }
627             len = strlen(ua->cmd);
628             if (len == 0) {
629                break;
630             }
631             insert_one_file_or_dir(ua, rx, date, false);
632          }
633          return 2;
634
635       case 8:                         /* Find JobIds for current backup */
636          bstrutime(date, sizeof(date), now);
637          if (!select_backups_before_date(ua, rx, date)) {
638             return 0;
639          }
640          done = false;
641          break;
642
643       case 9:                         /* Find JobIds for give date */
644          if (!get_date(ua, date, sizeof(date))) {
645             return 0;
646          }
647          if (!select_backups_before_date(ua, rx, date)) {
648             return 0;
649          }
650          done = false;
651          break;
652
653       case 10:                        /* Enter directories */
654          if (*rx->JobIds != 0) {
655             ua->send_msg(_("You have already selected the following JobIds: %s\n"),
656                rx->JobIds);
657          } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
658             if (*rx->JobIds != 0 && *ua->cmd) {
659                pm_strcat(rx->JobIds, ",");
660             }
661             pm_strcat(rx->JobIds, ua->cmd);
662          }
663          if (*rx->JobIds == 0 || *rx->JobIds == '.') {
664             return 0;                 /* nothing entered, return */
665          }
666          bstrutime(date, sizeof(date), now);
667          if (!get_client_name(ua, rx)) {
668             return 0;
669          }
670          ua->send_msg(_("Enter full directory names or start the name\n"
671                         "with a < to indicate it is a filename containing a list\n"
672                         "of directories and terminate them with a blank line.\n"));
673          for ( ;; ) {
674             if (!get_cmd(ua, _("Enter directory name: "))) {
675                return 0;
676             }
677             len = strlen(ua->cmd);
678             if (len == 0) {
679                break;
680             }
681             /* Add trailing slash to end of directory names */
682             if (ua->cmd[0] != '<' && !IsPathSeparator(ua->cmd[len-1])) {
683                strcat(ua->cmd, "/");
684             }
685             insert_one_file_or_dir(ua, rx, date, true);
686          }
687          return 2;
688
689       case 11:                        /* Cancel or quit */
690          return 0;
691       }
692    }
693
694    POOLMEM *JobIds = get_pool_memory(PM_FNAME);
695    *JobIds = 0;
696    rx->TotalFiles = 0;
697    /*        
698     * Find total number of files to be restored, and filter the JobId
699     *  list to contain only ones permitted by the ACL conditions.
700     */
701    for (p=rx->JobIds; ; ) {
702       char ed1[50];
703       int stat = get_next_jobid_from_list(&p, &JobId);
704       if (stat < 0) {
705          ua->error_msg(_("Invalid JobId in list.\n"));
706          free_pool_memory(JobIds);
707          return 0;
708       }
709       if (stat == 0) {
710          break;
711       }
712       if (jr.JobId == JobId) {
713          continue;                    /* duplicate of last JobId */
714       }
715       memset(&jr, 0, sizeof(JOB_DBR));
716       jr.JobId = JobId;
717       if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
718          ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
719             edit_int64(JobId, ed1), db_strerror(ua->db));
720          free_pool_memory(JobIds);
721          return 0;
722       }
723       if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
724          ua->error_msg(_("Access to JobId=%s (Job \"%s\") not authorized. Not selected.\n"),
725             edit_int64(JobId, ed1), jr.Name);
726          continue;
727       }
728       if (*JobIds != 0) {
729          pm_strcat(JobIds, ",");
730       }
731       pm_strcat(JobIds, edit_int64(JobId, ed1));
732       rx->TotalFiles += jr.JobFiles;
733    }
734    free_pool_memory(rx->JobIds);
735    rx->JobIds = JobIds;               /* Set ACL filtered list */
736    if (*rx->JobIds == 0) {
737       ua->warning_msg(_("No Jobs selected.\n"));
738       return 0;
739    }
740    if (strchr(rx->JobIds,',')) {
741       ua->info_msg(_("You have selected the following JobIds: %s\n"), rx->JobIds);
742    } else {
743       ua->info_msg(_("You have selected the following JobId: %s\n"), rx->JobIds);
744    }
745    return 1;
746 }
747
748 /*
749  * Get date from user
750  */
751 static int get_date(UAContext *ua, char *date, int date_len)
752 {
753    ua->send_msg(_("The restored files will the most current backup\n"
754                   "BEFORE the date you specify below.\n\n"));
755    for ( ;; ) {
756       if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
757          return 0;
758       }
759       if (str_to_utime(ua->cmd) != 0) {
760          break;
761       }
762       ua->error_msg(_("Improper date format.\n"));
763    }
764    bstrncpy(date, ua->cmd, date_len);
765    return 1;
766 }
767
768 /*
769  * Insert a single file, or read a list of files from a file
770  */
771 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
772 {
773    FILE *ffd;
774    char file[5000];
775    char *p = ua->cmd;
776    int line = 0;
777
778    switch (*p) {
779    case '<':
780       p++;
781       if ((ffd = fopen(p, "rb")) == NULL) {
782          berrno be;
783          ua->error_msg(_("Cannot open file %s: ERR=%s\n"),
784             p, be.strerror());
785          break;
786       }
787       while (fgets(file, sizeof(file), ffd)) {
788          line++;
789          if (dir) {
790             if (!insert_dir_into_findex_list(ua, rx, file, date)) {
791                ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
792             }
793          } else {
794             if (!insert_file_into_findex_list(ua, rx, file, date)) {
795                ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
796             }
797          }
798       }
799       fclose(ffd);
800       break;
801    case '?':
802       p++;
803       insert_table_into_findex_list(ua, rx, p);
804       break;
805    default:
806       if (dir) {
807          insert_dir_into_findex_list(ua, rx, ua->cmd, date);
808       } else {
809          insert_file_into_findex_list(ua, rx, ua->cmd, date);
810       }
811       break;
812    }
813 }
814
815 /*
816  * For a given file (path+filename), split into path and file, then
817  *   lookup the most recent backup in the catalog to get the JobId
818  *   and FileIndex, then insert them into the findex list.
819  */
820 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
821                                         char *date)
822 {
823    strip_trailing_newline(file);
824    split_path_and_filename(rx, file);
825    if (*rx->JobIds == 0) {
826       Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname, 
827            rx->ClientName);
828    } else {
829       Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
830            rx->path, rx->fname, rx->ClientName);
831    }
832    rx->found = false;
833    /* Find and insert jobid and File Index */
834    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
835       ua->error_msg(_("Query failed: %s. ERR=%s\n"),
836          rx->query, db_strerror(ua->db));
837    }
838    if (!rx->found) {
839       ua->error_msg(_("No database record found for: %s\n"), file);
840 //    ua->error_msg("Query=%s\n", rx->query);
841       return true;
842    }
843    return true;
844 }
845
846 /*
847  * For a given path lookup the most recent backup in the catalog
848  * to get the JobId and FileIndexes of all files in that directory.
849  */
850 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
851                                         char *date)
852 {
853    strip_trailing_junk(dir);
854    if (*rx->JobIds == 0) {
855       ua->error_msg(_("No JobId specified cannot continue.\n"));
856       return false;
857    } else {
858       Mmsg(rx->query, uar_jobid_fileindex_from_dir, rx->JobIds, 
859            dir, rx->ClientName);
860    }
861    rx->found = false;
862    /* Find and insert jobid and File Index */
863    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
864       ua->error_msg(_("Query failed: %s. ERR=%s\n"),
865          rx->query, db_strerror(ua->db));
866    }
867    if (!rx->found) {
868       ua->error_msg(_("No database record found for: %s\n"), dir);
869       return true;
870    }
871    return true;
872 }
873
874 /*
875  * Get the JobId and FileIndexes of all files in the specified table
876  */
877 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
878 {
879    strip_trailing_junk(table);
880    Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
881
882    rx->found = false;
883    /* Find and insert jobid and File Index */
884    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
885       ua->error_msg(_("Query failed: %s. ERR=%s\n"),
886          rx->query, db_strerror(ua->db));
887    }
888    if (!rx->found) {
889       ua->error_msg(_("No table found: %s\n"), table);
890       return true;
891    }
892    return true;
893 }
894
895 static void split_path_and_filename(RESTORE_CTX *rx, char *name)
896 {
897    char *p, *f;
898
899    /* Find path without the filename.
900     * I.e. everything after the last / is a "filename".
901     * OK, maybe it is a directory name, but we treat it like
902     * a filename. If we don't find a / then the whole name
903     * must be a path name (e.g. c:).
904     */
905    for (p=f=name; *p; p++) {
906       if (IsPathSeparator(*p)) {
907          f = p;                       /* set pos of last slash */
908       }
909    }
910    if (IsPathSeparator(*f)) {         /* did we find a slash? */
911       f++;                            /* yes, point to filename */
912    } else {                           /* no, whole thing must be path name */
913       f = p;
914    }
915
916    /* If filename doesn't exist (i.e. root directory), we
917     * simply create a blank name consisting of a single
918     * space. This makes handling zero length filenames
919     * easier.
920     */
921    rx->fnl = p - f;
922    if (rx->fnl > 0) {
923       rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
924       memcpy(rx->fname, f, rx->fnl);    /* copy filename */
925       rx->fname[rx->fnl] = 0;
926    } else {
927       rx->fname[0] = 0;
928       rx->fnl = 0;
929    }
930
931    rx->pnl = f - name;
932    if (rx->pnl > 0) {
933       rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
934       memcpy(rx->path, name, rx->pnl);
935       rx->path[rx->pnl] = 0;
936    } else {
937       rx->path[0] = 0;
938       rx->pnl = 0;
939    }
940
941    Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
942 }
943
944 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
945 {
946    TREE_CTX tree;
947    JobId_t JobId, last_JobId;
948    char *p;
949    bool OK = true;
950    char ed1[50];
951
952    memset(&tree, 0, sizeof(TREE_CTX));
953    /*
954     * Build the directory tree containing JobIds user selected
955     */
956    tree.root = new_tree(rx->TotalFiles);
957    tree.ua = ua;
958    tree.all = rx->all;
959    last_JobId = 0;
960    /*
961     * For display purposes, the same JobId, with different volumes may
962     * appear more than once, however, we only insert it once.
963     */
964    int items = 0;
965    p = rx->JobIds;
966    tree.FileEstimate = 0;
967    if (get_next_jobid_from_list(&p, &JobId) > 0) {
968       /* Use first JobId as estimate of the number of files to restore */
969       Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
970       if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
971          ua->error_msg("%s\n", db_strerror(ua->db));
972       }
973       if (rx->found) {
974          /* Add about 25% more than this job for over estimate */
975          tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
976          tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
977       }
978    }
979    for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
980       char ed1[50];
981
982       if (JobId == last_JobId) {
983          continue;                    /* eliminate duplicate JobIds */
984       }
985       last_JobId = JobId;
986       ua->info_msg(_("\nBuilding directory tree for JobId %s ...  "), 
987          edit_int64(JobId, ed1));
988       items++;
989       /*
990        * Find files for this JobId and insert them in the tree
991        */
992       Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
993       if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
994          ua->error_msg("%s", db_strerror(ua->db));
995       }
996    }
997    if (tree.FileCount == 0) {
998       ua->send_msg(_("\nThere were no files inserted into the tree, so file selection\n"
999          "is not possible.Most likely your retention policy pruned the files\n"));
1000       if (!get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
1001          OK = false;
1002       } else {
1003          last_JobId = 0;
1004          for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1005              if (JobId == last_JobId) {
1006                 continue;                    /* eliminate duplicate JobIds */
1007              }
1008              add_findex_all(rx->bsr, JobId);
1009           }
1010           OK = true;
1011       }
1012    } else {
1013       char ec1[50];
1014       if (items==1) {
1015          if (tree.all) {
1016             ua->info_msg(_("\n1 Job, %s files inserted into the tree and marked for extraction.\n"),
1017               edit_uint64_with_commas(tree.FileCount, ec1));
1018          }
1019          else {
1020             ua->info_msg(_("\n1 Job, %s files inserted into the tree.\n"),
1021               edit_uint64_with_commas(tree.FileCount, ec1));
1022          }
1023       }
1024       else {
1025          if (tree.all) {
1026             ua->info_msg(_("\n%d Jobs, %s files inserted into the tree and marked for extraction.\n"),
1027               items, edit_uint64_with_commas(tree.FileCount, ec1));
1028          }
1029          else {
1030             ua->info_msg(_("\n%d Jobs, %s files inserted into the tree.\n"),
1031               items, edit_uint64_with_commas(tree.FileCount, ec1));
1032          }
1033       }
1034
1035       if (find_arg(ua, NT_("done")) < 0) {
1036          /* Let the user interact in selecting which files to restore */
1037          OK = user_select_files_from_tree(&tree);
1038       }
1039
1040       /*
1041        * Walk down through the tree finding all files marked to be
1042        *  extracted making a bootstrap file.
1043        */
1044       if (OK) {
1045          for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
1046             Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
1047             if (node->extract || node->extract_dir) {
1048                Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
1049                add_findex(rx->bsr, node->JobId, node->FileIndex);
1050                if (node->extract && node->type != TN_NEWDIR) {
1051                   rx->selected_files++;  /* count only saved files */
1052                }
1053             }
1054          }
1055       }
1056    }
1057
1058    free_tree(tree.root);              /* free the directory tree */
1059    return OK;
1060 }
1061
1062
1063 /*
1064  * This routine is used to get the current backup or a backup
1065  *   before the specified date.
1066  */
1067 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
1068 {
1069    bool ok = false;
1070    FILESET_DBR fsr;
1071    CLIENT_DBR cr;
1072    char fileset_name[MAX_NAME_LENGTH];
1073    char ed1[50], ed2[50];
1074    char pool_select[MAX_NAME_LENGTH];
1075    int i;
1076
1077
1078    /* Create temp tables */
1079    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1080    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1081    if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
1082       ua->error_msg("%s\n", db_strerror(ua->db));
1083    }
1084    if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
1085       ua->error_msg("%s\n", db_strerror(ua->db));
1086    }
1087    /*
1088     * Select Client from the Catalog
1089     */
1090    memset(&cr, 0, sizeof(cr));
1091    if (!get_client_dbr(ua, &cr)) {
1092       goto bail_out;
1093    }
1094    bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1095
1096    /*
1097     * Get FileSet
1098     */
1099    memset(&fsr, 0, sizeof(fsr));
1100    i = find_arg_with_value(ua, "FileSet");
1101    if (i >= 0) {
1102       bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1103       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1104          ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1105             db_strerror(ua->db));
1106          i = -1;
1107       }
1108    }
1109    if (i < 0) {                       /* fileset not found */
1110       edit_int64(cr.ClientId, ed1);
1111       Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1112       start_prompt(ua, _("The defined FileSet resources are:\n"));
1113       if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1114          ua->error_msg("%s\n", db_strerror(ua->db));
1115       }
1116       if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1117                  fileset_name, sizeof(fileset_name)) < 0) {
1118          ua->error_msg(_("No FileSet found for client \"%s\".\n"), cr.Name);
1119          goto bail_out;
1120       }
1121
1122       bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1123       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1124          ua->warning_msg(_("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1125          ua->send_msg(_("This probably means you modified the FileSet.\n"
1126                      "Continuing anyway.\n"));
1127       }
1128    }
1129
1130    /* If Pool specified, add PoolId specification */
1131    pool_select[0] = 0;
1132    if (rx->pool) {
1133       POOL_DBR pr;
1134       memset(&pr, 0, sizeof(pr));
1135       bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1136       if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1137          bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ", 
1138             edit_int64(pr.PoolId, ed1));
1139       } else {
1140          ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1141       }
1142    }
1143
1144    /* Find JobId of last Full backup for this client, fileset */
1145    edit_int64(cr.ClientId, ed1);
1146    Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1147          pool_select);
1148    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1149       ua->error_msg("%s\n", db_strerror(ua->db));
1150       goto bail_out;
1151    }
1152
1153    /* Find all Volumes used by that JobId */
1154    if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1155       ua->error_msg("%s\n", db_strerror(ua->db));
1156       goto bail_out;
1157    }
1158    /* Note, this is needed because I don't seem to get the callback
1159     * from the call just above.
1160     */
1161    rx->JobTDate = 0;
1162    if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1163       ua->warning_msg("%s\n", db_strerror(ua->db));
1164    }
1165    if (rx->JobTDate == 0) {
1166       ua->error_msg(_("No Full backup before %s found.\n"), date);
1167       goto bail_out;
1168    }
1169
1170    /* Now find most recent Differental Job after Full save, if any */
1171    Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1172         edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1173    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1174       ua->warning_msg("%s\n", db_strerror(ua->db));
1175    }
1176    /* Now update JobTDate to lock onto Differental, if any */
1177    rx->JobTDate = 0;
1178    if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1179       ua->warning_msg("%s\n", db_strerror(ua->db));
1180    }
1181    if (rx->JobTDate == 0) {
1182       ua->error_msg(_("No Full backup before %s found.\n"), date);
1183       goto bail_out;
1184    }
1185
1186    /* Now find all Incremental Jobs after Full/dif save */
1187    Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1188         edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1189    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1190       ua->warning_msg("%s\n", db_strerror(ua->db));
1191    }
1192
1193    /* Get the JobIds from that list */
1194    rx->JobIds[0] = 0;
1195    rx->last_jobid[0] = 0;
1196    if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1197       ua->warning_msg("%s\n", db_strerror(ua->db));
1198    }
1199
1200    if (rx->JobIds[0] != 0) {
1201       /* Display a list of Jobs selected for this restore */
1202       db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
1203       ok = true;
1204    } else {
1205       ua->warning_msg(_("No jobs found.\n"));
1206    }
1207
1208 bail_out:
1209    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1210    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1211    return ok;
1212 }
1213
1214
1215 /* 
1216  * Return next JobId from comma separated list   
1217  *
1218  * Returns:
1219  *   1 if next JobId returned
1220  *   0 if no more JobIds are in list
1221  *  -1 there is an error
1222  */
1223 int get_next_jobid_from_list(char **p, JobId_t *JobId)
1224 {
1225    char jobid[30];
1226    char *q = *p;
1227
1228    jobid[0] = 0;
1229    for (int i=0; i<(int)sizeof(jobid); i++) {
1230       if (*q == 0) {
1231          break;
1232       } else if (*q == ',') {
1233          q++;
1234          break;
1235       }
1236       jobid[i] = *q++;
1237       jobid[i+1] = 0;
1238    }
1239    if (jobid[0] == 0) {
1240       return 0;
1241    } else if (!is_a_number(jobid)) {
1242       return -1;                      /* error */
1243    }
1244    *p = q;
1245    *JobId = str_to_int64(jobid);
1246    return 1;
1247 }
1248
1249 static int restore_count_handler(void *ctx, int num_fields, char **row)
1250 {
1251    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1252    rx->JobId = str_to_int64(row[0]);
1253    rx->found = true;
1254    return 0;
1255 }
1256
1257 /*
1258  * Callback handler to get JobId and FileIndex for files
1259  *   can insert more than one depending on the caller.
1260  */
1261 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1262 {
1263    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1264    rx->JobId = str_to_int64(row[0]);
1265    add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1266    rx->found = true;
1267    rx->selected_files++;
1268    return 0;
1269 }
1270
1271 /*
1272  * Callback handler make list of JobIds
1273  */
1274 static int jobid_handler(void *ctx, int num_fields, char **row)
1275 {
1276    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1277
1278    if (strcmp(rx->last_jobid, row[0]) == 0) {
1279       return 0;                       /* duplicate id */
1280    }
1281    bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1282    if (rx->JobIds[0] != 0) {
1283       pm_strcat(rx->JobIds, ",");
1284    }
1285    pm_strcat(rx->JobIds, row[0]);
1286    return 0;
1287 }
1288
1289
1290 /*
1291  * Callback handler to pickup last Full backup JobTDate
1292  */
1293 static int last_full_handler(void *ctx, int num_fields, char **row)
1294 {
1295    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1296
1297    rx->JobTDate = str_to_int64(row[1]);
1298    return 0;
1299 }
1300
1301 /*
1302  * Callback handler build FileSet name prompt list
1303  */
1304 static int fileset_handler(void *ctx, int num_fields, char **row)
1305 {
1306    /* row[0] = FileSet (name) */
1307    if (row[0]) {
1308       add_prompt((UAContext *)ctx, row[0]);
1309    }
1310    return 0;
1311 }
1312
1313 /*
1314  * Free names in the list
1315  */
1316 static void free_name_list(NAME_LIST *name_list)
1317 {
1318    for (int i=0; i < name_list->num_ids; i++) {
1319       free(name_list->name[i]);
1320    }
1321    if (name_list->name) {
1322       free(name_list->name);
1323       name_list->name = NULL;
1324    }
1325    name_list->max_ids = 0;
1326    name_list->num_ids = 0;
1327 }
1328
1329 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType) 
1330 {
1331    STORE *store;
1332
1333    if (rx.store) {
1334       Dmsg1(200, "Already have store=%s\n", rx.store->name());
1335       return;
1336    }
1337    /*
1338     * Try looking up Storage by name
1339     */
1340    LockRes();
1341    foreach_res(store, R_STORAGE) {
1342       if (strcmp(Storage, store->name()) == 0) {
1343          if (acl_access_ok(ua, Storage_ACL, store->name())) {
1344             rx.store = store;
1345          }
1346          break;
1347       }
1348    }
1349    UnlockRes();
1350
1351    if (rx.store) {
1352       /* Check if an explicit storage resource is given */
1353       store = NULL;
1354       int i = find_arg_with_value(ua, "storage");
1355       if (i > 0) {
1356          store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1357          if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1358             store = NULL;
1359          }
1360       }
1361       if (store && (store != rx.store)) {
1362          ua->info_msg(_("Warning default storage overridden by \"%s\" on command line.\n"),
1363             store->name());
1364          rx.store = store;
1365          Dmsg1(200, "Set store=%s\n", rx.store->name());
1366       }
1367       return;
1368    }
1369
1370    /* If no storage resource, try to find one from MediaType */
1371    if (!rx.store) {
1372       LockRes();
1373       foreach_res(store, R_STORAGE) {
1374          if (strcmp(MediaType, store->media_type) == 0) {
1375             if (acl_access_ok(ua, Storage_ACL, store->name())) {
1376                rx.store = store;
1377                Dmsg1(200, "Set store=%s\n", rx.store->name());
1378                ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1379                   Storage, store->name(), MediaType);
1380             }
1381             UnlockRes();
1382             return;
1383          }
1384       }
1385       UnlockRes();
1386       ua->warning_msg(_("\nUnable to find Storage resource for\n"
1387          "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1388    }
1389
1390    /* Take command line arg, or ask user if none */
1391    rx.store = get_storage_resource(ua, false /* don't use default */);
1392    Dmsg1(200, "Set store=%s\n", rx.store->name());
1393
1394 }