]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
892a20d8b74773b8cbb60e18280fa523b3721d21
[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    rx->found = false;
1006    /* Find and insert jobid and File Index */
1007    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
1008       ua->error_msg(_("Query failed: %s. ERR=%s\n"),
1009          rx->query, db_strerror(ua->db));
1010    }
1011    if (!rx->found) {
1012       ua->error_msg(_("No database record found for: %s\n"), file);
1013 //    ua->error_msg("Query=%s\n", rx->query);
1014       return true;
1015    }
1016    return true;
1017 }
1018
1019 /*
1020  * For a given path lookup the most recent backup in the catalog
1021  * to get the JobId and FileIndexes of all files in that directory.
1022  */
1023 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
1024                                         char *date)
1025 {
1026    strip_trailing_junk(dir);
1027    if (*rx->JobIds == 0) {
1028       ua->error_msg(_("No JobId specified cannot continue.\n"));
1029       return false;
1030    } else {
1031       Mmsg(rx->query, uar_jobid_fileindex_from_dir[db_get_type_index(ua->db)], rx->JobIds, dir, rx->ClientName);
1032    }
1033    rx->found = false;
1034    /* Find and insert jobid and File Index */
1035    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
1036       ua->error_msg(_("Query failed: %s. ERR=%s\n"),
1037          rx->query, db_strerror(ua->db));
1038    }
1039    if (!rx->found) {
1040       ua->error_msg(_("No database record found for: %s\n"), dir);
1041       return true;
1042    }
1043    return true;
1044 }
1045
1046 /*
1047  * Get the JobId and FileIndexes of all files in the specified table
1048  */
1049 bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
1050 {
1051    strip_trailing_junk(table);
1052    Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
1053
1054    rx->found = false;
1055    /* Find and insert jobid and File Index */
1056    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
1057       ua->error_msg(_("Query failed: %s. ERR=%s\n"),
1058          rx->query, db_strerror(ua->db));
1059    }
1060    if (!rx->found) {
1061       ua->error_msg(_("No table found: %s\n"), table);
1062       return true;
1063    }
1064    return true;
1065 }
1066
1067 static void split_path_and_filename(UAContext *ua, RESTORE_CTX *rx, char *name)
1068 {
1069    char *p, *f;
1070
1071    /* Find path without the filename.
1072     * I.e. everything after the last / is a "filename".
1073     * OK, maybe it is a directory name, but we treat it like
1074     * a filename. If we don't find a / then the whole name
1075     * must be a path name (e.g. c:).
1076     */
1077    for (p=f=name; *p; p++) {
1078       if (IsPathSeparator(*p)) {
1079          f = p;                       /* set pos of last slash */
1080       }
1081    }
1082    if (IsPathSeparator(*f)) {         /* did we find a slash? */
1083       f++;                            /* yes, point to filename */
1084    } else {                           /* no, whole thing must be path name */
1085       f = p;
1086    }
1087
1088    /* If filename doesn't exist (i.e. root directory), we
1089     * simply create a blank name consisting of a single
1090     * space. This makes handling zero length filenames
1091     * easier.
1092     */
1093    rx->fnl = p - f;
1094    if (rx->fnl > 0) {
1095       rx->fname = check_pool_memory_size(rx->fname, 2*(rx->fnl)+1);
1096       db_escape_string(ua->jcr, ua->db, rx->fname, f, rx->fnl);
1097    } else {
1098       rx->fname[0] = 0;
1099       rx->fnl = 0;
1100    }
1101
1102    rx->pnl = f - name;
1103    if (rx->pnl > 0) {
1104       rx->path = check_pool_memory_size(rx->path, 2*(rx->pnl)+1);
1105       db_escape_string(ua->jcr, ua->db, rx->path, name, rx->pnl);
1106    } else {
1107       rx->path[0] = 0;
1108       rx->pnl = 0;
1109    }
1110
1111    Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
1112 }
1113
1114 static bool can_restore_all_files(UAContext *ua)
1115 {
1116    alist *lst;
1117    if (ua->cons) {
1118       lst = ua->cons->ACL_lists[Directory_ACL];
1119       /* ACL not defined, or the first entry is not *all* */
1120       /* TODO: See if we search for *all* in all the list */
1121       if (!lst || strcasecmp((char*)lst->get(0), "*all*") != 0) {
1122          return false;
1123       }
1124       if (!lst || strcasecmp((char *)lst->get(0), "*all*") != 0) {
1125          return false;
1126       }
1127    }
1128    return true;
1129 }
1130
1131 static bool ask_for_fileregex(UAContext *ua, RESTORE_CTX *rx)
1132 {
1133    bool can_restore=can_restore_all_files(ua);
1134
1135    if (can_restore && find_arg(ua, NT_("all")) >= 0) {  /* if user enters all on command line */
1136       return true;                       /* select everything */
1137    }
1138
1139    ua->send_msg(_("\n\nFor one or more of the JobIds selected, no files were found,\n"
1140                  "so file selection is not possible.\n"
1141                  "Most likely your retention policy pruned the files.\n"));
1142
1143    if (!can_restore) {
1144       ua->error_msg(_("\nThe current Console has UserId or Directory restrictions. "
1145                       "The full restore is not allowed.\n"));
1146       return false;
1147    }
1148
1149    if (get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
1150       if (ua->pint32_val == 1)
1151          return true;
1152       while (get_cmd(ua, _("\nRegexp matching files to restore? (empty to abort): "))) {
1153          if (ua->cmd[0] == '\0') {
1154             break;
1155          } else {
1156             regex_t *fileregex_re = NULL;
1157             int rc;
1158             char errmsg[500] = "";
1159
1160             fileregex_re = (regex_t *)bmalloc(sizeof(regex_t));
1161             rc = regcomp(fileregex_re, ua->cmd, REG_EXTENDED|REG_NOSUB);
1162             if (rc != 0) {
1163                regerror(rc, fileregex_re, errmsg, sizeof(errmsg));
1164             }
1165             regfree(fileregex_re);
1166             free(fileregex_re);
1167             if (*errmsg) {
1168                ua->send_msg(_("Regex compile error: %s\n"), errmsg);
1169             } else {
1170                rx->fileregex = bstrdup(ua->cmd);
1171                return true;
1172             }
1173          }
1174       }
1175    }
1176    return false;
1177 }
1178
1179 /* Walk on the delta_list of a TREE_NODE item and insert all parts
1180  * TODO: Optimize for bootstrap creation, remove recursion
1181  * 6 -> 5 -> 4 -> 3 -> 2 -> 1 -> 0
1182  * should insert as
1183  * 0, 1, 2, 3, 4, 5, 6
1184  */
1185 static void add_delta_list_findex(RESTORE_CTX *rx, struct delta_list *lst)
1186 {
1187    if (lst == NULL) {
1188       return;
1189    }
1190    if (lst->next) {
1191       add_delta_list_findex(rx, lst->next);
1192    }
1193    add_findex(rx->bsr_list, lst->JobId, lst->FileIndex);
1194 }
1195
1196 /*
1197  * This is a list of all the files (components) that the
1198  *  user has requested for restore. It is requested by
1199  *  the plugin (for now hard coded only for VSS).
1200  *  In the future, this will be requested by a RestoreObject
1201  *  and the plugin name will be sent to the FD.
1202  */
1203 static bool write_component_file(UAContext *ua, RESTORE_CTX *rx, char *fname)
1204 {
1205    int fd;
1206    if (!rx->component_fd) {
1207       Mmsg(rx->component_fname, "%s/%s.restore.sel.XXXXXX", working_directory, my_name);
1208       fd = mkstemp(rx->component_fname);
1209       if (fd < 0) {
1210          berrno be;
1211          ua->error_msg(_("Unable to create component file %s. ERR=%s\n"),
1212             rx->component_fname, be.bstrerror());
1213          return false;
1214       }
1215       rx->component_fd = fdopen(fd, "w+");
1216       if (!rx->component_fd) {
1217          berrno be;
1218          ua->error_msg(_("Unable to fdopen component file %s. ERR=%s\n"),
1219             rx->component_fname, be.bstrerror());
1220          return false;
1221       }
1222    }
1223    fprintf(rx->component_fd, "%s\n", fname);
1224    if (ferror(rx->component_fd)) {
1225       ua->error_msg(_("Error writing component file.\n"));
1226      fclose(rx->component_fd);
1227      unlink(rx->component_fname);
1228      rx->component_fd = NULL;
1229      return false;
1230    }
1231    return true;
1232 }
1233
1234 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
1235 {
1236    TREE_CTX tree;
1237    JobId_t JobId, last_JobId;
1238    char *p;
1239    bool OK = true;
1240    char ed1[50];
1241
1242    memset(&tree, 0, sizeof(TREE_CTX));
1243    /*
1244     * Build the directory tree containing JobIds user selected
1245     */
1246    tree.root = new_tree(rx->TotalFiles);
1247    tree.ua = ua;
1248    tree.all = rx->all;
1249    tree.hardlinks_in_mem = rx->hardlinks_in_mem;
1250    tree.no_auto_parent = rx->no_auto_parent;
1251    last_JobId = 0;
1252    tree.last_dir_acl = NULL;
1253    /*
1254     * For display purposes, the same JobId, with different volumes may
1255     * appear more than once, however, we only insert it once.
1256     */
1257    p = rx->JobIds;
1258    tree.FileEstimate = 0;
1259    if (get_next_jobid_from_list(&p, &JobId) > 0) {
1260       /* Use first JobId as estimate of the number of files to restore */
1261       Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
1262       if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1263          ua->error_msg("%s\n", db_strerror(ua->db));
1264       }
1265       if (rx->found) {
1266          /* Add about 25% more than this job for over estimate */
1267          tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
1268          tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
1269       }
1270    }
1271
1272    ua->info_msg(_("\nBuilding directory tree for JobId(s) %s ...  "),
1273                 rx->JobIds);
1274
1275 #define new_get_file_list
1276 #ifdef new_get_file_list
1277    if (!db_get_file_list(ua->jcr, ua->db,
1278                          rx->JobIds, false /* do not use md5 */,
1279                          true /* get delta */,
1280                          insert_tree_handler, (void *)&tree))
1281    {
1282       ua->error_msg("%s", db_strerror(ua->db));
1283    }
1284    if (*rx->BaseJobIds) {
1285       pm_strcat(rx->JobIds, ",");
1286       pm_strcat(rx->JobIds, rx->BaseJobIds);
1287    }
1288 #else
1289    for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1290       char ed1[50];
1291
1292       if (JobId == last_JobId) {
1293          continue;                    /* eliminate duplicate JobIds */
1294       }
1295       last_JobId = JobId;
1296       /*
1297        * Find files for this JobId and insert them in the tree
1298        */
1299       Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
1300       if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
1301          ua->error_msg("%s", db_strerror(ua->db));
1302       }
1303    }
1304 #endif
1305    /*
1306     * At this point, the tree is built, so we can garbage collect
1307     * any memory released by the SQL engine that RedHat has
1308     * not returned to the OS :-(
1309     */
1310     garbage_collect_memory();
1311
1312    /*
1313     * Look at the first JobId on the list (presumably the oldest) and
1314     *  if it is marked purged, don't do the manual selection because
1315     *  the Job was pruned, so the tree is incomplete.
1316     */
1317    if (tree.FileCount != 0) {
1318       /* Find out if any Job is purged */
1319       Mmsg(rx->query, "SELECT SUM(PurgedFiles) FROM Job WHERE JobId IN (%s)", rx->JobIds);
1320       if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1321          ua->error_msg("%s\n", db_strerror(ua->db));
1322       }
1323       /* rx->JobId is the PurgedFiles flag */
1324       if (rx->found && rx->JobId > 0) {
1325          tree.FileCount = 0;           /* set count to zero, no tree selection */
1326       }
1327    }
1328    if (tree.FileCount == 0) {
1329       OK = ask_for_fileregex(ua, rx);
1330       if (OK) {
1331          last_JobId = 0;
1332          for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1333              if (JobId == last_JobId) {
1334                 continue;                    /* eliminate duplicate JobIds */
1335              }
1336              add_findex_all(rx->bsr_list, JobId, rx->fileregex);
1337          }
1338       }
1339    } else {
1340       char ec1[50];
1341       if (tree.all) {
1342          ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
1343                       edit_uint64_with_commas(tree.FileCount, ec1));
1344       } else {
1345          ua->info_msg(_("\n%s files inserted into the tree.\n"),
1346                       edit_uint64_with_commas(tree.FileCount, ec1));
1347       }
1348
1349       if (find_arg(ua, NT_("done")) < 0) {
1350          /* Let the user interact in selecting which files to restore */
1351          OK = user_select_files_from_tree(&tree);
1352       }
1353
1354       /*
1355        * Walk down through the tree finding all files marked to be
1356        *  extracted making a bootstrap file.
1357        */
1358       if (OK) {
1359          char cwd[2000];
1360          for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
1361             Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
1362             if (node->extract || node->extract_dir) {
1363                Dmsg3(400, "JobId=%lld type=%d FI=%d\n", (uint64_t)node->JobId, node->type, node->FileIndex);
1364                /* TODO: optimize bsr insertion when jobid are non sorted */
1365                add_delta_list_findex(rx, node->delta_list);
1366                add_findex(rx->bsr_list, node->JobId, node->FileIndex);
1367                /*
1368                 * Special VSS plugin code to return selected
1369                 *   components. For the moment, it is hard coded
1370                 *   for the VSS plugin.
1371                 */
1372                if (fnmatch(":component_info_*", node->fname, 0) == 0) {
1373                   tree_getpath(node, cwd, sizeof(cwd));
1374                   if (!write_component_file(ua, rx, cwd)) {
1375                      OK = false;
1376                      break;
1377                   }
1378                }
1379                if (node->extract && node->type != TN_NEWDIR) {
1380                   rx->selected_files++;  /* count only saved files */
1381                }
1382             }
1383          }
1384       }
1385    }
1386    if (tree.uid_acl) {
1387       delete tree.uid_acl;
1388       delete tree.gid_acl;
1389       delete tree.dir_acl;
1390    }
1391    free_tree(tree.root);              /* free the directory tree */
1392    return OK;
1393 }
1394
1395
1396 /*
1397  * This routine is used to get the current backup or a backup
1398  *   before the specified date.
1399  */
1400 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
1401 {
1402    bool ok = false;
1403    FILESET_DBR fsr;
1404    CLIENT_DBR cr;
1405    char fileset_name[MAX_NAME_LENGTH];
1406    char ed1[50], ed2[50];
1407    char pool_select[MAX_NAME_LENGTH];
1408    int i;
1409
1410    /* Create temp tables */
1411   db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1412   db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1413    if (!db_sql_query(ua->db, uar_create_temp[db_get_type_index(ua->db)], NULL, NULL)) {
1414       ua->error_msg("%s\n", db_strerror(ua->db));
1415    }
1416    if (!db_sql_query(ua->db, uar_create_temp1[db_get_type_index(ua->db)], NULL, NULL)) {
1417       ua->error_msg("%s\n", db_strerror(ua->db));
1418    }
1419    /*
1420     * Select Client from the Catalog
1421     */
1422    memset(&cr, 0, sizeof(cr));
1423    if (!get_client_dbr(ua, &cr, JT_BACKUP_RESTORE)) {
1424       goto bail_out;
1425    }
1426    bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1427
1428    /*
1429     * Get FileSet
1430     */
1431    memset(&fsr, 0, sizeof(fsr));
1432    i = find_arg_with_value(ua, "FileSet");
1433
1434    if (i >= 0 && is_name_valid(ua->argv[i], &ua->errmsg)) {
1435       bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1436       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1437          ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1438             db_strerror(ua->db));
1439          i = -1;
1440       }
1441    } else if (i >= 0) {         /* name is invalid */
1442       ua->error_msg(_("FileSet argument: %s\n"), ua->errmsg);
1443    }
1444
1445    if (i < 0) {                       /* fileset not found */
1446       edit_int64(cr.ClientId, ed1);
1447       Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1448       start_prompt(ua, _("The defined FileSet resources are:\n"));
1449       if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1450          ua->error_msg("%s\n", db_strerror(ua->db));
1451       }
1452       if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1453                  fileset_name, sizeof(fileset_name)) < 0) {
1454          ua->error_msg(_("No FileSet found for client \"%s\".\n"), cr.Name);
1455          goto bail_out;
1456       }
1457
1458       bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1459       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1460          ua->warning_msg(_("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1461          ua->send_msg(_("This probably means you modified the FileSet.\n"
1462                      "Continuing anyway.\n"));
1463       }
1464    }
1465
1466    /* If Pool specified, add PoolId specification */
1467    pool_select[0] = 0;
1468    if (rx->pool) {
1469       POOL_DBR pr;
1470       memset(&pr, 0, sizeof(pr));
1471       bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1472       if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1473          bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
1474             edit_int64(pr.PoolId, ed1));
1475       } else {
1476          ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1477       }
1478    }
1479
1480    /* Find JobId of last Full backup for this client, fileset */
1481    edit_int64(cr.ClientId, ed1);
1482    Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1483          pool_select);
1484    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1485       ua->error_msg("%s\n", db_strerror(ua->db));
1486       goto bail_out;
1487    }
1488
1489    /* Find all Volumes used by that JobId */
1490    if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1491       ua->error_msg("%s\n", db_strerror(ua->db));
1492       goto bail_out;
1493    }
1494
1495    /* Note, this is needed because I don't seem to get the callback
1496     * from the call just above.
1497     */
1498    rx->JobTDate = 0;
1499    if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1500       ua->warning_msg("%s\n", db_strerror(ua->db));
1501    }
1502    if (rx->JobTDate == 0) {
1503       ua->error_msg(_("No Full backup before %s found.\n"), date);
1504       goto bail_out;
1505    }
1506
1507    /* Now find most recent Differental Job after Full save, if any */
1508    Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1509         edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1510    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1511       ua->warning_msg("%s\n", db_strerror(ua->db));
1512    }
1513    /* Now update JobTDate to look into Differental, if any */
1514    rx->JobTDate = 0;
1515    if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1516       ua->warning_msg("%s\n", db_strerror(ua->db));
1517    }
1518    if (rx->JobTDate == 0) {
1519       ua->error_msg(_("No Full backup before %s found.\n"), date);
1520       goto bail_out;
1521    }
1522
1523    /* Now find all Incremental Jobs after Full/dif save */
1524    Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1525         edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1526    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1527       ua->warning_msg("%s\n", db_strerror(ua->db));
1528    }
1529
1530    /* Get the JobIds from that list */
1531    rx->last_jobid[0] = rx->JobIds[0] = 0;
1532
1533    if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1534       ua->warning_msg("%s\n", db_strerror(ua->db));
1535    }
1536
1537    if (rx->JobIds[0] != 0) {
1538       if (find_arg(ua, NT_("copies")) > 0) {
1539          /* Display a list of all copies */
1540          db_list_copies_records(ua->jcr, ua->db, 0, rx->JobIds,
1541                                 prtit, ua, HORZ_LIST);
1542       }
1543       /* Display a list of Jobs selected for this restore */
1544       db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1,HORZ_LIST);
1545       ok = true;
1546
1547    } else {
1548       ua->warning_msg(_("No jobs found.\n"));
1549    }
1550
1551 bail_out:
1552   db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1553   db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1554    return ok;
1555 }
1556
1557 static int restore_count_handler(void *ctx, int num_fields, char **row)
1558 {
1559    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1560    rx->JobId = str_to_int64(row[0]);
1561    rx->found = true;
1562    return 0;
1563 }
1564
1565 /*
1566  * Callback handler to get JobId and FileIndex for files
1567  *   can insert more than one depending on the caller.
1568  */
1569 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1570 {
1571    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1572    JobId_t JobId = str_to_int64(row[0]);
1573
1574    Dmsg3(200, "JobId=%s JobIds=%s FileIndex=%s\n", row[0], rx->JobIds, row[1]);
1575
1576    /* New JobId, add it to JobIds
1577     * The list is sorted by JobId, so we need a cache for the previous value
1578     *
1579     * It will permit to find restore objects to send during the restore
1580     */
1581    if (rx->JobId != JobId) {
1582       if (*rx->JobIds) {
1583          pm_strcat(rx->JobIds, ",");
1584       }
1585       pm_strcat(rx->JobIds, row[0]);
1586       rx->JobId = JobId;
1587    }
1588
1589    add_findex(rx->bsr_list, rx->JobId, str_to_int64(row[1]));
1590    rx->found = true;
1591    rx->selected_files++;
1592    return 0;
1593 }
1594
1595 /*
1596  * Callback handler make list of JobIds
1597  */
1598 static int jobid_handler(void *ctx, int num_fields, char **row)
1599 {
1600    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1601
1602    if (strcmp(rx->last_jobid, row[0]) == 0) {
1603       return 0;                       /* duplicate id */
1604    }
1605    bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1606    if (rx->JobIds[0] != 0) {
1607       pm_strcat(rx->JobIds, ",");
1608    }
1609    pm_strcat(rx->JobIds, row[0]);
1610    return 0;
1611 }
1612
1613
1614 /*
1615  * Callback handler to pickup last Full backup JobTDate
1616  */
1617 static int last_full_handler(void *ctx, int num_fields, char **row)
1618 {
1619    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1620
1621    rx->JobTDate = str_to_int64(row[1]);
1622    return 0;
1623 }
1624
1625 /*
1626  * Callback handler build FileSet name prompt list
1627  */
1628 static int fileset_handler(void *ctx, int num_fields, char **row)
1629 {
1630    /* row[0] = FileSet (name) */
1631    if (row[0]) {
1632       add_prompt((UAContext *)ctx, row[0]);
1633    }
1634    return 0;
1635 }
1636
1637 /*
1638  * Free names in the list
1639  */
1640 static void free_name_list(NAME_LIST *name_list)
1641 {
1642    for (int i=0; i < name_list->num_ids; i++) {
1643       free(name_list->name[i]);
1644    }
1645    bfree_and_null(name_list->name);
1646    name_list->max_ids = 0;
1647    name_list->num_ids = 0;
1648 }
1649
1650 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType)
1651 {
1652    STORE *store;
1653
1654    if (rx.store) {
1655       Dmsg1(200, "Already have store=%s\n", rx.store->name());
1656       return;
1657    }
1658    /*
1659     * Try looking up Storage by name
1660     */
1661    LockRes();
1662    foreach_res(store, R_STORAGE) {
1663       if (strcmp(Storage, store->name()) == 0) {
1664          if (acl_access_ok(ua, Storage_ACL, store->name())) {
1665             rx.store = store;
1666          }
1667          break;
1668       }
1669    }
1670    UnlockRes();
1671
1672    if (rx.store) {
1673       /* Check if an explicit storage resource is given */
1674       store = NULL;
1675       int i = find_arg_with_value(ua, "storage");
1676       if (i > 0) {
1677          store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1678          if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1679             store = NULL;
1680          }
1681       }
1682       if (store && (store != rx.store)) {
1683          ua->info_msg(_("\nWarning Storage is overridden by \"%s\" on the command line.\n"),
1684             store->name());
1685          rx.store = store;
1686          bstrncpy(rx.RestoreMediaType, MediaType, sizeof(rx.RestoreMediaType));
1687          if (strcmp(MediaType, store->media_type) != 0) {
1688             ua->info_msg(_("This may not work because of two different MediaTypes:\n"
1689                "  Storage MediaType=\"%s\"\n"
1690                "  Volume  MediaType=\"%s\".\n\n"),
1691                store->media_type, MediaType);
1692          }
1693          Dmsg2(200, "Set store=%s MediaType=%s\n", rx.store->name(), rx.RestoreMediaType);
1694       }
1695       return;
1696    }
1697
1698    /* If no storage resource, try to find one from MediaType */
1699    if (!rx.store) {
1700       LockRes();
1701       foreach_res(store, R_STORAGE) {
1702          if (strcmp(MediaType, store->media_type) == 0) {
1703             if (acl_access_ok(ua, Storage_ACL, store->name())) {
1704                rx.store = store;
1705                Dmsg1(200, "Set store=%s\n", rx.store->name());
1706                if (Storage == NULL || Storage[0] == 0) {
1707                   ua->warning_msg(_("Using Storage \"%s\" from MediaType \"%s\".\n"),
1708                      store->name(), MediaType);
1709                } else {
1710                   ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1711                      Storage, store->name(), MediaType);
1712                }
1713             }
1714             UnlockRes();
1715             return;
1716          }
1717       }
1718       UnlockRes();
1719       ua->warning_msg(_("\nUnable to find Storage resource for\n"
1720          "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1721    }
1722
1723    /* Take command line arg, or ask user if none */
1724    rx.store = get_storage_resource(ua, false /* don't use default */);
1725    if (rx.store) {
1726       Dmsg1(200, "Set store=%s\n", rx.store->name());
1727    }
1728
1729 }