]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
- Rename mac.c migrate.c
[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-2006 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 /* 
1157  * Return next JobId from comma separated list   
1158  *
1159  * Returns:
1160  *   1 if next JobId returned
1161  *   0 if no more JobIds are in list
1162  *  -1 there is an error
1163  */
1164 int get_next_jobid_from_list(char **p, JobId_t *JobId)
1165 {
1166    char jobid[30];
1167    char *q = *p;
1168
1169    jobid[0] = 0;
1170    for (int i=0; i<(int)sizeof(jobid); i++) {
1171       if (*q == 0) {
1172          break;
1173       } else if (*q == ',') {
1174          q++;
1175          break;
1176       }
1177       jobid[i] = *q++;
1178       jobid[i+1] = 0;
1179    }
1180    if (jobid[0] == 0) {
1181       return 0;
1182    } else if (!is_a_number(jobid)) {
1183       return -1;                      /* error */
1184    }
1185    *p = q;
1186    *JobId = str_to_int64(jobid);
1187    return 1;
1188 }
1189
1190 static int count_handler(void *ctx, int num_fields, char **row)
1191 {
1192    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1193    rx->JobId = str_to_int64(row[0]);
1194    rx->found = true;
1195    return 0;
1196 }
1197
1198 /*
1199  * Callback handler to get JobId and FileIndex for files
1200  *   can insert more than one depending on the caller.
1201  */
1202 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1203 {
1204    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1205    rx->JobId = str_to_int64(row[0]);
1206    add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1207    rx->found = true;
1208    rx->selected_files++;
1209    return 0;
1210 }
1211
1212 /*
1213  * Callback handler make list of JobIds
1214  */
1215 static int jobid_handler(void *ctx, int num_fields, char **row)
1216 {
1217    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1218
1219    if (strcmp(rx->last_jobid, row[0]) == 0) {
1220       return 0;                       /* duplicate id */
1221    }
1222    bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1223    if (rx->JobIds[0] != 0) {
1224       pm_strcat(rx->JobIds, ",");
1225    }
1226    pm_strcat(rx->JobIds, row[0]);
1227    return 0;
1228 }
1229
1230
1231 /*
1232  * Callback handler to pickup last Full backup JobTDate
1233  */
1234 static int last_full_handler(void *ctx, int num_fields, char **row)
1235 {
1236    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1237
1238    rx->JobTDate = str_to_int64(row[1]);
1239    return 0;
1240 }
1241
1242 /*
1243  * Callback handler build FileSet name prompt list
1244  */
1245 static int fileset_handler(void *ctx, int num_fields, char **row)
1246 {
1247    /* row[0] = FileSet (name) */
1248    if (row[0]) {
1249       add_prompt((UAContext *)ctx, row[0]);
1250    }
1251    return 0;
1252 }
1253
1254 /*
1255  * Called here with each name to be added to the list. The name is
1256  *   added to the list if it is not already in the list.
1257  *
1258  * Used to make unique list of FileSets and MediaTypes
1259  */
1260 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1261 {
1262    NAME_LIST *name = (NAME_LIST *)ctx;
1263
1264    if (name->num_ids == MAX_ID_LIST_LEN) {
1265       return 1;
1266    }
1267    if (name->num_ids == name->max_ids) {
1268       if (name->max_ids == 0) {
1269          name->max_ids = 1000;
1270          name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1271       } else {
1272          name->max_ids = (name->max_ids * 3) / 2;
1273          name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1274       }
1275    }
1276    for (int i=0; i<name->num_ids; i++) {
1277       if (strcmp(name->name[i], row[0]) == 0) {
1278          return 0;                    /* already in list, return */
1279       }
1280    }
1281    /* Add new name to list */
1282    name->name[name->num_ids++] = bstrdup(row[0]);
1283    return 0;
1284 }
1285
1286
1287 /*
1288  * Print names in the list
1289  */
1290 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1291 {
1292    for (int i=0; i < name_list->num_ids; i++) {
1293       bsendmsg(ua, "%s\n", name_list->name[i]);
1294    }
1295 }
1296
1297
1298 /*
1299  * Free names in the list
1300  */
1301 static void free_name_list(NAME_LIST *name_list)
1302 {
1303    for (int i=0; i < name_list->num_ids; i++) {
1304       free(name_list->name[i]);
1305    }
1306    if (name_list->name) {
1307       free(name_list->name);
1308       name_list->name = NULL;
1309    }
1310    name_list->max_ids = 0;
1311    name_list->num_ids = 0;
1312 }
1313
1314 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, RESTORE_CTX *rx)
1315 {
1316    STORE *store;
1317
1318    if (name_list->num_ids > 1) {
1319       bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1320          "Restore is not possible. The MediaTypes used are:\n"));
1321       print_name_list(ua, name_list);
1322       rx->store = select_storage_resource(ua);
1323       return;
1324    }
1325
1326    if (name_list->num_ids == 0) {
1327       bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1328       rx->store = select_storage_resource(ua);
1329       return;
1330    }
1331    if (rx->store) {
1332       return;
1333    }
1334    /*
1335     * We have a single MediaType, look it up in our Storage resource
1336     */
1337    LockRes();
1338    foreach_res(store, R_STORAGE) {
1339       if (strcmp(name_list->name[0], store->media_type) == 0) {
1340          if (acl_access_ok(ua, Storage_ACL, store->hdr.name)) {
1341             rx->store = store;
1342          }
1343          break;
1344       }
1345    }
1346    UnlockRes();
1347
1348    if (rx->store) {
1349       /* Check if an explicit storage resource is given */
1350       store = NULL;
1351       int i = find_arg_with_value(ua, "storage");
1352       if (i > 0) {
1353          store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1354          if (store && !acl_access_ok(ua, Storage_ACL, store->hdr.name)) {
1355             store = NULL;
1356          }
1357       }
1358       if (store && (store != rx->store)) {
1359          bsendmsg(ua, _("Warning default storage overridden by %s on command line.\n"),
1360             store->hdr.name);
1361          rx->store = store;
1362       }
1363       return;
1364    }
1365
1366    /* Take command line arg, or ask user if none */
1367    rx->store = get_storage_resource(ua, false /* don't use default */);
1368
1369    if (!rx->store) {
1370       bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1371          "MediaType \"%s\", needed by the Jobs you selected.\n"
1372          "You will be allowed to select a Storage device later.\n"),
1373          name_list->name[0]);
1374    }
1375 }