]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
kes Start implementing bstrerror() in place of strerror().
[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 plus additions
11    that are listed 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 (!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          bstrutime(date, sizeof(date), now);
614          if (!select_backups_before_date(ua, rx, date)) {
615             return 0;
616          }
617          break;
618       case 5:                         /* select backup at specified time */
619          if (!get_date(ua, date, sizeof(date))) {
620             return 0;
621          }
622          if (!select_backups_before_date(ua, rx, date)) {
623             return 0;
624          }
625          break;
626       case 6:                         /* Enter files */
627          bstrutime(date, sizeof(date), now);
628          if (!get_client_name(ua, rx)) {
629             return 0;
630          }
631          ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
632                         "containing a list of file names with paths, and terminate\n"
633                         "them with a blank line.\n"));
634          for ( ;; ) {
635             if (!get_cmd(ua, _("Enter full filename: "))) {
636                return 0;
637             }
638             len = strlen(ua->cmd);
639             if (len == 0) {
640                break;
641             }
642             insert_one_file_or_dir(ua, rx, date, false);
643          }
644          return 2;
645        case 7:                        /* enter files backed up before specified time */
646          if (!get_date(ua, date, sizeof(date))) {
647             return 0;
648          }
649          if (!get_client_name(ua, rx)) {
650             return 0;
651          }
652          ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
653                         "containing a list of file names with paths, and terminate\n"
654                         "them with a blank line.\n"));
655          for ( ;; ) {
656             if (!get_cmd(ua, _("Enter full filename: "))) {
657                return 0;
658             }
659             len = strlen(ua->cmd);
660             if (len == 0) {
661                break;
662             }
663             insert_one_file_or_dir(ua, rx, date, false);
664          }
665          return 2;
666
667       case 8:                         /* Find JobIds for current backup */
668          bstrutime(date, sizeof(date), now);
669          if (!select_backups_before_date(ua, rx, date)) {
670             return 0;
671          }
672          done = false;
673          break;
674
675       case 9:                         /* Find JobIds for give date */
676          if (!get_date(ua, date, sizeof(date))) {
677             return 0;
678          }
679          if (!select_backups_before_date(ua, rx, date)) {
680             return 0;
681          }
682          done = false;
683          break;
684
685       case 10:                        /* Enter directories */
686          if (*rx->JobIds != 0) {
687             ua->send_msg(_("You have already selected the following JobIds: %s\n"),
688                rx->JobIds);
689          } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
690             if (*rx->JobIds != 0 && *ua->cmd) {
691                pm_strcat(rx->JobIds, ",");
692             }
693             pm_strcat(rx->JobIds, ua->cmd);
694          }
695          if (*rx->JobIds == 0 || *rx->JobIds == '.') {
696             return 0;                 /* nothing entered, return */
697          }
698          bstrutime(date, sizeof(date), now);
699          if (!get_client_name(ua, rx)) {
700             return 0;
701          }
702          ua->send_msg(_("Enter full directory names or start the name\n"
703                         "with a < to indicate it is a filename containing a list\n"
704                         "of directories and terminate them with a blank line.\n"));
705          for ( ;; ) {
706             if (!get_cmd(ua, _("Enter directory name: "))) {
707                return 0;
708             }
709             len = strlen(ua->cmd);
710             if (len == 0) {
711                break;
712             }
713             /* Add trailing slash to end of directory names */
714             if (ua->cmd[0] != '<' && !IsPathSeparator(ua->cmd[len-1])) {
715                strcat(ua->cmd, "/");
716             }
717             insert_one_file_or_dir(ua, rx, date, true);
718          }
719          return 2;
720
721       case 11:                        /* Cancel or quit */
722          return 0;
723       }
724    }
725
726    POOLMEM *JobIds = get_pool_memory(PM_FNAME);
727    *JobIds = 0;
728    rx->TotalFiles = 0;
729    /*        
730     * Find total number of files to be restored, and filter the JobId
731     *  list to contain only ones permitted by the ACL conditions.
732     */
733    for (p=rx->JobIds; ; ) {
734       char ed1[50];
735       int stat = get_next_jobid_from_list(&p, &JobId);
736       if (stat < 0) {
737          ua->error_msg(_("Invalid JobId in list.\n"));
738          free_pool_memory(JobIds);
739          return 0;
740       }
741       if (stat == 0) {
742          break;
743       }
744       if (jr.JobId == JobId) {
745          continue;                    /* duplicate of last JobId */
746       }
747       memset(&jr, 0, sizeof(JOB_DBR));
748       jr.JobId = JobId;
749       if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
750          ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
751             edit_int64(JobId, ed1), db_strerror(ua->db));
752          free_pool_memory(JobIds);
753          return 0;
754       }
755       if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
756          ua->error_msg(_("Access to JobId=%s (Job \"%s\") not authorized. Not selected.\n"),
757             edit_int64(JobId, ed1), jr.Name);
758          continue;
759       }
760       if (*JobIds != 0) {
761          pm_strcat(JobIds, ",");
762       }
763       pm_strcat(JobIds, edit_int64(JobId, ed1));
764       rx->TotalFiles += jr.JobFiles;
765    }
766    free_pool_memory(rx->JobIds);
767    rx->JobIds = JobIds;               /* Set ACL filtered list */
768    if (*rx->JobIds == 0) {
769       ua->warning_msg(_("No Jobs selected.\n"));
770       return 0;
771    }
772    if (strchr(rx->JobIds,',')) {
773       ua->info_msg(_("You have selected the following JobIds: %s\n"), rx->JobIds);
774    } else {
775       ua->info_msg(_("You have selected the following JobId: %s\n"), rx->JobIds);
776    }
777    return 1;
778 }
779
780 /*
781  * Get date from user
782  */
783 static int get_date(UAContext *ua, char *date, int date_len)
784 {
785    ua->send_msg(_("The restored files will the most current backup\n"
786                   "BEFORE the date you specify below.\n\n"));
787    for ( ;; ) {
788       if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
789          return 0;
790       }
791       if (str_to_utime(ua->cmd) != 0) {
792          break;
793       }
794       ua->error_msg(_("Improper date format.\n"));
795    }
796    bstrncpy(date, ua->cmd, date_len);
797    return 1;
798 }
799
800 /*
801  * Insert a single file, or read a list of files from a file
802  */
803 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
804 {
805    FILE *ffd;
806    char file[5000];
807    char *p = ua->cmd;
808    int line = 0;
809
810    switch (*p) {
811    case '<':
812       p++;
813       if ((ffd = fopen(p, "rb")) == NULL) {
814          berrno be;
815          ua->error_msg(_("Cannot open file %s: ERR=%s\n"),
816             p, be.bstrerror());
817          break;
818       }
819       while (fgets(file, sizeof(file), ffd)) {
820          line++;
821          if (dir) {
822             if (!insert_dir_into_findex_list(ua, rx, file, date)) {
823                ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
824             }
825          } else {
826             if (!insert_file_into_findex_list(ua, rx, file, date)) {
827                ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
828             }
829          }
830       }
831       fclose(ffd);
832       break;
833    case '?':
834       p++;
835       insert_table_into_findex_list(ua, rx, p);
836       break;
837    default:
838       if (dir) {
839          insert_dir_into_findex_list(ua, rx, ua->cmd, date);
840       } else {
841          insert_file_into_findex_list(ua, rx, ua->cmd, date);
842       }
843       break;
844    }
845 }
846
847 /*
848  * For a given file (path+filename), split into path and file, then
849  *   lookup the most recent backup in the catalog to get the JobId
850  *   and FileIndex, then insert them into the findex list.
851  */
852 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
853                                         char *date)
854 {
855    strip_trailing_newline(file);
856    split_path_and_filename(rx, file);
857    if (*rx->JobIds == 0) {
858       Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname, 
859            rx->ClientName);
860    } else {
861       Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
862            rx->path, rx->fname, rx->ClientName);
863    }
864    rx->found = false;
865    /* Find and insert jobid and File Index */
866    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
867       ua->error_msg(_("Query failed: %s. ERR=%s\n"),
868          rx->query, db_strerror(ua->db));
869    }
870    if (!rx->found) {
871       ua->error_msg(_("No database record found for: %s\n"), file);
872 //    ua->error_msg("Query=%s\n", rx->query);
873       return true;
874    }
875    return true;
876 }
877
878 /*
879  * For a given path lookup the most recent backup in the catalog
880  * to get the JobId and FileIndexes of all files in that directory.
881  */
882 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
883                                         char *date)
884 {
885    strip_trailing_junk(dir);
886    if (*rx->JobIds == 0) {
887       ua->error_msg(_("No JobId specified cannot continue.\n"));
888       return false;
889    } else {
890       Mmsg(rx->query, uar_jobid_fileindex_from_dir, rx->JobIds, 
891            dir, rx->ClientName);
892    }
893    rx->found = false;
894    /* Find and insert jobid and File Index */
895    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
896       ua->error_msg(_("Query failed: %s. ERR=%s\n"),
897          rx->query, db_strerror(ua->db));
898    }
899    if (!rx->found) {
900       ua->error_msg(_("No database record found for: %s\n"), dir);
901       return true;
902    }
903    return true;
904 }
905
906 /*
907  * Get the JobId and FileIndexes of all files in the specified table
908  */
909 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
910 {
911    strip_trailing_junk(table);
912    Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
913
914    rx->found = false;
915    /* Find and insert jobid and File Index */
916    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
917       ua->error_msg(_("Query failed: %s. ERR=%s\n"),
918          rx->query, db_strerror(ua->db));
919    }
920    if (!rx->found) {
921       ua->error_msg(_("No table found: %s\n"), table);
922       return true;
923    }
924    return true;
925 }
926
927 static void split_path_and_filename(RESTORE_CTX *rx, char *name)
928 {
929    char *p, *f;
930
931    /* Find path without the filename.
932     * I.e. everything after the last / is a "filename".
933     * OK, maybe it is a directory name, but we treat it like
934     * a filename. If we don't find a / then the whole name
935     * must be a path name (e.g. c:).
936     */
937    for (p=f=name; *p; p++) {
938       if (IsPathSeparator(*p)) {
939          f = p;                       /* set pos of last slash */
940       }
941    }
942    if (IsPathSeparator(*f)) {         /* did we find a slash? */
943       f++;                            /* yes, point to filename */
944    } else {                           /* no, whole thing must be path name */
945       f = p;
946    }
947
948    /* If filename doesn't exist (i.e. root directory), we
949     * simply create a blank name consisting of a single
950     * space. This makes handling zero length filenames
951     * easier.
952     */
953    rx->fnl = p - f;
954    if (rx->fnl > 0) {
955       rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
956       memcpy(rx->fname, f, rx->fnl);    /* copy filename */
957       rx->fname[rx->fnl] = 0;
958    } else {
959       rx->fname[0] = 0;
960       rx->fnl = 0;
961    }
962
963    rx->pnl = f - name;
964    if (rx->pnl > 0) {
965       rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
966       memcpy(rx->path, name, rx->pnl);
967       rx->path[rx->pnl] = 0;
968    } else {
969       rx->path[0] = 0;
970       rx->pnl = 0;
971    }
972
973    Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
974 }
975
976 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
977 {
978    TREE_CTX tree;
979    JobId_t JobId, last_JobId;
980    char *p;
981    bool OK = true;
982    char ed1[50];
983
984    memset(&tree, 0, sizeof(TREE_CTX));
985    /*
986     * Build the directory tree containing JobIds user selected
987     */
988    tree.root = new_tree(rx->TotalFiles);
989    tree.ua = ua;
990    tree.all = rx->all;
991    last_JobId = 0;
992    /*
993     * For display purposes, the same JobId, with different volumes may
994     * appear more than once, however, we only insert it once.
995     */
996    int items = 0;
997    p = rx->JobIds;
998    tree.FileEstimate = 0;
999    if (get_next_jobid_from_list(&p, &JobId) > 0) {
1000       /* Use first JobId as estimate of the number of files to restore */
1001       Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
1002       if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1003          ua->error_msg("%s\n", db_strerror(ua->db));
1004       }
1005       if (rx->found) {
1006          /* Add about 25% more than this job for over estimate */
1007          tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
1008          tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
1009       }
1010    }
1011    for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1012       char ed1[50];
1013
1014       if (JobId == last_JobId) {
1015          continue;                    /* eliminate duplicate JobIds */
1016       }
1017       last_JobId = JobId;
1018       ua->info_msg(_("\nBuilding directory tree for JobId %s ...  "), 
1019          edit_int64(JobId, ed1));
1020       items++;
1021       /*
1022        * Find files for this JobId and insert them in the tree
1023        */
1024       Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
1025       if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
1026          ua->error_msg("%s", db_strerror(ua->db));
1027       }
1028    }
1029    if (tree.FileCount == 0) {
1030       ua->send_msg(_("\nThere were no files inserted into the tree, so file selection\n"
1031          "is not possible.Most likely your retention policy pruned the files\n"));
1032       if (!get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
1033          OK = false;
1034       } else {
1035          last_JobId = 0;
1036          for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1037              if (JobId == last_JobId) {
1038                 continue;                    /* eliminate duplicate JobIds */
1039              }
1040              add_findex_all(rx->bsr, JobId);
1041           }
1042           OK = true;
1043       }
1044    } else {
1045       char ec1[50];
1046       if (items==1) {
1047          if (tree.all) {
1048             ua->info_msg(_("\n1 Job, %s files inserted into the tree and marked for extraction.\n"),
1049               edit_uint64_with_commas(tree.FileCount, ec1));
1050          }
1051          else {
1052             ua->info_msg(_("\n1 Job, %s files inserted into the tree.\n"),
1053               edit_uint64_with_commas(tree.FileCount, ec1));
1054          }
1055       }
1056       else {
1057          if (tree.all) {
1058             ua->info_msg(_("\n%d Jobs, %s files inserted into the tree and marked for extraction.\n"),
1059               items, edit_uint64_with_commas(tree.FileCount, ec1));
1060          }
1061          else {
1062             ua->info_msg(_("\n%d Jobs, %s files inserted into the tree.\n"),
1063               items, edit_uint64_with_commas(tree.FileCount, ec1));
1064          }
1065       }
1066
1067       if (find_arg(ua, NT_("done")) < 0) {
1068          /* Let the user interact in selecting which files to restore */
1069          OK = user_select_files_from_tree(&tree);
1070       }
1071
1072       /*
1073        * Walk down through the tree finding all files marked to be
1074        *  extracted making a bootstrap file.
1075        */
1076       if (OK) {
1077          for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
1078             Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
1079             if (node->extract || node->extract_dir) {
1080                Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
1081                add_findex(rx->bsr, node->JobId, node->FileIndex);
1082                if (node->extract && node->type != TN_NEWDIR) {
1083                   rx->selected_files++;  /* count only saved files */
1084                }
1085             }
1086          }
1087       }
1088    }
1089
1090    free_tree(tree.root);              /* free the directory tree */
1091    return OK;
1092 }
1093
1094
1095 /*
1096  * This routine is used to get the current backup or a backup
1097  *   before the specified date.
1098  */
1099 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
1100 {
1101    bool ok = false;
1102    FILESET_DBR fsr;
1103    CLIENT_DBR cr;
1104    char fileset_name[MAX_NAME_LENGTH];
1105    char ed1[50], ed2[50];
1106    char pool_select[MAX_NAME_LENGTH];
1107    int i;
1108
1109
1110    /* Create temp tables */
1111    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1112    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1113    if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
1114       ua->error_msg("%s\n", db_strerror(ua->db));
1115    }
1116    if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
1117       ua->error_msg("%s\n", db_strerror(ua->db));
1118    }
1119    /*
1120     * Select Client from the Catalog
1121     */
1122    memset(&cr, 0, sizeof(cr));
1123    if (!get_client_dbr(ua, &cr)) {
1124       goto bail_out;
1125    }
1126    bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1127
1128    /*
1129     * Get FileSet
1130     */
1131    memset(&fsr, 0, sizeof(fsr));
1132    i = find_arg_with_value(ua, "FileSet");
1133    if (i >= 0) {
1134       bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1135       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1136          ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1137             db_strerror(ua->db));
1138          i = -1;
1139       }
1140    }
1141    if (i < 0) {                       /* fileset not found */
1142       edit_int64(cr.ClientId, ed1);
1143       Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1144       start_prompt(ua, _("The defined FileSet resources are:\n"));
1145       if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1146          ua->error_msg("%s\n", db_strerror(ua->db));
1147       }
1148       if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1149                  fileset_name, sizeof(fileset_name)) < 0) {
1150          ua->error_msg(_("No FileSet found for client \"%s\".\n"), cr.Name);
1151          goto bail_out;
1152       }
1153
1154       bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1155       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1156          ua->warning_msg(_("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1157          ua->send_msg(_("This probably means you modified the FileSet.\n"
1158                      "Continuing anyway.\n"));
1159       }
1160    }
1161
1162    /* If Pool specified, add PoolId specification */
1163    pool_select[0] = 0;
1164    if (rx->pool) {
1165       POOL_DBR pr;
1166       memset(&pr, 0, sizeof(pr));
1167       bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1168       if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1169          bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ", 
1170             edit_int64(pr.PoolId, ed1));
1171       } else {
1172          ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1173       }
1174    }
1175
1176    /* Find JobId of last Full backup for this client, fileset */
1177    edit_int64(cr.ClientId, ed1);
1178    Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1179          pool_select);
1180    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1181       ua->error_msg("%s\n", db_strerror(ua->db));
1182       goto bail_out;
1183    }
1184
1185    /* Find all Volumes used by that JobId */
1186    if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1187       ua->error_msg("%s\n", db_strerror(ua->db));
1188       goto bail_out;
1189    }
1190    /* Note, this is needed because I don't seem to get the callback
1191     * from the call just above.
1192     */
1193    rx->JobTDate = 0;
1194    if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1195       ua->warning_msg("%s\n", db_strerror(ua->db));
1196    }
1197    if (rx->JobTDate == 0) {
1198       ua->error_msg(_("No Full backup before %s found.\n"), date);
1199       goto bail_out;
1200    }
1201
1202    /* Now find most recent Differental Job after Full save, if any */
1203    Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1204         edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1205    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1206       ua->warning_msg("%s\n", db_strerror(ua->db));
1207    }
1208    /* Now update JobTDate to lock onto Differental, if any */
1209    rx->JobTDate = 0;
1210    if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1211       ua->warning_msg("%s\n", db_strerror(ua->db));
1212    }
1213    if (rx->JobTDate == 0) {
1214       ua->error_msg(_("No Full backup before %s found.\n"), date);
1215       goto bail_out;
1216    }
1217
1218    /* Now find all Incremental Jobs after Full/dif save */
1219    Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1220         edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1221    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1222       ua->warning_msg("%s\n", db_strerror(ua->db));
1223    }
1224
1225    /* Get the JobIds from that list */
1226    rx->JobIds[0] = 0;
1227    rx->last_jobid[0] = 0;
1228    if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1229       ua->warning_msg("%s\n", db_strerror(ua->db));
1230    }
1231
1232    if (rx->JobIds[0] != 0) {
1233       /* Display a list of Jobs selected for this restore */
1234       db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
1235       ok = true;
1236    } else {
1237       ua->warning_msg(_("No jobs found.\n"));
1238    }
1239
1240 bail_out:
1241    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1242    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1243    return ok;
1244 }
1245
1246
1247 /* 
1248  * Return next JobId from comma separated list   
1249  *
1250  * Returns:
1251  *   1 if next JobId returned
1252  *   0 if no more JobIds are in list
1253  *  -1 there is an error
1254  */
1255 int get_next_jobid_from_list(char **p, JobId_t *JobId)
1256 {
1257    char jobid[30];
1258    char *q = *p;
1259
1260    jobid[0] = 0;
1261    for (int i=0; i<(int)sizeof(jobid); i++) {
1262       if (*q == 0) {
1263          break;
1264       } else if (*q == ',') {
1265          q++;
1266          break;
1267       }
1268       jobid[i] = *q++;
1269       jobid[i+1] = 0;
1270    }
1271    if (jobid[0] == 0) {
1272       return 0;
1273    } else if (!is_a_number(jobid)) {
1274       return -1;                      /* error */
1275    }
1276    *p = q;
1277    *JobId = str_to_int64(jobid);
1278    return 1;
1279 }
1280
1281 static int restore_count_handler(void *ctx, int num_fields, char **row)
1282 {
1283    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1284    rx->JobId = str_to_int64(row[0]);
1285    rx->found = true;
1286    return 0;
1287 }
1288
1289 /*
1290  * Callback handler to get JobId and FileIndex for files
1291  *   can insert more than one depending on the caller.
1292  */
1293 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1294 {
1295    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1296    rx->JobId = str_to_int64(row[0]);
1297    add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1298    rx->found = true;
1299    rx->selected_files++;
1300    return 0;
1301 }
1302
1303 /*
1304  * Callback handler make list of JobIds
1305  */
1306 static int jobid_handler(void *ctx, int num_fields, char **row)
1307 {
1308    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1309
1310    if (strcmp(rx->last_jobid, row[0]) == 0) {
1311       return 0;                       /* duplicate id */
1312    }
1313    bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1314    if (rx->JobIds[0] != 0) {
1315       pm_strcat(rx->JobIds, ",");
1316    }
1317    pm_strcat(rx->JobIds, row[0]);
1318    return 0;
1319 }
1320
1321
1322 /*
1323  * Callback handler to pickup last Full backup JobTDate
1324  */
1325 static int last_full_handler(void *ctx, int num_fields, char **row)
1326 {
1327    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1328
1329    rx->JobTDate = str_to_int64(row[1]);
1330    return 0;
1331 }
1332
1333 /*
1334  * Callback handler build FileSet name prompt list
1335  */
1336 static int fileset_handler(void *ctx, int num_fields, char **row)
1337 {
1338    /* row[0] = FileSet (name) */
1339    if (row[0]) {
1340       add_prompt((UAContext *)ctx, row[0]);
1341    }
1342    return 0;
1343 }
1344
1345 /*
1346  * Free names in the list
1347  */
1348 static void free_name_list(NAME_LIST *name_list)
1349 {
1350    for (int i=0; i < name_list->num_ids; i++) {
1351       free(name_list->name[i]);
1352    }
1353    if (name_list->name) {
1354       free(name_list->name);
1355       name_list->name = NULL;
1356    }
1357    name_list->max_ids = 0;
1358    name_list->num_ids = 0;
1359 }
1360
1361 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType) 
1362 {
1363    STORE *store;
1364
1365    if (rx.store) {
1366       Dmsg1(200, "Already have store=%s\n", rx.store->name());
1367       return;
1368    }
1369    /*
1370     * Try looking up Storage by name
1371     */
1372    LockRes();
1373    foreach_res(store, R_STORAGE) {
1374       if (strcmp(Storage, store->name()) == 0) {
1375          if (acl_access_ok(ua, Storage_ACL, store->name())) {
1376             rx.store = store;
1377          }
1378          break;
1379       }
1380    }
1381    UnlockRes();
1382
1383    if (rx.store) {
1384       /* Check if an explicit storage resource is given */
1385       store = NULL;
1386       int i = find_arg_with_value(ua, "storage");
1387       if (i > 0) {
1388          store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1389          if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1390             store = NULL;
1391          }
1392       }
1393       if (store && (store != rx.store)) {
1394          ua->info_msg(_("Warning default storage overridden by \"%s\" on command line.\n"),
1395             store->name());
1396          rx.store = store;
1397          Dmsg1(200, "Set store=%s\n", rx.store->name());
1398       }
1399       return;
1400    }
1401
1402    /* If no storage resource, try to find one from MediaType */
1403    if (!rx.store) {
1404       LockRes();
1405       foreach_res(store, R_STORAGE) {
1406          if (strcmp(MediaType, store->media_type) == 0) {
1407             if (acl_access_ok(ua, Storage_ACL, store->name())) {
1408                rx.store = store;
1409                Dmsg1(200, "Set store=%s\n", rx.store->name());
1410                ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1411                   Storage, store->name(), MediaType);
1412             }
1413             UnlockRes();
1414             return;
1415          }
1416       }
1417       UnlockRes();
1418       ua->warning_msg(_("\nUnable to find Storage resource for\n"
1419          "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1420    }
1421
1422    /* Take command line arg, or ask user if none */
1423    rx.store = get_storage_resource(ua, false /* don't use default */);
1424    Dmsg1(200, "Set store=%s\n", rx.store->name());
1425
1426 }