]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
Mult Vols + fix restore hard links + file permissions
[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 "ua.h"
34 #include <fnmatch.h>
35 #include "findlib/find.h"
36
37
38
39 /* Imported functions */
40 extern int runcmd(UAContext *ua, char *cmd);
41
42 /* Imported variables */
43 extern char *uar_list_jobs,     *uar_file,        *uar_sel_files;
44 extern char *uar_del_temp,      *uar_del_temp1,   *uar_create_temp;
45 extern char *uar_create_temp1,  *uar_last_full,   *uar_full;
46 extern char *uar_inc,           *uar_list_temp,   *uar_sel_jobid_temp;
47 extern char *uar_sel_all_temp1, *uar_sel_fileset, *uar_mediatype;
48
49
50 /* Context for insert_tree_handler() */
51 typedef struct s_tree_ctx {
52    TREE_ROOT *root;                   /* root */
53    TREE_NODE *node;                   /* current node */
54    TREE_NODE *avail_node;             /* unused node last insert */
55    int cnt;                           /* count for user feedback */
56    UAContext *ua;
57 } TREE_CTX;
58
59 /* Main structure for obtaining JobIds */
60 typedef struct s_jobids {
61    utime_t JobTDate;
62    uint32_t TotalFiles;
63    char ClientName[MAX_NAME_LENGTH];
64    char JobIds[200];
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    int      VolCount;                 /* Volume parameter count */
83    VOL_PARAMS *VolParams;             /* Volume, start/end file/blocks */
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, "%d items inserted into the tree and marked for extraction.\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.ClientName[0]) {
255       Mmsg(&ua->cmd, 
256          "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
257          job->hdr.name, ji.ClientName, 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, ed1[50];
284    FILESET_DBR fsr;
285    CLIENT_DBR cr;
286    JobId_t JobId;
287    JOB_DBR jr;
288    POOLMEM *query;
289    int done = 0;
290    char *list[] = { 
291       "List last 20 Jobs run",
292       "List Jobs where a given File is saved",
293       "Enter list of JobIds to select",
294       "Enter SQL list command", 
295       "Select the most recent backup for a client",
296       "Cancel",
297       NULL };
298
299    bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
300                   "to be restored. You will be presented several methods\n"
301                   "of specifying the JobIds. Then you will be allowed to\n"
302                   "select which files from those JobIds are to be restored.\n\n"));
303
304    for ( ; !done; ) {
305       start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
306       for (int i=0; list[i]; i++) {
307          add_prompt(ua, list[i]);
308       }
309       done = 1;
310       switch (do_prompt(ua, "Select item: ", NULL, 0)) {
311       case -1:                        /* error */
312          return 0;
313       case 0:                         /* list last 20 Jobs run */
314          db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1);
315          done = 0;
316          break;
317       case 1:                         /* list where a file is saved */
318          if (!get_cmd(ua, _("Enter Filename: "))) {
319             return 0;
320          }
321          query = get_pool_memory(PM_MESSAGE);
322          Mmsg(&query, uar_file, ua->cmd);
323          db_list_sql_query(ua->jcr, ua->db, query, prtit, ua, 1);
324          free_pool_memory(query);
325          done = 0;
326          break;
327       case 2:                         /* enter a list of JobIds */
328          if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
329             return 0;
330          }
331          bstrncpy(ji->JobIds, ua->cmd, sizeof(ji->JobIds));
332          break;
333       case 3:                         /* Enter an SQL list command */
334          if (!get_cmd(ua, _("Enter SQL list command: "))) {
335             return 0;
336          }
337          db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1);
338          done = 0;
339          break;
340       case 4:                         /* Select the most recent backups */
341          query = get_pool_memory(PM_MESSAGE);
342          db_sql_query(ua->db, uar_del_temp, NULL, NULL);
343          db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
344          if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
345             bsendmsg(ua, "%s\n", db_strerror(ua->db));
346          }
347          if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
348             bsendmsg(ua, "%s\n", db_strerror(ua->db));
349          }
350          /*
351           * Select Client from the Catalog
352           */
353          memset(&cr, 0, sizeof(cr));
354          if (!get_client_dbr(ua, &cr)) {
355             free_pool_memory(query);
356             db_sql_query(ua->db, uar_del_temp, NULL, NULL);
357             db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
358             return 0;
359          }
360          bstrncpy(ji->ClientName, cr.Name, sizeof(ji->ClientName));
361
362          /*
363           * Select FileSet 
364           */
365          Mmsg(&query, uar_sel_fileset, cr.ClientId, cr.ClientId);
366          start_prompt(ua, _("The defined FileSet resources are:\n"));
367          if (!db_sql_query(ua->db, query, fileset_handler, (void *)ua)) {
368             bsendmsg(ua, "%s\n", db_strerror(ua->db));
369          }
370          if (do_prompt(ua, _("Select FileSet resource"), 
371                        fileset_name, sizeof(fileset_name)) < 0) {
372             free_pool_memory(query);
373             db_sql_query(ua->db, uar_del_temp, NULL, NULL);
374             db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
375             return 0;
376          }
377          fsr.FileSetId = atoi(fileset_name);  /* Id is first part of name */
378          if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
379             bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
380             bsendmsg(ua, _("This probably means you modified the FileSet.\n"
381                            "Continuing anyway.\n"));
382          }
383
384          /* Find JobId of last Full backup for this client, fileset */
385          Mmsg(&query, uar_last_full, cr.ClientId, cr.ClientId, fsr.FileSetId);
386          if (!db_sql_query(ua->db, query, NULL, NULL)) {
387             bsendmsg(ua, "%s\n", db_strerror(ua->db));
388          }
389          /* Find all Volumes used by that JobId */
390          if (!db_sql_query(ua->db, uar_full, NULL,NULL)) {
391             bsendmsg(ua, "%s\n", db_strerror(ua->db));
392          }
393          /* Note, this is needed as I don't seem to get the callback
394           * from the call just above.
395           */
396          if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)ji)) {
397             bsendmsg(ua, "%s\n", db_strerror(ua->db));
398          }
399          /* Now find all Incremental Jobs */
400          Mmsg(&query, uar_inc, edit_uint64(ji->JobTDate, ed1), cr.ClientId, fsr.FileSetId);
401          if (!db_sql_query(ua->db, query, NULL, NULL)) {
402             bsendmsg(ua, "%s\n", db_strerror(ua->db));
403          }
404          free_pool_memory(query);
405          db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1);
406
407          if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)ji)) {
408             bsendmsg(ua, "%s\n", db_strerror(ua->db));
409          }
410          db_sql_query(ua->db, uar_del_temp, NULL, NULL);
411          db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
412          break;
413       case 5:
414          return 0;
415       }
416    }
417
418    if (*ji->JobIds == 0) {
419       bsendmsg(ua, _("No Jobs selected.\n"));
420       return 0;
421    }
422    bsendmsg(ua, _("You have selected the following JobId: %s\n"), ji->JobIds);
423
424    memset(&jr, 0, sizeof(JOB_DBR));
425
426    ji->TotalFiles = 0;
427    for (p=ji->JobIds; ; ) {
428       int stat = next_jobid_from_list(&p, &JobId);
429       if (stat < 0) {
430          bsendmsg(ua, _("Invalid JobId in list.\n"));
431          return 0;
432       }
433       if (stat == 0) {
434          break;
435       }
436       jr.JobId = JobId;
437       if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
438          bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
439          return 0;
440       }
441       ji->TotalFiles += jr.JobFiles;
442    }
443    return 1;
444 }
445
446 static int next_jobid_from_list(char **p, uint32_t *JobId)
447 {
448    char jobid[30];
449    int i;
450    char *q = *p;
451
452    jobid[0] = 0;
453    for (i=0; i<(int)sizeof(jobid); i++) {
454       if (*q == ',' || *q == 0) {
455          q++;
456          break;
457       }
458       jobid[i] = *q++;
459       jobid[i+1] = 0;
460    }
461    if (jobid[0] == 0 || !is_a_number(jobid)) {
462       return 0;
463    }
464    *p = q;
465    *JobId = strtoul(jobid, NULL, 10);
466    return 1;
467 }
468
469 /*
470  * Callback handler make list of JobIds
471  */
472 static int jobid_handler(void *ctx, int num_fields, char **row)
473 {
474    JobIds *ji = (JobIds *)ctx;
475
476    if (strlen(ji->JobIds)+strlen(row[0])+2 < sizeof(ji->JobIds)) {
477       if (ji->JobIds[0] != 0) {
478          strcat(ji->JobIds, ",");
479       }
480       strcat(ji->JobIds, row[0]);
481    }
482
483    return 0;
484 }
485
486
487 /*
488  * Callback handler to pickup last Full backup JobId and ClientId
489  */
490 static int last_full_handler(void *ctx, int num_fields, char **row)
491 {
492    JobIds *ji = (JobIds *)ctx;
493
494    ji->JobTDate = strtoll(row[1], NULL, 10);
495
496    return 0;
497 }
498
499 /*
500  * Callback handler build fileset prompt list
501  */
502 static int fileset_handler(void *ctx, int num_fields, char **row)
503 {
504    char prompt[MAX_NAME_LENGTH+200];
505
506    snprintf(prompt, sizeof(prompt), "%s  %s  %s", row[0], row[1], row[2]);
507    add_prompt((UAContext *)ctx, prompt);
508    return 0;
509 }
510
511 /* Forward referenced commands */
512
513 static int markcmd(UAContext *ua, TREE_CTX *tree);
514 static int countcmd(UAContext *ua, TREE_CTX *tree);
515 static int findcmd(UAContext *ua, TREE_CTX *tree);
516 static int lscmd(UAContext *ua, TREE_CTX *tree);
517 static int dircmd(UAContext *ua, TREE_CTX *tree);
518 static int helpcmd(UAContext *ua, TREE_CTX *tree);
519 static int cdcmd(UAContext *ua, TREE_CTX *tree);
520 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
521 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
522 static int quitcmd(UAContext *ua, TREE_CTX *tree);
523
524
525 struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; }; 
526 static struct cmdstruct commands[] = {
527  { N_("mark"),       markcmd,      _("mark file for restoration")},
528  { N_("unmark"),     unmarkcmd,    _("unmark file for restoration")},
529  { N_("cd"),         cdcmd,        _("change current directory")},
530  { N_("pwd"),        pwdcmd,       _("print current working directory")},
531  { N_("ls"),         lscmd,        _("list current directory")},    
532  { N_("dir"),        dircmd,       _("list current directory")},    
533  { N_("count"),      countcmd,     _("count marked files")},
534  { N_("find"),       findcmd,      _("find files")},
535  { N_("done"),       quitcmd,      _("leave file selection mode")},
536  { N_("exit"),       quitcmd,      _("exit = done")},
537  { N_("help"),       helpcmd,      _("print help")},
538  { N_("?"),          helpcmd,      _("print help")},    
539              };
540 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
541
542
543 /*
544  * Enter a prompt mode where the user can select/deselect
545  *  files to be restored. This is sort of like a mini-shell
546  *  that allows "cd", "pwd", "add", "rm", ...
547  */
548 static void user_select_files(TREE_CTX *tree)
549 {
550    char cwd[2000];
551
552    bsendmsg(tree->ua, _( 
553       "\nYou are now entering file selection mode where you add and\n"
554       "remove files to be restored. All files are initially added.\n"
555       "Enter \"done\" to leave this mode.\n\n"));
556    /*
557     * Enter interactive command handler allowing selection
558     *  of individual files.
559     */
560    tree->node = (TREE_NODE *)tree->root;
561    tree_getpath(tree->node, cwd, sizeof(cwd));
562    bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
563    for ( ;; ) {       
564       int found, len, stat, i;
565       if (!get_cmd(tree->ua, "$ ")) {
566          break;
567       }
568       parse_command_args(tree->ua);
569       if (tree->ua->argc == 0) {
570          return;
571       }
572
573       len = strlen(tree->ua->argk[0]);
574       found = 0;
575       stat = 0;
576       for (i=0; i<(int)comsize; i++)       /* search for command */
577          if (strncasecmp(tree->ua->argk[0],  _(commands[i].key), len) == 0) {
578             stat = (*commands[i].func)(tree->ua, tree);   /* go execute command */
579             found = 1;
580             break;
581          }
582       if (!found) {
583          bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
584          continue;
585       }
586       if (!stat) {
587          break;
588       }
589    }
590 }
591
592 /*
593  * Create new FileIndex entry for BSR 
594  */
595 static RBSR_FINDEX *new_findex() 
596 {
597    RBSR_FINDEX *fi = (RBSR_FINDEX *)bmalloc(sizeof(RBSR_FINDEX));
598    memset(fi, 0, sizeof(RBSR_FINDEX));
599    return fi;
600 }
601
602 /* Free all BSR FileIndex entries */
603 static void free_findex(RBSR_FINDEX *fi)
604 {
605    if (fi) {
606       free_findex(fi->next);
607       free(fi);
608    }
609 }
610
611 static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd) 
612 {
613    if (fi) {
614       if (fi->findex == fi->findex2) {
615          fprintf(fd, "FileIndex=%d\n", fi->findex);
616       } else {
617          fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
618       }
619       write_findex(ua, fi->next, fd);
620    }
621 }
622
623
624 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
625 {
626    if (fi) {
627       if (fi->findex == fi->findex2) {
628          bsendmsg(ua, "FileIndex=%d\n", fi->findex);
629       } else {
630          bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
631       }
632       print_findex(ua, fi->next);
633    }
634 }
635
636 /* Create a new bootstrap record */
637 static RBSR *new_bsr()
638 {
639    RBSR *bsr = (RBSR *)bmalloc(sizeof(RBSR));
640    memset(bsr, 0, sizeof(RBSR));
641    return bsr;
642 }
643
644 /* Free the entire BSR */
645 static void free_bsr(RBSR *bsr)
646 {
647    if (bsr) {
648       free_findex(bsr->fi);
649       free_bsr(bsr->next);
650       if (bsr->VolParams) {
651          free(bsr->VolParams);
652       }
653       free(bsr);
654    }
655 }
656
657 /*
658  * Complete the BSR by filling in the VolumeName and
659  *  VolSessionId and VolSessionTime using the JobId
660  */
661 static int complete_bsr(UAContext *ua, RBSR *bsr)
662 {
663    JOB_DBR jr;
664
665    if (bsr) {
666       memset(&jr, 0, sizeof(jr));
667       jr.JobId = bsr->JobId;
668       if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
669          bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
670          return 0;
671       }
672       bsr->VolSessionId = jr.VolSessionId;
673       bsr->VolSessionTime = jr.VolSessionTime;
674       if ((bsr->VolCount=db_get_job_volume_parameters(ua->jcr, ua->db, bsr->JobId, 
675            &(bsr->VolParams))) == 0) {
676          bsendmsg(ua, _("Unable to get Job Volume Parameters. ERR=%s\n"), db_strerror(ua->db));
677          if (bsr->VolParams) {
678             free(bsr->VolParams);
679             bsr->VolParams = NULL;
680          }
681          return 0;
682       }
683       return complete_bsr(ua, bsr->next);
684    }
685    return 1;
686 }
687
688 /*
689  * Write the bootstrap record to file
690  */
691 static int write_bsr_file(UAContext *ua, RBSR *bsr)
692 {
693    FILE *fd;
694    POOLMEM *fname = get_pool_memory(PM_MESSAGE);
695    int stat;
696    RBSR *nbsr;
697
698    Mmsg(&fname, "%s/restore.bsr", working_directory);
699    fd = fopen(fname, "w+");
700    if (!fd) {
701       bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"), 
702          fname, strerror(errno));
703       free_pool_memory(fname);
704       return 0;
705    }
706    write_bsr(ua, bsr, fd);
707    stat = !ferror(fd);
708    fclose(fd);
709    bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
710    bsendmsg(ua, _("\nThe restore job will require the following Volumes:\n"));
711    /* Create Unique list of Volumes using prompt list */
712    start_prompt(ua, "");
713    for (nbsr=bsr; nbsr; nbsr=nbsr->next) {
714       for (int i=0; i < nbsr->VolCount; i++) {
715          add_prompt(ua, nbsr->VolParams[i].VolumeName);
716       }
717    }
718    for (int i=0; i < ua->num_prompts; i++) {
719       bsendmsg(ua, "   %s\n", ua->prompt[i]);
720       free(ua->prompt[i]);
721    }
722    ua->num_prompts = 0;
723    bsendmsg(ua, "\n");
724    free_pool_memory(fname);
725    return stat;
726 }
727
728 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
729 {
730    if (bsr) {
731       for (int i=0; i < bsr->VolCount; i++) {
732          fprintf(fd, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
733          fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
734          fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
735          fprintf(fd, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile, 
736                  bsr->VolParams[i].EndFile);
737          fprintf(fd, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
738                  bsr->VolParams[i].EndBlock);
739          write_findex(ua, bsr->fi, fd);
740       }
741       write_bsr(ua, bsr->next, fd);
742    }
743 }
744
745 static void print_bsr(UAContext *ua, RBSR *bsr)
746 {
747    if (bsr) {
748       for (int i=0; i < bsr->VolCount; i++) {
749          bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
750          bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
751          bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
752          bsendmsg(ua, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile, 
753                   bsr->VolParams[i].EndFile);
754          bsendmsg(ua, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
755                   bsr->VolParams[i].EndBlock);
756          print_findex(ua, bsr->fi);
757       }
758       print_bsr(ua, bsr->next);
759    }
760 }
761
762
763 /*
764  * Add a FileIndex to the list of BootStrap records.
765  *  Here we are only dealing with JobId's and the FileIndexes
766  *  associated with those JobIds.
767  */
768 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
769 {
770    RBSR *nbsr;
771    RBSR_FINDEX *fi, *lfi;
772
773    if (findex == 0) {
774       return;                         /* probably a dummy directory */
775    }
776    
777    if (!bsr->fi) {                    /* if no FI add one */
778       /* This is the first FileIndex item in the chain */
779       bsr->fi = new_findex();
780       bsr->JobId = JobId;
781       bsr->fi->findex = findex;
782       bsr->fi->findex2 = findex;
783       return;
784    }
785    /* Walk down list of bsrs until we find the JobId */
786    if (bsr->JobId != JobId) {
787       for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
788          if (nbsr->JobId == JobId) {
789             bsr = nbsr;
790             break;
791          }
792       }
793
794       if (!nbsr) {                    /* Must add new JobId */
795          /* Add new JobId at end of chain */
796          for (nbsr=bsr; nbsr->next; nbsr=nbsr->next) 
797             {  }
798          nbsr->next = new_bsr();
799          nbsr->next->JobId = JobId;
800          nbsr->next->fi = new_findex();
801          nbsr->next->fi->findex = findex;
802          nbsr->next->fi->findex2 = findex;
803          return;
804       }
805    }
806
807    /* 
808     * At this point, bsr points to bsr containing JobId,
809     *  and we are sure that there is at least one fi record.
810     */
811    lfi = fi = bsr->fi;
812    /* Check if this findex is smaller than first item */
813    if (findex < fi->findex) {
814       if ((findex+1) == fi->findex) {
815          fi->findex = findex;         /* extend down */
816          return;
817       }
818       fi = new_findex();              /* yes, insert before first item */
819       fi->findex = findex;
820       fi->findex2 = findex;
821       fi->next = lfi;
822       bsr->fi = fi;
823       return;
824    }
825    /* Walk down fi chain and find where to insert insert new FileIndex */
826    for ( ; fi; fi=fi->next) {
827       if (findex == (fi->findex2 + 1)) {  /* extend up */
828          RBSR_FINDEX *nfi;     
829          fi->findex2 = findex;
830          if (fi->next && ((findex+1) == fi->next->findex)) { 
831             nfi = fi->next;
832             fi->findex2 = nfi->findex2;
833             fi->next = nfi->next;
834             free(nfi);
835          }
836          return;
837       }
838       if (findex < fi->findex) {      /* add before */
839          if ((findex+1) == fi->findex) {
840             fi->findex = findex;
841             return;
842          }
843          break;
844       }
845       lfi = fi;
846    }
847    /* Add to last place found */
848    fi = new_findex();
849    fi->findex = findex;
850    fi->findex2 = findex;
851    fi->next = lfi->next;
852    lfi->next = fi;
853    return;
854 }
855
856 /*
857  * This callback routine is responsible for inserting the
858  *  items it gets into the directory tree. For each JobId selected
859  *  this routine is called once for each file. We do not allow
860  *  duplicate filenames, but instead keep the info from the most
861  *  recent file entered (i.e. the JobIds are assumed to be sorted)
862  */
863 static int insert_tree_handler(void *ctx, int num_fields, char **row)
864 {
865    TREE_CTX *tree = (TREE_CTX *)ctx;
866    char fname[2000];
867    TREE_NODE *node, *new_node;
868    int type;
869
870    strip_trailing_junk(row[1]);
871    if (*row[1] == 0) {
872       if (*row[0] != '/') {           /* Must be Win32 directory */
873          type = TN_DIR_NLS;
874       } else {
875          type = TN_DIR;
876       }
877    } else {
878       type = TN_FILE;
879    }
880    sprintf(fname, "%s%s", row[0], row[1]);
881    if (tree->avail_node) {
882       node = tree->avail_node;
883    } else {
884       node = new_tree_node(tree->root, type);
885       tree->avail_node = node;
886    }
887    Dmsg3(200, "FI=%d type=%d fname=%s\n", node->FileIndex, type, fname);
888    new_node = insert_tree_node(fname, node, tree->root, NULL);
889    /* Note, if node already exists, save new one for next time */
890    if (new_node != node) {
891       tree->avail_node = node;
892    } else {
893       tree->avail_node = NULL;
894    }
895    new_node->FileIndex = atoi(row[2]);
896    new_node->JobId = atoi(row[3]);
897    new_node->type = type;
898    new_node->extract = 1;             /* extract all by default */
899    tree->cnt++;
900    return 0;
901 }
902
903
904 /*
905  * Set extract to value passed. We recursively walk
906  *  down the tree setting all children if the 
907  *  node is a directory.
908  */
909 static void set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, int value)
910 {
911    TREE_NODE *n;
912    FILE_DBR fdbr;
913    struct stat statp;
914
915    node->extract = value;
916    /* For a non-file (i.e. directory), we see all the children */
917    if (node->type != TN_FILE) {
918       for (n=node->child; n; n=n->sibling) {
919          set_extract(ua, n, tree, value);
920       }
921    } else if (value) {
922       char cwd[2000];
923       /* Ordinary file, we get the full path, look up the
924        * attributes, decode them, and if we are hard linked to
925        * a file that was saved, we must load that file too.
926        */
927       tree_getpath(node, cwd, sizeof(cwd));
928       fdbr.FileId = 0;
929       fdbr.JobId = node->JobId;
930       if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
931          uint32_t LinkFI;
932          decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
933          /* If we point to a hard linked file, traverse the tree to
934           * find that file, and mark it for restoration as well.
935           */
936          if (LinkFI) {
937             for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
938                if (n->FileIndex == LinkFI) {
939                   n->extract = 1;
940                   break;
941                }
942             }
943          }
944       }
945    }
946 }
947
948 static int markcmd(UAContext *ua, TREE_CTX *tree)
949 {
950    TREE_NODE *node;
951
952    if (ua->argc < 2)
953       return 1;
954    if (!tree->node->child) {     
955       return 1;
956    }
957    for (node = tree->node->child; node; node=node->sibling) {
958       if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
959          set_extract(ua, node, tree, 1);
960       }
961    }
962    return 1;
963 }
964
965 static int countcmd(UAContext *ua, TREE_CTX *tree)
966 {
967    int total, extract;
968
969    total = extract = 0;
970    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
971       if (node->type != TN_NEWDIR) {
972          total++;
973          if (node->extract) {
974             extract++;
975          }
976       }
977    }
978    bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
979    return 1;
980 }
981
982 static int findcmd(UAContext *ua, TREE_CTX *tree)
983 {
984    char cwd[2000];
985
986    if (ua->argc == 1) {
987       bsendmsg(ua, _("No file specification given.\n"));
988       return 0;
989    }
990    
991    for (int i=1; i < ua->argc; i++) {
992       for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
993          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
994             tree_getpath(node, cwd, sizeof(cwd));
995             bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
996          }
997       }
998    }
999    return 1;
1000 }
1001
1002
1003
1004 static int lscmd(UAContext *ua, TREE_CTX *tree)
1005 {
1006    TREE_NODE *node;
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          bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1014             (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1015       }
1016    }
1017    return 1;
1018 }
1019
1020 extern char *getuser(uid_t uid);
1021 extern char *getgroup(gid_t gid);
1022
1023 /*
1024  * This is actually the long form used for "dir"
1025  */
1026 static void ls_output(char *buf, char *fname, int extract, struct stat *statp)
1027 {
1028    char *p, *f;
1029    char ec1[30];
1030    int n;
1031
1032    p = encode_mode(statp->st_mode, buf);
1033    n = sprintf(p, "  %2d ", (uint32_t)statp->st_nlink);
1034    p += n;
1035    n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
1036    p += n;
1037    n = sprintf(p, "%8.8s  ", edit_uint64(statp->st_size, ec1));
1038    p += n;
1039    p = encode_time(statp->st_ctime, p);
1040    *p++ = ' ';
1041    if (extract) {
1042       *p++ = '*';
1043    } else {
1044       *p++ = ' ';
1045    }
1046    for (f=fname; *f; )
1047       *p++ = *f++;
1048    *p = 0;
1049 }
1050
1051
1052 /*
1053  * Like ls command, but give more detail on each file
1054  */
1055 static int dircmd(UAContext *ua, TREE_CTX *tree)
1056 {
1057    TREE_NODE *node;
1058    FILE_DBR fdbr;
1059    struct stat statp;
1060    char buf[1000];
1061    char cwd[1100];
1062
1063    if (!tree->node->child) {     
1064       return 1;
1065    }
1066    for (node = tree->node->child; node; node=node->sibling) {
1067       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1068          tree_getpath(node, cwd, sizeof(cwd));
1069          fdbr.FileId = 0;
1070          fdbr.JobId = node->JobId;
1071          if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
1072             uint32_t LinkFI;
1073             decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
1074             ls_output(buf, cwd, node->extract, &statp);
1075             bsendmsg(ua, "%s\n", buf);
1076          } else {
1077             /* Something went wrong getting attributes -- print name */
1078             bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1079                (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1080          }
1081       }
1082    }
1083    return 1;
1084 }
1085
1086
1087 static int helpcmd(UAContext *ua, TREE_CTX *tree) 
1088 {
1089    unsigned int i;
1090
1091 /* usage(); */
1092    bsendmsg(ua, _("  Command    Description\n  =======    ===========\n"));
1093    for (i=0; i<comsize; i++) {
1094       bsendmsg(ua, _("  %-10s %s\n"), _(commands[i].key), _(commands[i].help));
1095    }
1096    bsendmsg(ua, "\n");
1097    return 1;
1098 }
1099
1100 /*
1101  * Change directories.  Note, if the user specifies x: and it fails,
1102  *   we assume it is a Win32 absolute cd rather than relative and
1103  *   try a second time with /x: ...  Win32 kludge.
1104  */
1105 static int cdcmd(UAContext *ua, TREE_CTX *tree) 
1106 {
1107    TREE_NODE *node;
1108    char cwd[2000];
1109
1110    if (ua->argc != 2) {
1111       return 1;
1112    }
1113    node = tree_cwd(ua->argk[1], tree->root, tree->node);
1114    if (!node) {
1115       /* Try once more if Win32 drive -- make absolute */
1116       if (ua->argk[1][1] == ':') {  /* win32 drive */
1117          strcpy(cwd, "/");
1118          strcat(cwd, ua->argk[1]);
1119          node = tree_cwd(cwd, tree->root, tree->node);
1120       }
1121       if (!node) {
1122          bsendmsg(ua, _("Invalid path given.\n"));
1123       } else {
1124          tree->node = node;
1125       }
1126    } else {
1127       tree->node = node;
1128    }
1129    tree_getpath(tree->node, cwd, sizeof(cwd));
1130    bsendmsg(ua, _("cwd is: %s\n"), cwd);
1131    return 1;
1132 }
1133
1134 static int pwdcmd(UAContext *ua, TREE_CTX *tree) 
1135 {
1136    char cwd[2000];
1137    tree_getpath(tree->node, cwd, sizeof(cwd));
1138    bsendmsg(ua, _("cwd is: %s\n"), cwd);
1139    return 1;
1140 }
1141
1142
1143 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
1144 {
1145    TREE_NODE *node;
1146
1147    if (ua->argc < 2)
1148       return 1;
1149    if (!tree->node->child) {     
1150       return 1;
1151    }
1152    for (node = tree->node->child; node; node=node->sibling) {
1153       if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1154          set_extract(ua, node, tree, 0);
1155       }
1156    }
1157    return 1;
1158 }
1159
1160 static int quitcmd(UAContext *ua, TREE_CTX *tree) 
1161 {
1162    return 0;
1163 }
1164
1165
1166 /*
1167  * Called here with each name to be added to the list. The name is
1168  *   added to the list if it is not already in the list.
1169  */
1170 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1171 {
1172    NAME_LIST *name = (NAME_LIST *)ctx;
1173
1174    if (name->num_ids == MAX_ID_LIST_LEN) {  
1175       return 1;
1176    }
1177    if (name->num_ids == name->max_ids) {
1178       if (name->max_ids == 0) {
1179          name->max_ids = 1000;
1180          name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1181       } else {
1182          name->max_ids = (name->max_ids * 3) / 2;
1183          name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1184       }
1185    }
1186    for (int i=0; i<name->num_ids; i++) {
1187       if (strcmp(name->name[i], row[0]) == 0) {
1188          return 0;                    /* already in list, return */
1189       }
1190    }
1191    /* Add new name to list */
1192    name->name[name->num_ids++] = bstrdup(row[0]);
1193    return 0;
1194 }
1195
1196
1197 /*
1198  * Print names in the list
1199  */
1200 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1201
1202    int i;
1203
1204    for (i=0; i < name_list->num_ids; i++) {
1205       bsendmsg(ua, "%s\n", name_list->name[i]);
1206    }
1207 }
1208
1209
1210 /*
1211  * Free names in the list
1212  */
1213 static void free_name_list(NAME_LIST *name_list)
1214
1215    int i;
1216
1217    for (i=0; i < name_list->num_ids; i++) {
1218       free(name_list->name[i]);
1219    }
1220    if (name_list->name) {
1221       free(name_list->name);
1222    }
1223    name_list->max_ids = 0;
1224    name_list->num_ids = 0;
1225 }
1226
1227 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JobIds *ji)
1228 {
1229    char name[MAX_NAME_LENGTH];
1230    STORE *store = NULL;
1231
1232    if (name_list->num_ids > 1) {
1233       bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1234          "Restore is not possible. The MediaTypes used are:\n"));
1235       print_name_list(ua, name_list);
1236       ji->store = select_storage_resource(ua);
1237       return;
1238    }
1239
1240    if (name_list->num_ids == 0) {
1241       bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1242       ji->store = select_storage_resource(ua);
1243       return;
1244    }
1245
1246    start_prompt(ua, _("The defined Storage resources are:\n"));
1247    LockRes();
1248    while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
1249       if (strcmp(store->media_type, name_list->name[0]) == 0) {
1250          add_prompt(ua, store->hdr.name);
1251       }
1252    }
1253    UnlockRes();
1254    do_prompt(ua, _("Select Storage resource"), name, sizeof(name));
1255    ji->store = (STORE *)GetResWithName(R_STORAGE, name);
1256    if (!ji->store) {
1257       bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1258          "MediaType %s, needed by the Jobs you selected.\n"
1259          "You will be allowed to select a Storage device later.\n"),
1260          name_list->name[0]); 
1261    }
1262 }