]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
9c9faec58e6240fd36480cad993a1956398a82dc
[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
419       start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
420       for (int i=0; list[i]; i++) {
421          add_prompt(ua, list[i]);
422       }
423       done = true;
424       switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
425       case -1:                        /* error */
426          return 0;
427       case 0:                         /* list last 20 Jobs run */
428          db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
429          done = false;
430          break;
431       case 1:                         /* list where a file is saved */
432          if (!get_cmd(ua, _("Enter Filename: "))) {
433             return 0;
434          }
435          len = strlen(ua->cmd);
436          fname = (char *)malloc(len * 2 + 1);
437          db_escape_string(fname, ua->cmd, len);
438          Mmsg(&rx->query, uar_file, fname);
439          free(fname);
440          db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
441          done = false;
442          break;
443       case 2:                         /* enter a list of JobIds */
444          if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
445             return 0;
446          }
447          pm_strcpy(&rx->JobIds, ua->cmd);
448          break;
449       case 3:                         /* Enter an SQL list command */
450          if (!get_cmd(ua, _("Enter SQL list command: "))) {
451             return 0;
452          }
453          db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
454          done = false;
455          break;
456       case 4:                         /* Select the most recent backups */
457          bstrutime(date, sizeof(date), time(NULL));
458          if (!select_backups_before_date(ua, rx, date)) {
459             return 0;
460          }
461          break;
462       case 5:                         /* select backup at specified time */
463          if (!get_date(ua, date, sizeof(date))) {
464             return 0;
465          }
466          if (!select_backups_before_date(ua, rx, date)) {
467             return 0;
468          }
469          break;
470       case 6:                         /* Enter files */
471          bstrutime(date, sizeof(date), time(NULL));
472          if (!get_client_name(ua, rx)) {
473             return 0;
474          }
475          bsendmsg(ua, _("Enter file names, or < to enter a filename\n"      
476                         "containg a list of file names, and terminate\n"
477                         "them with a blank line.\n"));
478          for ( ;; ) {
479             if (!get_cmd(ua, _("Enter filename: "))) {
480                return 0;
481             }
482             len = strlen(ua->cmd);
483             if (len == 0) {
484                break;
485             }
486             insert_one_file(ua, rx, date);
487          }
488          /* Check MediaType and select storage that corresponds */
489          if (rx->name_list.num_ids) {
490             get_storage_from_mediatype(ua, &rx->name_list, rx);
491          }
492          return 2;
493        case 7:                        /* enter files backed up before specified time */
494          if (!get_date(ua, date, sizeof(date))) {
495             return 0;
496          }
497          if (!get_client_name(ua, rx)) {
498             return 0;
499          }
500          bsendmsg(ua, _("Enter file names, or < to enter a filename\n"      
501                         "containg a list of file names, and terminate\n"
502                         "them with a blank line.\n"));
503          for ( ;; ) {
504             if (!get_cmd(ua, _("Enter filename: "))) {
505                return 0;
506             }
507             len = strlen(ua->cmd);
508             if (len == 0) {
509                break;
510             }
511             insert_one_file(ua, rx, date);
512          }
513          /* Check MediaType and select storage that corresponds */
514          if (rx->name_list.num_ids) {
515             get_storage_from_mediatype(ua, &rx->name_list, rx);
516          }
517          return 2;
518
519       
520       case 8:                         /* Cancel or quit */
521          return 0;
522       }
523    }
524
525    if (*rx->JobIds == 0) {
526       bsendmsg(ua, _("No Jobs selected.\n"));
527       return 0;
528    }
529    bsendmsg(ua, _("You have selected the following JobId%s: %s\n"), 
530       strchr(rx->JobIds,',')?"s":"",rx->JobIds);
531
532    memset(&jr, 0, sizeof(JOB_DBR));
533
534    rx->TotalFiles = 0;
535    for (p=rx->JobIds; ; ) {
536       int stat = get_next_jobid_from_list(&p, &JobId);
537       if (stat < 0) {
538          bsendmsg(ua, _("Invalid JobId in list.\n"));
539          return 0;
540       }
541       if (stat == 0) {
542          break;
543       }
544       if (jr.JobId == JobId) {
545          continue;                    /* duplicate of last JobId */
546       }
547       jr.JobId = JobId;
548       if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
549          bsendmsg(ua, _("Unable to get Job record for JobId=%u: ERR=%s\n"), 
550             JobId, db_strerror(ua->db));
551          return 0;
552       }
553       if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
554          bsendmsg(ua, _("No authorization. Job \"%s\" not selected.\n"), 
555             jr.Name);
556          continue; 
557       }
558       rx->TotalFiles += jr.JobFiles;
559    }
560    return 1;
561 }
562
563 /* 
564  * Get date from user
565  */
566 static int get_date(UAContext *ua, char *date, int date_len)
567 {
568    bsendmsg(ua, _("The restored files will the most current backup\n"
569                   "BEFORE the date you specify below.\n\n"));
570    for ( ;; ) {
571       if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
572          return 0;
573       }
574       if (str_to_utime(ua->cmd) != 0) {
575          break;
576       }
577       bsendmsg(ua, _("Improper date format.\n"));
578    }              
579    bstrncpy(date, ua->cmd, date_len);
580    return 1;
581 }
582
583 /*
584  * Insert a single file, or read a list of files from a file 
585  */
586 static void insert_one_file(UAContext *ua, RESTORE_CTX *rx, char *date)
587 {
588    FILE *ffd;
589    char file[5000];
590    char *p = ua->cmd;
591    int line = 0;
592   
593    switch (*p) {
594    case '<':
595       p++;
596       if ((ffd = fopen(p, "r")) == NULL) {
597          bsendmsg(ua, _("Cannot open file %s: ERR=%s\n"),
598             p, strerror(errno));
599          break;
600       }
601       while (fgets(file, sizeof(file), ffd)) {
602          line++;
603          if (!insert_file_into_findex_list(ua, rx, file, date)) {
604             bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
605          }
606       }
607       fclose(ffd);
608       break;
609    default:
610       insert_file_into_findex_list(ua, rx, ua->cmd, date);
611       break;
612    }
613 }
614
615 /*
616  * For a given file (path+filename), split into path and file, then
617  *   lookup the most recent backup in the catalog to get the JobId
618  *   and FileIndex, then insert them into the findex list.
619  */
620 static int insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file, 
621                                         char *date)
622 {
623    strip_trailing_junk(file);
624    split_path_and_filename(rx, file);
625    Mmsg(&rx->query, uar_jobid_fileindex, date, rx->path, rx->fname, rx->ClientName);
626    rx->found = false;
627    /* Find and insert jobid and File Index */
628    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
629       bsendmsg(ua, _("Query failed: %s. ERR=%s\n"), 
630          rx->query, db_strerror(ua->db));
631    }
632    if (!rx->found) {
633       bsendmsg(ua, _("No database record found for: %s\n"), file);
634       return 0;
635    }
636    rx->selected_files++;
637    /*
638     * Find the MediaTypes for this JobId and add to the name_list
639     */
640    Mmsg(&rx->query, uar_mediatype, rx->JobId);
641    if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
642       bsendmsg(ua, "%s", db_strerror(ua->db));
643       return 0;
644    }
645    return 1;
646 }
647
648 static void split_path_and_filename(RESTORE_CTX *rx, char *name)
649 {
650    char *p, *f;
651
652    /* Find path without the filename.  
653     * I.e. everything after the last / is a "filename".
654     * OK, maybe it is a directory name, but we treat it like
655     * a filename. If we don't find a / then the whole name
656     * must be a path name (e.g. c:).
657     */
658    for (p=f=name; *p; p++) {
659       if (*p == '/') {
660          f = p;                       /* set pos of last slash */
661       }
662    }
663    if (*f == '/') {                   /* did we find a slash? */
664       f++;                            /* yes, point to filename */
665    } else {                           /* no, whole thing must be path name */
666       f = p;
667    }
668
669    /* If filename doesn't exist (i.e. root directory), we
670     * simply create a blank name consisting of a single 
671     * space. This makes handling zero length filenames
672     * easier.
673     */
674    rx->fnl = p - f;
675    if (rx->fnl > 0) {
676       rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
677       memcpy(rx->fname, f, rx->fnl);    /* copy filename */
678       rx->fname[rx->fnl] = 0;
679    } else {
680       rx->fname[0] = ' ';            /* blank filename */
681       rx->fname[1] = 0;
682       rx->fnl = 1;
683    }
684
685    rx->pnl = f - name;    
686    if (rx->pnl > 0) {
687       rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
688       memcpy(rx->path, name, rx->pnl);
689       rx->path[rx->pnl] = 0;
690    } else {
691       rx->path[0] = ' ';
692       rx->path[1] = 0;
693       rx->pnl = 1;
694    }
695
696    Dmsg2(100, "sllit path=%s file=%s\n", rx->path, rx->fname);
697 }
698
699 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
700 {
701    TREE_CTX tree;
702    JobId_t JobId, last_JobId;
703    char *p;
704    bool OK = true;
705
706    memset(&tree, 0, sizeof(TREE_CTX));
707    /* 
708     * Build the directory tree containing JobIds user selected
709     */
710    tree.root = new_tree(rx->TotalFiles);
711    tree.ua = ua;
712    tree.all = rx->all;
713    last_JobId = 0;
714    /*
715     * For display purposes, the same JobId, with different volumes may
716     * appear more than once, however, we only insert it once.
717     */
718    int items = 0;
719    p = rx->JobIds;
720    tree.FileEstimate = 0;
721    if (get_next_jobid_from_list(&p, &JobId) > 0) {
722       /* Use first JobId as estimate of the number of files to restore */
723       Mmsg(&rx->query, uar_count_files, JobId);
724       if (!db_sql_query(ua->db, rx->query, count_handler, (void *)rx)) {
725          bsendmsg(ua, "%s\n", db_strerror(ua->db));
726       }
727       if (rx->found) {
728          /* Add about 25% more than this job for over estimate */
729          tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
730          tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
731       }
732    }
733    for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
734
735       if (JobId == last_JobId) {             
736          continue;                    /* eliminate duplicate JobIds */
737       }
738       last_JobId = JobId;
739       bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
740       items++;
741       /*
742        * Find files for this JobId and insert them in the tree
743        */
744       Mmsg(&rx->query, uar_sel_files, JobId);
745       if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
746          bsendmsg(ua, "%s", db_strerror(ua->db));
747       }
748       /*
749        * Find the MediaTypes for this JobId and add to the name_list
750        */
751       Mmsg(&rx->query, uar_mediatype, JobId);
752       if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
753          bsendmsg(ua, "%s", db_strerror(ua->db));
754       }
755    }
756    char ec1[50];
757    bsendmsg(ua, "\n%d Job%s, %s files inserted into the tree%s.\n", 
758       items, items==1?"":"s", edit_uint64_with_commas(tree.FileCount, ec1),
759       tree.all?" and marked for extraction":"");
760
761    /* Check MediaType and select storage that corresponds */
762    get_storage_from_mediatype(ua, &rx->name_list, rx);
763
764    if (find_arg(ua, _("done")) < 0) {
765       /* Let the user interact in selecting which files to restore */
766       OK = user_select_files_from_tree(&tree);
767    }
768
769    /*
770     * Walk down through the tree finding all files marked to be 
771     *  extracted making a bootstrap file.
772     */
773    if (OK) {
774       for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
775          Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
776          if (node->extract || node->extract_dir) {
777             Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
778             add_findex(rx->bsr, node->JobId, node->FileIndex);
779             if (node->extract) {
780                rx->selected_files++;  /* count only saved files */
781             }
782          }
783       }
784    }
785
786    free_tree(tree.root);              /* free the directory tree */
787    return OK;
788 }
789
790
791 /*
792  * This routine is used to get the current backup or a backup
793  *   before the specified date.
794  */
795 static int select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
796 {
797    int stat = 0;
798    FILESET_DBR fsr;
799    CLIENT_DBR cr;
800    char fileset_name[MAX_NAME_LENGTH];
801    char ed1[50];
802    char pool_select[MAX_NAME_LENGTH];
803    int i;
804
805
806    /* Create temp tables */
807    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
808    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
809    if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
810       bsendmsg(ua, "%s\n", db_strerror(ua->db));
811    }
812    if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
813       bsendmsg(ua, "%s\n", db_strerror(ua->db));
814    }
815    /*
816     * Select Client from the Catalog
817     */
818    memset(&cr, 0, sizeof(cr));
819    if (!get_client_dbr(ua, &cr)) {
820       goto bail_out;
821    }
822    bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
823
824    /*
825     * Get FileSet 
826     */
827    memset(&fsr, 0, sizeof(fsr));
828    i = find_arg_with_value(ua, "FileSet"); 
829    if (i >= 0) {
830       bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
831       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
832          bsendmsg(ua, _("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet, 
833             db_strerror(ua->db));
834          i = -1;
835       }
836    }
837    if (i < 0) {                       /* fileset not found */
838       Mmsg(&rx->query, uar_sel_fileset, cr.ClientId, cr.ClientId);
839       start_prompt(ua, _("The defined FileSet resources are:\n"));
840       if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
841          bsendmsg(ua, "%s\n", db_strerror(ua->db));
842       }
843       if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"), 
844                  fileset_name, sizeof(fileset_name)) < 0) {
845          goto bail_out;
846       }
847
848       bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
849       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
850          bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
851          bsendmsg(ua, _("This probably means you modified the FileSet.\n"
852                      "Continuing anyway.\n"));
853       }
854    }
855
856    /* If Pool specified, add PoolId specification */
857    pool_select[0] = 0;
858    if (rx->pool) {
859       POOL_DBR pr;
860       memset(&pr, 0, sizeof(pr));
861       bstrncpy(pr.Name, rx->pool->hdr.name, sizeof(pr.Name));
862       if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
863          bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%u ", pr.PoolId);
864       } else {
865          bsendmsg(ua, _("Pool \"%s\" not found, using any pool.\n"), pr.Name);
866       }
867    }
868
869    /* Find JobId of last Full backup for this client, fileset */
870    Mmsg(&rx->query, uar_last_full, cr.ClientId, cr.ClientId, date, fsr.FileSet,
871          pool_select);
872    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
873       bsendmsg(ua, "%s\n", db_strerror(ua->db));
874       goto bail_out;
875    }
876
877    /* Find all Volumes used by that JobId */
878    if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
879       bsendmsg(ua, "%s\n", db_strerror(ua->db));
880       goto bail_out;
881    }
882    /* Note, this is needed because I don't seem to get the callback
883     * from the call just above.
884     */
885    rx->JobTDate = 0;
886    if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
887       bsendmsg(ua, "%s\n", db_strerror(ua->db));
888    }
889    if (rx->JobTDate == 0) {
890       bsendmsg(ua, _("No Full backup before %s found.\n"), date);
891       goto bail_out;
892    }
893
894    /* Now find most recent Differental Job after Full save, if any */
895    Mmsg(&rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
896         cr.ClientId, fsr.FileSet, pool_select);
897    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
898       bsendmsg(ua, "%s\n", db_strerror(ua->db));
899    }
900    /* Now update JobTDate to lock onto Differental, if any */
901    rx->JobTDate = 0;
902    if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
903       bsendmsg(ua, "%s\n", db_strerror(ua->db));
904    }
905    if (rx->JobTDate == 0) {
906       bsendmsg(ua, _("No Full backup before %s found.\n"), date);
907       goto bail_out;
908    }
909
910    /* Now find all Incremental Jobs after Full/dif save */
911    Mmsg(&rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
912         cr.ClientId, fsr.FileSet, pool_select);
913    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
914       bsendmsg(ua, "%s\n", db_strerror(ua->db));
915    }
916
917    /* Get the JobIds from that list */
918    rx->JobIds[0] = 0;
919    rx->last_jobid[0] = 0;
920    if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
921       bsendmsg(ua, "%s\n", db_strerror(ua->db));
922    }
923
924    if (rx->JobIds[0] != 0) {
925       /* Display a list of Jobs selected for this restore */
926       db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
927    } else {
928       bsendmsg(ua, _("No jobs found.\n")); 
929    }
930
931    stat = 1;
932  
933 bail_out:
934    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
935    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
936    return stat;
937 }
938
939
940 /* Return next JobId from comma separated list */
941 static int get_next_jobid_from_list(char **p, uint32_t *JobId)
942 {
943    char jobid[30];
944    char *q = *p;
945
946    jobid[0] = 0;
947    for (int i=0; i<(int)sizeof(jobid); i++) {
948       if (*q == ',' || *q == 0) {
949          q++;
950          break;
951       }
952       jobid[i] = *q++;
953       jobid[i+1] = 0;
954    }
955    if (jobid[0] == 0 || !is_a_number(jobid)) {
956       return 0;
957    }
958    *p = q;
959    *JobId = strtoul(jobid, NULL, 10);
960    return 1;
961 }
962
963 static int count_handler(void *ctx, int num_fields, char **row)
964 {
965    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
966    rx->JobId = atoi(row[0]);
967    rx->found = true;
968    return 0;
969 }
970
971 /*
972  * Callback handler to get JobId and FileIndex for files
973  */
974 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
975 {
976    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
977    rx->JobId = atoi(row[0]);
978    add_findex(rx->bsr, rx->JobId, atoi(row[1]));
979    rx->found = true;
980    return 0;
981 }
982
983 /*
984  * Callback handler make list of JobIds
985  */
986 static int jobid_handler(void *ctx, int num_fields, char **row)
987 {
988    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
989
990    if (strcmp(rx->last_jobid, row[0]) == 0) {           
991       return 0;                       /* duplicate id */
992    }
993    bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
994    if (rx->JobIds[0] != 0) {
995       pm_strcat(&rx->JobIds, ",");
996    }
997    pm_strcat(&rx->JobIds, row[0]);
998    return 0;
999 }
1000
1001
1002 /*
1003  * Callback handler to pickup last Full backup JobTDate
1004  */
1005 static int last_full_handler(void *ctx, int num_fields, char **row)
1006 {
1007    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1008
1009    rx->JobTDate = str_to_int64(row[1]); 
1010    return 0;
1011 }
1012
1013 /*
1014  * Callback handler build FileSet name prompt list
1015  */
1016 static int fileset_handler(void *ctx, int num_fields, char **row)
1017 {
1018    /* row[0] = FileSet (name) */
1019    if (row[0]) {
1020       add_prompt((UAContext *)ctx, row[0]);
1021    }
1022    return 0;
1023 }
1024
1025 /*
1026  * Called here with each name to be added to the list. The name is
1027  *   added to the list if it is not already in the list.
1028  *
1029  * Used to make unique list of FileSets and MediaTypes
1030  */
1031 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1032 {
1033    NAME_LIST *name = (NAME_LIST *)ctx;
1034
1035    if (name->num_ids == MAX_ID_LIST_LEN) {  
1036       return 1;
1037    }
1038    if (name->num_ids == name->max_ids) {
1039       if (name->max_ids == 0) {
1040          name->max_ids = 1000;
1041          name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1042       } else {
1043          name->max_ids = (name->max_ids * 3) / 2;
1044          name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1045       }
1046    }
1047    for (int i=0; i<name->num_ids; i++) {
1048       if (strcmp(name->name[i], row[0]) == 0) {
1049          return 0;                    /* already in list, return */
1050       }
1051    }
1052    /* Add new name to list */
1053    name->name[name->num_ids++] = bstrdup(row[0]);
1054    return 0;
1055 }
1056
1057
1058 /*
1059  * Print names in the list
1060  */
1061 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1062
1063    for (int i=0; i < name_list->num_ids; i++) {
1064       bsendmsg(ua, "%s\n", name_list->name[i]);
1065    }
1066 }
1067
1068
1069 /*
1070  * Free names in the list
1071  */
1072 static void free_name_list(NAME_LIST *name_list)
1073
1074    for (int i=0; i < name_list->num_ids; i++) {
1075       free(name_list->name[i]);
1076    }
1077    if (name_list->name) {
1078       free(name_list->name);
1079       name_list->name = NULL;
1080    }
1081    name_list->max_ids = 0;
1082    name_list->num_ids = 0;
1083 }
1084
1085 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, RESTORE_CTX *rx)
1086 {
1087    STORE *store;
1088
1089    if (name_list->num_ids > 1) {
1090       bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1091          "Restore is not possible. The MediaTypes used are:\n"));
1092       print_name_list(ua, name_list);
1093       rx->store = select_storage_resource(ua);
1094       return;
1095    }
1096
1097    if (name_list->num_ids == 0) {
1098       bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1099       rx->store = select_storage_resource(ua);
1100       return;
1101    }
1102    if (rx->store) {
1103       return;
1104    }
1105    /*
1106     * We have a single MediaType, look it up in our Storage resource 
1107     */
1108    LockRes();
1109    foreach_res(store, R_STORAGE) {
1110       if (strcmp(name_list->name[0], store->media_type) == 0) {
1111          if (acl_access_ok(ua, Storage_ACL, store->hdr.name)) {
1112             rx->store = store;
1113          }
1114          break;
1115       }
1116    }
1117    UnlockRes();
1118
1119    if (rx->store) {
1120       /* Check if an explicit storage resource is given */
1121       store = NULL;
1122       int i = find_arg_with_value(ua, "storage");        
1123       if (i > 0) {
1124          store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1125          if (store && !acl_access_ok(ua, Storage_ACL, store->hdr.name)) {
1126             store = NULL;
1127          }
1128       }
1129       if (store && (store != rx->store)) {
1130          bsendmsg(ua, _("Warning default storage overridden by %s on command line.\n"),
1131             store->hdr.name);
1132          rx->store = store;
1133       }
1134       return;
1135    }
1136
1137    /* Take command line arg, or ask user if none */
1138    rx->store = get_storage_resource(ua, false /* don't use default */);
1139
1140    if (!rx->store) {
1141       bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1142          "MediaType \"%s\", needed by the Jobs you selected.\n"
1143          "You will be allowed to select a Storage device later.\n"),
1144          name_list->name[0]); 
1145    }
1146 }