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