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-2003 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,
34 #include "findlib/find.h"
38 /* Imported functions */
39 extern int runcmd(UAContext *ua, char *cmd);
41 /* Imported variables */
42 extern char *uar_list_jobs, *uar_file, *uar_sel_files;
43 extern char *uar_del_temp, *uar_del_temp1, *uar_create_temp;
44 extern char *uar_create_temp1, *uar_last_full, *uar_full;
45 extern char *uar_inc, *uar_list_temp, *uar_sel_jobid_temp;
46 extern char *uar_sel_all_temp1, *uar_sel_fileset, *uar_mediatype;
49 /* Context for insert_tree_handler() */
51 TREE_ROOT *root; /* root */
52 TREE_NODE *node; /* current node */
53 TREE_NODE *avail_node; /* unused node last insert */
54 int cnt; /* count for user feedback */
58 /* Main structure for obtaining JobIds */
62 char ClientName[MAX_NAME_LENGTH];
63 char JobIds[200]; /* User entered string of JobIds */
68 /* FileIndex entry in restore bootstrap record */
76 * Restore bootstrap record -- not the real one, but useful here
77 * The restore bsr is a chain of BSR records (linked by next).
78 * Each BSR represents a single JobId, and within it, it
79 * contains a linked list of file indexes for that JobId.
80 * The complete_bsr() routine, will then add all the volumes
81 * on which the Job is stored to the BSR.
84 RBSR *next; /* next JobId */
85 uint32_t JobId; /* JobId this bsr */
86 uint32_t VolSessionId;
87 uint32_t VolSessionTime;
88 int VolCount; /* Volume parameter count */
89 VOL_PARAMS *VolParams; /* Volume, start/end file/blocks */
90 RBSR_FINDEX *fi; /* File indexes this JobId */
94 char **name; /* list of names */
95 int num_ids; /* ids stored */
96 int max_ids; /* size of array */
97 int num_del; /* number deleted */
98 int tot_ids; /* total to process */
101 #define MAX_ID_LIST_LEN 1000000
104 /* Forward referenced functions */
105 static RBSR *new_bsr();
106 static void free_bsr(RBSR *bsr);
107 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd);
108 static int write_bsr_file(UAContext *ua, RBSR *bsr);
109 static void print_bsr(UAContext *ua, RBSR *bsr);
110 static int complete_bsr(UAContext *ua, RBSR *bsr);
111 static int insert_tree_handler(void *ctx, int num_fields, char **row);
112 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex);
113 static int last_full_handler(void *ctx, int num_fields, char **row);
114 static int jobid_handler(void *ctx, int num_fields, char **row);
115 static int next_jobid_from_list(char **p, uint32_t *JobId);
116 static int user_select_jobids(UAContext *ua, JOBIDS *ji);
117 static void user_select_files(TREE_CTX *tree);
118 static int fileset_handler(void *ctx, int num_fields, char **row);
119 static void print_name_list(UAContext *ua, NAME_LIST *name_list);
120 static int unique_name_list_handler(void *ctx, int num_fields, char **row);
121 static void free_name_list(NAME_LIST *name_list);
122 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JOBIDS *ji);
123 static RBSR *sort_bsr(RBSR *bsr);
130 int restorecmd(UAContext *ua, char *cmd)
134 JobId_t JobId, last_JobId;
140 JOB *restore_job = NULL;
141 int restore_jobs = 0;
143 uint32_t selected_files = 0;
147 i = find_arg_with_value(ua, "where");
156 memset(&tree, 0, sizeof(TREE_CTX));
157 memset(&name_list, 0, sizeof(name_list));
158 memset(&ji, 0, sizeof(ji));
160 /* Ensure there is at least one Restore Job */
162 while ( (job = (JOB *)GetNextRes(R_JOB, (RES *)job)) ) {
163 if (job->JobType == JT_RESTORE) {
173 "No Restore Job Resource found. You must create at least\n"
174 "one before running this command.\n"));
179 * Request user to select JobIds by various different methods
180 * last 20 jobs, where File saved, most recent backup, ...
182 if (!user_select_jobids(ua, &ji)) {
187 * Build the directory tree containing JobIds user selected
189 tree.root = new_tree(ji.TotalFiles);
190 tree.root->fname = nofname;
192 query = get_pool_memory(PM_MESSAGE);
195 * For display purposes, the same JobId, with different volumes may
196 * appear more than once, however, we only insert it once.
198 for (p=ji.JobIds; next_jobid_from_list(&p, &JobId) > 0; ) {
200 if (JobId == last_JobId) {
201 continue; /* eliminate duplicate JobIds */
204 bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
206 * Find files for this JobId and insert them in the tree
208 Mmsg(&query, uar_sel_files, JobId);
209 if (!db_sql_query(ua->db, query, insert_tree_handler, (void *)&tree)) {
210 bsendmsg(ua, "%s", db_strerror(ua->db));
213 * Find the FileSets for this JobId and add to the name_list
215 Mmsg(&query, uar_mediatype, JobId);
216 if (!db_sql_query(ua->db, query, unique_name_list_handler, (void *)&name_list)) {
217 bsendmsg(ua, "%s", db_strerror(ua->db));
221 bsendmsg(ua, "%d items inserted into the tree and marked for extraction.\n");
222 free_pool_memory(query);
224 /* Check MediaType and select storage that corresponds */
225 get_storage_from_mediatype(ua, &name_list, &ji);
226 free_name_list(&name_list);
228 /* Let the user select which files to restore */
229 user_select_files(&tree);
232 * Walk down through the tree finding all files marked to be
233 * extracted making a bootstrap file.
236 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
237 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
239 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
240 add_findex(bsr, node->JobId, node->FileIndex);
245 free_tree(tree.root); /* free the directory tree */
248 if (!complete_bsr(ua, bsr)) { /* find Vol, SessId, SessTime from JobIds */
249 bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
253 // print_bsr(ua, bsr);
254 write_bsr_file(ua, bsr);
255 bsendmsg(ua, _("\n%u files selected to restore.\n\n"), selected_files);
257 bsendmsg(ua, _("No files selected to restore.\n"));
261 if (restore_jobs == 1) {
264 job = select_restore_job_resource(ua);
267 bsendmsg(ua, _("No Restore Job resource found!\n"));
271 /* If no client name specified yet, get it now */
272 if (!ji.ClientName[0]) {
274 memset(&cr, 0, sizeof(cr));
275 if (!get_client_dbr(ua, &cr)) {
278 bstrncpy(ji.ClientName, cr.Name, sizeof(ji.ClientName));
281 /* Build run command */
284 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\""
286 job->hdr.name, ji.ClientName, ji.store?ji.store->hdr.name:"",
287 working_directory, where);
290 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
291 job->hdr.name, ji.ClientName, ji.store?ji.store->hdr.name:"",
294 Dmsg1(400, "Submitting: %s\n", ua->cmd);
299 bsendmsg(ua, _("Restore command done.\n"));
304 * The first step in the restore process is for the user to
305 * select a list of JobIds from which he will subsequently
306 * select which files are to be restored.
308 static int user_select_jobids(UAContext *ua, JOBIDS *ji)
310 char fileset_name[MAX_NAME_LENGTH];
319 "List last 20 Jobs run",
320 "List Jobs where a given File is saved",
321 "Enter list of JobIds to select",
322 "Enter SQL list command",
323 "Select the most recent backup for a client",
327 bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
328 "to be restored. You will be presented several methods\n"
329 "of specifying the JobIds. Then you will be allowed to\n"
330 "select which files from those JobIds are to be restored.\n\n"));
333 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
334 for (int i=0; list[i]; i++) {
335 add_prompt(ua, list[i]);
338 switch (do_prompt(ua, "Select item: ", NULL, 0)) {
341 case 0: /* list last 20 Jobs run */
342 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, 0);
345 case 1: /* list where a file is saved */
348 if (!get_cmd(ua, _("Enter Filename: "))) {
351 len = strlen(ua->cmd);
352 fname = (char *)malloc(len * 2 + 1);
353 db_escape_string(fname, ua->cmd, len);
354 query = get_pool_memory(PM_MESSAGE);
355 Mmsg(&query, uar_file, fname);
357 db_list_sql_query(ua->jcr, ua->db, query, prtit, ua, 1, 0);
358 free_pool_memory(query);
361 case 2: /* enter a list of JobIds */
362 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
365 bstrncpy(ji->JobIds, ua->cmd, sizeof(ji->JobIds));
367 case 3: /* Enter an SQL list command */
368 if (!get_cmd(ua, _("Enter SQL list command: "))) {
371 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, 0);
374 case 4: /* Select the most recent backups */
375 query = get_pool_memory(PM_MESSAGE);
376 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
377 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
378 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
379 bsendmsg(ua, "%s\n", db_strerror(ua->db));
381 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
382 bsendmsg(ua, "%s\n", db_strerror(ua->db));
385 * Select Client from the Catalog
387 memset(&cr, 0, sizeof(cr));
388 if (!get_client_dbr(ua, &cr)) {
389 free_pool_memory(query);
390 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
391 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
394 bstrncpy(ji->ClientName, cr.Name, sizeof(ji->ClientName));
399 Mmsg(&query, uar_sel_fileset, cr.ClientId, cr.ClientId);
400 start_prompt(ua, _("The defined FileSet resources are:\n"));
401 if (!db_sql_query(ua->db, query, fileset_handler, (void *)ua)) {
402 bsendmsg(ua, "%s\n", db_strerror(ua->db));
404 if (do_prompt(ua, _("Select FileSet resource"),
405 fileset_name, sizeof(fileset_name)) < 0) {
406 free_pool_memory(query);
407 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
408 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
411 fsr.FileSetId = atoi(fileset_name); /* Id is first part of name */
412 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
413 bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
414 bsendmsg(ua, _("This probably means you modified the FileSet.\n"
415 "Continuing anyway.\n"));
418 /* Find JobId of last Full backup for this client, fileset */
419 Mmsg(&query, uar_last_full, cr.ClientId, cr.ClientId, fsr.FileSetId);
420 if (!db_sql_query(ua->db, query, NULL, NULL)) {
421 bsendmsg(ua, "%s\n", db_strerror(ua->db));
423 /* Find all Volumes used by that JobId */
424 if (!db_sql_query(ua->db, uar_full, NULL,NULL)) {
425 bsendmsg(ua, "%s\n", db_strerror(ua->db));
427 /* Note, this is needed as I don't seem to get the callback
428 * from the call just above.
430 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)ji)) {
431 bsendmsg(ua, "%s\n", db_strerror(ua->db));
433 /* Now find all Incremental Jobs */
434 Mmsg(&query, uar_inc, edit_uint64(ji->JobTDate, ed1), cr.ClientId, fsr.FileSetId);
435 if (!db_sql_query(ua->db, query, NULL, NULL)) {
436 bsendmsg(ua, "%s\n", db_strerror(ua->db));
438 free_pool_memory(query);
439 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, 0);
441 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)ji)) {
442 bsendmsg(ua, "%s\n", db_strerror(ua->db));
444 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
445 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
452 if (*ji->JobIds == 0) {
453 bsendmsg(ua, _("No Jobs selected.\n"));
456 bsendmsg(ua, _("You have selected the following JobId: %s\n"), ji->JobIds);
458 memset(&jr, 0, sizeof(JOB_DBR));
461 for (p=ji->JobIds; ; ) {
462 int stat = next_jobid_from_list(&p, &JobId);
464 bsendmsg(ua, _("Invalid JobId in list.\n"));
471 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
472 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
475 ji->TotalFiles += jr.JobFiles;
480 static int next_jobid_from_list(char **p, uint32_t *JobId)
487 for (i=0; i<(int)sizeof(jobid); i++) {
488 if (*q == ',' || *q == 0) {
495 if (jobid[0] == 0 || !is_a_number(jobid)) {
499 *JobId = strtoul(jobid, NULL, 10);
504 * Callback handler make list of JobIds
506 static int jobid_handler(void *ctx, int num_fields, char **row)
508 JOBIDS *ji = (JOBIDS *)ctx;
510 if (strlen(ji->JobIds)+strlen(row[0])+2 < sizeof(ji->JobIds)) {
511 if (ji->JobIds[0] != 0) {
512 strcat(ji->JobIds, ",");
514 strcat(ji->JobIds, row[0]);
522 * Callback handler to pickup last Full backup JobId and ClientId
524 static int last_full_handler(void *ctx, int num_fields, char **row)
526 JOBIDS *ji = (JOBIDS *)ctx;
528 ji->JobTDate = strtoll(row[1], NULL, 10);
534 * Callback handler build fileset prompt list
536 static int fileset_handler(void *ctx, int num_fields, char **row)
538 char prompt[MAX_NAME_LENGTH+200];
540 snprintf(prompt, sizeof(prompt), "%s %s %s", row[0], row[1], row[2]);
541 add_prompt((UAContext *)ctx, prompt);
545 /* Forward referenced commands */
547 static int markcmd(UAContext *ua, TREE_CTX *tree);
548 static int countcmd(UAContext *ua, TREE_CTX *tree);
549 static int findcmd(UAContext *ua, TREE_CTX *tree);
550 static int lscmd(UAContext *ua, TREE_CTX *tree);
551 static int dircmd(UAContext *ua, TREE_CTX *tree);
552 static int helpcmd(UAContext *ua, TREE_CTX *tree);
553 static int cdcmd(UAContext *ua, TREE_CTX *tree);
554 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
555 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
556 static int quitcmd(UAContext *ua, TREE_CTX *tree);
559 struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; };
560 static struct cmdstruct commands[] = {
561 { N_("mark"), markcmd, _("mark file for restoration")},
562 { N_("unmark"), unmarkcmd, _("unmark file for restoration")},
563 { N_("cd"), cdcmd, _("change current directory")},
564 { N_("pwd"), pwdcmd, _("print current working directory")},
565 { N_("ls"), lscmd, _("list current directory")},
566 { N_("dir"), dircmd, _("list current directory")},
567 { N_("count"), countcmd, _("count marked files")},
568 { N_("find"), findcmd, _("find files")},
569 { N_("done"), quitcmd, _("leave file selection mode")},
570 { N_("exit"), quitcmd, _("exit = done")},
571 { N_("help"), helpcmd, _("print help")},
572 { N_("?"), helpcmd, _("print help")},
574 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
578 * Enter a prompt mode where the user can select/deselect
579 * files to be restored. This is sort of like a mini-shell
580 * that allows "cd", "pwd", "add", "rm", ...
582 static void user_select_files(TREE_CTX *tree)
586 bsendmsg(tree->ua, _(
587 "\nYou are now entering file selection mode where you add and\n"
588 "remove files to be restored. All files are initially added.\n"
589 "Enter \"done\" to leave this mode.\n\n"));
591 * Enter interactive command handler allowing selection
592 * of individual files.
594 tree->node = (TREE_NODE *)tree->root;
595 tree_getpath(tree->node, cwd, sizeof(cwd));
596 bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
598 int found, len, stat, i;
599 if (!get_cmd(tree->ua, "$ ")) {
602 parse_ua_args(tree->ua);
603 if (tree->ua->argc == 0) {
607 len = strlen(tree->ua->argk[0]);
610 for (i=0; i<(int)comsize; i++) /* search for command */
611 if (strncasecmp(tree->ua->argk[0], _(commands[i].key), len) == 0) {
612 stat = (*commands[i].func)(tree->ua, tree); /* go execute command */
617 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
627 * Create new FileIndex entry for BSR
629 static RBSR_FINDEX *new_findex()
631 RBSR_FINDEX *fi = (RBSR_FINDEX *)bmalloc(sizeof(RBSR_FINDEX));
632 memset(fi, 0, sizeof(RBSR_FINDEX));
636 /* Free all BSR FileIndex entries */
637 static void free_findex(RBSR_FINDEX *fi)
640 free_findex(fi->next);
645 static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd)
648 if (fi->findex == fi->findex2) {
649 fprintf(fd, "FileIndex=%d\n", fi->findex);
651 fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
653 write_findex(ua, fi->next, fd);
658 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
661 if (fi->findex == fi->findex2) {
662 bsendmsg(ua, "FileIndex=%d\n", fi->findex);
664 bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
666 print_findex(ua, fi->next);
670 /* Create a new bootstrap record */
671 static RBSR *new_bsr()
673 RBSR *bsr = (RBSR *)bmalloc(sizeof(RBSR));
674 memset(bsr, 0, sizeof(RBSR));
678 /* Free the entire BSR */
679 static void free_bsr(RBSR *bsr)
682 free_findex(bsr->fi);
684 if (bsr->VolParams) {
685 free(bsr->VolParams);
692 * Complete the BSR by filling in the VolumeName and
693 * VolSessionId and VolSessionTime using the JobId
695 static int complete_bsr(UAContext *ua, RBSR *bsr)
700 memset(&jr, 0, sizeof(jr));
701 jr.JobId = bsr->JobId;
702 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
703 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
706 bsr->VolSessionId = jr.VolSessionId;
707 bsr->VolSessionTime = jr.VolSessionTime;
708 if ((bsr->VolCount=db_get_job_volume_parameters(ua->jcr, ua->db, bsr->JobId,
709 &(bsr->VolParams))) == 0) {
710 bsendmsg(ua, _("Unable to get Job Volume Parameters. ERR=%s\n"), db_strerror(ua->db));
711 if (bsr->VolParams) {
712 free(bsr->VolParams);
713 bsr->VolParams = NULL;
717 return complete_bsr(ua, bsr->next);
723 * Write the bootstrap record to file
725 static int write_bsr_file(UAContext *ua, RBSR *bsr)
728 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
731 Mmsg(&fname, "%s/restore.bsr", working_directory);
732 fd = fopen(fname, "w+");
734 bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"),
735 fname, strerror(errno));
736 free_pool_memory(fname);
739 /* Sort the bsr chain */
741 /* Write them to file */
742 write_bsr(ua, bsr, fd);
745 bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
747 /* Tell the user what he will need to mount */
748 bsendmsg(ua, _("\nThe restore job will require the following Volumes:\n"));
749 /* Create Unique list of Volumes using prompt list */
750 start_prompt(ua, "");
751 for (RBSR *nbsr=bsr; nbsr; nbsr=nbsr->next) {
752 for (int i=0; i < nbsr->VolCount; i++) {
753 add_prompt(ua, nbsr->VolParams[i].VolumeName);
756 for (int i=0; i < ua->num_prompts; i++) {
757 bsendmsg(ua, " %s\n", ua->prompt[i]);
762 free_pool_memory(fname);
767 * ***FIXME*** we need a better volume sequence number
769 int comp_vol_params(const void *v1, const void *v2)
771 VOL_PARAMS *vol1 = (VOL_PARAMS *)v1;
772 VOL_PARAMS *vol2 = (VOL_PARAMS *)v2;
774 if (vol1->JobMediaId < vol2->JobMediaId) {
776 } else if (vol1->JobMediaId > vol2->JobMediaId) {
784 * First sort the bsr chain, then sort the VolParams
786 static RBSR *sort_bsr(RBSR *bsr)
791 /* ****FIXME**** sort the bsr chain */
792 /* Sort the VolParams for each bsr */
793 for (RBSR *nbsr=bsr; nbsr; nbsr=nbsr->next) {
794 if (nbsr->VolCount > 1) {
795 qsort((void *)nbsr->VolParams, nbsr->VolCount, sizeof(VOL_PARAMS),
802 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
805 for (int i=0; i < bsr->VolCount; i++) {
806 fprintf(fd, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
807 fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
808 fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
809 fprintf(fd, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile,
810 bsr->VolParams[i].EndFile);
811 fprintf(fd, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
812 bsr->VolParams[i].EndBlock);
813 write_findex(ua, bsr->fi, fd);
815 write_bsr(ua, bsr->next, fd);
819 static void print_bsr(UAContext *ua, RBSR *bsr)
822 for (int i=0; i < bsr->VolCount; i++) {
823 bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
824 bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
825 bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
826 bsendmsg(ua, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile,
827 bsr->VolParams[i].EndFile);
828 bsendmsg(ua, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
829 bsr->VolParams[i].EndBlock);
830 print_findex(ua, bsr->fi);
832 print_bsr(ua, bsr->next);
838 * Add a FileIndex to the list of BootStrap records.
839 * Here we are only dealing with JobId's and the FileIndexes
840 * associated with those JobIds.
842 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
845 RBSR_FINDEX *fi, *lfi;
848 return; /* probably a dummy directory */
851 if (!bsr->fi) { /* if no FI add one */
852 /* This is the first FileIndex item in the chain */
853 bsr->fi = new_findex();
855 bsr->fi->findex = findex;
856 bsr->fi->findex2 = findex;
859 /* Walk down list of bsrs until we find the JobId */
860 if (bsr->JobId != JobId) {
861 for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
862 if (nbsr->JobId == JobId) {
868 if (!nbsr) { /* Must add new JobId */
869 /* Add new JobId at end of chain */
870 for (nbsr=bsr; nbsr->next; nbsr=nbsr->next)
872 nbsr->next = new_bsr();
873 nbsr->next->JobId = JobId;
874 nbsr->next->fi = new_findex();
875 nbsr->next->fi->findex = findex;
876 nbsr->next->fi->findex2 = findex;
882 * At this point, bsr points to bsr containing JobId,
883 * and we are sure that there is at least one fi record.
886 /* Check if this findex is smaller than first item */
887 if (findex < fi->findex) {
888 if ((findex+1) == fi->findex) {
889 fi->findex = findex; /* extend down */
892 fi = new_findex(); /* yes, insert before first item */
894 fi->findex2 = findex;
899 /* Walk down fi chain and find where to insert insert new FileIndex */
900 for ( ; fi; fi=fi->next) {
901 if (findex == (fi->findex2 + 1)) { /* extend up */
903 fi->findex2 = findex;
904 if (fi->next && ((findex+1) == fi->next->findex)) {
906 fi->findex2 = nfi->findex2;
907 fi->next = nfi->next;
912 if (findex < fi->findex) { /* add before */
913 if ((findex+1) == fi->findex) {
921 /* Add to last place found */
924 fi->findex2 = findex;
925 fi->next = lfi->next;
931 * This callback routine is responsible for inserting the
932 * items it gets into the directory tree. For each JobId selected
933 * this routine is called once for each file. We do not allow
934 * duplicate filenames, but instead keep the info from the most
935 * recent file entered (i.e. the JobIds are assumed to be sorted)
937 static int insert_tree_handler(void *ctx, int num_fields, char **row)
939 TREE_CTX *tree = (TREE_CTX *)ctx;
941 TREE_NODE *node, *new_node;
944 strip_trailing_junk(row[1]);
946 if (*row[0] != '/') { /* Must be Win32 directory */
954 sprintf(fname, "%s%s", row[0], row[1]);
955 if (tree->avail_node) {
956 node = tree->avail_node;
958 node = new_tree_node(tree->root, type);
959 tree->avail_node = node;
961 Dmsg3(200, "FI=%d type=%d fname=%s\n", node->FileIndex, type, fname);
962 new_node = insert_tree_node(fname, node, tree->root, NULL);
963 /* Note, if node already exists, save new one for next time */
964 if (new_node != node) {
965 tree->avail_node = node;
967 tree->avail_node = NULL;
969 new_node->FileIndex = atoi(row[2]);
970 new_node->JobId = atoi(row[3]);
971 new_node->type = type;
972 new_node->extract = 1; /* extract all by default */
979 * Set extract to value passed. We recursively walk
980 * down the tree setting all children if the
981 * node is a directory.
983 static void set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, int value)
989 node->extract = value;
990 /* For a non-file (i.e. directory), we see all the children */
991 if (node->type != TN_FILE) {
992 for (n=node->child; n; n=n->sibling) {
993 set_extract(ua, n, tree, value);
997 /* Ordinary file, we get the full path, look up the
998 * attributes, decode them, and if we are hard linked to
999 * a file that was saved, we must load that file too.
1001 tree_getpath(node, cwd, sizeof(cwd));
1003 fdbr.JobId = node->JobId;
1004 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
1006 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
1008 * If we point to a hard linked file, traverse the tree to
1009 * find that file, and mark it for restoration as well. It
1010 * must have the Link we just obtained and the same JobId.
1013 for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
1014 if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
1024 static int markcmd(UAContext *ua, TREE_CTX *tree)
1030 if (!tree->node->child) {
1033 for (node = tree->node->child; node; node=node->sibling) {
1034 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1035 set_extract(ua, node, tree, 1);
1041 static int countcmd(UAContext *ua, TREE_CTX *tree)
1045 total = extract = 0;
1046 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
1047 if (node->type != TN_NEWDIR) {
1049 if (node->extract) {
1054 bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
1058 static int findcmd(UAContext *ua, TREE_CTX *tree)
1062 if (ua->argc == 1) {
1063 bsendmsg(ua, _("No file specification given.\n"));
1067 for (int i=1; i < ua->argc; i++) {
1068 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
1069 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
1070 tree_getpath(node, cwd, sizeof(cwd));
1071 bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
1080 static int lscmd(UAContext *ua, TREE_CTX *tree)
1084 if (!tree->node->child) {
1087 for (node = tree->node->child; node; node=node->sibling) {
1088 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1089 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1090 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1096 extern char *getuser(uid_t uid);
1097 extern char *getgroup(gid_t gid);
1100 * This is actually the long form used for "dir"
1102 static void ls_output(char *buf, char *fname, int extract, struct stat *statp)
1108 p = encode_mode(statp->st_mode, buf);
1109 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
1111 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
1113 n = sprintf(p, "%8.8s ", edit_uint64(statp->st_size, ec1));
1115 p = encode_time(statp->st_ctime, p);
1129 * Like ls command, but give more detail on each file
1131 static int dircmd(UAContext *ua, TREE_CTX *tree)
1139 if (!tree->node->child) {
1142 for (node = tree->node->child; node; node=node->sibling) {
1143 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1144 tree_getpath(node, cwd, sizeof(cwd));
1146 fdbr.JobId = node->JobId;
1147 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
1149 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
1150 ls_output(buf, cwd, node->extract, &statp);
1151 bsendmsg(ua, "%s\n", buf);
1153 /* Something went wrong getting attributes -- print name */
1154 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1155 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1163 static int helpcmd(UAContext *ua, TREE_CTX *tree)
1168 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
1169 for (i=0; i<comsize; i++) {
1170 bsendmsg(ua, _(" %-10s %s\n"), _(commands[i].key), _(commands[i].help));
1177 * Change directories. Note, if the user specifies x: and it fails,
1178 * we assume it is a Win32 absolute cd rather than relative and
1179 * try a second time with /x: ... Win32 kludge.
1181 static int cdcmd(UAContext *ua, TREE_CTX *tree)
1186 if (ua->argc != 2) {
1189 node = tree_cwd(ua->argk[1], tree->root, tree->node);
1191 /* Try once more if Win32 drive -- make absolute */
1192 if (ua->argk[1][1] == ':') { /* win32 drive */
1194 strcat(cwd, ua->argk[1]);
1195 node = tree_cwd(cwd, tree->root, tree->node);
1198 bsendmsg(ua, _("Invalid path given.\n"));
1205 tree_getpath(tree->node, cwd, sizeof(cwd));
1206 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1210 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
1213 tree_getpath(tree->node, cwd, sizeof(cwd));
1214 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1219 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
1225 if (!tree->node->child) {
1228 for (node = tree->node->child; node; node=node->sibling) {
1229 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1230 set_extract(ua, node, tree, 0);
1236 static int quitcmd(UAContext *ua, TREE_CTX *tree)
1243 * Called here with each name to be added to the list. The name is
1244 * added to the list if it is not already in the list.
1246 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1248 NAME_LIST *name = (NAME_LIST *)ctx;
1250 if (name->num_ids == MAX_ID_LIST_LEN) {
1253 if (name->num_ids == name->max_ids) {
1254 if (name->max_ids == 0) {
1255 name->max_ids = 1000;
1256 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1258 name->max_ids = (name->max_ids * 3) / 2;
1259 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1262 for (int i=0; i<name->num_ids; i++) {
1263 if (strcmp(name->name[i], row[0]) == 0) {
1264 return 0; /* already in list, return */
1267 /* Add new name to list */
1268 name->name[name->num_ids++] = bstrdup(row[0]);
1274 * Print names in the list
1276 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1280 for (i=0; i < name_list->num_ids; i++) {
1281 bsendmsg(ua, "%s\n", name_list->name[i]);
1287 * Free names in the list
1289 static void free_name_list(NAME_LIST *name_list)
1293 for (i=0; i < name_list->num_ids; i++) {
1294 free(name_list->name[i]);
1296 if (name_list->name) {
1297 free(name_list->name);
1299 name_list->max_ids = 0;
1300 name_list->num_ids = 0;
1303 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JOBIDS *ji)
1305 char name[MAX_NAME_LENGTH];
1306 STORE *store = NULL;
1308 if (name_list->num_ids > 1) {
1309 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1310 "Restore is not possible. The MediaTypes used are:\n"));
1311 print_name_list(ua, name_list);
1312 ji->store = select_storage_resource(ua);
1316 if (name_list->num_ids == 0) {
1317 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1318 ji->store = select_storage_resource(ua);
1322 start_prompt(ua, _("The defined Storage resources are:\n"));
1324 while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
1325 if (strcmp(store->media_type, name_list->name[0]) == 0) {
1326 add_prompt(ua, store->hdr.name);
1330 do_prompt(ua, _("Select Storage resource"), name, sizeof(name));
1331 ji->store = (STORE *)GetResWithName(R_STORAGE, name);
1333 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1334 "MediaType %s, needed by the Jobs you selected.\n"
1335 "You will be allowed to select a Storage device later.\n"),
1336 name_list->name[0]);