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