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