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, 0)) {
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"),
365 fileset_name, sizeof(fileset_name)) < 0) {
366 free_pool_memory(query);
369 fsr.FileSetId = atoi(fileset_name); /* Id is first part of 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 /* Find JobId of last Full backup for this client, fileset */
377 Mmsg(&query, uar_last_full, ji->client->hdr.name, fsr.FileSetId);
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 char prompt[MAX_NAME_LENGTH+200];
499 snprintf(prompt, sizeof(prompt), "%s %s %s", row[0], row[1], row[2]);
500 add_prompt((UAContext *)ctx, prompt);
504 /* Forward referenced commands */
506 static int markcmd(UAContext *ua, TREE_CTX *tree);
507 static int countcmd(UAContext *ua, TREE_CTX *tree);
508 static int findcmd(UAContext *ua, TREE_CTX *tree);
509 static int lscmd(UAContext *ua, TREE_CTX *tree);
510 static int dircmd(UAContext *ua, TREE_CTX *tree);
511 static int helpcmd(UAContext *ua, TREE_CTX *tree);
512 static int cdcmd(UAContext *ua, TREE_CTX *tree);
513 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
514 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
515 static int quitcmd(UAContext *ua, TREE_CTX *tree);
518 struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; };
519 static struct cmdstruct commands[] = {
520 { N_("mark"), markcmd, _("mark file for restoration")},
521 { N_("unmark"), unmarkcmd, _("unmark file for restoration")},
522 { N_("cd"), cdcmd, _("change current directory")},
523 { N_("pwd"), pwdcmd, _("print current working directory")},
524 { N_("ls"), lscmd, _("list current directory")},
525 { N_("dir"), dircmd, _("list current directory")},
526 { N_("count"), countcmd, _("count marked files")},
527 { N_("find"), findcmd, _("find files")},
528 { N_("done"), quitcmd, _("leave file selection mode")},
529 { N_("exit"), quitcmd, _("exit = done")},
530 { N_("help"), helpcmd, _("print help")},
531 { N_("?"), helpcmd, _("print help")},
533 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
537 * Enter a prompt mode where the user can select/deselect
538 * files to be restored. This is sort of like a mini-shell
539 * that allows "cd", "pwd", "add", "rm", ...
541 static void user_select_files(TREE_CTX *tree)
545 bsendmsg(tree->ua, _(
546 "\nYou are now entering file selection mode where you add and\n"
547 "remove files to be restored. All files are initially added.\n"
548 "Enter \"done\" to leave this mode.\n\n"));
550 * Enter interactive command handler allowing selection
551 * of individual files.
553 tree->node = (TREE_NODE *)tree->root;
554 tree_getpath(tree->node, cwd, sizeof(cwd));
555 bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
557 int found, len, stat, i;
558 if (!get_cmd(tree->ua, "$ ")) {
561 parse_command_args(tree->ua);
562 if (tree->ua->argc == 0) {
566 len = strlen(tree->ua->argk[0]);
569 for (i=0; i<(int)comsize; i++) /* search for command */
570 if (strncasecmp(tree->ua->argk[0], _(commands[i].key), len) == 0) {
571 stat = (*commands[i].func)(tree->ua, tree); /* go execute command */
576 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
586 * Create new FileIndex entry for BSR
588 static RBSR_FINDEX *new_findex()
590 RBSR_FINDEX *fi = (RBSR_FINDEX *)bmalloc(sizeof(RBSR_FINDEX));
591 memset(fi, 0, sizeof(RBSR_FINDEX));
595 /* Free all BSR FileIndex entries */
596 static void free_findex(RBSR_FINDEX *fi)
599 free_findex(fi->next);
604 static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd)
607 if (fi->findex == fi->findex2) {
608 fprintf(fd, "FileIndex=%d\n", fi->findex);
610 fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
612 write_findex(ua, fi->next, fd);
617 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
620 if (fi->findex == fi->findex2) {
621 bsendmsg(ua, "FileIndex=%d\n", fi->findex);
623 bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
625 print_findex(ua, fi->next);
629 /* Create a new bootstrap record */
630 static RBSR *new_bsr()
632 RBSR *bsr = (RBSR *)bmalloc(sizeof(RBSR));
633 memset(bsr, 0, sizeof(RBSR));
637 /* Free the entire BSR */
638 static void free_bsr(RBSR *bsr)
641 free_findex(bsr->fi);
643 if (bsr->VolumeName) {
644 free(bsr->VolumeName);
651 * Complete the BSR by filling in the VolumeName and
652 * VolSessionId and VolSessionTime using the JobId
654 static int complete_bsr(UAContext *ua, RBSR *bsr)
657 POOLMEM *VolumeNames;
660 VolumeNames = get_pool_memory(PM_MESSAGE);
661 memset(&jr, 0, sizeof(jr));
662 jr.JobId = bsr->JobId;
663 if (!db_get_job_record(ua->db, &jr)) {
664 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
667 bsr->VolSessionId = jr.VolSessionId;
668 bsr->VolSessionTime = jr.VolSessionTime;
669 if (!db_get_job_volume_names(ua->db, bsr->JobId, &VolumeNames)) {
670 bsendmsg(ua, _("Unable to get Job Volumes. ERR=%s\n"), db_strerror(ua->db));
671 free_pool_memory(VolumeNames);
674 bsr->VolumeName = bstrdup(VolumeNames);
675 free_pool_memory(VolumeNames);
676 return complete_bsr(ua, bsr->next);
682 * Write the bootstrap record to file
684 static int write_bsr_file(UAContext *ua, RBSR *bsr)
687 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
691 Mmsg(&fname, "%s/restore.bsr", working_directory);
692 fd = fopen(fname, "w+");
694 bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"),
695 fname, strerror(errno));
696 free_pool_memory(fname);
699 write_bsr(ua, bsr, fd);
702 bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
703 bsendmsg(ua, _("\nThe restore job will require the following Volumes:\n"));
704 for (nbsr=bsr; nbsr; nbsr=nbsr->next) {
705 if (nbsr->VolumeName) {
706 bsendmsg(ua, " %s\n", nbsr->VolumeName);
710 free_pool_memory(fname);
714 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
717 if (bsr->VolumeName) {
718 fprintf(fd, "Volume=\"%s\"\n", bsr->VolumeName);
720 fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
721 fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
722 write_findex(ua, bsr->fi, fd);
723 write_bsr(ua, bsr->next, fd);
727 static void print_bsr(UAContext *ua, RBSR *bsr)
730 if (bsr->VolumeName) {
731 bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolumeName);
733 bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
734 bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
735 print_findex(ua, bsr->fi);
736 print_bsr(ua, bsr->next);
742 * Add a FileIndex to the list of BootStrap records.
743 * Here we are only dealing with JobId's and the FileIndexes
744 * associated with those JobIds.
746 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
749 RBSR_FINDEX *fi, *lfi;
752 return; /* probably a dummy directory */
755 if (!bsr->fi) { /* if no FI add one */
756 /* This is the first FileIndex item in the chain */
757 bsr->fi = new_findex();
759 bsr->fi->findex = findex;
760 bsr->fi->findex2 = findex;
763 /* Walk down list of bsrs until we find the JobId */
764 if (bsr->JobId != JobId) {
765 for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
766 if (nbsr->JobId == JobId) {
772 if (!nbsr) { /* Must add new JobId */
773 /* Add new JobId at end of chain */
774 for (nbsr=bsr; nbsr->next; nbsr=nbsr->next)
776 nbsr->next = new_bsr();
777 nbsr->next->JobId = JobId;
778 nbsr->next->fi = new_findex();
779 nbsr->next->fi->findex = findex;
780 nbsr->next->fi->findex2 = findex;
786 * At this point, bsr points to bsr containing JobId,
787 * and we are sure that there is at least one fi record.
790 /* Check if this findex is smaller than first item */
791 if (findex < fi->findex) {
792 if ((findex+1) == fi->findex) {
793 fi->findex = findex; /* extend down */
796 fi = new_findex(); /* yes, insert before first item */
798 fi->findex2 = findex;
803 /* Walk down fi chain and find where to insert insert new FileIndex */
804 for ( ; fi; fi=fi->next) {
805 if (findex == (fi->findex2 + 1)) { /* extend up */
807 fi->findex2 = findex;
808 if (fi->next && ((findex+1) == fi->next->findex)) {
810 fi->findex2 = nfi->findex2;
811 fi->next = nfi->next;
816 if (findex < fi->findex) { /* add before */
817 if ((findex+1) == fi->findex) {
825 /* Add to last place found */
828 fi->findex2 = findex;
829 fi->next = lfi->next;
835 * This callback routine is responsible for inserting the
836 * items it gets into the directory tree. For each JobId selected
837 * this routine is called once for each file. We do not allow
838 * duplicate filenames, but instead keep the info from the most
839 * recent file entered (i.e. the JobIds are assumed to be sorted)
841 static int insert_tree_handler(void *ctx, int num_fields, char **row)
843 TREE_CTX *tree = (TREE_CTX *)ctx;
845 TREE_NODE *node, *new_node;
848 strip_trailing_junk(row[1]);
850 if (*row[0] != '/') { /* Must be Win32 directory */
858 sprintf(fname, "%s%s", row[0], row[1]);
859 if (tree->avail_node) {
860 node = tree->avail_node;
862 node = new_tree_node(tree->root, type);
863 tree->avail_node = node;
865 Dmsg3(200, "FI=%d type=%d fname=%s\n", node->FileIndex, type, fname);
866 new_node = insert_tree_node(fname, node, tree->root, NULL);
867 /* Note, if node already exists, save new one for next time */
868 if (new_node != node) {
869 tree->avail_node = node;
871 tree->avail_node = NULL;
873 new_node->FileIndex = atoi(row[2]);
874 new_node->JobId = atoi(row[3]);
875 new_node->type = type;
876 new_node->extract = 1; /* extract all by default */
883 * Set extract to value passed. We recursively walk
884 * down the tree setting all children if the
885 * node is a directory.
887 static void set_extract(TREE_NODE *node, int value)
891 node->extract = value;
892 if (node->type != TN_FILE) {
893 for (n=node->child; n; n=n->sibling) {
894 set_extract(n, value);
899 static int markcmd(UAContext *ua, TREE_CTX *tree)
905 if (!tree->node->child) {
908 for (node = tree->node->child; node; node=node->sibling) {
909 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
910 set_extract(node, 1);
916 static int countcmd(UAContext *ua, TREE_CTX *tree)
921 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
922 if (node->type != TN_NEWDIR) {
929 bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
933 static int findcmd(UAContext *ua, TREE_CTX *tree)
938 bsendmsg(ua, _("No file specification given.\n"));
942 for (int i=1; i < ua->argc; i++) {
943 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
944 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
945 tree_getpath(node, cwd, sizeof(cwd));
946 bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
955 static int lscmd(UAContext *ua, TREE_CTX *tree)
959 if (!tree->node->child) {
962 for (node = tree->node->child; node; node=node->sibling) {
963 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
964 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
965 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
971 extern char *getuser(uid_t uid);
972 extern char *getgroup(gid_t gid);
974 static void ls_output(char *buf, char *fname, struct stat *statp)
980 // Dmsg2(000, "%s mode=0%o\n", fname, statp->st_mode);
982 p = encode_mode(statp->st_mode, buf);
983 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
985 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
987 n = sprintf(p, "%8.8s ", edit_uint64(statp->st_size, ec1));
989 p = encode_time(statp->st_ctime, p);
999 * Like ls command, but give more detail on each file
1001 static int dircmd(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 tree_getpath(node, cwd, sizeof(cwd));
1016 fdbr.JobId = node->JobId;
1017 if (db_get_file_attributes_record(ua->db, cwd, &fdbr)) {
1018 decode_stat(fdbr.LStat, &statp); /* decode stat pkt */
1019 ls_output(buf, cwd, &statp);
1020 bsendmsg(ua, "%s\n", buf);
1022 /* Something went wrong getting attributes -- print name */
1023 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1024 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1032 static int helpcmd(UAContext *ua, TREE_CTX *tree)
1037 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
1038 for (i=0; i<comsize; i++) {
1039 bsendmsg(ua, _(" %-10s %s\n"), _(commands[i].key), _(commands[i].help));
1046 * Change directories. Note, if the user specifies x: and it fails,
1047 * we assume it is a Win32 absolute cd rather than relative and
1048 * try a second time with /x: ... Win32 kludge.
1050 static int cdcmd(UAContext *ua, TREE_CTX *tree)
1055 if (ua->argc != 2) {
1058 node = tree_cwd(ua->argk[1], tree->root, tree->node);
1060 /* Try once more if Win32 drive -- make absolute */
1061 if (ua->argk[1][1] == ':') { /* win32 drive */
1063 strcat(cwd, ua->argk[1]);
1064 node = tree_cwd(cwd, tree->root, tree->node);
1067 bsendmsg(ua, _("Invalid path given.\n"));
1074 tree_getpath(tree->node, cwd, sizeof(cwd));
1075 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1079 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
1082 tree_getpath(tree->node, cwd, sizeof(cwd));
1083 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1088 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
1094 if (!tree->node->child) {
1097 for (node = tree->node->child; node; node=node->sibling) {
1098 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1099 set_extract(node, 0);
1105 static int quitcmd(UAContext *ua, TREE_CTX *tree)
1112 * Called here with each name to be added to the list. The name is
1113 * added to the list if it is not already in the list.
1115 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1117 NAME_LIST *name = (NAME_LIST *)ctx;
1119 if (name->num_ids == MAX_ID_LIST_LEN) {
1122 if (name->num_ids == name->max_ids) {
1123 if (name->max_ids == 0) {
1124 name->max_ids = 1000;
1125 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1127 name->max_ids = (name->max_ids * 3) / 2;
1128 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1131 for (int i=0; i<name->num_ids; i++) {
1132 if (strcmp(name->name[i], row[0]) == 0) {
1133 return 0; /* already in list, return */
1136 /* Add new name to list */
1137 name->name[name->num_ids++] = bstrdup(row[0]);
1143 * Print names in the list
1145 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1149 for (i=0; i < name_list->num_ids; i++) {
1150 bsendmsg(ua, "%s\n", name_list->name[i]);
1156 * Free names in the list
1158 static void free_name_list(NAME_LIST *name_list)
1162 for (i=0; i < name_list->num_ids; i++) {
1163 free(name_list->name[i]);
1165 if (name_list->name) {
1166 free(name_list->name);
1168 name_list->max_ids = 0;
1169 name_list->num_ids = 0;
1172 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JobIds *ji)
1174 char name[MAX_NAME_LENGTH];
1175 STORE *store = NULL;
1177 if (name_list->num_ids > 1) {
1178 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1179 "Restore is not possible. The MediaTypes used are:\n"));
1180 print_name_list(ua, name_list);
1181 ji->store = select_storage_resource(ua);
1185 if (name_list->num_ids == 0) {
1186 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1187 ji->store = select_storage_resource(ua);
1191 start_prompt(ua, _("The defined Storage resources are:\n"));
1193 while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
1194 if (strcmp(store->media_type, name_list->name[0]) == 0) {
1195 add_prompt(ua, store->hdr.name);
1199 do_prompt(ua, _("Select Storage resource"), name, sizeof(name));
1200 ji->store = (STORE *)GetResWithName(R_STORAGE, name);
1202 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1203 "MediaType %s, needed by the Jobs you selected.\n"
1204 "You will be allowed to select a Storage device later.\n"),
1205 name_list->name[0]);