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