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 * First sort the bsr chain, then sort the VolParams
769 static RBSR *sort_bsr(RBSR *bsr)
774 /* ****FIXME**** sort the bsr chain */
775 for (RBSR *nbsr=bsr; nbsr; nbsr=nbsr->next) {
780 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
783 for (int i=0; i < bsr->VolCount; i++) {
784 fprintf(fd, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
785 fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
786 fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
787 fprintf(fd, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile,
788 bsr->VolParams[i].EndFile);
789 fprintf(fd, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
790 bsr->VolParams[i].EndBlock);
791 write_findex(ua, bsr->fi, fd);
793 write_bsr(ua, bsr->next, fd);
797 static void print_bsr(UAContext *ua, RBSR *bsr)
800 for (int i=0; i < bsr->VolCount; i++) {
801 bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
802 bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
803 bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
804 bsendmsg(ua, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile,
805 bsr->VolParams[i].EndFile);
806 bsendmsg(ua, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
807 bsr->VolParams[i].EndBlock);
808 print_findex(ua, bsr->fi);
810 print_bsr(ua, bsr->next);
816 * Add a FileIndex to the list of BootStrap records.
817 * Here we are only dealing with JobId's and the FileIndexes
818 * associated with those JobIds.
820 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
823 RBSR_FINDEX *fi, *lfi;
826 return; /* probably a dummy directory */
829 if (!bsr->fi) { /* if no FI add one */
830 /* This is the first FileIndex item in the chain */
831 bsr->fi = new_findex();
833 bsr->fi->findex = findex;
834 bsr->fi->findex2 = findex;
837 /* Walk down list of bsrs until we find the JobId */
838 if (bsr->JobId != JobId) {
839 for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
840 if (nbsr->JobId == JobId) {
846 if (!nbsr) { /* Must add new JobId */
847 /* Add new JobId at end of chain */
848 for (nbsr=bsr; nbsr->next; nbsr=nbsr->next)
850 nbsr->next = new_bsr();
851 nbsr->next->JobId = JobId;
852 nbsr->next->fi = new_findex();
853 nbsr->next->fi->findex = findex;
854 nbsr->next->fi->findex2 = findex;
860 * At this point, bsr points to bsr containing JobId,
861 * and we are sure that there is at least one fi record.
864 /* Check if this findex is smaller than first item */
865 if (findex < fi->findex) {
866 if ((findex+1) == fi->findex) {
867 fi->findex = findex; /* extend down */
870 fi = new_findex(); /* yes, insert before first item */
872 fi->findex2 = findex;
877 /* Walk down fi chain and find where to insert insert new FileIndex */
878 for ( ; fi; fi=fi->next) {
879 if (findex == (fi->findex2 + 1)) { /* extend up */
881 fi->findex2 = findex;
882 if (fi->next && ((findex+1) == fi->next->findex)) {
884 fi->findex2 = nfi->findex2;
885 fi->next = nfi->next;
890 if (findex < fi->findex) { /* add before */
891 if ((findex+1) == fi->findex) {
899 /* Add to last place found */
902 fi->findex2 = findex;
903 fi->next = lfi->next;
909 * This callback routine is responsible for inserting the
910 * items it gets into the directory tree. For each JobId selected
911 * this routine is called once for each file. We do not allow
912 * duplicate filenames, but instead keep the info from the most
913 * recent file entered (i.e. the JobIds are assumed to be sorted)
915 static int insert_tree_handler(void *ctx, int num_fields, char **row)
917 TREE_CTX *tree = (TREE_CTX *)ctx;
919 TREE_NODE *node, *new_node;
922 strip_trailing_junk(row[1]);
924 if (*row[0] != '/') { /* Must be Win32 directory */
932 sprintf(fname, "%s%s", row[0], row[1]);
933 if (tree->avail_node) {
934 node = tree->avail_node;
936 node = new_tree_node(tree->root, type);
937 tree->avail_node = node;
939 Dmsg3(200, "FI=%d type=%d fname=%s\n", node->FileIndex, type, fname);
940 new_node = insert_tree_node(fname, node, tree->root, NULL);
941 /* Note, if node already exists, save new one for next time */
942 if (new_node != node) {
943 tree->avail_node = node;
945 tree->avail_node = NULL;
947 new_node->FileIndex = atoi(row[2]);
948 new_node->JobId = atoi(row[3]);
949 new_node->type = type;
950 new_node->extract = 1; /* extract all by default */
957 * Set extract to value passed. We recursively walk
958 * down the tree setting all children if the
959 * node is a directory.
961 static void set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, int value)
967 node->extract = value;
968 /* For a non-file (i.e. directory), we see all the children */
969 if (node->type != TN_FILE) {
970 for (n=node->child; n; n=n->sibling) {
971 set_extract(ua, n, tree, value);
975 /* Ordinary file, we get the full path, look up the
976 * attributes, decode them, and if we are hard linked to
977 * a file that was saved, we must load that file too.
979 tree_getpath(node, cwd, sizeof(cwd));
981 fdbr.JobId = node->JobId;
982 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
984 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
986 * If we point to a hard linked file, traverse the tree to
987 * find that file, and mark it for restoration as well. It
988 * must have the Link we just obtained and the same JobId.
991 for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
992 if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
1002 static int markcmd(UAContext *ua, TREE_CTX *tree)
1008 if (!tree->node->child) {
1011 for (node = tree->node->child; node; node=node->sibling) {
1012 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1013 set_extract(ua, node, tree, 1);
1019 static int countcmd(UAContext *ua, TREE_CTX *tree)
1023 total = extract = 0;
1024 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
1025 if (node->type != TN_NEWDIR) {
1027 if (node->extract) {
1032 bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
1036 static int findcmd(UAContext *ua, TREE_CTX *tree)
1040 if (ua->argc == 1) {
1041 bsendmsg(ua, _("No file specification given.\n"));
1045 for (int i=1; i < ua->argc; i++) {
1046 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
1047 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
1048 tree_getpath(node, cwd, sizeof(cwd));
1049 bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
1058 static int lscmd(UAContext *ua, TREE_CTX *tree)
1062 if (!tree->node->child) {
1065 for (node = tree->node->child; node; node=node->sibling) {
1066 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1067 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1068 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1074 extern char *getuser(uid_t uid);
1075 extern char *getgroup(gid_t gid);
1078 * This is actually the long form used for "dir"
1080 static void ls_output(char *buf, char *fname, int extract, struct stat *statp)
1086 p = encode_mode(statp->st_mode, buf);
1087 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
1089 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
1091 n = sprintf(p, "%8.8s ", edit_uint64(statp->st_size, ec1));
1093 p = encode_time(statp->st_ctime, p);
1107 * Like ls command, but give more detail on each file
1109 static int dircmd(UAContext *ua, TREE_CTX *tree)
1117 if (!tree->node->child) {
1120 for (node = tree->node->child; node; node=node->sibling) {
1121 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1122 tree_getpath(node, cwd, sizeof(cwd));
1124 fdbr.JobId = node->JobId;
1125 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
1127 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
1128 ls_output(buf, cwd, node->extract, &statp);
1129 bsendmsg(ua, "%s\n", buf);
1131 /* Something went wrong getting attributes -- print name */
1132 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1133 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1141 static int helpcmd(UAContext *ua, TREE_CTX *tree)
1146 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
1147 for (i=0; i<comsize; i++) {
1148 bsendmsg(ua, _(" %-10s %s\n"), _(commands[i].key), _(commands[i].help));
1155 * Change directories. Note, if the user specifies x: and it fails,
1156 * we assume it is a Win32 absolute cd rather than relative and
1157 * try a second time with /x: ... Win32 kludge.
1159 static int cdcmd(UAContext *ua, TREE_CTX *tree)
1164 if (ua->argc != 2) {
1167 node = tree_cwd(ua->argk[1], tree->root, tree->node);
1169 /* Try once more if Win32 drive -- make absolute */
1170 if (ua->argk[1][1] == ':') { /* win32 drive */
1172 strcat(cwd, ua->argk[1]);
1173 node = tree_cwd(cwd, tree->root, tree->node);
1176 bsendmsg(ua, _("Invalid path given.\n"));
1183 tree_getpath(tree->node, cwd, sizeof(cwd));
1184 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1188 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
1191 tree_getpath(tree->node, cwd, sizeof(cwd));
1192 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1197 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
1203 if (!tree->node->child) {
1206 for (node = tree->node->child; node; node=node->sibling) {
1207 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1208 set_extract(ua, node, tree, 0);
1214 static int quitcmd(UAContext *ua, TREE_CTX *tree)
1221 * Called here with each name to be added to the list. The name is
1222 * added to the list if it is not already in the list.
1224 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1226 NAME_LIST *name = (NAME_LIST *)ctx;
1228 if (name->num_ids == MAX_ID_LIST_LEN) {
1231 if (name->num_ids == name->max_ids) {
1232 if (name->max_ids == 0) {
1233 name->max_ids = 1000;
1234 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1236 name->max_ids = (name->max_ids * 3) / 2;
1237 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1240 for (int i=0; i<name->num_ids; i++) {
1241 if (strcmp(name->name[i], row[0]) == 0) {
1242 return 0; /* already in list, return */
1245 /* Add new name to list */
1246 name->name[name->num_ids++] = bstrdup(row[0]);
1252 * Print names in the list
1254 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1258 for (i=0; i < name_list->num_ids; i++) {
1259 bsendmsg(ua, "%s\n", name_list->name[i]);
1265 * Free names in the list
1267 static void free_name_list(NAME_LIST *name_list)
1271 for (i=0; i < name_list->num_ids; i++) {
1272 free(name_list->name[i]);
1274 if (name_list->name) {
1275 free(name_list->name);
1277 name_list->max_ids = 0;
1278 name_list->num_ids = 0;
1281 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JOBIDS *ji)
1283 char name[MAX_NAME_LENGTH];
1284 STORE *store = NULL;
1286 if (name_list->num_ids > 1) {
1287 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1288 "Restore is not possible. The MediaTypes used are:\n"));
1289 print_name_list(ua, name_list);
1290 ji->store = select_storage_resource(ua);
1294 if (name_list->num_ids == 0) {
1295 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1296 ji->store = select_storage_resource(ua);
1300 start_prompt(ua, _("The defined Storage resources are:\n"));
1302 while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
1303 if (strcmp(store->media_type, name_list->name[0]) == 0) {
1304 add_prompt(ua, store->hdr.name);
1308 do_prompt(ua, _("Select Storage resource"), name, sizeof(name));
1309 ji->store = (STORE *)GetResWithName(R_STORAGE, name);
1311 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1312 "MediaType %s, needed by the Jobs you selected.\n"
1313 "You will be allowed to select a Storage device later.\n"),
1314 name_list->name[0]);