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