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