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