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