]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
- Add index file to JobId field of File records for PostgreSQL.
[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] = ' ';            /* blank filename */
691       rx->fname[1] = 0;
692       rx->fnl = 1;
693    }
694
695    rx->pnl = f - name;    
696    if (rx->pnl > 0) {
697       rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
698       memcpy(rx->path, name, rx->pnl);
699       rx->path[rx->pnl] = 0;
700    } else {
701       rx->path[0] = ' ';
702       rx->path[1] = 0;
703       rx->pnl = 1;
704    }
705
706    Dmsg2(100, "sllit path=%s file=%s\n", rx->path, rx->fname);
707 }
708
709 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
710 {
711    TREE_CTX tree;
712    JobId_t JobId, last_JobId;
713    char *p;
714    bool OK = true;
715
716    memset(&tree, 0, sizeof(TREE_CTX));
717    /* 
718     * Build the directory tree containing JobIds user selected
719     */
720    tree.root = new_tree(rx->TotalFiles);
721    tree.ua = ua;
722    tree.all = rx->all;
723    last_JobId = 0;
724    /*
725     * For display purposes, the same JobId, with different volumes may
726     * appear more than once, however, we only insert it once.
727     */
728    int items = 0;
729    p = rx->JobIds;
730    tree.FileEstimate = 0;
731    if (get_next_jobid_from_list(&p, &JobId) > 0) {
732       /* Use first JobId as estimate of the number of files to restore */
733       Mmsg(rx->query, uar_count_files, JobId);
734       if (!db_sql_query(ua->db, rx->query, count_handler, (void *)rx)) {
735          bsendmsg(ua, "%s\n", db_strerror(ua->db));
736       }
737       if (rx->found) {
738          /* Add about 25% more than this job for over estimate */
739          tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
740          tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
741       }
742    }
743    for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
744
745       if (JobId == last_JobId) {             
746          continue;                    /* eliminate duplicate JobIds */
747       }
748       last_JobId = JobId;
749       bsendmsg(ua, _("\nBuilding directory tree for JobId %u ...  "), JobId);
750       items++;
751       /*
752        * Find files for this JobId and insert them in the tree
753        */
754       Mmsg(rx->query, uar_sel_files, JobId);
755       if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
756          bsendmsg(ua, "%s", db_strerror(ua->db));
757       }
758       /*
759        * Find the MediaTypes for this JobId and add to the name_list
760        */
761       Mmsg(rx->query, uar_mediatype, JobId);
762       if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
763          bsendmsg(ua, "%s", db_strerror(ua->db));
764       }
765    }
766    char ec1[50];
767    bsendmsg(ua, "\n%d Job%s, %s files inserted into the tree%s.\n", 
768       items, items==1?"":"s", edit_uint64_with_commas(tree.FileCount, ec1),
769       tree.all?" and marked for extraction":"");
770
771    /* Check MediaType and select storage that corresponds */
772    get_storage_from_mediatype(ua, &rx->name_list, rx);
773
774    if (find_arg(ua, _("done")) < 0) {
775       /* Let the user interact in selecting which files to restore */
776       OK = user_select_files_from_tree(&tree);
777    }
778
779    /*
780     * Walk down through the tree finding all files marked to be 
781     *  extracted making a bootstrap file.
782     */
783    if (OK) {
784       for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
785          Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
786          if (node->extract || node->extract_dir) {
787             Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
788             add_findex(rx->bsr, node->JobId, node->FileIndex);
789             if (node->extract) {
790                rx->selected_files++;  /* count only saved files */
791             }
792          }
793       }
794    }
795
796    free_tree(tree.root);              /* free the directory tree */
797    return OK;
798 }
799
800
801 /*
802  * This routine is used to get the current backup or a backup
803  *   before the specified date.
804  */
805 static int select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
806 {
807    int stat = 0;
808    FILESET_DBR fsr;
809    CLIENT_DBR cr;
810    char fileset_name[MAX_NAME_LENGTH];
811    char ed1[50];
812    char pool_select[MAX_NAME_LENGTH];
813    int i;
814
815
816    /* Create temp tables */
817    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
818    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
819    if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
820       bsendmsg(ua, "%s\n", db_strerror(ua->db));
821    }
822    if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
823       bsendmsg(ua, "%s\n", db_strerror(ua->db));
824    }
825    /*
826     * Select Client from the Catalog
827     */
828    memset(&cr, 0, sizeof(cr));
829    if (!get_client_dbr(ua, &cr)) {
830       goto bail_out;
831    }
832    bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
833
834    /*
835     * Get FileSet 
836     */
837    memset(&fsr, 0, sizeof(fsr));
838    i = find_arg_with_value(ua, "FileSet"); 
839    if (i >= 0) {
840       bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
841       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
842          bsendmsg(ua, _("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet, 
843             db_strerror(ua->db));
844          i = -1;
845       }
846    }
847    if (i < 0) {                       /* fileset not found */
848       Mmsg(rx->query, uar_sel_fileset, cr.ClientId, cr.ClientId);
849       start_prompt(ua, _("The defined FileSet resources are:\n"));
850       if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
851          bsendmsg(ua, "%s\n", db_strerror(ua->db));
852       }
853       if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"), 
854                  fileset_name, sizeof(fileset_name)) < 0) {
855          goto bail_out;
856       }
857
858       bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
859       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
860          bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
861          bsendmsg(ua, _("This probably means you modified the FileSet.\n"
862                      "Continuing anyway.\n"));
863       }
864    }
865
866    /* If Pool specified, add PoolId specification */
867    pool_select[0] = 0;
868    if (rx->pool) {
869       POOL_DBR pr;
870       memset(&pr, 0, sizeof(pr));
871       bstrncpy(pr.Name, rx->pool->hdr.name, sizeof(pr.Name));
872       if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
873          bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%u ", pr.PoolId);
874       } else {
875          bsendmsg(ua, _("Pool \"%s\" not found, using any pool.\n"), pr.Name);
876       }
877    }
878
879    /* Find JobId of last Full backup for this client, fileset */
880    Mmsg(rx->query, uar_last_full, cr.ClientId, cr.ClientId, date, fsr.FileSet,
881          pool_select);
882    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
883       bsendmsg(ua, "%s\n", db_strerror(ua->db));
884       goto bail_out;
885    }
886
887    /* Find all Volumes used by that JobId */
888    if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
889       bsendmsg(ua, "%s\n", db_strerror(ua->db));
890       goto bail_out;
891    }
892    /* Note, this is needed because I don't seem to get the callback
893     * from the call just above.
894     */
895    rx->JobTDate = 0;
896    if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
897       bsendmsg(ua, "%s\n", db_strerror(ua->db));
898    }
899    if (rx->JobTDate == 0) {
900       bsendmsg(ua, _("No Full backup before %s found.\n"), date);
901       goto bail_out;
902    }
903
904    /* Now find most recent Differental Job after Full save, if any */
905    Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
906         cr.ClientId, fsr.FileSet, pool_select);
907    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
908       bsendmsg(ua, "%s\n", db_strerror(ua->db));
909    }
910    /* Now update JobTDate to lock onto Differental, if any */
911    rx->JobTDate = 0;
912    if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
913       bsendmsg(ua, "%s\n", db_strerror(ua->db));
914    }
915    if (rx->JobTDate == 0) {
916       bsendmsg(ua, _("No Full backup before %s found.\n"), date);
917       goto bail_out;
918    }
919
920    /* Now find all Incremental Jobs after Full/dif save */
921    Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
922         cr.ClientId, fsr.FileSet, pool_select);
923    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
924       bsendmsg(ua, "%s\n", db_strerror(ua->db));
925    }
926
927    /* Get the JobIds from that list */
928    rx->JobIds[0] = 0;
929    rx->last_jobid[0] = 0;
930    if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
931       bsendmsg(ua, "%s\n", db_strerror(ua->db));
932    }
933
934    if (rx->JobIds[0] != 0) {
935       /* Display a list of Jobs selected for this restore */
936       db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
937    } else {
938       bsendmsg(ua, _("No jobs found.\n")); 
939    }
940
941    stat = 1;
942  
943 bail_out:
944    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
945    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
946    return stat;
947 }
948
949
950 /* Return next JobId from comma separated list */
951 static int get_next_jobid_from_list(char **p, uint32_t *JobId)
952 {
953    char jobid[30];
954    char *q = *p;
955
956    jobid[0] = 0;
957    for (int i=0; i<(int)sizeof(jobid); i++) {
958       if (*q == ',' || *q == 0) {
959          q++;
960          break;
961       }
962       jobid[i] = *q++;
963       jobid[i+1] = 0;
964    }
965    if (jobid[0] == 0 || !is_a_number(jobid)) {
966       return 0;
967    }
968    *p = q;
969    *JobId = strtoul(jobid, NULL, 10);
970    return 1;
971 }
972
973 static int count_handler(void *ctx, int num_fields, char **row)
974 {
975    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
976    rx->JobId = atoi(row[0]);
977    rx->found = true;
978    return 0;
979 }
980
981 /*
982  * Callback handler to get JobId and FileIndex for files
983  */
984 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
985 {
986    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
987    rx->JobId = atoi(row[0]);
988    add_findex(rx->bsr, rx->JobId, atoi(row[1]));
989    rx->found = true;
990    return 0;
991 }
992
993 /*
994  * Callback handler make list of JobIds
995  */
996 static int jobid_handler(void *ctx, int num_fields, char **row)
997 {
998    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
999
1000    if (strcmp(rx->last_jobid, row[0]) == 0) {           
1001       return 0;                       /* duplicate id */
1002    }
1003    bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1004    if (rx->JobIds[0] != 0) {
1005       pm_strcat(rx->JobIds, ",");
1006    }
1007    pm_strcat(rx->JobIds, row[0]);
1008    return 0;
1009 }
1010
1011
1012 /*
1013  * Callback handler to pickup last Full backup JobTDate
1014  */
1015 static int last_full_handler(void *ctx, int num_fields, char **row)
1016 {
1017    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1018
1019    rx->JobTDate = str_to_int64(row[1]); 
1020    return 0;
1021 }
1022
1023 /*
1024  * Callback handler build FileSet name prompt list
1025  */
1026 static int fileset_handler(void *ctx, int num_fields, char **row)
1027 {
1028    /* row[0] = FileSet (name) */
1029    if (row[0]) {
1030       add_prompt((UAContext *)ctx, row[0]);
1031    }
1032    return 0;
1033 }
1034
1035 /*
1036  * Called here with each name to be added to the list. The name is
1037  *   added to the list if it is not already in the list.
1038  *
1039  * Used to make unique list of FileSets and MediaTypes
1040  */
1041 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1042 {
1043    NAME_LIST *name = (NAME_LIST *)ctx;
1044
1045    if (name->num_ids == MAX_ID_LIST_LEN) {  
1046       return 1;
1047    }
1048    if (name->num_ids == name->max_ids) {
1049       if (name->max_ids == 0) {
1050          name->max_ids = 1000;
1051          name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1052       } else {
1053          name->max_ids = (name->max_ids * 3) / 2;
1054          name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1055       }
1056    }
1057    for (int i=0; i<name->num_ids; i++) {
1058       if (strcmp(name->name[i], row[0]) == 0) {
1059          return 0;                    /* already in list, return */
1060       }
1061    }
1062    /* Add new name to list */
1063    name->name[name->num_ids++] = bstrdup(row[0]);
1064    return 0;
1065 }
1066
1067
1068 /*
1069  * Print names in the list
1070  */
1071 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1072
1073    for (int i=0; i < name_list->num_ids; i++) {
1074       bsendmsg(ua, "%s\n", name_list->name[i]);
1075    }
1076 }
1077
1078
1079 /*
1080  * Free names in the list
1081  */
1082 static void free_name_list(NAME_LIST *name_list)
1083
1084    for (int i=0; i < name_list->num_ids; i++) {
1085       free(name_list->name[i]);
1086    }
1087    if (name_list->name) {
1088       free(name_list->name);
1089       name_list->name = NULL;
1090    }
1091    name_list->max_ids = 0;
1092    name_list->num_ids = 0;
1093 }
1094
1095 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, RESTORE_CTX *rx)
1096 {
1097    STORE *store;
1098
1099    if (name_list->num_ids > 1) {
1100       bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1101          "Restore is not possible. The MediaTypes used are:\n"));
1102       print_name_list(ua, name_list);
1103       rx->store = select_storage_resource(ua);
1104       return;
1105    }
1106
1107    if (name_list->num_ids == 0) {
1108       bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1109       rx->store = select_storage_resource(ua);
1110       return;
1111    }
1112    if (rx->store) {
1113       return;
1114    }
1115    /*
1116     * We have a single MediaType, look it up in our Storage resource 
1117     */
1118    LockRes();
1119    foreach_res(store, R_STORAGE) {
1120       if (strcmp(name_list->name[0], store->media_type) == 0) {
1121          if (acl_access_ok(ua, Storage_ACL, store->hdr.name)) {
1122             rx->store = store;
1123          }
1124          break;
1125       }
1126    }
1127    UnlockRes();
1128
1129    if (rx->store) {
1130       /* Check if an explicit storage resource is given */
1131       store = NULL;
1132       int i = find_arg_with_value(ua, "storage");        
1133       if (i > 0) {
1134          store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1135          if (store && !acl_access_ok(ua, Storage_ACL, store->hdr.name)) {
1136             store = NULL;
1137          }
1138       }
1139       if (store && (store != rx->store)) {
1140          bsendmsg(ua, _("Warning default storage overridden by %s on command line.\n"),
1141             store->hdr.name);
1142          rx->store = store;
1143       }
1144       return;
1145    }
1146
1147    /* Take command line arg, or ask user if none */
1148    rx->store = get_storage_resource(ua, false /* don't use default */);
1149
1150    if (!rx->store) {
1151       bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1152          "MediaType \"%s\", needed by the Jobs you selected.\n"
1153          "You will be allowed to select a Storage device later.\n"),
1154          name_list->name[0]); 
1155    }
1156 }