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