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