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