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