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,
35 #include "findlib/find.h"
39 /* Imported functions */
40 extern int runcmd(UAContext *ua, char *cmd);
42 /* Imported variables */
43 extern char *uar_list_jobs, *uar_file, *uar_sel_files;
44 extern char *uar_del_temp, *uar_del_temp1, *uar_create_temp;
45 extern char *uar_create_temp1, *uar_last_full, *uar_full;
46 extern char *uar_inc, *uar_list_temp, *uar_sel_jobid_temp;
47 extern char *uar_sel_all_temp1, *uar_sel_fileset, *uar_mediatype;
50 /* Context for insert_tree_handler() */
51 typedef struct s_tree_ctx {
52 TREE_ROOT *root; /* root */
53 TREE_NODE *node; /* current node */
54 TREE_NODE *avail_node; /* unused node last insert */
55 int cnt; /* count for user feedback */
59 /* Main structure for obtaining JobIds */
60 typedef struct s_jobids {
63 char ClientName[MAX_NAME_LENGTH];
69 /* FileIndex entry in bootstrap record */
70 typedef struct s_rbsr_findex {
71 struct s_rbsr_findex *next;
76 /* Restore bootstrap record -- not the real one, but useful here */
77 typedef struct s_rbsr {
78 struct s_rbsr *next; /* next JobId */
79 uint32_t JobId; /* JobId this bsr */
80 uint32_t VolSessionId;
81 uint32_t VolSessionTime;
82 int VolCount; /* Volume parameter count */
83 VOL_PARAMS *VolParams; /* Volume, start/end file/blocks */
84 RBSR_FINDEX *fi; /* File indexes this JobId */
87 typedef struct s_name_ctx {
88 char **name; /* list of names */
89 int num_ids; /* ids stored */
90 int max_ids; /* size of array */
91 int num_del; /* number deleted */
92 int tot_ids; /* total to process */
95 #define MAX_ID_LIST_LEN 1000000
98 /* Forward referenced functions */
99 static RBSR *new_bsr();
100 static void free_bsr(RBSR *bsr);
101 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd);
102 static int write_bsr_file(UAContext *ua, RBSR *bsr);
103 static void print_bsr(UAContext *ua, RBSR *bsr);
104 static int complete_bsr(UAContext *ua, RBSR *bsr);
105 static int insert_tree_handler(void *ctx, int num_fields, char **row);
106 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex);
107 static int last_full_handler(void *ctx, int num_fields, char **row);
108 static int jobid_handler(void *ctx, int num_fields, char **row);
109 static int next_jobid_from_list(char **p, uint32_t *JobId);
110 static int user_select_jobids(UAContext *ua, JobIds *ji);
111 static void user_select_files(TREE_CTX *tree);
112 static int fileset_handler(void *ctx, int num_fields, char **row);
113 static void print_name_list(UAContext *ua, NAME_LIST *name_list);
114 static int unique_name_list_handler(void *ctx, int num_fields, char **row);
115 static void free_name_list(NAME_LIST *name_list);
116 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JobIds *ji);
123 int restorecmd(UAContext *ua, char *cmd)
127 JobId_t JobId, last_JobId;
133 JOB *restore_job = NULL;
134 int restore_jobs = 0;
136 uint32_t selected_files = 0;
142 memset(&tree, 0, sizeof(TREE_CTX));
143 memset(&name_list, 0, sizeof(name_list));
144 memset(&ji, 0, sizeof(ji));
146 /* Ensure there is at least one Restore Job */
148 while ( (job = (JOB *)GetNextRes(R_JOB, (RES *)job)) ) {
149 if (job->JobType == JT_RESTORE) {
159 "No Restore Job Resource found. You must create at least\n"
160 "one before running this command.\n"));
165 if (!user_select_jobids(ua, &ji)) {
170 * Build the directory tree
172 tree.root = new_tree(ji.TotalFiles);
173 tree.root->fname = nofname;
175 query = get_pool_memory(PM_MESSAGE);
178 * For display purposes, the same JobId, with different volumes may
179 * appear more than once, however, we only insert it once.
181 for (p=ji.JobIds; next_jobid_from_list(&p, &JobId) > 0; ) {
183 if (JobId == last_JobId) {
184 continue; /* eliminate duplicate JobIds */
187 bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
189 * Find files for this JobId and insert them in the tree
191 Mmsg(&query, uar_sel_files, JobId);
192 if (!db_sql_query(ua->db, query, insert_tree_handler, (void *)&tree)) {
193 bsendmsg(ua, "%s", db_strerror(ua->db));
196 * Find the FileSets for this JobId and add to the name_list
198 Mmsg(&query, uar_mediatype, JobId);
199 if (!db_sql_query(ua->db, query, unique_name_list_handler, (void *)&name_list)) {
200 bsendmsg(ua, "%s", db_strerror(ua->db));
204 bsendmsg(ua, "%d items inserted into the tree and marked for extraction.\n");
205 free_pool_memory(query);
207 /* Check MediaType and select storage that corresponds */
208 get_storage_from_mediatype(ua, &name_list, &ji);
209 free_name_list(&name_list);
211 /* Let the user select which files to restore */
212 user_select_files(&tree);
215 * Walk down through the tree finding all files marked to be
216 * extracted making a bootstrap file.
219 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
220 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
222 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
223 add_findex(bsr, node->JobId, node->FileIndex);
228 free_tree(tree.root); /* free the directory tree */
231 if (!complete_bsr(ua, bsr)) { /* find Vol, SessId, SessTime from JobIds */
232 bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
236 // print_bsr(ua, bsr);
237 write_bsr_file(ua, bsr);
238 bsendmsg(ua, _("\n%u files selected to restore.\n\n"), selected_files);
240 bsendmsg(ua, _("No files selected to restore.\n"));
244 if (restore_jobs == 1) {
247 job = select_restore_job_resource(ua);
250 bsendmsg(ua, _("No Restore Job resource found!\n"));
254 if (ji.ClientName[0]) {
256 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
257 job->hdr.name, ji.ClientName, ji.store?ji.store->hdr.name:"",
261 "run job=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
262 job->hdr.name, ji.store?ji.store->hdr.name:"", working_directory);
266 Dmsg1(400, "Submitting: %s\n", ua->cmd);
268 parse_command_args(ua);
271 bsendmsg(ua, _("Restore command done.\n"));
276 * The first step in the restore process is for the user to
277 * select a list of JobIds from which he will subsequently
278 * select which files are to be restored.
280 static int user_select_jobids(UAContext *ua, JobIds *ji)
282 char fileset_name[MAX_NAME_LENGTH];
291 "List last 20 Jobs run",
292 "List Jobs where a given File is saved",
293 "Enter list of JobIds to select",
294 "Enter SQL list command",
295 "Select the most recent backup for a client",
299 bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
300 "to be restored. You will be presented several methods\n"
301 "of specifying the JobIds. Then you will be allowed to\n"
302 "select which files from those JobIds are to be restored.\n\n"));
305 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
306 for (int i=0; list[i]; i++) {
307 add_prompt(ua, list[i]);
310 switch (do_prompt(ua, "Select item: ", NULL, 0)) {
313 case 0: /* list last 20 Jobs run */
314 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1);
317 case 1: /* list where a file is saved */
318 if (!get_cmd(ua, _("Enter Filename: "))) {
321 query = get_pool_memory(PM_MESSAGE);
322 Mmsg(&query, uar_file, ua->cmd);
323 db_list_sql_query(ua->jcr, ua->db, query, prtit, ua, 1);
324 free_pool_memory(query);
327 case 2: /* enter a list of JobIds */
328 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
331 bstrncpy(ji->JobIds, ua->cmd, sizeof(ji->JobIds));
333 case 3: /* Enter an SQL list command */
334 if (!get_cmd(ua, _("Enter SQL list command: "))) {
337 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1);
340 case 4: /* Select the most recent backups */
341 query = get_pool_memory(PM_MESSAGE);
342 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
343 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
344 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
345 bsendmsg(ua, "%s\n", db_strerror(ua->db));
347 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
348 bsendmsg(ua, "%s\n", db_strerror(ua->db));
351 * Select Client from the Catalog
353 memset(&cr, 0, sizeof(cr));
354 if (!get_client_dbr(ua, &cr)) {
355 free_pool_memory(query);
356 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
357 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
360 bstrncpy(ji->ClientName, cr.Name, sizeof(ji->ClientName));
365 Mmsg(&query, uar_sel_fileset, cr.ClientId, cr.ClientId);
366 start_prompt(ua, _("The defined FileSet resources are:\n"));
367 if (!db_sql_query(ua->db, query, fileset_handler, (void *)ua)) {
368 bsendmsg(ua, "%s\n", db_strerror(ua->db));
370 if (do_prompt(ua, _("Select FileSet resource"),
371 fileset_name, sizeof(fileset_name)) < 0) {
372 free_pool_memory(query);
373 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
374 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
377 fsr.FileSetId = atoi(fileset_name); /* Id is first part of name */
378 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
379 bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
380 bsendmsg(ua, _("This probably means you modified the FileSet.\n"
381 "Continuing anyway.\n"));
384 /* Find JobId of last Full backup for this client, fileset */
385 Mmsg(&query, uar_last_full, cr.ClientId, cr.ClientId, fsr.FileSetId);
386 if (!db_sql_query(ua->db, query, NULL, NULL)) {
387 bsendmsg(ua, "%s\n", db_strerror(ua->db));
389 /* Find all Volumes used by that JobId */
390 if (!db_sql_query(ua->db, uar_full, NULL,NULL)) {
391 bsendmsg(ua, "%s\n", db_strerror(ua->db));
393 /* Note, this is needed as I don't seem to get the callback
394 * from the call just above.
396 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)ji)) {
397 bsendmsg(ua, "%s\n", db_strerror(ua->db));
399 /* Now find all Incremental Jobs */
400 Mmsg(&query, uar_inc, edit_uint64(ji->JobTDate, ed1), cr.ClientId, fsr.FileSetId);
401 if (!db_sql_query(ua->db, query, NULL, NULL)) {
402 bsendmsg(ua, "%s\n", db_strerror(ua->db));
404 free_pool_memory(query);
405 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1);
407 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)ji)) {
408 bsendmsg(ua, "%s\n", db_strerror(ua->db));
410 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
411 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
418 if (*ji->JobIds == 0) {
419 bsendmsg(ua, _("No Jobs selected.\n"));
422 bsendmsg(ua, _("You have selected the following JobId: %s\n"), ji->JobIds);
424 memset(&jr, 0, sizeof(JOB_DBR));
427 for (p=ji->JobIds; ; ) {
428 int stat = next_jobid_from_list(&p, &JobId);
430 bsendmsg(ua, _("Invalid JobId in list.\n"));
437 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
438 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
441 ji->TotalFiles += jr.JobFiles;
446 static int next_jobid_from_list(char **p, uint32_t *JobId)
453 for (i=0; i<(int)sizeof(jobid); i++) {
454 if (*q == ',' || *q == 0) {
461 if (jobid[0] == 0 || !is_a_number(jobid)) {
465 *JobId = strtoul(jobid, NULL, 10);
470 * Callback handler make list of JobIds
472 static int jobid_handler(void *ctx, int num_fields, char **row)
474 JobIds *ji = (JobIds *)ctx;
476 if (strlen(ji->JobIds)+strlen(row[0])+2 < sizeof(ji->JobIds)) {
477 if (ji->JobIds[0] != 0) {
478 strcat(ji->JobIds, ",");
480 strcat(ji->JobIds, row[0]);
488 * Callback handler to pickup last Full backup JobId and ClientId
490 static int last_full_handler(void *ctx, int num_fields, char **row)
492 JobIds *ji = (JobIds *)ctx;
494 ji->JobTDate = strtoll(row[1], NULL, 10);
500 * Callback handler build fileset prompt list
502 static int fileset_handler(void *ctx, int num_fields, char **row)
504 char prompt[MAX_NAME_LENGTH+200];
506 snprintf(prompt, sizeof(prompt), "%s %s %s", row[0], row[1], row[2]);
507 add_prompt((UAContext *)ctx, prompt);
511 /* Forward referenced commands */
513 static int markcmd(UAContext *ua, TREE_CTX *tree);
514 static int countcmd(UAContext *ua, TREE_CTX *tree);
515 static int findcmd(UAContext *ua, TREE_CTX *tree);
516 static int lscmd(UAContext *ua, TREE_CTX *tree);
517 static int dircmd(UAContext *ua, TREE_CTX *tree);
518 static int helpcmd(UAContext *ua, TREE_CTX *tree);
519 static int cdcmd(UAContext *ua, TREE_CTX *tree);
520 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
521 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
522 static int quitcmd(UAContext *ua, TREE_CTX *tree);
525 struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; };
526 static struct cmdstruct commands[] = {
527 { N_("mark"), markcmd, _("mark file for restoration")},
528 { N_("unmark"), unmarkcmd, _("unmark file for restoration")},
529 { N_("cd"), cdcmd, _("change current directory")},
530 { N_("pwd"), pwdcmd, _("print current working directory")},
531 { N_("ls"), lscmd, _("list current directory")},
532 { N_("dir"), dircmd, _("list current directory")},
533 { N_("count"), countcmd, _("count marked files")},
534 { N_("find"), findcmd, _("find files")},
535 { N_("done"), quitcmd, _("leave file selection mode")},
536 { N_("exit"), quitcmd, _("exit = done")},
537 { N_("help"), helpcmd, _("print help")},
538 { N_("?"), helpcmd, _("print help")},
540 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
544 * Enter a prompt mode where the user can select/deselect
545 * files to be restored. This is sort of like a mini-shell
546 * that allows "cd", "pwd", "add", "rm", ...
548 static void user_select_files(TREE_CTX *tree)
552 bsendmsg(tree->ua, _(
553 "\nYou are now entering file selection mode where you add and\n"
554 "remove files to be restored. All files are initially added.\n"
555 "Enter \"done\" to leave this mode.\n\n"));
557 * Enter interactive command handler allowing selection
558 * of individual files.
560 tree->node = (TREE_NODE *)tree->root;
561 tree_getpath(tree->node, cwd, sizeof(cwd));
562 bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
564 int found, len, stat, i;
565 if (!get_cmd(tree->ua, "$ ")) {
568 parse_command_args(tree->ua);
569 if (tree->ua->argc == 0) {
573 len = strlen(tree->ua->argk[0]);
576 for (i=0; i<(int)comsize; i++) /* search for command */
577 if (strncasecmp(tree->ua->argk[0], _(commands[i].key), len) == 0) {
578 stat = (*commands[i].func)(tree->ua, tree); /* go execute command */
583 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
593 * Create new FileIndex entry for BSR
595 static RBSR_FINDEX *new_findex()
597 RBSR_FINDEX *fi = (RBSR_FINDEX *)bmalloc(sizeof(RBSR_FINDEX));
598 memset(fi, 0, sizeof(RBSR_FINDEX));
602 /* Free all BSR FileIndex entries */
603 static void free_findex(RBSR_FINDEX *fi)
606 free_findex(fi->next);
611 static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd)
614 if (fi->findex == fi->findex2) {
615 fprintf(fd, "FileIndex=%d\n", fi->findex);
617 fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
619 write_findex(ua, fi->next, fd);
624 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
627 if (fi->findex == fi->findex2) {
628 bsendmsg(ua, "FileIndex=%d\n", fi->findex);
630 bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
632 print_findex(ua, fi->next);
636 /* Create a new bootstrap record */
637 static RBSR *new_bsr()
639 RBSR *bsr = (RBSR *)bmalloc(sizeof(RBSR));
640 memset(bsr, 0, sizeof(RBSR));
644 /* Free the entire BSR */
645 static void free_bsr(RBSR *bsr)
648 free_findex(bsr->fi);
650 if (bsr->VolParams) {
651 free(bsr->VolParams);
658 * Complete the BSR by filling in the VolumeName and
659 * VolSessionId and VolSessionTime using the JobId
661 static int complete_bsr(UAContext *ua, RBSR *bsr)
666 memset(&jr, 0, sizeof(jr));
667 jr.JobId = bsr->JobId;
668 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
669 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
672 bsr->VolSessionId = jr.VolSessionId;
673 bsr->VolSessionTime = jr.VolSessionTime;
674 if ((bsr->VolCount=db_get_job_volume_parameters(ua->jcr, ua->db, bsr->JobId,
675 &(bsr->VolParams))) == 0) {
676 bsendmsg(ua, _("Unable to get Job Volume Parameters. ERR=%s\n"), db_strerror(ua->db));
677 if (bsr->VolParams) {
678 free(bsr->VolParams);
679 bsr->VolParams = NULL;
683 return complete_bsr(ua, bsr->next);
689 * Write the bootstrap record to file
691 static int write_bsr_file(UAContext *ua, RBSR *bsr)
694 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
698 Mmsg(&fname, "%s/restore.bsr", working_directory);
699 fd = fopen(fname, "w+");
701 bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"),
702 fname, strerror(errno));
703 free_pool_memory(fname);
706 write_bsr(ua, bsr, fd);
709 bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
710 bsendmsg(ua, _("\nThe restore job will require the following Volumes:\n"));
711 /* Create Unique list of Volumes using prompt list */
712 start_prompt(ua, "");
713 for (nbsr=bsr; nbsr; nbsr=nbsr->next) {
714 for (int i=0; i < nbsr->VolCount; i++) {
715 add_prompt(ua, nbsr->VolParams[i].VolumeName);
718 for (int i=0; i < ua->num_prompts; i++) {
719 bsendmsg(ua, " %s\n", ua->prompt[i]);
724 free_pool_memory(fname);
728 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
731 for (int i=0; i < bsr->VolCount; i++) {
732 fprintf(fd, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
733 fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
734 fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
735 fprintf(fd, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile,
736 bsr->VolParams[i].EndFile);
737 fprintf(fd, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
738 bsr->VolParams[i].EndBlock);
739 write_findex(ua, bsr->fi, fd);
741 write_bsr(ua, bsr->next, fd);
745 static void print_bsr(UAContext *ua, RBSR *bsr)
748 for (int i=0; i < bsr->VolCount; i++) {
749 bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
750 bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
751 bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
752 bsendmsg(ua, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile,
753 bsr->VolParams[i].EndFile);
754 bsendmsg(ua, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
755 bsr->VolParams[i].EndBlock);
756 print_findex(ua, bsr->fi);
758 print_bsr(ua, bsr->next);
764 * Add a FileIndex to the list of BootStrap records.
765 * Here we are only dealing with JobId's and the FileIndexes
766 * associated with those JobIds.
768 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
771 RBSR_FINDEX *fi, *lfi;
774 return; /* probably a dummy directory */
777 if (!bsr->fi) { /* if no FI add one */
778 /* This is the first FileIndex item in the chain */
779 bsr->fi = new_findex();
781 bsr->fi->findex = findex;
782 bsr->fi->findex2 = findex;
785 /* Walk down list of bsrs until we find the JobId */
786 if (bsr->JobId != JobId) {
787 for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
788 if (nbsr->JobId == JobId) {
794 if (!nbsr) { /* Must add new JobId */
795 /* Add new JobId at end of chain */
796 for (nbsr=bsr; nbsr->next; nbsr=nbsr->next)
798 nbsr->next = new_bsr();
799 nbsr->next->JobId = JobId;
800 nbsr->next->fi = new_findex();
801 nbsr->next->fi->findex = findex;
802 nbsr->next->fi->findex2 = findex;
808 * At this point, bsr points to bsr containing JobId,
809 * and we are sure that there is at least one fi record.
812 /* Check if this findex is smaller than first item */
813 if (findex < fi->findex) {
814 if ((findex+1) == fi->findex) {
815 fi->findex = findex; /* extend down */
818 fi = new_findex(); /* yes, insert before first item */
820 fi->findex2 = findex;
825 /* Walk down fi chain and find where to insert insert new FileIndex */
826 for ( ; fi; fi=fi->next) {
827 if (findex == (fi->findex2 + 1)) { /* extend up */
829 fi->findex2 = findex;
830 if (fi->next && ((findex+1) == fi->next->findex)) {
832 fi->findex2 = nfi->findex2;
833 fi->next = nfi->next;
838 if (findex < fi->findex) { /* add before */
839 if ((findex+1) == fi->findex) {
847 /* Add to last place found */
850 fi->findex2 = findex;
851 fi->next = lfi->next;
857 * This callback routine is responsible for inserting the
858 * items it gets into the directory tree. For each JobId selected
859 * this routine is called once for each file. We do not allow
860 * duplicate filenames, but instead keep the info from the most
861 * recent file entered (i.e. the JobIds are assumed to be sorted)
863 static int insert_tree_handler(void *ctx, int num_fields, char **row)
865 TREE_CTX *tree = (TREE_CTX *)ctx;
867 TREE_NODE *node, *new_node;
870 strip_trailing_junk(row[1]);
872 if (*row[0] != '/') { /* Must be Win32 directory */
880 sprintf(fname, "%s%s", row[0], row[1]);
881 if (tree->avail_node) {
882 node = tree->avail_node;
884 node = new_tree_node(tree->root, type);
885 tree->avail_node = node;
887 Dmsg3(200, "FI=%d type=%d fname=%s\n", node->FileIndex, type, fname);
888 new_node = insert_tree_node(fname, node, tree->root, NULL);
889 /* Note, if node already exists, save new one for next time */
890 if (new_node != node) {
891 tree->avail_node = node;
893 tree->avail_node = NULL;
895 new_node->FileIndex = atoi(row[2]);
896 new_node->JobId = atoi(row[3]);
897 new_node->type = type;
898 new_node->extract = 1; /* extract all by default */
905 * Set extract to value passed. We recursively walk
906 * down the tree setting all children if the
907 * node is a directory.
909 static void set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, int value)
915 node->extract = value;
916 /* For a non-file (i.e. directory), we see all the children */
917 if (node->type != TN_FILE) {
918 for (n=node->child; n; n=n->sibling) {
919 set_extract(ua, n, tree, value);
923 /* Ordinary file, we get the full path, look up the
924 * attributes, decode them, and if we are hard linked to
925 * a file that was saved, we must load that file too.
927 tree_getpath(node, cwd, sizeof(cwd));
929 fdbr.JobId = node->JobId;
930 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
932 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
933 /* If we point to a hard linked file, traverse the tree to
934 * find that file, and mark it for restoration as well.
937 for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
938 if (n->FileIndex == LinkFI) {
948 static int markcmd(UAContext *ua, TREE_CTX *tree)
954 if (!tree->node->child) {
957 for (node = tree->node->child; node; node=node->sibling) {
958 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
959 set_extract(ua, node, tree, 1);
965 static int countcmd(UAContext *ua, TREE_CTX *tree)
970 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
971 if (node->type != TN_NEWDIR) {
978 bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
982 static int findcmd(UAContext *ua, TREE_CTX *tree)
987 bsendmsg(ua, _("No file specification given.\n"));
991 for (int i=1; i < ua->argc; i++) {
992 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
993 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
994 tree_getpath(node, cwd, sizeof(cwd));
995 bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
1004 static int lscmd(UAContext *ua, TREE_CTX *tree)
1008 if (!tree->node->child) {
1011 for (node = tree->node->child; node; node=node->sibling) {
1012 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1013 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1014 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1020 extern char *getuser(uid_t uid);
1021 extern char *getgroup(gid_t gid);
1024 * This is actually the long form used for "dir"
1026 static void ls_output(char *buf, char *fname, int extract, struct stat *statp)
1032 p = encode_mode(statp->st_mode, buf);
1033 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
1035 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
1037 n = sprintf(p, "%8.8s ", edit_uint64(statp->st_size, ec1));
1039 p = encode_time(statp->st_ctime, p);
1053 * Like ls command, but give more detail on each file
1055 static int dircmd(UAContext *ua, TREE_CTX *tree)
1063 if (!tree->node->child) {
1066 for (node = tree->node->child; node; node=node->sibling) {
1067 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1068 tree_getpath(node, cwd, sizeof(cwd));
1070 fdbr.JobId = node->JobId;
1071 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
1073 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
1074 ls_output(buf, cwd, node->extract, &statp);
1075 bsendmsg(ua, "%s\n", buf);
1077 /* Something went wrong getting attributes -- print name */
1078 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1079 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1087 static int helpcmd(UAContext *ua, TREE_CTX *tree)
1092 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
1093 for (i=0; i<comsize; i++) {
1094 bsendmsg(ua, _(" %-10s %s\n"), _(commands[i].key), _(commands[i].help));
1101 * Change directories. Note, if the user specifies x: and it fails,
1102 * we assume it is a Win32 absolute cd rather than relative and
1103 * try a second time with /x: ... Win32 kludge.
1105 static int cdcmd(UAContext *ua, TREE_CTX *tree)
1110 if (ua->argc != 2) {
1113 node = tree_cwd(ua->argk[1], tree->root, tree->node);
1115 /* Try once more if Win32 drive -- make absolute */
1116 if (ua->argk[1][1] == ':') { /* win32 drive */
1118 strcat(cwd, ua->argk[1]);
1119 node = tree_cwd(cwd, tree->root, tree->node);
1122 bsendmsg(ua, _("Invalid path given.\n"));
1129 tree_getpath(tree->node, cwd, sizeof(cwd));
1130 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1134 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
1137 tree_getpath(tree->node, cwd, sizeof(cwd));
1138 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1143 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
1149 if (!tree->node->child) {
1152 for (node = tree->node->child; node; node=node->sibling) {
1153 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1154 set_extract(ua, node, tree, 0);
1160 static int quitcmd(UAContext *ua, TREE_CTX *tree)
1167 * Called here with each name to be added to the list. The name is
1168 * added to the list if it is not already in the list.
1170 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1172 NAME_LIST *name = (NAME_LIST *)ctx;
1174 if (name->num_ids == MAX_ID_LIST_LEN) {
1177 if (name->num_ids == name->max_ids) {
1178 if (name->max_ids == 0) {
1179 name->max_ids = 1000;
1180 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1182 name->max_ids = (name->max_ids * 3) / 2;
1183 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1186 for (int i=0; i<name->num_ids; i++) {
1187 if (strcmp(name->name[i], row[0]) == 0) {
1188 return 0; /* already in list, return */
1191 /* Add new name to list */
1192 name->name[name->num_ids++] = bstrdup(row[0]);
1198 * Print names in the list
1200 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1204 for (i=0; i < name_list->num_ids; i++) {
1205 bsendmsg(ua, "%s\n", name_list->name[i]);
1211 * Free names in the list
1213 static void free_name_list(NAME_LIST *name_list)
1217 for (i=0; i < name_list->num_ids; i++) {
1218 free(name_list->name[i]);
1220 if (name_list->name) {
1221 free(name_list->name);
1223 name_list->max_ids = 0;
1224 name_list->num_ids = 0;
1227 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JobIds *ji)
1229 char name[MAX_NAME_LENGTH];
1230 STORE *store = NULL;
1232 if (name_list->num_ids > 1) {
1233 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1234 "Restore is not possible. The MediaTypes used are:\n"));
1235 print_name_list(ua, name_list);
1236 ji->store = select_storage_resource(ua);
1240 if (name_list->num_ids == 0) {
1241 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1242 ji->store = select_storage_resource(ua);
1246 start_prompt(ua, _("The defined Storage resources are:\n"));
1248 while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
1249 if (strcmp(store->media_type, name_list->name[0]) == 0) {
1250 add_prompt(ua, store->hdr.name);
1254 do_prompt(ua, _("Select Storage resource"), name, sizeof(name));
1255 ji->store = (STORE *)GetResWithName(R_STORAGE, name);
1257 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1258 "MediaType %s, needed by the Jobs you selected.\n"
1259 "You will be allowed to select a Storage device later.\n"),
1260 name_list->name[0]);