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;
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 */
53 /* FileIndex entry in bootstrap record */
54 typedef struct s_rbsr_findex {
55 struct s_rbsr_findex *next;
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 */
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);
85 int restorecmd(UAContext *ua, char *cmd)
95 "Enter list of JobIds",
96 "Enter SQL list command",
105 memset(&tree, 0, sizeof(TREE_CTX));
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]);
113 switch (do_prompt(ua, "Select item: ", NULL)) {
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: "))) {
121 if (!is_a_number(ua->cmd)) {
122 bsendmsg(ua, _("Bad JobId entered.\n"));
125 JobId = atoi(ua->cmd);
129 if (!get_cmd(ua, _("Enter JobIds: "))) {
132 JobId = atoi(ua->cmd);
135 if (!get_cmd(ua, _("Enter SQL list command: "))) {
138 db_list_sql_query(ua->db, ua->cmd, prtit, ua, 1);
142 if (!get_cmd(ua, _("Enter Filename: "))) {
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: "))) {
152 if (!is_a_number(ua->cmd)) {
153 bsendmsg(ua, _("Bad JobId entered.\n"));
156 JobId = atoi(ua->cmd);
163 memset(&jr, 0, sizeof(JOB_DBR));
165 if (!db_get_job_record(ua->db, &jr)) {
166 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
171 * Build the directory tree
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;
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));
183 free_pool_memory(query);
185 /* Let the user select which files to restore */
186 user_select_files(&tree);
189 * Walk down through the tree finding all files marked to be
190 * extracted making a bootstrap file.
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);
196 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
197 add_findex(bsr, node->JobId, node->FileIndex);
201 free_tree(tree.root); /* free the directory tree */
204 complete_bsr(ua, bsr);
207 bsendmsg(ua, _("No files selected to restore.\n"));
211 bsendmsg(ua, _("Restore command done.\n"));
217 /* Forward referenced commands */
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);
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")},
242 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
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", ...
250 static void user_select_files(TREE_CTX *tree)
254 * Enter interactive command handler allowing selection
255 * of individual files.
257 tree->node = (TREE_NODE *)tree->root;
258 tree_getpath(tree->node, cwd, sizeof(cwd));
259 bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
261 int found, len, stat, i;
262 if (!get_cmd(tree->ua, "$ ")) {
265 parse_command_args(tree->ua);
266 if (tree->ua->argc == 0) {
270 len = strlen(tree->ua->argk[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 */
279 bsendmsg(tree->ua, _("Illegal command\n"));
288 static RBSR_FINDEX *new_findex()
290 RBSR_FINDEX *fi = (RBSR_FINDEX *)malloc(sizeof(RBSR_FINDEX));
291 memset(fi, 0, sizeof(RBSR_FINDEX));
295 static void free_findex(RBSR_FINDEX *fi)
298 free_findex(fi->next);
303 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
306 if (fi->findex == fi->findex2) {
307 bsendmsg(ua, "FileIndex=%d\n", fi->findex);
309 bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
311 print_findex(ua, fi->next);
315 static RBSR *new_bsr()
317 RBSR *bsr = (RBSR *)malloc(sizeof(RBSR));
318 memset(bsr, 0, sizeof(RBSR));
322 static void free_bsr(RBSR *bsr)
325 free_findex(bsr->fi);
327 if (bsr->VolumeName) {
328 free(bsr->VolumeName);
335 * Complete the BSR by filling in the VolumeName and
336 * VolSessionId and VolSessionTime
338 static int complete_bsr(UAContext *ua, RBSR *bsr)
341 char VolumeNames[1000]; /* ****FIXME**** */
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));
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));
356 bsr->VolumeName = bstrdup(VolumeNames);
357 return complete_bsr(ua, bsr->next);
363 static void print_bsr(UAContext *ua, RBSR *bsr)
366 if (bsr->VolumeName) {
367 bsendmsg(ua, "VolumeName=%s\n", bsr->VolumeName);
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);
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.
383 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
386 RBSR_FINDEX *fi, *lfi;
389 return; /* probably a dummy directory */
392 if (!bsr->fi) { /* if no FI add one */
393 /* This is the first FileIndex item in the chain */
394 bsr->fi = new_findex();
396 bsr->fi->findex = findex;
397 bsr->fi->findex2 = findex;
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) {
409 if (!nbsr) { /* Must add new JobId */
410 /* Add new JobId at end of chain */
411 for (nbsr=bsr; nbsr->next; nbsr=nbsr->next)
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;
423 * At this point, bsr points to bsr containing JobId,
424 * and we are sure that there is at least one fi record.
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 */
433 fi = new_findex(); /* yes, insert before first item */
435 fi->findex2 = findex;
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 */
444 fi->findex2 = findex;
445 if (fi->next && ((findex+1) == fi->next->findex)) {
446 Dmsg1(400, "Coallase %d\n", findex);
448 fi->findex2 = nfi->findex2;
449 fi->next = nfi->next;
454 if (findex < fi->findex) { /* add before */
455 if ((findex+1) == fi->findex) {
463 /* Add to last place found */
466 fi->findex2 = findex;
467 fi->next = lfi->next;
472 static int insert_tree_handler(void *ctx, int num_fields, char **row)
474 TREE_CTX *tree = (TREE_CTX *)ctx;
476 TREE_NODE *node, *new_node;
479 strip_trailing_junk(row[1]);
485 sprintf(fname, "%s%s", row[0], row[1]);
486 if (tree->avail_node) {
487 node = tree->avail_node;
489 node = new_tree_node(tree->root, type);
490 tree->avail_node = node;
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;
498 tree->avail_node = NULL;
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);
512 * Set extract to value passed. We recursively walk
513 * down the tree setting all children.
515 static void set_extract(TREE_NODE *node, int value)
519 node->extract = value;
520 if (node->type != TN_FILE) {
521 for (n=node->child; n; n=n->sibling) {
522 set_extract(n, value);
527 static int addcmd(UAContext *ua, TREE_CTX *tree)
533 if (!tree->node->child) {
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);
544 static int lscmd(UAContext *ua, TREE_CTX *tree)
548 if (!tree->node->child) {
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)?"/":"");
560 static int helpcmd(UAContext *ua, TREE_CTX *tree)
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));
573 static int cdcmd(UAContext *ua, TREE_CTX *tree)
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);
585 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
588 tree_getpath(tree->node, cwd, sizeof(cwd));
589 bsendmsg(ua, _("cwd is: %s\n"), cwd);
594 static int rmcmd(UAContext *ua, TREE_CTX *tree)
600 if (!tree->node->child) {
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);
611 static int quitcmd(UAContext *ua, TREE_CTX *tree)