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;
83 char *VolumeName; /* Volume name */
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"));
256 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
257 job->hdr.name, ji.client->hdr.name, 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];
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)) {
312 case 0: /* list last 20 Jobs run */
313 db_list_sql_query(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->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->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));
352 if (!(ji->client = get_client_resource(ua))) {
359 Mmsg(&query, uar_sel_fileset, ji->client->hdr.name);
360 start_prompt(ua, _("The defined FileSet resources are:\n"));
361 if (!db_sql_query(ua->db, query, fileset_handler, (void *)ua)) {
362 bsendmsg(ua, "%s\n", db_strerror(ua->db));
364 if (do_prompt(ua, _("Select FileSet resource"), fileset_name) < 0) {
365 free_pool_memory(query);
368 fsr.FileSetId = atoi(fileset_name); /* Id is first part of name */
369 if (!db_get_fileset_record(ua->db, &fsr)) {
370 bsendmsg(ua, "Error getting FileSet record: %s\n", db_strerror(ua->db));
371 bsendmsg(ua, _("This probably means you modified the FileSet.\n"
372 "Continuing anyway.\n"));
375 /* Find JobId of last Full backup for this client, fileset */
376 Mmsg(&query, uar_last_full, ji->client->hdr.name, fsr.FileSetId);
377 if (!db_sql_query(ua->db, query, NULL, NULL)) {
378 bsendmsg(ua, "%s\n", db_strerror(ua->db));
380 /* Find all Volumes used by that JobId */
381 if (!db_sql_query(ua->db, uar_full, NULL,NULL)) {
382 bsendmsg(ua, "%s\n", db_strerror(ua->db));
384 /* Note, this is needed as I don't seem to get the callback
385 * from the call just above.
387 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)ji)) {
388 bsendmsg(ua, "%s\n", db_strerror(ua->db));
390 /* Now find all Incremental Jobs */
391 Mmsg(&query, uar_inc, (uint32_t)ji->JobTDate, ji->ClientId, fsr.FileSetId);
392 if (!db_sql_query(ua->db, query, NULL, NULL)) {
393 bsendmsg(ua, "%s\n", db_strerror(ua->db));
395 free_pool_memory(query);
396 db_list_sql_query(ua->db, uar_list_temp, prtit, ua, 1);
398 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)ji)) {
399 bsendmsg(ua, "%s\n", db_strerror(ua->db));
401 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
402 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
409 if (*ji->JobIds == 0) {
410 bsendmsg(ua, _("No Jobs selected.\n"));
413 bsendmsg(ua, _("You have selected the following JobId: %s\n"), ji->JobIds);
415 memset(&jr, 0, sizeof(JOB_DBR));
418 for (p=ji->JobIds; ; ) {
419 int stat = next_jobid_from_list(&p, &JobId);
421 bsendmsg(ua, _("Invalid JobId in list.\n"));
428 if (!db_get_job_record(ua->db, &jr)) {
429 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
432 ji->TotalFiles += jr.JobFiles;
437 static int next_jobid_from_list(char **p, uint32_t *JobId)
444 for (i=0; i<(int)sizeof(jobid); i++) {
445 if (*q == ',' || *q == 0) {
452 if (jobid[0] == 0 || !is_a_number(jobid)) {
456 *JobId = strtoul(jobid, NULL, 10);
461 * Callback handler make list of JobIds
463 static int jobid_handler(void *ctx, int num_fields, char **row)
465 JobIds *ji = (JobIds *)ctx;
467 if (strlen(ji->JobIds)+strlen(row[0])+2 < sizeof(ji->JobIds)) {
468 if (ji->JobIds[0] != 0) {
469 strcat(ji->JobIds, ",");
471 strcat(ji->JobIds, row[0]);
479 * Callback handler to pickup last Full backup JobId and ClientId
481 static int last_full_handler(void *ctx, int num_fields, char **row)
483 JobIds *ji = (JobIds *)ctx;
485 ji->JobTDate = atoi(row[1]);
486 ji->ClientId = atoi(row[2]);
492 * Callback handler build fileset prompt list
494 static int fileset_handler(void *ctx, int num_fields, char **row)
496 char prompt[MAX_NAME_LENGTH+200];
498 snprintf(prompt, sizeof(prompt), "%s %s %s", row[0], row[1], row[2]);
499 add_prompt((UAContext *)ctx, prompt);
503 /* Forward referenced commands */
505 static int markcmd(UAContext *ua, TREE_CTX *tree);
506 static int countcmd(UAContext *ua, TREE_CTX *tree);
507 static int findcmd(UAContext *ua, TREE_CTX *tree);
508 static int lscmd(UAContext *ua, TREE_CTX *tree);
509 static int dircmd(UAContext *ua, TREE_CTX *tree);
510 static int helpcmd(UAContext *ua, TREE_CTX *tree);
511 static int cdcmd(UAContext *ua, TREE_CTX *tree);
512 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
513 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
514 static int quitcmd(UAContext *ua, TREE_CTX *tree);
517 struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; };
518 static struct cmdstruct commands[] = {
519 { N_("mark"), markcmd, _("mark file for restoration")},
520 { N_("unmark"), unmarkcmd, _("unmark file for restoration")},
521 { N_("cd"), cdcmd, _("change current directory")},
522 { N_("pwd"), pwdcmd, _("print current working directory")},
523 { N_("ls"), lscmd, _("list current directory")},
524 { N_("dir"), dircmd, _("list current directory")},
525 { N_("count"), countcmd, _("count marked files")},
526 { N_("find"), findcmd, _("find files")},
527 { N_("done"), quitcmd, _("leave file selection mode")},
528 { N_("exit"), quitcmd, _("exit = done")},
529 { N_("help"), helpcmd, _("print help")},
530 { N_("?"), helpcmd, _("print help")},
532 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
536 * Enter a prompt mode where the user can select/deselect
537 * files to be restored. This is sort of like a mini-shell
538 * that allows "cd", "pwd", "add", "rm", ...
540 static void user_select_files(TREE_CTX *tree)
544 bsendmsg(tree->ua, _(
545 "\nYou are now entering file selection mode where you add and\n"
546 "remove files to be restored. All files are initially added.\n"
547 "Enter \"done\" to leave this mode.\n\n"));
549 * Enter interactive command handler allowing selection
550 * of individual files.
552 tree->node = (TREE_NODE *)tree->root;
553 tree_getpath(tree->node, cwd, sizeof(cwd));
554 bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
556 int found, len, stat, i;
557 if (!get_cmd(tree->ua, "$ ")) {
560 parse_command_args(tree->ua);
561 if (tree->ua->argc == 0) {
565 len = strlen(tree->ua->argk[0]);
568 for (i=0; i<(int)comsize; i++) /* search for command */
569 if (strncasecmp(tree->ua->argk[0], _(commands[i].key), len) == 0) {
570 stat = (*commands[i].func)(tree->ua, tree); /* go execute command */
575 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
585 * Create new FileIndex entry for BSR
587 static RBSR_FINDEX *new_findex()
589 RBSR_FINDEX *fi = (RBSR_FINDEX *)bmalloc(sizeof(RBSR_FINDEX));
590 memset(fi, 0, sizeof(RBSR_FINDEX));
594 /* Free all BSR FileIndex entries */
595 static void free_findex(RBSR_FINDEX *fi)
598 free_findex(fi->next);
603 static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd)
606 if (fi->findex == fi->findex2) {
607 fprintf(fd, "FileIndex=%d\n", fi->findex);
609 fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
611 write_findex(ua, fi->next, fd);
616 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
619 if (fi->findex == fi->findex2) {
620 bsendmsg(ua, "FileIndex=%d\n", fi->findex);
622 bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
624 print_findex(ua, fi->next);
628 /* Create a new bootstrap record */
629 static RBSR *new_bsr()
631 RBSR *bsr = (RBSR *)bmalloc(sizeof(RBSR));
632 memset(bsr, 0, sizeof(RBSR));
636 /* Free the entire BSR */
637 static void free_bsr(RBSR *bsr)
640 free_findex(bsr->fi);
642 if (bsr->VolumeName) {
643 free(bsr->VolumeName);
650 * Complete the BSR by filling in the VolumeName and
651 * VolSessionId and VolSessionTime using the JobId
653 static int complete_bsr(UAContext *ua, RBSR *bsr)
656 POOLMEM *VolumeNames;
659 VolumeNames = get_pool_memory(PM_MESSAGE);
660 memset(&jr, 0, sizeof(jr));
661 jr.JobId = bsr->JobId;
662 if (!db_get_job_record(ua->db, &jr)) {
663 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
666 bsr->VolSessionId = jr.VolSessionId;
667 bsr->VolSessionTime = jr.VolSessionTime;
668 if (!db_get_job_volume_names(ua->db, bsr->JobId, &VolumeNames)) {
669 bsendmsg(ua, _("Unable to get Job Volumes. ERR=%s\n"), db_strerror(ua->db));
670 free_pool_memory(VolumeNames);
673 bsr->VolumeName = bstrdup(VolumeNames);
674 free_pool_memory(VolumeNames);
675 return complete_bsr(ua, bsr->next);
681 * Write the bootstrap record to file
683 static int write_bsr_file(UAContext *ua, RBSR *bsr)
686 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
690 Mmsg(&fname, "%s/restore.bsr", working_directory);
691 fd = fopen(fname, "w+");
693 bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"),
694 fname, strerror(errno));
695 free_pool_memory(fname);
698 write_bsr(ua, bsr, fd);
701 bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
702 bsendmsg(ua, _("\nThe restore job will require the following Volumes:\n"));
703 for (nbsr=bsr; nbsr; nbsr=nbsr->next) {
704 if (nbsr->VolumeName) {
705 bsendmsg(ua, " %s\n", nbsr->VolumeName);
709 free_pool_memory(fname);
713 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
716 if (bsr->VolumeName) {
717 fprintf(fd, "Volume=\"%s\"\n", bsr->VolumeName);
719 fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
720 fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
721 write_findex(ua, bsr->fi, fd);
722 write_bsr(ua, bsr->next, fd);
726 static void print_bsr(UAContext *ua, RBSR *bsr)
729 if (bsr->VolumeName) {
730 bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolumeName);
732 bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
733 bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
734 print_findex(ua, bsr->fi);
735 print_bsr(ua, bsr->next);
741 * Add a FileIndex to the list of BootStrap records.
742 * Here we are only dealing with JobId's and the FileIndexes
743 * associated with those JobIds.
745 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
748 RBSR_FINDEX *fi, *lfi;
751 return; /* probably a dummy directory */
754 if (!bsr->fi) { /* if no FI add one */
755 /* This is the first FileIndex item in the chain */
756 bsr->fi = new_findex();
758 bsr->fi->findex = findex;
759 bsr->fi->findex2 = findex;
762 /* Walk down list of bsrs until we find the JobId */
763 if (bsr->JobId != JobId) {
764 for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
765 if (nbsr->JobId == JobId) {
771 if (!nbsr) { /* Must add new JobId */
772 /* Add new JobId at end of chain */
773 for (nbsr=bsr; nbsr->next; nbsr=nbsr->next)
775 nbsr->next = new_bsr();
776 nbsr->next->JobId = JobId;
777 nbsr->next->fi = new_findex();
778 nbsr->next->fi->findex = findex;
779 nbsr->next->fi->findex2 = findex;
785 * At this point, bsr points to bsr containing JobId,
786 * and we are sure that there is at least one fi record.
789 /* Check if this findex is smaller than first item */
790 if (findex < fi->findex) {
791 if ((findex+1) == fi->findex) {
792 fi->findex = findex; /* extend down */
795 fi = new_findex(); /* yes, insert before first item */
797 fi->findex2 = findex;
802 /* Walk down fi chain and find where to insert insert new FileIndex */
803 for ( ; fi; fi=fi->next) {
804 if (findex == (fi->findex2 + 1)) { /* extend up */
806 fi->findex2 = findex;
807 if (fi->next && ((findex+1) == fi->next->findex)) {
809 fi->findex2 = nfi->findex2;
810 fi->next = nfi->next;
815 if (findex < fi->findex) { /* add before */
816 if ((findex+1) == fi->findex) {
824 /* Add to last place found */
827 fi->findex2 = findex;
828 fi->next = lfi->next;
834 * This callback routine is responsible for inserting the
835 * items it gets into the directory tree. For each JobId selected
836 * this routine is called once for each file. We do not allow
837 * duplicate filenames, but instead keep the info from the most
838 * recent file entered (i.e. the JobIds are assumed to be sorted)
840 static int insert_tree_handler(void *ctx, int num_fields, char **row)
842 TREE_CTX *tree = (TREE_CTX *)ctx;
844 TREE_NODE *node, *new_node;
847 strip_trailing_junk(row[1]);
849 if (*row[0] != '/') { /* Must be Win32 directory */
857 sprintf(fname, "%s%s", row[0], row[1]);
858 if (tree->avail_node) {
859 node = tree->avail_node;
861 node = new_tree_node(tree->root, type);
862 tree->avail_node = node;
864 Dmsg3(200, "FI=%d type=%d fname=%s\n", node->FileIndex, type, fname);
865 new_node = insert_tree_node(fname, node, tree->root, NULL);
866 /* Note, if node already exists, save new one for next time */
867 if (new_node != node) {
868 tree->avail_node = node;
870 tree->avail_node = NULL;
872 new_node->FileIndex = atoi(row[2]);
873 new_node->JobId = atoi(row[3]);
874 new_node->type = type;
875 new_node->extract = 1; /* extract all by default */
882 * Set extract to value passed. We recursively walk
883 * down the tree setting all children if the
884 * node is a directory.
886 static void set_extract(TREE_NODE *node, int value)
890 node->extract = value;
891 if (node->type != TN_FILE) {
892 for (n=node->child; n; n=n->sibling) {
893 set_extract(n, value);
898 static int markcmd(UAContext *ua, TREE_CTX *tree)
904 if (!tree->node->child) {
907 for (node = tree->node->child; node; node=node->sibling) {
908 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
909 set_extract(node, 1);
915 static int countcmd(UAContext *ua, TREE_CTX *tree)
920 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
921 if (node->type != TN_NEWDIR) {
928 bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
932 static int findcmd(UAContext *ua, TREE_CTX *tree)
937 bsendmsg(ua, _("No file specification given.\n"));
941 for (int i=1; i < ua->argc; i++) {
942 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
943 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
944 tree_getpath(node, cwd, sizeof(cwd));
945 bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
954 static int lscmd(UAContext *ua, TREE_CTX *tree)
958 if (!tree->node->child) {
961 for (node = tree->node->child; node; node=node->sibling) {
962 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
963 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
964 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
970 extern char *getuser(uid_t uid);
971 extern char *getgroup(gid_t gid);
973 static void ls_output(char *buf, char *fname, struct stat *statp)
979 // Dmsg2(000, "%s mode=0%o\n", fname, statp->st_mode);
981 p = encode_mode(statp->st_mode, buf);
982 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
984 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
986 n = sprintf(p, "%8.8s ", edit_uint64(statp->st_size, ec1));
988 p = encode_time(statp->st_ctime, p);
998 * Like ls command, but give more detail on each file
1000 static int dircmd(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 tree_getpath(node, cwd, sizeof(cwd));
1015 fdbr.JobId = node->JobId;
1016 if (db_get_file_attributes_record(ua->db, cwd, &fdbr)) {
1017 decode_stat(fdbr.LStat, &statp); /* decode stat pkt */
1018 ls_output(buf, cwd, &statp);
1019 bsendmsg(ua, "%s\n", buf);
1021 /* Something went wrong getting attributes -- print name */
1022 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1023 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1031 static int helpcmd(UAContext *ua, TREE_CTX *tree)
1036 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
1037 for (i=0; i<comsize; i++) {
1038 bsendmsg(ua, _(" %-10s %s\n"), _(commands[i].key), _(commands[i].help));
1045 * Change directories. Note, if the user specifies x: and it fails,
1046 * we assume it is a Win32 absolute cd rather than relative and
1047 * try a second time with /x: ... Win32 kludge.
1049 static int cdcmd(UAContext *ua, TREE_CTX *tree)
1054 if (ua->argc != 2) {
1057 node = tree_cwd(ua->argk[1], tree->root, tree->node);
1059 /* Try once more if Win32 drive -- make absolute */
1060 if (ua->argk[1][1] == ':') { /* win32 drive */
1062 strcat(cwd, ua->argk[1]);
1063 node = tree_cwd(cwd, tree->root, tree->node);
1066 bsendmsg(ua, _("Invalid path given.\n"));
1073 tree_getpath(tree->node, cwd, sizeof(cwd));
1074 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1078 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
1081 tree_getpath(tree->node, cwd, sizeof(cwd));
1082 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1087 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
1093 if (!tree->node->child) {
1096 for (node = tree->node->child; node; node=node->sibling) {
1097 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1098 set_extract(node, 0);
1104 static int quitcmd(UAContext *ua, TREE_CTX *tree)
1111 * Called here with each name to be added to the list. The name is
1112 * added to the list if it is not already in the list.
1114 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1116 NAME_LIST *name = (NAME_LIST *)ctx;
1118 if (name->num_ids == MAX_ID_LIST_LEN) {
1121 if (name->num_ids == name->max_ids) {
1122 if (name->max_ids == 0) {
1123 name->max_ids = 1000;
1124 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1126 name->max_ids = (name->max_ids * 3) / 2;
1127 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1130 for (int i=0; i<name->num_ids; i++) {
1131 if (strcmp(name->name[i], row[0]) == 0) {
1132 return 0; /* already in list, return */
1135 /* Add new name to list */
1136 name->name[name->num_ids++] = bstrdup(row[0]);
1142 * Print names in the list
1144 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1148 for (i=0; i < name_list->num_ids; i++) {
1149 bsendmsg(ua, "%s\n", name_list->name[i]);
1155 * Free names in the list
1157 static void free_name_list(NAME_LIST *name_list)
1161 for (i=0; i < name_list->num_ids; i++) {
1162 free(name_list->name[i]);
1164 if (name_list->name) {
1165 free(name_list->name);
1167 name_list->max_ids = 0;
1168 name_list->num_ids = 0;
1171 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JobIds *ji)
1173 char name[MAX_NAME_LENGTH];
1174 STORE *store = NULL;
1176 if (name_list->num_ids > 1) {
1177 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1178 "Restore is not possible. The MediaTypes used are:\n"));
1179 print_name_list(ua, name_list);
1180 ji->store = select_storage_resource(ua);
1184 if (name_list->num_ids == 0) {
1185 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1186 ji->store = select_storage_resource(ua);
1190 start_prompt(ua, _("The defined Storage resources are:\n"));
1192 while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
1193 if (strcmp(store->media_type, name_list->name[0]) == 0) {
1194 add_prompt(ua, store->hdr.name);
1198 do_prompt(ua, _("Select Storage resource"), name);
1199 ji->store = (STORE *)GetResWithName(R_STORAGE, name);
1201 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1202 "MediaType %s, needed by the Jobs you selected.\n"
1203 "You will be allowed to select a Storage device later.\n"),
1204 name_list->name[0]);