3 * Bacula Director -- User Agent Database restore Command
4 * Creates a bootstrap file for restoring files
6 * Kern Sibbald, July MMII
12 Copyright (C) 2002 Kern Sibbald and John Walker
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.
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.
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,
38 /* Imported functions */
39 extern int runcmd(UAContext *ua, char *cmd);
41 /* Imported variables */
42 extern char *uar_list_jobs;
43 extern char *uar_file;
44 extern char *uar_sel_files;
45 extern char *uar_del_temp;
46 extern char *uar_del_temp1;
47 extern char *uar_create_temp;
48 extern char *uar_create_temp1;
49 extern char *uar_last_full;
50 extern char *uar_full;
52 extern char *uar_list_temp;
53 extern char *uar_sel_jobid_temp;
54 extern char *uar_sel_all_temp1;
56 /* Context for insert_tree_handler() */
57 typedef struct s_tree_ctx {
58 TREE_ROOT *root; /* root */
59 TREE_NODE *node; /* current node */
60 TREE_NODE *avail_node; /* unused node last insert */
61 int cnt; /* count for user feedback */
65 typedef struct s_jobids {
74 /* FileIndex entry in bootstrap record */
75 typedef struct s_rbsr_findex {
76 struct s_rbsr_findex *next;
81 /* Restore bootstrap record -- not the real one, but useful here */
82 typedef struct s_rbsr {
83 struct s_rbsr *next; /* next JobId */
84 uint32_t JobId; /* JobId this bsr */
85 uint32_t VolSessionId;
86 uint32_t VolSessionTime;
87 char *VolumeName; /* Volume name */
88 RBSR_FINDEX *fi; /* File indexes this JobId */
91 /* Forward referenced functions */
92 static RBSR *new_bsr();
93 static void free_bsr(RBSR *bsr);
94 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd);
95 static int write_bsr_file(UAContext *ua, RBSR *bsr);
96 static void print_bsr(UAContext *ua, RBSR *bsr);
97 static int complete_bsr(UAContext *ua, RBSR *bsr);
98 static int insert_tree_handler(void *ctx, int num_fields, char **row);
99 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex);
100 static int last_full_handler(void *ctx, int num_fields, char **row);
101 static int jobid_handler(void *ctx, int num_fields, char **row);
102 static int next_jobid_from_list(char **p, uint32_t *JobId);
103 static int user_select_jobids(UAContext *ua, JobIds *ji);
104 static void user_select_files(TREE_CTX *tree);
111 int restorecmd(UAContext *ua, char *cmd)
115 JobId_t JobId, last_JobId;
121 JOB *restore_job = NULL;
122 int restore_jobs = 0;
128 memset(&tree, 0, sizeof(TREE_CTX));
129 memset(&ji, 0, sizeof(ji));
131 /* Ensure there is at least one Restore Job */
133 while ( (job = (JOB *)GetNextRes(R_JOB, (RES *)job)) ) {
134 if (job->JobType == JT_RESTORE) {
144 "No Restore Job Resource found. You must create at least\n"
145 "one before running this command.\n"));
150 if (!user_select_jobids(ua, &ji)) {
155 * Build the directory tree
157 tree.root = new_tree(ji.TotalFiles);
158 tree.root->fname = nofname;
160 query = get_pool_memory(PM_MESSAGE);
163 * For display purposes, the same JobId, with different volumes may
164 * appear more than once, however, we only insert it once.
166 for (p=ji.JobIds; next_jobid_from_list(&p, &JobId) > 0; ) {
167 if (JobId == last_JobId) {
168 continue; /* eliminate duplicate JobIds */
171 bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
172 Mmsg(&query, uar_sel_files, JobId);
173 if (!db_sql_query(ua->db, query, insert_tree_handler, (void *)&tree)) {
174 bsendmsg(ua, "%s", db_strerror(ua->db));
178 free_pool_memory(query);
180 /* Let the user select which files to restore */
181 user_select_files(&tree);
184 * Walk down through the tree finding all files marked to be
185 * extracted making a bootstrap file.
188 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
189 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
191 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
192 add_findex(bsr, node->JobId, node->FileIndex);
196 free_tree(tree.root); /* free the directory tree */
199 complete_bsr(ua, bsr); /* find Vol, SessId, SessTime from JobIds */
201 write_bsr_file(ua, bsr);
203 bsendmsg(ua, _("No files selected to restore.\n"));
207 if (restore_jobs == 1) {
210 job = select_restore_job_resource(ua);
213 bsendmsg(ua, _("No Restore Job resource found!\n"));
218 Mmsg(&ua->cmd, "run job=%s client=%s bootstrap=%s/restore.bsr",
219 job->hdr.name, ji.client->hdr.name, working_directory);
221 Mmsg(&ua->cmd, "run job=%s bootstrap=%s/restore.bsr",
222 job->hdr.name, working_directory);
225 Dmsg1(400, "Submitting: %s\n", ua->cmd);
227 parse_command_args(ua);
230 bsendmsg(ua, _("Restore command done.\n"));
235 * The first step in the restore process is for the user to
236 * select a list of JobIds from which he will subsequently
237 * select which files are to be restored.
239 static int user_select_jobids(UAContext *ua, JobIds *ji)
247 "List last 20 Jobs run",
248 "List Jobs where a given File is saved",
249 "Enter list of JobIds to select",
250 "Enter SQL list command",
251 "Select the most recent backup for a client",
255 bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
256 "to be restored. You will be presented several methods\n"
257 "of specifying the JobIds. Then you will be allowed to\n"
258 "select which files from those JobIds are to be restored.\n\n"));
261 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
262 for (int i=0; list[i]; i++) {
263 add_prompt(ua, list[i]);
266 switch (do_prompt(ua, "Select item: ", NULL)) {
269 case 0: /* list last 20 Jobs run */
270 db_list_sql_query(ua->db, uar_list_jobs, prtit, ua, 1);
273 case 1: /* list where a file is saved */
274 if (!get_cmd(ua, _("Enter Filename: "))) {
277 query = get_pool_memory(PM_MESSAGE);
278 Mmsg(&query, uar_file, ua->cmd);
279 db_list_sql_query(ua->db, query, prtit, ua, 1);
280 free_pool_memory(query);
283 case 2: /* enter a list of JobIds */
284 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
287 bstrncpy(ji->JobIds, ua->cmd, sizeof(ji->JobIds));
289 case 3: /* Enter an SQL list command */
290 if (!get_cmd(ua, _("Enter SQL list command: "))) {
293 db_list_sql_query(ua->db, ua->cmd, prtit, ua, 1);
296 case 4: /* Select the most recent backups */
297 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
298 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
299 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
300 bsendmsg(ua, "%s\n", db_strerror(ua->db));
302 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
303 bsendmsg(ua, "%s\n", db_strerror(ua->db));
305 if (!(ji->client = get_client_resource(ua))) {
308 query = get_pool_memory(PM_MESSAGE);
309 Mmsg(&query, uar_last_full, ji->client->hdr.name);
310 /* Find JobId of full Backup of system */
311 if (!db_sql_query(ua->db, query, NULL, NULL)) {
312 bsendmsg(ua, "%s\n", db_strerror(ua->db));
314 /* Find all Volumes used by that JobId */
315 if (!db_sql_query(ua->db, uar_full, NULL,NULL)) {
316 bsendmsg(ua, "%s\n", db_strerror(ua->db));
318 /* Note, this is needed as I don't seem to get the callback
319 * from the call just above.
321 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)ji)) {
322 bsendmsg(ua, "%s\n", db_strerror(ua->db));
324 /* Now find all Incremental Jobs */
325 Mmsg(&query, uar_inc, (uint32_t)ji->JobTDate, ji->ClientId);
326 if (!db_sql_query(ua->db, query, NULL, NULL)) {
327 bsendmsg(ua, "%s\n", db_strerror(ua->db));
329 free_pool_memory(query);
330 db_list_sql_query(ua->db, uar_list_temp, prtit, ua, 1);
332 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)ji)) {
333 bsendmsg(ua, "%s\n", db_strerror(ua->db));
335 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
336 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
343 if (*ji->JobIds == 0) {
344 bsendmsg(ua, _("No Jobs selected.\n"));
347 bsendmsg(ua, _("You have selected the following JobId: %s\n"), ji->JobIds);
349 memset(&jr, 0, sizeof(JOB_DBR));
352 for (p=ji->JobIds; ; ) {
353 int stat = next_jobid_from_list(&p, &JobId);
355 bsendmsg(ua, _("Invalid JobId in list.\n"));
362 if (!db_get_job_record(ua->db, &jr)) {
363 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
366 ji->TotalFiles += jr.JobFiles;
371 static int next_jobid_from_list(char **p, uint32_t *JobId)
378 for (i=0; i<(int)sizeof(jobid); i++) {
379 if (*q == ',' || *q == 0) {
386 if (jobid[0] == 0 || !is_a_number(jobid)) {
390 *JobId = strtoul(jobid, NULL, 10);
395 * Callback handler make list of JobIds
397 static int jobid_handler(void *ctx, int num_fields, char **row)
399 JobIds *ji = (JobIds *)ctx;
401 if (strlen(ji->JobIds)+strlen(row[0])+2 < sizeof(ji->JobIds)) {
402 if (ji->JobIds[0] != 0) {
403 strcat(ji->JobIds, ",");
405 strcat(ji->JobIds, row[0]);
413 * Callback handler to pickup last Full backup JobId and ClientId
415 static int last_full_handler(void *ctx, int num_fields, char **row)
417 JobIds *ji = (JobIds *)ctx;
419 ji->JobTDate = atoi(row[1]);
420 ji->ClientId = atoi(row[2]);
428 /* Forward referenced commands */
430 static int addcmd(UAContext *ua, TREE_CTX *tree);
431 static int countcmd(UAContext *ua, TREE_CTX *tree);
432 static int findcmd(UAContext *ua, TREE_CTX *tree);
433 static int lscmd(UAContext *ua, TREE_CTX *tree);
434 static int helpcmd(UAContext *ua, TREE_CTX *tree);
435 static int cdcmd(UAContext *ua, TREE_CTX *tree);
436 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
437 static int rmcmd(UAContext *ua, TREE_CTX *tree);
438 static int quitcmd(UAContext *ua, TREE_CTX *tree);
441 struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; };
442 static struct cmdstruct commands[] = {
443 { N_("add"), addcmd, _("add file")},
444 { N_("count"), countcmd, _("count files")},
445 { N_("find"), findcmd, _("find files")},
446 { N_("ls"), lscmd, _("list current directory")},
447 { N_("dir"), lscmd, _("list current directory")},
448 { N_("help"), helpcmd, _("print help")},
449 { N_("cd"), cdcmd, _("change directory")},
450 { N_("pwd"), pwdcmd, _("print directory")},
451 { N_("rm"), rmcmd, _("remove a file")},
452 { N_("remove"), rmcmd, _("remove a file")},
453 { N_("done"), quitcmd, _("leave file selection mode")},
454 { N_("exit"), quitcmd, _("exit = done")},
455 { N_("?"), helpcmd, _("print help")},
457 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
461 * Enter a prompt mode where the user can select/deselect
462 * files to be restored. This is sort of like a mini-shell
463 * that allows "cd", "pwd", "add", "rm", ...
465 static void user_select_files(TREE_CTX *tree)
469 bsendmsg(tree->ua, _(
470 "You are now entering file selection mode where you add and\n"
471 "remove files to be restored. All files are initially added.\n"
472 "Enter done to leave this mode.\n\n"));
474 * Enter interactive command handler allowing selection
475 * of individual files.
477 tree->node = (TREE_NODE *)tree->root;
478 tree_getpath(tree->node, cwd, sizeof(cwd));
479 bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
481 int found, len, stat, i;
482 if (!get_cmd(tree->ua, "$ ")) {
485 parse_command_args(tree->ua);
486 if (tree->ua->argc == 0) {
490 len = strlen(tree->ua->argk[0]);
492 for (i=0; i<(int)comsize; i++) /* search for command */
493 if (strncasecmp(tree->ua->argk[0], _(commands[i].key), len) == 0) {
494 stat = (*commands[i].func)(tree->ua, tree); /* go execute command */
499 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
508 * Create new FileIndex entry for BSR
510 static RBSR_FINDEX *new_findex()
512 RBSR_FINDEX *fi = (RBSR_FINDEX *)malloc(sizeof(RBSR_FINDEX));
513 memset(fi, 0, sizeof(RBSR_FINDEX));
517 /* Free all BSR FileIndex entries */
518 static void free_findex(RBSR_FINDEX *fi)
521 free_findex(fi->next);
526 static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd)
529 if (fi->findex == fi->findex2) {
530 fprintf(fd, "FileIndex=%d\n", fi->findex);
532 fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
534 write_findex(ua, fi->next, fd);
539 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
542 if (fi->findex == fi->findex2) {
543 bsendmsg(ua, "FileIndex=%d\n", fi->findex);
545 bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
547 print_findex(ua, fi->next);
551 /* Create a new bootstrap record */
552 static RBSR *new_bsr()
554 RBSR *bsr = (RBSR *)malloc(sizeof(RBSR));
555 memset(bsr, 0, sizeof(RBSR));
559 /* Free the entire BSR */
560 static void free_bsr(RBSR *bsr)
563 free_findex(bsr->fi);
565 if (bsr->VolumeName) {
566 free(bsr->VolumeName);
573 * Complete the BSR by filling in the VolumeName and
574 * VolSessionId and VolSessionTime using the JobId
576 static int complete_bsr(UAContext *ua, RBSR *bsr)
579 char VolumeNames[1000]; /* ****FIXME**** */
582 memset(&jr, 0, sizeof(jr));
583 jr.JobId = bsr->JobId;
584 if (!db_get_job_record(ua->db, &jr)) {
585 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
588 bsr->VolSessionId = jr.VolSessionId;
589 bsr->VolSessionTime = jr.VolSessionTime;
590 if (!db_get_job_volume_names(ua->db, bsr->JobId, VolumeNames)) {
591 bsendmsg(ua, _("Unable to get Job Volumes. ERR=%s\n"), db_strerror(ua->db));
594 bsr->VolumeName = bstrdup(VolumeNames);
595 return complete_bsr(ua, bsr->next);
601 * Write the bootstrap record to file
603 static int write_bsr_file(UAContext *ua, RBSR *bsr)
606 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
609 Mmsg(&fname, "%s/restore.bsr", working_directory);
610 fd = fopen(fname, "w+");
612 bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"),
613 fname, strerror(errno));
614 free_pool_memory(fname);
617 write_bsr(ua, bsr, fd);
620 bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
621 free_pool_memory(fname);
625 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
628 if (bsr->VolumeName) {
629 fprintf(fd, "VolumeName=%s\n", bsr->VolumeName);
631 fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
632 fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
633 write_findex(ua, bsr->fi, fd);
634 write_bsr(ua, bsr->next, fd);
638 static void print_bsr(UAContext *ua, RBSR *bsr)
641 if (bsr->VolumeName) {
642 bsendmsg(ua, "VolumeName=%s\n", bsr->VolumeName);
644 bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
645 bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
646 print_findex(ua, bsr->fi);
647 print_bsr(ua, bsr->next);
653 * Add a FileIndex to the list of BootStrap records.
654 * Here we are only dealing with JobId's and the FileIndexes
655 * associated with those JobIds.
657 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
660 RBSR_FINDEX *fi, *lfi;
663 return; /* probably a dummy directory */
666 if (!bsr->fi) { /* if no FI add one */
667 /* This is the first FileIndex item in the chain */
668 bsr->fi = new_findex();
670 bsr->fi->findex = findex;
671 bsr->fi->findex2 = findex;
674 /* Walk down list of bsrs until we find the JobId */
675 if (bsr->JobId != JobId) {
676 for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
677 if (nbsr->JobId == JobId) {
683 if (!nbsr) { /* Must add new JobId */
684 /* Add new JobId at end of chain */
685 for (nbsr=bsr; nbsr->next; nbsr=nbsr->next)
687 nbsr->next = new_bsr();
688 nbsr->next->JobId = JobId;
689 nbsr->next->fi = new_findex();
690 nbsr->next->fi->findex = findex;
691 nbsr->next->fi->findex2 = findex;
697 * At this point, bsr points to bsr containing JobId,
698 * and we are sure that there is at least one fi record.
701 /* Check if this findex is smaller than first item */
702 if (findex < fi->findex) {
703 if ((findex+1) == fi->findex) {
704 fi->findex = findex; /* extend down */
707 fi = new_findex(); /* yes, insert before first item */
709 fi->findex2 = findex;
714 /* Walk down fi chain and find where to insert insert new FileIndex */
715 for ( ; fi; fi=fi->next) {
716 if (findex == (fi->findex2 + 1)) { /* extend up */
718 fi->findex2 = findex;
719 if (fi->next && ((findex+1) == fi->next->findex)) {
721 fi->findex2 = nfi->findex2;
722 fi->next = nfi->next;
727 if (findex < fi->findex) { /* add before */
728 if ((findex+1) == fi->findex) {
736 /* Add to last place found */
739 fi->findex2 = findex;
740 fi->next = lfi->next;
746 * This callback routine is responsible for inserting the
747 * items it gets into the directory tree. For each JobId selected
748 * this routine is called once for each file. We do not allow
749 * duplicate filenames, but instead keep the info from the most
750 * recent file entered (i.e. the JobIds are assumed to be sorted)
752 static int insert_tree_handler(void *ctx, int num_fields, char **row)
754 TREE_CTX *tree = (TREE_CTX *)ctx;
756 TREE_NODE *node, *new_node;
759 strip_trailing_junk(row[1]);
765 sprintf(fname, "%s%s", row[0], row[1]);
766 if (tree->avail_node) {
767 node = tree->avail_node;
769 node = new_tree_node(tree->root, type);
770 tree->avail_node = node;
772 Dmsg2(400, "FI=%d fname=%s\n", node->FileIndex, fname);
773 new_node = insert_tree_node(fname, node, tree->root, NULL);
774 /* Note, if node already exists, save new one for next time */
775 if (new_node != node) {
776 tree->avail_node = node;
778 tree->avail_node = NULL;
780 new_node->FileIndex = atoi(row[2]);
781 new_node->JobId = atoi(row[3]);
782 new_node->type = type;
783 new_node->extract = 1; /* extract all by default */
790 * Set extract to value passed. We recursively walk
791 * down the tree setting all children if the
792 * node is a directory.
794 static void set_extract(TREE_NODE *node, int value)
798 node->extract = value;
799 if (node->type != TN_FILE) {
800 for (n=node->child; n; n=n->sibling) {
801 set_extract(n, value);
806 static int addcmd(UAContext *ua, TREE_CTX *tree)
812 if (!tree->node->child) {
815 for (node = tree->node->child; node; node=node->sibling) {
816 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
817 set_extract(node, 1);
823 static int countcmd(UAContext *ua, TREE_CTX *tree)
828 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
829 if (node->type != TN_NEWDIR) {
836 bsendmsg(ua, "%d total files. %d marked for extraction.\n", total, extract);
840 static int findcmd(UAContext *ua, TREE_CTX *tree)
845 bsendmsg(ua, _("No file specification given.\n"));
849 for (int i=1; i < ua->argc; i++) {
850 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
851 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
852 tree_getpath(node, cwd, sizeof(cwd));
853 bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
862 static int lscmd(UAContext *ua, TREE_CTX *tree)
866 if (!tree->node->child) {
869 for (node = tree->node->child; node; node=node->sibling) {
870 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
871 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
872 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
878 static int helpcmd(UAContext *ua, TREE_CTX *tree)
883 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
884 for (i=0; i<comsize; i++) {
885 bsendmsg(ua, _(" %-10s %s\n"), _(commands[i].key), _(commands[i].help));
891 static int cdcmd(UAContext *ua, TREE_CTX *tree)
899 node = tree_cwd(ua->argk[1], tree->root, tree->node);
901 bsendmsg(ua, _("Invalid path given.\n"));
905 tree_getpath(tree->node, cwd, sizeof(cwd));
906 bsendmsg(ua, _("cwd is: %s\n"), cwd);
910 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
913 tree_getpath(tree->node, cwd, sizeof(cwd));
914 bsendmsg(ua, _("cwd is: %s\n"), cwd);
919 static int rmcmd(UAContext *ua, TREE_CTX *tree)
925 if (!tree->node->child) {
928 for (node = tree->node->child; node; node=node->sibling) {
929 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
930 set_extract(node, 0);
936 static int quitcmd(UAContext *ua, TREE_CTX *tree)