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