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