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