]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
807efa13db37b380a0c8b891e4678623fafb1b0a
[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
5  *
6  *     Kern Sibbald, July MMII
7  *
8  *   Version $Id$
9  */
10
11 /*
12    Copyright (C) 2002 Kern Sibbald and John Walker
13
14    This program is free software; you can redistribute it and/or
15    modify it under the terms of the GNU General Public License as
16    published by the Free Software Foundation; either version 2 of
17    the License, or (at your option) any later version.
18
19    This program is distributed in the hope that it will be useful,
20    but WITHOUT ANY WARRANTY; without even the implied warranty of
21    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22    General Public License for more details.
23
24    You should have received a copy of the GNU General Public
25    License along with this program; if not, write to the Free
26    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
27    MA 02111-1307, USA.
28
29  */
30
31 #include "bacula.h"
32 #include "dird.h"
33 #include "ua.h"
34 #include <fnmatch.h>
35
36
37
38 /* Imported functions */
39 extern int runcmd(UAContext *ua, char *cmd);
40
41 /* Imported variables */
42 extern char *uar_list_jobs,     *uar_file,        *uar_sel_files;
43 extern char *uar_del_temp,      *uar_del_temp1,   *uar_create_temp;
44 extern char *uar_create_temp1,  *uar_last_full,   *uar_full;
45 extern char *uar_inc,           *uar_list_temp,   *uar_sel_jobid_temp;
46 extern char *uar_sel_all_temp1, *uar_sel_fileset, *uar_mediatype;
47
48
49 /* Context for insert_tree_handler() */
50 typedef struct s_tree_ctx {
51    TREE_ROOT *root;                   /* root */
52    TREE_NODE *node;                   /* current node */
53    TREE_NODE *avail_node;             /* unused node last insert */
54    int cnt;                           /* count for user feedback */
55    UAContext *ua;
56 } TREE_CTX;
57
58 /* Main structure for obtaining JobIds */
59 typedef struct s_jobids {
60    btime_t JobTDate;
61    uint32_t ClientId;
62    uint32_t TotalFiles;
63    char JobIds[200];
64    CLIENT *client;
65    STORE  *store;
66 } JobIds;
67
68
69 /* FileIndex entry in bootstrap record */
70 typedef struct s_rbsr_findex {
71    struct s_rbsr_findex *next;
72    int32_t findex;
73    int32_t findex2;
74 } RBSR_FINDEX;
75
76 /* Restore bootstrap record -- not the real one, but useful here */
77 typedef struct s_rbsr {
78    struct s_rbsr *next;               /* next JobId */
79    uint32_t JobId;                    /* JobId this bsr */
80    uint32_t VolSessionId;                   
81    uint32_t VolSessionTime;
82    char *VolumeName;                  /* Volume name */
83    RBSR_FINDEX *fi;                   /* File indexes this JobId */
84 } RBSR;
85
86 typedef struct s_name_ctx {
87    char **name;                       /* list of names */
88    int num_ids;                       /* ids stored */
89    int max_ids;                       /* size of array */
90    int num_del;                       /* number deleted */
91    int tot_ids;                       /* total to process */
92 } NAME_LIST;
93
94 #define MAX_ID_LIST_LEN 1000000
95
96
97 /* Forward referenced functions */
98 static RBSR *new_bsr();
99 static void free_bsr(RBSR *bsr);
100 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd);
101 static int  write_bsr_file(UAContext *ua, RBSR *bsr);
102 static void print_bsr(UAContext *ua, RBSR *bsr);
103 static int  complete_bsr(UAContext *ua, RBSR *bsr);
104 static int insert_tree_handler(void *ctx, int num_fields, char **row);
105 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex);
106 static int last_full_handler(void *ctx, int num_fields, char **row);
107 static int jobid_handler(void *ctx, int num_fields, char **row);
108 static int next_jobid_from_list(char **p, uint32_t *JobId);
109 static int user_select_jobids(UAContext *ua, JobIds *ji);
110 static void user_select_files(TREE_CTX *tree);
111 static int fileset_handler(void *ctx, int num_fields, char **row);
112 static void print_name_list(UAContext *ua, NAME_LIST *name_list);
113 static int unique_name_list_handler(void *ctx, int num_fields, char **row);
114 static void free_name_list(NAME_LIST *name_list);
115 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JobIds *ji);
116
117
118 /*
119  *   Restore files
120  *
121  */
122 int restorecmd(UAContext *ua, char *cmd)
123 {
124    POOLMEM *query;
125    TREE_CTX tree;
126    JobId_t JobId, last_JobId;
127    char *p;
128    RBSR *bsr;
129    char *nofname = "";
130    JobIds ji;
131    JOB *job = NULL;
132    JOB *restore_job = NULL;
133    int restore_jobs = 0;
134    NAME_LIST name_list;
135    uint32_t selected_files = 0;
136
137    if (!open_db(ua)) {
138       return 0;
139    }
140
141    memset(&tree, 0, sizeof(TREE_CTX));
142    memset(&name_list, 0, sizeof(name_list));
143    memset(&ji, 0, sizeof(ji));
144
145    /* Ensure there is at least one Restore Job */
146    LockRes();
147    while ( (job = (JOB *)GetNextRes(R_JOB, (RES *)job)) ) {
148       if (job->JobType == JT_RESTORE) {
149          if (!restore_job) {
150             restore_job = job;
151          }
152          restore_jobs++;
153       }
154    }
155    UnlockRes();
156    if (!restore_jobs) {
157       bsendmsg(ua, _(
158          "No Restore Job Resource found. You must create at least\n"
159          "one before running this command.\n"));
160       return 0;
161    }
162
163
164    if (!user_select_jobids(ua, &ji)) {
165       return 0;
166    }
167
168    /* 
169     * Build the directory tree  
170     */
171    tree.root = new_tree(ji.TotalFiles);
172    tree.root->fname = nofname;
173    tree.ua = ua;
174    query = get_pool_memory(PM_MESSAGE);
175    last_JobId = 0;
176    /*
177     * For display purposes, the same JobId, with different volumes may
178     * appear more than once, however, we only insert it once.
179     */
180    for (p=ji.JobIds; next_jobid_from_list(&p, &JobId) > 0; ) {
181
182       if (JobId == last_JobId) {             
183          continue;                    /* eliminate duplicate JobIds */
184       }
185       last_JobId = JobId;
186       bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
187       /*
188        * Find files for this JobId and insert them in the tree
189        */
190       Mmsg(&query, uar_sel_files, JobId);
191       if (!db_sql_query(ua->db, query, insert_tree_handler, (void *)&tree)) {
192          bsendmsg(ua, "%s", db_strerror(ua->db));
193       }
194       /*
195        * Find the FileSets for this JobId and add to the name_list
196        */
197       Mmsg(&query, uar_mediatype, JobId);
198       if (!db_sql_query(ua->db, query, unique_name_list_handler, (void *)&name_list)) {
199          bsendmsg(ua, "%s", db_strerror(ua->db));
200       }
201
202    }
203    bsendmsg(ua, "\n");
204    free_pool_memory(query);
205
206    /* Check MediaType and select storage that corresponds */
207    get_storage_from_mediatype(ua, &name_list, &ji);
208    free_name_list(&name_list);
209
210    /* Let the user select which files to restore */
211    user_select_files(&tree);
212
213    /*
214     * Walk down through the tree finding all files marked to be 
215     *  extracted making a bootstrap file.
216     */
217    bsr = new_bsr();
218    for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
219       Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
220       if (node->extract) {
221          Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
222          add_findex(bsr, node->JobId, node->FileIndex);
223          selected_files++;
224       }
225    }
226
227    free_tree(tree.root);              /* free the directory tree */
228
229    if (bsr->JobId) {
230       if (!complete_bsr(ua, bsr)) {   /* find Vol, SessId, SessTime from JobIds */
231          bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
232          free_bsr(bsr);
233          return 0;
234       }
235 //    print_bsr(ua, bsr);
236       write_bsr_file(ua, bsr);
237       bsendmsg(ua, _("\n%u files selected to restore.\n\n"), selected_files);
238    } else {
239       bsendmsg(ua, _("No files selected to restore.\n"));
240    }
241    free_bsr(bsr);
242
243    if (restore_jobs == 1) {
244       job = restore_job;
245    } else {
246       job = select_restore_job_resource(ua);
247    }
248    if (!job) {
249       bsendmsg(ua, _("No Restore Job resource found!\n"));
250       return 0;
251    }
252
253    if (ji.client) {
254       Mmsg(&ua->cmd, 
255          "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
256          job->hdr.name, ji.client->hdr.name, ji.store?ji.store->hdr.name:"",
257          working_directory);
258    } else {
259       Mmsg(&ua->cmd, 
260          "run job=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
261          job->hdr.name, ji.store?ji.store->hdr.name:"", working_directory);
262    }
263    
264
265    Dmsg1(400, "Submitting: %s\n", ua->cmd);
266    
267    parse_command_args(ua);
268    runcmd(ua, ua->cmd);
269
270    bsendmsg(ua, _("Restore command done.\n"));
271    return 1;
272 }
273
274 /*
275  * The first step in the restore process is for the user to 
276  *  select a list of JobIds from which he will subsequently
277  *  select which files are to be restored.
278  */
279 static int user_select_jobids(UAContext *ua, JobIds *ji)
280 {
281    char fileset_name[MAX_NAME_LENGTH];
282    char *p;
283    FILESET_DBR fsr;
284    JobId_t JobId;
285    JOB_DBR jr;
286    POOLMEM *query;
287    int done = 0;
288    char *list[] = { 
289       "List last 20 Jobs run",
290       "List Jobs where a given File is saved",
291       "Enter list of JobIds to select",
292       "Enter SQL list command", 
293       "Select the most recent backup for a client",
294       "Cancel",
295       NULL };
296
297    bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
298                   "to be restored. You will be presented several methods\n"
299                   "of specifying the JobIds. Then you will be allowed to\n"
300                   "select which files from those JobIds are to be restored.\n\n"));
301
302    for ( ; !done; ) {
303       start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
304       for (int i=0; list[i]; i++) {
305          add_prompt(ua, list[i]);
306       }
307       done = 1;
308       switch (do_prompt(ua, "Select item: ", NULL)) {
309       case -1:                        /* error */
310          return 0;
311       case 0:                         /* list last 20 Jobs run */
312          db_list_sql_query(ua->db, uar_list_jobs, prtit, ua, 1);
313          done = 0;
314          break;
315       case 1:                         /* list where a file is saved */
316          if (!get_cmd(ua, _("Enter Filename: "))) {
317             return 0;
318          }
319          query = get_pool_memory(PM_MESSAGE);
320          Mmsg(&query, uar_file, ua->cmd);
321          db_list_sql_query(ua->db, query, prtit, ua, 1);
322          free_pool_memory(query);
323          done = 0;
324          break;
325       case 2:                         /* enter a list of JobIds */
326          if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
327             return 0;
328          }
329          bstrncpy(ji->JobIds, ua->cmd, sizeof(ji->JobIds));
330          break;
331       case 3:                         /* Enter an SQL list command */
332          if (!get_cmd(ua, _("Enter SQL list command: "))) {
333             return 0;
334          }
335          db_list_sql_query(ua->db, ua->cmd, prtit, ua, 1);
336          done = 0;
337          break;
338       case 4:                         /* Select the most recent backups */
339          query = get_pool_memory(PM_MESSAGE);
340          db_sql_query(ua->db, uar_del_temp, NULL, NULL);
341          db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
342          if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
343             bsendmsg(ua, "%s\n", db_strerror(ua->db));
344          }
345          if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
346             bsendmsg(ua, "%s\n", db_strerror(ua->db));
347          }
348          /*
349           * Select Client 
350           */
351          if (!(ji->client = get_client_resource(ua))) {
352             return 0;
353          }
354
355          /*
356           * Select FileSet 
357           */
358          Mmsg(&query, uar_sel_fileset, ji->client->hdr.name);
359          start_prompt(ua, _("The defined FileSet resources are:\n"));
360          if (!db_sql_query(ua->db, query, fileset_handler, (void *)ua)) {
361             bsendmsg(ua, "%s\n", db_strerror(ua->db));
362          }
363          if (do_prompt(ua, _("Select FileSet resource"), fileset_name) < 0) {
364             free_pool_memory(query);
365             return 0;
366          }
367          fsr.FileSetId = 0;
368          strcpy(fsr.FileSet, fileset_name);
369          if (!db_get_fileset_record(ua->db, &fsr)) {
370             bsendmsg(ua, "Error getting FileSet record: %s\n", db_strerror(ua->db));
371             bsendmsg(ua, _("This probably means you modified the FileSet.\n"
372                            "Continuing anyway.\n"));
373          }
374
375          Mmsg(&query, uar_last_full, ji->client->hdr.name, fsr.FileSetId);
376          /* Find JobId of full Backup of system */
377          if (!db_sql_query(ua->db, query, NULL, NULL)) {
378             bsendmsg(ua, "%s\n", db_strerror(ua->db));
379          }
380          /* Find all Volumes used by that JobId */
381          if (!db_sql_query(ua->db, uar_full, NULL,NULL)) {
382             bsendmsg(ua, "%s\n", db_strerror(ua->db));
383          }
384          /* Note, this is needed as I don't seem to get the callback
385           * from the call just above.
386           */
387          if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)ji)) {
388             bsendmsg(ua, "%s\n", db_strerror(ua->db));
389          }
390          /* Now find all Incremental Jobs */
391          Mmsg(&query, uar_inc, (uint32_t)ji->JobTDate, ji->ClientId, fsr.FileSetId);
392          if (!db_sql_query(ua->db, query, NULL, NULL)) {
393             bsendmsg(ua, "%s\n", db_strerror(ua->db));
394          }
395          free_pool_memory(query);
396          db_list_sql_query(ua->db, uar_list_temp, prtit, ua, 1);
397
398          if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)ji)) {
399             bsendmsg(ua, "%s\n", db_strerror(ua->db));
400          }
401          db_sql_query(ua->db, uar_del_temp, NULL, NULL);
402          db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
403          break;
404       case 5:
405          return 0;
406       }
407    }
408
409    if (*ji->JobIds == 0) {
410       bsendmsg(ua, _("No Jobs selected.\n"));
411       return 0;
412    }
413    bsendmsg(ua, _("You have selected the following JobId: %s\n"), ji->JobIds);
414
415    memset(&jr, 0, sizeof(JOB_DBR));
416
417    ji->TotalFiles = 0;
418    for (p=ji->JobIds; ; ) {
419       int stat = next_jobid_from_list(&p, &JobId);
420       if (stat < 0) {
421          bsendmsg(ua, _("Invalid JobId in list.\n"));
422          return 0;
423       }
424       if (stat == 0) {
425          break;
426       }
427       jr.JobId = JobId;
428       if (!db_get_job_record(ua->db, &jr)) {
429          bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
430          return 0;
431       }
432       ji->TotalFiles += jr.JobFiles;
433    }
434    return 1;
435 }
436
437 static int next_jobid_from_list(char **p, uint32_t *JobId)
438 {
439    char jobid[30];
440    int i;
441    char *q = *p;
442
443    jobid[0] = 0;
444    for (i=0; i<(int)sizeof(jobid); i++) {
445       if (*q == ',' || *q == 0) {
446          q++;
447          break;
448       }
449       jobid[i] = *q++;
450       jobid[i+1] = 0;
451    }
452    if (jobid[0] == 0 || !is_a_number(jobid)) {
453       return 0;
454    }
455    *p = q;
456    *JobId = strtoul(jobid, NULL, 10);
457    return 1;
458 }
459
460 /*
461  * Callback handler make list of JobIds
462  */
463 static int jobid_handler(void *ctx, int num_fields, char **row)
464 {
465    JobIds *ji = (JobIds *)ctx;
466
467    if (strlen(ji->JobIds)+strlen(row[0])+2 < sizeof(ji->JobIds)) {
468       if (ji->JobIds[0] != 0) {
469          strcat(ji->JobIds, ",");
470       }
471       strcat(ji->JobIds, row[0]);
472    }
473
474    return 0;
475 }
476
477
478 /*
479  * Callback handler to pickup last Full backup JobId and ClientId
480  */
481 static int last_full_handler(void *ctx, int num_fields, char **row)
482 {
483    JobIds *ji = (JobIds *)ctx;
484
485    ji->JobTDate = atoi(row[1]);
486    ji->ClientId = atoi(row[2]);
487
488    return 0;
489 }
490
491 /*
492  * Callback handler build fileset prompt list
493  */
494 static int fileset_handler(void *ctx, int num_fields, char **row)
495 {
496    add_prompt((UAContext *)ctx, row[1]);
497    return 0;
498 }
499
500 /* Forward referenced commands */
501
502 static int markcmd(UAContext *ua, TREE_CTX *tree);
503 static int countcmd(UAContext *ua, TREE_CTX *tree);
504 static int findcmd(UAContext *ua, TREE_CTX *tree);
505 static int lscmd(UAContext *ua, TREE_CTX *tree);
506 static int dircmd(UAContext *ua, TREE_CTX *tree);
507 static int helpcmd(UAContext *ua, TREE_CTX *tree);
508 static int cdcmd(UAContext *ua, TREE_CTX *tree);
509 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
510 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
511 static int quitcmd(UAContext *ua, TREE_CTX *tree);
512
513
514 struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; }; 
515 static struct cmdstruct commands[] = {
516  { N_("mark"),       markcmd,      _("mark file for restoration")},
517  { N_("unmark"),     unmarkcmd,    _("unmark file for restoration")},
518  { N_("cd"),         cdcmd,        _("change current directory")},
519  { N_("pwd"),        pwdcmd,       _("print current working directory")},
520  { N_("ls"),         lscmd,        _("list current directory")},    
521  { N_("dir"),        dircmd,       _("list current directory")},    
522  { N_("count"),      countcmd,     _("count marked files")},
523  { N_("find"),       findcmd,      _("find files")},
524  { N_("done"),       quitcmd,      _("leave file selection mode")},
525  { N_("exit"),       quitcmd,      _("exit = done")},
526  { N_("help"),       helpcmd,      _("print help")},
527  { N_("?"),          helpcmd,      _("print help")},    
528              };
529 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
530
531
532 /*
533  * Enter a prompt mode where the user can select/deselect
534  *  files to be restored. This is sort of like a mini-shell
535  *  that allows "cd", "pwd", "add", "rm", ...
536  */
537 static void user_select_files(TREE_CTX *tree)
538 {
539    char cwd[2000];
540
541    bsendmsg(tree->ua, _( 
542       "\nYou are now entering file selection mode where you add and\n"
543       "remove files to be restored. All files are initially added.\n"
544       "Enter \"done\" to leave this mode.\n\n"));
545    /*
546     * Enter interactive command handler allowing selection
547     *  of individual files.
548     */
549    tree->node = (TREE_NODE *)tree->root;
550    tree_getpath(tree->node, cwd, sizeof(cwd));
551    bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
552    for ( ;; ) {       
553       int found, len, stat, i;
554       if (!get_cmd(tree->ua, "$ ")) {
555          break;
556       }
557       parse_command_args(tree->ua);
558       if (tree->ua->argc == 0) {
559          return;
560       }
561
562       len = strlen(tree->ua->argk[0]);
563       found = 0;
564       stat = 0;
565       for (i=0; i<(int)comsize; i++)       /* search for command */
566          if (strncasecmp(tree->ua->argk[0],  _(commands[i].key), len) == 0) {
567             stat = (*commands[i].func)(tree->ua, tree);   /* go execute command */
568             found = 1;
569             break;
570          }
571       if (!found) {
572          bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
573          continue;
574       }
575       if (!stat) {
576          break;
577       }
578    }
579 }
580
581 /*
582  * Create new FileIndex entry for BSR 
583  */
584 static RBSR_FINDEX *new_findex() 
585 {
586    RBSR_FINDEX *fi = (RBSR_FINDEX *)bmalloc(sizeof(RBSR_FINDEX));
587    memset(fi, 0, sizeof(RBSR_FINDEX));
588    return fi;
589 }
590
591 /* Free all BSR FileIndex entries */
592 static void free_findex(RBSR_FINDEX *fi)
593 {
594    if (fi) {
595       free_findex(fi->next);
596       free(fi);
597    }
598 }
599
600 static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd) 
601 {
602    if (fi) {
603       if (fi->findex == fi->findex2) {
604          fprintf(fd, "FileIndex=%d\n", fi->findex);
605       } else {
606          fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
607       }
608       write_findex(ua, fi->next, fd);
609    }
610 }
611
612
613 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
614 {
615    if (fi) {
616       if (fi->findex == fi->findex2) {
617          bsendmsg(ua, "FileIndex=%d\n", fi->findex);
618       } else {
619          bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
620       }
621       print_findex(ua, fi->next);
622    }
623 }
624
625 /* Create a new bootstrap record */
626 static RBSR *new_bsr()
627 {
628    RBSR *bsr = (RBSR *)bmalloc(sizeof(RBSR));
629    memset(bsr, 0, sizeof(RBSR));
630    return bsr;
631 }
632
633 /* Free the entire BSR */
634 static void free_bsr(RBSR *bsr)
635 {
636    if (bsr) {
637       free_findex(bsr->fi);
638       free_bsr(bsr->next);
639       if (bsr->VolumeName) {
640          free(bsr->VolumeName);
641       }
642       free(bsr);
643    }
644 }
645
646 /*
647  * Complete the BSR by filling in the VolumeName and
648  *  VolSessionId and VolSessionTime using the JobId
649  */
650 static int complete_bsr(UAContext *ua, RBSR *bsr)
651 {
652    JOB_DBR jr;
653    POOLMEM *VolumeNames;
654
655    if (bsr) {
656       VolumeNames = get_pool_memory(PM_MESSAGE);
657       memset(&jr, 0, sizeof(jr));
658       jr.JobId = bsr->JobId;
659       if (!db_get_job_record(ua->db, &jr)) {
660          bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
661          return 0;
662       }
663       bsr->VolSessionId = jr.VolSessionId;
664       bsr->VolSessionTime = jr.VolSessionTime;
665       if (!db_get_job_volume_names(ua->db, bsr->JobId, &VolumeNames)) {
666          bsendmsg(ua, _("Unable to get Job Volumes. ERR=%s\n"), db_strerror(ua->db));
667          free_pool_memory(VolumeNames);
668          return 0;
669       }
670       bsr->VolumeName = bstrdup(VolumeNames);
671       free_pool_memory(VolumeNames);
672       return complete_bsr(ua, bsr->next);
673    }
674    return 1;
675 }
676
677 /*
678  * Write the bootstrap record to file
679  */
680 static int write_bsr_file(UAContext *ua, RBSR *bsr)
681 {
682    FILE *fd;
683    POOLMEM *fname = get_pool_memory(PM_MESSAGE);
684    int stat;
685
686    Mmsg(&fname, "%s/restore.bsr", working_directory);
687    fd = fopen(fname, "w+");
688    if (!fd) {
689       bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"), 
690          fname, strerror(errno));
691       free_pool_memory(fname);
692       return 0;
693    }
694    write_bsr(ua, bsr, fd);
695    stat = !ferror(fd);
696    fclose(fd);
697 // bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
698    free_pool_memory(fname);
699    return stat;
700 }
701
702 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
703 {
704    if (bsr) {
705       if (bsr->VolumeName) {
706          fprintf(fd, "Volume=\"%s\"\n", bsr->VolumeName);
707       }
708       fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
709       fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
710       write_findex(ua, bsr->fi, fd);
711       write_bsr(ua, bsr->next, fd);
712    }
713 }
714
715 static void print_bsr(UAContext *ua, RBSR *bsr)
716 {
717    if (bsr) {
718       if (bsr->VolumeName) {
719          bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolumeName);
720       }
721       bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
722       bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
723       print_findex(ua, bsr->fi);
724       print_bsr(ua, bsr->next);
725    }
726 }
727
728
729 /*
730  * Add a FileIndex to the list of BootStrap records.
731  *  Here we are only dealing with JobId's and the FileIndexes
732  *  associated with those JobIds.
733  */
734 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
735 {
736    RBSR *nbsr;
737    RBSR_FINDEX *fi, *lfi;
738
739    if (findex == 0) {
740       return;                         /* probably a dummy directory */
741    }
742    
743    if (!bsr->fi) {                    /* if no FI add one */
744       /* This is the first FileIndex item in the chain */
745       bsr->fi = new_findex();
746       bsr->JobId = JobId;
747       bsr->fi->findex = findex;
748       bsr->fi->findex2 = findex;
749       return;
750    }
751    /* Walk down list of bsrs until we find the JobId */
752    if (bsr->JobId != JobId) {
753       for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
754          if (nbsr->JobId == JobId) {
755             bsr = nbsr;
756             break;
757          }
758       }
759
760       if (!nbsr) {                    /* Must add new JobId */
761          /* Add new JobId at end of chain */
762          for (nbsr=bsr; nbsr->next; nbsr=nbsr->next) 
763             {  }
764          nbsr->next = new_bsr();
765          nbsr->next->JobId = JobId;
766          nbsr->next->fi = new_findex();
767          nbsr->next->fi->findex = findex;
768          nbsr->next->fi->findex2 = findex;
769          return;
770       }
771    }
772
773    /* 
774     * At this point, bsr points to bsr containing JobId,
775     *  and we are sure that there is at least one fi record.
776     */
777    lfi = fi = bsr->fi;
778    /* Check if this findex is smaller than first item */
779    if (findex < fi->findex) {
780       if ((findex+1) == fi->findex) {
781          fi->findex = findex;         /* extend down */
782          return;
783       }
784       fi = new_findex();              /* yes, insert before first item */
785       fi->findex = findex;
786       fi->findex2 = findex;
787       fi->next = lfi;
788       bsr->fi = fi;
789       return;
790    }
791    /* Walk down fi chain and find where to insert insert new FileIndex */
792    for ( ; fi; fi=fi->next) {
793       if (findex == (fi->findex2 + 1)) {  /* extend up */
794          RBSR_FINDEX *nfi;     
795          fi->findex2 = findex;
796          if (fi->next && ((findex+1) == fi->next->findex)) { 
797             nfi = fi->next;
798             fi->findex2 = nfi->findex2;
799             fi->next = nfi->next;
800             free(nfi);
801          }
802          return;
803       }
804       if (findex < fi->findex) {      /* add before */
805          if ((findex+1) == fi->findex) {
806             fi->findex = findex;
807             return;
808          }
809          break;
810       }
811       lfi = fi;
812    }
813    /* Add to last place found */
814    fi = new_findex();
815    fi->findex = findex;
816    fi->findex2 = findex;
817    fi->next = lfi->next;
818    lfi->next = fi;
819    return;
820 }
821
822 /*
823  * This callback routine is responsible for inserting the
824  *  items it gets into the directory tree. For each JobId selected
825  *  this routine is called once for each file. We do not allow
826  *  duplicate filenames, but instead keep the info from the most
827  *  recent file entered (i.e. the JobIds are assumed to be sorted)
828  */
829 static int insert_tree_handler(void *ctx, int num_fields, char **row)
830 {
831    TREE_CTX *tree = (TREE_CTX *)ctx;
832    char fname[2000];
833    TREE_NODE *node, *new_node;
834    int type;
835
836    strip_trailing_junk(row[1]);
837    if (*row[1] == 0) {
838       if (*row[0] != '/') {           /* Must be Win32 directory */
839          type = TN_DIR_NLS;
840       } else {
841          type = TN_DIR;
842       }
843    } else {
844       type = TN_FILE;
845    }
846    sprintf(fname, "%s%s", row[0], row[1]);
847    if (tree->avail_node) {
848       node = tree->avail_node;
849    } else {
850       node = new_tree_node(tree->root, type);
851       tree->avail_node = node;
852    }
853    Dmsg3(200, "FI=%d type=%d fname=%s\n", node->FileIndex, type, fname);
854    new_node = insert_tree_node(fname, node, tree->root, NULL);
855    /* Note, if node already exists, save new one for next time */
856    if (new_node != node) {
857       tree->avail_node = node;
858    } else {
859       tree->avail_node = NULL;
860    }
861    new_node->FileIndex = atoi(row[2]);
862    new_node->JobId = atoi(row[3]);
863    new_node->type = type;
864    new_node->extract = 1;             /* extract all by default */
865    tree->cnt++;
866    return 0;
867 }
868
869
870 /*
871  * Set extract to value passed. We recursively walk
872  *  down the tree setting all children if the 
873  *  node is a directory.
874  */
875 static void set_extract(TREE_NODE *node, int value)
876 {
877    TREE_NODE *n;
878
879    node->extract = value;
880    if (node->type != TN_FILE) {
881       for (n=node->child; n; n=n->sibling) {
882          set_extract(n, value);
883       }
884    }
885 }
886
887 static int markcmd(UAContext *ua, TREE_CTX *tree)
888 {
889    TREE_NODE *node;
890
891    if (ua->argc < 2)
892       return 1;
893    if (!tree->node->child) {     
894       return 1;
895    }
896    for (node = tree->node->child; node; node=node->sibling) {
897       if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
898          set_extract(node, 1);
899       }
900    }
901    return 1;
902 }
903
904 static int countcmd(UAContext *ua, TREE_CTX *tree)
905 {
906    int total, extract;
907
908    total = extract = 0;
909    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
910       if (node->type != TN_NEWDIR) {
911          total++;
912          if (node->extract) {
913             extract++;
914          }
915       }
916    }
917    bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
918    return 1;
919 }
920
921 static int findcmd(UAContext *ua, TREE_CTX *tree)
922 {
923    char cwd[2000];
924
925    if (ua->argc == 1) {
926       bsendmsg(ua, _("No file specification given.\n"));
927       return 0;
928    }
929    
930    for (int i=1; i < ua->argc; i++) {
931       for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
932          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
933             tree_getpath(node, cwd, sizeof(cwd));
934             bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
935          }
936       }
937    }
938    return 1;
939 }
940
941
942
943 static int lscmd(UAContext *ua, TREE_CTX *tree)
944 {
945    TREE_NODE *node;
946
947    if (!tree->node->child) {     
948       return 1;
949    }
950    for (node = tree->node->child; node; node=node->sibling) {
951       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
952          bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
953             (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
954       }
955    }
956    return 1;
957 }
958
959 extern char *getuser(uid_t uid);
960 extern char *getgroup(gid_t gid);
961
962 static void ls_output(char *buf, char *fname, struct stat *statp)
963 {
964    char *p, *f;
965    char ec1[30];
966    int n;
967
968 // Dmsg2(000, "%s mode=0%o\n", fname, statp->st_mode);
969
970    p = encode_mode(statp->st_mode, buf);
971    n = sprintf(p, "  %2d ", (uint32_t)statp->st_nlink);
972    p += n;
973    n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
974    p += n;
975    n = sprintf(p, "%8.8s  ", edit_uint64(statp->st_size, ec1));
976    p += n;
977    p = encode_time(statp->st_ctime, p);
978    *p++ = ' ';
979    *p++ = ' ';
980    for (f=fname; *f; )
981       *p++ = *f++;
982    *p = 0;
983 }
984
985
986 /*
987  * Like ls command, but give more detail on each file
988  */
989 static int dircmd(UAContext *ua, TREE_CTX *tree)
990 {
991    TREE_NODE *node;
992    FILE_DBR fdbr;
993    struct stat statp;
994    char buf[1000];
995    char cwd[1100];
996
997    if (!tree->node->child) {     
998       return 1;
999    }
1000    for (node = tree->node->child; node; node=node->sibling) {
1001       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1002          tree_getpath(node, cwd, sizeof(cwd));
1003          fdbr.FileId = 0;
1004          fdbr.JobId = node->JobId;
1005          if (db_get_file_attributes_record(ua->db, cwd, &fdbr)) {
1006             decode_stat(fdbr.LStat, &statp); /* decode stat pkt */
1007             ls_output(buf, cwd, &statp);
1008             bsendmsg(ua, "%s\n", buf);
1009          } else {
1010             /* Something went wrong getting attributes -- print name */
1011             bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1012                (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1013          }
1014       }
1015    }
1016    return 1;
1017 }
1018
1019
1020 static int helpcmd(UAContext *ua, TREE_CTX *tree) 
1021 {
1022    unsigned int i;
1023
1024 /* usage(); */
1025    bsendmsg(ua, _("  Command    Description\n  =======    ===========\n"));
1026    for (i=0; i<comsize; i++) {
1027       bsendmsg(ua, _("  %-10s %s\n"), _(commands[i].key), _(commands[i].help));
1028    }
1029    bsendmsg(ua, "\n");
1030    return 1;
1031 }
1032
1033 /*
1034  * Change directories.  Note, if the user specifies x: and it fails,
1035  *   we assume it is a Win32 absolute cd rather than relative and
1036  *   try a second time with /x: ...  Win32 kludge.
1037  */
1038 static int cdcmd(UAContext *ua, TREE_CTX *tree) 
1039 {
1040    TREE_NODE *node;
1041    char cwd[2000];
1042
1043    if (ua->argc != 2) {
1044       return 1;
1045    }
1046    node = tree_cwd(ua->argk[1], tree->root, tree->node);
1047    if (!node) {
1048       /* Try once more if Win32 drive -- make absolute */
1049       if (ua->argk[1][1] == ':') {  /* win32 drive */
1050          strcpy(cwd, "/");
1051          strcat(cwd, ua->argk[1]);
1052          node = tree_cwd(cwd, tree->root, tree->node);
1053       }
1054       if (!node) {
1055          bsendmsg(ua, _("Invalid path given.\n"));
1056       } else {
1057          tree->node = node;
1058       }
1059    } else {
1060       tree->node = node;
1061    }
1062    tree_getpath(tree->node, cwd, sizeof(cwd));
1063    bsendmsg(ua, _("cwd is: %s\n"), cwd);
1064    return 1;
1065 }
1066
1067 static int pwdcmd(UAContext *ua, TREE_CTX *tree) 
1068 {
1069    char cwd[2000];
1070    tree_getpath(tree->node, cwd, sizeof(cwd));
1071    bsendmsg(ua, _("cwd is: %s\n"), cwd);
1072    return 1;
1073 }
1074
1075
1076 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
1077 {
1078    TREE_NODE *node;
1079
1080    if (ua->argc < 2)
1081       return 1;
1082    if (!tree->node->child) {     
1083       return 1;
1084    }
1085    for (node = tree->node->child; node; node=node->sibling) {
1086       if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1087          set_extract(node, 0);
1088       }
1089    }
1090    return 1;
1091 }
1092
1093 static int quitcmd(UAContext *ua, TREE_CTX *tree) 
1094 {
1095    return 0;
1096 }
1097
1098
1099 /*
1100  * Called here with each name to be added to the list. The name is
1101  *   added to the list if it is not already in the list.
1102  */
1103 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1104 {
1105    NAME_LIST *name = (NAME_LIST *)ctx;
1106
1107    if (name->num_ids == MAX_ID_LIST_LEN) {  
1108       return 1;
1109    }
1110    if (name->num_ids == name->max_ids) {
1111       if (name->max_ids == 0) {
1112          name->max_ids = 1000;
1113          name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1114       } else {
1115          name->max_ids = (name->max_ids * 3) / 2;
1116          name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1117       }
1118    }
1119    for (int i=0; i<name->num_ids; i++) {
1120       if (strcmp(name->name[i], row[0]) == 0) {
1121          return 0;                    /* already in list, return */
1122       }
1123    }
1124    /* Add new name to list */
1125    name->name[name->num_ids++] = bstrdup(row[0]);
1126    return 0;
1127 }
1128
1129
1130 /*
1131  * Print names in the list
1132  */
1133 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1134
1135    int i;
1136
1137    for (i=0; i < name_list->num_ids; i++) {
1138       bsendmsg(ua, "%s\n", name_list->name[i]);
1139    }
1140 }
1141
1142
1143 /*
1144  * Free names in the list
1145  */
1146 static void free_name_list(NAME_LIST *name_list)
1147
1148    int i;
1149
1150    for (i=0; i < name_list->num_ids; i++) {
1151       free(name_list->name[i]);
1152    }
1153    if (name_list->name) {
1154       free(name_list->name);
1155    }
1156    name_list->max_ids = 0;
1157    name_list->num_ids = 0;
1158 }
1159
1160 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JobIds *ji)
1161 {
1162    char name[MAX_NAME_LENGTH];
1163    STORE *store = NULL;
1164
1165    if (name_list->num_ids > 1) {
1166       bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1167          "Restore is not possible. The MediaTypes used are:\n"));
1168       print_name_list(ua, name_list);
1169       ji->store = select_storage_resource(ua);
1170       return;
1171    }
1172
1173    if (name_list->num_ids == 0) {
1174       bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1175       ji->store = select_storage_resource(ua);
1176       return;
1177    }
1178
1179    start_prompt(ua, _("The defined Storage resources are:\n"));
1180    LockRes();
1181    while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
1182       if (strcmp(store->media_type, name_list->name[0]) == 0) {
1183          add_prompt(ua, store->hdr.name);
1184       }
1185    }
1186    UnlockRes();
1187    do_prompt(ua, _("Select Storage resource"), name);
1188    ji->store = (STORE *)GetResWithName(R_STORAGE, name);
1189    if (!ji->store) {
1190       bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1191          "MediaType %s, needed by the Jobs you selected.\n"
1192          "You will be allowed to select a Storage device later.\n"),
1193          name_list->name[0]); 
1194    }
1195 }