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