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