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