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