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