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