]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
Tweak Storage warning message for restore when StorageId not found
[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 (!is_name_valid(ua->argv[i], &ua->errmsg)) {
375             ua->error_msg("%s argument: %s", ua->argk[i], ua->errmsg);
376             return 0;
377          }
378          bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
379          return 1;
380       }
381       memset(&cr, 0, sizeof(cr));
382       if (!get_client_dbr(ua, &cr)) {
383          return 0;
384       }
385       bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
386    }
387    return 1;
388 }
389
390 /*
391  * This is where we pick up a client name to restore to.
392  */
393 static int get_restore_client_name(UAContext *ua, RESTORE_CTX &rx)
394 {
395    /* Start with same name as backup client */
396    bstrncpy(rx.RestoreClientName, rx.ClientName, sizeof(rx.RestoreClientName));    
397
398    /* try command line argument */
399    int i = find_arg_with_value(ua, NT_("restoreclient"));
400    if (i >= 0) {
401       if (!is_name_valid(ua->argv[i], &ua->errmsg)) {
402          ua->error_msg("%s argument: %s", ua->argk[i], ua->errmsg);
403          return 0;
404       }
405       bstrncpy(rx.RestoreClientName, ua->argv[i], sizeof(rx.RestoreClientName));
406       return 1;
407    }
408    return 1;
409 }
410
411
412
413 /*
414  * The first step in the restore process is for the user to
415  *  select a list of JobIds from which he will subsequently
416  *  select which files are to be restored.
417  *
418  *  Returns:  2  if filename list made
419  *            1  if jobid list made
420  *            0  on error
421  */
422 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
423 {
424    char *p;
425    char date[MAX_TIME_LENGTH];
426    bool have_date = false;
427    /* Include current second if using current time */
428    utime_t now = time(NULL) + 1;
429    JobId_t JobId;
430    JOB_DBR jr = { (JobId_t)-1 };
431    bool done = false;
432    int i, j;
433    const char *list[] = {
434       _("List last 20 Jobs run"),
435       _("List Jobs where a given File is saved"),
436       _("Enter list of comma separated JobIds to select"),
437       _("Enter SQL list command"),
438       _("Select the most recent backup for a client"),
439       _("Select backup for a client before a specified time"),
440       _("Enter a list of files to restore"),
441       _("Enter a list of files to restore before a specified time"),
442       _("Find the JobIds of the most recent backup for a client"),
443       _("Find the JobIds for a backup for a client before a specified time"),
444       _("Enter a list of directories to restore for found JobIds"),
445       _("Select full restore to a specified Job date"),
446       _("Cancel"),
447       NULL };
448
449    const char *kw[] = {
450        /* These keywords are handled in a for loop */
451       "jobid",       /* 0 */
452       "current",     /* 1 */
453       "before",      /* 2 */
454       "file",        /* 3 */
455       "directory",   /* 4 */
456       "select",      /* 5 */
457       "pool",        /* 6 */
458       "all",         /* 7 */
459
460       /* The keyword below are handled by individual arg lookups */
461       "client",       /* 8 */
462       "storage",      /* 9 */
463       "fileset",      /* 10 */
464       "where",        /* 11 */
465       "yes",          /* 12 */
466       "bootstrap",    /* 13 */
467       "done",         /* 14 */
468       "strip_prefix", /* 15 */
469       "add_prefix",   /* 16 */
470       "add_suffix",   /* 17 */
471       "regexwhere",   /* 18 */
472       "restoreclient", /* 19 */
473       "copies",        /* 20 */
474       "comment",       /* 21 */
475       "restorejob",    /* 22 */
476       "replace",       /* 23 */
477       NULL
478    };
479
480    rx->JobIds[0] = 0;
481
482    for (i=1; i<ua->argc; i++) {       /* loop through arguments */
483       bool found_kw = false;
484       for (j=0; kw[j]; j++) {         /* loop through keywords */
485          if (strcasecmp(kw[j], ua->argk[i]) == 0) {
486             found_kw = true;
487             break;
488          }
489       }
490       if (!found_kw) {
491          ua->error_msg(_("Unknown keyword: %s\n"), ua->argk[i]);
492          return 0;
493       }
494       /* Found keyword in kw[] list, process it */
495       switch (j) {
496       case 0:                            /* jobid */
497          if (!has_value(ua, i)) {
498             return 0;
499          }
500          if (*rx->JobIds != 0) {
501             pm_strcat(rx->JobIds, ",");
502          }
503          pm_strcat(rx->JobIds, ua->argv[i]);
504          done = true;
505          break;
506       case 1:                            /* current */
507          /*
508           * Note, we add one second here just to include any job
509           *  that may have finished within the current second,
510           *  which happens a lot in scripting small jobs.
511           */
512          bstrutime(date, sizeof(date), now);
513          have_date = true;
514          break;
515       case 2:                            /* before */
516          if (have_date || !has_value(ua, i)) {
517             return 0;
518          }
519          if (str_to_utime(ua->argv[i]) == 0) {
520             ua->error_msg(_("Improper date format: %s\n"), ua->argv[i]);
521             return 0;
522          }
523          bstrncpy(date, ua->argv[i], sizeof(date));
524          have_date = true;
525          break;
526       case 3:                            /* file */
527       case 4:                            /* dir */
528          if (!has_value(ua, i)) {
529             return 0;
530          }
531          if (!have_date) {
532             bstrutime(date, sizeof(date), now);
533          }
534          if (!get_client_name(ua, rx)) {
535             return 0;
536          }
537          pm_strcpy(ua->cmd, ua->argv[i]);
538          insert_one_file_or_dir(ua, rx, date, j==4);
539          return 2;
540       case 5:                            /* select */
541          if (!have_date) {
542             bstrutime(date, sizeof(date), now);
543          }
544          if (!select_backups_before_date(ua, rx, date)) {
545             return 0;
546          }
547          done = true;
548          break;
549       case 6:                            /* pool specified */
550          if (!has_value(ua, i)) {
551             return 0;
552          }
553          rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
554          if (!rx->pool) {
555             ua->error_msg(_("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
556             return 0;
557          }
558          if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
559             rx->pool = NULL;
560             ua->error_msg(_("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
561             return 0;
562          }
563          break;
564       case 7:                         /* all specified */
565          rx->all = true;
566          break;
567       /*
568        * All keywords 7 or greater are ignored or handled by a select prompt
569        */
570       default:
571          break;
572       }
573    }
574
575    if (!done) {
576       ua->send_msg(_("\nFirst you select one or more JobIds that contain files\n"
577                   "to be restored. You will be presented several methods\n"
578                   "of specifying the JobIds. Then you will be allowed to\n"
579                   "select which files from those JobIds are to be restored.\n\n"));
580    }
581
582    /* If choice not already made above, prompt */
583    for ( ; !done; ) {
584       char *fname;
585       int len;
586       bool gui_save;
587       db_list_ctx jobids;
588
589       start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
590       for (int i=0; list[i]; i++) {
591          add_prompt(ua, list[i]);
592       }
593       done = true;
594       switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
595       case -1:                        /* error or cancel */
596          return 0;
597       case 0:                         /* list last 20 Jobs run */
598          if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
599             ua->error_msg(_("SQL query not authorized.\n"));
600             return 0;
601          }
602          gui_save = ua->jcr->gui;
603          ua->jcr->gui = true;
604          db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
605          ua->jcr->gui = gui_save;
606          done = false;
607          break;
608       case 1:                         /* list where a file is saved */
609          if (!get_client_name(ua, rx)) {
610             return 0;
611          }
612          if (!get_cmd(ua, _("Enter Filename (no path):"))) {
613             return 0;
614          }
615          len = strlen(ua->cmd);
616          fname = (char *)malloc(len * 2 + 1);
617          db_escape_string(ua->jcr, ua->db, fname, ua->cmd, len);
618          Mmsg(rx->query, uar_file[db_get_type_index(ua->db)], rx->ClientName, fname);
619          free(fname);
620          gui_save = ua->jcr->gui;
621          ua->jcr->gui = true;
622          db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
623          ua->jcr->gui = gui_save;
624          done = false;
625          break;
626       case 2:                         /* enter a list of JobIds */
627          if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
628             return 0;
629          }
630          pm_strcpy(rx->JobIds, ua->cmd);
631          break;
632       case 3:                         /* Enter an SQL list command */
633          if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
634             ua->error_msg(_("SQL query not authorized.\n"));
635             return 0;
636          }
637          if (!get_cmd(ua, _("Enter SQL list command: "))) {
638             return 0;
639          }
640          gui_save = ua->jcr->gui;
641          ua->jcr->gui = true;
642          db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
643          ua->jcr->gui = gui_save;
644          done = false;
645          break;
646       case 4:                         /* Select the most recent backups */
647          if (!have_date) {
648             bstrutime(date, sizeof(date), now);
649          }
650          if (!select_backups_before_date(ua, rx, date)) {
651             return 0;
652          }
653          break;
654       case 5:                         /* select backup at specified time */
655          if (!have_date) {
656             if (!get_date(ua, date, sizeof(date))) {
657                return 0;
658             }
659          }
660          if (!select_backups_before_date(ua, rx, date)) {
661             return 0;
662          }
663          break;
664       case 6:                         /* Enter files */
665          if (!have_date) {
666             bstrutime(date, sizeof(date), now);
667          }
668          if (!get_client_name(ua, rx)) {
669             return 0;
670          }
671          ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
672                         "containing a list of file names with paths, and terminate\n"
673                         "them with a blank line.\n"));
674          for ( ;; ) {
675             if (!get_cmd(ua, _("Enter full filename: "))) {
676                return 0;
677             }
678             len = strlen(ua->cmd);
679             if (len == 0) {
680                break;
681             }
682             insert_one_file_or_dir(ua, rx, date, false);
683          }
684          return 2;
685        case 7:                        /* enter files backed up before specified time */
686          if (!have_date) {
687             if (!get_date(ua, date, sizeof(date))) {
688                return 0;
689             }
690          }
691          if (!get_client_name(ua, rx)) {
692             return 0;
693          }
694          ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
695                         "containing a list of file names with paths, and terminate\n"
696                         "them with a blank line.\n"));
697          for ( ;; ) {
698             if (!get_cmd(ua, _("Enter full filename: "))) {
699                return 0;
700             }
701             len = strlen(ua->cmd);
702             if (len == 0) {
703                break;
704             }
705             insert_one_file_or_dir(ua, rx, date, false);
706          }
707          return 2;
708
709       case 8:                         /* Find JobIds for current backup */
710          if (!have_date) {
711             bstrutime(date, sizeof(date), now);
712          }
713          if (!select_backups_before_date(ua, rx, date)) {
714             return 0;
715          }
716          done = false;
717          break;
718
719       case 9:                         /* Find JobIds for give date */
720          if (!have_date) {
721             if (!get_date(ua, date, sizeof(date))) {
722                return 0;
723             }
724          }
725          if (!select_backups_before_date(ua, rx, date)) {
726             return 0;
727          }
728          done = false;
729          break;
730
731       case 10:                        /* Enter directories */
732          if (*rx->JobIds != 0) {
733             ua->send_msg(_("You have already selected the following JobIds: %s\n"),
734                rx->JobIds);
735          } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
736             if (*rx->JobIds != 0 && *ua->cmd) {
737                pm_strcat(rx->JobIds, ",");
738             }
739             pm_strcat(rx->JobIds, ua->cmd);
740          }
741          if (*rx->JobIds == 0 || *rx->JobIds == '.') {
742             *rx->JobIds = 0;
743             return 0;                 /* nothing entered, return */
744          }
745          if (!have_date) {
746             bstrutime(date, sizeof(date), now);
747          }
748          if (!get_client_name(ua, rx)) {
749             return 0;
750          }
751          ua->send_msg(_("Enter full directory names or start the name\n"
752                         "with a < to indicate it is a filename containing a list\n"
753                         "of directories and terminate them with a blank line.\n"));
754          for ( ;; ) {
755             if (!get_cmd(ua, _("Enter directory name: "))) {
756                return 0;
757             }
758             len = strlen(ua->cmd);
759             if (len == 0) {
760                break;
761             }
762             /* Add trailing slash to end of directory names */
763             if (ua->cmd[0] != '<' && !IsPathSeparator(ua->cmd[len-1])) {
764                strcat(ua->cmd, "/");
765             }
766             insert_one_file_or_dir(ua, rx, date, true);
767          }
768          return 2;
769
770       case 11:                        /* Choose a jobid and select jobs */
771          if (!get_cmd(ua, _("Enter JobId to get the state to restore: ")) ||
772              !is_an_integer(ua->cmd)) 
773          {
774             return 0;
775          }
776
777          memset(&jr, 0, sizeof(JOB_DBR));
778          jr.JobId = str_to_int64(ua->cmd);
779          if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
780             ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
781                           ua->cmd, db_strerror(ua->db));
782             return 0;
783          }
784          ua->send_msg(_("Selecting jobs to build the Full state at %s\n"),
785                       jr.cStartTime);
786          jr.JobLevel = L_INCREMENTAL; /* Take Full+Diff+Incr */
787          if (!db_accurate_get_jobids(ua->jcr, ua->db, &jr, &jobids)) {
788             return 0;
789          }
790          pm_strcpy(rx->JobIds, jobids.list);
791          Dmsg1(30, "Item 12: jobids = %s\n", rx->JobIds);
792          break;
793       case 12:                        /* Cancel or quit */
794          return 0;
795       }
796    }
797
798    memset(&jr, 0, sizeof(JOB_DBR));
799    POOLMEM *JobIds = get_pool_memory(PM_FNAME);
800    *JobIds = 0;
801    rx->TotalFiles = 0;
802    /*        
803     * Find total number of files to be restored, and filter the JobId
804     *  list to contain only ones permitted by the ACL conditions.
805     */
806    for (p=rx->JobIds; ; ) {
807       char ed1[50];
808       int stat = get_next_jobid_from_list(&p, &JobId);
809       if (stat < 0) {
810          ua->error_msg(_("Invalid JobId in list.\n"));
811          free_pool_memory(JobIds);
812          return 0;
813       }
814       if (stat == 0) {
815          break;
816       }
817       if (jr.JobId == JobId) {
818          continue;                    /* duplicate of last JobId */
819       }
820       memset(&jr, 0, sizeof(JOB_DBR));
821       jr.JobId = JobId;
822       if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
823          ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
824             edit_int64(JobId, ed1), db_strerror(ua->db));
825          free_pool_memory(JobIds);
826          return 0;
827       }
828       if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
829          ua->error_msg(_("Access to JobId=%s (Job \"%s\") not authorized. Not selected.\n"),
830             edit_int64(JobId, ed1), jr.Name);
831          continue;
832       }
833       if (*JobIds != 0) {
834          pm_strcat(JobIds, ",");
835       }
836       pm_strcat(JobIds, edit_int64(JobId, ed1));
837       rx->TotalFiles += jr.JobFiles;
838    }
839    free_pool_memory(rx->JobIds);
840    rx->JobIds = JobIds;               /* Set ACL filtered list */
841    if (*rx->JobIds == 0) {
842       ua->warning_msg(_("No Jobs selected.\n"));
843       return 0;
844    }
845
846    if (strchr(rx->JobIds,',')) {
847       ua->info_msg(_("You have selected the following JobIds: %s\n"), rx->JobIds);
848    } else {
849       ua->info_msg(_("You have selected the following JobId: %s\n"), rx->JobIds);
850    }
851    return 1;
852 }
853
854 /*
855  * Get date from user
856  */
857 static bool get_date(UAContext *ua, char *date, int date_len)
858 {
859    ua->send_msg(_("The restored files will the most current backup\n"
860                   "BEFORE the date you specify below.\n\n"));
861    for ( ;; ) {
862       if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
863          return false;
864       }
865       if (str_to_utime(ua->cmd) != 0) {
866          break;
867       }
868       ua->error_msg(_("Improper date format.\n"));
869    }
870    bstrncpy(date, ua->cmd, date_len);
871    return true;
872 }
873
874 /*
875  * Insert a single file, or read a list of files from a file
876  */
877 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
878 {
879    FILE *ffd;
880    char file[5000];
881    char *p = ua->cmd;
882    int line = 0;
883
884    switch (*p) {
885    case '<':
886       p++;
887       if ((ffd = fopen(p, "rb")) == NULL) {
888          berrno be;
889          ua->error_msg(_("Cannot open file %s: ERR=%s\n"),
890             p, be.bstrerror());
891          break;
892       }
893       while (fgets(file, sizeof(file), ffd)) {
894          line++;
895          if (dir) {
896             if (!insert_dir_into_findex_list(ua, rx, file, date)) {
897                ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
898             }
899          } else {
900             if (!insert_file_into_findex_list(ua, rx, file, date)) {
901                ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
902             }
903          }
904       }
905       fclose(ffd);
906       break;
907    case '?':
908       p++;
909       insert_table_into_findex_list(ua, rx, p);
910       break;
911    default:
912       if (dir) {
913          insert_dir_into_findex_list(ua, rx, ua->cmd, date);
914       } else {
915          insert_file_into_findex_list(ua, rx, ua->cmd, date);
916       }
917       break;
918    }
919 }
920
921 /*
922  * For a given file (path+filename), split into path and file, then
923  *   lookup the most recent backup in the catalog to get the JobId
924  *   and FileIndex, then insert them into the findex list.
925  */
926 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
927                                         char *date)
928 {
929    strip_trailing_newline(file);
930    split_path_and_filename(ua, rx, file);
931    if (*rx->JobIds == 0) {
932       Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname, 
933            rx->ClientName);
934    } else {
935       Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
936            rx->path, rx->fname, rx->ClientName);
937    }
938    rx->found = false;
939    /* Find and insert jobid and File Index */
940    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
941       ua->error_msg(_("Query failed: %s. ERR=%s\n"),
942          rx->query, db_strerror(ua->db));
943    }
944    if (!rx->found) {
945       ua->error_msg(_("No database record found for: %s\n"), file);
946 //    ua->error_msg("Query=%s\n", rx->query);
947       return true;
948    }
949    return true;
950 }
951
952 /*
953  * For a given path lookup the most recent backup in the catalog
954  * to get the JobId and FileIndexes of all files in that directory.
955  */
956 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
957                                         char *date)
958 {  
959    strip_trailing_junk(dir);
960    if (*rx->JobIds == 0) {
961       ua->error_msg(_("No JobId specified cannot continue.\n"));
962       return false;
963    } else {
964       Mmsg(rx->query, uar_jobid_fileindex_from_dir[db_get_type_index(ua->db)], rx->JobIds, dir, rx->ClientName);
965    }
966    rx->found = false;
967    /* Find and insert jobid and File Index */
968    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
969       ua->error_msg(_("Query failed: %s. ERR=%s\n"),
970          rx->query, db_strerror(ua->db));
971    }
972    if (!rx->found) {
973       ua->error_msg(_("No database record found for: %s\n"), dir);
974       return true;
975    }
976    return true;
977 }
978
979 /*
980  * Get the JobId and FileIndexes of all files in the specified table
981  */
982 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
983 {
984    strip_trailing_junk(table);
985    Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
986
987    rx->found = false;
988    /* Find and insert jobid and File Index */
989    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
990       ua->error_msg(_("Query failed: %s. ERR=%s\n"),
991          rx->query, db_strerror(ua->db));
992    }
993    if (!rx->found) {
994       ua->error_msg(_("No table found: %s\n"), table);
995       return true;
996    }
997    return true;
998 }
999
1000 static void split_path_and_filename(UAContext *ua, RESTORE_CTX *rx, char *name)
1001 {
1002    char *p, *f;
1003
1004    /* Find path without the filename.
1005     * I.e. everything after the last / is a "filename".
1006     * OK, maybe it is a directory name, but we treat it like
1007     * a filename. If we don't find a / then the whole name
1008     * must be a path name (e.g. c:).
1009     */
1010    for (p=f=name; *p; p++) {
1011       if (IsPathSeparator(*p)) {
1012          f = p;                       /* set pos of last slash */
1013       }
1014    }
1015    if (IsPathSeparator(*f)) {         /* did we find a slash? */
1016       f++;                            /* yes, point to filename */
1017    } else {                           /* no, whole thing must be path name */
1018       f = p;
1019    }
1020
1021    /* If filename doesn't exist (i.e. root directory), we
1022     * simply create a blank name consisting of a single
1023     * space. This makes handling zero length filenames
1024     * easier.
1025     */
1026    rx->fnl = p - f;
1027    if (rx->fnl > 0) {
1028       rx->fname = check_pool_memory_size(rx->fname, 2*(rx->fnl)+1);
1029       db_escape_string(ua->jcr, ua->db, rx->fname, f, rx->fnl);
1030    } else {
1031       rx->fname[0] = 0;
1032       rx->fnl = 0;
1033    }
1034
1035    rx->pnl = f - name;
1036    if (rx->pnl > 0) {
1037       rx->path = check_pool_memory_size(rx->path, 2*(rx->pnl)+1);
1038       db_escape_string(ua->jcr, ua->db, rx->path, name, rx->pnl);
1039    } else {
1040       rx->path[0] = 0;
1041       rx->pnl = 0;
1042    }
1043
1044    Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
1045 }
1046
1047 static bool ask_for_fileregex(UAContext *ua, RESTORE_CTX *rx)
1048 {
1049    if (find_arg(ua, NT_("all")) >= 0) {  /* if user enters all on command line */
1050       return true;                       /* select everything */
1051    }
1052    ua->send_msg(_("\n\nFor one or more of the JobIds selected, no files were found,\n"
1053                  "so file selection is not possible.\n"
1054                  "Most likely your retention policy pruned the files.\n"));
1055    if (get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
1056       if (ua->pint32_val == 1)
1057          return true;
1058       while (get_cmd(ua, _("\nRegexp matching files to restore? (empty to abort): "))) {
1059          if (ua->cmd[0] == '\0') {
1060             break;
1061          } else {
1062             regex_t *fileregex_re = NULL;
1063             int rc;
1064             char errmsg[500] = "";
1065
1066             fileregex_re = (regex_t *)bmalloc(sizeof(regex_t));
1067             rc = regcomp(fileregex_re, ua->cmd, REG_EXTENDED|REG_NOSUB);
1068             if (rc != 0) {
1069                regerror(rc, fileregex_re, errmsg, sizeof(errmsg));
1070             }
1071             regfree(fileregex_re);
1072             free(fileregex_re);
1073             if (*errmsg) {
1074                ua->send_msg(_("Regex compile error: %s\n"), errmsg);
1075             } else {
1076                rx->bsr->fileregex = bstrdup(ua->cmd);
1077                return true;
1078             }
1079          }
1080       }
1081    }
1082    return false;
1083 }
1084
1085 /* Walk on the delta_list of a TREE_NODE item and insert all parts
1086  * TODO: Optimize for bootstrap creation, remove recursion
1087  * 6 -> 5 -> 4 -> 3 -> 2 -> 1 -> 0
1088  * should insert as
1089  * 0, 1, 2, 3, 4, 5, 6
1090  */
1091 static void add_delta_list_findex(RESTORE_CTX *rx, struct delta_list *lst)
1092 {
1093    if (lst == NULL) {
1094       return;
1095    }
1096    if (lst->next) {
1097       add_delta_list_findex(rx, lst->next);
1098    }
1099    add_findex(rx->bsr, lst->JobId, lst->FileIndex);
1100 }
1101
1102 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
1103 {
1104    TREE_CTX tree;
1105    JobId_t JobId, last_JobId;
1106    char *p;
1107    bool OK = true;
1108    char ed1[50];
1109
1110    memset(&tree, 0, sizeof(TREE_CTX));
1111    /*
1112     * Build the directory tree containing JobIds user selected
1113     */
1114    tree.root = new_tree(rx->TotalFiles);
1115    tree.ua = ua;
1116    tree.all = rx->all;
1117    last_JobId = 0;
1118    /*
1119     * For display purposes, the same JobId, with different volumes may
1120     * appear more than once, however, we only insert it once.
1121     */
1122    p = rx->JobIds;
1123    tree.FileEstimate = 0;
1124    if (get_next_jobid_from_list(&p, &JobId) > 0) {
1125       /* Use first JobId as estimate of the number of files to restore */
1126       Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
1127       if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1128          ua->error_msg("%s\n", db_strerror(ua->db));
1129       }
1130       if (rx->found) {
1131          /* Add about 25% more than this job for over estimate */
1132          tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
1133          tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
1134       }
1135    }
1136
1137    ua->info_msg(_("\nBuilding directory tree for JobId(s) %s ...  "),
1138                 rx->JobIds);
1139
1140 #define new_get_file_list
1141 #ifdef new_get_file_list
1142    if (!db_get_file_list(ua->jcr, ua->db, 
1143                          rx->JobIds, false /* do not use md5 */, 
1144                          true /* get delta */,
1145                          insert_tree_handler, (void *)&tree))
1146    {
1147       ua->error_msg("%s", db_strerror(ua->db));
1148    }
1149    if (*rx->BaseJobIds) {
1150       pm_strcat(rx->JobIds, ",");
1151       pm_strcat(rx->JobIds, rx->BaseJobIds);
1152    }
1153 #else
1154    for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1155       char ed1[50];
1156
1157       if (JobId == last_JobId) {
1158          continue;                    /* eliminate duplicate JobIds */
1159       }
1160       last_JobId = JobId;
1161       /*
1162        * Find files for this JobId and insert them in the tree
1163        */
1164       Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
1165       if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
1166          ua->error_msg("%s", db_strerror(ua->db));
1167       }
1168    }
1169 #endif
1170    /* 
1171     * At this point, the tree is built, so we can garbage collect
1172     * any memory released by the SQL engine that RedHat has 
1173     * not returned to the OS :-( 
1174     */
1175     garbage_collect_memory();
1176
1177    /*
1178     * Look at the first JobId on the list (presumably the oldest) and
1179     *  if it is marked purged, don't do the manual selection because
1180     *  the Job was pruned, so the tree is incomplete.
1181     */
1182    if (tree.FileCount != 0) {
1183       /* Find out if any Job is purged */
1184       Mmsg(rx->query, "SELECT SUM(PurgedFiles) FROM Job WHERE JobId IN (%s)", rx->JobIds);
1185       if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1186          ua->error_msg("%s\n", db_strerror(ua->db));
1187       }
1188       /* rx->JobId is the PurgedFiles flag */
1189       if (rx->found && rx->JobId > 0) {
1190          tree.FileCount = 0;           /* set count to zero, no tree selection */
1191       }
1192    }
1193    if (tree.FileCount == 0) {
1194       OK = ask_for_fileregex(ua, rx);
1195       if (OK) {
1196          last_JobId = 0;
1197          for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1198              if (JobId == last_JobId) {
1199                 continue;                    /* eliminate duplicate JobIds */
1200              }
1201              add_findex_all(rx->bsr, JobId);
1202          }
1203       }
1204    } else {
1205       char ec1[50];
1206       if (tree.all) {
1207          ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
1208                       edit_uint64_with_commas(tree.FileCount, ec1));
1209       } else {
1210          ua->info_msg(_("\n%s files inserted into the tree.\n"),
1211                       edit_uint64_with_commas(tree.FileCount, ec1));
1212       }
1213
1214       if (find_arg(ua, NT_("done")) < 0) {
1215          /* Let the user interact in selecting which files to restore */
1216          OK = user_select_files_from_tree(&tree);
1217       }
1218
1219       /*
1220        * Walk down through the tree finding all files marked to be
1221        *  extracted making a bootstrap file.
1222        */
1223       if (OK) {
1224          for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
1225             Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
1226             if (node->extract || node->extract_dir) {
1227                Dmsg3(400, "JobId=%lld type=%d FI=%d\n", (uint64_t)node->JobId, node->type, node->FileIndex);
1228                /* TODO: optimize bsr insertion when jobid are non sorted */
1229                add_delta_list_findex(rx, node->delta_list);
1230                add_findex(rx->bsr, node->JobId, node->FileIndex);
1231                if (node->extract && node->type != TN_NEWDIR) {
1232                   rx->selected_files++;  /* count only saved files */
1233                }
1234             }
1235          }
1236       }
1237    }
1238
1239    free_tree(tree.root);              /* free the directory tree */
1240    return OK;
1241 }
1242
1243
1244 /*
1245  * This routine is used to get the current backup or a backup
1246  *   before the specified date.
1247  */
1248 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
1249 {
1250    bool ok = false;
1251    FILESET_DBR fsr;
1252    CLIENT_DBR cr;
1253    char fileset_name[MAX_NAME_LENGTH];
1254    char ed1[50], ed2[50];
1255    char pool_select[MAX_NAME_LENGTH];
1256    int i;
1257
1258    /* Create temp tables */
1259    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1260    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1261    if (!db_sql_query(ua->db, uar_create_temp[db_get_type_index(ua->db)], NULL, NULL)) {
1262       ua->error_msg("%s\n", db_strerror(ua->db));
1263    }
1264    if (!db_sql_query(ua->db, uar_create_temp1[db_get_type_index(ua->db)], NULL, NULL)) {
1265       ua->error_msg("%s\n", db_strerror(ua->db));
1266    }
1267    /*
1268     * Select Client from the Catalog
1269     */
1270    memset(&cr, 0, sizeof(cr));
1271    if (!get_client_dbr(ua, &cr)) {
1272       goto bail_out;
1273    }
1274    bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1275
1276    /*
1277     * Get FileSet
1278     */
1279    memset(&fsr, 0, sizeof(fsr));
1280    i = find_arg_with_value(ua, "FileSet");
1281
1282    if (i >= 0 && is_name_valid(ua->argv[i], &ua->errmsg)) {
1283       bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1284       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1285          ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1286             db_strerror(ua->db));
1287          i = -1;
1288       }
1289    } else if (i >= 0) {         /* name is invalid */
1290       ua->error_msg(_("FileSet argument: %s\n"), ua->errmsg);
1291    }
1292
1293    if (i < 0) {                       /* fileset not found */
1294       edit_int64(cr.ClientId, ed1);
1295       Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1296       start_prompt(ua, _("The defined FileSet resources are:\n"));
1297       if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1298          ua->error_msg("%s\n", db_strerror(ua->db));
1299       }
1300       if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1301                  fileset_name, sizeof(fileset_name)) < 0) {
1302          ua->error_msg(_("No FileSet found for client \"%s\".\n"), cr.Name);
1303          goto bail_out;
1304       }
1305
1306       bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1307       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1308          ua->warning_msg(_("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1309          ua->send_msg(_("This probably means you modified the FileSet.\n"
1310                      "Continuing anyway.\n"));
1311       }
1312    }
1313
1314    /* If Pool specified, add PoolId specification */
1315    pool_select[0] = 0;
1316    if (rx->pool) {
1317       POOL_DBR pr;
1318       memset(&pr, 0, sizeof(pr));
1319       bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1320       if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1321          bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ", 
1322             edit_int64(pr.PoolId, ed1));
1323       } else {
1324          ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1325       }
1326    }
1327
1328    /* Find JobId of last Full backup for this client, fileset */
1329    edit_int64(cr.ClientId, ed1);
1330    Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1331          pool_select);
1332    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1333       ua->error_msg("%s\n", db_strerror(ua->db));
1334       goto bail_out;
1335    }
1336
1337    /* Find all Volumes used by that JobId */
1338    if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1339       ua->error_msg("%s\n", db_strerror(ua->db));
1340       goto bail_out;
1341    }
1342
1343    /* Note, this is needed because I don't seem to get the callback
1344     * from the call just above.
1345     */
1346    rx->JobTDate = 0;
1347    if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1348       ua->warning_msg("%s\n", db_strerror(ua->db));
1349    }
1350    if (rx->JobTDate == 0) {
1351       ua->error_msg(_("No Full backup before %s found.\n"), date);
1352       goto bail_out;
1353    }
1354
1355    /* Now find most recent Differental Job after Full save, if any */
1356    Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1357         edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1358    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1359       ua->warning_msg("%s\n", db_strerror(ua->db));
1360    }
1361    /* Now update JobTDate to look into Differental, if any */
1362    rx->JobTDate = 0;
1363    if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1364       ua->warning_msg("%s\n", db_strerror(ua->db));
1365    }
1366    if (rx->JobTDate == 0) {
1367       ua->error_msg(_("No Full backup before %s found.\n"), date);
1368       goto bail_out;
1369    }
1370
1371    /* Now find all Incremental Jobs after Full/dif save */
1372    Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1373         edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1374    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1375       ua->warning_msg("%s\n", db_strerror(ua->db));
1376    }
1377
1378    /* Get the JobIds from that list */
1379    rx->last_jobid[0] = rx->JobIds[0] = 0;
1380
1381    if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1382       ua->warning_msg("%s\n", db_strerror(ua->db));
1383    }
1384
1385    if (rx->JobIds[0] != 0) {
1386       if (find_arg(ua, NT_("copies")) > 0) {
1387          /* Display a list of all copies */
1388          db_list_copies_records(ua->jcr, ua->db, 0, rx->JobIds, 
1389                                 prtit, ua, HORZ_LIST);
1390       }
1391       /* Display a list of Jobs selected for this restore */
1392       db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1,HORZ_LIST);
1393       ok = true;
1394
1395    } else {
1396       ua->warning_msg(_("No jobs found.\n"));
1397    }
1398
1399 bail_out:
1400    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1401    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1402    return ok;
1403 }
1404
1405 static int restore_count_handler(void *ctx, int num_fields, char **row)
1406 {
1407    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1408    rx->JobId = str_to_int64(row[0]);
1409    rx->found = true;
1410    return 0;
1411 }
1412
1413 /*
1414  * Callback handler to get JobId and FileIndex for files
1415  *   can insert more than one depending on the caller.
1416  */
1417 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1418 {
1419    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1420
1421    Dmsg2(200, "JobId=%s FileIndex=%s\n", row[0], row[1]);
1422    rx->JobId = str_to_int64(row[0]);
1423    add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1424    rx->found = true;
1425    rx->selected_files++;
1426    return 0;
1427 }
1428
1429 /*
1430  * Callback handler make list of JobIds
1431  */
1432 static int jobid_handler(void *ctx, int num_fields, char **row)
1433 {
1434    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1435
1436    if (strcmp(rx->last_jobid, row[0]) == 0) {
1437       return 0;                       /* duplicate id */
1438    }
1439    bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1440    if (rx->JobIds[0] != 0) {
1441       pm_strcat(rx->JobIds, ",");
1442    }
1443    pm_strcat(rx->JobIds, row[0]);
1444    return 0;
1445 }
1446
1447
1448 /*
1449  * Callback handler to pickup last Full backup JobTDate
1450  */
1451 static int last_full_handler(void *ctx, int num_fields, char **row)
1452 {
1453    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1454
1455    rx->JobTDate = str_to_int64(row[1]);
1456    return 0;
1457 }
1458
1459 /*
1460  * Callback handler build FileSet name prompt list
1461  */
1462 static int fileset_handler(void *ctx, int num_fields, char **row)
1463 {
1464    /* row[0] = FileSet (name) */
1465    if (row[0]) {
1466       add_prompt((UAContext *)ctx, row[0]);
1467    }
1468    return 0;
1469 }
1470
1471 /*
1472  * Free names in the list
1473  */
1474 static void free_name_list(NAME_LIST *name_list)
1475 {
1476    for (int i=0; i < name_list->num_ids; i++) {
1477       free(name_list->name[i]);
1478    }
1479    bfree_and_null(name_list->name);
1480    name_list->max_ids = 0;
1481    name_list->num_ids = 0;
1482 }
1483
1484 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType) 
1485 {
1486    STORE *store;
1487
1488    if (rx.store) {
1489       Dmsg1(200, "Already have store=%s\n", rx.store->name());
1490       return;
1491    }
1492    /*
1493     * Try looking up Storage by name
1494     */
1495    LockRes();
1496    foreach_res(store, R_STORAGE) {
1497       if (strcmp(Storage, store->name()) == 0) {
1498          if (acl_access_ok(ua, Storage_ACL, store->name())) {
1499             rx.store = store;
1500          }
1501          break;
1502       }
1503    }
1504    UnlockRes();
1505
1506    if (rx.store) {
1507       /* Check if an explicit storage resource is given */
1508       store = NULL;
1509       int i = find_arg_with_value(ua, "storage");
1510       if (i > 0) {
1511          store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1512          if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1513             store = NULL;
1514          }
1515       }
1516       if (store && (store != rx.store)) {
1517          ua->info_msg(_("Warning default storage overridden by \"%s\" on command line.\n"),
1518             store->name());
1519          rx.store = store;
1520          Dmsg1(200, "Set store=%s\n", rx.store->name());
1521       }
1522       return;
1523    }
1524
1525    /* If no storage resource, try to find one from MediaType */
1526    if (!rx.store) {
1527       LockRes();
1528       foreach_res(store, R_STORAGE) {
1529          if (strcmp(MediaType, store->media_type) == 0) {
1530             if (acl_access_ok(ua, Storage_ACL, store->name())) {
1531                rx.store = store;
1532                Dmsg1(200, "Set store=%s\n", rx.store->name());
1533                if (Storage == NULL) {
1534                   ua->warning(_("Using Storage \"%s\" from MediaType \"%s\".\n"),
1535                      store->name(), MediaType);
1536                } else {
1537                   ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1538                      Storage, store->name(), MediaType);
1539                }
1540             }
1541             UnlockRes();
1542             return;
1543          }
1544       }
1545       UnlockRes();
1546       ua->warning_msg(_("\nUnable to find Storage resource for\n"
1547          "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1548    }
1549
1550    /* Take command line arg, or ask user if none */
1551    rx.store = get_storage_resource(ua, false /* don't use default */);
1552    if (rx.store) {
1553       Dmsg1(200, "Set store=%s\n", rx.store->name());
1554    }
1555
1556 }