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