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