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() */
50 typedef struct s_tree_ctx {
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 */
59 typedef struct s_jobids {
62 char ClientName[MAX_NAME_LENGTH];
68 /* FileIndex entry in bootstrap record */
69 typedef struct s_rbsr_findex {
70 struct s_rbsr_findex *next;
75 /* Restore bootstrap record -- not the real one, but useful here */
76 typedef struct s_rbsr {
77 struct s_rbsr *next; /* next JobId */
78 uint32_t JobId; /* JobId this bsr */
79 uint32_t VolSessionId;
80 uint32_t VolSessionTime;
81 int VolCount; /* Volume parameter count */
82 VOL_PARAMS *VolParams; /* Volume, start/end file/blocks */
83 RBSR_FINDEX *fi; /* File indexes this JobId */
86 typedef struct s_name_ctx {
87 char **name; /* list of names */
88 int num_ids; /* ids stored */
89 int max_ids; /* size of array */
90 int num_del; /* number deleted */
91 int tot_ids; /* total to process */
94 #define MAX_ID_LIST_LEN 1000000
97 /* Forward referenced functions */
98 static RBSR *new_bsr();
99 static void free_bsr(RBSR *bsr);
100 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd);
101 static int write_bsr_file(UAContext *ua, RBSR *bsr);
102 static void print_bsr(UAContext *ua, RBSR *bsr);
103 static int complete_bsr(UAContext *ua, RBSR *bsr);
104 static int insert_tree_handler(void *ctx, int num_fields, char **row);
105 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex);
106 static int last_full_handler(void *ctx, int num_fields, char **row);
107 static int jobid_handler(void *ctx, int num_fields, char **row);
108 static int next_jobid_from_list(char **p, uint32_t *JobId);
109 static int user_select_jobids(UAContext *ua, JobIds *ji);
110 static void user_select_files(TREE_CTX *tree);
111 static int fileset_handler(void *ctx, int num_fields, char **row);
112 static void print_name_list(UAContext *ua, NAME_LIST *name_list);
113 static int unique_name_list_handler(void *ctx, int num_fields, char **row);
114 static void free_name_list(NAME_LIST *name_list);
115 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JobIds *ji);
122 int restorecmd(UAContext *ua, char *cmd)
126 JobId_t JobId, last_JobId;
132 JOB *restore_job = NULL;
133 int restore_jobs = 0;
135 uint32_t selected_files = 0;
139 i = find_arg_with_value(ua, "where");
148 memset(&tree, 0, sizeof(TREE_CTX));
149 memset(&name_list, 0, sizeof(name_list));
150 memset(&ji, 0, sizeof(ji));
152 /* Ensure there is at least one Restore Job */
154 while ( (job = (JOB *)GetNextRes(R_JOB, (RES *)job)) ) {
155 if (job->JobType == JT_RESTORE) {
165 "No Restore Job Resource found. You must create at least\n"
166 "one before running this command.\n"));
171 * Request user to select JobIds by various different methods
172 * last 20 jobs, where File saved, most recent backup, ...
174 if (!user_select_jobids(ua, &ji)) {
179 * Build the directory tree containing JobIds user selected
181 tree.root = new_tree(ji.TotalFiles);
182 tree.root->fname = nofname;
184 query = get_pool_memory(PM_MESSAGE);
187 * For display purposes, the same JobId, with different volumes may
188 * appear more than once, however, we only insert it once.
190 for (p=ji.JobIds; next_jobid_from_list(&p, &JobId) > 0; ) {
192 if (JobId == last_JobId) {
193 continue; /* eliminate duplicate JobIds */
196 bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
198 * Find files for this JobId and insert them in the tree
200 Mmsg(&query, uar_sel_files, JobId);
201 if (!db_sql_query(ua->db, query, insert_tree_handler, (void *)&tree)) {
202 bsendmsg(ua, "%s", db_strerror(ua->db));
205 * Find the FileSets for this JobId and add to the name_list
207 Mmsg(&query, uar_mediatype, JobId);
208 if (!db_sql_query(ua->db, query, unique_name_list_handler, (void *)&name_list)) {
209 bsendmsg(ua, "%s", db_strerror(ua->db));
213 bsendmsg(ua, "%d items inserted into the tree and marked for extraction.\n");
214 free_pool_memory(query);
216 /* Check MediaType and select storage that corresponds */
217 get_storage_from_mediatype(ua, &name_list, &ji);
218 free_name_list(&name_list);
220 /* Let the user select which files to restore */
221 user_select_files(&tree);
224 * Walk down through the tree finding all files marked to be
225 * extracted making a bootstrap file.
228 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
229 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
231 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
232 add_findex(bsr, node->JobId, node->FileIndex);
237 free_tree(tree.root); /* free the directory tree */
240 if (!complete_bsr(ua, bsr)) { /* find Vol, SessId, SessTime from JobIds */
241 bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
245 // print_bsr(ua, bsr);
246 write_bsr_file(ua, bsr);
247 bsendmsg(ua, _("\n%u files selected to restore.\n\n"), selected_files);
249 bsendmsg(ua, _("No files selected to restore.\n"));
253 if (restore_jobs == 1) {
256 job = select_restore_job_resource(ua);
259 bsendmsg(ua, _("No Restore Job resource found!\n"));
263 /* If no client name specified yet, get it now */
264 if (!ji.ClientName[0]) {
266 memset(&cr, 0, sizeof(cr));
267 if (!get_client_dbr(ua, &cr)) {
270 bstrncpy(ji.ClientName, cr.Name, sizeof(ji.ClientName));
273 /* Build run command */
276 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\""
278 job->hdr.name, ji.ClientName, ji.store?ji.store->hdr.name:"",
279 working_directory, where);
282 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
283 job->hdr.name, ji.ClientName, ji.store?ji.store->hdr.name:"",
286 Dmsg1(400, "Submitting: %s\n", ua->cmd);
291 bsendmsg(ua, _("Restore command done.\n"));
296 * The first step in the restore process is for the user to
297 * select a list of JobIds from which he will subsequently
298 * select which files are to be restored.
300 static int user_select_jobids(UAContext *ua, JobIds *ji)
302 char fileset_name[MAX_NAME_LENGTH];
311 "List last 20 Jobs run",
312 "List Jobs where a given File is saved",
313 "Enter list of JobIds to select",
314 "Enter SQL list command",
315 "Select the most recent backup for a client",
319 bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
320 "to be restored. You will be presented several methods\n"
321 "of specifying the JobIds. Then you will be allowed to\n"
322 "select which files from those JobIds are to be restored.\n\n"));
325 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
326 for (int i=0; list[i]; i++) {
327 add_prompt(ua, list[i]);
330 switch (do_prompt(ua, "Select item: ", NULL, 0)) {
333 case 0: /* list last 20 Jobs run */
334 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, 0);
337 case 1: /* list where a file is saved */
340 if (!get_cmd(ua, _("Enter Filename: "))) {
343 len = strlen(ua->cmd);
344 fname = (char *)malloc(len * 2 + 1);
345 db_escape_string(fname, ua->cmd, len);
346 query = get_pool_memory(PM_MESSAGE);
347 Mmsg(&query, uar_file, fname);
349 db_list_sql_query(ua->jcr, ua->db, query, prtit, ua, 1, 0);
350 free_pool_memory(query);
353 case 2: /* enter a list of JobIds */
354 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
357 bstrncpy(ji->JobIds, ua->cmd, sizeof(ji->JobIds));
359 case 3: /* Enter an SQL list command */
360 if (!get_cmd(ua, _("Enter SQL list command: "))) {
363 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, 0);
366 case 4: /* Select the most recent backups */
367 query = get_pool_memory(PM_MESSAGE);
368 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
369 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
370 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
371 bsendmsg(ua, "%s\n", db_strerror(ua->db));
373 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
374 bsendmsg(ua, "%s\n", db_strerror(ua->db));
377 * Select Client from the Catalog
379 memset(&cr, 0, sizeof(cr));
380 if (!get_client_dbr(ua, &cr)) {
381 free_pool_memory(query);
382 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
383 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
386 bstrncpy(ji->ClientName, cr.Name, sizeof(ji->ClientName));
391 Mmsg(&query, uar_sel_fileset, cr.ClientId, cr.ClientId);
392 start_prompt(ua, _("The defined FileSet resources are:\n"));
393 if (!db_sql_query(ua->db, query, fileset_handler, (void *)ua)) {
394 bsendmsg(ua, "%s\n", db_strerror(ua->db));
396 if (do_prompt(ua, _("Select FileSet resource"),
397 fileset_name, sizeof(fileset_name)) < 0) {
398 free_pool_memory(query);
399 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
400 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
403 fsr.FileSetId = atoi(fileset_name); /* Id is first part of name */
404 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
405 bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
406 bsendmsg(ua, _("This probably means you modified the FileSet.\n"
407 "Continuing anyway.\n"));
410 /* Find JobId of last Full backup for this client, fileset */
411 Mmsg(&query, uar_last_full, cr.ClientId, cr.ClientId, fsr.FileSetId);
412 if (!db_sql_query(ua->db, query, NULL, NULL)) {
413 bsendmsg(ua, "%s\n", db_strerror(ua->db));
415 /* Find all Volumes used by that JobId */
416 if (!db_sql_query(ua->db, uar_full, NULL,NULL)) {
417 bsendmsg(ua, "%s\n", db_strerror(ua->db));
419 /* Note, this is needed as I don't seem to get the callback
420 * from the call just above.
422 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)ji)) {
423 bsendmsg(ua, "%s\n", db_strerror(ua->db));
425 /* Now find all Incremental Jobs */
426 Mmsg(&query, uar_inc, edit_uint64(ji->JobTDate, ed1), cr.ClientId, fsr.FileSetId);
427 if (!db_sql_query(ua->db, query, NULL, NULL)) {
428 bsendmsg(ua, "%s\n", db_strerror(ua->db));
430 free_pool_memory(query);
431 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, 0);
433 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)ji)) {
434 bsendmsg(ua, "%s\n", db_strerror(ua->db));
436 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
437 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
444 if (*ji->JobIds == 0) {
445 bsendmsg(ua, _("No Jobs selected.\n"));
448 bsendmsg(ua, _("You have selected the following JobId: %s\n"), ji->JobIds);
450 memset(&jr, 0, sizeof(JOB_DBR));
453 for (p=ji->JobIds; ; ) {
454 int stat = next_jobid_from_list(&p, &JobId);
456 bsendmsg(ua, _("Invalid JobId in list.\n"));
463 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
464 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
467 ji->TotalFiles += jr.JobFiles;
472 static int next_jobid_from_list(char **p, uint32_t *JobId)
479 for (i=0; i<(int)sizeof(jobid); i++) {
480 if (*q == ',' || *q == 0) {
487 if (jobid[0] == 0 || !is_a_number(jobid)) {
491 *JobId = strtoul(jobid, NULL, 10);
496 * Callback handler make list of JobIds
498 static int jobid_handler(void *ctx, int num_fields, char **row)
500 JobIds *ji = (JobIds *)ctx;
502 if (strlen(ji->JobIds)+strlen(row[0])+2 < sizeof(ji->JobIds)) {
503 if (ji->JobIds[0] != 0) {
504 strcat(ji->JobIds, ",");
506 strcat(ji->JobIds, row[0]);
514 * Callback handler to pickup last Full backup JobId and ClientId
516 static int last_full_handler(void *ctx, int num_fields, char **row)
518 JobIds *ji = (JobIds *)ctx;
520 ji->JobTDate = strtoll(row[1], NULL, 10);
526 * Callback handler build fileset prompt list
528 static int fileset_handler(void *ctx, int num_fields, char **row)
530 char prompt[MAX_NAME_LENGTH+200];
532 snprintf(prompt, sizeof(prompt), "%s %s %s", row[0], row[1], row[2]);
533 add_prompt((UAContext *)ctx, prompt);
537 /* Forward referenced commands */
539 static int markcmd(UAContext *ua, TREE_CTX *tree);
540 static int countcmd(UAContext *ua, TREE_CTX *tree);
541 static int findcmd(UAContext *ua, TREE_CTX *tree);
542 static int lscmd(UAContext *ua, TREE_CTX *tree);
543 static int dircmd(UAContext *ua, TREE_CTX *tree);
544 static int helpcmd(UAContext *ua, TREE_CTX *tree);
545 static int cdcmd(UAContext *ua, TREE_CTX *tree);
546 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
547 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
548 static int quitcmd(UAContext *ua, TREE_CTX *tree);
551 struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; };
552 static struct cmdstruct commands[] = {
553 { N_("mark"), markcmd, _("mark file for restoration")},
554 { N_("unmark"), unmarkcmd, _("unmark file for restoration")},
555 { N_("cd"), cdcmd, _("change current directory")},
556 { N_("pwd"), pwdcmd, _("print current working directory")},
557 { N_("ls"), lscmd, _("list current directory")},
558 { N_("dir"), dircmd, _("list current directory")},
559 { N_("count"), countcmd, _("count marked files")},
560 { N_("find"), findcmd, _("find files")},
561 { N_("done"), quitcmd, _("leave file selection mode")},
562 { N_("exit"), quitcmd, _("exit = done")},
563 { N_("help"), helpcmd, _("print help")},
564 { N_("?"), helpcmd, _("print help")},
566 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
570 * Enter a prompt mode where the user can select/deselect
571 * files to be restored. This is sort of like a mini-shell
572 * that allows "cd", "pwd", "add", "rm", ...
574 static void user_select_files(TREE_CTX *tree)
578 bsendmsg(tree->ua, _(
579 "\nYou are now entering file selection mode where you add and\n"
580 "remove files to be restored. All files are initially added.\n"
581 "Enter \"done\" to leave this mode.\n\n"));
583 * Enter interactive command handler allowing selection
584 * of individual files.
586 tree->node = (TREE_NODE *)tree->root;
587 tree_getpath(tree->node, cwd, sizeof(cwd));
588 bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
590 int found, len, stat, i;
591 if (!get_cmd(tree->ua, "$ ")) {
594 parse_ua_args(tree->ua);
595 if (tree->ua->argc == 0) {
599 len = strlen(tree->ua->argk[0]);
602 for (i=0; i<(int)comsize; i++) /* search for command */
603 if (strncasecmp(tree->ua->argk[0], _(commands[i].key), len) == 0) {
604 stat = (*commands[i].func)(tree->ua, tree); /* go execute command */
609 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
619 * Create new FileIndex entry for BSR
621 static RBSR_FINDEX *new_findex()
623 RBSR_FINDEX *fi = (RBSR_FINDEX *)bmalloc(sizeof(RBSR_FINDEX));
624 memset(fi, 0, sizeof(RBSR_FINDEX));
628 /* Free all BSR FileIndex entries */
629 static void free_findex(RBSR_FINDEX *fi)
632 free_findex(fi->next);
637 static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd)
640 if (fi->findex == fi->findex2) {
641 fprintf(fd, "FileIndex=%d\n", fi->findex);
643 fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
645 write_findex(ua, fi->next, fd);
650 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
653 if (fi->findex == fi->findex2) {
654 bsendmsg(ua, "FileIndex=%d\n", fi->findex);
656 bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
658 print_findex(ua, fi->next);
662 /* Create a new bootstrap record */
663 static RBSR *new_bsr()
665 RBSR *bsr = (RBSR *)bmalloc(sizeof(RBSR));
666 memset(bsr, 0, sizeof(RBSR));
670 /* Free the entire BSR */
671 static void free_bsr(RBSR *bsr)
674 free_findex(bsr->fi);
676 if (bsr->VolParams) {
677 free(bsr->VolParams);
684 * Complete the BSR by filling in the VolumeName and
685 * VolSessionId and VolSessionTime using the JobId
687 static int complete_bsr(UAContext *ua, RBSR *bsr)
692 memset(&jr, 0, sizeof(jr));
693 jr.JobId = bsr->JobId;
694 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
695 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
698 bsr->VolSessionId = jr.VolSessionId;
699 bsr->VolSessionTime = jr.VolSessionTime;
700 if ((bsr->VolCount=db_get_job_volume_parameters(ua->jcr, ua->db, bsr->JobId,
701 &(bsr->VolParams))) == 0) {
702 bsendmsg(ua, _("Unable to get Job Volume Parameters. ERR=%s\n"), db_strerror(ua->db));
703 if (bsr->VolParams) {
704 free(bsr->VolParams);
705 bsr->VolParams = NULL;
709 return complete_bsr(ua, bsr->next);
715 * Write the bootstrap record to file
717 static int write_bsr_file(UAContext *ua, RBSR *bsr)
720 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
724 Mmsg(&fname, "%s/restore.bsr", working_directory);
725 fd = fopen(fname, "w+");
727 bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"),
728 fname, strerror(errno));
729 free_pool_memory(fname);
732 write_bsr(ua, bsr, fd);
735 bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
736 bsendmsg(ua, _("\nThe restore job will require the following Volumes:\n"));
737 /* Create Unique list of Volumes using prompt list */
738 start_prompt(ua, "");
739 for (nbsr=bsr; nbsr; nbsr=nbsr->next) {
740 for (int i=0; i < nbsr->VolCount; i++) {
741 add_prompt(ua, nbsr->VolParams[i].VolumeName);
744 for (int i=0; i < ua->num_prompts; i++) {
745 bsendmsg(ua, " %s\n", ua->prompt[i]);
750 free_pool_memory(fname);
754 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
757 for (int i=0; i < bsr->VolCount; i++) {
758 fprintf(fd, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
759 fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
760 fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
761 fprintf(fd, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile,
762 bsr->VolParams[i].EndFile);
763 fprintf(fd, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
764 bsr->VolParams[i].EndBlock);
765 write_findex(ua, bsr->fi, fd);
767 write_bsr(ua, bsr->next, fd);
771 static void print_bsr(UAContext *ua, RBSR *bsr)
774 for (int i=0; i < bsr->VolCount; i++) {
775 bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
776 bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
777 bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
778 bsendmsg(ua, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile,
779 bsr->VolParams[i].EndFile);
780 bsendmsg(ua, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
781 bsr->VolParams[i].EndBlock);
782 print_findex(ua, bsr->fi);
784 print_bsr(ua, bsr->next);
790 * Add a FileIndex to the list of BootStrap records.
791 * Here we are only dealing with JobId's and the FileIndexes
792 * associated with those JobIds.
794 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
797 RBSR_FINDEX *fi, *lfi;
800 return; /* probably a dummy directory */
803 if (!bsr->fi) { /* if no FI add one */
804 /* This is the first FileIndex item in the chain */
805 bsr->fi = new_findex();
807 bsr->fi->findex = findex;
808 bsr->fi->findex2 = findex;
811 /* Walk down list of bsrs until we find the JobId */
812 if (bsr->JobId != JobId) {
813 for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
814 if (nbsr->JobId == JobId) {
820 if (!nbsr) { /* Must add new JobId */
821 /* Add new JobId at end of chain */
822 for (nbsr=bsr; nbsr->next; nbsr=nbsr->next)
824 nbsr->next = new_bsr();
825 nbsr->next->JobId = JobId;
826 nbsr->next->fi = new_findex();
827 nbsr->next->fi->findex = findex;
828 nbsr->next->fi->findex2 = findex;
834 * At this point, bsr points to bsr containing JobId,
835 * and we are sure that there is at least one fi record.
838 /* Check if this findex is smaller than first item */
839 if (findex < fi->findex) {
840 if ((findex+1) == fi->findex) {
841 fi->findex = findex; /* extend down */
844 fi = new_findex(); /* yes, insert before first item */
846 fi->findex2 = findex;
851 /* Walk down fi chain and find where to insert insert new FileIndex */
852 for ( ; fi; fi=fi->next) {
853 if (findex == (fi->findex2 + 1)) { /* extend up */
855 fi->findex2 = findex;
856 if (fi->next && ((findex+1) == fi->next->findex)) {
858 fi->findex2 = nfi->findex2;
859 fi->next = nfi->next;
864 if (findex < fi->findex) { /* add before */
865 if ((findex+1) == fi->findex) {
873 /* Add to last place found */
876 fi->findex2 = findex;
877 fi->next = lfi->next;
883 * This callback routine is responsible for inserting the
884 * items it gets into the directory tree. For each JobId selected
885 * this routine is called once for each file. We do not allow
886 * duplicate filenames, but instead keep the info from the most
887 * recent file entered (i.e. the JobIds are assumed to be sorted)
889 static int insert_tree_handler(void *ctx, int num_fields, char **row)
891 TREE_CTX *tree = (TREE_CTX *)ctx;
893 TREE_NODE *node, *new_node;
896 strip_trailing_junk(row[1]);
898 if (*row[0] != '/') { /* Must be Win32 directory */
906 sprintf(fname, "%s%s", row[0], row[1]);
907 if (tree->avail_node) {
908 node = tree->avail_node;
910 node = new_tree_node(tree->root, type);
911 tree->avail_node = node;
913 Dmsg3(200, "FI=%d type=%d fname=%s\n", node->FileIndex, type, fname);
914 new_node = insert_tree_node(fname, node, tree->root, NULL);
915 /* Note, if node already exists, save new one for next time */
916 if (new_node != node) {
917 tree->avail_node = node;
919 tree->avail_node = NULL;
921 new_node->FileIndex = atoi(row[2]);
922 new_node->JobId = atoi(row[3]);
923 new_node->type = type;
924 new_node->extract = 1; /* extract all by default */
931 * Set extract to value passed. We recursively walk
932 * down the tree setting all children if the
933 * node is a directory.
935 static void set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, int value)
941 node->extract = value;
942 /* For a non-file (i.e. directory), we see all the children */
943 if (node->type != TN_FILE) {
944 for (n=node->child; n; n=n->sibling) {
945 set_extract(ua, n, tree, value);
949 /* Ordinary file, we get the full path, look up the
950 * attributes, decode them, and if we are hard linked to
951 * a file that was saved, we must load that file too.
953 tree_getpath(node, cwd, sizeof(cwd));
955 fdbr.JobId = node->JobId;
956 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
958 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
960 * If we point to a hard linked file, traverse the tree to
961 * find that file, and mark it for restoration as well. It
962 * must have the Link we just obtained and the same JobId.
965 for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
966 if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
976 static int markcmd(UAContext *ua, TREE_CTX *tree)
982 if (!tree->node->child) {
985 for (node = tree->node->child; node; node=node->sibling) {
986 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
987 set_extract(ua, node, tree, 1);
993 static int countcmd(UAContext *ua, TREE_CTX *tree)
998 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
999 if (node->type != TN_NEWDIR) {
1001 if (node->extract) {
1006 bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
1010 static int findcmd(UAContext *ua, TREE_CTX *tree)
1014 if (ua->argc == 1) {
1015 bsendmsg(ua, _("No file specification given.\n"));
1019 for (int i=1; i < ua->argc; i++) {
1020 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
1021 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
1022 tree_getpath(node, cwd, sizeof(cwd));
1023 bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
1032 static int lscmd(UAContext *ua, TREE_CTX *tree)
1036 if (!tree->node->child) {
1039 for (node = tree->node->child; node; node=node->sibling) {
1040 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1041 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1042 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1048 extern char *getuser(uid_t uid);
1049 extern char *getgroup(gid_t gid);
1052 * This is actually the long form used for "dir"
1054 static void ls_output(char *buf, char *fname, int extract, struct stat *statp)
1060 p = encode_mode(statp->st_mode, buf);
1061 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
1063 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
1065 n = sprintf(p, "%8.8s ", edit_uint64(statp->st_size, ec1));
1067 p = encode_time(statp->st_ctime, p);
1081 * Like ls command, but give more detail on each file
1083 static int dircmd(UAContext *ua, TREE_CTX *tree)
1091 if (!tree->node->child) {
1094 for (node = tree->node->child; node; node=node->sibling) {
1095 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1096 tree_getpath(node, cwd, sizeof(cwd));
1098 fdbr.JobId = node->JobId;
1099 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
1101 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
1102 ls_output(buf, cwd, node->extract, &statp);
1103 bsendmsg(ua, "%s\n", buf);
1105 /* Something went wrong getting attributes -- print name */
1106 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1107 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1115 static int helpcmd(UAContext *ua, TREE_CTX *tree)
1120 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
1121 for (i=0; i<comsize; i++) {
1122 bsendmsg(ua, _(" %-10s %s\n"), _(commands[i].key), _(commands[i].help));
1129 * Change directories. Note, if the user specifies x: and it fails,
1130 * we assume it is a Win32 absolute cd rather than relative and
1131 * try a second time with /x: ... Win32 kludge.
1133 static int cdcmd(UAContext *ua, TREE_CTX *tree)
1138 if (ua->argc != 2) {
1141 node = tree_cwd(ua->argk[1], tree->root, tree->node);
1143 /* Try once more if Win32 drive -- make absolute */
1144 if (ua->argk[1][1] == ':') { /* win32 drive */
1146 strcat(cwd, ua->argk[1]);
1147 node = tree_cwd(cwd, tree->root, tree->node);
1150 bsendmsg(ua, _("Invalid path given.\n"));
1157 tree_getpath(tree->node, cwd, sizeof(cwd));
1158 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1162 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
1165 tree_getpath(tree->node, cwd, sizeof(cwd));
1166 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1171 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
1177 if (!tree->node->child) {
1180 for (node = tree->node->child; node; node=node->sibling) {
1181 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1182 set_extract(ua, node, tree, 0);
1188 static int quitcmd(UAContext *ua, TREE_CTX *tree)
1195 * Called here with each name to be added to the list. The name is
1196 * added to the list if it is not already in the list.
1198 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1200 NAME_LIST *name = (NAME_LIST *)ctx;
1202 if (name->num_ids == MAX_ID_LIST_LEN) {
1205 if (name->num_ids == name->max_ids) {
1206 if (name->max_ids == 0) {
1207 name->max_ids = 1000;
1208 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1210 name->max_ids = (name->max_ids * 3) / 2;
1211 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1214 for (int i=0; i<name->num_ids; i++) {
1215 if (strcmp(name->name[i], row[0]) == 0) {
1216 return 0; /* already in list, return */
1219 /* Add new name to list */
1220 name->name[name->num_ids++] = bstrdup(row[0]);
1226 * Print names in the list
1228 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1232 for (i=0; i < name_list->num_ids; i++) {
1233 bsendmsg(ua, "%s\n", name_list->name[i]);
1239 * Free names in the list
1241 static void free_name_list(NAME_LIST *name_list)
1245 for (i=0; i < name_list->num_ids; i++) {
1246 free(name_list->name[i]);
1248 if (name_list->name) {
1249 free(name_list->name);
1251 name_list->max_ids = 0;
1252 name_list->num_ids = 0;
1255 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JobIds *ji)
1257 char name[MAX_NAME_LENGTH];
1258 STORE *store = NULL;
1260 if (name_list->num_ids > 1) {
1261 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1262 "Restore is not possible. The MediaTypes used are:\n"));
1263 print_name_list(ua, name_list);
1264 ji->store = select_storage_resource(ua);
1268 if (name_list->num_ids == 0) {
1269 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1270 ji->store = select_storage_resource(ua);
1274 start_prompt(ua, _("The defined Storage resources are:\n"));
1276 while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
1277 if (strcmp(store->media_type, name_list->name[0]) == 0) {
1278 add_prompt(ua, store->hdr.name);
1282 do_prompt(ua, _("Select Storage resource"), name, sizeof(name));
1283 ji->store = (STORE *)GetResWithName(R_STORAGE, name);
1285 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1286 "MediaType %s, needed by the Jobs you selected.\n"
1287 "You will be allowed to select a Storage device later.\n"),
1288 name_list->name[0]);