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