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