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;
141 memset(&tree, 0, sizeof(TREE_CTX));
142 memset(&name_list, 0, sizeof(name_list));
143 memset(&ji, 0, sizeof(ji));
145 /* Ensure there is at least one Restore Job */
147 while ( (job = (JOB *)GetNextRes(R_JOB, (RES *)job)) ) {
148 if (job->JobType == JT_RESTORE) {
158 "No Restore Job Resource found. You must create at least\n"
159 "one before running this command.\n"));
164 * Request user to select JobIds by various different methods
165 * last 20 jobs, where File saved, most recent backup, ...
167 if (!user_select_jobids(ua, &ji)) {
172 * Build the directory tree containing JobIds user selected
174 tree.root = new_tree(ji.TotalFiles);
175 tree.root->fname = nofname;
177 query = get_pool_memory(PM_MESSAGE);
180 * For display purposes, the same JobId, with different volumes may
181 * appear more than once, however, we only insert it once.
183 for (p=ji.JobIds; next_jobid_from_list(&p, &JobId) > 0; ) {
185 if (JobId == last_JobId) {
186 continue; /* eliminate duplicate JobIds */
189 bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
191 * Find files for this JobId and insert them in the tree
193 Mmsg(&query, uar_sel_files, JobId);
194 if (!db_sql_query(ua->db, query, insert_tree_handler, (void *)&tree)) {
195 bsendmsg(ua, "%s", db_strerror(ua->db));
198 * Find the FileSets for this JobId and add to the name_list
200 Mmsg(&query, uar_mediatype, JobId);
201 if (!db_sql_query(ua->db, query, unique_name_list_handler, (void *)&name_list)) {
202 bsendmsg(ua, "%s", db_strerror(ua->db));
206 bsendmsg(ua, "%d items inserted into the tree and marked for extraction.\n");
207 free_pool_memory(query);
209 /* Check MediaType and select storage that corresponds */
210 get_storage_from_mediatype(ua, &name_list, &ji);
211 free_name_list(&name_list);
213 /* Let the user select which files to restore */
214 user_select_files(&tree);
217 * Walk down through the tree finding all files marked to be
218 * extracted making a bootstrap file.
221 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
222 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
224 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
225 add_findex(bsr, node->JobId, node->FileIndex);
230 free_tree(tree.root); /* free the directory tree */
233 if (!complete_bsr(ua, bsr)) { /* find Vol, SessId, SessTime from JobIds */
234 bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
238 // print_bsr(ua, bsr);
239 write_bsr_file(ua, bsr);
240 bsendmsg(ua, _("\n%u files selected to restore.\n\n"), selected_files);
242 bsendmsg(ua, _("No files selected to restore.\n"));
246 if (restore_jobs == 1) {
249 job = select_restore_job_resource(ua);
252 bsendmsg(ua, _("No Restore Job resource found!\n"));
256 /* If no client name specified yet, get it now */
257 if (!ji.ClientName[0]) {
259 memset(&cr, 0, sizeof(cr));
260 if (!get_client_dbr(ua, &cr)) {
263 bstrncpy(ji.ClientName, cr.Name, sizeof(ji.ClientName));
266 /* Build run command */
268 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
269 job->hdr.name, ji.ClientName, ji.store?ji.store->hdr.name:"",
272 Dmsg1(400, "Submitting: %s\n", ua->cmd);
277 bsendmsg(ua, _("Restore command done.\n"));
282 * The first step in the restore process is for the user to
283 * select a list of JobIds from which he will subsequently
284 * select which files are to be restored.
286 static int user_select_jobids(UAContext *ua, JobIds *ji)
288 char fileset_name[MAX_NAME_LENGTH];
297 "List last 20 Jobs run",
298 "List Jobs where a given File is saved",
299 "Enter list of JobIds to select",
300 "Enter SQL list command",
301 "Select the most recent backup for a client",
305 bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
306 "to be restored. You will be presented several methods\n"
307 "of specifying the JobIds. Then you will be allowed to\n"
308 "select which files from those JobIds are to be restored.\n\n"));
311 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
312 for (int i=0; list[i]; i++) {
313 add_prompt(ua, list[i]);
316 switch (do_prompt(ua, "Select item: ", NULL, 0)) {
319 case 0: /* list last 20 Jobs run */
320 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, 0);
323 case 1: /* list where a file is saved */
326 if (!get_cmd(ua, _("Enter Filename: "))) {
329 len = strlen(ua->cmd);
330 fname = (char *)malloc(len * 2 + 1);
331 db_escape_string(fname, ua->cmd, len);
332 query = get_pool_memory(PM_MESSAGE);
333 Mmsg(&query, uar_file, fname);
335 db_list_sql_query(ua->jcr, ua->db, query, prtit, ua, 1, 0);
336 free_pool_memory(query);
339 case 2: /* enter a list of JobIds */
340 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
343 bstrncpy(ji->JobIds, ua->cmd, sizeof(ji->JobIds));
345 case 3: /* Enter an SQL list command */
346 if (!get_cmd(ua, _("Enter SQL list command: "))) {
349 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, 0);
352 case 4: /* Select the most recent backups */
353 query = get_pool_memory(PM_MESSAGE);
354 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
355 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
356 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
357 bsendmsg(ua, "%s\n", db_strerror(ua->db));
359 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
360 bsendmsg(ua, "%s\n", db_strerror(ua->db));
363 * Select Client from the Catalog
365 memset(&cr, 0, sizeof(cr));
366 if (!get_client_dbr(ua, &cr)) {
367 free_pool_memory(query);
368 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
369 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
372 bstrncpy(ji->ClientName, cr.Name, sizeof(ji->ClientName));
377 Mmsg(&query, uar_sel_fileset, cr.ClientId, cr.ClientId);
378 start_prompt(ua, _("The defined FileSet resources are:\n"));
379 if (!db_sql_query(ua->db, query, fileset_handler, (void *)ua)) {
380 bsendmsg(ua, "%s\n", db_strerror(ua->db));
382 if (do_prompt(ua, _("Select FileSet resource"),
383 fileset_name, sizeof(fileset_name)) < 0) {
384 free_pool_memory(query);
385 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
386 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
389 fsr.FileSetId = atoi(fileset_name); /* Id is first part of name */
390 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
391 bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
392 bsendmsg(ua, _("This probably means you modified the FileSet.\n"
393 "Continuing anyway.\n"));
396 /* Find JobId of last Full backup for this client, fileset */
397 Mmsg(&query, uar_last_full, cr.ClientId, cr.ClientId, fsr.FileSetId);
398 if (!db_sql_query(ua->db, query, NULL, NULL)) {
399 bsendmsg(ua, "%s\n", db_strerror(ua->db));
401 /* Find all Volumes used by that JobId */
402 if (!db_sql_query(ua->db, uar_full, NULL,NULL)) {
403 bsendmsg(ua, "%s\n", db_strerror(ua->db));
405 /* Note, this is needed as I don't seem to get the callback
406 * from the call just above.
408 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)ji)) {
409 bsendmsg(ua, "%s\n", db_strerror(ua->db));
411 /* Now find all Incremental Jobs */
412 Mmsg(&query, uar_inc, edit_uint64(ji->JobTDate, ed1), cr.ClientId, fsr.FileSetId);
413 if (!db_sql_query(ua->db, query, NULL, NULL)) {
414 bsendmsg(ua, "%s\n", db_strerror(ua->db));
416 free_pool_memory(query);
417 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, 0);
419 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)ji)) {
420 bsendmsg(ua, "%s\n", db_strerror(ua->db));
422 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
423 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
430 if (*ji->JobIds == 0) {
431 bsendmsg(ua, _("No Jobs selected.\n"));
434 bsendmsg(ua, _("You have selected the following JobId: %s\n"), ji->JobIds);
436 memset(&jr, 0, sizeof(JOB_DBR));
439 for (p=ji->JobIds; ; ) {
440 int stat = next_jobid_from_list(&p, &JobId);
442 bsendmsg(ua, _("Invalid JobId in list.\n"));
449 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
450 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
453 ji->TotalFiles += jr.JobFiles;
458 static int next_jobid_from_list(char **p, uint32_t *JobId)
465 for (i=0; i<(int)sizeof(jobid); i++) {
466 if (*q == ',' || *q == 0) {
473 if (jobid[0] == 0 || !is_a_number(jobid)) {
477 *JobId = strtoul(jobid, NULL, 10);
482 * Callback handler make list of JobIds
484 static int jobid_handler(void *ctx, int num_fields, char **row)
486 JobIds *ji = (JobIds *)ctx;
488 if (strlen(ji->JobIds)+strlen(row[0])+2 < sizeof(ji->JobIds)) {
489 if (ji->JobIds[0] != 0) {
490 strcat(ji->JobIds, ",");
492 strcat(ji->JobIds, row[0]);
500 * Callback handler to pickup last Full backup JobId and ClientId
502 static int last_full_handler(void *ctx, int num_fields, char **row)
504 JobIds *ji = (JobIds *)ctx;
506 ji->JobTDate = strtoll(row[1], NULL, 10);
512 * Callback handler build fileset prompt list
514 static int fileset_handler(void *ctx, int num_fields, char **row)
516 char prompt[MAX_NAME_LENGTH+200];
518 snprintf(prompt, sizeof(prompt), "%s %s %s", row[0], row[1], row[2]);
519 add_prompt((UAContext *)ctx, prompt);
523 /* Forward referenced commands */
525 static int markcmd(UAContext *ua, TREE_CTX *tree);
526 static int countcmd(UAContext *ua, TREE_CTX *tree);
527 static int findcmd(UAContext *ua, TREE_CTX *tree);
528 static int lscmd(UAContext *ua, TREE_CTX *tree);
529 static int dircmd(UAContext *ua, TREE_CTX *tree);
530 static int helpcmd(UAContext *ua, TREE_CTX *tree);
531 static int cdcmd(UAContext *ua, TREE_CTX *tree);
532 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
533 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
534 static int quitcmd(UAContext *ua, TREE_CTX *tree);
537 struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; };
538 static struct cmdstruct commands[] = {
539 { N_("mark"), markcmd, _("mark file for restoration")},
540 { N_("unmark"), unmarkcmd, _("unmark file for restoration")},
541 { N_("cd"), cdcmd, _("change current directory")},
542 { N_("pwd"), pwdcmd, _("print current working directory")},
543 { N_("ls"), lscmd, _("list current directory")},
544 { N_("dir"), dircmd, _("list current directory")},
545 { N_("count"), countcmd, _("count marked files")},
546 { N_("find"), findcmd, _("find files")},
547 { N_("done"), quitcmd, _("leave file selection mode")},
548 { N_("exit"), quitcmd, _("exit = done")},
549 { N_("help"), helpcmd, _("print help")},
550 { N_("?"), helpcmd, _("print help")},
552 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
556 * Enter a prompt mode where the user can select/deselect
557 * files to be restored. This is sort of like a mini-shell
558 * that allows "cd", "pwd", "add", "rm", ...
560 static void user_select_files(TREE_CTX *tree)
564 bsendmsg(tree->ua, _(
565 "\nYou are now entering file selection mode where you add and\n"
566 "remove files to be restored. All files are initially added.\n"
567 "Enter \"done\" to leave this mode.\n\n"));
569 * Enter interactive command handler allowing selection
570 * of individual files.
572 tree->node = (TREE_NODE *)tree->root;
573 tree_getpath(tree->node, cwd, sizeof(cwd));
574 bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
576 int found, len, stat, i;
577 if (!get_cmd(tree->ua, "$ ")) {
580 parse_ua_args(tree->ua);
581 if (tree->ua->argc == 0) {
585 len = strlen(tree->ua->argk[0]);
588 for (i=0; i<(int)comsize; i++) /* search for command */
589 if (strncasecmp(tree->ua->argk[0], _(commands[i].key), len) == 0) {
590 stat = (*commands[i].func)(tree->ua, tree); /* go execute command */
595 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
605 * Create new FileIndex entry for BSR
607 static RBSR_FINDEX *new_findex()
609 RBSR_FINDEX *fi = (RBSR_FINDEX *)bmalloc(sizeof(RBSR_FINDEX));
610 memset(fi, 0, sizeof(RBSR_FINDEX));
614 /* Free all BSR FileIndex entries */
615 static void free_findex(RBSR_FINDEX *fi)
618 free_findex(fi->next);
623 static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd)
626 if (fi->findex == fi->findex2) {
627 fprintf(fd, "FileIndex=%d\n", fi->findex);
629 fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
631 write_findex(ua, fi->next, fd);
636 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
639 if (fi->findex == fi->findex2) {
640 bsendmsg(ua, "FileIndex=%d\n", fi->findex);
642 bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
644 print_findex(ua, fi->next);
648 /* Create a new bootstrap record */
649 static RBSR *new_bsr()
651 RBSR *bsr = (RBSR *)bmalloc(sizeof(RBSR));
652 memset(bsr, 0, sizeof(RBSR));
656 /* Free the entire BSR */
657 static void free_bsr(RBSR *bsr)
660 free_findex(bsr->fi);
662 if (bsr->VolParams) {
663 free(bsr->VolParams);
670 * Complete the BSR by filling in the VolumeName and
671 * VolSessionId and VolSessionTime using the JobId
673 static int complete_bsr(UAContext *ua, RBSR *bsr)
678 memset(&jr, 0, sizeof(jr));
679 jr.JobId = bsr->JobId;
680 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
681 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
684 bsr->VolSessionId = jr.VolSessionId;
685 bsr->VolSessionTime = jr.VolSessionTime;
686 if ((bsr->VolCount=db_get_job_volume_parameters(ua->jcr, ua->db, bsr->JobId,
687 &(bsr->VolParams))) == 0) {
688 bsendmsg(ua, _("Unable to get Job Volume Parameters. ERR=%s\n"), db_strerror(ua->db));
689 if (bsr->VolParams) {
690 free(bsr->VolParams);
691 bsr->VolParams = NULL;
695 return complete_bsr(ua, bsr->next);
701 * Write the bootstrap record to file
703 static int write_bsr_file(UAContext *ua, RBSR *bsr)
706 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
710 Mmsg(&fname, "%s/restore.bsr", working_directory);
711 fd = fopen(fname, "w+");
713 bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"),
714 fname, strerror(errno));
715 free_pool_memory(fname);
718 write_bsr(ua, bsr, fd);
721 bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
722 bsendmsg(ua, _("\nThe restore job will require the following Volumes:\n"));
723 /* Create Unique list of Volumes using prompt list */
724 start_prompt(ua, "");
725 for (nbsr=bsr; nbsr; nbsr=nbsr->next) {
726 for (int i=0; i < nbsr->VolCount; i++) {
727 add_prompt(ua, nbsr->VolParams[i].VolumeName);
730 for (int i=0; i < ua->num_prompts; i++) {
731 bsendmsg(ua, " %s\n", ua->prompt[i]);
736 free_pool_memory(fname);
740 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
743 for (int i=0; i < bsr->VolCount; i++) {
744 fprintf(fd, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
745 fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
746 fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
747 fprintf(fd, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile,
748 bsr->VolParams[i].EndFile);
749 fprintf(fd, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
750 bsr->VolParams[i].EndBlock);
751 write_findex(ua, bsr->fi, fd);
753 write_bsr(ua, bsr->next, fd);
757 static void print_bsr(UAContext *ua, RBSR *bsr)
760 for (int i=0; i < bsr->VolCount; i++) {
761 bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
762 bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
763 bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
764 bsendmsg(ua, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile,
765 bsr->VolParams[i].EndFile);
766 bsendmsg(ua, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
767 bsr->VolParams[i].EndBlock);
768 print_findex(ua, bsr->fi);
770 print_bsr(ua, bsr->next);
776 * Add a FileIndex to the list of BootStrap records.
777 * Here we are only dealing with JobId's and the FileIndexes
778 * associated with those JobIds.
780 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
783 RBSR_FINDEX *fi, *lfi;
786 return; /* probably a dummy directory */
789 if (!bsr->fi) { /* if no FI add one */
790 /* This is the first FileIndex item in the chain */
791 bsr->fi = new_findex();
793 bsr->fi->findex = findex;
794 bsr->fi->findex2 = findex;
797 /* Walk down list of bsrs until we find the JobId */
798 if (bsr->JobId != JobId) {
799 for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
800 if (nbsr->JobId == JobId) {
806 if (!nbsr) { /* Must add new JobId */
807 /* Add new JobId at end of chain */
808 for (nbsr=bsr; nbsr->next; nbsr=nbsr->next)
810 nbsr->next = new_bsr();
811 nbsr->next->JobId = JobId;
812 nbsr->next->fi = new_findex();
813 nbsr->next->fi->findex = findex;
814 nbsr->next->fi->findex2 = findex;
820 * At this point, bsr points to bsr containing JobId,
821 * and we are sure that there is at least one fi record.
824 /* Check if this findex is smaller than first item */
825 if (findex < fi->findex) {
826 if ((findex+1) == fi->findex) {
827 fi->findex = findex; /* extend down */
830 fi = new_findex(); /* yes, insert before first item */
832 fi->findex2 = findex;
837 /* Walk down fi chain and find where to insert insert new FileIndex */
838 for ( ; fi; fi=fi->next) {
839 if (findex == (fi->findex2 + 1)) { /* extend up */
841 fi->findex2 = findex;
842 if (fi->next && ((findex+1) == fi->next->findex)) {
844 fi->findex2 = nfi->findex2;
845 fi->next = nfi->next;
850 if (findex < fi->findex) { /* add before */
851 if ((findex+1) == fi->findex) {
859 /* Add to last place found */
862 fi->findex2 = findex;
863 fi->next = lfi->next;
869 * This callback routine is responsible for inserting the
870 * items it gets into the directory tree. For each JobId selected
871 * this routine is called once for each file. We do not allow
872 * duplicate filenames, but instead keep the info from the most
873 * recent file entered (i.e. the JobIds are assumed to be sorted)
875 static int insert_tree_handler(void *ctx, int num_fields, char **row)
877 TREE_CTX *tree = (TREE_CTX *)ctx;
879 TREE_NODE *node, *new_node;
882 strip_trailing_junk(row[1]);
884 if (*row[0] != '/') { /* Must be Win32 directory */
892 sprintf(fname, "%s%s", row[0], row[1]);
893 if (tree->avail_node) {
894 node = tree->avail_node;
896 node = new_tree_node(tree->root, type);
897 tree->avail_node = node;
899 Dmsg3(200, "FI=%d type=%d fname=%s\n", node->FileIndex, type, fname);
900 new_node = insert_tree_node(fname, node, tree->root, NULL);
901 /* Note, if node already exists, save new one for next time */
902 if (new_node != node) {
903 tree->avail_node = node;
905 tree->avail_node = NULL;
907 new_node->FileIndex = atoi(row[2]);
908 new_node->JobId = atoi(row[3]);
909 new_node->type = type;
910 new_node->extract = 1; /* extract all by default */
917 * Set extract to value passed. We recursively walk
918 * down the tree setting all children if the
919 * node is a directory.
921 static void set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, int value)
927 node->extract = value;
928 /* For a non-file (i.e. directory), we see all the children */
929 if (node->type != TN_FILE) {
930 for (n=node->child; n; n=n->sibling) {
931 set_extract(ua, n, tree, value);
935 /* Ordinary file, we get the full path, look up the
936 * attributes, decode them, and if we are hard linked to
937 * a file that was saved, we must load that file too.
939 tree_getpath(node, cwd, sizeof(cwd));
941 fdbr.JobId = node->JobId;
942 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
944 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
946 * If we point to a hard linked file, traverse the tree to
947 * find that file, and mark it for restoration as well. It
948 * must have the Link we just obtained and the same JobId.
951 for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
952 if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
962 static int markcmd(UAContext *ua, TREE_CTX *tree)
968 if (!tree->node->child) {
971 for (node = tree->node->child; node; node=node->sibling) {
972 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
973 set_extract(ua, node, tree, 1);
979 static int countcmd(UAContext *ua, TREE_CTX *tree)
984 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
985 if (node->type != TN_NEWDIR) {
992 bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
996 static int findcmd(UAContext *ua, TREE_CTX *tree)
1000 if (ua->argc == 1) {
1001 bsendmsg(ua, _("No file specification given.\n"));
1005 for (int i=1; i < ua->argc; i++) {
1006 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
1007 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
1008 tree_getpath(node, cwd, sizeof(cwd));
1009 bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
1018 static int lscmd(UAContext *ua, TREE_CTX *tree)
1022 if (!tree->node->child) {
1025 for (node = tree->node->child; node; node=node->sibling) {
1026 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1027 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1028 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1034 extern char *getuser(uid_t uid);
1035 extern char *getgroup(gid_t gid);
1038 * This is actually the long form used for "dir"
1040 static void ls_output(char *buf, char *fname, int extract, struct stat *statp)
1046 p = encode_mode(statp->st_mode, buf);
1047 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
1049 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
1051 n = sprintf(p, "%8.8s ", edit_uint64(statp->st_size, ec1));
1053 p = encode_time(statp->st_ctime, p);
1067 * Like ls command, but give more detail on each file
1069 static int dircmd(UAContext *ua, TREE_CTX *tree)
1077 if (!tree->node->child) {
1080 for (node = tree->node->child; node; node=node->sibling) {
1081 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1082 tree_getpath(node, cwd, sizeof(cwd));
1084 fdbr.JobId = node->JobId;
1085 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
1087 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
1088 ls_output(buf, cwd, node->extract, &statp);
1089 bsendmsg(ua, "%s\n", buf);
1091 /* Something went wrong getting attributes -- print name */
1092 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1093 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1101 static int helpcmd(UAContext *ua, TREE_CTX *tree)
1106 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
1107 for (i=0; i<comsize; i++) {
1108 bsendmsg(ua, _(" %-10s %s\n"), _(commands[i].key), _(commands[i].help));
1115 * Change directories. Note, if the user specifies x: and it fails,
1116 * we assume it is a Win32 absolute cd rather than relative and
1117 * try a second time with /x: ... Win32 kludge.
1119 static int cdcmd(UAContext *ua, TREE_CTX *tree)
1124 if (ua->argc != 2) {
1127 node = tree_cwd(ua->argk[1], tree->root, tree->node);
1129 /* Try once more if Win32 drive -- make absolute */
1130 if (ua->argk[1][1] == ':') { /* win32 drive */
1132 strcat(cwd, ua->argk[1]);
1133 node = tree_cwd(cwd, tree->root, tree->node);
1136 bsendmsg(ua, _("Invalid path given.\n"));
1143 tree_getpath(tree->node, cwd, sizeof(cwd));
1144 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1148 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
1151 tree_getpath(tree->node, cwd, sizeof(cwd));
1152 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1157 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
1163 if (!tree->node->child) {
1166 for (node = tree->node->child; node; node=node->sibling) {
1167 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1168 set_extract(ua, node, tree, 0);
1174 static int quitcmd(UAContext *ua, TREE_CTX *tree)
1181 * Called here with each name to be added to the list. The name is
1182 * added to the list if it is not already in the list.
1184 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1186 NAME_LIST *name = (NAME_LIST *)ctx;
1188 if (name->num_ids == MAX_ID_LIST_LEN) {
1191 if (name->num_ids == name->max_ids) {
1192 if (name->max_ids == 0) {
1193 name->max_ids = 1000;
1194 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1196 name->max_ids = (name->max_ids * 3) / 2;
1197 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1200 for (int i=0; i<name->num_ids; i++) {
1201 if (strcmp(name->name[i], row[0]) == 0) {
1202 return 0; /* already in list, return */
1205 /* Add new name to list */
1206 name->name[name->num_ids++] = bstrdup(row[0]);
1212 * Print names in the list
1214 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1218 for (i=0; i < name_list->num_ids; i++) {
1219 bsendmsg(ua, "%s\n", name_list->name[i]);
1225 * Free names in the list
1227 static void free_name_list(NAME_LIST *name_list)
1231 for (i=0; i < name_list->num_ids; i++) {
1232 free(name_list->name[i]);
1234 if (name_list->name) {
1235 free(name_list->name);
1237 name_list->max_ids = 0;
1238 name_list->num_ids = 0;
1241 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JobIds *ji)
1243 char name[MAX_NAME_LENGTH];
1244 STORE *store = NULL;
1246 if (name_list->num_ids > 1) {
1247 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1248 "Restore is not possible. The MediaTypes used are:\n"));
1249 print_name_list(ua, name_list);
1250 ji->store = select_storage_resource(ua);
1254 if (name_list->num_ids == 0) {
1255 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1256 ji->store = select_storage_resource(ua);
1260 start_prompt(ua, _("The defined Storage resources are:\n"));
1262 while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
1263 if (strcmp(store->media_type, name_list->name[0]) == 0) {
1264 add_prompt(ua, store->hdr.name);
1268 do_prompt(ua, _("Select Storage resource"), name, sizeof(name));
1269 ji->store = (STORE *)GetResWithName(R_STORAGE, name);
1271 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1272 "MediaType %s, needed by the Jobs you selected.\n"
1273 "You will be allowed to select a Storage device later.\n"),
1274 name_list->name[0]);