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