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