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.
199 for (p=ji.JobIds; next_jobid_from_list(&p, &JobId) > 0; ) {
201 if (JobId == last_JobId) {
202 continue; /* eliminate duplicate JobIds */
205 bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
208 * Find files for this JobId and insert them in the tree
210 Mmsg(&query, uar_sel_files, JobId);
211 if (!db_sql_query(ua->db, query, insert_tree_handler, (void *)&tree)) {
212 bsendmsg(ua, "%s", db_strerror(ua->db));
215 * Find the FileSets for this JobId and add to the name_list
217 Mmsg(&query, uar_mediatype, JobId);
218 if (!db_sql_query(ua->db, query, unique_name_list_handler, (void *)&name_list)) {
219 bsendmsg(ua, "%s", db_strerror(ua->db));
223 bsendmsg(ua, "%d items inserted into the tree and marked for extraction.\n", items);
224 free_pool_memory(query);
226 /* Check MediaType and select storage that corresponds */
227 get_storage_from_mediatype(ua, &name_list, &ji);
228 free_name_list(&name_list);
230 /* Let the user select which files to restore */
231 user_select_files(&tree);
234 * Walk down through the tree finding all files marked to be
235 * extracted making a bootstrap file.
238 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
239 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
241 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
242 add_findex(bsr, node->JobId, node->FileIndex);
247 free_tree(tree.root); /* free the directory tree */
250 if (!complete_bsr(ua, bsr)) { /* find Vol, SessId, SessTime from JobIds */
251 bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
255 // print_bsr(ua, bsr);
256 write_bsr_file(ua, bsr);
257 bsendmsg(ua, _("\n%u files selected to restore.\n\n"), selected_files);
259 bsendmsg(ua, _("No files selected to restore.\n"));
263 if (restore_jobs == 1) {
266 job = select_restore_job_resource(ua);
269 bsendmsg(ua, _("No Restore Job resource found!\n"));
273 /* If no client name specified yet, get it now */
274 if (!ji.ClientName[0]) {
276 memset(&cr, 0, sizeof(cr));
277 if (!get_client_dbr(ua, &cr)) {
280 bstrncpy(ji.ClientName, cr.Name, sizeof(ji.ClientName));
283 /* Build run command */
286 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\""
288 job->hdr.name, ji.ClientName, ji.store?ji.store->hdr.name:"",
289 working_directory, where);
292 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
293 job->hdr.name, ji.ClientName, ji.store?ji.store->hdr.name:"",
296 Dmsg1(400, "Submitting: %s\n", ua->cmd);
301 bsendmsg(ua, _("Restore command done.\n"));
306 * The first step in the restore process is for the user to
307 * select a list of JobIds from which he will subsequently
308 * select which files are to be restored.
310 static int user_select_jobids(UAContext *ua, JOBIDS *ji)
312 char fileset_name[MAX_NAME_LENGTH];
321 "List last 20 Jobs run",
322 "List Jobs where a given File is saved",
323 "Enter list of JobIds to select",
324 "Enter SQL list command",
325 "Select the most recent backup for a client",
329 bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
330 "to be restored. You will be presented several methods\n"
331 "of specifying the JobIds. Then you will be allowed to\n"
332 "select which files from those JobIds are to be restored.\n\n"));
335 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
336 for (int i=0; list[i]; i++) {
337 add_prompt(ua, list[i]);
340 switch (do_prompt(ua, "Select item: ", NULL, 0)) {
343 case 0: /* list last 20 Jobs run */
344 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, 0);
347 case 1: /* list where a file is saved */
350 if (!get_cmd(ua, _("Enter Filename: "))) {
353 len = strlen(ua->cmd);
354 fname = (char *)malloc(len * 2 + 1);
355 db_escape_string(fname, ua->cmd, len);
356 query = get_pool_memory(PM_MESSAGE);
357 Mmsg(&query, uar_file, fname);
359 db_list_sql_query(ua->jcr, ua->db, query, prtit, ua, 1, 0);
360 free_pool_memory(query);
363 case 2: /* enter a list of JobIds */
364 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
367 bstrncpy(ji->JobIds, ua->cmd, sizeof(ji->JobIds));
369 case 3: /* Enter an SQL list command */
370 if (!get_cmd(ua, _("Enter SQL list command: "))) {
373 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, 0);
376 case 4: /* Select the most recent backups */
377 query = get_pool_memory(PM_MESSAGE);
378 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
379 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
380 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
381 bsendmsg(ua, "%s\n", db_strerror(ua->db));
383 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
384 bsendmsg(ua, "%s\n", db_strerror(ua->db));
387 * Select Client from the Catalog
389 memset(&cr, 0, sizeof(cr));
390 if (!get_client_dbr(ua, &cr)) {
391 free_pool_memory(query);
392 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
393 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
396 bstrncpy(ji->ClientName, cr.Name, sizeof(ji->ClientName));
401 Mmsg(&query, uar_sel_fileset, cr.ClientId, cr.ClientId);
402 start_prompt(ua, _("The defined FileSet resources are:\n"));
403 if (!db_sql_query(ua->db, query, fileset_handler, (void *)ua)) {
404 bsendmsg(ua, "%s\n", db_strerror(ua->db));
406 if (do_prompt(ua, _("Select FileSet resource"),
407 fileset_name, sizeof(fileset_name)) < 0) {
408 free_pool_memory(query);
409 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
410 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
413 fsr.FileSetId = atoi(fileset_name); /* Id is first part of name */
414 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
415 bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
416 bsendmsg(ua, _("This probably means you modified the FileSet.\n"
417 "Continuing anyway.\n"));
420 /* Find JobId of last Full backup for this client, fileset */
421 Mmsg(&query, uar_last_full, cr.ClientId, cr.ClientId, fsr.FileSetId);
422 if (!db_sql_query(ua->db, query, NULL, NULL)) {
423 bsendmsg(ua, "%s\n", db_strerror(ua->db));
425 /* Find all Volumes used by that JobId */
426 if (!db_sql_query(ua->db, uar_full, NULL,NULL)) {
427 bsendmsg(ua, "%s\n", db_strerror(ua->db));
429 /* Note, this is needed as I don't seem to get the callback
430 * from the call just above.
432 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)ji)) {
433 bsendmsg(ua, "%s\n", db_strerror(ua->db));
435 /* Now find all Incremental Jobs */
436 Mmsg(&query, uar_inc, edit_uint64(ji->JobTDate, ed1), cr.ClientId, fsr.FileSetId);
437 if (!db_sql_query(ua->db, query, NULL, NULL)) {
438 bsendmsg(ua, "%s\n", db_strerror(ua->db));
440 free_pool_memory(query);
441 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, 0);
443 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)ji)) {
444 bsendmsg(ua, "%s\n", db_strerror(ua->db));
446 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
447 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
454 if (*ji->JobIds == 0) {
455 bsendmsg(ua, _("No Jobs selected.\n"));
458 bsendmsg(ua, _("You have selected the following JobId: %s\n"), ji->JobIds);
460 memset(&jr, 0, sizeof(JOB_DBR));
463 for (p=ji->JobIds; ; ) {
464 int stat = next_jobid_from_list(&p, &JobId);
466 bsendmsg(ua, _("Invalid JobId in list.\n"));
473 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
474 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
477 ji->TotalFiles += jr.JobFiles;
482 static int next_jobid_from_list(char **p, uint32_t *JobId)
489 for (i=0; i<(int)sizeof(jobid); i++) {
490 if (*q == ',' || *q == 0) {
497 if (jobid[0] == 0 || !is_a_number(jobid)) {
501 *JobId = strtoul(jobid, NULL, 10);
506 * Callback handler make list of JobIds
508 static int jobid_handler(void *ctx, int num_fields, char **row)
510 JOBIDS *ji = (JOBIDS *)ctx;
512 if (strlen(ji->JobIds)+strlen(row[0])+2 < sizeof(ji->JobIds)) {
513 if (ji->JobIds[0] != 0) {
514 strcat(ji->JobIds, ",");
516 strcat(ji->JobIds, row[0]);
524 * Callback handler to pickup last Full backup JobId and ClientId
526 static int last_full_handler(void *ctx, int num_fields, char **row)
528 JOBIDS *ji = (JOBIDS *)ctx;
530 ji->JobTDate = strtoll(row[1], NULL, 10);
536 * Callback handler build fileset prompt list
538 static int fileset_handler(void *ctx, int num_fields, char **row)
540 char prompt[MAX_NAME_LENGTH+200];
542 snprintf(prompt, sizeof(prompt), "%s %s %s", row[0], row[1], row[2]);
543 add_prompt((UAContext *)ctx, prompt);
547 /* Forward referenced commands */
549 static int markcmd(UAContext *ua, TREE_CTX *tree);
550 static int countcmd(UAContext *ua, TREE_CTX *tree);
551 static int findcmd(UAContext *ua, TREE_CTX *tree);
552 static int lscmd(UAContext *ua, TREE_CTX *tree);
553 static int dircmd(UAContext *ua, TREE_CTX *tree);
554 static int helpcmd(UAContext *ua, TREE_CTX *tree);
555 static int cdcmd(UAContext *ua, TREE_CTX *tree);
556 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
557 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
558 static int quitcmd(UAContext *ua, TREE_CTX *tree);
561 struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; };
562 static struct cmdstruct commands[] = {
563 { N_("mark"), markcmd, _("mark file for restoration")},
564 { N_("unmark"), unmarkcmd, _("unmark file for restoration")},
565 { N_("cd"), cdcmd, _("change current directory")},
566 { N_("pwd"), pwdcmd, _("print current working directory")},
567 { N_("ls"), lscmd, _("list current directory")},
568 { N_("dir"), dircmd, _("list current directory")},
569 { N_("count"), countcmd, _("count marked files")},
570 { N_("find"), findcmd, _("find files")},
571 { N_("done"), quitcmd, _("leave file selection mode")},
572 { N_("exit"), quitcmd, _("exit = done")},
573 { N_("help"), helpcmd, _("print help")},
574 { N_("?"), helpcmd, _("print help")},
576 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
580 * Enter a prompt mode where the user can select/deselect
581 * files to be restored. This is sort of like a mini-shell
582 * that allows "cd", "pwd", "add", "rm", ...
584 static void user_select_files(TREE_CTX *tree)
588 bsendmsg(tree->ua, _(
589 "\nYou are now entering file selection mode where you add and\n"
590 "remove files to be restored. All files are initially added.\n"
591 "Enter \"done\" to leave this mode.\n\n"));
593 * Enter interactive command handler allowing selection
594 * of individual files.
596 tree->node = (TREE_NODE *)tree->root;
597 tree_getpath(tree->node, cwd, sizeof(cwd));
598 bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
600 int found, len, stat, i;
601 if (!get_cmd(tree->ua, "$ ")) {
604 parse_ua_args(tree->ua);
605 if (tree->ua->argc == 0) {
609 len = strlen(tree->ua->argk[0]);
612 for (i=0; i<(int)comsize; i++) /* search for command */
613 if (strncasecmp(tree->ua->argk[0], _(commands[i].key), len) == 0) {
614 stat = (*commands[i].func)(tree->ua, tree); /* go execute command */
619 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
629 * Create new FileIndex entry for BSR
631 static RBSR_FINDEX *new_findex()
633 RBSR_FINDEX *fi = (RBSR_FINDEX *)bmalloc(sizeof(RBSR_FINDEX));
634 memset(fi, 0, sizeof(RBSR_FINDEX));
638 /* Free all BSR FileIndex entries */
639 static void free_findex(RBSR_FINDEX *fi)
642 free_findex(fi->next);
647 static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd)
650 if (fi->findex == fi->findex2) {
651 fprintf(fd, "FileIndex=%d\n", fi->findex);
653 fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
655 write_findex(ua, fi->next, fd);
660 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
663 if (fi->findex == fi->findex2) {
664 bsendmsg(ua, "FileIndex=%d\n", fi->findex);
666 bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
668 print_findex(ua, fi->next);
672 /* Create a new bootstrap record */
673 static RBSR *new_bsr()
675 RBSR *bsr = (RBSR *)bmalloc(sizeof(RBSR));
676 memset(bsr, 0, sizeof(RBSR));
680 /* Free the entire BSR */
681 static void free_bsr(RBSR *bsr)
684 free_findex(bsr->fi);
686 if (bsr->VolParams) {
687 free(bsr->VolParams);
694 * Complete the BSR by filling in the VolumeName and
695 * VolSessionId and VolSessionTime using the JobId
697 static int complete_bsr(UAContext *ua, RBSR *bsr)
702 memset(&jr, 0, sizeof(jr));
703 jr.JobId = bsr->JobId;
704 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
705 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
708 bsr->VolSessionId = jr.VolSessionId;
709 bsr->VolSessionTime = jr.VolSessionTime;
710 if ((bsr->VolCount=db_get_job_volume_parameters(ua->jcr, ua->db, bsr->JobId,
711 &(bsr->VolParams))) == 0) {
712 bsendmsg(ua, _("Unable to get Job Volume Parameters. ERR=%s\n"), db_strerror(ua->db));
713 if (bsr->VolParams) {
714 free(bsr->VolParams);
715 bsr->VolParams = NULL;
719 return complete_bsr(ua, bsr->next);
725 * Write the bootstrap record to file
727 static int write_bsr_file(UAContext *ua, RBSR *bsr)
730 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
733 Mmsg(&fname, "%s/restore.bsr", working_directory);
734 fd = fopen(fname, "w+");
736 bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"),
737 fname, strerror(errno));
738 free_pool_memory(fname);
741 /* Sort the bsr chain */
743 /* Write them to file */
744 write_bsr(ua, bsr, fd);
747 bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
749 /* Tell the user what he will need to mount */
750 bsendmsg(ua, _("\nThe restore job will require the following Volumes:\n"));
751 /* Create Unique list of Volumes using prompt list */
752 start_prompt(ua, "");
753 for (RBSR *nbsr=bsr; nbsr; nbsr=nbsr->next) {
754 for (int i=0; i < nbsr->VolCount; i++) {
755 add_prompt(ua, nbsr->VolParams[i].VolumeName);
758 for (int i=0; i < ua->num_prompts; i++) {
759 bsendmsg(ua, " %s\n", ua->prompt[i]);
764 free_pool_memory(fname);
769 * First sort the bsr chain, then sort the VolParams
771 static RBSR *sort_bsr(RBSR *bsr)
776 /* ****FIXME**** sort the bsr chain */
777 for (RBSR *nbsr=bsr; nbsr; nbsr=nbsr->next) {
782 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
785 for (int i=0; i < bsr->VolCount; i++) {
786 fprintf(fd, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
787 fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
788 fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
789 fprintf(fd, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile,
790 bsr->VolParams[i].EndFile);
791 fprintf(fd, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
792 bsr->VolParams[i].EndBlock);
793 write_findex(ua, bsr->fi, fd);
795 write_bsr(ua, bsr->next, fd);
799 static void print_bsr(UAContext *ua, RBSR *bsr)
802 for (int i=0; i < bsr->VolCount; i++) {
803 bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
804 bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
805 bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
806 bsendmsg(ua, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile,
807 bsr->VolParams[i].EndFile);
808 bsendmsg(ua, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
809 bsr->VolParams[i].EndBlock);
810 print_findex(ua, bsr->fi);
812 print_bsr(ua, bsr->next);
818 * Add a FileIndex to the list of BootStrap records.
819 * Here we are only dealing with JobId's and the FileIndexes
820 * associated with those JobIds.
822 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
825 RBSR_FINDEX *fi, *lfi;
828 return; /* probably a dummy directory */
831 if (!bsr->fi) { /* if no FI add one */
832 /* This is the first FileIndex item in the chain */
833 bsr->fi = new_findex();
835 bsr->fi->findex = findex;
836 bsr->fi->findex2 = findex;
839 /* Walk down list of bsrs until we find the JobId */
840 if (bsr->JobId != JobId) {
841 for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
842 if (nbsr->JobId == JobId) {
848 if (!nbsr) { /* Must add new JobId */
849 /* Add new JobId at end of chain */
850 for (nbsr=bsr; nbsr->next; nbsr=nbsr->next)
852 nbsr->next = new_bsr();
853 nbsr->next->JobId = JobId;
854 nbsr->next->fi = new_findex();
855 nbsr->next->fi->findex = findex;
856 nbsr->next->fi->findex2 = findex;
862 * At this point, bsr points to bsr containing JobId,
863 * and we are sure that there is at least one fi record.
866 /* Check if this findex is smaller than first item */
867 if (findex < fi->findex) {
868 if ((findex+1) == fi->findex) {
869 fi->findex = findex; /* extend down */
872 fi = new_findex(); /* yes, insert before first item */
874 fi->findex2 = findex;
879 /* Walk down fi chain and find where to insert insert new FileIndex */
880 for ( ; fi; fi=fi->next) {
881 if (findex == (fi->findex2 + 1)) { /* extend up */
883 fi->findex2 = findex;
884 if (fi->next && ((findex+1) == fi->next->findex)) {
886 fi->findex2 = nfi->findex2;
887 fi->next = nfi->next;
892 if (findex < fi->findex) { /* add before */
893 if ((findex+1) == fi->findex) {
901 /* Add to last place found */
904 fi->findex2 = findex;
905 fi->next = lfi->next;
911 * This callback routine is responsible for inserting the
912 * items it gets into the directory tree. For each JobId selected
913 * this routine is called once for each file. We do not allow
914 * duplicate filenames, but instead keep the info from the most
915 * recent file entered (i.e. the JobIds are assumed to be sorted)
917 static int insert_tree_handler(void *ctx, int num_fields, char **row)
919 TREE_CTX *tree = (TREE_CTX *)ctx;
921 TREE_NODE *node, *new_node;
924 strip_trailing_junk(row[1]);
926 if (*row[0] != '/') { /* Must be Win32 directory */
934 sprintf(fname, "%s%s", row[0], row[1]);
935 if (tree->avail_node) {
936 node = tree->avail_node;
938 node = new_tree_node(tree->root, type);
939 tree->avail_node = node;
941 Dmsg3(200, "FI=%d type=%d fname=%s\n", node->FileIndex, type, fname);
942 new_node = insert_tree_node(fname, node, tree->root, NULL);
943 /* Note, if node already exists, save new one for next time */
944 if (new_node != node) {
945 tree->avail_node = node;
947 tree->avail_node = NULL;
949 new_node->FileIndex = atoi(row[2]);
950 new_node->JobId = atoi(row[3]);
951 new_node->type = type;
952 new_node->extract = 1; /* extract all by default */
959 * Set extract to value passed. We recursively walk
960 * down the tree setting all children if the
961 * node is a directory.
963 static void set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, int value)
969 node->extract = value;
970 /* For a non-file (i.e. directory), we see all the children */
971 if (node->type != TN_FILE) {
972 for (n=node->child; n; n=n->sibling) {
973 set_extract(ua, n, tree, value);
977 /* Ordinary file, we get the full path, look up the
978 * attributes, decode them, and if we are hard linked to
979 * a file that was saved, we must load that file too.
981 tree_getpath(node, cwd, sizeof(cwd));
983 fdbr.JobId = node->JobId;
984 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
986 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
988 * If we point to a hard linked file, traverse the tree to
989 * find that file, and mark it for restoration as well. It
990 * must have the Link we just obtained and the same JobId.
993 for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
994 if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
1004 static int markcmd(UAContext *ua, TREE_CTX *tree)
1010 if (!tree->node->child) {
1013 for (node = tree->node->child; node; node=node->sibling) {
1014 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1015 set_extract(ua, node, tree, 1);
1021 static int countcmd(UAContext *ua, TREE_CTX *tree)
1025 total = extract = 0;
1026 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
1027 if (node->type != TN_NEWDIR) {
1029 if (node->extract) {
1034 bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
1038 static int findcmd(UAContext *ua, TREE_CTX *tree)
1042 if (ua->argc == 1) {
1043 bsendmsg(ua, _("No file specification given.\n"));
1047 for (int i=1; i < ua->argc; i++) {
1048 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
1049 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
1050 tree_getpath(node, cwd, sizeof(cwd));
1051 bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
1060 static int lscmd(UAContext *ua, TREE_CTX *tree)
1064 if (!tree->node->child) {
1067 for (node = tree->node->child; node; node=node->sibling) {
1068 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1069 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1070 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1076 extern char *getuser(uid_t uid);
1077 extern char *getgroup(gid_t gid);
1080 * This is actually the long form used for "dir"
1082 static void ls_output(char *buf, char *fname, int extract, struct stat *statp)
1088 p = encode_mode(statp->st_mode, buf);
1089 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
1091 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
1093 n = sprintf(p, "%8.8s ", edit_uint64(statp->st_size, ec1));
1095 p = encode_time(statp->st_ctime, p);
1109 * Like ls command, but give more detail on each file
1111 static int dircmd(UAContext *ua, TREE_CTX *tree)
1119 if (!tree->node->child) {
1122 for (node = tree->node->child; node; node=node->sibling) {
1123 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1124 tree_getpath(node, cwd, sizeof(cwd));
1126 fdbr.JobId = node->JobId;
1127 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
1129 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
1130 ls_output(buf, cwd, node->extract, &statp);
1131 bsendmsg(ua, "%s\n", buf);
1133 /* Something went wrong getting attributes -- print name */
1134 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1135 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1143 static int helpcmd(UAContext *ua, TREE_CTX *tree)
1148 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
1149 for (i=0; i<comsize; i++) {
1150 bsendmsg(ua, _(" %-10s %s\n"), _(commands[i].key), _(commands[i].help));
1157 * Change directories. Note, if the user specifies x: and it fails,
1158 * we assume it is a Win32 absolute cd rather than relative and
1159 * try a second time with /x: ... Win32 kludge.
1161 static int cdcmd(UAContext *ua, TREE_CTX *tree)
1166 if (ua->argc != 2) {
1169 node = tree_cwd(ua->argk[1], tree->root, tree->node);
1171 /* Try once more if Win32 drive -- make absolute */
1172 if (ua->argk[1][1] == ':') { /* win32 drive */
1174 strcat(cwd, ua->argk[1]);
1175 node = tree_cwd(cwd, tree->root, tree->node);
1178 bsendmsg(ua, _("Invalid path given.\n"));
1185 tree_getpath(tree->node, cwd, sizeof(cwd));
1186 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1190 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
1193 tree_getpath(tree->node, cwd, sizeof(cwd));
1194 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1199 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
1205 if (!tree->node->child) {
1208 for (node = tree->node->child; node; node=node->sibling) {
1209 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1210 set_extract(ua, node, tree, 0);
1216 static int quitcmd(UAContext *ua, TREE_CTX *tree)
1223 * Called here with each name to be added to the list. The name is
1224 * added to the list if it is not already in the list.
1226 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1228 NAME_LIST *name = (NAME_LIST *)ctx;
1230 if (name->num_ids == MAX_ID_LIST_LEN) {
1233 if (name->num_ids == name->max_ids) {
1234 if (name->max_ids == 0) {
1235 name->max_ids = 1000;
1236 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1238 name->max_ids = (name->max_ids * 3) / 2;
1239 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1242 for (int i=0; i<name->num_ids; i++) {
1243 if (strcmp(name->name[i], row[0]) == 0) {
1244 return 0; /* already in list, return */
1247 /* Add new name to list */
1248 name->name[name->num_ids++] = bstrdup(row[0]);
1254 * Print names in the list
1256 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1260 for (i=0; i < name_list->num_ids; i++) {
1261 bsendmsg(ua, "%s\n", name_list->name[i]);
1267 * Free names in the list
1269 static void free_name_list(NAME_LIST *name_list)
1273 for (i=0; i < name_list->num_ids; i++) {
1274 free(name_list->name[i]);
1276 if (name_list->name) {
1277 free(name_list->name);
1279 name_list->max_ids = 0;
1280 name_list->num_ids = 0;
1283 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JOBIDS *ji)
1285 char name[MAX_NAME_LENGTH];
1286 STORE *store = NULL;
1288 if (name_list->num_ids > 1) {
1289 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1290 "Restore is not possible. The MediaTypes used are:\n"));
1291 print_name_list(ua, name_list);
1292 ji->store = select_storage_resource(ua);
1296 if (name_list->num_ids == 0) {
1297 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1298 ji->store = select_storage_resource(ua);
1302 start_prompt(ua, _("The defined Storage resources are:\n"));
1304 while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
1305 if (strcmp(store->media_type, name_list->name[0]) == 0) {
1306 add_prompt(ua, store->hdr.name);
1310 do_prompt(ua, _("Select Storage resource"), name, sizeof(name));
1311 ji->store = (STORE *)GetResWithName(R_STORAGE, name);
1313 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1314 "MediaType %s, needed by the Jobs you selected.\n"
1315 "You will be allowed to select a Storage device later.\n"),
1316 name_list->name[0]);