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