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(000, "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 markcmd(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 unmarkcmd(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_("mark"), markcmd, _("mark file for restoration")},
444 { N_("unmark"), unmarkcmd, _("unmark file for restoration")},
445 { N_("cd"), cdcmd, _("change current directory")},
446 { N_("pwd"), pwdcmd, _("print current working directory")},
447 { N_("ls"), lscmd, _("list current directory")},
448 { N_("dir"), lscmd, _("list current directory")},
449 { N_("count"), countcmd, _("count marked files")},
450 { N_("find"), findcmd, _("find files")},
451 { N_("done"), quitcmd, _("leave file selection mode")},
452 { N_("exit"), quitcmd, _("exit = done")},
453 { N_("help"), helpcmd, _("print help")},
454 { N_("?"), helpcmd, _("print help")},
456 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
460 * Enter a prompt mode where the user can select/deselect
461 * files to be restored. This is sort of like a mini-shell
462 * that allows "cd", "pwd", "add", "rm", ...
464 static void user_select_files(TREE_CTX *tree)
468 bsendmsg(tree->ua, _(
469 "You are now entering file selection mode where you add and\n"
470 "remove files to be restored. All files are initially added.\n"
471 "Enter done to leave this mode.\n\n"));
473 * Enter interactive command handler allowing selection
474 * of individual files.
476 tree->node = (TREE_NODE *)tree->root;
477 tree_getpath(tree->node, cwd, sizeof(cwd));
478 bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
480 int found, len, stat, i;
481 if (!get_cmd(tree->ua, "$ ")) {
484 parse_command_args(tree->ua);
485 if (tree->ua->argc == 0) {
489 len = strlen(tree->ua->argk[0]);
491 for (i=0; i<(int)comsize; i++) /* search for command */
492 if (strncasecmp(tree->ua->argk[0], _(commands[i].key), len) == 0) {
493 stat = (*commands[i].func)(tree->ua, tree); /* go execute command */
498 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
507 * Create new FileIndex entry for BSR
509 static RBSR_FINDEX *new_findex()
511 RBSR_FINDEX *fi = (RBSR_FINDEX *)malloc(sizeof(RBSR_FINDEX));
512 memset(fi, 0, sizeof(RBSR_FINDEX));
516 /* Free all BSR FileIndex entries */
517 static void free_findex(RBSR_FINDEX *fi)
520 free_findex(fi->next);
525 static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd)
528 if (fi->findex == fi->findex2) {
529 fprintf(fd, "FileIndex=%d\n", fi->findex);
531 fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
533 write_findex(ua, fi->next, fd);
538 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
541 if (fi->findex == fi->findex2) {
542 bsendmsg(ua, "FileIndex=%d\n", fi->findex);
544 bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
546 print_findex(ua, fi->next);
550 /* Create a new bootstrap record */
551 static RBSR *new_bsr()
553 RBSR *bsr = (RBSR *)malloc(sizeof(RBSR));
554 memset(bsr, 0, sizeof(RBSR));
558 /* Free the entire BSR */
559 static void free_bsr(RBSR *bsr)
562 free_findex(bsr->fi);
564 if (bsr->VolumeName) {
565 free(bsr->VolumeName);
572 * Complete the BSR by filling in the VolumeName and
573 * VolSessionId and VolSessionTime using the JobId
575 static int complete_bsr(UAContext *ua, RBSR *bsr)
578 char VolumeNames[1000]; /* ****FIXME**** */
581 memset(&jr, 0, sizeof(jr));
582 jr.JobId = bsr->JobId;
583 if (!db_get_job_record(ua->db, &jr)) {
584 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
587 bsr->VolSessionId = jr.VolSessionId;
588 bsr->VolSessionTime = jr.VolSessionTime;
589 if (!db_get_job_volume_names(ua->db, bsr->JobId, VolumeNames)) {
590 bsendmsg(ua, _("Unable to get Job Volumes. ERR=%s\n"), db_strerror(ua->db));
593 bsr->VolumeName = bstrdup(VolumeNames);
594 return complete_bsr(ua, bsr->next);
600 * Write the bootstrap record to file
602 static int write_bsr_file(UAContext *ua, RBSR *bsr)
605 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
608 Mmsg(&fname, "%s/restore.bsr", working_directory);
609 fd = fopen(fname, "w+");
611 bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"),
612 fname, strerror(errno));
613 free_pool_memory(fname);
616 write_bsr(ua, bsr, fd);
619 bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
620 free_pool_memory(fname);
624 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
627 if (bsr->VolumeName) {
628 fprintf(fd, "Volume=%s\n", bsr->VolumeName);
630 fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
631 fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
632 write_findex(ua, bsr->fi, fd);
633 write_bsr(ua, bsr->next, fd);
637 static void print_bsr(UAContext *ua, RBSR *bsr)
640 if (bsr->VolumeName) {
641 bsendmsg(ua, "Volume=%s\n", bsr->VolumeName);
643 bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
644 bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
645 print_findex(ua, bsr->fi);
646 print_bsr(ua, bsr->next);
652 * Add a FileIndex to the list of BootStrap records.
653 * Here we are only dealing with JobId's and the FileIndexes
654 * associated with those JobIds.
656 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
659 RBSR_FINDEX *fi, *lfi;
662 return; /* probably a dummy directory */
665 if (!bsr->fi) { /* if no FI add one */
666 /* This is the first FileIndex item in the chain */
667 bsr->fi = new_findex();
669 bsr->fi->findex = findex;
670 bsr->fi->findex2 = findex;
673 /* Walk down list of bsrs until we find the JobId */
674 if (bsr->JobId != JobId) {
675 for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
676 if (nbsr->JobId == JobId) {
682 if (!nbsr) { /* Must add new JobId */
683 /* Add new JobId at end of chain */
684 for (nbsr=bsr; nbsr->next; nbsr=nbsr->next)
686 nbsr->next = new_bsr();
687 nbsr->next->JobId = JobId;
688 nbsr->next->fi = new_findex();
689 nbsr->next->fi->findex = findex;
690 nbsr->next->fi->findex2 = findex;
696 * At this point, bsr points to bsr containing JobId,
697 * and we are sure that there is at least one fi record.
700 /* Check if this findex is smaller than first item */
701 if (findex < fi->findex) {
702 if ((findex+1) == fi->findex) {
703 fi->findex = findex; /* extend down */
706 fi = new_findex(); /* yes, insert before first item */
708 fi->findex2 = findex;
713 /* Walk down fi chain and find where to insert insert new FileIndex */
714 for ( ; fi; fi=fi->next) {
715 if (findex == (fi->findex2 + 1)) { /* extend up */
717 fi->findex2 = findex;
718 if (fi->next && ((findex+1) == fi->next->findex)) {
720 fi->findex2 = nfi->findex2;
721 fi->next = nfi->next;
726 if (findex < fi->findex) { /* add before */
727 if ((findex+1) == fi->findex) {
735 /* Add to last place found */
738 fi->findex2 = findex;
739 fi->next = lfi->next;
745 * This callback routine is responsible for inserting the
746 * items it gets into the directory tree. For each JobId selected
747 * this routine is called once for each file. We do not allow
748 * duplicate filenames, but instead keep the info from the most
749 * recent file entered (i.e. the JobIds are assumed to be sorted)
751 static int insert_tree_handler(void *ctx, int num_fields, char **row)
753 TREE_CTX *tree = (TREE_CTX *)ctx;
755 TREE_NODE *node, *new_node;
758 strip_trailing_junk(row[1]);
764 sprintf(fname, "%s%s", row[0], row[1]);
765 if (tree->avail_node) {
766 node = tree->avail_node;
768 node = new_tree_node(tree->root, type);
769 tree->avail_node = node;
771 Dmsg2(400, "FI=%d fname=%s\n", node->FileIndex, fname);
772 new_node = insert_tree_node(fname, node, tree->root, NULL);
773 /* Note, if node already exists, save new one for next time */
774 if (new_node != node) {
775 tree->avail_node = node;
777 tree->avail_node = NULL;
779 new_node->FileIndex = atoi(row[2]);
780 new_node->JobId = atoi(row[3]);
781 new_node->type = type;
782 new_node->extract = 1; /* extract all by default */
789 * Set extract to value passed. We recursively walk
790 * down the tree setting all children if the
791 * node is a directory.
793 static void set_extract(TREE_NODE *node, int value)
797 node->extract = value;
798 if (node->type != TN_FILE) {
799 for (n=node->child; n; n=n->sibling) {
800 set_extract(n, value);
805 static int markcmd(UAContext *ua, TREE_CTX *tree)
811 if (!tree->node->child) {
814 for (node = tree->node->child; node; node=node->sibling) {
815 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
816 set_extract(node, 1);
822 static int countcmd(UAContext *ua, TREE_CTX *tree)
827 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
828 if (node->type != TN_NEWDIR) {
835 bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
839 static int findcmd(UAContext *ua, TREE_CTX *tree)
844 bsendmsg(ua, _("No file specification given.\n"));
848 for (int i=1; i < ua->argc; i++) {
849 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
850 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
851 tree_getpath(node, cwd, sizeof(cwd));
852 bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
861 static int lscmd(UAContext *ua, TREE_CTX *tree)
865 if (!tree->node->child) {
868 for (node = tree->node->child; node; node=node->sibling) {
869 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
870 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
871 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
877 static int helpcmd(UAContext *ua, TREE_CTX *tree)
882 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
883 for (i=0; i<comsize; i++) {
884 bsendmsg(ua, _(" %-10s %s\n"), _(commands[i].key), _(commands[i].help));
890 static int cdcmd(UAContext *ua, TREE_CTX *tree)
898 node = tree_cwd(ua->argk[1], tree->root, tree->node);
900 bsendmsg(ua, _("Invalid path given.\n"));
904 tree_getpath(tree->node, cwd, sizeof(cwd));
905 bsendmsg(ua, _("cwd is: %s\n"), cwd);
909 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
912 tree_getpath(tree->node, cwd, sizeof(cwd));
913 bsendmsg(ua, _("cwd is: %s\n"), cwd);
918 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
924 if (!tree->node->child) {
927 for (node = tree->node->child; node; node=node->sibling) {
928 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
929 set_extract(node, 0);
935 static int quitcmd(UAContext *ua, TREE_CTX *tree)