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