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 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));
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->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->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->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->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->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->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->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->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(TREE_NODE *node, int value)
913 node->extract = value;
914 if (node->type != TN_FILE) {
915 for (n=node->child; n; n=n->sibling) {
916 set_extract(n, value);
921 static int markcmd(UAContext *ua, TREE_CTX *tree)
927 if (!tree->node->child) {
930 for (node = tree->node->child; node; node=node->sibling) {
931 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
932 set_extract(node, 1);
938 static int countcmd(UAContext *ua, TREE_CTX *tree)
943 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
944 if (node->type != TN_NEWDIR) {
951 bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
955 static int findcmd(UAContext *ua, TREE_CTX *tree)
960 bsendmsg(ua, _("No file specification given.\n"));
964 for (int i=1; i < ua->argc; i++) {
965 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
966 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
967 tree_getpath(node, cwd, sizeof(cwd));
968 bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
977 static int lscmd(UAContext *ua, TREE_CTX *tree)
981 if (!tree->node->child) {
984 for (node = tree->node->child; node; node=node->sibling) {
985 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
986 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
987 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
993 extern char *getuser(uid_t uid);
994 extern char *getgroup(gid_t gid);
996 static void ls_output(char *buf, char *fname, struct stat *statp)
1002 // Dmsg2(000, "%s mode=0%o\n", fname, statp->st_mode);
1004 p = encode_mode(statp->st_mode, buf);
1005 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
1007 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
1009 n = sprintf(p, "%8.8s ", edit_uint64(statp->st_size, ec1));
1011 p = encode_time(statp->st_ctime, p);
1021 * Like ls command, but give more detail on each file
1023 static int dircmd(UAContext *ua, TREE_CTX *tree)
1031 if (!tree->node->child) {
1034 for (node = tree->node->child; node; node=node->sibling) {
1035 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1036 tree_getpath(node, cwd, sizeof(cwd));
1038 fdbr.JobId = node->JobId;
1039 if (db_get_file_attributes_record(ua->db, cwd, &fdbr)) {
1040 decode_stat(fdbr.LStat, &statp); /* decode stat pkt */
1041 ls_output(buf, cwd, &statp);
1042 bsendmsg(ua, "%s\n", buf);
1044 /* Something went wrong getting attributes -- print name */
1045 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1046 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1054 static int helpcmd(UAContext *ua, TREE_CTX *tree)
1059 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
1060 for (i=0; i<comsize; i++) {
1061 bsendmsg(ua, _(" %-10s %s\n"), _(commands[i].key), _(commands[i].help));
1068 * Change directories. Note, if the user specifies x: and it fails,
1069 * we assume it is a Win32 absolute cd rather than relative and
1070 * try a second time with /x: ... Win32 kludge.
1072 static int cdcmd(UAContext *ua, TREE_CTX *tree)
1077 if (ua->argc != 2) {
1080 node = tree_cwd(ua->argk[1], tree->root, tree->node);
1082 /* Try once more if Win32 drive -- make absolute */
1083 if (ua->argk[1][1] == ':') { /* win32 drive */
1085 strcat(cwd, ua->argk[1]);
1086 node = tree_cwd(cwd, tree->root, tree->node);
1089 bsendmsg(ua, _("Invalid path given.\n"));
1096 tree_getpath(tree->node, cwd, sizeof(cwd));
1097 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1101 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
1104 tree_getpath(tree->node, cwd, sizeof(cwd));
1105 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1110 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
1116 if (!tree->node->child) {
1119 for (node = tree->node->child; node; node=node->sibling) {
1120 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1121 set_extract(node, 0);
1127 static int quitcmd(UAContext *ua, TREE_CTX *tree)
1134 * Called here with each name to be added to the list. The name is
1135 * added to the list if it is not already in the list.
1137 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1139 NAME_LIST *name = (NAME_LIST *)ctx;
1141 if (name->num_ids == MAX_ID_LIST_LEN) {
1144 if (name->num_ids == name->max_ids) {
1145 if (name->max_ids == 0) {
1146 name->max_ids = 1000;
1147 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1149 name->max_ids = (name->max_ids * 3) / 2;
1150 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1153 for (int i=0; i<name->num_ids; i++) {
1154 if (strcmp(name->name[i], row[0]) == 0) {
1155 return 0; /* already in list, return */
1158 /* Add new name to list */
1159 name->name[name->num_ids++] = bstrdup(row[0]);
1165 * Print names in the list
1167 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1171 for (i=0; i < name_list->num_ids; i++) {
1172 bsendmsg(ua, "%s\n", name_list->name[i]);
1178 * Free names in the list
1180 static void free_name_list(NAME_LIST *name_list)
1184 for (i=0; i < name_list->num_ids; i++) {
1185 free(name_list->name[i]);
1187 if (name_list->name) {
1188 free(name_list->name);
1190 name_list->max_ids = 0;
1191 name_list->num_ids = 0;
1194 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JobIds *ji)
1196 char name[MAX_NAME_LENGTH];
1197 STORE *store = NULL;
1199 if (name_list->num_ids > 1) {
1200 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1201 "Restore is not possible. The MediaTypes used are:\n"));
1202 print_name_list(ua, name_list);
1203 ji->store = select_storage_resource(ua);
1207 if (name_list->num_ids == 0) {
1208 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1209 ji->store = select_storage_resource(ua);
1213 start_prompt(ua, _("The defined Storage resources are:\n"));
1215 while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
1216 if (strcmp(store->media_type, name_list->name[0]) == 0) {
1217 add_prompt(ua, store->hdr.name);
1221 do_prompt(ua, _("Select Storage resource"), name, sizeof(name));
1222 ji->store = (STORE *)GetResWithName(R_STORAGE, name);
1224 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1225 "MediaType %s, needed by the Jobs you selected.\n"
1226 "You will be allowed to select a Storage device later.\n"),
1227 name_list->name[0]);