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