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 char *uar_list_jobs;
40 extern char *uar_file;
41 extern char *uar_sel_files;
42 extern char *uar_del_temp;
43 extern char *uar_del_temp1;
44 extern char *uar_create_temp;
45 extern char *uar_create_temp1;
46 extern char *uar_last_full;
47 extern char *uar_full;
49 extern char *uar_list_temp;
50 extern char *uar_sel_jobid_temp;
52 /* Context for insert_tree_handler() */
53 typedef struct s_tree_ctx {
54 TREE_ROOT *root; /* root */
55 TREE_NODE *node; /* current node */
56 TREE_NODE *avail_node; /* unused node last insert */
57 int cnt; /* count for user feedback */
69 /* FileIndex entry in bootstrap record */
70 typedef struct s_rbsr_findex {
71 struct s_rbsr_findex *next;
76 /* Restore bootstrap record -- not the real one, but useful here */
77 typedef struct s_rbsr {
78 struct s_rbsr *next; /* next JobId */
79 uint32_t JobId; /* JobId this bsr */
80 uint32_t VolSessionId;
81 uint32_t VolSessionTime;
82 char *VolumeName; /* Volume name */
83 RBSR_FINDEX *fi; /* File indexes this JobId */
86 /* Forward referenced functions */
87 static RBSR *new_bsr();
88 static void free_bsr(RBSR *bsr);
89 static void print_bsr(UAContext *ua, RBSR *bsr);
90 static int complete_bsr(UAContext *ua, RBSR *bsr);
91 static int insert_tree_handler(void *ctx, int num_fields, char **row);
92 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex);
93 static int last_full_handler(void *ctx, int num_fields, char **row);
94 static int jobid_handler(void *ctx, int num_fields, char **row);
95 static int next_jobid_from_list(char **p, uint32_t *JobId);
96 static int user_select_jobids(UAContext *ua, struct s_full_ctx *full);
97 static void user_select_files(TREE_CTX *tree);
104 int restorecmd(UAContext *ua, char *cmd)
112 struct s_full_ctx full;
118 memset(&tree, 0, sizeof(TREE_CTX));
119 memset(&full, 0, sizeof(full));
121 if (!user_select_jobids(ua, &full)) {
127 * Build the directory tree
129 tree.root = new_tree(full.TotalFiles);
130 tree.root->fname = nofname;
132 query = get_pool_memory(PM_MESSAGE);
133 for (p=full.JobIds; next_jobid_from_list(&p, &JobId) > 0; ) {
134 bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
135 Mmsg(&query, uar_sel_files, JobId);
136 if (!db_sql_query(ua->db, query, insert_tree_handler, (void *)&tree)) {
137 bsendmsg(ua, "%s", db_strerror(ua->db));
141 free_pool_memory(query);
143 /* Let the user select which files to restore */
144 user_select_files(&tree);
147 * Walk down through the tree finding all files marked to be
148 * extracted making a bootstrap file.
151 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
152 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
154 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
155 add_findex(bsr, node->JobId, node->FileIndex);
159 free_tree(tree.root); /* free the directory tree */
162 complete_bsr(ua, bsr); /* find Vol, SessId, SessTime from JobIds */
165 bsendmsg(ua, _("No files selected to restore.\n"));
169 bsendmsg(ua, _("Restore command done.\n"));
174 * The first step in the restore process is for the user to
175 * select a list of JobIds from which he will subsequently
176 * select which files are to be restored.
178 static int user_select_jobids(UAContext *ua, struct s_full_ctx *full)
186 "List last 20 Jobs run",
187 "List Jobs where a given File is saved",
188 "Enter list of JobIds to select",
189 "Enter SQL list command",
190 "Select the most recent backup for a client"
194 bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
195 "to be restored. You will be presented several methods\n"
196 "of specifying the JobIds. Then you will be allowed to\n"
197 "select which files from those JobIds are to be restored.\n\n"));
200 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
201 for (int i=0; list[i]; i++) {
202 add_prompt(ua, list[i]);
205 switch (do_prompt(ua, "Select item: ", NULL)) {
208 case 0: /* list last 20 Jobs run */
209 db_list_sql_query(ua->db, uar_list_jobs, prtit, ua, 1);
212 case 1: /* list where a file is saved */
213 if (!get_cmd(ua, _("Enter Filename: "))) {
216 query = get_pool_memory(PM_MESSAGE);
217 Mmsg(&query, uar_file, ua->cmd);
218 db_list_sql_query(ua->db, query, prtit, ua, 1);
219 free_pool_memory(query);
222 case 2: /* enter a list of JobIds */
223 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
226 bstrncpy(full->JobIds, ua->cmd, sizeof(full->JobIds));
228 case 3: /* Enter an SQL list command */
229 if (!get_cmd(ua, _("Enter SQL list command: "))) {
232 db_list_sql_query(ua->db, ua->cmd, prtit, ua, 1);
235 case 4: /* Select the most recent backups */
236 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
237 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
238 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
239 bsendmsg(ua, "%s\n", db_strerror(ua->db));
241 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
242 bsendmsg(ua, "%s\n", db_strerror(ua->db));
244 if (!get_cmd(ua, _("Enter Client name: "))) {
247 query = get_pool_memory(PM_MESSAGE);
248 Mmsg(&query, uar_last_full, ua->cmd);
249 /* Find JobId of full Backup of system */
250 if (!db_sql_query(ua->db, query, NULL, NULL)) {
251 bsendmsg(ua, "%s\n", db_strerror(ua->db));
253 /* Find all Volumes used by that JobId */
254 if (!db_sql_query(ua->db, uar_full, NULL,NULL)) {
255 bsendmsg(ua, "%s\n", db_strerror(ua->db));
257 /* Note, this is needed as I don't seem to get the callback
258 * from the call just above.
260 if (!db_sql_query(ua->db, "SELECT * from temp1", last_full_handler, (void *)full)) {
261 bsendmsg(ua, "%s\n", db_strerror(ua->db));
263 /* Now find all Incremental Jobs */
264 Mmsg(&query, uar_inc, (uint32_t)full->JobTDate, full->ClientId);
265 if (!db_sql_query(ua->db, query, NULL, NULL)) {
266 bsendmsg(ua, "%s\n", db_strerror(ua->db));
268 free_pool_memory(query);
269 db_list_sql_query(ua->db, uar_list_temp, prtit, ua, 1);
271 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)full)) {
272 bsendmsg(ua, "%s\n", db_strerror(ua->db));
274 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
275 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
282 if (*full->JobIds == 0) {
283 bsendmsg(ua, _("No Jobs selected.\n"));
286 bsendmsg(ua, _("You have selected the following JobId: %s\n"), full->JobIds);
288 memset(&jr, 0, sizeof(JOB_DBR));
290 for (p=full->JobIds; ; ) {
291 int stat = next_jobid_from_list(&p, &JobId);
293 bsendmsg(ua, _("Invalid JobId in list.\n"));
300 if (!db_get_job_record(ua->db, &jr)) {
301 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
304 full->TotalFiles = jr.JobFiles;
309 static int next_jobid_from_list(char **p, uint32_t *JobId)
316 for (i=0; i<(int)sizeof(jobid); i++) {
324 if (jobid[0] == 0 || !is_a_number(jobid)) {
328 *JobId = strtoul(jobid, NULL, 10);
336 * Callback handler make list of JobIds
338 static int jobid_handler(void *ctx, int num_fields, char **row)
340 struct s_full_ctx *full = (struct s_full_ctx *)ctx;
342 if (strlen(full->JobIds)+strlen(row[0])+2 < sizeof(full->JobIds)) {
343 if (full->JobIds[0] != 0) {
344 strcat(full->JobIds, ",");
346 strcat(full->JobIds, row[0]);
354 * Callback handler to pickup last Full backup JobId and ClientId
356 static int last_full_handler(void *ctx, int num_fields, char **row)
358 struct s_full_ctx *full = (struct s_full_ctx *)ctx;
360 full->JobTDate = atoi(row[1]);
361 full->ClientId = atoi(row[2]);
369 /* Forward referenced commands */
371 static int addcmd(UAContext *ua, TREE_CTX *tree);
372 static int countcmd(UAContext *ua, TREE_CTX *tree);
373 static int findcmd(UAContext *ua, TREE_CTX *tree);
374 static int lscmd(UAContext *ua, TREE_CTX *tree);
375 static int helpcmd(UAContext *ua, TREE_CTX *tree);
376 static int cdcmd(UAContext *ua, TREE_CTX *tree);
377 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
378 static int rmcmd(UAContext *ua, TREE_CTX *tree);
379 static int quitcmd(UAContext *ua, TREE_CTX *tree);
382 struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; };
383 static struct cmdstruct commands[] = {
384 { N_("add"), addcmd, _("add file")},
385 { N_("count"), countcmd, _("count files")},
386 { N_("find"), findcmd, _("find files")},
387 { N_("ls"), lscmd, _("list current directory")},
388 { N_("dir"), lscmd, _("list current directory")},
389 { N_("help"), helpcmd, _("print help")},
390 { N_("cd"), cdcmd, _("change directory")},
391 { N_("pwd"), pwdcmd, _("print directory")},
392 { N_("rm"), rmcmd, _("remove a file")},
393 { N_("remove"), rmcmd, _("remove a file")},
394 { N_("done"), quitcmd, _("quit")},
395 { N_("exit"), quitcmd, _("exit = quit")},
396 { N_("?"), helpcmd, _("print help")},
398 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
402 * Enter a prompt mode where the user can select/deselect
403 * files to be restored. This is sort of like a mini-shell
404 * that allows "cd", "pwd", "add", "rm", ...
406 static void user_select_files(TREE_CTX *tree)
410 * Enter interactive command handler allowing selection
411 * of individual files.
413 tree->node = (TREE_NODE *)tree->root;
414 tree_getpath(tree->node, cwd, sizeof(cwd));
415 bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
417 int found, len, stat, i;
418 if (!get_cmd(tree->ua, "$ ")) {
421 parse_command_args(tree->ua);
422 if (tree->ua->argc == 0) {
426 len = strlen(tree->ua->argk[0]);
428 for (i=0; i<(int)comsize; i++) /* search for command */
429 if (strncasecmp(tree->ua->argk[0], _(commands[i].key), len) == 0) {
430 stat = (*commands[i].func)(tree->ua, tree); /* go execute command */
435 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to end.\n"));
444 static RBSR_FINDEX *new_findex()
446 RBSR_FINDEX *fi = (RBSR_FINDEX *)malloc(sizeof(RBSR_FINDEX));
447 memset(fi, 0, sizeof(RBSR_FINDEX));
451 static void free_findex(RBSR_FINDEX *fi)
454 free_findex(fi->next);
459 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
462 if (fi->findex == fi->findex2) {
463 bsendmsg(ua, "FileIndex=%d\n", fi->findex);
465 bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
467 print_findex(ua, fi->next);
471 static RBSR *new_bsr()
473 RBSR *bsr = (RBSR *)malloc(sizeof(RBSR));
474 memset(bsr, 0, sizeof(RBSR));
478 static void free_bsr(RBSR *bsr)
481 free_findex(bsr->fi);
483 if (bsr->VolumeName) {
484 free(bsr->VolumeName);
491 * Complete the BSR by filling in the VolumeName and
492 * VolSessionId and VolSessionTime
494 static int complete_bsr(UAContext *ua, RBSR *bsr)
497 char VolumeNames[1000]; /* ****FIXME**** */
500 memset(&jr, 0, sizeof(jr));
501 jr.JobId = bsr->JobId;
502 if (!db_get_job_record(ua->db, &jr)) {
503 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
506 bsr->VolSessionId = jr.VolSessionId;
507 bsr->VolSessionTime = jr.VolSessionTime;
508 if (!db_get_job_volume_names(ua->db, bsr->JobId, VolumeNames)) {
509 bsendmsg(ua, _("Unable to get Job Volumes. ERR=%s\n"), db_strerror(ua->db));
512 bsr->VolumeName = bstrdup(VolumeNames);
513 return complete_bsr(ua, bsr->next);
519 static void print_bsr(UAContext *ua, RBSR *bsr)
522 if (bsr->VolumeName) {
523 bsendmsg(ua, "VolumeName=%s\n", bsr->VolumeName);
525 // bsendmsg(ua, "JobId=%u\n", bsr->JobId);
526 bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
527 bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
528 print_findex(ua, bsr->fi);
529 print_bsr(ua, bsr->next);
535 * Add a FileIndex to the list of BootStrap records.
536 * Here we are only dealing with JobId's and the FileIndexes
537 * associated with those JobIds.
539 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
542 RBSR_FINDEX *fi, *lfi;
545 return; /* probably a dummy directory */
548 if (!bsr->fi) { /* if no FI add one */
549 /* This is the first FileIndex item in the chain */
550 bsr->fi = new_findex();
552 bsr->fi->findex = findex;
553 bsr->fi->findex2 = findex;
556 /* Walk down list of bsrs until we find the JobId */
557 if (bsr->JobId != JobId) {
558 for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
559 if (nbsr->JobId == JobId) {
565 if (!nbsr) { /* Must add new JobId */
566 /* Add new JobId at end of chain */
567 for (nbsr=bsr; nbsr->next; nbsr=nbsr->next)
569 nbsr->next = new_bsr();
570 nbsr->next->JobId = JobId;
571 nbsr->next->fi = new_findex();
572 nbsr->next->fi->findex = findex;
573 nbsr->next->fi->findex2 = findex;
579 * At this point, bsr points to bsr containing JobId,
580 * and we are sure that there is at least one fi record.
583 /* Check if this findex is smaller than first item */
584 if (findex < fi->findex) {
585 if ((findex+1) == fi->findex) {
586 fi->findex = findex; /* extend down */
589 fi = new_findex(); /* yes, insert before first item */
591 fi->findex2 = findex;
596 /* Walk down fi chain and find where to insert insert new FileIndex */
597 for ( ; fi; fi=fi->next) {
598 if (findex == (fi->findex2 + 1)) { /* extend up */
600 fi->findex2 = findex;
601 if (fi->next && ((findex+1) == fi->next->findex)) {
602 Dmsg1(400, "Coallase %d\n", findex);
604 fi->findex2 = nfi->findex2;
605 fi->next = nfi->next;
610 if (findex < fi->findex) { /* add before */
611 if ((findex+1) == fi->findex) {
619 /* Add to last place found */
622 fi->findex2 = findex;
623 fi->next = lfi->next;
628 static int insert_tree_handler(void *ctx, int num_fields, char **row)
630 TREE_CTX *tree = (TREE_CTX *)ctx;
632 TREE_NODE *node, *new_node;
635 strip_trailing_junk(row[1]);
641 sprintf(fname, "%s%s", row[0], row[1]);
642 if (tree->avail_node) {
643 node = tree->avail_node;
645 node = new_tree_node(tree->root, type);
646 tree->avail_node = node;
648 Dmsg2(400, "FI=%d fname=%s\n", node->FileIndex, fname);
649 new_node = insert_tree_node(fname, node, tree->root, NULL);
650 /* Note, if node already exists, save new one for next time */
651 if (new_node != node) {
652 tree->avail_node = node;
654 tree->avail_node = NULL;
656 new_node->FileIndex = atoi(row[2]);
657 new_node->JobId = atoi(row[3]);
658 new_node->type = type;
660 if (((tree->cnt) % 10000) == 0) {
661 bsendmsg(tree->ua, "%d ", tree->cnt);
670 * Set extract to value passed. We recursively walk
671 * down the tree setting all children.
673 static void set_extract(TREE_NODE *node, int value)
677 node->extract = value;
678 if (node->type != TN_FILE) {
679 for (n=node->child; n; n=n->sibling) {
680 set_extract(n, value);
685 static int addcmd(UAContext *ua, TREE_CTX *tree)
691 if (!tree->node->child) {
694 for (node = tree->node->child; node; node=node->sibling) {
695 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
696 set_extract(node, 1);
702 static int countcmd(UAContext *ua, TREE_CTX *tree)
707 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
708 if (node->type != TN_NEWDIR) {
715 bsendmsg(ua, "%d total files. %d marked for extraction.\n", total, extract);
719 static int findcmd(UAContext *ua, TREE_CTX *tree)
724 bsendmsg(ua, _("No file specification given.\n"));
728 for (int i=1; i < ua->argc; i++) {
729 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
730 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
731 tree_getpath(node, cwd, sizeof(cwd));
732 bsendmsg(ua, "%s\n", cwd);
741 static int lscmd(UAContext *ua, TREE_CTX *tree)
745 if (!tree->node->child) {
748 for (node = tree->node->child; node; node=node->sibling) {
749 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
750 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
751 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
757 static int helpcmd(UAContext *ua, TREE_CTX *tree)
762 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
763 for (i=0; i<comsize; i++) {
764 bsendmsg(ua, _(" %-10s %s\n"), _(commands[i].key), _(commands[i].help));
770 static int cdcmd(UAContext *ua, TREE_CTX *tree)
778 node = tree_cwd(ua->argk[1], tree->root, tree->node);
780 bsendmsg(ua, _("Invalid path given.\n"));
784 tree_getpath(tree->node, cwd, sizeof(cwd));
785 bsendmsg(ua, _("cwd is: %s\n"), cwd);
789 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
792 tree_getpath(tree->node, cwd, sizeof(cwd));
793 bsendmsg(ua, _("cwd is: %s\n"), cwd);
798 static int rmcmd(UAContext *ua, TREE_CTX *tree)
804 if (!tree->node->child) {
807 for (node = tree->node->child; node; node=node->sibling) {
808 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
809 set_extract(node, 0);
815 static int quitcmd(UAContext *ua, TREE_CTX *tree)