]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
Switch from GPLv2 to AGPLv3
[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, rx->JobIds, insert_tree_handler, (void *)&tree)) {
1110       ua->error_msg("%s", db_strerror(ua->db));
1111    }
1112    if (*rx->BaseJobIds) {
1113       pm_strcat(rx->JobIds, ",");
1114       pm_strcat(rx->JobIds, rx->BaseJobIds);
1115    }
1116 #else
1117    for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1118       char ed1[50];
1119
1120       if (JobId == last_JobId) {
1121          continue;                    /* eliminate duplicate JobIds */
1122       }
1123       last_JobId = JobId;
1124       /*
1125        * Find files for this JobId and insert them in the tree
1126        */
1127       Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
1128       if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
1129          ua->error_msg("%s", db_strerror(ua->db));
1130       }
1131    }
1132 #endif
1133    /*
1134     * Look at the first JobId on the list (presumably the oldest) and
1135     *  if it is marked purged, don't do the manual selection because
1136     *  the Job was pruned, so the tree is incomplete.
1137     */
1138    if (tree.FileCount != 0) {
1139       /* Find out if any Job is purged */
1140       Mmsg(rx->query, "SELECT SUM(PurgedFiles) FROM Job WHERE JobId IN (%s)", rx->JobIds);
1141       if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1142          ua->error_msg("%s\n", db_strerror(ua->db));
1143       }
1144       /* rx->JobId is the PurgedFiles flag */
1145       if (rx->found && rx->JobId > 0) {
1146          tree.FileCount = 0;           /* set count to zero, no tree selection */
1147       }
1148    }
1149    if (tree.FileCount == 0) {
1150       OK = ask_for_fileregex(ua, rx);
1151       if (OK) {
1152          last_JobId = 0;
1153          for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1154              if (JobId == last_JobId) {
1155                 continue;                    /* eliminate duplicate JobIds */
1156              }
1157              add_findex_all(rx->bsr, JobId);
1158          }
1159       }
1160    } else {
1161       char ec1[50];
1162       if (tree.all) {
1163          ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
1164                       edit_uint64_with_commas(tree.FileCount, ec1));
1165       } else {
1166          ua->info_msg(_("\n%s files inserted into the tree.\n"),
1167                       edit_uint64_with_commas(tree.FileCount, ec1));
1168       }
1169
1170       if (find_arg(ua, NT_("done")) < 0) {
1171          /* Let the user interact in selecting which files to restore */
1172          OK = user_select_files_from_tree(&tree);
1173       }
1174
1175       /*
1176        * Walk down through the tree finding all files marked to be
1177        *  extracted making a bootstrap file.
1178        */
1179       if (OK) {
1180          for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
1181             Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
1182             if (node->extract || node->extract_dir) {
1183                Dmsg3(400, "JobId=%lld type=%d FI=%d\n", (uint64_t)node->JobId, node->type, node->FileIndex);
1184                add_findex(rx->bsr, node->JobId, node->FileIndex);
1185                if (node->extract && node->type != TN_NEWDIR) {
1186                   rx->selected_files++;  /* count only saved files */
1187                }
1188             }
1189          }
1190       }
1191    }
1192
1193    free_tree(tree.root);              /* free the directory tree */
1194    return OK;
1195 }
1196
1197
1198 /*
1199  * This routine is used to get the current backup or a backup
1200  *   before the specified date.
1201  */
1202 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
1203 {
1204    bool ok = false;
1205    FILESET_DBR fsr;
1206    CLIENT_DBR cr;
1207    char fileset_name[MAX_NAME_LENGTH];
1208    char ed1[50], ed2[50];
1209    char pool_select[MAX_NAME_LENGTH];
1210    int i;
1211
1212    /* Create temp tables */
1213    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1214    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1215    if (!db_sql_query(ua->db, uar_create_temp[db_type], NULL, NULL)) {
1216       ua->error_msg("%s\n", db_strerror(ua->db));
1217    }
1218    if (!db_sql_query(ua->db, uar_create_temp1[db_type], NULL, NULL)) {
1219       ua->error_msg("%s\n", db_strerror(ua->db));
1220    }
1221    /*
1222     * Select Client from the Catalog
1223     */
1224    memset(&cr, 0, sizeof(cr));
1225    if (!get_client_dbr(ua, &cr)) {
1226       goto bail_out;
1227    }
1228    bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1229
1230    /*
1231     * Get FileSet
1232     */
1233    memset(&fsr, 0, sizeof(fsr));
1234    i = find_arg_with_value(ua, "FileSet");
1235    if (i >= 0) {
1236       bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1237       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1238          ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1239             db_strerror(ua->db));
1240          i = -1;
1241       }
1242    }
1243    if (i < 0) {                       /* fileset not found */
1244       edit_int64(cr.ClientId, ed1);
1245       Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1246       start_prompt(ua, _("The defined FileSet resources are:\n"));
1247       if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1248          ua->error_msg("%s\n", db_strerror(ua->db));
1249       }
1250       if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1251                  fileset_name, sizeof(fileset_name)) < 0) {
1252          ua->error_msg(_("No FileSet found for client \"%s\".\n"), cr.Name);
1253          goto bail_out;
1254       }
1255
1256       bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1257       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1258          ua->warning_msg(_("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1259          ua->send_msg(_("This probably means you modified the FileSet.\n"
1260                      "Continuing anyway.\n"));
1261       }
1262    }
1263
1264    /* If Pool specified, add PoolId specification */
1265    pool_select[0] = 0;
1266    if (rx->pool) {
1267       POOL_DBR pr;
1268       memset(&pr, 0, sizeof(pr));
1269       bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1270       if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1271          bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ", 
1272             edit_int64(pr.PoolId, ed1));
1273       } else {
1274          ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1275       }
1276    }
1277
1278    /* Find JobId of last Full backup for this client, fileset */
1279    edit_int64(cr.ClientId, ed1);
1280    Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1281          pool_select);
1282    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1283       ua->error_msg("%s\n", db_strerror(ua->db));
1284       goto bail_out;
1285    }
1286
1287    /* Find all Volumes used by that JobId */
1288    if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1289       ua->error_msg("%s\n", db_strerror(ua->db));
1290       goto bail_out;
1291    }
1292
1293    /* Note, this is needed because I don't seem to get the callback
1294     * from the call just above.
1295     */
1296    rx->JobTDate = 0;
1297    if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1298       ua->warning_msg("%s\n", db_strerror(ua->db));
1299    }
1300    if (rx->JobTDate == 0) {
1301       ua->error_msg(_("No Full backup before %s found.\n"), date);
1302       goto bail_out;
1303    }
1304
1305    /* Now find most recent Differental Job after Full save, if any */
1306    Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1307         edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1308    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1309       ua->warning_msg("%s\n", db_strerror(ua->db));
1310    }
1311    /* Now update JobTDate to look into Differental, if any */
1312    rx->JobTDate = 0;
1313    if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1314       ua->warning_msg("%s\n", db_strerror(ua->db));
1315    }
1316    if (rx->JobTDate == 0) {
1317       ua->error_msg(_("No Full backup before %s found.\n"), date);
1318       goto bail_out;
1319    }
1320
1321    /* Now find all Incremental Jobs after Full/dif save */
1322    Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1323         edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1324    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1325       ua->warning_msg("%s\n", db_strerror(ua->db));
1326    }
1327
1328    /* Get the JobIds from that list */
1329    rx->last_jobid[0] = rx->JobIds[0] = 0;
1330
1331    if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1332       ua->warning_msg("%s\n", db_strerror(ua->db));
1333    }
1334
1335    if (rx->JobIds[0] != 0) {
1336       if (find_arg(ua, NT_("copies")) > 0) {
1337          /* Display a list of all copies */
1338          db_list_copies_records(ua->jcr, ua->db, 0, rx->JobIds, 
1339                                 prtit, ua, HORZ_LIST);
1340       }
1341       /* Display a list of Jobs selected for this restore */
1342       db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1,HORZ_LIST);
1343       ok = true;
1344
1345    } else {
1346       ua->warning_msg(_("No jobs found.\n"));
1347    }
1348
1349 bail_out:
1350    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1351    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1352    return ok;
1353 }
1354
1355 static int restore_count_handler(void *ctx, int num_fields, char **row)
1356 {
1357    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1358    rx->JobId = str_to_int64(row[0]);
1359    rx->found = true;
1360    return 0;
1361 }
1362
1363 /*
1364  * Callback handler to get JobId and FileIndex for files
1365  *   can insert more than one depending on the caller.
1366  */
1367 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1368 {
1369    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1370
1371    Dmsg2(200, "JobId=%s FileIndex=%s\n", row[0], row[1]);
1372    rx->JobId = str_to_int64(row[0]);
1373    add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1374    rx->found = true;
1375    rx->selected_files++;
1376    return 0;
1377 }
1378
1379 /*
1380  * Callback handler make list of JobIds
1381  */
1382 static int jobid_handler(void *ctx, int num_fields, char **row)
1383 {
1384    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1385
1386    if (strcmp(rx->last_jobid, row[0]) == 0) {
1387       return 0;                       /* duplicate id */
1388    }
1389    bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1390    if (rx->JobIds[0] != 0) {
1391       pm_strcat(rx->JobIds, ",");
1392    }
1393    pm_strcat(rx->JobIds, row[0]);
1394    return 0;
1395 }
1396
1397
1398 /*
1399  * Callback handler to pickup last Full backup JobTDate
1400  */
1401 static int last_full_handler(void *ctx, int num_fields, char **row)
1402 {
1403    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1404
1405    rx->JobTDate = str_to_int64(row[1]);
1406    return 0;
1407 }
1408
1409 /*
1410  * Callback handler build FileSet name prompt list
1411  */
1412 static int fileset_handler(void *ctx, int num_fields, char **row)
1413 {
1414    /* row[0] = FileSet (name) */
1415    if (row[0]) {
1416       add_prompt((UAContext *)ctx, row[0]);
1417    }
1418    return 0;
1419 }
1420
1421 /*
1422  * Free names in the list
1423  */
1424 static void free_name_list(NAME_LIST *name_list)
1425 {
1426    for (int i=0; i < name_list->num_ids; i++) {
1427       free(name_list->name[i]);
1428    }
1429    bfree_and_null(name_list->name);
1430    name_list->max_ids = 0;
1431    name_list->num_ids = 0;
1432 }
1433
1434 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType) 
1435 {
1436    STORE *store;
1437
1438    if (rx.store) {
1439       Dmsg1(200, "Already have store=%s\n", rx.store->name());
1440       return;
1441    }
1442    /*
1443     * Try looking up Storage by name
1444     */
1445    LockRes();
1446    foreach_res(store, R_STORAGE) {
1447       if (strcmp(Storage, store->name()) == 0) {
1448          if (acl_access_ok(ua, Storage_ACL, store->name())) {
1449             rx.store = store;
1450          }
1451          break;
1452       }
1453    }
1454    UnlockRes();
1455
1456    if (rx.store) {
1457       /* Check if an explicit storage resource is given */
1458       store = NULL;
1459       int i = find_arg_with_value(ua, "storage");
1460       if (i > 0) {
1461          store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1462          if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1463             store = NULL;
1464          }
1465       }
1466       if (store && (store != rx.store)) {
1467          ua->info_msg(_("Warning default storage overridden by \"%s\" on command line.\n"),
1468             store->name());
1469          rx.store = store;
1470          Dmsg1(200, "Set store=%s\n", rx.store->name());
1471       }
1472       return;
1473    }
1474
1475    /* If no storage resource, try to find one from MediaType */
1476    if (!rx.store) {
1477       LockRes();
1478       foreach_res(store, R_STORAGE) {
1479          if (strcmp(MediaType, store->media_type) == 0) {
1480             if (acl_access_ok(ua, Storage_ACL, store->name())) {
1481                rx.store = store;
1482                Dmsg1(200, "Set store=%s\n", rx.store->name());
1483                ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1484                   Storage, store->name(), MediaType);
1485             }
1486             UnlockRes();
1487             return;
1488          }
1489       }
1490       UnlockRes();
1491       ua->warning_msg(_("\nUnable to find Storage resource for\n"
1492          "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1493    }
1494
1495    /* Take command line arg, or ask user if none */
1496    rx.store = get_storage_resource(ua, false /* don't use default */);
1497    if (rx.store) {
1498       Dmsg1(200, "Set store=%s\n", rx.store->name());
1499    }
1500
1501 }