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