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