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