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,
38 /* Imported functions */
39 extern int runcmd(UAContext *ua, char *cmd);
41 /* Imported variables */
42 extern char *uar_list_jobs, *uar_file, *uar_sel_files;
43 extern char *uar_del_temp, *uar_del_temp1, *uar_create_temp;
44 extern char *uar_create_temp1, *uar_last_full, *uar_full;
45 extern char *uar_inc, *uar_list_temp, *uar_sel_jobid_temp;
46 extern char *uar_sel_all_temp1, *uar_sel_fileset, *uar_mediatype;
49 /* Context for insert_tree_handler() */
50 typedef struct s_tree_ctx {
51 TREE_ROOT *root; /* root */
52 TREE_NODE *node; /* current node */
53 TREE_NODE *avail_node; /* unused node last insert */
54 int cnt; /* count for user feedback */
58 /* Main structure for obtaining JobIds */
59 typedef struct s_jobids {
69 /* FileIndex entry in bootstrap record */
70 typedef struct s_rbsr_findex {
71 struct s_rbsr_findex *next;
76 /* Restore bootstrap record -- not the real one, but useful here */
77 typedef struct s_rbsr {
78 struct s_rbsr *next; /* next JobId */
79 uint32_t JobId; /* JobId this bsr */
80 uint32_t VolSessionId;
81 uint32_t VolSessionTime;
82 char *VolumeName; /* Volume name */
83 RBSR_FINDEX *fi; /* File indexes this JobId */
86 typedef struct s_name_ctx {
87 char **name; /* list of names */
88 int num_ids; /* ids stored */
89 int max_ids; /* size of array */
90 int num_del; /* number deleted */
91 int tot_ids; /* total to process */
94 #define MAX_ID_LIST_LEN 1000000
97 /* Forward referenced functions */
98 static RBSR *new_bsr();
99 static void free_bsr(RBSR *bsr);
100 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd);
101 static int write_bsr_file(UAContext *ua, RBSR *bsr);
102 static void print_bsr(UAContext *ua, RBSR *bsr);
103 static int complete_bsr(UAContext *ua, RBSR *bsr);
104 static int insert_tree_handler(void *ctx, int num_fields, char **row);
105 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex);
106 static int last_full_handler(void *ctx, int num_fields, char **row);
107 static int jobid_handler(void *ctx, int num_fields, char **row);
108 static int next_jobid_from_list(char **p, uint32_t *JobId);
109 static int user_select_jobids(UAContext *ua, JobIds *ji);
110 static void user_select_files(TREE_CTX *tree);
111 static int fileset_handler(void *ctx, int num_fields, char **row);
112 static void print_name_list(UAContext *ua, NAME_LIST *name_list);
113 static int unique_name_list_handler(void *ctx, int num_fields, char **row);
114 static void free_name_list(NAME_LIST *name_list);
115 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JobIds *ji);
122 int restorecmd(UAContext *ua, char *cmd)
126 JobId_t JobId, last_JobId;
132 JOB *restore_job = NULL;
133 int restore_jobs = 0;
135 uint32_t selected_files = 0;
141 memset(&tree, 0, sizeof(TREE_CTX));
142 memset(&name_list, 0, sizeof(name_list));
143 memset(&ji, 0, sizeof(ji));
145 /* Ensure there is at least one Restore Job */
147 while ( (job = (JOB *)GetNextRes(R_JOB, (RES *)job)) ) {
148 if (job->JobType == JT_RESTORE) {
158 "No Restore Job Resource found. You must create at least\n"
159 "one before running this command.\n"));
164 if (!user_select_jobids(ua, &ji)) {
169 * Build the directory tree
171 tree.root = new_tree(ji.TotalFiles);
172 tree.root->fname = nofname;
174 query = get_pool_memory(PM_MESSAGE);
177 * For display purposes, the same JobId, with different volumes may
178 * appear more than once, however, we only insert it once.
180 for (p=ji.JobIds; next_jobid_from_list(&p, &JobId) > 0; ) {
182 if (JobId == last_JobId) {
183 continue; /* eliminate duplicate JobIds */
186 bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
188 * Find files for this JobId and insert them in the tree
190 Mmsg(&query, uar_sel_files, JobId);
191 if (!db_sql_query(ua->db, query, insert_tree_handler, (void *)&tree)) {
192 bsendmsg(ua, "%s", db_strerror(ua->db));
195 * Find the FileSets for this JobId and add to the name_list
197 Mmsg(&query, uar_mediatype, JobId);
198 if (!db_sql_query(ua->db, query, unique_name_list_handler, (void *)&name_list)) {
199 bsendmsg(ua, "%s", db_strerror(ua->db));
204 free_pool_memory(query);
206 /* Check MediaType and select storage that corresponds */
207 get_storage_from_mediatype(ua, &name_list, &ji);
208 free_name_list(&name_list);
210 /* Let the user select which files to restore */
211 user_select_files(&tree);
214 * Walk down through the tree finding all files marked to be
215 * extracted making a bootstrap file.
218 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
219 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
221 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
222 add_findex(bsr, node->JobId, node->FileIndex);
227 free_tree(tree.root); /* free the directory tree */
230 if (!complete_bsr(ua, bsr)) { /* find Vol, SessId, SessTime from JobIds */
231 bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
235 // print_bsr(ua, bsr);
236 write_bsr_file(ua, bsr);
237 bsendmsg(ua, _("\n%u files selected to restore.\n\n"), selected_files);
239 bsendmsg(ua, _("No files selected to restore.\n"));
243 if (restore_jobs == 1) {
246 job = select_restore_job_resource(ua);
249 bsendmsg(ua, _("No Restore Job resource found!\n"));
255 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
256 job->hdr.name, ji.client->hdr.name, ji.store?ji.store->hdr.name:"",
260 "run job=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
261 job->hdr.name, ji.store?ji.store->hdr.name:"", working_directory);
265 Dmsg1(400, "Submitting: %s\n", ua->cmd);
267 parse_command_args(ua);
270 bsendmsg(ua, _("Restore command done.\n"));
275 * The first step in the restore process is for the user to
276 * select a list of JobIds from which he will subsequently
277 * select which files are to be restored.
279 static int user_select_jobids(UAContext *ua, JobIds *ji)
281 char fileset_name[MAX_NAME_LENGTH];
289 "List last 20 Jobs run",
290 "List Jobs where a given File is saved",
291 "Enter list of JobIds to select",
292 "Enter SQL list command",
293 "Select the most recent backup for a client",
297 bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
298 "to be restored. You will be presented several methods\n"
299 "of specifying the JobIds. Then you will be allowed to\n"
300 "select which files from those JobIds are to be restored.\n\n"));
303 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
304 for (int i=0; list[i]; i++) {
305 add_prompt(ua, list[i]);
308 switch (do_prompt(ua, "Select item: ", NULL)) {
311 case 0: /* list last 20 Jobs run */
312 db_list_sql_query(ua->db, uar_list_jobs, prtit, ua, 1);
315 case 1: /* list where a file is saved */
316 if (!get_cmd(ua, _("Enter Filename: "))) {
319 query = get_pool_memory(PM_MESSAGE);
320 Mmsg(&query, uar_file, ua->cmd);
321 db_list_sql_query(ua->db, query, prtit, ua, 1);
322 free_pool_memory(query);
325 case 2: /* enter a list of JobIds */
326 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
329 bstrncpy(ji->JobIds, ua->cmd, sizeof(ji->JobIds));
331 case 3: /* Enter an SQL list command */
332 if (!get_cmd(ua, _("Enter SQL list command: "))) {
335 db_list_sql_query(ua->db, ua->cmd, prtit, ua, 1);
338 case 4: /* Select the most recent backups */
339 query = get_pool_memory(PM_MESSAGE);
340 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
341 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
342 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
343 bsendmsg(ua, "%s\n", db_strerror(ua->db));
345 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
346 bsendmsg(ua, "%s\n", db_strerror(ua->db));
351 if (!(ji->client = get_client_resource(ua))) {
358 Mmsg(&query, uar_sel_fileset, ji->client->hdr.name);
359 start_prompt(ua, _("The defined FileSet resources are:\n"));
360 if (!db_sql_query(ua->db, query, fileset_handler, (void *)ua)) {
361 bsendmsg(ua, "%s\n", db_strerror(ua->db));
363 if (do_prompt(ua, _("Select FileSet resource"), fileset_name) < 0) {
364 free_pool_memory(query);
368 strcpy(fsr.FileSet, fileset_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 Mmsg(&query, uar_last_full, ji->client->hdr.name, fsr.FileSetId);
376 /* Find JobId of full Backup of system */
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 add_prompt((UAContext *)ctx, row[1]);
500 /* Forward referenced commands */
502 static int markcmd(UAContext *ua, TREE_CTX *tree);
503 static int countcmd(UAContext *ua, TREE_CTX *tree);
504 static int findcmd(UAContext *ua, TREE_CTX *tree);
505 static int lscmd(UAContext *ua, TREE_CTX *tree);
506 static int dircmd(UAContext *ua, TREE_CTX *tree);
507 static int helpcmd(UAContext *ua, TREE_CTX *tree);
508 static int cdcmd(UAContext *ua, TREE_CTX *tree);
509 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
510 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
511 static int quitcmd(UAContext *ua, TREE_CTX *tree);
514 struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; };
515 static struct cmdstruct commands[] = {
516 { N_("mark"), markcmd, _("mark file for restoration")},
517 { N_("unmark"), unmarkcmd, _("unmark file for restoration")},
518 { N_("cd"), cdcmd, _("change current directory")},
519 { N_("pwd"), pwdcmd, _("print current working directory")},
520 { N_("ls"), lscmd, _("list current directory")},
521 { N_("dir"), dircmd, _("list current directory")},
522 { N_("count"), countcmd, _("count marked files")},
523 { N_("find"), findcmd, _("find files")},
524 { N_("done"), quitcmd, _("leave file selection mode")},
525 { N_("exit"), quitcmd, _("exit = done")},
526 { N_("help"), helpcmd, _("print help")},
527 { N_("?"), helpcmd, _("print help")},
529 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
533 * Enter a prompt mode where the user can select/deselect
534 * files to be restored. This is sort of like a mini-shell
535 * that allows "cd", "pwd", "add", "rm", ...
537 static void user_select_files(TREE_CTX *tree)
541 bsendmsg(tree->ua, _(
542 "\nYou are now entering file selection mode where you add and\n"
543 "remove files to be restored. All files are initially added.\n"
544 "Enter \"done\" to leave this mode.\n\n"));
546 * Enter interactive command handler allowing selection
547 * of individual files.
549 tree->node = (TREE_NODE *)tree->root;
550 tree_getpath(tree->node, cwd, sizeof(cwd));
551 bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
553 int found, len, stat, i;
554 if (!get_cmd(tree->ua, "$ ")) {
557 parse_command_args(tree->ua);
558 if (tree->ua->argc == 0) {
562 len = strlen(tree->ua->argk[0]);
565 for (i=0; i<(int)comsize; i++) /* search for command */
566 if (strncasecmp(tree->ua->argk[0], _(commands[i].key), len) == 0) {
567 stat = (*commands[i].func)(tree->ua, tree); /* go execute command */
572 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
582 * Create new FileIndex entry for BSR
584 static RBSR_FINDEX *new_findex()
586 RBSR_FINDEX *fi = (RBSR_FINDEX *)bmalloc(sizeof(RBSR_FINDEX));
587 memset(fi, 0, sizeof(RBSR_FINDEX));
591 /* Free all BSR FileIndex entries */
592 static void free_findex(RBSR_FINDEX *fi)
595 free_findex(fi->next);
600 static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd)
603 if (fi->findex == fi->findex2) {
604 fprintf(fd, "FileIndex=%d\n", fi->findex);
606 fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
608 write_findex(ua, fi->next, fd);
613 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
616 if (fi->findex == fi->findex2) {
617 bsendmsg(ua, "FileIndex=%d\n", fi->findex);
619 bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
621 print_findex(ua, fi->next);
625 /* Create a new bootstrap record */
626 static RBSR *new_bsr()
628 RBSR *bsr = (RBSR *)bmalloc(sizeof(RBSR));
629 memset(bsr, 0, sizeof(RBSR));
633 /* Free the entire BSR */
634 static void free_bsr(RBSR *bsr)
637 free_findex(bsr->fi);
639 if (bsr->VolumeName) {
640 free(bsr->VolumeName);
647 * Complete the BSR by filling in the VolumeName and
648 * VolSessionId and VolSessionTime using the JobId
650 static int complete_bsr(UAContext *ua, RBSR *bsr)
653 POOLMEM *VolumeNames;
656 VolumeNames = get_pool_memory(PM_MESSAGE);
657 memset(&jr, 0, sizeof(jr));
658 jr.JobId = bsr->JobId;
659 if (!db_get_job_record(ua->db, &jr)) {
660 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
663 bsr->VolSessionId = jr.VolSessionId;
664 bsr->VolSessionTime = jr.VolSessionTime;
665 if (!db_get_job_volume_names(ua->db, bsr->JobId, &VolumeNames)) {
666 bsendmsg(ua, _("Unable to get Job Volumes. ERR=%s\n"), db_strerror(ua->db));
667 free_pool_memory(VolumeNames);
670 bsr->VolumeName = bstrdup(VolumeNames);
671 free_pool_memory(VolumeNames);
672 return complete_bsr(ua, bsr->next);
678 * Write the bootstrap record to file
680 static int write_bsr_file(UAContext *ua, RBSR *bsr)
683 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
686 Mmsg(&fname, "%s/restore.bsr", working_directory);
687 fd = fopen(fname, "w+");
689 bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"),
690 fname, strerror(errno));
691 free_pool_memory(fname);
694 write_bsr(ua, bsr, fd);
697 // bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
698 free_pool_memory(fname);
702 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
705 if (bsr->VolumeName) {
706 fprintf(fd, "Volume=\"%s\"\n", bsr->VolumeName);
708 fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
709 fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
710 write_findex(ua, bsr->fi, fd);
711 write_bsr(ua, bsr->next, fd);
715 static void print_bsr(UAContext *ua, RBSR *bsr)
718 if (bsr->VolumeName) {
719 bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolumeName);
721 bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
722 bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
723 print_findex(ua, bsr->fi);
724 print_bsr(ua, bsr->next);
730 * Add a FileIndex to the list of BootStrap records.
731 * Here we are only dealing with JobId's and the FileIndexes
732 * associated with those JobIds.
734 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
737 RBSR_FINDEX *fi, *lfi;
740 return; /* probably a dummy directory */
743 if (!bsr->fi) { /* if no FI add one */
744 /* This is the first FileIndex item in the chain */
745 bsr->fi = new_findex();
747 bsr->fi->findex = findex;
748 bsr->fi->findex2 = findex;
751 /* Walk down list of bsrs until we find the JobId */
752 if (bsr->JobId != JobId) {
753 for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
754 if (nbsr->JobId == JobId) {
760 if (!nbsr) { /* Must add new JobId */
761 /* Add new JobId at end of chain */
762 for (nbsr=bsr; nbsr->next; nbsr=nbsr->next)
764 nbsr->next = new_bsr();
765 nbsr->next->JobId = JobId;
766 nbsr->next->fi = new_findex();
767 nbsr->next->fi->findex = findex;
768 nbsr->next->fi->findex2 = findex;
774 * At this point, bsr points to bsr containing JobId,
775 * and we are sure that there is at least one fi record.
778 /* Check if this findex is smaller than first item */
779 if (findex < fi->findex) {
780 if ((findex+1) == fi->findex) {
781 fi->findex = findex; /* extend down */
784 fi = new_findex(); /* yes, insert before first item */
786 fi->findex2 = findex;
791 /* Walk down fi chain and find where to insert insert new FileIndex */
792 for ( ; fi; fi=fi->next) {
793 if (findex == (fi->findex2 + 1)) { /* extend up */
795 fi->findex2 = findex;
796 if (fi->next && ((findex+1) == fi->next->findex)) {
798 fi->findex2 = nfi->findex2;
799 fi->next = nfi->next;
804 if (findex < fi->findex) { /* add before */
805 if ((findex+1) == fi->findex) {
813 /* Add to last place found */
816 fi->findex2 = findex;
817 fi->next = lfi->next;
823 * This callback routine is responsible for inserting the
824 * items it gets into the directory tree. For each JobId selected
825 * this routine is called once for each file. We do not allow
826 * duplicate filenames, but instead keep the info from the most
827 * recent file entered (i.e. the JobIds are assumed to be sorted)
829 static int insert_tree_handler(void *ctx, int num_fields, char **row)
831 TREE_CTX *tree = (TREE_CTX *)ctx;
833 TREE_NODE *node, *new_node;
836 strip_trailing_junk(row[1]);
838 if (*row[0] != '/') { /* Must be Win32 directory */
846 sprintf(fname, "%s%s", row[0], row[1]);
847 if (tree->avail_node) {
848 node = tree->avail_node;
850 node = new_tree_node(tree->root, type);
851 tree->avail_node = node;
853 Dmsg3(200, "FI=%d type=%d fname=%s\n", node->FileIndex, type, fname);
854 new_node = insert_tree_node(fname, node, tree->root, NULL);
855 /* Note, if node already exists, save new one for next time */
856 if (new_node != node) {
857 tree->avail_node = node;
859 tree->avail_node = NULL;
861 new_node->FileIndex = atoi(row[2]);
862 new_node->JobId = atoi(row[3]);
863 new_node->type = type;
864 new_node->extract = 1; /* extract all by default */
871 * Set extract to value passed. We recursively walk
872 * down the tree setting all children if the
873 * node is a directory.
875 static void set_extract(TREE_NODE *node, int value)
879 node->extract = value;
880 if (node->type != TN_FILE) {
881 for (n=node->child; n; n=n->sibling) {
882 set_extract(n, value);
887 static int markcmd(UAContext *ua, TREE_CTX *tree)
893 if (!tree->node->child) {
896 for (node = tree->node->child; node; node=node->sibling) {
897 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
898 set_extract(node, 1);
904 static int countcmd(UAContext *ua, TREE_CTX *tree)
909 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
910 if (node->type != TN_NEWDIR) {
917 bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
921 static int findcmd(UAContext *ua, TREE_CTX *tree)
926 bsendmsg(ua, _("No file specification given.\n"));
930 for (int i=1; i < ua->argc; i++) {
931 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
932 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
933 tree_getpath(node, cwd, sizeof(cwd));
934 bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
943 static int lscmd(UAContext *ua, TREE_CTX *tree)
947 if (!tree->node->child) {
950 for (node = tree->node->child; node; node=node->sibling) {
951 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
952 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
953 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
959 extern char *getuser(uid_t uid);
960 extern char *getgroup(gid_t gid);
962 static void ls_output(char *buf, char *fname, struct stat *statp)
968 // Dmsg2(000, "%s mode=0%o\n", fname, statp->st_mode);
970 p = encode_mode(statp->st_mode, buf);
971 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
973 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
975 n = sprintf(p, "%8.8s ", edit_uint64(statp->st_size, ec1));
977 p = encode_time(statp->st_ctime, p);
987 * Like ls command, but give more detail on each file
989 static int dircmd(UAContext *ua, TREE_CTX *tree)
997 if (!tree->node->child) {
1000 for (node = tree->node->child; node; node=node->sibling) {
1001 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1002 tree_getpath(node, cwd, sizeof(cwd));
1004 fdbr.JobId = node->JobId;
1005 if (db_get_file_attributes_record(ua->db, cwd, &fdbr)) {
1006 decode_stat(fdbr.LStat, &statp); /* decode stat pkt */
1007 ls_output(buf, cwd, &statp);
1008 bsendmsg(ua, "%s\n", buf);
1010 /* Something went wrong getting attributes -- print name */
1011 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1012 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1020 static int helpcmd(UAContext *ua, TREE_CTX *tree)
1025 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
1026 for (i=0; i<comsize; i++) {
1027 bsendmsg(ua, _(" %-10s %s\n"), _(commands[i].key), _(commands[i].help));
1034 * Change directories. Note, if the user specifies x: and it fails,
1035 * we assume it is a Win32 absolute cd rather than relative and
1036 * try a second time with /x: ... Win32 kludge.
1038 static int cdcmd(UAContext *ua, TREE_CTX *tree)
1043 if (ua->argc != 2) {
1046 node = tree_cwd(ua->argk[1], tree->root, tree->node);
1048 /* Try once more if Win32 drive -- make absolute */
1049 if (ua->argk[1][1] == ':') { /* win32 drive */
1051 strcat(cwd, ua->argk[1]);
1052 node = tree_cwd(cwd, tree->root, tree->node);
1055 bsendmsg(ua, _("Invalid path given.\n"));
1062 tree_getpath(tree->node, cwd, sizeof(cwd));
1063 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1067 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
1070 tree_getpath(tree->node, cwd, sizeof(cwd));
1071 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1076 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
1082 if (!tree->node->child) {
1085 for (node = tree->node->child; node; node=node->sibling) {
1086 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1087 set_extract(node, 0);
1093 static int quitcmd(UAContext *ua, TREE_CTX *tree)
1100 * Called here with each name to be added to the list. The name is
1101 * added to the list if it is not already in the list.
1103 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1105 NAME_LIST *name = (NAME_LIST *)ctx;
1107 if (name->num_ids == MAX_ID_LIST_LEN) {
1110 if (name->num_ids == name->max_ids) {
1111 if (name->max_ids == 0) {
1112 name->max_ids = 1000;
1113 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1115 name->max_ids = (name->max_ids * 3) / 2;
1116 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1119 for (int i=0; i<name->num_ids; i++) {
1120 if (strcmp(name->name[i], row[0]) == 0) {
1121 return 0; /* already in list, return */
1124 /* Add new name to list */
1125 name->name[name->num_ids++] = bstrdup(row[0]);
1131 * Print names in the list
1133 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1137 for (i=0; i < name_list->num_ids; i++) {
1138 bsendmsg(ua, "%s\n", name_list->name[i]);
1144 * Free names in the list
1146 static void free_name_list(NAME_LIST *name_list)
1150 for (i=0; i < name_list->num_ids; i++) {
1151 free(name_list->name[i]);
1153 if (name_list->name) {
1154 free(name_list->name);
1156 name_list->max_ids = 0;
1157 name_list->num_ids = 0;
1160 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JobIds *ji)
1162 char name[MAX_NAME_LENGTH];
1163 STORE *store = NULL;
1165 if (name_list->num_ids > 1) {
1166 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1167 "Restore is not possible. The MediaTypes used are:\n"));
1168 print_name_list(ua, name_list);
1169 ji->store = select_storage_resource(ua);
1173 if (name_list->num_ids == 0) {
1174 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1175 ji->store = select_storage_resource(ua);
1179 start_prompt(ua, _("The defined Storage resources are:\n"));
1181 while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
1182 if (strcmp(store->media_type, name_list->name[0]) == 0) {
1183 add_prompt(ua, store->hdr.name);
1187 do_prompt(ua, _("Select Storage resource"), name);
1188 ji->store = (STORE *)GetResWithName(R_STORAGE, name);
1190 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1191 "MediaType %s, needed by the Jobs you selected.\n"
1192 "You will be allowed to select a Storage device later.\n"),
1193 name_list->name[0]);