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);
369 strcpy(fsr.FileSet, fileset_name);
370 if (!db_get_fileset_record(ua->db, &fsr)) {
371 bsendmsg(ua, "Error getting FileSet record: %s\n", db_strerror(ua->db));
372 bsendmsg(ua, _("This probably means you modified the FileSet.\n"
373 "Continuing anyway.\n"));
376 Mmsg(&query, uar_last_full, ji->client->hdr.name, fsr.FileSetId);
377 /* Find JobId of full Backup of system */
378 if (!db_sql_query(ua->db, query, NULL, NULL)) {
379 bsendmsg(ua, "%s\n", db_strerror(ua->db));
381 /* Find all Volumes used by that JobId */
382 if (!db_sql_query(ua->db, uar_full, NULL,NULL)) {
383 bsendmsg(ua, "%s\n", db_strerror(ua->db));
385 /* Note, this is needed as I don't seem to get the callback
386 * from the call just above.
388 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)ji)) {
389 bsendmsg(ua, "%s\n", db_strerror(ua->db));
391 /* Now find all Incremental Jobs */
392 Mmsg(&query, uar_inc, (uint32_t)ji->JobTDate, ji->ClientId, fsr.FileSetId);
393 if (!db_sql_query(ua->db, query, NULL, NULL)) {
394 bsendmsg(ua, "%s\n", db_strerror(ua->db));
396 free_pool_memory(query);
397 db_list_sql_query(ua->db, uar_list_temp, prtit, ua, 1);
399 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)ji)) {
400 bsendmsg(ua, "%s\n", db_strerror(ua->db));
402 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
403 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
410 if (*ji->JobIds == 0) {
411 bsendmsg(ua, _("No Jobs selected.\n"));
414 bsendmsg(ua, _("You have selected the following JobId: %s\n"), ji->JobIds);
416 memset(&jr, 0, sizeof(JOB_DBR));
419 for (p=ji->JobIds; ; ) {
420 int stat = next_jobid_from_list(&p, &JobId);
422 bsendmsg(ua, _("Invalid JobId in list.\n"));
429 if (!db_get_job_record(ua->db, &jr)) {
430 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
433 ji->TotalFiles += jr.JobFiles;
438 static int next_jobid_from_list(char **p, uint32_t *JobId)
445 for (i=0; i<(int)sizeof(jobid); i++) {
446 if (*q == ',' || *q == 0) {
453 if (jobid[0] == 0 || !is_a_number(jobid)) {
457 *JobId = strtoul(jobid, NULL, 10);
462 * Callback handler make list of JobIds
464 static int jobid_handler(void *ctx, int num_fields, char **row)
466 JobIds *ji = (JobIds *)ctx;
468 if (strlen(ji->JobIds)+strlen(row[0])+2 < sizeof(ji->JobIds)) {
469 if (ji->JobIds[0] != 0) {
470 strcat(ji->JobIds, ",");
472 strcat(ji->JobIds, row[0]);
480 * Callback handler to pickup last Full backup JobId and ClientId
482 static int last_full_handler(void *ctx, int num_fields, char **row)
484 JobIds *ji = (JobIds *)ctx;
486 ji->JobTDate = atoi(row[1]);
487 ji->ClientId = atoi(row[2]);
493 * Callback handler build fileset prompt list
495 static int fileset_handler(void *ctx, int num_fields, char **row)
497 add_prompt((UAContext *)ctx, row[1]);
501 /* Forward referenced commands */
503 static int markcmd(UAContext *ua, TREE_CTX *tree);
504 static int countcmd(UAContext *ua, TREE_CTX *tree);
505 static int findcmd(UAContext *ua, TREE_CTX *tree);
506 static int lscmd(UAContext *ua, TREE_CTX *tree);
507 static int dircmd(UAContext *ua, TREE_CTX *tree);
508 static int helpcmd(UAContext *ua, TREE_CTX *tree);
509 static int cdcmd(UAContext *ua, TREE_CTX *tree);
510 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
511 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
512 static int quitcmd(UAContext *ua, TREE_CTX *tree);
515 struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; };
516 static struct cmdstruct commands[] = {
517 { N_("mark"), markcmd, _("mark file for restoration")},
518 { N_("unmark"), unmarkcmd, _("unmark file for restoration")},
519 { N_("cd"), cdcmd, _("change current directory")},
520 { N_("pwd"), pwdcmd, _("print current working directory")},
521 { N_("ls"), lscmd, _("list current directory")},
522 { N_("dir"), dircmd, _("list current directory")},
523 { N_("count"), countcmd, _("count marked files")},
524 { N_("find"), findcmd, _("find files")},
525 { N_("done"), quitcmd, _("leave file selection mode")},
526 { N_("exit"), quitcmd, _("exit = done")},
527 { N_("help"), helpcmd, _("print help")},
528 { N_("?"), helpcmd, _("print help")},
530 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
534 * Enter a prompt mode where the user can select/deselect
535 * files to be restored. This is sort of like a mini-shell
536 * that allows "cd", "pwd", "add", "rm", ...
538 static void user_select_files(TREE_CTX *tree)
542 bsendmsg(tree->ua, _(
543 "\nYou are now entering file selection mode where you add and\n"
544 "remove files to be restored. All files are initially added.\n"
545 "Enter \"done\" to leave this mode.\n\n"));
547 * Enter interactive command handler allowing selection
548 * of individual files.
550 tree->node = (TREE_NODE *)tree->root;
551 tree_getpath(tree->node, cwd, sizeof(cwd));
552 bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
554 int found, len, stat, i;
555 if (!get_cmd(tree->ua, "$ ")) {
558 parse_command_args(tree->ua);
559 if (tree->ua->argc == 0) {
563 len = strlen(tree->ua->argk[0]);
566 for (i=0; i<(int)comsize; i++) /* search for command */
567 if (strncasecmp(tree->ua->argk[0], _(commands[i].key), len) == 0) {
568 stat = (*commands[i].func)(tree->ua, tree); /* go execute command */
573 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
583 * Create new FileIndex entry for BSR
585 static RBSR_FINDEX *new_findex()
587 RBSR_FINDEX *fi = (RBSR_FINDEX *)bmalloc(sizeof(RBSR_FINDEX));
588 memset(fi, 0, sizeof(RBSR_FINDEX));
592 /* Free all BSR FileIndex entries */
593 static void free_findex(RBSR_FINDEX *fi)
596 free_findex(fi->next);
601 static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd)
604 if (fi->findex == fi->findex2) {
605 fprintf(fd, "FileIndex=%d\n", fi->findex);
607 fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
609 write_findex(ua, fi->next, fd);
614 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
617 if (fi->findex == fi->findex2) {
618 bsendmsg(ua, "FileIndex=%d\n", fi->findex);
620 bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
622 print_findex(ua, fi->next);
626 /* Create a new bootstrap record */
627 static RBSR *new_bsr()
629 RBSR *bsr = (RBSR *)bmalloc(sizeof(RBSR));
630 memset(bsr, 0, sizeof(RBSR));
634 /* Free the entire BSR */
635 static void free_bsr(RBSR *bsr)
638 free_findex(bsr->fi);
640 if (bsr->VolumeName) {
641 free(bsr->VolumeName);
648 * Complete the BSR by filling in the VolumeName and
649 * VolSessionId and VolSessionTime using the JobId
651 static int complete_bsr(UAContext *ua, RBSR *bsr)
654 POOLMEM *VolumeNames;
657 VolumeNames = get_pool_memory(PM_MESSAGE);
658 memset(&jr, 0, sizeof(jr));
659 jr.JobId = bsr->JobId;
660 if (!db_get_job_record(ua->db, &jr)) {
661 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
664 bsr->VolSessionId = jr.VolSessionId;
665 bsr->VolSessionTime = jr.VolSessionTime;
666 if (!db_get_job_volume_names(ua->db, bsr->JobId, &VolumeNames)) {
667 bsendmsg(ua, _("Unable to get Job Volumes. ERR=%s\n"), db_strerror(ua->db));
668 free_pool_memory(VolumeNames);
671 bsr->VolumeName = bstrdup(VolumeNames);
672 free_pool_memory(VolumeNames);
673 return complete_bsr(ua, bsr->next);
679 * Write the bootstrap record to file
681 static int write_bsr_file(UAContext *ua, RBSR *bsr)
684 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
688 Mmsg(&fname, "%s/restore.bsr", working_directory);
689 fd = fopen(fname, "w+");
691 bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"),
692 fname, strerror(errno));
693 free_pool_memory(fname);
696 write_bsr(ua, bsr, fd);
699 bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
700 bsendmsg(ua, _("\nThe restore job will require the following Volumes:\n"));
701 for (nbsr=bsr; nbsr; nbsr=nbsr->next) {
702 if (nbsr->VolumeName) {
703 bsendmsg(ua, " %s\n", nbsr->VolumeName);
707 free_pool_memory(fname);
711 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
714 if (bsr->VolumeName) {
715 fprintf(fd, "Volume=\"%s\"\n", bsr->VolumeName);
717 fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
718 fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
719 write_findex(ua, bsr->fi, fd);
720 write_bsr(ua, bsr->next, fd);
724 static void print_bsr(UAContext *ua, RBSR *bsr)
727 if (bsr->VolumeName) {
728 bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolumeName);
730 bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
731 bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
732 print_findex(ua, bsr->fi);
733 print_bsr(ua, bsr->next);
739 * Add a FileIndex to the list of BootStrap records.
740 * Here we are only dealing with JobId's and the FileIndexes
741 * associated with those JobIds.
743 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
746 RBSR_FINDEX *fi, *lfi;
749 return; /* probably a dummy directory */
752 if (!bsr->fi) { /* if no FI add one */
753 /* This is the first FileIndex item in the chain */
754 bsr->fi = new_findex();
756 bsr->fi->findex = findex;
757 bsr->fi->findex2 = findex;
760 /* Walk down list of bsrs until we find the JobId */
761 if (bsr->JobId != JobId) {
762 for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
763 if (nbsr->JobId == JobId) {
769 if (!nbsr) { /* Must add new JobId */
770 /* Add new JobId at end of chain */
771 for (nbsr=bsr; nbsr->next; nbsr=nbsr->next)
773 nbsr->next = new_bsr();
774 nbsr->next->JobId = JobId;
775 nbsr->next->fi = new_findex();
776 nbsr->next->fi->findex = findex;
777 nbsr->next->fi->findex2 = findex;
783 * At this point, bsr points to bsr containing JobId,
784 * and we are sure that there is at least one fi record.
787 /* Check if this findex is smaller than first item */
788 if (findex < fi->findex) {
789 if ((findex+1) == fi->findex) {
790 fi->findex = findex; /* extend down */
793 fi = new_findex(); /* yes, insert before first item */
795 fi->findex2 = findex;
800 /* Walk down fi chain and find where to insert insert new FileIndex */
801 for ( ; fi; fi=fi->next) {
802 if (findex == (fi->findex2 + 1)) { /* extend up */
804 fi->findex2 = findex;
805 if (fi->next && ((findex+1) == fi->next->findex)) {
807 fi->findex2 = nfi->findex2;
808 fi->next = nfi->next;
813 if (findex < fi->findex) { /* add before */
814 if ((findex+1) == fi->findex) {
822 /* Add to last place found */
825 fi->findex2 = findex;
826 fi->next = lfi->next;
832 * This callback routine is responsible for inserting the
833 * items it gets into the directory tree. For each JobId selected
834 * this routine is called once for each file. We do not allow
835 * duplicate filenames, but instead keep the info from the most
836 * recent file entered (i.e. the JobIds are assumed to be sorted)
838 static int insert_tree_handler(void *ctx, int num_fields, char **row)
840 TREE_CTX *tree = (TREE_CTX *)ctx;
842 TREE_NODE *node, *new_node;
845 strip_trailing_junk(row[1]);
847 if (*row[0] != '/') { /* Must be Win32 directory */
855 sprintf(fname, "%s%s", row[0], row[1]);
856 if (tree->avail_node) {
857 node = tree->avail_node;
859 node = new_tree_node(tree->root, type);
860 tree->avail_node = node;
862 Dmsg3(200, "FI=%d type=%d fname=%s\n", node->FileIndex, type, fname);
863 new_node = insert_tree_node(fname, node, tree->root, NULL);
864 /* Note, if node already exists, save new one for next time */
865 if (new_node != node) {
866 tree->avail_node = node;
868 tree->avail_node = NULL;
870 new_node->FileIndex = atoi(row[2]);
871 new_node->JobId = atoi(row[3]);
872 new_node->type = type;
873 new_node->extract = 1; /* extract all by default */
880 * Set extract to value passed. We recursively walk
881 * down the tree setting all children if the
882 * node is a directory.
884 static void set_extract(TREE_NODE *node, int value)
888 node->extract = value;
889 if (node->type != TN_FILE) {
890 for (n=node->child; n; n=n->sibling) {
891 set_extract(n, value);
896 static int markcmd(UAContext *ua, TREE_CTX *tree)
902 if (!tree->node->child) {
905 for (node = tree->node->child; node; node=node->sibling) {
906 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
907 set_extract(node, 1);
913 static int countcmd(UAContext *ua, TREE_CTX *tree)
918 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
919 if (node->type != TN_NEWDIR) {
926 bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
930 static int findcmd(UAContext *ua, TREE_CTX *tree)
935 bsendmsg(ua, _("No file specification given.\n"));
939 for (int i=1; i < ua->argc; i++) {
940 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
941 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
942 tree_getpath(node, cwd, sizeof(cwd));
943 bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
952 static int lscmd(UAContext *ua, TREE_CTX *tree)
956 if (!tree->node->child) {
959 for (node = tree->node->child; node; node=node->sibling) {
960 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
961 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
962 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
968 extern char *getuser(uid_t uid);
969 extern char *getgroup(gid_t gid);
971 static void ls_output(char *buf, char *fname, struct stat *statp)
977 // Dmsg2(000, "%s mode=0%o\n", fname, statp->st_mode);
979 p = encode_mode(statp->st_mode, buf);
980 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
982 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
984 n = sprintf(p, "%8.8s ", edit_uint64(statp->st_size, ec1));
986 p = encode_time(statp->st_ctime, p);
996 * Like ls command, but give more detail on each file
998 static int dircmd(UAContext *ua, TREE_CTX *tree)
1006 if (!tree->node->child) {
1009 for (node = tree->node->child; node; node=node->sibling) {
1010 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1011 tree_getpath(node, cwd, sizeof(cwd));
1013 fdbr.JobId = node->JobId;
1014 if (db_get_file_attributes_record(ua->db, cwd, &fdbr)) {
1015 decode_stat(fdbr.LStat, &statp); /* decode stat pkt */
1016 ls_output(buf, cwd, &statp);
1017 bsendmsg(ua, "%s\n", buf);
1019 /* Something went wrong getting attributes -- print name */
1020 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1021 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1029 static int helpcmd(UAContext *ua, TREE_CTX *tree)
1034 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
1035 for (i=0; i<comsize; i++) {
1036 bsendmsg(ua, _(" %-10s %s\n"), _(commands[i].key), _(commands[i].help));
1043 * Change directories. Note, if the user specifies x: and it fails,
1044 * we assume it is a Win32 absolute cd rather than relative and
1045 * try a second time with /x: ... Win32 kludge.
1047 static int cdcmd(UAContext *ua, TREE_CTX *tree)
1052 if (ua->argc != 2) {
1055 node = tree_cwd(ua->argk[1], tree->root, tree->node);
1057 /* Try once more if Win32 drive -- make absolute */
1058 if (ua->argk[1][1] == ':') { /* win32 drive */
1060 strcat(cwd, ua->argk[1]);
1061 node = tree_cwd(cwd, tree->root, tree->node);
1064 bsendmsg(ua, _("Invalid path given.\n"));
1071 tree_getpath(tree->node, cwd, sizeof(cwd));
1072 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1076 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
1079 tree_getpath(tree->node, cwd, sizeof(cwd));
1080 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1085 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
1091 if (!tree->node->child) {
1094 for (node = tree->node->child; node; node=node->sibling) {
1095 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1096 set_extract(node, 0);
1102 static int quitcmd(UAContext *ua, TREE_CTX *tree)
1109 * Called here with each name to be added to the list. The name is
1110 * added to the list if it is not already in the list.
1112 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1114 NAME_LIST *name = (NAME_LIST *)ctx;
1116 if (name->num_ids == MAX_ID_LIST_LEN) {
1119 if (name->num_ids == name->max_ids) {
1120 if (name->max_ids == 0) {
1121 name->max_ids = 1000;
1122 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1124 name->max_ids = (name->max_ids * 3) / 2;
1125 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1128 for (int i=0; i<name->num_ids; i++) {
1129 if (strcmp(name->name[i], row[0]) == 0) {
1130 return 0; /* already in list, return */
1133 /* Add new name to list */
1134 name->name[name->num_ids++] = bstrdup(row[0]);
1140 * Print names in the list
1142 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1146 for (i=0; i < name_list->num_ids; i++) {
1147 bsendmsg(ua, "%s\n", name_list->name[i]);
1153 * Free names in the list
1155 static void free_name_list(NAME_LIST *name_list)
1159 for (i=0; i < name_list->num_ids; i++) {
1160 free(name_list->name[i]);
1162 if (name_list->name) {
1163 free(name_list->name);
1165 name_list->max_ids = 0;
1166 name_list->num_ids = 0;
1169 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JobIds *ji)
1171 char name[MAX_NAME_LENGTH];
1172 STORE *store = NULL;
1174 if (name_list->num_ids > 1) {
1175 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1176 "Restore is not possible. The MediaTypes used are:\n"));
1177 print_name_list(ua, name_list);
1178 ji->store = select_storage_resource(ua);
1182 if (name_list->num_ids == 0) {
1183 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1184 ji->store = select_storage_resource(ua);
1188 start_prompt(ua, _("The defined Storage resources are:\n"));
1190 while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
1191 if (strcmp(store->media_type, name_list->name[0]) == 0) {
1192 add_prompt(ua, store->hdr.name);
1196 do_prompt(ua, _("Select Storage resource"), name);
1197 ji->store = (STORE *)GetResWithName(R_STORAGE, name);
1199 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1200 "MediaType %s, needed by the Jobs you selected.\n"
1201 "You will be allowed to select a Storage device later.\n"),
1202 name_list->name[0]);