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