]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
Remove -lnsl from tpc wrappers link unless needed
[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 restorecmd(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    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:                            /* file */
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          bsendmsg(ua, _("Enter file names, or < to enter a filename\n"      
426                         "containg a list of file names, and terminate\n"
427                         "them with a blank line.\n"));
428          for ( ;; ) {
429             if (!get_cmd(ua, _("Enter filename: "))) {
430                return 0;
431             }
432             len = strlen(ua->cmd);
433             if (len == 0) {
434                break;
435             }
436             insert_one_file(ua, rx);
437          }
438          /* Check MediaType and select storage that corresponds */
439          get_storage_from_mediatype(ua, &rx->name_list, rx);
440          return 2;
441       
442       case 7:                         /* Cancel or quit */
443          return 0;
444       }
445    }
446
447    if (*rx->JobIds == 0) {
448       bsendmsg(ua, _("No Jobs selected.\n"));
449       return 0;
450    }
451    bsendmsg(ua, _("You have selected the following JobId%s: %s\n"), 
452       strchr(rx->JobIds,',')?"s":"",rx->JobIds);
453
454    memset(&jr, 0, sizeof(JOB_DBR));
455
456    rx->TotalFiles = 0;
457    for (p=rx->JobIds; ; ) {
458       int stat = next_jobid_from_list(&p, &JobId);
459       if (stat < 0) {
460          bsendmsg(ua, _("Invalid JobId in list.\n"));
461          return 0;
462       }
463       if (stat == 0) {
464          break;
465       }
466       if (jr.JobId == JobId) {
467          continue;                    /* duplicate of last JobId */
468       }
469       jr.JobId = JobId;
470       if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
471          bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
472          return 0;
473       }
474       rx->TotalFiles += jr.JobFiles;
475    }
476    return 1;
477 }
478
479 static void insert_one_file(UAContext *ua, RESTORE_CTX *rx)
480 {
481    FILE *ffd;
482    char file[5000];
483    char *p = ua->cmd;
484    int line = 0;
485   
486    switch (*p) {
487    case '<':
488       p++;
489       if ((ffd = fopen(p, "r")) == NULL) {
490          bsendmsg(ua, _("Cannot open file %s: ERR=%s\n"),
491             p, strerror(errno));
492          break;
493       }
494       while (fgets(file, sizeof(file), ffd)) {
495          line++;
496          if (!insert_file_into_findex_list(ua, rx, file)) {
497             bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
498          }
499       }
500       fclose(ffd);
501       break;
502    default:
503       insert_file_into_findex_list(ua, rx, ua->cmd);
504       break;
505    }
506 }
507
508 /*
509  * For a given file (path+filename), split into path and file, then
510  *   lookup the most recent backup in the catalog to get the JobId
511  *   and FileIndex, then insert them into the findex list.
512  */
513 static int insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file)
514 {
515    strip_trailing_junk(file);
516    split_path_and_filename(rx, file);
517    Mmsg(&rx->query, uar_jobid_fileindex, rx->path, rx->fname, rx->ClientName);
518    rx->found = false;
519    /* Find and insert jobid and File Index */
520    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
521       bsendmsg(ua, _("Query failed: %s. ERR=%s\n"), 
522          rx->query, db_strerror(ua->db));
523    }
524    if (!rx->found) {
525       bsendmsg(ua, _("No database record found for: %s\n"), file);
526       return 0;
527    }
528    rx->selected_files++;
529    /*
530     * Find the FileSets for this JobId and add to the name_list
531     */
532    Mmsg(&rx->query, uar_mediatype, rx->JobId);
533    if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
534       bsendmsg(ua, "%s", db_strerror(ua->db));
535       return 0;
536    }
537    return 1;
538 }
539
540 static void split_path_and_filename(RESTORE_CTX *rx, char *name)
541 {
542    char *p, *f;
543
544    /* Find path without the filename.  
545     * I.e. everything after the last / is a "filename".
546     * OK, maybe it is a directory name, but we treat it like
547     * a filename. If we don't find a / then the whole name
548     * must be a path name (e.g. c:).
549     */
550    for (p=f=name; *p; p++) {
551       if (*p == '/') {
552          f = p;                       /* set pos of last slash */
553       }
554    }
555    if (*f == '/') {                   /* did we find a slash? */
556       f++;                            /* yes, point to filename */
557    } else {                           /* no, whole thing must be path name */
558       f = p;
559    }
560
561    /* If filename doesn't exist (i.e. root directory), we
562     * simply create a blank name consisting of a single 
563     * space. This makes handling zero length filenames
564     * easier.
565     */
566    rx->fnl = p - f;
567    if (rx->fnl > 0) {
568       rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
569       memcpy(rx->fname, f, rx->fnl);    /* copy filename */
570       rx->fname[rx->fnl] = 0;
571    } else {
572       rx->fname[0] = ' ';            /* blank filename */
573       rx->fname[1] = 0;
574       rx->fnl = 1;
575    }
576
577    rx->pnl = f - name;    
578    if (rx->pnl > 0) {
579       rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
580       memcpy(rx->path, name, rx->pnl);
581       rx->path[rx->pnl] = 0;
582    } else {
583       rx->path[0] = ' ';
584       rx->path[1] = 0;
585       rx->pnl = 1;
586    }
587
588    Dmsg2(100, "sllit path=%s file=%s\n", rx->path, rx->fname);
589 }
590
591 static void build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
592 {
593    TREE_CTX tree;
594    JobId_t JobId, last_JobId;
595    char *p;
596    char *nofname = "";
597
598    memset(&tree, 0, sizeof(TREE_CTX));
599    /* 
600     * Build the directory tree containing JobIds user selected
601     */
602    tree.root = new_tree(rx->TotalFiles);
603    tree.root->fname = nofname;
604    tree.ua = ua;
605    last_JobId = 0;
606    /*
607     * For display purposes, the same JobId, with different volumes may
608     * appear more than once, however, we only insert it once.
609     */
610    int items = 0;
611    for (p=rx->JobIds; next_jobid_from_list(&p, &JobId) > 0; ) {
612
613       if (JobId == last_JobId) {             
614          continue;                    /* eliminate duplicate JobIds */
615       }
616       last_JobId = JobId;
617       bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
618       items++;
619       /*
620        * Find files for this JobId and insert them in the tree
621        */
622       Mmsg(&rx->query, uar_sel_files, JobId);
623       if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
624          bsendmsg(ua, "%s", db_strerror(ua->db));
625       }
626       /*
627        * Find the FileSets for this JobId and add to the name_list
628        */
629       Mmsg(&rx->query, uar_mediatype, JobId);
630       if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
631          bsendmsg(ua, "%s", db_strerror(ua->db));
632       }
633    }
634    bsendmsg(ua, "%d Job%s inserted into the tree and marked for extraction.\n", 
635       items, items==1?"":"s");
636
637    /* Check MediaType and select storage that corresponds */
638    get_storage_from_mediatype(ua, &rx->name_list, rx);
639
640    if (find_arg(ua, _("all")) < 0) {
641       /* Let the user select which files to restore */
642       user_select_files_from_tree(&tree);
643    }
644
645    /*
646     * Walk down through the tree finding all files marked to be 
647     *  extracted making a bootstrap file.
648     */
649    for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
650       Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
651       if (node->extract) {
652          Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
653          add_findex(rx->bsr, node->JobId, node->FileIndex);
654          rx->selected_files++;
655       }
656    }
657
658    free_tree(tree.root);              /* free the directory tree */
659 }
660
661
662 /*
663  * This routine is used to get the current backup or a backup
664  *   before the specified date.
665  */
666 static int select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
667 {
668    int stat = 0;
669    FILESET_DBR fsr;
670    CLIENT_DBR cr;
671    char fileset_name[MAX_NAME_LENGTH];
672    char ed1[50];
673
674
675    /* Create temp tables */
676    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
677    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
678    if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
679       bsendmsg(ua, "%s\n", db_strerror(ua->db));
680    }
681    if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
682       bsendmsg(ua, "%s\n", db_strerror(ua->db));
683    }
684    /*
685     * Select Client from the Catalog
686     */
687    memset(&cr, 0, sizeof(cr));
688    if (!get_client_dbr(ua, &cr)) {
689       goto bail_out;
690    }
691    bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
692
693    /*
694     * Select FileSet 
695     */
696    Mmsg(&rx->query, uar_sel_fileset, cr.ClientId, cr.ClientId);
697    start_prompt(ua, _("The defined FileSet resources are:\n"));
698    if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
699       bsendmsg(ua, "%s\n", db_strerror(ua->db));
700    }
701    if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"), 
702                  fileset_name, sizeof(fileset_name)) < 0) {
703       goto bail_out;
704    }
705    fsr.FileSetId = atoi(fileset_name);  /* Id is first part of name */
706    if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
707       bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
708       bsendmsg(ua, _("This probably means you modified the FileSet.\n"
709                      "Continuing anyway.\n"));
710    }
711
712
713    /* Find JobId of last Full backup for this client, fileset */
714    Mmsg(&rx->query, uar_last_full, cr.ClientId, cr.ClientId, date, fsr.FileSetId);
715    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
716       bsendmsg(ua, "%s\n", db_strerror(ua->db));
717       goto bail_out;
718    }
719
720    /* Find all Volumes used by that JobId */
721    if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
722       bsendmsg(ua, "%s\n", db_strerror(ua->db));
723       goto bail_out;
724    }
725    /* Note, this is needed as I don't seem to get the callback
726     * from the call just above.
727     */
728    rx->JobTDate = 0;
729    if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
730       bsendmsg(ua, "%s\n", db_strerror(ua->db));
731    }
732    if (rx->JobTDate == 0) {
733       bsendmsg(ua, _("No Full backup before %s found.\n"), date);
734       goto bail_out;
735    }
736
737    /* Now find all Incremental/Decremental Jobs after Full save */
738    Mmsg(&rx->query, uar_inc_dec, edit_uint64(rx->JobTDate, ed1), date,
739         cr.ClientId, fsr.FileSetId);
740    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
741       bsendmsg(ua, "%s\n", db_strerror(ua->db));
742    }
743
744    /* Get the JobIds from that list */
745    rx->JobIds[0] = 0;
746    rx->last_jobid[0] = 0;
747    if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
748       bsendmsg(ua, "%s\n", db_strerror(ua->db));
749    }
750
751    if (rx->JobIds[0] != 0) {
752       /* Display a list of Jobs selected for this restore */
753       db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
754    } else {
755       bsendmsg(ua, _("No jobs found.\n")); 
756    }
757
758    stat = 1;
759  
760 bail_out:
761    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
762    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
763    return stat;
764 }
765
766 /* Return next JobId from comma separated list */
767 static int next_jobid_from_list(char **p, uint32_t *JobId)
768 {
769    char jobid[30];
770    char *q = *p;
771
772    jobid[0] = 0;
773    for (int i=0; i<(int)sizeof(jobid); i++) {
774       if (*q == ',' || *q == 0) {
775          q++;
776          break;
777       }
778       jobid[i] = *q++;
779       jobid[i+1] = 0;
780    }
781    if (jobid[0] == 0 || !is_a_number(jobid)) {
782       return 0;
783    }
784    *p = q;
785    *JobId = strtoul(jobid, NULL, 10);
786    return 1;
787 }
788
789 /*
790  * Callback handler to get JobId and FileIndex for files
791  */
792 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
793 {
794    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
795    rx->JobId = atoi(row[0]);
796    add_findex(rx->bsr, rx->JobId, atoi(row[1]));
797    rx->found = true;
798    return 0;
799 }
800
801 /*
802  * Callback handler make list of JobIds
803  */
804 static int jobid_handler(void *ctx, int num_fields, char **row)
805 {
806    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
807
808    if (strcmp(rx->last_jobid, row[0]) == 0) {           
809       return 0;                       /* duplicate id */
810    }
811    bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
812    if (rx->JobIds[0] != 0) {
813       pm_strcat(&rx->JobIds, ",");
814    }
815    pm_strcat(&rx->JobIds, row[0]);
816    return 0;
817 }
818
819
820 /*
821  * Callback handler to pickup last Full backup JobTDate
822  */
823 static int last_full_handler(void *ctx, int num_fields, char **row)
824 {
825    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
826
827    rx->JobTDate = strtoll(row[1], NULL, 10);
828
829    return 0;
830 }
831
832 /*
833  * Callback handler build fileset prompt list
834  */
835 static int fileset_handler(void *ctx, int num_fields, char **row)
836 {
837    char prompt[MAX_NAME_LENGTH+200];
838
839    snprintf(prompt, sizeof(prompt), "%s  %s  %s", row[0], row[1], row[2]);
840    add_prompt((UAContext *)ctx, prompt);
841    return 0;
842 }
843
844 /*
845  * Called here with each name to be added to the list. The name is
846  *   added to the list if it is not already in the list.
847  *
848  * Used to make unique list of FileSets and MediaTypes
849  */
850 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
851 {
852    NAME_LIST *name = (NAME_LIST *)ctx;
853
854    if (name->num_ids == MAX_ID_LIST_LEN) {  
855       return 1;
856    }
857    if (name->num_ids == name->max_ids) {
858       if (name->max_ids == 0) {
859          name->max_ids = 1000;
860          name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
861       } else {
862          name->max_ids = (name->max_ids * 3) / 2;
863          name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
864       }
865    }
866    for (int i=0; i<name->num_ids; i++) {
867       if (strcmp(name->name[i], row[0]) == 0) {
868          return 0;                    /* already in list, return */
869       }
870    }
871    /* Add new name to list */
872    name->name[name->num_ids++] = bstrdup(row[0]);
873    return 0;
874 }
875
876
877 /*
878  * Print names in the list
879  */
880 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
881
882    for (int i=0; i < name_list->num_ids; i++) {
883       bsendmsg(ua, "%s\n", name_list->name[i]);
884    }
885 }
886
887
888 /*
889  * Free names in the list
890  */
891 static void free_name_list(NAME_LIST *name_list)
892
893    for (int i=0; i < name_list->num_ids; i++) {
894       free(name_list->name[i]);
895    }
896    if (name_list->name) {
897       free(name_list->name);
898    }
899    name_list->max_ids = 0;
900    name_list->num_ids = 0;
901 }
902
903 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, RESTORE_CTX *rx)
904 {
905    char name[MAX_NAME_LENGTH];
906    STORE *store = NULL;
907
908    if (name_list->num_ids > 1) {
909       bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
910          "Restore is not possible. The MediaTypes used are:\n"));
911       print_name_list(ua, name_list);
912       rx->store = select_storage_resource(ua);
913       return;
914    }
915
916    if (name_list->num_ids == 0) {
917       bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
918       rx->store = select_storage_resource(ua);
919       return;
920    }
921
922    start_prompt(ua, _("The defined Storage resources are:\n"));
923    LockRes();
924    while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
925       if (strcmp(store->media_type, name_list->name[0]) == 0) {
926          add_prompt(ua, store->hdr.name);
927       }
928    }
929    UnlockRes();
930    do_prompt(ua, _("Storage"),  _("Select Storage resource"), name, sizeof(name));
931    rx->store = (STORE *)GetResWithName(R_STORAGE, name);
932    if (!rx->store) {
933       bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
934          "MediaType %s, needed by the Jobs you selected.\n"
935          "You will be allowed to select a Storage device later.\n"),
936          name_list->name[0]); 
937    }
938 }