]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
6266ab4ea0a6349b7bb1938082f4c845c70b6fc8
[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    utime_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, 0)) {
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"), 
365                        fileset_name, sizeof(fileset_name)) < 0) {
366             free_pool_memory(query);
367             return 0;
368          }
369          fsr.FileSetId = atoi(fileset_name);  /* Id is first part of 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          /* Find JobId of last Full backup for this client, fileset */
377          Mmsg(&query, uar_last_full, ji->client->hdr.name, fsr.FileSetId);
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    char prompt[MAX_NAME_LENGTH+200];
498
499    snprintf(prompt, sizeof(prompt), "%s  %s  %s", row[0], row[1], row[2]);
500    add_prompt((UAContext *)ctx, prompt);
501    return 0;
502 }
503
504 /* Forward referenced commands */
505
506 static int markcmd(UAContext *ua, TREE_CTX *tree);
507 static int countcmd(UAContext *ua, TREE_CTX *tree);
508 static int findcmd(UAContext *ua, TREE_CTX *tree);
509 static int lscmd(UAContext *ua, TREE_CTX *tree);
510 static int dircmd(UAContext *ua, TREE_CTX *tree);
511 static int helpcmd(UAContext *ua, TREE_CTX *tree);
512 static int cdcmd(UAContext *ua, TREE_CTX *tree);
513 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
514 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
515 static int quitcmd(UAContext *ua, TREE_CTX *tree);
516
517
518 struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; }; 
519 static struct cmdstruct commands[] = {
520  { N_("mark"),       markcmd,      _("mark file for restoration")},
521  { N_("unmark"),     unmarkcmd,    _("unmark file for restoration")},
522  { N_("cd"),         cdcmd,        _("change current directory")},
523  { N_("pwd"),        pwdcmd,       _("print current working directory")},
524  { N_("ls"),         lscmd,        _("list current directory")},    
525  { N_("dir"),        dircmd,       _("list current directory")},    
526  { N_("count"),      countcmd,     _("count marked files")},
527  { N_("find"),       findcmd,      _("find files")},
528  { N_("done"),       quitcmd,      _("leave file selection mode")},
529  { N_("exit"),       quitcmd,      _("exit = done")},
530  { N_("help"),       helpcmd,      _("print help")},
531  { N_("?"),          helpcmd,      _("print help")},    
532              };
533 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
534
535
536 /*
537  * Enter a prompt mode where the user can select/deselect
538  *  files to be restored. This is sort of like a mini-shell
539  *  that allows "cd", "pwd", "add", "rm", ...
540  */
541 static void user_select_files(TREE_CTX *tree)
542 {
543    char cwd[2000];
544
545    bsendmsg(tree->ua, _( 
546       "\nYou are now entering file selection mode where you add and\n"
547       "remove files to be restored. All files are initially added.\n"
548       "Enter \"done\" to leave this mode.\n\n"));
549    /*
550     * Enter interactive command handler allowing selection
551     *  of individual files.
552     */
553    tree->node = (TREE_NODE *)tree->root;
554    tree_getpath(tree->node, cwd, sizeof(cwd));
555    bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
556    for ( ;; ) {       
557       int found, len, stat, i;
558       if (!get_cmd(tree->ua, "$ ")) {
559          break;
560       }
561       parse_command_args(tree->ua);
562       if (tree->ua->argc == 0) {
563          return;
564       }
565
566       len = strlen(tree->ua->argk[0]);
567       found = 0;
568       stat = 0;
569       for (i=0; i<(int)comsize; i++)       /* search for command */
570          if (strncasecmp(tree->ua->argk[0],  _(commands[i].key), len) == 0) {
571             stat = (*commands[i].func)(tree->ua, tree);   /* go execute command */
572             found = 1;
573             break;
574          }
575       if (!found) {
576          bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
577          continue;
578       }
579       if (!stat) {
580          break;
581       }
582    }
583 }
584
585 /*
586  * Create new FileIndex entry for BSR 
587  */
588 static RBSR_FINDEX *new_findex() 
589 {
590    RBSR_FINDEX *fi = (RBSR_FINDEX *)bmalloc(sizeof(RBSR_FINDEX));
591    memset(fi, 0, sizeof(RBSR_FINDEX));
592    return fi;
593 }
594
595 /* Free all BSR FileIndex entries */
596 static void free_findex(RBSR_FINDEX *fi)
597 {
598    if (fi) {
599       free_findex(fi->next);
600       free(fi);
601    }
602 }
603
604 static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd) 
605 {
606    if (fi) {
607       if (fi->findex == fi->findex2) {
608          fprintf(fd, "FileIndex=%d\n", fi->findex);
609       } else {
610          fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
611       }
612       write_findex(ua, fi->next, fd);
613    }
614 }
615
616
617 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
618 {
619    if (fi) {
620       if (fi->findex == fi->findex2) {
621          bsendmsg(ua, "FileIndex=%d\n", fi->findex);
622       } else {
623          bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
624       }
625       print_findex(ua, fi->next);
626    }
627 }
628
629 /* Create a new bootstrap record */
630 static RBSR *new_bsr()
631 {
632    RBSR *bsr = (RBSR *)bmalloc(sizeof(RBSR));
633    memset(bsr, 0, sizeof(RBSR));
634    return bsr;
635 }
636
637 /* Free the entire BSR */
638 static void free_bsr(RBSR *bsr)
639 {
640    if (bsr) {
641       free_findex(bsr->fi);
642       free_bsr(bsr->next);
643       if (bsr->VolumeName) {
644          free(bsr->VolumeName);
645       }
646       free(bsr);
647    }
648 }
649
650 /*
651  * Complete the BSR by filling in the VolumeName and
652  *  VolSessionId and VolSessionTime using the JobId
653  */
654 static int complete_bsr(UAContext *ua, RBSR *bsr)
655 {
656    JOB_DBR jr;
657    POOLMEM *VolumeNames;
658
659    if (bsr) {
660       VolumeNames = get_pool_memory(PM_MESSAGE);
661       memset(&jr, 0, sizeof(jr));
662       jr.JobId = bsr->JobId;
663       if (!db_get_job_record(ua->db, &jr)) {
664          bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
665          return 0;
666       }
667       bsr->VolSessionId = jr.VolSessionId;
668       bsr->VolSessionTime = jr.VolSessionTime;
669       if (!db_get_job_volume_names(ua->db, bsr->JobId, &VolumeNames)) {
670          bsendmsg(ua, _("Unable to get Job Volumes. ERR=%s\n"), db_strerror(ua->db));
671          free_pool_memory(VolumeNames);
672          return 0;
673       }
674       bsr->VolumeName = bstrdup(VolumeNames);
675       free_pool_memory(VolumeNames);
676       return complete_bsr(ua, bsr->next);
677    }
678    return 1;
679 }
680
681 /*
682  * Write the bootstrap record to file
683  */
684 static int write_bsr_file(UAContext *ua, RBSR *bsr)
685 {
686    FILE *fd;
687    POOLMEM *fname = get_pool_memory(PM_MESSAGE);
688    int stat;
689    RBSR *nbsr;
690
691    Mmsg(&fname, "%s/restore.bsr", working_directory);
692    fd = fopen(fname, "w+");
693    if (!fd) {
694       bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"), 
695          fname, strerror(errno));
696       free_pool_memory(fname);
697       return 0;
698    }
699    write_bsr(ua, bsr, fd);
700    stat = !ferror(fd);
701    fclose(fd);
702    bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
703    bsendmsg(ua, _("\nThe restore job will require the following Volumes:\n"));
704    for (nbsr=bsr; nbsr; nbsr=nbsr->next) {
705       if (nbsr->VolumeName) {
706          bsendmsg(ua, "   %s\n", nbsr->VolumeName);
707       }
708    }
709    bsendmsg(ua, "\n");
710    free_pool_memory(fname);
711    return stat;
712 }
713
714 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
715 {
716    if (bsr) {
717       if (bsr->VolumeName) {
718          fprintf(fd, "Volume=\"%s\"\n", bsr->VolumeName);
719       }
720       fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
721       fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
722       write_findex(ua, bsr->fi, fd);
723       write_bsr(ua, bsr->next, fd);
724    }
725 }
726
727 static void print_bsr(UAContext *ua, RBSR *bsr)
728 {
729    if (bsr) {
730       if (bsr->VolumeName) {
731          bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolumeName);
732       }
733       bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
734       bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
735       print_findex(ua, bsr->fi);
736       print_bsr(ua, bsr->next);
737    }
738 }
739
740
741 /*
742  * Add a FileIndex to the list of BootStrap records.
743  *  Here we are only dealing with JobId's and the FileIndexes
744  *  associated with those JobIds.
745  */
746 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
747 {
748    RBSR *nbsr;
749    RBSR_FINDEX *fi, *lfi;
750
751    if (findex == 0) {
752       return;                         /* probably a dummy directory */
753    }
754    
755    if (!bsr->fi) {                    /* if no FI add one */
756       /* This is the first FileIndex item in the chain */
757       bsr->fi = new_findex();
758       bsr->JobId = JobId;
759       bsr->fi->findex = findex;
760       bsr->fi->findex2 = findex;
761       return;
762    }
763    /* Walk down list of bsrs until we find the JobId */
764    if (bsr->JobId != JobId) {
765       for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
766          if (nbsr->JobId == JobId) {
767             bsr = nbsr;
768             break;
769          }
770       }
771
772       if (!nbsr) {                    /* Must add new JobId */
773          /* Add new JobId at end of chain */
774          for (nbsr=bsr; nbsr->next; nbsr=nbsr->next) 
775             {  }
776          nbsr->next = new_bsr();
777          nbsr->next->JobId = JobId;
778          nbsr->next->fi = new_findex();
779          nbsr->next->fi->findex = findex;
780          nbsr->next->fi->findex2 = findex;
781          return;
782       }
783    }
784
785    /* 
786     * At this point, bsr points to bsr containing JobId,
787     *  and we are sure that there is at least one fi record.
788     */
789    lfi = fi = bsr->fi;
790    /* Check if this findex is smaller than first item */
791    if (findex < fi->findex) {
792       if ((findex+1) == fi->findex) {
793          fi->findex = findex;         /* extend down */
794          return;
795       }
796       fi = new_findex();              /* yes, insert before first item */
797       fi->findex = findex;
798       fi->findex2 = findex;
799       fi->next = lfi;
800       bsr->fi = fi;
801       return;
802    }
803    /* Walk down fi chain and find where to insert insert new FileIndex */
804    for ( ; fi; fi=fi->next) {
805       if (findex == (fi->findex2 + 1)) {  /* extend up */
806          RBSR_FINDEX *nfi;     
807          fi->findex2 = findex;
808          if (fi->next && ((findex+1) == fi->next->findex)) { 
809             nfi = fi->next;
810             fi->findex2 = nfi->findex2;
811             fi->next = nfi->next;
812             free(nfi);
813          }
814          return;
815       }
816       if (findex < fi->findex) {      /* add before */
817          if ((findex+1) == fi->findex) {
818             fi->findex = findex;
819             return;
820          }
821          break;
822       }
823       lfi = fi;
824    }
825    /* Add to last place found */
826    fi = new_findex();
827    fi->findex = findex;
828    fi->findex2 = findex;
829    fi->next = lfi->next;
830    lfi->next = fi;
831    return;
832 }
833
834 /*
835  * This callback routine is responsible for inserting the
836  *  items it gets into the directory tree. For each JobId selected
837  *  this routine is called once for each file. We do not allow
838  *  duplicate filenames, but instead keep the info from the most
839  *  recent file entered (i.e. the JobIds are assumed to be sorted)
840  */
841 static int insert_tree_handler(void *ctx, int num_fields, char **row)
842 {
843    TREE_CTX *tree = (TREE_CTX *)ctx;
844    char fname[2000];
845    TREE_NODE *node, *new_node;
846    int type;
847
848    strip_trailing_junk(row[1]);
849    if (*row[1] == 0) {
850       if (*row[0] != '/') {           /* Must be Win32 directory */
851          type = TN_DIR_NLS;
852       } else {
853          type = TN_DIR;
854       }
855    } else {
856       type = TN_FILE;
857    }
858    sprintf(fname, "%s%s", row[0], row[1]);
859    if (tree->avail_node) {
860       node = tree->avail_node;
861    } else {
862       node = new_tree_node(tree->root, type);
863       tree->avail_node = node;
864    }
865    Dmsg3(200, "FI=%d type=%d fname=%s\n", node->FileIndex, type, fname);
866    new_node = insert_tree_node(fname, node, tree->root, NULL);
867    /* Note, if node already exists, save new one for next time */
868    if (new_node != node) {
869       tree->avail_node = node;
870    } else {
871       tree->avail_node = NULL;
872    }
873    new_node->FileIndex = atoi(row[2]);
874    new_node->JobId = atoi(row[3]);
875    new_node->type = type;
876    new_node->extract = 1;             /* extract all by default */
877    tree->cnt++;
878    return 0;
879 }
880
881
882 /*
883  * Set extract to value passed. We recursively walk
884  *  down the tree setting all children if the 
885  *  node is a directory.
886  */
887 static void set_extract(TREE_NODE *node, int value)
888 {
889    TREE_NODE *n;
890
891    node->extract = value;
892    if (node->type != TN_FILE) {
893       for (n=node->child; n; n=n->sibling) {
894          set_extract(n, value);
895       }
896    }
897 }
898
899 static int markcmd(UAContext *ua, TREE_CTX *tree)
900 {
901    TREE_NODE *node;
902
903    if (ua->argc < 2)
904       return 1;
905    if (!tree->node->child) {     
906       return 1;
907    }
908    for (node = tree->node->child; node; node=node->sibling) {
909       if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
910          set_extract(node, 1);
911       }
912    }
913    return 1;
914 }
915
916 static int countcmd(UAContext *ua, TREE_CTX *tree)
917 {
918    int total, extract;
919
920    total = extract = 0;
921    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
922       if (node->type != TN_NEWDIR) {
923          total++;
924          if (node->extract) {
925             extract++;
926          }
927       }
928    }
929    bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
930    return 1;
931 }
932
933 static int findcmd(UAContext *ua, TREE_CTX *tree)
934 {
935    char cwd[2000];
936
937    if (ua->argc == 1) {
938       bsendmsg(ua, _("No file specification given.\n"));
939       return 0;
940    }
941    
942    for (int i=1; i < ua->argc; i++) {
943       for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
944          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
945             tree_getpath(node, cwd, sizeof(cwd));
946             bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
947          }
948       }
949    }
950    return 1;
951 }
952
953
954
955 static int lscmd(UAContext *ua, TREE_CTX *tree)
956 {
957    TREE_NODE *node;
958
959    if (!tree->node->child) {     
960       return 1;
961    }
962    for (node = tree->node->child; node; node=node->sibling) {
963       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
964          bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
965             (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
966       }
967    }
968    return 1;
969 }
970
971 extern char *getuser(uid_t uid);
972 extern char *getgroup(gid_t gid);
973
974 static void ls_output(char *buf, char *fname, struct stat *statp)
975 {
976    char *p, *f;
977    char ec1[30];
978    int n;
979
980 // Dmsg2(000, "%s mode=0%o\n", fname, statp->st_mode);
981
982    p = encode_mode(statp->st_mode, buf);
983    n = sprintf(p, "  %2d ", (uint32_t)statp->st_nlink);
984    p += n;
985    n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
986    p += n;
987    n = sprintf(p, "%8.8s  ", edit_uint64(statp->st_size, ec1));
988    p += n;
989    p = encode_time(statp->st_ctime, p);
990    *p++ = ' ';
991    *p++ = ' ';
992    for (f=fname; *f; )
993       *p++ = *f++;
994    *p = 0;
995 }
996
997
998 /*
999  * Like ls command, but give more detail on each file
1000  */
1001 static int dircmd(UAContext *ua, TREE_CTX *tree)
1002 {
1003    TREE_NODE *node;
1004    FILE_DBR fdbr;
1005    struct stat statp;
1006    char buf[1000];
1007    char cwd[1100];
1008
1009    if (!tree->node->child) {     
1010       return 1;
1011    }
1012    for (node = tree->node->child; node; node=node->sibling) {
1013       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1014          tree_getpath(node, cwd, sizeof(cwd));
1015          fdbr.FileId = 0;
1016          fdbr.JobId = node->JobId;
1017          if (db_get_file_attributes_record(ua->db, cwd, &fdbr)) {
1018             decode_stat(fdbr.LStat, &statp); /* decode stat pkt */
1019             ls_output(buf, cwd, &statp);
1020             bsendmsg(ua, "%s\n", buf);
1021          } else {
1022             /* Something went wrong getting attributes -- print name */
1023             bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1024                (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1025          }
1026       }
1027    }
1028    return 1;
1029 }
1030
1031
1032 static int helpcmd(UAContext *ua, TREE_CTX *tree) 
1033 {
1034    unsigned int i;
1035
1036 /* usage(); */
1037    bsendmsg(ua, _("  Command    Description\n  =======    ===========\n"));
1038    for (i=0; i<comsize; i++) {
1039       bsendmsg(ua, _("  %-10s %s\n"), _(commands[i].key), _(commands[i].help));
1040    }
1041    bsendmsg(ua, "\n");
1042    return 1;
1043 }
1044
1045 /*
1046  * Change directories.  Note, if the user specifies x: and it fails,
1047  *   we assume it is a Win32 absolute cd rather than relative and
1048  *   try a second time with /x: ...  Win32 kludge.
1049  */
1050 static int cdcmd(UAContext *ua, TREE_CTX *tree) 
1051 {
1052    TREE_NODE *node;
1053    char cwd[2000];
1054
1055    if (ua->argc != 2) {
1056       return 1;
1057    }
1058    node = tree_cwd(ua->argk[1], tree->root, tree->node);
1059    if (!node) {
1060       /* Try once more if Win32 drive -- make absolute */
1061       if (ua->argk[1][1] == ':') {  /* win32 drive */
1062          strcpy(cwd, "/");
1063          strcat(cwd, ua->argk[1]);
1064          node = tree_cwd(cwd, tree->root, tree->node);
1065       }
1066       if (!node) {
1067          bsendmsg(ua, _("Invalid path given.\n"));
1068       } else {
1069          tree->node = node;
1070       }
1071    } else {
1072       tree->node = node;
1073    }
1074    tree_getpath(tree->node, cwd, sizeof(cwd));
1075    bsendmsg(ua, _("cwd is: %s\n"), cwd);
1076    return 1;
1077 }
1078
1079 static int pwdcmd(UAContext *ua, TREE_CTX *tree) 
1080 {
1081    char cwd[2000];
1082    tree_getpath(tree->node, cwd, sizeof(cwd));
1083    bsendmsg(ua, _("cwd is: %s\n"), cwd);
1084    return 1;
1085 }
1086
1087
1088 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
1089 {
1090    TREE_NODE *node;
1091
1092    if (ua->argc < 2)
1093       return 1;
1094    if (!tree->node->child) {     
1095       return 1;
1096    }
1097    for (node = tree->node->child; node; node=node->sibling) {
1098       if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1099          set_extract(node, 0);
1100       }
1101    }
1102    return 1;
1103 }
1104
1105 static int quitcmd(UAContext *ua, TREE_CTX *tree) 
1106 {
1107    return 0;
1108 }
1109
1110
1111 /*
1112  * Called here with each name to be added to the list. The name is
1113  *   added to the list if it is not already in the list.
1114  */
1115 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1116 {
1117    NAME_LIST *name = (NAME_LIST *)ctx;
1118
1119    if (name->num_ids == MAX_ID_LIST_LEN) {  
1120       return 1;
1121    }
1122    if (name->num_ids == name->max_ids) {
1123       if (name->max_ids == 0) {
1124          name->max_ids = 1000;
1125          name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1126       } else {
1127          name->max_ids = (name->max_ids * 3) / 2;
1128          name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1129       }
1130    }
1131    for (int i=0; i<name->num_ids; i++) {
1132       if (strcmp(name->name[i], row[0]) == 0) {
1133          return 0;                    /* already in list, return */
1134       }
1135    }
1136    /* Add new name to list */
1137    name->name[name->num_ids++] = bstrdup(row[0]);
1138    return 0;
1139 }
1140
1141
1142 /*
1143  * Print names in the list
1144  */
1145 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1146
1147    int i;
1148
1149    for (i=0; i < name_list->num_ids; i++) {
1150       bsendmsg(ua, "%s\n", name_list->name[i]);
1151    }
1152 }
1153
1154
1155 /*
1156  * Free names in the list
1157  */
1158 static void free_name_list(NAME_LIST *name_list)
1159
1160    int i;
1161
1162    for (i=0; i < name_list->num_ids; i++) {
1163       free(name_list->name[i]);
1164    }
1165    if (name_list->name) {
1166       free(name_list->name);
1167    }
1168    name_list->max_ids = 0;
1169    name_list->num_ids = 0;
1170 }
1171
1172 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JobIds *ji)
1173 {
1174    char name[MAX_NAME_LENGTH];
1175    STORE *store = NULL;
1176
1177    if (name_list->num_ids > 1) {
1178       bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1179          "Restore is not possible. The MediaTypes used are:\n"));
1180       print_name_list(ua, name_list);
1181       ji->store = select_storage_resource(ua);
1182       return;
1183    }
1184
1185    if (name_list->num_ids == 0) {
1186       bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1187       ji->store = select_storage_resource(ua);
1188       return;
1189    }
1190
1191    start_prompt(ua, _("The defined Storage resources are:\n"));
1192    LockRes();
1193    while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
1194       if (strcmp(store->media_type, name_list->name[0]) == 0) {
1195          add_prompt(ua, store->hdr.name);
1196       }
1197    }
1198    UnlockRes();
1199    do_prompt(ua, _("Select Storage resource"), name, sizeof(name));
1200    ji->store = (STORE *)GetResWithName(R_STORAGE, name);
1201    if (!ji->store) {
1202       bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1203          "MediaType %s, needed by the Jobs you selected.\n"
1204          "You will be allowed to select a Storage device later.\n"),
1205          name_list->name[0]); 
1206    }
1207 }