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