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