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