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