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