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