]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
Make option 2 of restore work better (list last 20
[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[20];
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_client_name(ua, rx)) {
452             return 0;
453          }
454          if (!get_cmd(ua, _("Enter Filename (no path):"))) {
455             return 0;
456          }
457          len = strlen(ua->cmd);
458          fname = (char *)malloc(len * 2 + 1);
459          db_escape_string(fname, ua->cmd, len);
460          Mmsg(rx->query, uar_file, rx->ClientName, fname);
461          free(fname);
462          gui_save = ua->jcr->gui;
463          ua->jcr->gui = true;
464          db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
465          ua->jcr->gui = gui_save;
466          done = false;
467          break;
468       case 2:                         /* enter a list of JobIds */
469          if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
470             return 0;
471          }
472          pm_strcpy(rx->JobIds, ua->cmd);
473          break;
474       case 3:                         /* Enter an SQL list command */
475          if (!get_cmd(ua, _("Enter SQL list command: "))) {
476             return 0;
477          }
478          gui_save = ua->jcr->gui;
479          ua->jcr->gui = true;
480          db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
481          ua->jcr->gui = gui_save;
482          done = false;
483          break;
484       case 4:                         /* Select the most recent backups */
485          bstrutime(date, sizeof(date), time(NULL));
486          if (!select_backups_before_date(ua, rx, date)) {
487             return 0;
488          }
489          break;
490       case 5:                         /* select backup at specified time */
491          if (!get_date(ua, date, sizeof(date))) {
492             return 0;
493          }
494          if (!select_backups_before_date(ua, rx, date)) {
495             return 0;
496          }
497          break;
498       case 6:                         /* Enter files */
499          bstrutime(date, sizeof(date), time(NULL));
500          if (!get_client_name(ua, rx)) {
501             return 0;
502          }
503          bsendmsg(ua, _("Enter file names with paths, or < to enter a filename\n"
504                         "containg a list of file names with paths, and terminate\n"
505                         "them with a blank line.\n"));
506          for ( ;; ) {
507             if (!get_cmd(ua, _("Enter full filename: "))) {
508                return 0;
509             }
510             len = strlen(ua->cmd);
511             if (len == 0) {
512                break;
513             }
514             insert_one_file_or_dir(ua, rx, date, false);
515          }
516          /* Check MediaType and select storage that corresponds */
517          if (rx->name_list.num_ids) {
518             get_storage_from_mediatype(ua, &rx->name_list, rx);
519          }
520          return 2;
521        case 7:                        /* enter files backed up before specified time */
522          if (!get_date(ua, date, sizeof(date))) {
523             return 0;
524          }
525          if (!get_client_name(ua, rx)) {
526             return 0;
527          }
528          bsendmsg(ua, _("Enter file names with paths, or < to enter a filename\n"
529                         "containg a list of file names with paths, and terminate\n"
530                         "them with a blank line.\n"));
531          for ( ;; ) {
532             if (!get_cmd(ua, _("Enter full filename: "))) {
533                return 0;
534             }
535             len = strlen(ua->cmd);
536             if (len == 0) {
537                break;
538             }
539             insert_one_file_or_dir(ua, rx, date, false);
540          }
541          /* Check MediaType and select storage that corresponds */
542          if (rx->name_list.num_ids) {
543             get_storage_from_mediatype(ua, &rx->name_list, rx);
544          }
545          return 2;
546
547       case 8:                         /* Find JobIds for current backup */
548          bstrutime(date, sizeof(date), time(NULL));
549          if (!select_backups_before_date(ua, rx, date)) {
550             return 0;
551          }
552          done = false;
553          break;
554
555       case 9:                         /* Find JobIds for give date */
556          if (!get_date(ua, date, sizeof(date))) {
557             return 0;
558          }
559          if (!select_backups_before_date(ua, rx, date)) {
560             return 0;
561          }
562          done = false;
563          break;
564
565       case 10:                        /* Enter directories */
566          if (*rx->JobIds != 0) {
567             bsendmsg(ua, _("You have already seleted the following JobIds: %s\n"),
568                rx->JobIds);
569          } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
570             if (*rx->JobIds != 0 && *ua->cmd) {
571                pm_strcat(rx->JobIds, ",");
572             }
573             pm_strcat(rx->JobIds, ua->cmd);
574          }
575          if (*rx->JobIds == 0 || *rx->JobIds == '.') {
576             return 0;                 /* nothing entered, return */
577          }
578          bstrutime(date, sizeof(date), time(NULL));
579          if (!get_client_name(ua, rx)) {
580             return 0;
581          }
582          bsendmsg(ua, _("Enter directory names with a trailing /, or < to enter a filename\n"
583                         "containg a list of directories and terminate\n"
584                         "them with a blank line.\n"));
585          for ( ;; ) {
586             if (!get_cmd(ua, _("Enter directory name: "))) {
587                return 0;
588             }
589             len = strlen(ua->cmd);
590             if (len == 0) {
591                break;
592             }
593             if (ua->cmd[len-1] != '/') {
594                strcat(ua->cmd, "/");
595             }
596             insert_one_file_or_dir(ua, rx, date, true);
597          }
598          /* Check MediaType and select storage that corresponds */
599          if (rx->name_list.num_ids) {
600             get_storage_from_mediatype(ua, &rx->name_list, rx);
601          }
602          return 2;
603
604       case 11:                        /* Cancel or quit */
605          return 0;
606       }
607    }
608
609    if (*rx->JobIds == 0) {
610       bsendmsg(ua, _("No Jobs selected.\n"));
611       return 0;
612    }
613    bsendmsg(ua, _("You have selected the following JobId%s: %s\n"),
614       strchr(rx->JobIds,',')?"s":"",rx->JobIds);
615
616    memset(&jr, 0, sizeof(JOB_DBR));
617
618    rx->TotalFiles = 0;
619    for (p=rx->JobIds; ; ) {
620       int stat = get_next_jobid_from_list(&p, &JobId);
621       if (stat < 0) {
622          bsendmsg(ua, _("Invalid JobId in list.\n"));
623          return 0;
624       }
625       if (stat == 0) {
626          break;
627       }
628       if (jr.JobId == JobId) {
629          continue;                    /* duplicate of last JobId */
630       }
631       jr.JobId = JobId;
632       if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
633          char ed1[50];
634          bsendmsg(ua, _("Unable to get Job record for JobId=%s: ERR=%s\n"),
635             edit_int64(JobId, ed1), db_strerror(ua->db));
636          return 0;
637       }
638       if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
639          bsendmsg(ua, _("No authorization. Job \"%s\" not selected.\n"),
640             jr.Name);
641          continue;
642       }
643       rx->TotalFiles += jr.JobFiles;
644    }
645    return 1;
646 }
647
648 /*
649  * Get date from user
650  */
651 static int get_date(UAContext *ua, char *date, int date_len)
652 {
653    bsendmsg(ua, _("The restored files will the most current backup\n"
654                   "BEFORE the date you specify below.\n\n"));
655    for ( ;; ) {
656       if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
657          return 0;
658       }
659       if (str_to_utime(ua->cmd) != 0) {
660          break;
661       }
662       bsendmsg(ua, _("Improper date format.\n"));
663    }
664    bstrncpy(date, ua->cmd, date_len);
665    return 1;
666 }
667
668 /*
669  * Insert a single file, or read a list of files from a file
670  */
671 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
672 {
673    FILE *ffd;
674    char file[5000];
675    char *p = ua->cmd;
676    int line = 0;
677
678    switch (*p) {
679    case '<':
680       p++;
681       if ((ffd = fopen(p, "r")) == NULL) {
682          berrno be;
683          bsendmsg(ua, _("Cannot open file %s: ERR=%s\n"),
684             p, be.strerror());
685          break;
686       }
687       while (fgets(file, sizeof(file), ffd)) {
688          line++;
689          if (dir) {
690             if (!insert_dir_into_findex_list(ua, rx, file, date)) {
691                bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
692             }
693          } else {
694             if (!insert_file_into_findex_list(ua, rx, file, date)) {
695                bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
696             }
697          }
698       }
699       fclose(ffd);
700       break;
701    default:
702       if (dir) {
703          insert_dir_into_findex_list(ua, rx, ua->cmd, date);
704       } else {
705          insert_file_into_findex_list(ua, rx, ua->cmd, date);
706       }
707       break;
708    }
709 }
710
711 /*
712  * For a given file (path+filename), split into path and file, then
713  *   lookup the most recent backup in the catalog to get the JobId
714  *   and FileIndex, then insert them into the findex list.
715  */
716 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
717                                         char *date)
718 {
719    char ed1[50];
720
721    strip_trailing_junk(file);
722    split_path_and_filename(rx, file);
723    if (*rx->JobIds == 0) {
724       Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname, 
725            rx->ClientName);
726    } else {
727       Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
728            rx->path, rx->fname, rx->ClientName);
729    }
730    rx->found = false;
731    /* Find and insert jobid and File Index */
732    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
733       bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
734          rx->query, db_strerror(ua->db));
735    }
736    if (!rx->found) {
737       bsendmsg(ua, _("No database record found for: %s\n"), file);
738       return true;
739    }
740    /*
741     * Find the MediaTypes for this JobId and add to the name_list
742     */
743    Mmsg(rx->query, uar_mediatype, edit_int64(rx->JobId, ed1));
744    if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
745       bsendmsg(ua, "%s", db_strerror(ua->db));
746       return false;
747    }
748    return true;
749 }
750
751 /*
752  * For a given path lookup the most recent backup in the catalog
753  * to get the JobId and FileIndexes of all files in that directory.
754  */
755 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
756                                         char *date)
757 {
758    char ed1[50];
759
760    strip_trailing_junk(dir);
761    if (*rx->JobIds == 0) {
762       bsendmsg(ua, _("No JobId specified cannot continue.\n"));
763       return false;
764    } else {
765       Mmsg(rx->query, uar_jobid_fileindex_from_dir, rx->JobIds, 
766            dir, rx->ClientName);
767    }
768    rx->found = false;
769    /* Find and insert jobid and File Index */
770    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
771       bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
772          rx->query, db_strerror(ua->db));
773    }
774    if (!rx->found) {
775       bsendmsg(ua, _("No database record found for: %s\n"), dir);
776       return true;
777    }
778    /*
779     * Find the MediaTypes for this JobId and add to the name_list
780     */
781    Mmsg(rx->query, uar_mediatype, edit_int64(rx->JobId, ed1));
782    if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
783       bsendmsg(ua, "%s", db_strerror(ua->db));
784       return false;
785    }
786    return true;
787 }
788
789
790 static void split_path_and_filename(RESTORE_CTX *rx, char *name)
791 {
792    char *p, *f;
793
794    /* Find path without the filename.
795     * I.e. everything after the last / is a "filename".
796     * OK, maybe it is a directory name, but we treat it like
797     * a filename. If we don't find a / then the whole name
798     * must be a path name (e.g. c:).
799     */
800    for (p=f=name; *p; p++) {
801       if (*p == '/') {
802          f = p;                       /* set pos of last slash */
803       }
804    }
805    if (*f == '/') {                   /* did we find a slash? */
806       f++;                            /* yes, point to filename */
807    } else {                           /* no, whole thing must be path name */
808       f = p;
809    }
810
811    /* If filename doesn't exist (i.e. root directory), we
812     * simply create a blank name consisting of a single
813     * space. This makes handling zero length filenames
814     * easier.
815     */
816    rx->fnl = p - f;
817    if (rx->fnl > 0) {
818       rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
819       memcpy(rx->fname, f, rx->fnl);    /* copy filename */
820       rx->fname[rx->fnl] = 0;
821    } else {
822       rx->fname[0] = 0;
823       rx->fnl = 0;
824    }
825
826    rx->pnl = f - name;
827    if (rx->pnl > 0) {
828       rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
829       memcpy(rx->path, name, rx->pnl);
830       rx->path[rx->pnl] = 0;
831    } else {
832       rx->path[0] = 0;
833       rx->pnl = 0;
834    }
835
836    Dmsg2(100, "sllit path=%s file=%s\n", rx->path, rx->fname);
837 }
838
839 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
840 {
841    TREE_CTX tree;
842    JobId_t JobId, last_JobId;
843    char *p;
844    bool OK = true;
845    char ed1[50];
846
847    memset(&tree, 0, sizeof(TREE_CTX));
848    /*
849     * Build the directory tree containing JobIds user selected
850     */
851    tree.root = new_tree(rx->TotalFiles);
852    tree.ua = ua;
853    tree.all = rx->all;
854    last_JobId = 0;
855    /*
856     * For display purposes, the same JobId, with different volumes may
857     * appear more than once, however, we only insert it once.
858     */
859    int items = 0;
860    p = rx->JobIds;
861    tree.FileEstimate = 0;
862    if (get_next_jobid_from_list(&p, &JobId) > 0) {
863       /* Use first JobId as estimate of the number of files to restore */
864       Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
865       if (!db_sql_query(ua->db, rx->query, count_handler, (void *)rx)) {
866          bsendmsg(ua, "%s\n", db_strerror(ua->db));
867       }
868       if (rx->found) {
869          /* Add about 25% more than this job for over estimate */
870          tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
871          tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
872       }
873    }
874    for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
875       char ed1[50];
876
877       if (JobId == last_JobId) {
878          continue;                    /* eliminate duplicate JobIds */
879       }
880       last_JobId = JobId;
881       bsendmsg(ua, _("\nBuilding directory tree for JobId %s ...  "), 
882          edit_int64(JobId, ed1));
883       items++;
884       /*
885        * Find files for this JobId and insert them in the tree
886        */
887       Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
888       if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
889          bsendmsg(ua, "%s", db_strerror(ua->db));
890       }
891       /*
892        * Find the MediaTypes for this JobId and add to the name_list
893        */
894       Mmsg(rx->query, uar_mediatype, edit_int64(JobId, ed1));
895       if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
896          bsendmsg(ua, "%s", db_strerror(ua->db));
897       }
898    }
899    if (tree.FileCount == 0) {
900       bsendmsg(ua, "\nThere were no files inserted into the tree, so file selection\n"
901          "is not possible.Most likely your retention policy pruned the files\n");
902       if (!get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
903          OK = false;
904       } else {
905          last_JobId = 0;
906          for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
907              if (JobId == last_JobId) {
908                 continue;                    /* eliminate duplicate JobIds */
909              }
910              add_findex_all(rx->bsr, JobId);
911           }
912           OK = true;
913       }
914    } else {
915       char ec1[50];
916       bsendmsg(ua, "\n%d Job%s, %s files inserted into the tree%s.\n",
917          items, items==1?"":"s", edit_uint64_with_commas(tree.FileCount, ec1),
918          tree.all?" and marked for extraction":"");
919
920       /* Check MediaType and select storage that corresponds */
921       get_storage_from_mediatype(ua, &rx->name_list, rx);
922
923       if (find_arg(ua, _("done")) < 0) {
924          /* Let the user interact in selecting which files to restore */
925          OK = user_select_files_from_tree(&tree);
926       }
927
928       /*
929        * Walk down through the tree finding all files marked to be
930        *  extracted making a bootstrap file.
931        */
932       if (OK) {
933          for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
934             Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
935             if (node->extract || node->extract_dir) {
936                Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
937                add_findex(rx->bsr, node->JobId, node->FileIndex);
938                if (node->extract && node->type != TN_NEWDIR) {
939                   rx->selected_files++;  /* count only saved files */
940                }
941             }
942          }
943       }
944    }
945
946    free_tree(tree.root);              /* free the directory tree */
947    return OK;
948 }
949
950
951 /*
952  * This routine is used to get the current backup or a backup
953  *   before the specified date.
954  */
955 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
956 {
957    bool ok = false;
958    FILESET_DBR fsr;
959    CLIENT_DBR cr;
960    char fileset_name[MAX_NAME_LENGTH];
961    char ed1[50], ed2[50];
962    char pool_select[MAX_NAME_LENGTH];
963    int i;
964
965
966    /* Create temp tables */
967    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
968    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
969    if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
970       bsendmsg(ua, "%s\n", db_strerror(ua->db));
971    }
972    if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
973       bsendmsg(ua, "%s\n", db_strerror(ua->db));
974    }
975    /*
976     * Select Client from the Catalog
977     */
978    memset(&cr, 0, sizeof(cr));
979    if (!get_client_dbr(ua, &cr)) {
980       goto bail_out;
981    }
982    bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
983
984    /*
985     * Get FileSet
986     */
987    memset(&fsr, 0, sizeof(fsr));
988    i = find_arg_with_value(ua, "FileSet");
989    if (i >= 0) {
990       bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
991       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
992          bsendmsg(ua, _("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
993             db_strerror(ua->db));
994          i = -1;
995       }
996    }
997    if (i < 0) {                       /* fileset not found */
998       edit_int64(cr.ClientId, ed1);
999       Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1000       start_prompt(ua, _("The defined FileSet resources are:\n"));
1001       if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1002          bsendmsg(ua, "%s\n", db_strerror(ua->db));
1003       }
1004       if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1005                  fileset_name, sizeof(fileset_name)) < 0) {
1006          goto bail_out;
1007       }
1008
1009       bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1010       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1011          bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1012          bsendmsg(ua, _("This probably means you modified the FileSet.\n"
1013                      "Continuing anyway.\n"));
1014       }
1015    }
1016
1017    /* If Pool specified, add PoolId specification */
1018    pool_select[0] = 0;
1019    if (rx->pool) {
1020       POOL_DBR pr;
1021       memset(&pr, 0, sizeof(pr));
1022       bstrncpy(pr.Name, rx->pool->hdr.name, sizeof(pr.Name));
1023       if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1024          bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ", 
1025             edit_int64(pr.PoolId, ed1));
1026       } else {
1027          bsendmsg(ua, _("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1028       }
1029    }
1030
1031    /* Find JobId of last Full backup for this client, fileset */
1032    edit_int64(cr.ClientId, ed1);
1033    Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1034          pool_select);
1035    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1036       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1037       goto bail_out;
1038    }
1039
1040    /* Find all Volumes used by that JobId */
1041    if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1042       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1043       goto bail_out;
1044    }
1045    /* Note, this is needed because I don't seem to get the callback
1046     * from the call just above.
1047     */
1048    rx->JobTDate = 0;
1049    if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1050       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1051    }
1052    if (rx->JobTDate == 0) {
1053       bsendmsg(ua, _("No Full backup before %s found.\n"), date);
1054       goto bail_out;
1055    }
1056
1057    /* Now find most recent Differental Job after Full save, if any */
1058    Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1059         edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1060    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1061       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1062    }
1063    /* Now update JobTDate to lock onto Differental, if any */
1064    rx->JobTDate = 0;
1065    if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1066       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1067    }
1068    if (rx->JobTDate == 0) {
1069       bsendmsg(ua, _("No Full backup before %s found.\n"), date);
1070       goto bail_out;
1071    }
1072
1073    /* Now find all Incremental Jobs after Full/dif save */
1074    Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1075         edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1076    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1077       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1078    }
1079
1080    /* Get the JobIds from that list */
1081    rx->JobIds[0] = 0;
1082    rx->last_jobid[0] = 0;
1083    if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1084       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1085    }
1086
1087    if (rx->JobIds[0] != 0) {
1088       /* Display a list of Jobs selected for this restore */
1089       db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
1090       ok = true;
1091    } else {
1092       bsendmsg(ua, _("No jobs found.\n"));
1093    }
1094
1095 bail_out:
1096    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1097    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1098    return ok;
1099 }
1100
1101
1102 /* Return next JobId from comma separated list */
1103 static int get_next_jobid_from_list(char **p, uint32_t *JobId)
1104 {
1105    char jobid[30];
1106    char *q = *p;
1107
1108    jobid[0] = 0;
1109    for (int i=0; i<(int)sizeof(jobid); i++) {
1110       if (*q == 0) {
1111          break;
1112       } else if (*q == ',') {
1113          q++;
1114          break;
1115       }
1116       jobid[i] = *q++;
1117       jobid[i+1] = 0;
1118    }
1119    if (jobid[0] == 0) {
1120       return 0;
1121    } else if (!is_a_number(jobid)) {
1122       return -1;                      /* error */
1123    }
1124    *p = q;
1125    *JobId = str_to_int64(jobid);
1126    return 1;
1127 }
1128
1129 static int count_handler(void *ctx, int num_fields, char **row)
1130 {
1131    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1132    rx->JobId = str_to_int64(row[0]);
1133    rx->found = true;
1134    return 0;
1135 }
1136
1137 /*
1138  * Callback handler to get JobId and FileIndex for files
1139  *   can insert more than one depending on the caller.
1140  */
1141 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1142 {
1143    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1144    rx->JobId = str_to_int64(row[0]);
1145    add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1146    rx->found = true;
1147    rx->selected_files++;
1148    return 0;
1149 }
1150
1151 /*
1152  * Callback handler make list of JobIds
1153  */
1154 static int jobid_handler(void *ctx, int num_fields, char **row)
1155 {
1156    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1157
1158    if (strcmp(rx->last_jobid, row[0]) == 0) {
1159       return 0;                       /* duplicate id */
1160    }
1161    bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1162    if (rx->JobIds[0] != 0) {
1163       pm_strcat(rx->JobIds, ",");
1164    }
1165    pm_strcat(rx->JobIds, row[0]);
1166    return 0;
1167 }
1168
1169
1170 /*
1171  * Callback handler to pickup last Full backup JobTDate
1172  */
1173 static int last_full_handler(void *ctx, int num_fields, char **row)
1174 {
1175    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1176
1177    rx->JobTDate = str_to_int64(row[1]);
1178    return 0;
1179 }
1180
1181 /*
1182  * Callback handler build FileSet name prompt list
1183  */
1184 static int fileset_handler(void *ctx, int num_fields, char **row)
1185 {
1186    /* row[0] = FileSet (name) */
1187    if (row[0]) {
1188       add_prompt((UAContext *)ctx, row[0]);
1189    }
1190    return 0;
1191 }
1192
1193 /*
1194  * Called here with each name to be added to the list. The name is
1195  *   added to the list if it is not already in the list.
1196  *
1197  * Used to make unique list of FileSets and MediaTypes
1198  */
1199 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1200 {
1201    NAME_LIST *name = (NAME_LIST *)ctx;
1202
1203    if (name->num_ids == MAX_ID_LIST_LEN) {
1204       return 1;
1205    }
1206    if (name->num_ids == name->max_ids) {
1207       if (name->max_ids == 0) {
1208          name->max_ids = 1000;
1209          name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1210       } else {
1211          name->max_ids = (name->max_ids * 3) / 2;
1212          name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1213       }
1214    }
1215    for (int i=0; i<name->num_ids; i++) {
1216       if (strcmp(name->name[i], row[0]) == 0) {
1217          return 0;                    /* already in list, return */
1218       }
1219    }
1220    /* Add new name to list */
1221    name->name[name->num_ids++] = bstrdup(row[0]);
1222    return 0;
1223 }
1224
1225
1226 /*
1227  * Print names in the list
1228  */
1229 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1230 {
1231    for (int i=0; i < name_list->num_ids; i++) {
1232       bsendmsg(ua, "%s\n", name_list->name[i]);
1233    }
1234 }
1235
1236
1237 /*
1238  * Free names in the list
1239  */
1240 static void free_name_list(NAME_LIST *name_list)
1241 {
1242    for (int i=0; i < name_list->num_ids; i++) {
1243       free(name_list->name[i]);
1244    }
1245    if (name_list->name) {
1246       free(name_list->name);
1247       name_list->name = NULL;
1248    }
1249    name_list->max_ids = 0;
1250    name_list->num_ids = 0;
1251 }
1252
1253 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, RESTORE_CTX *rx)
1254 {
1255    STORE *store;
1256
1257    if (name_list->num_ids > 1) {
1258       bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1259          "Restore is not possible. The MediaTypes used are:\n"));
1260       print_name_list(ua, name_list);
1261       rx->store = select_storage_resource(ua);
1262       return;
1263    }
1264
1265    if (name_list->num_ids == 0) {
1266       bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1267       rx->store = select_storage_resource(ua);
1268       return;
1269    }
1270    if (rx->store) {
1271       return;
1272    }
1273    /*
1274     * We have a single MediaType, look it up in our Storage resource
1275     */
1276    LockRes();
1277    foreach_res(store, R_STORAGE) {
1278       if (strcmp(name_list->name[0], store->media_type) == 0) {
1279          if (acl_access_ok(ua, Storage_ACL, store->hdr.name)) {
1280             rx->store = store;
1281          }
1282          break;
1283       }
1284    }
1285    UnlockRes();
1286
1287    if (rx->store) {
1288       /* Check if an explicit storage resource is given */
1289       store = NULL;
1290       int i = find_arg_with_value(ua, "storage");
1291       if (i > 0) {
1292          store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1293          if (store && !acl_access_ok(ua, Storage_ACL, store->hdr.name)) {
1294             store = NULL;
1295          }
1296       }
1297       if (store && (store != rx->store)) {
1298          bsendmsg(ua, _("Warning default storage overridden by %s on command line.\n"),
1299             store->hdr.name);
1300          rx->store = store;
1301       }
1302       return;
1303    }
1304
1305    /* Take command line arg, or ask user if none */
1306    rx->store = get_storage_resource(ua, false /* don't use default */);
1307
1308    if (!rx->store) {
1309       bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1310          "MediaType \"%s\", needed by the Jobs you selected.\n"
1311          "You will be allowed to select a Storage device later.\n"),
1312          name_list->name[0]);
1313    }
1314 }