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