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