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