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