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 {
70 /* FileIndex entry in bootstrap record */
71 typedef struct s_rbsr_findex {
72 struct s_rbsr_findex *next;
77 /* Restore bootstrap record -- not the real one, but useful here */
78 typedef struct s_rbsr {
79 struct s_rbsr *next; /* next JobId */
80 uint32_t JobId; /* JobId this bsr */
81 uint32_t VolSessionId;
82 uint32_t VolSessionTime;
87 char *VolumeName; /* Volume name */
88 RBSR_FINDEX *fi; /* File indexes this JobId */
91 typedef struct s_name_ctx {
92 char **name; /* list of names */
93 int num_ids; /* ids stored */
94 int max_ids; /* size of array */
95 int num_del; /* number deleted */
96 int tot_ids; /* total to process */
99 #define MAX_ID_LIST_LEN 1000000
102 /* Forward referenced functions */
103 static RBSR *new_bsr();
104 static void free_bsr(RBSR *bsr);
105 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd);
106 static int write_bsr_file(UAContext *ua, RBSR *bsr);
107 static void print_bsr(UAContext *ua, RBSR *bsr);
108 static int complete_bsr(UAContext *ua, RBSR *bsr);
109 static int insert_tree_handler(void *ctx, int num_fields, char **row);
110 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex);
111 static int last_full_handler(void *ctx, int num_fields, char **row);
112 static int jobid_handler(void *ctx, int num_fields, char **row);
113 static int next_jobid_from_list(char **p, uint32_t *JobId);
114 static int user_select_jobids(UAContext *ua, JobIds *ji);
115 static void user_select_files(TREE_CTX *tree);
116 static int fileset_handler(void *ctx, int num_fields, char **row);
117 static void print_name_list(UAContext *ua, NAME_LIST *name_list);
118 static int unique_name_list_handler(void *ctx, int num_fields, char **row);
119 static void free_name_list(NAME_LIST *name_list);
120 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JobIds *ji);
127 int restorecmd(UAContext *ua, char *cmd)
131 JobId_t JobId, last_JobId;
137 JOB *restore_job = NULL;
138 int restore_jobs = 0;
140 uint32_t selected_files = 0;
146 memset(&tree, 0, sizeof(TREE_CTX));
147 memset(&name_list, 0, sizeof(name_list));
148 memset(&ji, 0, sizeof(ji));
150 /* Ensure there is at least one Restore Job */
152 while ( (job = (JOB *)GetNextRes(R_JOB, (RES *)job)) ) {
153 if (job->JobType == JT_RESTORE) {
163 "No Restore Job Resource found. You must create at least\n"
164 "one before running this command.\n"));
169 if (!user_select_jobids(ua, &ji)) {
174 * Build the directory tree
176 tree.root = new_tree(ji.TotalFiles);
177 tree.root->fname = nofname;
179 query = get_pool_memory(PM_MESSAGE);
182 * For display purposes, the same JobId, with different volumes may
183 * appear more than once, however, we only insert it once.
185 for (p=ji.JobIds; next_jobid_from_list(&p, &JobId) > 0; ) {
187 if (JobId == last_JobId) {
188 continue; /* eliminate duplicate JobIds */
191 bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
193 * Find files for this JobId and insert them in the tree
195 Mmsg(&query, uar_sel_files, JobId);
196 if (!db_sql_query(ua->db, query, insert_tree_handler, (void *)&tree)) {
197 bsendmsg(ua, "%s", db_strerror(ua->db));
200 * Find the FileSets for this JobId and add to the name_list
202 Mmsg(&query, uar_mediatype, JobId);
203 if (!db_sql_query(ua->db, query, unique_name_list_handler, (void *)&name_list)) {
204 bsendmsg(ua, "%s", db_strerror(ua->db));
209 free_pool_memory(query);
211 /* Check MediaType and select storage that corresponds */
212 get_storage_from_mediatype(ua, &name_list, &ji);
213 free_name_list(&name_list);
215 /* Let the user select which files to restore */
216 user_select_files(&tree);
219 * Walk down through the tree finding all files marked to be
220 * extracted making a bootstrap file.
223 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
224 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
226 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
227 add_findex(bsr, node->JobId, node->FileIndex);
232 free_tree(tree.root); /* free the directory tree */
235 if (!complete_bsr(ua, bsr)) { /* find Vol, SessId, SessTime from JobIds */
236 bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
240 // print_bsr(ua, bsr);
241 write_bsr_file(ua, bsr);
242 bsendmsg(ua, _("\n%u files selected to restore.\n\n"), selected_files);
244 bsendmsg(ua, _("No files selected to restore.\n"));
248 if (restore_jobs == 1) {
251 job = select_restore_job_resource(ua);
254 bsendmsg(ua, _("No Restore Job resource found!\n"));
260 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
261 job->hdr.name, ji.client->hdr.name, ji.store?ji.store->hdr.name:"",
265 "run job=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
266 job->hdr.name, ji.store?ji.store->hdr.name:"", working_directory);
270 Dmsg1(400, "Submitting: %s\n", ua->cmd);
272 parse_command_args(ua);
275 bsendmsg(ua, _("Restore command done.\n"));
280 * The first step in the restore process is for the user to
281 * select a list of JobIds from which he will subsequently
282 * select which files are to be restored.
284 static int user_select_jobids(UAContext *ua, JobIds *ji)
286 char fileset_name[MAX_NAME_LENGTH];
294 "List last 20 Jobs run",
295 "List Jobs where a given File is saved",
296 "Enter list of JobIds to select",
297 "Enter SQL list command",
298 "Select the most recent backup for a client",
302 bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
303 "to be restored. You will be presented several methods\n"
304 "of specifying the JobIds. Then you will be allowed to\n"
305 "select which files from those JobIds are to be restored.\n\n"));
308 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
309 for (int i=0; list[i]; i++) {
310 add_prompt(ua, list[i]);
313 switch (do_prompt(ua, "Select item: ", NULL, 0)) {
316 case 0: /* list last 20 Jobs run */
317 db_list_sql_query(ua->db, uar_list_jobs, prtit, ua, 1);
320 case 1: /* list where a file is saved */
321 if (!get_cmd(ua, _("Enter Filename: "))) {
324 query = get_pool_memory(PM_MESSAGE);
325 Mmsg(&query, uar_file, ua->cmd);
326 db_list_sql_query(ua->db, query, prtit, ua, 1);
327 free_pool_memory(query);
330 case 2: /* enter a list of JobIds */
331 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
334 bstrncpy(ji->JobIds, ua->cmd, sizeof(ji->JobIds));
336 case 3: /* Enter an SQL list command */
337 if (!get_cmd(ua, _("Enter SQL list command: "))) {
340 db_list_sql_query(ua->db, ua->cmd, prtit, ua, 1);
343 case 4: /* Select the most recent backups */
344 query = get_pool_memory(PM_MESSAGE);
345 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
346 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
347 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
348 bsendmsg(ua, "%s\n", db_strerror(ua->db));
350 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
351 bsendmsg(ua, "%s\n", db_strerror(ua->db));
356 if (!(ji->client = get_client_resource(ua))) {
363 Mmsg(&query, uar_sel_fileset, ji->client->hdr.name);
364 start_prompt(ua, _("The defined FileSet resources are:\n"));
365 if (!db_sql_query(ua->db, query, fileset_handler, (void *)ua)) {
366 bsendmsg(ua, "%s\n", db_strerror(ua->db));
368 if (do_prompt(ua, _("Select FileSet resource"),
369 fileset_name, sizeof(fileset_name)) < 0) {
370 free_pool_memory(query);
373 fsr.FileSetId = atoi(fileset_name); /* Id is first part of name */
374 if (!db_get_fileset_record(ua->db, &fsr)) {
375 bsendmsg(ua, "Error getting FileSet record: %s\n", db_strerror(ua->db));
376 bsendmsg(ua, _("This probably means you modified the FileSet.\n"
377 "Continuing anyway.\n"));
380 /* Find JobId of last Full backup for this client, fileset */
381 Mmsg(&query, uar_last_full, ji->client->hdr.name, fsr.FileSetId);
382 if (!db_sql_query(ua->db, query, NULL, NULL)) {
383 bsendmsg(ua, "%s\n", db_strerror(ua->db));
385 /* Find all Volumes used by that JobId */
386 if (!db_sql_query(ua->db, uar_full, NULL,NULL)) {
387 bsendmsg(ua, "%s\n", db_strerror(ua->db));
389 /* Note, this is needed as I don't seem to get the callback
390 * from the call just above.
392 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)ji)) {
393 bsendmsg(ua, "%s\n", db_strerror(ua->db));
395 /* Now find all Incremental Jobs */
396 Mmsg(&query, uar_inc, (uint32_t)ji->JobTDate, ji->ClientId, fsr.FileSetId);
397 if (!db_sql_query(ua->db, query, NULL, NULL)) {
398 bsendmsg(ua, "%s\n", db_strerror(ua->db));
400 free_pool_memory(query);
401 db_list_sql_query(ua->db, uar_list_temp, prtit, ua, 1);
403 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)ji)) {
404 bsendmsg(ua, "%s\n", db_strerror(ua->db));
406 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
407 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
414 if (*ji->JobIds == 0) {
415 bsendmsg(ua, _("No Jobs selected.\n"));
418 bsendmsg(ua, _("You have selected the following JobId: %s\n"), ji->JobIds);
420 memset(&jr, 0, sizeof(JOB_DBR));
423 for (p=ji->JobIds; ; ) {
424 int stat = next_jobid_from_list(&p, &JobId);
426 bsendmsg(ua, _("Invalid JobId in list.\n"));
433 if (!db_get_job_record(ua->db, &jr)) {
434 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
437 ji->TotalFiles += jr.JobFiles;
442 static int next_jobid_from_list(char **p, uint32_t *JobId)
449 for (i=0; i<(int)sizeof(jobid); i++) {
450 if (*q == ',' || *q == 0) {
457 if (jobid[0] == 0 || !is_a_number(jobid)) {
461 *JobId = strtoul(jobid, NULL, 10);
466 * Callback handler make list of JobIds
468 static int jobid_handler(void *ctx, int num_fields, char **row)
470 JobIds *ji = (JobIds *)ctx;
472 if (strlen(ji->JobIds)+strlen(row[0])+2 < sizeof(ji->JobIds)) {
473 if (ji->JobIds[0] != 0) {
474 strcat(ji->JobIds, ",");
476 strcat(ji->JobIds, row[0]);
484 * Callback handler to pickup last Full backup JobId and ClientId
486 static int last_full_handler(void *ctx, int num_fields, char **row)
488 JobIds *ji = (JobIds *)ctx;
490 ji->JobTDate = atoi(row[1]);
491 ji->ClientId = atoi(row[2]);
497 * Callback handler build fileset prompt list
499 static int fileset_handler(void *ctx, int num_fields, char **row)
501 char prompt[MAX_NAME_LENGTH+200];
503 snprintf(prompt, sizeof(prompt), "%s %s %s", row[0], row[1], row[2]);
504 add_prompt((UAContext *)ctx, prompt);
508 /* Forward referenced commands */
510 static int markcmd(UAContext *ua, TREE_CTX *tree);
511 static int countcmd(UAContext *ua, TREE_CTX *tree);
512 static int findcmd(UAContext *ua, TREE_CTX *tree);
513 static int lscmd(UAContext *ua, TREE_CTX *tree);
514 static int dircmd(UAContext *ua, TREE_CTX *tree);
515 static int helpcmd(UAContext *ua, TREE_CTX *tree);
516 static int cdcmd(UAContext *ua, TREE_CTX *tree);
517 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
518 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
519 static int quitcmd(UAContext *ua, TREE_CTX *tree);
522 struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; };
523 static struct cmdstruct commands[] = {
524 { N_("mark"), markcmd, _("mark file for restoration")},
525 { N_("unmark"), unmarkcmd, _("unmark file for restoration")},
526 { N_("cd"), cdcmd, _("change current directory")},
527 { N_("pwd"), pwdcmd, _("print current working directory")},
528 { N_("ls"), lscmd, _("list current directory")},
529 { N_("dir"), dircmd, _("list current directory")},
530 { N_("count"), countcmd, _("count marked files")},
531 { N_("find"), findcmd, _("find files")},
532 { N_("done"), quitcmd, _("leave file selection mode")},
533 { N_("exit"), quitcmd, _("exit = done")},
534 { N_("help"), helpcmd, _("print help")},
535 { N_("?"), helpcmd, _("print help")},
537 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
541 * Enter a prompt mode where the user can select/deselect
542 * files to be restored. This is sort of like a mini-shell
543 * that allows "cd", "pwd", "add", "rm", ...
545 static void user_select_files(TREE_CTX *tree)
549 bsendmsg(tree->ua, _(
550 "\nYou are now entering file selection mode where you add and\n"
551 "remove files to be restored. All files are initially added.\n"
552 "Enter \"done\" to leave this mode.\n\n"));
554 * Enter interactive command handler allowing selection
555 * of individual files.
557 tree->node = (TREE_NODE *)tree->root;
558 tree_getpath(tree->node, cwd, sizeof(cwd));
559 bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
561 int found, len, stat, i;
562 if (!get_cmd(tree->ua, "$ ")) {
565 parse_command_args(tree->ua);
566 if (tree->ua->argc == 0) {
570 len = strlen(tree->ua->argk[0]);
573 for (i=0; i<(int)comsize; i++) /* search for command */
574 if (strncasecmp(tree->ua->argk[0], _(commands[i].key), len) == 0) {
575 stat = (*commands[i].func)(tree->ua, tree); /* go execute command */
580 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
590 * Create new FileIndex entry for BSR
592 static RBSR_FINDEX *new_findex()
594 RBSR_FINDEX *fi = (RBSR_FINDEX *)bmalloc(sizeof(RBSR_FINDEX));
595 memset(fi, 0, sizeof(RBSR_FINDEX));
599 /* Free all BSR FileIndex entries */
600 static void free_findex(RBSR_FINDEX *fi)
603 free_findex(fi->next);
608 static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd)
611 if (fi->findex == fi->findex2) {
612 fprintf(fd, "FileIndex=%d\n", fi->findex);
614 fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
616 write_findex(ua, fi->next, fd);
621 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
624 if (fi->findex == fi->findex2) {
625 bsendmsg(ua, "FileIndex=%d\n", fi->findex);
627 bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
629 print_findex(ua, fi->next);
633 /* Create a new bootstrap record */
634 static RBSR *new_bsr()
636 RBSR *bsr = (RBSR *)bmalloc(sizeof(RBSR));
637 memset(bsr, 0, sizeof(RBSR));
641 /* Free the entire BSR */
642 static void free_bsr(RBSR *bsr)
645 free_findex(bsr->fi);
647 if (bsr->VolumeName) {
648 free(bsr->VolumeName);
655 * Complete the BSR by filling in the VolumeName and
656 * VolSessionId and VolSessionTime using the JobId
658 static int complete_bsr(UAContext *ua, RBSR *bsr)
661 VOL_PARAMS *VolParams;
665 memset(&jr, 0, sizeof(jr));
666 jr.JobId = bsr->JobId;
667 if (!db_get_job_record(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 ((count=db_get_job_volume_parameters(ua->db, bsr->JobId, &VolParams)) == 0) {
674 bsendmsg(ua, _("Unable to get Job Volume Parameters. ERR=%s\n"), db_strerror(ua->db));
675 free((char *)VolParams);
678 bsr->VolumeName = bstrdup(VolParams[0].VolumeName);
679 bsr->StartFile = VolParams[0].StartFile;
680 bsr->EndFile = VolParams[0].EndFile;
681 bsr->StartBlock = VolParams[0].StartBlock;
682 bsr->EndBlock = VolParams[0].EndBlock;
683 free((char *)VolParams);
684 return complete_bsr(ua, bsr->next);
690 * Write the bootstrap record to file
692 static int write_bsr_file(UAContext *ua, RBSR *bsr)
695 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
699 Mmsg(&fname, "%s/restore.bsr", working_directory);
700 fd = fopen(fname, "w+");
702 bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"),
703 fname, strerror(errno));
704 free_pool_memory(fname);
707 write_bsr(ua, bsr, fd);
710 bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
711 bsendmsg(ua, _("\nThe restore job will require the following Volumes:\n"));
712 for (nbsr=bsr; nbsr; nbsr=nbsr->next) {
713 if (nbsr->VolumeName) {
714 bsendmsg(ua, " %s\n", nbsr->VolumeName);
718 free_pool_memory(fname);
722 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
725 if (bsr->VolumeName) {
726 fprintf(fd, "Volume=\"%s\"\n", bsr->VolumeName);
728 fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
729 fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
730 fprintf(fd, "VolFile=%u-%u\n", bsr->StartFile, bsr->EndFile);
731 write_findex(ua, bsr->fi, fd);
732 write_bsr(ua, bsr->next, fd);
736 static void print_bsr(UAContext *ua, RBSR *bsr)
739 if (bsr->VolumeName) {
740 bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolumeName);
742 bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
743 bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
744 print_findex(ua, bsr->fi);
745 print_bsr(ua, bsr->next);
751 * Add a FileIndex to the list of BootStrap records.
752 * Here we are only dealing with JobId's and the FileIndexes
753 * associated with those JobIds.
755 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
758 RBSR_FINDEX *fi, *lfi;
761 return; /* probably a dummy directory */
764 if (!bsr->fi) { /* if no FI add one */
765 /* This is the first FileIndex item in the chain */
766 bsr->fi = new_findex();
768 bsr->fi->findex = findex;
769 bsr->fi->findex2 = findex;
772 /* Walk down list of bsrs until we find the JobId */
773 if (bsr->JobId != JobId) {
774 for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
775 if (nbsr->JobId == JobId) {
781 if (!nbsr) { /* Must add new JobId */
782 /* Add new JobId at end of chain */
783 for (nbsr=bsr; nbsr->next; nbsr=nbsr->next)
785 nbsr->next = new_bsr();
786 nbsr->next->JobId = JobId;
787 nbsr->next->fi = new_findex();
788 nbsr->next->fi->findex = findex;
789 nbsr->next->fi->findex2 = findex;
795 * At this point, bsr points to bsr containing JobId,
796 * and we are sure that there is at least one fi record.
799 /* Check if this findex is smaller than first item */
800 if (findex < fi->findex) {
801 if ((findex+1) == fi->findex) {
802 fi->findex = findex; /* extend down */
805 fi = new_findex(); /* yes, insert before first item */
807 fi->findex2 = findex;
812 /* Walk down fi chain and find where to insert insert new FileIndex */
813 for ( ; fi; fi=fi->next) {
814 if (findex == (fi->findex2 + 1)) { /* extend up */
816 fi->findex2 = findex;
817 if (fi->next && ((findex+1) == fi->next->findex)) {
819 fi->findex2 = nfi->findex2;
820 fi->next = nfi->next;
825 if (findex < fi->findex) { /* add before */
826 if ((findex+1) == fi->findex) {
834 /* Add to last place found */
837 fi->findex2 = findex;
838 fi->next = lfi->next;
844 * This callback routine is responsible for inserting the
845 * items it gets into the directory tree. For each JobId selected
846 * this routine is called once for each file. We do not allow
847 * duplicate filenames, but instead keep the info from the most
848 * recent file entered (i.e. the JobIds are assumed to be sorted)
850 static int insert_tree_handler(void *ctx, int num_fields, char **row)
852 TREE_CTX *tree = (TREE_CTX *)ctx;
854 TREE_NODE *node, *new_node;
857 strip_trailing_junk(row[1]);
859 if (*row[0] != '/') { /* Must be Win32 directory */
867 sprintf(fname, "%s%s", row[0], row[1]);
868 if (tree->avail_node) {
869 node = tree->avail_node;
871 node = new_tree_node(tree->root, type);
872 tree->avail_node = node;
874 Dmsg3(200, "FI=%d type=%d fname=%s\n", node->FileIndex, type, fname);
875 new_node = insert_tree_node(fname, node, tree->root, NULL);
876 /* Note, if node already exists, save new one for next time */
877 if (new_node != node) {
878 tree->avail_node = node;
880 tree->avail_node = NULL;
882 new_node->FileIndex = atoi(row[2]);
883 new_node->JobId = atoi(row[3]);
884 new_node->type = type;
885 new_node->extract = 1; /* extract all by default */
892 * Set extract to value passed. We recursively walk
893 * down the tree setting all children if the
894 * node is a directory.
896 static void set_extract(TREE_NODE *node, int value)
900 node->extract = value;
901 if (node->type != TN_FILE) {
902 for (n=node->child; n; n=n->sibling) {
903 set_extract(n, value);
908 static int markcmd(UAContext *ua, TREE_CTX *tree)
914 if (!tree->node->child) {
917 for (node = tree->node->child; node; node=node->sibling) {
918 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
919 set_extract(node, 1);
925 static int countcmd(UAContext *ua, TREE_CTX *tree)
930 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
931 if (node->type != TN_NEWDIR) {
938 bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
942 static int findcmd(UAContext *ua, TREE_CTX *tree)
947 bsendmsg(ua, _("No file specification given.\n"));
951 for (int i=1; i < ua->argc; i++) {
952 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
953 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
954 tree_getpath(node, cwd, sizeof(cwd));
955 bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
964 static int lscmd(UAContext *ua, TREE_CTX *tree)
968 if (!tree->node->child) {
971 for (node = tree->node->child; node; node=node->sibling) {
972 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
973 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
974 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
980 extern char *getuser(uid_t uid);
981 extern char *getgroup(gid_t gid);
983 static void ls_output(char *buf, char *fname, struct stat *statp)
989 // Dmsg2(000, "%s mode=0%o\n", fname, statp->st_mode);
991 p = encode_mode(statp->st_mode, buf);
992 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
994 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
996 n = sprintf(p, "%8.8s ", edit_uint64(statp->st_size, ec1));
998 p = encode_time(statp->st_ctime, p);
1008 * Like ls command, but give more detail on each file
1010 static int dircmd(UAContext *ua, TREE_CTX *tree)
1018 if (!tree->node->child) {
1021 for (node = tree->node->child; node; node=node->sibling) {
1022 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1023 tree_getpath(node, cwd, sizeof(cwd));
1025 fdbr.JobId = node->JobId;
1026 if (db_get_file_attributes_record(ua->db, cwd, &fdbr)) {
1027 decode_stat(fdbr.LStat, &statp); /* decode stat pkt */
1028 ls_output(buf, cwd, &statp);
1029 bsendmsg(ua, "%s\n", buf);
1031 /* Something went wrong getting attributes -- print name */
1032 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1033 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1041 static int helpcmd(UAContext *ua, TREE_CTX *tree)
1046 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
1047 for (i=0; i<comsize; i++) {
1048 bsendmsg(ua, _(" %-10s %s\n"), _(commands[i].key), _(commands[i].help));
1055 * Change directories. Note, if the user specifies x: and it fails,
1056 * we assume it is a Win32 absolute cd rather than relative and
1057 * try a second time with /x: ... Win32 kludge.
1059 static int cdcmd(UAContext *ua, TREE_CTX *tree)
1064 if (ua->argc != 2) {
1067 node = tree_cwd(ua->argk[1], tree->root, tree->node);
1069 /* Try once more if Win32 drive -- make absolute */
1070 if (ua->argk[1][1] == ':') { /* win32 drive */
1072 strcat(cwd, ua->argk[1]);
1073 node = tree_cwd(cwd, tree->root, tree->node);
1076 bsendmsg(ua, _("Invalid path given.\n"));
1083 tree_getpath(tree->node, cwd, sizeof(cwd));
1084 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1088 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
1091 tree_getpath(tree->node, cwd, sizeof(cwd));
1092 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1097 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
1103 if (!tree->node->child) {
1106 for (node = tree->node->child; node; node=node->sibling) {
1107 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1108 set_extract(node, 0);
1114 static int quitcmd(UAContext *ua, TREE_CTX *tree)
1121 * Called here with each name to be added to the list. The name is
1122 * added to the list if it is not already in the list.
1124 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1126 NAME_LIST *name = (NAME_LIST *)ctx;
1128 if (name->num_ids == MAX_ID_LIST_LEN) {
1131 if (name->num_ids == name->max_ids) {
1132 if (name->max_ids == 0) {
1133 name->max_ids = 1000;
1134 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1136 name->max_ids = (name->max_ids * 3) / 2;
1137 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1140 for (int i=0; i<name->num_ids; i++) {
1141 if (strcmp(name->name[i], row[0]) == 0) {
1142 return 0; /* already in list, return */
1145 /* Add new name to list */
1146 name->name[name->num_ids++] = bstrdup(row[0]);
1152 * Print names in the list
1154 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1158 for (i=0; i < name_list->num_ids; i++) {
1159 bsendmsg(ua, "%s\n", name_list->name[i]);
1165 * Free names in the list
1167 static void free_name_list(NAME_LIST *name_list)
1171 for (i=0; i < name_list->num_ids; i++) {
1172 free(name_list->name[i]);
1174 if (name_list->name) {
1175 free(name_list->name);
1177 name_list->max_ids = 0;
1178 name_list->num_ids = 0;
1181 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JobIds *ji)
1183 char name[MAX_NAME_LENGTH];
1184 STORE *store = NULL;
1186 if (name_list->num_ids > 1) {
1187 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1188 "Restore is not possible. The MediaTypes used are:\n"));
1189 print_name_list(ua, name_list);
1190 ji->store = select_storage_resource(ua);
1194 if (name_list->num_ids == 0) {
1195 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1196 ji->store = select_storage_resource(ua);
1200 start_prompt(ua, _("The defined Storage resources are:\n"));
1202 while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
1203 if (strcmp(store->media_type, name_list->name[0]) == 0) {
1204 add_prompt(ua, store->hdr.name);
1208 do_prompt(ua, _("Select Storage resource"), name, sizeof(name));
1209 ji->store = (STORE *)GetResWithName(R_STORAGE, name);
1211 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1212 "MediaType %s, needed by the Jobs you selected.\n"
1213 "You will be allowed to select a Storage device later.\n"),
1214 name_list->name[0]);