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