]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
First cut of restore
[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
36
37
38 /* Imported functions */
39 extern char *uar_list_jobs;
40 extern char *uar_file;
41 extern char *uar_sel_files;
42
43 /* Context for insert_tree_handler() */
44 typedef struct s_tree_ctx {
45    TREE_ROOT *root;                   /* root */
46    TREE_NODE *node;                   /* current node */
47    TREE_NODE *avail_node;             /* unused node last insert */
48    int cnt;                           /* count for user feedback */
49    UAContext *ua;
50 } TREE_CTX;
51
52
53 /* FileIndex entry in bootstrap record */
54 typedef struct s_rbsr_findex {
55    struct s_rbsr_findex *next;
56    int32_t findex;
57    int32_t findex2;
58 } RBSR_FINDEX;
59
60 /* Restore bootstrap record -- not the real one, but useful here */
61 typedef struct s_rbsr {
62    struct s_rbsr *next;               /* next JobId */
63    uint32_t JobId;                    /* JobId this bsr */
64    uint32_t VolSessionId;                   
65    uint32_t VolSessionTime;
66    char *VolumeName;                  /* Volume name */
67    RBSR_FINDEX *fi;                   /* File indexes this JobId */
68 } RBSR;
69
70 /* Forward referenced functions */
71 static RBSR *new_bsr();
72 static void free_bsr(RBSR *bsr);
73 static void print_bsr(UAContext *ua, RBSR *bsr);
74 static int  complete_bsr(UAContext *ua, RBSR *bsr);
75 static int insert_tree_handler(void *ctx, int num_fields, char **row);
76 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex);
77 static void user_select_files(TREE_CTX *tree);
78
79
80
81 /*
82  *   Restore files
83  *
84  */
85 int restorecmd(UAContext *ua, char *cmd)
86 {
87    POOLMEM *query;
88    int JobId, done = 0;
89    TREE_CTX tree;
90    RBSR *bsr;
91    char *nofname = "";
92    JOB_DBR jr;
93    char *list[] = { 
94       "List last Jobs run",
95       "Enter list of JobIds",
96       "Enter SQL list command", 
97       "Select a File",
98       "Cancel",
99       NULL };
100
101    if (!open_db(ua)) {
102       return 0;
103    }
104
105    memset(&tree, 0, sizeof(TREE_CTX));
106
107    for ( ; !done; ) {
108       start_prompt(ua, _("To narrow down the restore, you have the following choices:\n"));
109       for (int i=0; list[i]; i++) {
110          add_prompt(ua, list[i]);
111       }
112       done = 1;
113       switch (do_prompt(ua, "Select item: ", NULL)) {
114       case -1:
115          return 0;
116       case 0:
117          db_list_sql_query(ua->db, uar_list_jobs, prtit, ua, 1);
118          if (!get_cmd(ua, _("Enter JobId to select files for restore: "))) {
119             return 0;
120          }
121          if (!is_a_number(ua->cmd)) {
122            bsendmsg(ua, _("Bad JobId entered.\n"));
123            return 0;
124          }
125          JobId = atoi(ua->cmd);
126          break;
127
128       case 1:
129          if (!get_cmd(ua, _("Enter JobIds: "))) {
130             return 0;
131          }
132          JobId = atoi(ua->cmd);
133          break;
134       case 2:
135          if (!get_cmd(ua, _("Enter SQL list command: "))) {
136             return 0;
137          }
138          db_list_sql_query(ua->db, ua->cmd, prtit, ua, 1);
139          done = 0;
140          break;
141       case 3:
142          if (!get_cmd(ua, _("Enter Filename: "))) {
143             return 0;
144          }
145          query = get_pool_memory(PM_MESSAGE);
146          Mmsg(&query, uar_file, ua->cmd);
147          db_list_sql_query(ua->db, query, prtit, ua, 1);
148          free_pool_memory(query);
149          if (!get_cmd(ua, _("Enter JobId to select files for restore: "))) {
150             return 0;
151          }
152          if (!is_a_number(ua->cmd)) {
153            bsendmsg(ua, _("Bad JobId entered.\n"));
154            return 0;
155          }
156          JobId = atoi(ua->cmd);
157          break;
158       case 4:
159          return 0;
160       }
161    }
162
163    memset(&jr, 0, sizeof(JOB_DBR));
164    jr.JobId = JobId;
165    if (!db_get_job_record(ua->db, &jr)) {
166       bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
167       return 0;
168    }
169
170    /* 
171     * Build the directory tree  
172     */
173    bsendmsg(ua, _("Building directory tree of backed up files ...\n"));
174    memset(&tree, 0, sizeof(tree));
175    tree.root = new_tree(jr.JobFiles);
176    tree.root->fname = nofname;
177    tree.ua = ua;
178    query = get_pool_memory(PM_MESSAGE);
179    Mmsg(&query, uar_sel_files, JobId);
180    if (!db_sql_query(ua->db, query, insert_tree_handler, (void *)&tree)) {
181       bsendmsg(ua, "%s", db_strerror(ua->db));
182    }
183    free_pool_memory(query);
184
185    /* Let the user select which files to restore */
186    user_select_files(&tree);
187
188    /*
189     * Walk down through the tree finding all files marked to be 
190     *  extracted making a bootstrap file.
191     */
192    bsr = new_bsr();
193    for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
194       Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
195       if (node->extract) {
196          Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
197          add_findex(bsr, node->JobId, node->FileIndex);
198       }
199    }
200
201    free_tree(tree.root);              /* free the directory tree */
202
203    if (bsr->JobId) {
204       complete_bsr(ua, bsr);
205       print_bsr(ua, bsr);
206    } else {
207       bsendmsg(ua, _("No files selected to restore.\n"));
208    }
209    free_bsr(bsr);
210
211    bsendmsg(ua, _("Restore command done.\n"));
212    return 1;
213 }
214
215
216
217 /* Forward referenced commands */
218
219 static int addcmd(UAContext *ua, TREE_CTX *tree);
220 static int lscmd(UAContext *ua, TREE_CTX *tree);
221 static int helpcmd(UAContext *ua, TREE_CTX *tree);
222 static int cdcmd(UAContext *ua, TREE_CTX *tree);
223 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
224 static int rmcmd(UAContext *ua, TREE_CTX *tree);
225 static int quitcmd(UAContext *ua, TREE_CTX *tree);
226
227
228 struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; }; 
229 static struct cmdstruct commands[] = {
230  { N_("add"),        addcmd,       _("add file")},
231  { N_("ls"),         lscmd,        _("list current directory")},    
232  { N_("dir"),        lscmd,        _("list current directory")},    
233  { N_("help"),       helpcmd,      _("print help")},
234  { N_("cd"),         cdcmd,        _("change directory")},
235  { N_("pwd"),        pwdcmd,       _("print directory")},
236  { N_("rm"),         rmcmd,        _("remove a file")},
237  { N_("remove"),     rmcmd,        _("remove a file")},
238  { N_("done"),       quitcmd,      _("quit")},
239  { N_("exit"),       quitcmd,      _("exit = quit")},
240  { N_("?"),          helpcmd,      _("print help")},    
241              };
242 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
243
244
245 /*
246  * Enter a prompt mode where the user can select/deselect
247  *  files to be restored. This is sort of like a mini-shell
248  *  that allows "cd", "pwd", "add", "rm", ...
249  */
250 static void user_select_files(TREE_CTX *tree)
251 {
252    char cwd[2000];
253    /*
254     * Enter interactive command handler allowing selection
255     *  of individual files.
256     */
257    tree->node = (TREE_NODE *)tree->root;
258    tree_getpath(tree->node, cwd, sizeof(cwd));
259    bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
260    for ( ;; ) {       
261       int found, len, stat, i;
262       if (!get_cmd(tree->ua, "$ ")) {
263          break;
264       }
265       parse_command_args(tree->ua);
266       if (tree->ua->argc == 0) {
267          return;
268       }
269
270       len = strlen(tree->ua->argk[0]);
271       found = 0;
272       for (i=0; i<(int)comsize; i++)       /* search for command */
273          if (strncasecmp(tree->ua->argk[0],  _(commands[i].key), len) == 0) {
274             stat = (*commands[i].func)(tree->ua, tree);   /* go execute command */
275             found = 1;
276             break;
277          }
278       if (!found) {
279          bsendmsg(tree->ua, _("Illegal command\n"));
280       }
281       if (!stat) {
282          break;
283       }
284    }
285 }
286
287
288 static RBSR_FINDEX *new_findex() 
289 {
290    RBSR_FINDEX *fi = (RBSR_FINDEX *)malloc(sizeof(RBSR_FINDEX));
291    memset(fi, 0, sizeof(RBSR_FINDEX));
292    return fi;
293 }
294
295 static void free_findex(RBSR_FINDEX *fi)
296 {
297    if (fi) {
298       free_findex(fi->next);
299       free(fi);
300    }
301 }
302
303 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
304 {
305    if (fi) {
306       if (fi->findex == fi->findex2) {
307          bsendmsg(ua, "FileIndex=%d\n", fi->findex);
308       } else {
309          bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
310       }
311       print_findex(ua, fi->next);
312    }
313 }
314
315 static RBSR *new_bsr()
316 {
317    RBSR *bsr = (RBSR *)malloc(sizeof(RBSR));
318    memset(bsr, 0, sizeof(RBSR));
319    return bsr;
320 }
321
322 static void free_bsr(RBSR *bsr)
323 {
324    if (bsr) {
325       free_findex(bsr->fi);
326       free_bsr(bsr->next);
327       if (bsr->VolumeName) {
328          free(bsr->VolumeName);
329       }
330       free(bsr);
331    }
332 }
333
334 /*
335  * Complete the BSR by filling in the VolumeName and
336  *  VolSessionId and VolSessionTime
337  */
338 static int complete_bsr(UAContext *ua, RBSR *bsr)
339 {
340    JOB_DBR jr;
341    char VolumeNames[1000];            /* ****FIXME**** */
342
343    if (bsr) {
344       memset(&jr, 0, sizeof(jr));
345       jr.JobId = bsr->JobId;
346       if (!db_get_job_record(ua->db, &jr)) {
347          bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
348          return 0;
349       }
350       bsr->VolSessionId = jr.VolSessionId;
351       bsr->VolSessionTime = jr.VolSessionTime;
352       if (!db_get_job_volume_names(ua->db, bsr->JobId, VolumeNames)) {
353          bsendmsg(ua, _("Unable to get Job Volumes. ERR=%s\n"), db_strerror(ua->db));
354          return 0;
355       }
356       bsr->VolumeName = bstrdup(VolumeNames);
357       return complete_bsr(ua, bsr->next);
358    }
359    return 1;
360 }
361
362
363 static void print_bsr(UAContext *ua, RBSR *bsr)
364 {
365    if (bsr) {
366       if (bsr->VolumeName) {
367          bsendmsg(ua, "VolumeName=%s\n", bsr->VolumeName);
368       }
369 //    bsendmsg(ua, "JobId=%u\n", bsr->JobId);
370       bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
371       bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
372       print_findex(ua, bsr->fi);
373       print_bsr(ua, bsr->next);
374    }
375 }
376
377
378 /*
379  * Add a FileIndex to the list of BootStrap records.
380  *  Here we are only dealing with JobId's and the FileIndexes
381  *  associated with those JobIds.
382  */
383 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
384 {
385    RBSR *nbsr;
386    RBSR_FINDEX *fi, *lfi;
387
388    if (findex == 0) {
389       return;                         /* probably a dummy directory */
390    }
391
392    if (!bsr->fi) {                    /* if no FI add one */
393       /* This is the first FileIndex item in the chain */
394       bsr->fi = new_findex();
395       bsr->JobId = JobId;
396       bsr->fi->findex = findex;
397       bsr->fi->findex2 = findex;
398       return;
399    }
400    /* Walk down list of bsrs until we find the JobId */
401    if (bsr->JobId != JobId) {
402       for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
403          if (nbsr->JobId == JobId) {
404             bsr = nbsr;
405             break;
406          }
407       }
408
409       if (!nbsr) {                    /* Must add new JobId */
410          /* Add new JobId at end of chain */
411          for (nbsr=bsr; nbsr->next; nbsr=nbsr->next) 
412             {  }
413          nbsr->next = new_bsr();
414          nbsr->next->JobId = JobId;
415          nbsr->next->fi = new_findex();
416          nbsr->next->fi->findex = findex;
417          nbsr->next->fi->findex2 = findex;
418          return;
419       }
420    }
421
422    /* 
423     * At this point, bsr points to bsr containing JobId,
424     *  and we are sure that there is at least one fi record.
425     */
426    lfi = fi = bsr->fi;
427    /* Check if this findex is smaller than first item */
428    if (findex < fi->findex) {
429       if ((findex+1) == fi->findex) {
430          fi->findex = findex;         /* extend down */
431          return;
432       }
433       fi = new_findex();              /* yes, insert before first item */
434       fi->findex = findex;
435       fi->findex2 = findex;
436       fi->next = lfi;
437       bsr->fi = fi;
438       return;
439    }
440    /* Walk down fi chain and find where to insert insert new FileIndex */
441    for ( ; fi; fi=fi->next) {
442       if (findex == (fi->findex2 + 1)) {  /* extend up */
443          RBSR_FINDEX *nfi;     
444          fi->findex2 = findex;
445          if (fi->next && ((findex+1) == fi->next->findex)) { 
446             Dmsg1(400, "Coallase %d\n", findex);
447             nfi = fi->next;
448             fi->findex2 = nfi->findex2;
449             fi->next = nfi->next;
450             free(nfi);
451          }
452          return;
453       }
454       if (findex < fi->findex) {      /* add before */
455          if ((findex+1) == fi->findex) {
456             fi->findex = findex;
457             return;
458          }
459          break;
460       }
461       lfi = fi;
462    }
463    /* Add to last place found */
464    fi = new_findex();
465    fi->findex = findex;
466    fi->findex2 = findex;
467    fi->next = lfi->next;
468    lfi->next = fi;
469    return;
470 }
471
472 static int insert_tree_handler(void *ctx, int num_fields, char **row)
473 {
474    TREE_CTX *tree = (TREE_CTX *)ctx;
475    char fname[2000];
476    TREE_NODE *node, *new_node;
477    int type;
478
479    strip_trailing_junk(row[1]);
480    if (*row[1] == 0) {
481       type = TN_DIR;
482    } else {
483       type = TN_FILE;
484    }
485    sprintf(fname, "%s%s", row[0], row[1]);
486    if (tree->avail_node) {
487       node = tree->avail_node;
488    } else {
489       node = new_tree_node(tree->root, type);
490       tree->avail_node = node;
491    }
492    Dmsg2(400, "FI=%d fname=%s\n", node->FileIndex, fname);
493    new_node = insert_tree_node(fname, node, tree->root, NULL);
494    /* Note, if node already exists, save new one for next time */
495    if (new_node != node) {
496       tree->avail_node = node;
497    } else {
498       tree->avail_node = NULL;
499    }
500    new_node->FileIndex = atoi(row[2]);
501    new_node->JobId = atoi(row[3]);
502    new_node->type = type;
503    if (((tree->cnt) % 10000) == 0) {
504       bsendmsg(tree->ua, "%d ", tree->cnt);
505    }
506    tree->cnt++;
507    return 0;
508 }
509
510
511 /*
512  * Set extract to value passed. We recursively walk
513  *  down the tree setting all children.
514  */
515 static void set_extract(TREE_NODE *node, int value)
516 {
517    TREE_NODE *n;
518
519    node->extract = value;
520    if (node->type != TN_FILE) {
521       for (n=node->child; n; n=n->sibling) {
522          set_extract(n, value);
523       }
524    }
525 }
526
527 static int addcmd(UAContext *ua, TREE_CTX *tree)
528 {
529    TREE_NODE *node;
530
531    if (ua->argc < 2)
532       return 1;
533    if (!tree->node->child) {     
534       return 1;
535    }
536    for (node = tree->node->child; node; node=node->sibling) {
537       if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
538          set_extract(node, 1);
539       }
540    }
541    return 1;
542 }
543
544 static int lscmd(UAContext *ua, TREE_CTX *tree)
545 {
546    TREE_NODE *node;
547
548    if (!tree->node->child) {     
549       return 1;
550    }
551    for (node = tree->node->child; node; node=node->sibling) {
552       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
553          bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
554             (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
555       }
556    }
557    return 1;
558 }
559
560 static int helpcmd(UAContext *ua, TREE_CTX *tree) 
561 {
562    unsigned int i;
563
564 /* usage(); */
565    bsendmsg(ua, _("  Command    Description\n  =======    ===========\n"));
566    for (i=0; i<comsize; i++) {
567       bsendmsg(ua, _("  %-10s %s\n"), _(commands[i].key), _(commands[i].help));
568    }
569    bsendmsg(ua, "\n");
570    return 1;
571 }
572
573 static int cdcmd(UAContext *ua, TREE_CTX *tree) 
574 {
575    char cwd[2000];
576    if (ua->argc != 2) {
577       return 1;
578    }
579    tree->node = tree_cwd(ua->argk[1], tree->root, tree->node);
580    tree_getpath(tree->node, cwd, sizeof(cwd));
581    bsendmsg(ua, _("cwd is: %s\n"), cwd);
582    return 1;
583 }
584
585 static int pwdcmd(UAContext *ua, TREE_CTX *tree) 
586 {
587    char cwd[2000];
588    tree_getpath(tree->node, cwd, sizeof(cwd));
589    bsendmsg(ua, _("cwd is: %s\n"), cwd);
590    return 1;
591 }
592
593
594 static int rmcmd(UAContext *ua, TREE_CTX *tree)
595 {
596    TREE_NODE *node;
597
598    if (ua->argc < 2)
599       return 1;
600    if (!tree->node->child) {     
601       return 1;
602    }
603    for (node = tree->node->child; node; node=node->sibling) {
604       if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
605          set_extract(node, 0);
606       }
607    }
608    return 1;
609 }
610
611 static int quitcmd(UAContext *ua, TREE_CTX *tree) 
612 {
613    return 0;
614 }