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 if (!user_select_jobids(ua, &ji)) {
169 * Build the directory tree
171 tree.root = new_tree(ji.TotalFiles);
172 tree.root->fname = nofname;
174 query = get_pool_memory(PM_MESSAGE);
177 * For display purposes, the same JobId, with different volumes may
178 * appear more than once, however, we only insert it once.
180 for (p=ji.JobIds; next_jobid_from_list(&p, &JobId) > 0; ) {
182 if (JobId == last_JobId) {
183 continue; /* eliminate duplicate JobIds */
186 bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
188 * Find files for this JobId and insert them in the tree
190 Mmsg(&query, uar_sel_files, JobId);
191 if (!db_sql_query(ua->db, query, insert_tree_handler, (void *)&tree)) {
192 bsendmsg(ua, "%s", db_strerror(ua->db));
195 * Find the FileSets for this JobId and add to the name_list
197 Mmsg(&query, uar_mediatype, JobId);
198 if (!db_sql_query(ua->db, query, unique_name_list_handler, (void *)&name_list)) {
199 bsendmsg(ua, "%s", db_strerror(ua->db));
203 bsendmsg(ua, "%d items inserted into the tree and marked for extraction.\n");
204 free_pool_memory(query);
206 /* Check MediaType and select storage that corresponds */
207 get_storage_from_mediatype(ua, &name_list, &ji);
208 free_name_list(&name_list);
210 /* Let the user select which files to restore */
211 user_select_files(&tree);
214 * Walk down through the tree finding all files marked to be
215 * extracted making a bootstrap file.
218 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
219 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
221 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
222 add_findex(bsr, node->JobId, node->FileIndex);
227 free_tree(tree.root); /* free the directory tree */
230 if (!complete_bsr(ua, bsr)) { /* find Vol, SessId, SessTime from JobIds */
231 bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
235 // print_bsr(ua, bsr);
236 write_bsr_file(ua, bsr);
237 bsendmsg(ua, _("\n%u files selected to restore.\n\n"), selected_files);
239 bsendmsg(ua, _("No files selected to restore.\n"));
243 if (restore_jobs == 1) {
246 job = select_restore_job_resource(ua);
249 bsendmsg(ua, _("No Restore Job resource found!\n"));
253 if (ji.ClientName[0]) {
255 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
256 job->hdr.name, ji.ClientName, ji.store?ji.store->hdr.name:"",
260 "run job=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
261 job->hdr.name, ji.store?ji.store->hdr.name:"", working_directory);
265 Dmsg1(400, "Submitting: %s\n", ua->cmd);
267 parse_command_args(ua);
270 bsendmsg(ua, _("Restore command done.\n"));
275 * The first step in the restore process is for the user to
276 * select a list of JobIds from which he will subsequently
277 * select which files are to be restored.
279 static int user_select_jobids(UAContext *ua, JobIds *ji)
281 char fileset_name[MAX_NAME_LENGTH];
290 "List last 20 Jobs run",
291 "List Jobs where a given File is saved",
292 "Enter list of JobIds to select",
293 "Enter SQL list command",
294 "Select the most recent backup for a client",
298 bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
299 "to be restored. You will be presented several methods\n"
300 "of specifying the JobIds. Then you will be allowed to\n"
301 "select which files from those JobIds are to be restored.\n\n"));
304 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
305 for (int i=0; list[i]; i++) {
306 add_prompt(ua, list[i]);
309 switch (do_prompt(ua, "Select item: ", NULL, 0)) {
312 case 0: /* list last 20 Jobs run */
313 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1);
316 case 1: /* list where a file is saved */
317 if (!get_cmd(ua, _("Enter Filename: "))) {
320 query = get_pool_memory(PM_MESSAGE);
321 Mmsg(&query, uar_file, ua->cmd);
322 db_list_sql_query(ua->jcr, ua->db, query, prtit, ua, 1);
323 free_pool_memory(query);
326 case 2: /* enter a list of JobIds */
327 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
330 bstrncpy(ji->JobIds, ua->cmd, sizeof(ji->JobIds));
332 case 3: /* Enter an SQL list command */
333 if (!get_cmd(ua, _("Enter SQL list command: "))) {
336 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1);
339 case 4: /* Select the most recent backups */
340 query = get_pool_memory(PM_MESSAGE);
341 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
342 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
343 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
344 bsendmsg(ua, "%s\n", db_strerror(ua->db));
346 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
347 bsendmsg(ua, "%s\n", db_strerror(ua->db));
350 * Select Client from the Catalog
352 memset(&cr, 0, sizeof(cr));
353 if (!get_client_dbr(ua, &cr)) {
354 free_pool_memory(query);
355 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
356 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
359 bstrncpy(ji->ClientName, cr.Name, sizeof(ji->ClientName));
364 Mmsg(&query, uar_sel_fileset, cr.ClientId, cr.ClientId);
365 start_prompt(ua, _("The defined FileSet resources are:\n"));
366 if (!db_sql_query(ua->db, query, fileset_handler, (void *)ua)) {
367 bsendmsg(ua, "%s\n", db_strerror(ua->db));
369 if (do_prompt(ua, _("Select FileSet resource"),
370 fileset_name, sizeof(fileset_name)) < 0) {
371 free_pool_memory(query);
372 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
373 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
376 fsr.FileSetId = atoi(fileset_name); /* Id is first part of name */
377 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
378 bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
379 bsendmsg(ua, _("This probably means you modified the FileSet.\n"
380 "Continuing anyway.\n"));
383 /* Find JobId of last Full backup for this client, fileset */
384 Mmsg(&query, uar_last_full, cr.ClientId, cr.ClientId, fsr.FileSetId);
385 if (!db_sql_query(ua->db, query, NULL, NULL)) {
386 bsendmsg(ua, "%s\n", db_strerror(ua->db));
388 /* Find all Volumes used by that JobId */
389 if (!db_sql_query(ua->db, uar_full, NULL,NULL)) {
390 bsendmsg(ua, "%s\n", db_strerror(ua->db));
392 /* Note, this is needed as I don't seem to get the callback
393 * from the call just above.
395 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)ji)) {
396 bsendmsg(ua, "%s\n", db_strerror(ua->db));
398 /* Now find all Incremental Jobs */
399 Mmsg(&query, uar_inc, edit_uint64(ji->JobTDate, ed1), cr.ClientId, fsr.FileSetId);
400 if (!db_sql_query(ua->db, query, NULL, NULL)) {
401 bsendmsg(ua, "%s\n", db_strerror(ua->db));
403 free_pool_memory(query);
404 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1);
406 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)ji)) {
407 bsendmsg(ua, "%s\n", db_strerror(ua->db));
409 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
410 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
417 if (*ji->JobIds == 0) {
418 bsendmsg(ua, _("No Jobs selected.\n"));
421 bsendmsg(ua, _("You have selected the following JobId: %s\n"), ji->JobIds);
423 memset(&jr, 0, sizeof(JOB_DBR));
426 for (p=ji->JobIds; ; ) {
427 int stat = next_jobid_from_list(&p, &JobId);
429 bsendmsg(ua, _("Invalid JobId in list.\n"));
436 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
437 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
440 ji->TotalFiles += jr.JobFiles;
445 static int next_jobid_from_list(char **p, uint32_t *JobId)
452 for (i=0; i<(int)sizeof(jobid); i++) {
453 if (*q == ',' || *q == 0) {
460 if (jobid[0] == 0 || !is_a_number(jobid)) {
464 *JobId = strtoul(jobid, NULL, 10);
469 * Callback handler make list of JobIds
471 static int jobid_handler(void *ctx, int num_fields, char **row)
473 JobIds *ji = (JobIds *)ctx;
475 if (strlen(ji->JobIds)+strlen(row[0])+2 < sizeof(ji->JobIds)) {
476 if (ji->JobIds[0] != 0) {
477 strcat(ji->JobIds, ",");
479 strcat(ji->JobIds, row[0]);
487 * Callback handler to pickup last Full backup JobId and ClientId
489 static int last_full_handler(void *ctx, int num_fields, char **row)
491 JobIds *ji = (JobIds *)ctx;
493 ji->JobTDate = strtoll(row[1], NULL, 10);
499 * Callback handler build fileset prompt list
501 static int fileset_handler(void *ctx, int num_fields, char **row)
503 char prompt[MAX_NAME_LENGTH+200];
505 snprintf(prompt, sizeof(prompt), "%s %s %s", row[0], row[1], row[2]);
506 add_prompt((UAContext *)ctx, prompt);
510 /* Forward referenced commands */
512 static int markcmd(UAContext *ua, TREE_CTX *tree);
513 static int countcmd(UAContext *ua, TREE_CTX *tree);
514 static int findcmd(UAContext *ua, TREE_CTX *tree);
515 static int lscmd(UAContext *ua, TREE_CTX *tree);
516 static int dircmd(UAContext *ua, TREE_CTX *tree);
517 static int helpcmd(UAContext *ua, TREE_CTX *tree);
518 static int cdcmd(UAContext *ua, TREE_CTX *tree);
519 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
520 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
521 static int quitcmd(UAContext *ua, TREE_CTX *tree);
524 struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; };
525 static struct cmdstruct commands[] = {
526 { N_("mark"), markcmd, _("mark file for restoration")},
527 { N_("unmark"), unmarkcmd, _("unmark file for restoration")},
528 { N_("cd"), cdcmd, _("change current directory")},
529 { N_("pwd"), pwdcmd, _("print current working directory")},
530 { N_("ls"), lscmd, _("list current directory")},
531 { N_("dir"), dircmd, _("list current directory")},
532 { N_("count"), countcmd, _("count marked files")},
533 { N_("find"), findcmd, _("find files")},
534 { N_("done"), quitcmd, _("leave file selection mode")},
535 { N_("exit"), quitcmd, _("exit = done")},
536 { N_("help"), helpcmd, _("print help")},
537 { N_("?"), helpcmd, _("print help")},
539 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
543 * Enter a prompt mode where the user can select/deselect
544 * files to be restored. This is sort of like a mini-shell
545 * that allows "cd", "pwd", "add", "rm", ...
547 static void user_select_files(TREE_CTX *tree)
551 bsendmsg(tree->ua, _(
552 "\nYou are now entering file selection mode where you add and\n"
553 "remove files to be restored. All files are initially added.\n"
554 "Enter \"done\" to leave this mode.\n\n"));
556 * Enter interactive command handler allowing selection
557 * of individual files.
559 tree->node = (TREE_NODE *)tree->root;
560 tree_getpath(tree->node, cwd, sizeof(cwd));
561 bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
563 int found, len, stat, i;
564 if (!get_cmd(tree->ua, "$ ")) {
567 parse_command_args(tree->ua);
568 if (tree->ua->argc == 0) {
572 len = strlen(tree->ua->argk[0]);
575 for (i=0; i<(int)comsize; i++) /* search for command */
576 if (strncasecmp(tree->ua->argk[0], _(commands[i].key), len) == 0) {
577 stat = (*commands[i].func)(tree->ua, tree); /* go execute command */
582 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
592 * Create new FileIndex entry for BSR
594 static RBSR_FINDEX *new_findex()
596 RBSR_FINDEX *fi = (RBSR_FINDEX *)bmalloc(sizeof(RBSR_FINDEX));
597 memset(fi, 0, sizeof(RBSR_FINDEX));
601 /* Free all BSR FileIndex entries */
602 static void free_findex(RBSR_FINDEX *fi)
605 free_findex(fi->next);
610 static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd)
613 if (fi->findex == fi->findex2) {
614 fprintf(fd, "FileIndex=%d\n", fi->findex);
616 fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
618 write_findex(ua, fi->next, fd);
623 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
626 if (fi->findex == fi->findex2) {
627 bsendmsg(ua, "FileIndex=%d\n", fi->findex);
629 bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
631 print_findex(ua, fi->next);
635 /* Create a new bootstrap record */
636 static RBSR *new_bsr()
638 RBSR *bsr = (RBSR *)bmalloc(sizeof(RBSR));
639 memset(bsr, 0, sizeof(RBSR));
643 /* Free the entire BSR */
644 static void free_bsr(RBSR *bsr)
647 free_findex(bsr->fi);
649 if (bsr->VolParams) {
650 free(bsr->VolParams);
657 * Complete the BSR by filling in the VolumeName and
658 * VolSessionId and VolSessionTime using the JobId
660 static int complete_bsr(UAContext *ua, RBSR *bsr)
665 memset(&jr, 0, sizeof(jr));
666 jr.JobId = bsr->JobId;
667 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
668 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
671 bsr->VolSessionId = jr.VolSessionId;
672 bsr->VolSessionTime = jr.VolSessionTime;
673 if ((bsr->VolCount=db_get_job_volume_parameters(ua->jcr, ua->db, bsr->JobId,
674 &(bsr->VolParams))) == 0) {
675 bsendmsg(ua, _("Unable to get Job Volume Parameters. ERR=%s\n"), db_strerror(ua->db));
676 if (bsr->VolParams) {
677 free(bsr->VolParams);
678 bsr->VolParams = NULL;
682 return complete_bsr(ua, bsr->next);
688 * Write the bootstrap record to file
690 static int write_bsr_file(UAContext *ua, RBSR *bsr)
693 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
697 Mmsg(&fname, "%s/restore.bsr", working_directory);
698 fd = fopen(fname, "w+");
700 bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"),
701 fname, strerror(errno));
702 free_pool_memory(fname);
705 write_bsr(ua, bsr, fd);
708 bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
709 bsendmsg(ua, _("\nThe restore job will require the following Volumes:\n"));
710 /* Create Unique list of Volumes using prompt list */
711 start_prompt(ua, "");
712 for (nbsr=bsr; nbsr; nbsr=nbsr->next) {
713 for (int i=0; i < nbsr->VolCount; i++) {
714 add_prompt(ua, nbsr->VolParams[i].VolumeName);
717 for (int i=0; i < ua->num_prompts; i++) {
718 bsendmsg(ua, " %s\n", ua->prompt[i]);
723 free_pool_memory(fname);
727 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
730 for (int i=0; i < bsr->VolCount; i++) {
731 fprintf(fd, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
732 fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
733 fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
734 fprintf(fd, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile,
735 bsr->VolParams[i].EndFile);
736 fprintf(fd, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
737 bsr->VolParams[i].EndBlock);
738 write_findex(ua, bsr->fi, fd);
740 write_bsr(ua, bsr->next, fd);
744 static void print_bsr(UAContext *ua, RBSR *bsr)
747 for (int i=0; i < bsr->VolCount; i++) {
748 bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
749 bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
750 bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
751 bsendmsg(ua, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile,
752 bsr->VolParams[i].EndFile);
753 bsendmsg(ua, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
754 bsr->VolParams[i].EndBlock);
755 print_findex(ua, bsr->fi);
757 print_bsr(ua, bsr->next);
763 * Add a FileIndex to the list of BootStrap records.
764 * Here we are only dealing with JobId's and the FileIndexes
765 * associated with those JobIds.
767 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
770 RBSR_FINDEX *fi, *lfi;
773 return; /* probably a dummy directory */
776 if (!bsr->fi) { /* if no FI add one */
777 /* This is the first FileIndex item in the chain */
778 bsr->fi = new_findex();
780 bsr->fi->findex = findex;
781 bsr->fi->findex2 = findex;
784 /* Walk down list of bsrs until we find the JobId */
785 if (bsr->JobId != JobId) {
786 for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
787 if (nbsr->JobId == JobId) {
793 if (!nbsr) { /* Must add new JobId */
794 /* Add new JobId at end of chain */
795 for (nbsr=bsr; nbsr->next; nbsr=nbsr->next)
797 nbsr->next = new_bsr();
798 nbsr->next->JobId = JobId;
799 nbsr->next->fi = new_findex();
800 nbsr->next->fi->findex = findex;
801 nbsr->next->fi->findex2 = findex;
807 * At this point, bsr points to bsr containing JobId,
808 * and we are sure that there is at least one fi record.
811 /* Check if this findex is smaller than first item */
812 if (findex < fi->findex) {
813 if ((findex+1) == fi->findex) {
814 fi->findex = findex; /* extend down */
817 fi = new_findex(); /* yes, insert before first item */
819 fi->findex2 = findex;
824 /* Walk down fi chain and find where to insert insert new FileIndex */
825 for ( ; fi; fi=fi->next) {
826 if (findex == (fi->findex2 + 1)) { /* extend up */
828 fi->findex2 = findex;
829 if (fi->next && ((findex+1) == fi->next->findex)) {
831 fi->findex2 = nfi->findex2;
832 fi->next = nfi->next;
837 if (findex < fi->findex) { /* add before */
838 if ((findex+1) == fi->findex) {
846 /* Add to last place found */
849 fi->findex2 = findex;
850 fi->next = lfi->next;
856 * This callback routine is responsible for inserting the
857 * items it gets into the directory tree. For each JobId selected
858 * this routine is called once for each file. We do not allow
859 * duplicate filenames, but instead keep the info from the most
860 * recent file entered (i.e. the JobIds are assumed to be sorted)
862 static int insert_tree_handler(void *ctx, int num_fields, char **row)
864 TREE_CTX *tree = (TREE_CTX *)ctx;
866 TREE_NODE *node, *new_node;
869 strip_trailing_junk(row[1]);
871 if (*row[0] != '/') { /* Must be Win32 directory */
879 sprintf(fname, "%s%s", row[0], row[1]);
880 if (tree->avail_node) {
881 node = tree->avail_node;
883 node = new_tree_node(tree->root, type);
884 tree->avail_node = node;
886 Dmsg3(200, "FI=%d type=%d fname=%s\n", node->FileIndex, type, fname);
887 new_node = insert_tree_node(fname, node, tree->root, NULL);
888 /* Note, if node already exists, save new one for next time */
889 if (new_node != node) {
890 tree->avail_node = node;
892 tree->avail_node = NULL;
894 new_node->FileIndex = atoi(row[2]);
895 new_node->JobId = atoi(row[3]);
896 new_node->type = type;
897 new_node->extract = 1; /* extract all by default */
904 * Set extract to value passed. We recursively walk
905 * down the tree setting all children if the
906 * node is a directory.
908 static void set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, int value)
914 node->extract = value;
915 /* For a non-file (i.e. directory), we see all the children */
916 if (node->type != TN_FILE) {
917 for (n=node->child; n; n=n->sibling) {
918 set_extract(ua, n, tree, value);
922 /* Ordinary file, we get the full path, look up the
923 * attributes, decode them, and if we are hard linked to
924 * a file that was saved, we must load that file too.
926 tree_getpath(node, cwd, sizeof(cwd));
928 fdbr.JobId = node->JobId;
929 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
931 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. It
935 * must have the Link we just obtained and the same JobId.
938 for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
939 if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
949 static int markcmd(UAContext *ua, TREE_CTX *tree)
955 if (!tree->node->child) {
958 for (node = tree->node->child; node; node=node->sibling) {
959 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
960 set_extract(ua, node, tree, 1);
966 static int countcmd(UAContext *ua, TREE_CTX *tree)
971 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
972 if (node->type != TN_NEWDIR) {
979 bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
983 static int findcmd(UAContext *ua, TREE_CTX *tree)
988 bsendmsg(ua, _("No file specification given.\n"));
992 for (int i=1; i < ua->argc; i++) {
993 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
994 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
995 tree_getpath(node, cwd, sizeof(cwd));
996 bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
1005 static int lscmd(UAContext *ua, TREE_CTX *tree)
1009 if (!tree->node->child) {
1012 for (node = tree->node->child; node; node=node->sibling) {
1013 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1014 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1015 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1021 extern char *getuser(uid_t uid);
1022 extern char *getgroup(gid_t gid);
1025 * This is actually the long form used for "dir"
1027 static void ls_output(char *buf, char *fname, int extract, struct stat *statp)
1033 p = encode_mode(statp->st_mode, buf);
1034 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
1036 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
1038 n = sprintf(p, "%8.8s ", edit_uint64(statp->st_size, ec1));
1040 p = encode_time(statp->st_ctime, p);
1054 * Like ls command, but give more detail on each file
1056 static int dircmd(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 tree_getpath(node, cwd, sizeof(cwd));
1071 fdbr.JobId = node->JobId;
1072 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
1074 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
1075 ls_output(buf, cwd, node->extract, &statp);
1076 bsendmsg(ua, "%s\n", buf);
1078 /* Something went wrong getting attributes -- print name */
1079 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1080 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1088 static int helpcmd(UAContext *ua, TREE_CTX *tree)
1093 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
1094 for (i=0; i<comsize; i++) {
1095 bsendmsg(ua, _(" %-10s %s\n"), _(commands[i].key), _(commands[i].help));
1102 * Change directories. Note, if the user specifies x: and it fails,
1103 * we assume it is a Win32 absolute cd rather than relative and
1104 * try a second time with /x: ... Win32 kludge.
1106 static int cdcmd(UAContext *ua, TREE_CTX *tree)
1111 if (ua->argc != 2) {
1114 node = tree_cwd(ua->argk[1], tree->root, tree->node);
1116 /* Try once more if Win32 drive -- make absolute */
1117 if (ua->argk[1][1] == ':') { /* win32 drive */
1119 strcat(cwd, ua->argk[1]);
1120 node = tree_cwd(cwd, tree->root, tree->node);
1123 bsendmsg(ua, _("Invalid path given.\n"));
1130 tree_getpath(tree->node, cwd, sizeof(cwd));
1131 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1135 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
1138 tree_getpath(tree->node, cwd, sizeof(cwd));
1139 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1144 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
1150 if (!tree->node->child) {
1153 for (node = tree->node->child; node; node=node->sibling) {
1154 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1155 set_extract(ua, node, tree, 0);
1161 static int quitcmd(UAContext *ua, TREE_CTX *tree)
1168 * Called here with each name to be added to the list. The name is
1169 * added to the list if it is not already in the list.
1171 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1173 NAME_LIST *name = (NAME_LIST *)ctx;
1175 if (name->num_ids == MAX_ID_LIST_LEN) {
1178 if (name->num_ids == name->max_ids) {
1179 if (name->max_ids == 0) {
1180 name->max_ids = 1000;
1181 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1183 name->max_ids = (name->max_ids * 3) / 2;
1184 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1187 for (int i=0; i<name->num_ids; i++) {
1188 if (strcmp(name->name[i], row[0]) == 0) {
1189 return 0; /* already in list, return */
1192 /* Add new name to list */
1193 name->name[name->num_ids++] = bstrdup(row[0]);
1199 * Print names in the list
1201 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1205 for (i=0; i < name_list->num_ids; i++) {
1206 bsendmsg(ua, "%s\n", name_list->name[i]);
1212 * Free names in the list
1214 static void free_name_list(NAME_LIST *name_list)
1218 for (i=0; i < name_list->num_ids; i++) {
1219 free(name_list->name[i]);
1221 if (name_list->name) {
1222 free(name_list->name);
1224 name_list->max_ids = 0;
1225 name_list->num_ids = 0;
1228 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JobIds *ji)
1230 char name[MAX_NAME_LENGTH];
1231 STORE *store = NULL;
1233 if (name_list->num_ids > 1) {
1234 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1235 "Restore is not possible. The MediaTypes used are:\n"));
1236 print_name_list(ua, name_list);
1237 ji->store = select_storage_resource(ua);
1241 if (name_list->num_ids == 0) {
1242 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1243 ji->store = select_storage_resource(ua);
1247 start_prompt(ua, _("The defined Storage resources are:\n"));
1249 while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
1250 if (strcmp(store->media_type, name_list->name[0]) == 0) {
1251 add_prompt(ua, store->hdr.name);
1255 do_prompt(ua, _("Select Storage resource"), name, sizeof(name));
1256 ji->store = (STORE *)GetResWithName(R_STORAGE, name);
1258 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1259 "MediaType %s, needed by the Jobs you selected.\n"
1260 "You will be allowed to select a Storage device later.\n"),
1261 name_list->name[0]);