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