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