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;
140 memset(&tree, 0, sizeof(TREE_CTX));
141 memset(&name_list, 0, sizeof(name_list));
142 memset(&ji, 0, sizeof(ji));
144 /* Ensure there is at least one Restore Job */
146 while ( (job = (JOB *)GetNextRes(R_JOB, (RES *)job)) ) {
147 if (job->JobType == JT_RESTORE) {
157 "No Restore Job Resource found. You must create at least\n"
158 "one before running this command.\n"));
163 if (!user_select_jobids(ua, &ji)) {
168 * Build the directory tree
170 tree.root = new_tree(ji.TotalFiles);
171 tree.root->fname = nofname;
173 query = get_pool_memory(PM_MESSAGE);
176 * For display purposes, the same JobId, with different volumes may
177 * appear more than once, however, we only insert it once.
179 for (p=ji.JobIds; next_jobid_from_list(&p, &JobId) > 0; ) {
181 if (JobId == last_JobId) {
182 continue; /* eliminate duplicate JobIds */
185 bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
187 * Find files for this JobId and insert them in the tree
189 Mmsg(&query, uar_sel_files, JobId);
190 if (!db_sql_query(ua->db, query, insert_tree_handler, (void *)&tree)) {
191 bsendmsg(ua, "%s", db_strerror(ua->db));
194 * Find the FileSets for this JobId and add to the name_list
196 Mmsg(&query, uar_mediatype, JobId);
197 if (!db_sql_query(ua->db, query, unique_name_list_handler, (void *)&name_list)) {
198 bsendmsg(ua, "%s", db_strerror(ua->db));
203 free_pool_memory(query);
205 /* Check MediaType and select storage that corresponds */
206 get_storage_from_mediatype(ua, &name_list, &ji);
207 free_name_list(&name_list);
209 /* Let the user select which files to restore */
210 user_select_files(&tree);
213 * Walk down through the tree finding all files marked to be
214 * extracted making a bootstrap file.
217 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
218 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
220 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
221 add_findex(bsr, node->JobId, node->FileIndex);
225 free_tree(tree.root); /* free the directory tree */
228 complete_bsr(ua, bsr); /* find Vol, SessId, SessTime from JobIds */
229 // print_bsr(ua, bsr);
230 write_bsr_file(ua, bsr);
232 bsendmsg(ua, _("No files selected to restore.\n"));
236 if (restore_jobs == 1) {
239 job = select_restore_job_resource(ua);
242 bsendmsg(ua, _("No Restore Job resource found!\n"));
248 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
249 job->hdr.name, ji.client->hdr.name, ji.store?ji.store->hdr.name:"",
253 "run job=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
254 job->hdr.name, ji.store?ji.store->hdr.name:"", working_directory);
258 Dmsg1(400, "Submitting: %s\n", ua->cmd);
260 parse_command_args(ua);
263 bsendmsg(ua, _("Restore command done.\n"));
268 * The first step in the restore process is for the user to
269 * select a list of JobIds from which he will subsequently
270 * select which files are to be restored.
272 static int user_select_jobids(UAContext *ua, JobIds *ji)
274 char fileset_name[MAX_NAME_LENGTH];
282 "List last 20 Jobs run",
283 "List Jobs where a given File is saved",
284 "Enter list of JobIds to select",
285 "Enter SQL list command",
286 "Select the most recent backup for a client",
290 bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
291 "to be restored. You will be presented several methods\n"
292 "of specifying the JobIds. Then you will be allowed to\n"
293 "select which files from those JobIds are to be restored.\n\n"));
296 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
297 for (int i=0; list[i]; i++) {
298 add_prompt(ua, list[i]);
301 switch (do_prompt(ua, "Select item: ", NULL)) {
304 case 0: /* list last 20 Jobs run */
305 db_list_sql_query(ua->db, uar_list_jobs, prtit, ua, 1);
308 case 1: /* list where a file is saved */
309 if (!get_cmd(ua, _("Enter Filename: "))) {
312 query = get_pool_memory(PM_MESSAGE);
313 Mmsg(&query, uar_file, ua->cmd);
314 db_list_sql_query(ua->db, query, prtit, ua, 1);
315 free_pool_memory(query);
318 case 2: /* enter a list of JobIds */
319 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
322 bstrncpy(ji->JobIds, ua->cmd, sizeof(ji->JobIds));
324 case 3: /* Enter an SQL list command */
325 if (!get_cmd(ua, _("Enter SQL list command: "))) {
328 db_list_sql_query(ua->db, ua->cmd, prtit, ua, 1);
331 case 4: /* Select the most recent backups */
332 query = get_pool_memory(PM_MESSAGE);
333 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
334 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
335 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
336 bsendmsg(ua, "%s\n", db_strerror(ua->db));
338 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
339 bsendmsg(ua, "%s\n", db_strerror(ua->db));
344 if (!(ji->client = get_client_resource(ua))) {
351 Mmsg(&query, uar_sel_fileset, ji->client->hdr.name);
352 start_prompt(ua, _("The defined FileSet resources are:\n"));
353 if (!db_sql_query(ua->db, query, fileset_handler, (void *)ua)) {
354 bsendmsg(ua, "%s\n", db_strerror(ua->db));
356 if (do_prompt(ua, _("Select FileSet resource"), fileset_name) < 0) {
357 free_pool_memory(query);
361 strcpy(fsr.FileSet, fileset_name);
362 if (!db_get_fileset_record(ua->db, &fsr)) {
363 bsendmsg(ua, "%s\n", db_strerror(ua->db));
364 free_pool_memory(query);
368 Mmsg(&query, uar_last_full, ji->client->hdr.name, fsr.FileSetId);
369 /* Find JobId of full Backup of system */
370 if (!db_sql_query(ua->db, query, NULL, NULL)) {
371 bsendmsg(ua, "%s\n", db_strerror(ua->db));
373 /* Find all Volumes used by that JobId */
374 if (!db_sql_query(ua->db, uar_full, NULL,NULL)) {
375 bsendmsg(ua, "%s\n", db_strerror(ua->db));
377 /* Note, this is needed as I don't seem to get the callback
378 * from the call just above.
380 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)ji)) {
381 bsendmsg(ua, "%s\n", db_strerror(ua->db));
383 /* Now find all Incremental Jobs */
384 Mmsg(&query, uar_inc, (uint32_t)ji->JobTDate, ji->ClientId, fsr.FileSetId);
385 if (!db_sql_query(ua->db, query, NULL, NULL)) {
386 bsendmsg(ua, "%s\n", db_strerror(ua->db));
388 free_pool_memory(query);
389 db_list_sql_query(ua->db, uar_list_temp, prtit, ua, 1);
391 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)ji)) {
392 bsendmsg(ua, "%s\n", db_strerror(ua->db));
394 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
395 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
402 if (*ji->JobIds == 0) {
403 bsendmsg(ua, _("No Jobs selected.\n"));
406 bsendmsg(ua, _("You have selected the following JobId: %s\n"), ji->JobIds);
408 memset(&jr, 0, sizeof(JOB_DBR));
411 for (p=ji->JobIds; ; ) {
412 int stat = next_jobid_from_list(&p, &JobId);
414 bsendmsg(ua, _("Invalid JobId in list.\n"));
421 if (!db_get_job_record(ua->db, &jr)) {
422 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
425 ji->TotalFiles += jr.JobFiles;
430 static int next_jobid_from_list(char **p, uint32_t *JobId)
437 for (i=0; i<(int)sizeof(jobid); i++) {
438 if (*q == ',' || *q == 0) {
445 if (jobid[0] == 0 || !is_a_number(jobid)) {
449 *JobId = strtoul(jobid, NULL, 10);
454 * Callback handler make list of JobIds
456 static int jobid_handler(void *ctx, int num_fields, char **row)
458 JobIds *ji = (JobIds *)ctx;
460 if (strlen(ji->JobIds)+strlen(row[0])+2 < sizeof(ji->JobIds)) {
461 if (ji->JobIds[0] != 0) {
462 strcat(ji->JobIds, ",");
464 strcat(ji->JobIds, row[0]);
472 * Callback handler to pickup last Full backup JobId and ClientId
474 static int last_full_handler(void *ctx, int num_fields, char **row)
476 JobIds *ji = (JobIds *)ctx;
478 ji->JobTDate = atoi(row[1]);
479 ji->ClientId = atoi(row[2]);
485 * Callback handler build fileset prompt list
487 static int fileset_handler(void *ctx, int num_fields, char **row)
489 add_prompt((UAContext *)ctx, row[1]);
493 /* Forward referenced commands */
495 static int markcmd(UAContext *ua, TREE_CTX *tree);
496 static int countcmd(UAContext *ua, TREE_CTX *tree);
497 static int findcmd(UAContext *ua, TREE_CTX *tree);
498 static int lscmd(UAContext *ua, TREE_CTX *tree);
499 static int dircmd(UAContext *ua, TREE_CTX *tree);
500 static int helpcmd(UAContext *ua, TREE_CTX *tree);
501 static int cdcmd(UAContext *ua, TREE_CTX *tree);
502 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
503 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
504 static int quitcmd(UAContext *ua, TREE_CTX *tree);
507 struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; };
508 static struct cmdstruct commands[] = {
509 { N_("mark"), markcmd, _("mark file for restoration")},
510 { N_("unmark"), unmarkcmd, _("unmark file for restoration")},
511 { N_("cd"), cdcmd, _("change current directory")},
512 { N_("pwd"), pwdcmd, _("print current working directory")},
513 { N_("ls"), lscmd, _("list current directory")},
514 { N_("dir"), dircmd, _("list current directory")},
515 { N_("count"), countcmd, _("count marked files")},
516 { N_("find"), findcmd, _("find files")},
517 { N_("done"), quitcmd, _("leave file selection mode")},
518 { N_("exit"), quitcmd, _("exit = done")},
519 { N_("help"), helpcmd, _("print help")},
520 { N_("?"), helpcmd, _("print help")},
522 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
526 * Enter a prompt mode where the user can select/deselect
527 * files to be restored. This is sort of like a mini-shell
528 * that allows "cd", "pwd", "add", "rm", ...
530 static void user_select_files(TREE_CTX *tree)
534 bsendmsg(tree->ua, _(
535 "\nYou are now entering file selection mode where you add and\n"
536 "remove files to be restored. All files are initially added.\n"
537 "Enter done to leave this mode.\n\n"));
539 * Enter interactive command handler allowing selection
540 * of individual files.
542 tree->node = (TREE_NODE *)tree->root;
543 tree_getpath(tree->node, cwd, sizeof(cwd));
544 bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
546 int found, len, stat, i;
547 if (!get_cmd(tree->ua, "$ ")) {
550 parse_command_args(tree->ua);
551 if (tree->ua->argc == 0) {
555 len = strlen(tree->ua->argk[0]);
557 for (i=0; i<(int)comsize; i++) /* search for command */
558 if (strncasecmp(tree->ua->argk[0], _(commands[i].key), len) == 0) {
559 stat = (*commands[i].func)(tree->ua, tree); /* go execute command */
564 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
573 * Create new FileIndex entry for BSR
575 static RBSR_FINDEX *new_findex()
577 RBSR_FINDEX *fi = (RBSR_FINDEX *)bmalloc(sizeof(RBSR_FINDEX));
578 memset(fi, 0, sizeof(RBSR_FINDEX));
582 /* Free all BSR FileIndex entries */
583 static void free_findex(RBSR_FINDEX *fi)
586 free_findex(fi->next);
591 static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd)
594 if (fi->findex == fi->findex2) {
595 fprintf(fd, "FileIndex=%d\n", fi->findex);
597 fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
599 write_findex(ua, fi->next, fd);
604 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
607 if (fi->findex == fi->findex2) {
608 bsendmsg(ua, "FileIndex=%d\n", fi->findex);
610 bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
612 print_findex(ua, fi->next);
616 /* Create a new bootstrap record */
617 static RBSR *new_bsr()
619 RBSR *bsr = (RBSR *)bmalloc(sizeof(RBSR));
620 memset(bsr, 0, sizeof(RBSR));
624 /* Free the entire BSR */
625 static void free_bsr(RBSR *bsr)
628 free_findex(bsr->fi);
630 if (bsr->VolumeName) {
631 free(bsr->VolumeName);
638 * Complete the BSR by filling in the VolumeName and
639 * VolSessionId and VolSessionTime using the JobId
641 static int complete_bsr(UAContext *ua, RBSR *bsr)
644 POOLMEM *VolumeNames;
647 VolumeNames = get_pool_memory(PM_MESSAGE);
648 memset(&jr, 0, sizeof(jr));
649 jr.JobId = bsr->JobId;
650 if (!db_get_job_record(ua->db, &jr)) {
651 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
654 bsr->VolSessionId = jr.VolSessionId;
655 bsr->VolSessionTime = jr.VolSessionTime;
656 if (!db_get_job_volume_names(ua->db, bsr->JobId, &VolumeNames)) {
657 bsendmsg(ua, _("Unable to get Job Volumes. ERR=%s\n"), db_strerror(ua->db));
658 free_pool_memory(VolumeNames);
661 bsr->VolumeName = bstrdup(VolumeNames);
662 free_pool_memory(VolumeNames);
663 return complete_bsr(ua, bsr->next);
669 * Write the bootstrap record to file
671 static int write_bsr_file(UAContext *ua, RBSR *bsr)
674 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
677 Mmsg(&fname, "%s/restore.bsr", working_directory);
678 fd = fopen(fname, "w+");
680 bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"),
681 fname, strerror(errno));
682 free_pool_memory(fname);
685 write_bsr(ua, bsr, fd);
688 // bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
689 free_pool_memory(fname);
693 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
696 if (bsr->VolumeName) {
697 fprintf(fd, "Volume=\"%s\"\n", bsr->VolumeName);
699 fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
700 fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
701 write_findex(ua, bsr->fi, fd);
702 write_bsr(ua, bsr->next, fd);
706 static void print_bsr(UAContext *ua, RBSR *bsr)
709 if (bsr->VolumeName) {
710 bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolumeName);
712 bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
713 bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
714 print_findex(ua, bsr->fi);
715 print_bsr(ua, bsr->next);
721 * Add a FileIndex to the list of BootStrap records.
722 * Here we are only dealing with JobId's and the FileIndexes
723 * associated with those JobIds.
725 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
728 RBSR_FINDEX *fi, *lfi;
731 return; /* probably a dummy directory */
734 if (!bsr->fi) { /* if no FI add one */
735 /* This is the first FileIndex item in the chain */
736 bsr->fi = new_findex();
738 bsr->fi->findex = findex;
739 bsr->fi->findex2 = findex;
742 /* Walk down list of bsrs until we find the JobId */
743 if (bsr->JobId != JobId) {
744 for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
745 if (nbsr->JobId == JobId) {
751 if (!nbsr) { /* Must add new JobId */
752 /* Add new JobId at end of chain */
753 for (nbsr=bsr; nbsr->next; nbsr=nbsr->next)
755 nbsr->next = new_bsr();
756 nbsr->next->JobId = JobId;
757 nbsr->next->fi = new_findex();
758 nbsr->next->fi->findex = findex;
759 nbsr->next->fi->findex2 = findex;
765 * At this point, bsr points to bsr containing JobId,
766 * and we are sure that there is at least one fi record.
769 /* Check if this findex is smaller than first item */
770 if (findex < fi->findex) {
771 if ((findex+1) == fi->findex) {
772 fi->findex = findex; /* extend down */
775 fi = new_findex(); /* yes, insert before first item */
777 fi->findex2 = findex;
782 /* Walk down fi chain and find where to insert insert new FileIndex */
783 for ( ; fi; fi=fi->next) {
784 if (findex == (fi->findex2 + 1)) { /* extend up */
786 fi->findex2 = findex;
787 if (fi->next && ((findex+1) == fi->next->findex)) {
789 fi->findex2 = nfi->findex2;
790 fi->next = nfi->next;
795 if (findex < fi->findex) { /* add before */
796 if ((findex+1) == fi->findex) {
804 /* Add to last place found */
807 fi->findex2 = findex;
808 fi->next = lfi->next;
814 * This callback routine is responsible for inserting the
815 * items it gets into the directory tree. For each JobId selected
816 * this routine is called once for each file. We do not allow
817 * duplicate filenames, but instead keep the info from the most
818 * recent file entered (i.e. the JobIds are assumed to be sorted)
820 static int insert_tree_handler(void *ctx, int num_fields, char **row)
822 TREE_CTX *tree = (TREE_CTX *)ctx;
824 TREE_NODE *node, *new_node;
827 strip_trailing_junk(row[1]);
833 sprintf(fname, "%s%s", row[0], row[1]);
834 if (tree->avail_node) {
835 node = tree->avail_node;
837 node = new_tree_node(tree->root, type);
838 tree->avail_node = node;
840 Dmsg2(400, "FI=%d fname=%s\n", node->FileIndex, fname);
841 new_node = insert_tree_node(fname, node, tree->root, NULL);
842 /* Note, if node already exists, save new one for next time */
843 if (new_node != node) {
844 tree->avail_node = node;
846 tree->avail_node = NULL;
848 new_node->FileIndex = atoi(row[2]);
849 new_node->JobId = atoi(row[3]);
850 new_node->type = type;
851 new_node->extract = 1; /* extract all by default */
858 * Set extract to value passed. We recursively walk
859 * down the tree setting all children if the
860 * node is a directory.
862 static void set_extract(TREE_NODE *node, int value)
866 node->extract = value;
867 if (node->type != TN_FILE) {
868 for (n=node->child; n; n=n->sibling) {
869 set_extract(n, value);
874 static int markcmd(UAContext *ua, TREE_CTX *tree)
880 if (!tree->node->child) {
883 for (node = tree->node->child; node; node=node->sibling) {
884 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
885 set_extract(node, 1);
891 static int countcmd(UAContext *ua, TREE_CTX *tree)
896 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
897 if (node->type != TN_NEWDIR) {
904 bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
908 static int findcmd(UAContext *ua, TREE_CTX *tree)
913 bsendmsg(ua, _("No file specification given.\n"));
917 for (int i=1; i < ua->argc; i++) {
918 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
919 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
920 tree_getpath(node, cwd, sizeof(cwd));
921 bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
930 static int lscmd(UAContext *ua, TREE_CTX *tree)
934 if (!tree->node->child) {
937 for (node = tree->node->child; node; node=node->sibling) {
938 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
939 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
940 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
946 extern char *getuser(uid_t uid);
947 extern char *getgroup(gid_t gid);
949 static void ls_output(char *buf, char *fname, struct stat *statp)
954 p = encode_mode(statp->st_mode, buf);
955 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
957 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
959 n = sprintf(p, "%8ld ", statp->st_size);
961 p = encode_time(statp->st_ctime, p);
971 * Like ls command, but give more detail on each file
973 static int dircmd(UAContext *ua, TREE_CTX *tree)
981 if (!tree->node->child) {
984 for (node = tree->node->child; node; node=node->sibling) {
985 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
986 tree_getpath(node, cwd, sizeof(cwd));
988 fdbr.JobId = node->JobId;
989 if (db_get_file_attributes_record(ua->db, cwd, &fdbr)) {
990 decode_stat(fdbr.LStat, &statp); /* decode stat pkt */
991 ls_output(buf, cwd, &statp);
992 bsendmsg(ua, "%s\n", buf);
1000 static int helpcmd(UAContext *ua, TREE_CTX *tree)
1005 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
1006 for (i=0; i<comsize; i++) {
1007 bsendmsg(ua, _(" %-10s %s\n"), _(commands[i].key), _(commands[i].help));
1013 static int cdcmd(UAContext *ua, TREE_CTX *tree)
1018 if (ua->argc != 2) {
1021 node = tree_cwd(ua->argk[1], tree->root, tree->node);
1023 bsendmsg(ua, _("Invalid path given.\n"));
1027 tree_getpath(tree->node, cwd, sizeof(cwd));
1028 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1032 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
1035 tree_getpath(tree->node, cwd, sizeof(cwd));
1036 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1041 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
1047 if (!tree->node->child) {
1050 for (node = tree->node->child; node; node=node->sibling) {
1051 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1052 set_extract(node, 0);
1058 static int quitcmd(UAContext *ua, TREE_CTX *tree)
1065 * Called here with each name to be added to the list. The name is
1066 * added to the list if it is not already in the list.
1068 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1070 NAME_LIST *name = (NAME_LIST *)ctx;
1072 if (name->num_ids == MAX_ID_LIST_LEN) {
1075 if (name->num_ids == name->max_ids) {
1076 if (name->max_ids == 0) {
1077 name->max_ids = 1000;
1078 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1080 name->max_ids = (name->max_ids * 3) / 2;
1081 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1084 for (int i=0; i<name->num_ids; i++) {
1085 if (strcmp(name->name[i], row[0]) == 0) {
1086 return 0; /* already in list, return */
1089 /* Add new name to list */
1090 name->name[name->num_ids++] = bstrdup(row[0]);
1096 * Print names in the list
1098 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1102 for (i=0; i < name_list->num_ids; i++) {
1103 bsendmsg(ua, "%s\n", name_list->name[i]);
1109 * Free names in the list
1111 static void free_name_list(NAME_LIST *name_list)
1115 for (i=0; i < name_list->num_ids; i++) {
1116 free(name_list->name[i]);
1118 free(name_list->name);
1119 name_list->max_ids = 0;
1120 name_list->num_ids = 0;
1123 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JobIds *ji)
1125 char name[MAX_NAME_LENGTH];
1126 STORE *store = NULL;
1128 if (name_list->num_ids > 1) {
1129 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1130 "Restore is not possible. The MediaTypes used are:\n"));
1131 print_name_list(ua, name_list);
1132 ji->store = select_storage_resource(ua);
1136 start_prompt(ua, _("The defined Storage resources are:\n"));
1138 while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
1139 if (strcmp(store->hdr.name, name_list->name[0]) == 0) {
1140 add_prompt(ua, store->hdr.name);
1144 do_prompt(ua, _("Select Storage resource"), name);
1145 ji->store = (STORE *)GetResWithName(R_STORAGE, name);
1147 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1148 "MediaType %s, needed by the Jobs you selected.\n"
1149 "You will be allowed to select a Storage device later.\n"),
1150 name_list->name[0]);