]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
Fix run pool problem + remove bad ASSERTs
[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    Dmsg1(000, "Query=%s\n", rx->query);
596    rx->found = false;
597    /* Find and insert jobid and File Index */
598    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
599       bsendmsg(ua, _("Query failed: %s. ERR=%s\n"), 
600          rx->query, db_strerror(ua->db));
601    }
602    if (!rx->found) {
603       bsendmsg(ua, _("No database record found for: %s\n"), file);
604       return 0;
605    }
606    rx->selected_files++;
607    /*
608     * Find the MediaTypes for this JobId and add to the name_list
609     */
610    Mmsg(&rx->query, uar_mediatype, rx->JobId);
611    if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
612       bsendmsg(ua, "%s", db_strerror(ua->db));
613       return 0;
614    }
615    return 1;
616 }
617
618 static void split_path_and_filename(RESTORE_CTX *rx, char *name)
619 {
620    char *p, *f;
621
622    /* Find path without the filename.  
623     * I.e. everything after the last / is a "filename".
624     * OK, maybe it is a directory name, but we treat it like
625     * a filename. If we don't find a / then the whole name
626     * must be a path name (e.g. c:).
627     */
628    for (p=f=name; *p; p++) {
629       if (*p == '/') {
630          f = p;                       /* set pos of last slash */
631       }
632    }
633    if (*f == '/') {                   /* did we find a slash? */
634       f++;                            /* yes, point to filename */
635    } else {                           /* no, whole thing must be path name */
636       f = p;
637    }
638
639    /* If filename doesn't exist (i.e. root directory), we
640     * simply create a blank name consisting of a single 
641     * space. This makes handling zero length filenames
642     * easier.
643     */
644    rx->fnl = p - f;
645    if (rx->fnl > 0) {
646       rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
647       memcpy(rx->fname, f, rx->fnl);    /* copy filename */
648       rx->fname[rx->fnl] = 0;
649    } else {
650       rx->fname[0] = ' ';            /* blank filename */
651       rx->fname[1] = 0;
652       rx->fnl = 1;
653    }
654
655    rx->pnl = f - name;    
656    if (rx->pnl > 0) {
657       rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
658       memcpy(rx->path, name, rx->pnl);
659       rx->path[rx->pnl] = 0;
660    } else {
661       rx->path[0] = ' ';
662       rx->path[1] = 0;
663       rx->pnl = 1;
664    }
665
666    Dmsg2(100, "sllit path=%s file=%s\n", rx->path, rx->fname);
667 }
668
669 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
670 {
671    TREE_CTX tree;
672    JobId_t JobId, last_JobId;
673    char *p;
674    char *nofname = "";
675    bool OK = true;
676
677    memset(&tree, 0, sizeof(TREE_CTX));
678    /* 
679     * Build the directory tree containing JobIds user selected
680     */
681    tree.root = new_tree(rx->TotalFiles);
682    tree.root->fname = nofname;
683    tree.ua = ua;
684    last_JobId = 0;
685    /*
686     * For display purposes, the same JobId, with different volumes may
687     * appear more than once, however, we only insert it once.
688     */
689    int items = 0;
690    for (p=rx->JobIds; next_jobid_from_list(&p, &JobId) > 0; ) {
691
692       if (JobId == last_JobId) {             
693          continue;                    /* eliminate duplicate JobIds */
694       }
695       last_JobId = JobId;
696       bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
697       items++;
698       /*
699        * Find files for this JobId and insert them in the tree
700        */
701       Mmsg(&rx->query, uar_sel_files, JobId);
702       if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
703          bsendmsg(ua, "%s", db_strerror(ua->db));
704       }
705       /*
706        * Find the MediaTypes for this JobId and add to the name_list
707        */
708       Mmsg(&rx->query, uar_mediatype, JobId);
709       if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
710          bsendmsg(ua, "%s", db_strerror(ua->db));
711       }
712    }
713    bsendmsg(ua, "%d Job%s inserted into the tree and marked for extraction.\n", 
714       items, items==1?"":"s");
715
716    /* Check MediaType and select storage that corresponds */
717    get_storage_from_mediatype(ua, &rx->name_list, rx);
718
719    if (find_arg(ua, _("all")) < 0) {
720       /* Let the user interact in selecting which files to restore */
721       OK = user_select_files_from_tree(&tree);
722    }
723
724    /*
725     * Walk down through the tree finding all files marked to be 
726     *  extracted making a bootstrap file.
727     */
728    if (OK) {
729       for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
730          Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
731          if (node->extract || node->extract_dir) {
732             Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
733             add_findex(rx->bsr, node->JobId, node->FileIndex);
734             rx->selected_files++;
735          }
736       }
737    }
738
739    free_tree(tree.root);              /* free the directory tree */
740    return OK;
741 }
742
743
744 /*
745  * This routine is used to get the current backup or a backup
746  *   before the specified date.
747  */
748 static int select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
749 {
750    int stat = 0;
751    FILESET_DBR fsr;
752    CLIENT_DBR cr;
753    char fileset_name[MAX_NAME_LENGTH];
754    char ed1[50];
755    char pool_select[MAX_NAME_LENGTH];
756    int i;
757
758
759    /* Create temp tables */
760    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
761    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
762    if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
763       bsendmsg(ua, "%s\n", db_strerror(ua->db));
764    }
765    if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
766       bsendmsg(ua, "%s\n", db_strerror(ua->db));
767    }
768    /*
769     * Select Client from the Catalog
770     */
771    memset(&cr, 0, sizeof(cr));
772    if (!get_client_dbr(ua, &cr)) {
773       goto bail_out;
774    }
775    bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
776
777    /*
778     * Get FileSet 
779     */
780    memset(&fsr, 0, sizeof(fsr));
781    i = find_arg_with_value(ua, "FileSet"); 
782    if (i >= 0) {
783       bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
784       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
785          bsendmsg(ua, _("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet, 
786             db_strerror(ua->db));
787          i = -1;
788       }
789    }
790    if (i < 0) {                       /* fileset not found */
791       Mmsg(&rx->query, uar_sel_fileset, cr.ClientId, cr.ClientId);
792       start_prompt(ua, _("The defined FileSet resources are:\n"));
793       if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
794          bsendmsg(ua, "%s\n", db_strerror(ua->db));
795       }
796       if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"), 
797                  fileset_name, sizeof(fileset_name)) < 0) {
798          goto bail_out;
799       }
800
801       bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
802       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
803          bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
804          bsendmsg(ua, _("This probably means you modified the FileSet.\n"
805                      "Continuing anyway.\n"));
806       }
807    }
808
809    /* If Pool specified, add PoolId specification */
810    pool_select[0] = 0;
811    if (rx->pool) {
812       POOL_DBR pr;
813       memset(&pr, 0, sizeof(pr));
814       bstrncpy(pr.Name, rx->pool->hdr.name, sizeof(pr.Name));
815       if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
816          bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%u ", pr.PoolId);
817       } else {
818          bsendmsg(ua, _("Pool \"%s\" not found, using any pool.\n"), pr.Name);
819       }
820    }
821
822    /* Find JobId of last Full backup for this client, fileset */
823    Mmsg(&rx->query, uar_last_full, cr.ClientId, cr.ClientId, date, fsr.FileSet,
824          pool_select);
825    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
826       bsendmsg(ua, "%s\n", db_strerror(ua->db));
827       goto bail_out;
828    }
829
830    /* Find all Volumes used by that JobId */
831    if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
832       bsendmsg(ua, "%s\n", db_strerror(ua->db));
833       goto bail_out;
834    }
835    /* Note, this is needed because I don't seem to get the callback
836     * from the call just above.
837     */
838    rx->JobTDate = 0;
839    if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
840       bsendmsg(ua, "%s\n", db_strerror(ua->db));
841    }
842    if (rx->JobTDate == 0) {
843       bsendmsg(ua, _("No Full backup before %s found.\n"), date);
844       goto bail_out;
845    }
846
847    /* Now find most recent Differental Job after Full save, if any */
848    Mmsg(&rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
849         cr.ClientId, fsr.FileSet, pool_select);
850    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
851       bsendmsg(ua, "%s\n", db_strerror(ua->db));
852    }
853    /* Now update JobTDate to lock onto Differental, if any */
854    rx->JobTDate = 0;
855    if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
856       bsendmsg(ua, "%s\n", db_strerror(ua->db));
857    }
858    if (rx->JobTDate == 0) {
859       bsendmsg(ua, _("No Full backup before %s found.\n"), date);
860       goto bail_out;
861    }
862
863    /* Now find all Incremental Jobs after Full/dif save */
864    Mmsg(&rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
865         cr.ClientId, fsr.FileSet, pool_select);
866    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
867       bsendmsg(ua, "%s\n", db_strerror(ua->db));
868    }
869
870    /* Get the JobIds from that list */
871    rx->JobIds[0] = 0;
872    rx->last_jobid[0] = 0;
873    if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
874       bsendmsg(ua, "%s\n", db_strerror(ua->db));
875    }
876
877    if (rx->JobIds[0] != 0) {
878       /* Display a list of Jobs selected for this restore */
879       db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
880    } else {
881       bsendmsg(ua, _("No jobs found.\n")); 
882    }
883
884    stat = 1;
885  
886 bail_out:
887    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
888    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
889    return stat;
890 }
891
892
893 /* Return next JobId from comma separated list */
894 static int next_jobid_from_list(char **p, uint32_t *JobId)
895 {
896    char jobid[30];
897    char *q = *p;
898
899    jobid[0] = 0;
900    for (int i=0; i<(int)sizeof(jobid); i++) {
901       if (*q == ',' || *q == 0) {
902          q++;
903          break;
904       }
905       jobid[i] = *q++;
906       jobid[i+1] = 0;
907    }
908    if (jobid[0] == 0 || !is_a_number(jobid)) {
909       return 0;
910    }
911    *p = q;
912    *JobId = strtoul(jobid, NULL, 10);
913    return 1;
914 }
915
916 /*
917  * Callback handler to get JobId and FileIndex for files
918  */
919 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
920 {
921    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
922    rx->JobId = atoi(row[0]);
923    add_findex(rx->bsr, rx->JobId, atoi(row[1]));
924    rx->found = true;
925    return 0;
926 }
927
928 /*
929  * Callback handler make list of JobIds
930  */
931 static int jobid_handler(void *ctx, int num_fields, char **row)
932 {
933    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
934
935    if (strcmp(rx->last_jobid, row[0]) == 0) {           
936       return 0;                       /* duplicate id */
937    }
938    bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
939    if (rx->JobIds[0] != 0) {
940       pm_strcat(&rx->JobIds, ",");
941    }
942    pm_strcat(&rx->JobIds, row[0]);
943    return 0;
944 }
945
946
947 /*
948  * Callback handler to pickup last Full backup JobTDate
949  */
950 static int last_full_handler(void *ctx, int num_fields, char **row)
951 {
952    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
953
954    rx->JobTDate = str_to_int64(row[1]); 
955    return 0;
956 }
957
958 /*
959  * Callback handler build FileSet name prompt list
960  */
961 static int fileset_handler(void *ctx, int num_fields, char **row)
962 {
963    /* row[0] = FileSet (name) */
964    if (row[0]) {
965       add_prompt((UAContext *)ctx, row[0]);
966    }
967    return 0;
968 }
969
970 /*
971  * Called here with each name to be added to the list. The name is
972  *   added to the list if it is not already in the list.
973  *
974  * Used to make unique list of FileSets and MediaTypes
975  */
976 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
977 {
978    NAME_LIST *name = (NAME_LIST *)ctx;
979
980    if (name->num_ids == MAX_ID_LIST_LEN) {  
981       return 1;
982    }
983    if (name->num_ids == name->max_ids) {
984       if (name->max_ids == 0) {
985          name->max_ids = 1000;
986          name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
987       } else {
988          name->max_ids = (name->max_ids * 3) / 2;
989          name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
990       }
991    }
992    for (int i=0; i<name->num_ids; i++) {
993       if (strcmp(name->name[i], row[0]) == 0) {
994          return 0;                    /* already in list, return */
995       }
996    }
997    /* Add new name to list */
998    name->name[name->num_ids++] = bstrdup(row[0]);
999    return 0;
1000 }
1001
1002
1003 /*
1004  * Print names in the list
1005  */
1006 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1007
1008    for (int i=0; i < name_list->num_ids; i++) {
1009       bsendmsg(ua, "%s\n", name_list->name[i]);
1010    }
1011 }
1012
1013
1014 /*
1015  * Free names in the list
1016  */
1017 static void free_name_list(NAME_LIST *name_list)
1018
1019    for (int i=0; i < name_list->num_ids; i++) {
1020       free(name_list->name[i]);
1021    }
1022    if (name_list->name) {
1023       free(name_list->name);
1024       name_list->name = NULL;
1025    }
1026    name_list->max_ids = 0;
1027    name_list->num_ids = 0;
1028 }
1029
1030 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, RESTORE_CTX *rx)
1031 {
1032    if (name_list->num_ids > 1) {
1033       bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1034          "Restore is not possible. The MediaTypes used are:\n"));
1035       print_name_list(ua, name_list);
1036       rx->store = select_storage_resource(ua);
1037       return;
1038    }
1039
1040    if (name_list->num_ids == 0) {
1041       bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1042       rx->store = select_storage_resource(ua);
1043       return;
1044    }
1045
1046    rx->store = get_storage_resource(ua, false /* don't use default */);
1047
1048    if (!rx->store) {
1049       bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1050          "MediaType %s, needed by the Jobs you selected.\n"
1051          "You will be allowed to select a Storage device later.\n"),
1052          name_list->name[0]); 
1053    }
1054 }