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];
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);
766 int comp_vol_params(const void *v1, const void *v2)
768 VOL_PARAMS *vol1 = (VOL_PARAMS *)v1;
769 VOL_PARAMS *vol2 = (VOL_PARAMS *)v2;
771 if (vol1->FirstIndex < vol2->FirstIndex) {
773 } else if (vol1->FirstIndex > vol2->FirstIndex) {
781 * First sort the bsr chain, then sort the VolParams
783 static RBSR *sort_bsr(RBSR *bsr)
788 /* ****FIXME**** sort the bsr chain */
789 /* Sort the VolParams for each bsr */
790 for (RBSR *nbsr=bsr; nbsr; nbsr=nbsr->next) {
791 if (nbsr->VolCount > 1) {
792 Dmsg1(100, "VolCount=%d\n", nbsr->VolCount);
793 qsort((void *)nbsr->VolParams, nbsr->VolCount, sizeof(VOL_PARAMS),
800 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
803 for (int i=0; i < bsr->VolCount; i++) {
804 fprintf(fd, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
805 fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
806 fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
807 fprintf(fd, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile,
808 bsr->VolParams[i].EndFile);
809 fprintf(fd, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
810 bsr->VolParams[i].EndBlock);
811 write_findex(ua, bsr->fi, fd);
813 write_bsr(ua, bsr->next, fd);
817 static void print_bsr(UAContext *ua, RBSR *bsr)
820 for (int i=0; i < bsr->VolCount; i++) {
821 bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
822 bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
823 bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
824 bsendmsg(ua, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile,
825 bsr->VolParams[i].EndFile);
826 bsendmsg(ua, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
827 bsr->VolParams[i].EndBlock);
828 print_findex(ua, bsr->fi);
830 print_bsr(ua, bsr->next);
836 * Add a FileIndex to the list of BootStrap records.
837 * Here we are only dealing with JobId's and the FileIndexes
838 * associated with those JobIds.
840 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
843 RBSR_FINDEX *fi, *lfi;
846 return; /* probably a dummy directory */
849 if (!bsr->fi) { /* if no FI add one */
850 /* This is the first FileIndex item in the chain */
851 bsr->fi = new_findex();
853 bsr->fi->findex = findex;
854 bsr->fi->findex2 = findex;
857 /* Walk down list of bsrs until we find the JobId */
858 if (bsr->JobId != JobId) {
859 for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
860 if (nbsr->JobId == JobId) {
866 if (!nbsr) { /* Must add new JobId */
867 /* Add new JobId at end of chain */
868 for (nbsr=bsr; nbsr->next; nbsr=nbsr->next)
870 nbsr->next = new_bsr();
871 nbsr->next->JobId = JobId;
872 nbsr->next->fi = new_findex();
873 nbsr->next->fi->findex = findex;
874 nbsr->next->fi->findex2 = findex;
880 * At this point, bsr points to bsr containing JobId,
881 * and we are sure that there is at least one fi record.
884 /* Check if this findex is smaller than first item */
885 if (findex < fi->findex) {
886 if ((findex+1) == fi->findex) {
887 fi->findex = findex; /* extend down */
890 fi = new_findex(); /* yes, insert before first item */
892 fi->findex2 = findex;
897 /* Walk down fi chain and find where to insert insert new FileIndex */
898 for ( ; fi; fi=fi->next) {
899 if (findex == (fi->findex2 + 1)) { /* extend up */
901 fi->findex2 = findex;
902 if (fi->next && ((findex+1) == fi->next->findex)) {
904 fi->findex2 = nfi->findex2;
905 fi->next = nfi->next;
910 if (findex < fi->findex) { /* add before */
911 if ((findex+1) == fi->findex) {
919 /* Add to last place found */
922 fi->findex2 = findex;
923 fi->next = lfi->next;
929 * This callback routine is responsible for inserting the
930 * items it gets into the directory tree. For each JobId selected
931 * this routine is called once for each file. We do not allow
932 * duplicate filenames, but instead keep the info from the most
933 * recent file entered (i.e. the JobIds are assumed to be sorted)
935 static int insert_tree_handler(void *ctx, int num_fields, char **row)
937 TREE_CTX *tree = (TREE_CTX *)ctx;
939 TREE_NODE *node, *new_node;
942 strip_trailing_junk(row[1]);
944 if (*row[0] != '/') { /* Must be Win32 directory */
952 sprintf(fname, "%s%s", row[0], row[1]);
953 if (tree->avail_node) {
954 node = tree->avail_node;
956 node = new_tree_node(tree->root, type);
957 tree->avail_node = node;
959 Dmsg3(200, "FI=%d type=%d fname=%s\n", node->FileIndex, type, fname);
960 new_node = insert_tree_node(fname, node, tree->root, NULL);
961 /* Note, if node already exists, save new one for next time */
962 if (new_node != node) {
963 tree->avail_node = node;
965 tree->avail_node = NULL;
967 new_node->FileIndex = atoi(row[2]);
968 new_node->JobId = atoi(row[3]);
969 new_node->type = type;
970 new_node->extract = 1; /* extract all by default */
977 * Set extract to value passed. We recursively walk
978 * down the tree setting all children if the
979 * node is a directory.
981 static void set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, int value)
987 node->extract = value;
988 /* For a non-file (i.e. directory), we see all the children */
989 if (node->type != TN_FILE) {
990 for (n=node->child; n; n=n->sibling) {
991 set_extract(ua, n, tree, value);
995 /* Ordinary file, we get the full path, look up the
996 * attributes, decode them, and if we are hard linked to
997 * a file that was saved, we must load that file too.
999 tree_getpath(node, cwd, sizeof(cwd));
1001 fdbr.JobId = node->JobId;
1002 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
1004 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
1006 * If we point to a hard linked file, traverse the tree to
1007 * find that file, and mark it for restoration as well. It
1008 * must have the Link we just obtained and the same JobId.
1011 for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
1012 if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
1022 static int markcmd(UAContext *ua, TREE_CTX *tree)
1028 if (!tree->node->child) {
1031 for (node = tree->node->child; node; node=node->sibling) {
1032 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1033 set_extract(ua, node, tree, 1);
1039 static int countcmd(UAContext *ua, TREE_CTX *tree)
1043 total = extract = 0;
1044 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
1045 if (node->type != TN_NEWDIR) {
1047 if (node->extract) {
1052 bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
1056 static int findcmd(UAContext *ua, TREE_CTX *tree)
1060 if (ua->argc == 1) {
1061 bsendmsg(ua, _("No file specification given.\n"));
1065 for (int i=1; i < ua->argc; i++) {
1066 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
1067 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
1068 tree_getpath(node, cwd, sizeof(cwd));
1069 bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
1078 static int lscmd(UAContext *ua, TREE_CTX *tree)
1082 if (!tree->node->child) {
1085 for (node = tree->node->child; node; node=node->sibling) {
1086 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1087 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1088 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1094 extern char *getuser(uid_t uid);
1095 extern char *getgroup(gid_t gid);
1098 * This is actually the long form used for "dir"
1100 static void ls_output(char *buf, char *fname, int extract, struct stat *statp)
1106 p = encode_mode(statp->st_mode, buf);
1107 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
1109 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
1111 n = sprintf(p, "%8.8s ", edit_uint64(statp->st_size, ec1));
1113 p = encode_time(statp->st_ctime, p);
1127 * Like ls command, but give more detail on each file
1129 static int dircmd(UAContext *ua, TREE_CTX *tree)
1137 if (!tree->node->child) {
1140 for (node = tree->node->child; node; node=node->sibling) {
1141 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1142 tree_getpath(node, cwd, sizeof(cwd));
1144 fdbr.JobId = node->JobId;
1145 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
1147 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
1148 ls_output(buf, cwd, node->extract, &statp);
1149 bsendmsg(ua, "%s\n", buf);
1151 /* Something went wrong getting attributes -- print name */
1152 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1153 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1161 static int helpcmd(UAContext *ua, TREE_CTX *tree)
1166 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
1167 for (i=0; i<comsize; i++) {
1168 bsendmsg(ua, _(" %-10s %s\n"), _(commands[i].key), _(commands[i].help));
1175 * Change directories. Note, if the user specifies x: and it fails,
1176 * we assume it is a Win32 absolute cd rather than relative and
1177 * try a second time with /x: ... Win32 kludge.
1179 static int cdcmd(UAContext *ua, TREE_CTX *tree)
1184 if (ua->argc != 2) {
1187 node = tree_cwd(ua->argk[1], tree->root, tree->node);
1189 /* Try once more if Win32 drive -- make absolute */
1190 if (ua->argk[1][1] == ':') { /* win32 drive */
1192 strcat(cwd, ua->argk[1]);
1193 node = tree_cwd(cwd, tree->root, tree->node);
1196 bsendmsg(ua, _("Invalid path given.\n"));
1203 tree_getpath(tree->node, cwd, sizeof(cwd));
1204 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1208 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
1211 tree_getpath(tree->node, cwd, sizeof(cwd));
1212 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1217 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
1223 if (!tree->node->child) {
1226 for (node = tree->node->child; node; node=node->sibling) {
1227 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1228 set_extract(ua, node, tree, 0);
1234 static int quitcmd(UAContext *ua, TREE_CTX *tree)
1241 * Called here with each name to be added to the list. The name is
1242 * added to the list if it is not already in the list.
1244 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1246 NAME_LIST *name = (NAME_LIST *)ctx;
1248 if (name->num_ids == MAX_ID_LIST_LEN) {
1251 if (name->num_ids == name->max_ids) {
1252 if (name->max_ids == 0) {
1253 name->max_ids = 1000;
1254 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1256 name->max_ids = (name->max_ids * 3) / 2;
1257 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1260 for (int i=0; i<name->num_ids; i++) {
1261 if (strcmp(name->name[i], row[0]) == 0) {
1262 return 0; /* already in list, return */
1265 /* Add new name to list */
1266 name->name[name->num_ids++] = bstrdup(row[0]);
1272 * Print names in the list
1274 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1278 for (i=0; i < name_list->num_ids; i++) {
1279 bsendmsg(ua, "%s\n", name_list->name[i]);
1285 * Free names in the list
1287 static void free_name_list(NAME_LIST *name_list)
1291 for (i=0; i < name_list->num_ids; i++) {
1292 free(name_list->name[i]);
1294 if (name_list->name) {
1295 free(name_list->name);
1297 name_list->max_ids = 0;
1298 name_list->num_ids = 0;
1301 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JOBIDS *ji)
1303 char name[MAX_NAME_LENGTH];
1304 STORE *store = NULL;
1306 if (name_list->num_ids > 1) {
1307 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1308 "Restore is not possible. The MediaTypes used are:\n"));
1309 print_name_list(ua, name_list);
1310 ji->store = select_storage_resource(ua);
1314 if (name_list->num_ids == 0) {
1315 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1316 ji->store = select_storage_resource(ua);
1320 start_prompt(ua, _("The defined Storage resources are:\n"));
1322 while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
1323 if (strcmp(store->media_type, name_list->name[0]) == 0) {
1324 add_prompt(ua, store->hdr.name);
1328 do_prompt(ua, _("Select Storage resource"), name, sizeof(name));
1329 ji->store = (STORE *)GetResWithName(R_STORAGE, name);
1331 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1332 "MediaType %s, needed by the Jobs you selected.\n"
1333 "You will be allowed to select a Storage device later.\n"),
1334 name_list->name[0]);