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]);
558 for (i=0; i<(int)comsize; i++) /* search for command */
559 if (strncasecmp(tree->ua->argk[0], _(commands[i].key), len) == 0) {
560 stat = (*commands[i].func)(tree->ua, tree); /* go execute command */
565 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
575 * Create new FileIndex entry for BSR
577 static RBSR_FINDEX *new_findex()
579 RBSR_FINDEX *fi = (RBSR_FINDEX *)bmalloc(sizeof(RBSR_FINDEX));
580 memset(fi, 0, sizeof(RBSR_FINDEX));
584 /* Free all BSR FileIndex entries */
585 static void free_findex(RBSR_FINDEX *fi)
588 free_findex(fi->next);
593 static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd)
596 if (fi->findex == fi->findex2) {
597 fprintf(fd, "FileIndex=%d\n", fi->findex);
599 fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
601 write_findex(ua, fi->next, fd);
606 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
609 if (fi->findex == fi->findex2) {
610 bsendmsg(ua, "FileIndex=%d\n", fi->findex);
612 bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
614 print_findex(ua, fi->next);
618 /* Create a new bootstrap record */
619 static RBSR *new_bsr()
621 RBSR *bsr = (RBSR *)bmalloc(sizeof(RBSR));
622 memset(bsr, 0, sizeof(RBSR));
626 /* Free the entire BSR */
627 static void free_bsr(RBSR *bsr)
630 free_findex(bsr->fi);
632 if (bsr->VolumeName) {
633 free(bsr->VolumeName);
640 * Complete the BSR by filling in the VolumeName and
641 * VolSessionId and VolSessionTime using the JobId
643 static int complete_bsr(UAContext *ua, RBSR *bsr)
646 POOLMEM *VolumeNames;
649 VolumeNames = get_pool_memory(PM_MESSAGE);
650 memset(&jr, 0, sizeof(jr));
651 jr.JobId = bsr->JobId;
652 if (!db_get_job_record(ua->db, &jr)) {
653 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
656 bsr->VolSessionId = jr.VolSessionId;
657 bsr->VolSessionTime = jr.VolSessionTime;
658 if (!db_get_job_volume_names(ua->db, bsr->JobId, &VolumeNames)) {
659 bsendmsg(ua, _("Unable to get Job Volumes. ERR=%s\n"), db_strerror(ua->db));
660 free_pool_memory(VolumeNames);
663 bsr->VolumeName = bstrdup(VolumeNames);
664 free_pool_memory(VolumeNames);
665 return complete_bsr(ua, bsr->next);
671 * Write the bootstrap record to file
673 static int write_bsr_file(UAContext *ua, RBSR *bsr)
676 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
679 Mmsg(&fname, "%s/restore.bsr", working_directory);
680 fd = fopen(fname, "w+");
682 bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"),
683 fname, strerror(errno));
684 free_pool_memory(fname);
687 write_bsr(ua, bsr, fd);
690 // bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
691 free_pool_memory(fname);
695 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
698 if (bsr->VolumeName) {
699 fprintf(fd, "Volume=\"%s\"\n", bsr->VolumeName);
701 fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
702 fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
703 write_findex(ua, bsr->fi, fd);
704 write_bsr(ua, bsr->next, fd);
708 static void print_bsr(UAContext *ua, RBSR *bsr)
711 if (bsr->VolumeName) {
712 bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolumeName);
714 bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
715 bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
716 print_findex(ua, bsr->fi);
717 print_bsr(ua, bsr->next);
723 * Add a FileIndex to the list of BootStrap records.
724 * Here we are only dealing with JobId's and the FileIndexes
725 * associated with those JobIds.
727 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
730 RBSR_FINDEX *fi, *lfi;
733 return; /* probably a dummy directory */
736 if (!bsr->fi) { /* if no FI add one */
737 /* This is the first FileIndex item in the chain */
738 bsr->fi = new_findex();
740 bsr->fi->findex = findex;
741 bsr->fi->findex2 = findex;
744 /* Walk down list of bsrs until we find the JobId */
745 if (bsr->JobId != JobId) {
746 for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
747 if (nbsr->JobId == JobId) {
753 if (!nbsr) { /* Must add new JobId */
754 /* Add new JobId at end of chain */
755 for (nbsr=bsr; nbsr->next; nbsr=nbsr->next)
757 nbsr->next = new_bsr();
758 nbsr->next->JobId = JobId;
759 nbsr->next->fi = new_findex();
760 nbsr->next->fi->findex = findex;
761 nbsr->next->fi->findex2 = findex;
767 * At this point, bsr points to bsr containing JobId,
768 * and we are sure that there is at least one fi record.
771 /* Check if this findex is smaller than first item */
772 if (findex < fi->findex) {
773 if ((findex+1) == fi->findex) {
774 fi->findex = findex; /* extend down */
777 fi = new_findex(); /* yes, insert before first item */
779 fi->findex2 = findex;
784 /* Walk down fi chain and find where to insert insert new FileIndex */
785 for ( ; fi; fi=fi->next) {
786 if (findex == (fi->findex2 + 1)) { /* extend up */
788 fi->findex2 = findex;
789 if (fi->next && ((findex+1) == fi->next->findex)) {
791 fi->findex2 = nfi->findex2;
792 fi->next = nfi->next;
797 if (findex < fi->findex) { /* add before */
798 if ((findex+1) == fi->findex) {
806 /* Add to last place found */
809 fi->findex2 = findex;
810 fi->next = lfi->next;
816 * This callback routine is responsible for inserting the
817 * items it gets into the directory tree. For each JobId selected
818 * this routine is called once for each file. We do not allow
819 * duplicate filenames, but instead keep the info from the most
820 * recent file entered (i.e. the JobIds are assumed to be sorted)
822 static int insert_tree_handler(void *ctx, int num_fields, char **row)
824 TREE_CTX *tree = (TREE_CTX *)ctx;
826 TREE_NODE *node, *new_node;
829 strip_trailing_junk(row[1]);
835 sprintf(fname, "%s%s", row[0], row[1]);
836 if (tree->avail_node) {
837 node = tree->avail_node;
839 node = new_tree_node(tree->root, type);
840 tree->avail_node = node;
842 Dmsg2(400, "FI=%d fname=%s\n", node->FileIndex, fname);
843 new_node = insert_tree_node(fname, node, tree->root, NULL);
844 /* Note, if node already exists, save new one for next time */
845 if (new_node != node) {
846 tree->avail_node = node;
848 tree->avail_node = NULL;
850 new_node->FileIndex = atoi(row[2]);
851 new_node->JobId = atoi(row[3]);
852 new_node->type = type;
853 new_node->extract = 1; /* extract all by default */
860 * Set extract to value passed. We recursively walk
861 * down the tree setting all children if the
862 * node is a directory.
864 static void set_extract(TREE_NODE *node, int value)
868 node->extract = value;
869 if (node->type != TN_FILE) {
870 for (n=node->child; n; n=n->sibling) {
871 set_extract(n, value);
876 static int markcmd(UAContext *ua, TREE_CTX *tree)
882 if (!tree->node->child) {
885 for (node = tree->node->child; node; node=node->sibling) {
886 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
887 set_extract(node, 1);
893 static int countcmd(UAContext *ua, TREE_CTX *tree)
898 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
899 if (node->type != TN_NEWDIR) {
906 bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
910 static int findcmd(UAContext *ua, TREE_CTX *tree)
915 bsendmsg(ua, _("No file specification given.\n"));
919 for (int i=1; i < ua->argc; i++) {
920 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
921 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
922 tree_getpath(node, cwd, sizeof(cwd));
923 bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
932 static int lscmd(UAContext *ua, TREE_CTX *tree)
936 if (!tree->node->child) {
939 for (node = tree->node->child; node; node=node->sibling) {
940 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
941 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
942 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
948 extern char *getuser(uid_t uid);
949 extern char *getgroup(gid_t gid);
951 static void ls_output(char *buf, char *fname, struct stat *statp)
956 p = encode_mode(statp->st_mode, buf);
957 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
959 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
961 n = sprintf(p, "%8ld ", statp->st_size);
963 p = encode_time(statp->st_ctime, p);
973 * Like ls command, but give more detail on each file
975 static int dircmd(UAContext *ua, TREE_CTX *tree)
983 if (!tree->node->child) {
986 for (node = tree->node->child; node; node=node->sibling) {
987 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
988 tree_getpath(node, cwd, sizeof(cwd));
990 fdbr.JobId = node->JobId;
991 if (db_get_file_attributes_record(ua->db, cwd, &fdbr)) {
992 decode_stat(fdbr.LStat, &statp); /* decode stat pkt */
993 ls_output(buf, cwd, &statp);
994 bsendmsg(ua, "%s\n", buf);
1002 static int helpcmd(UAContext *ua, TREE_CTX *tree)
1007 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
1008 for (i=0; i<comsize; i++) {
1009 bsendmsg(ua, _(" %-10s %s\n"), _(commands[i].key), _(commands[i].help));
1015 static int cdcmd(UAContext *ua, TREE_CTX *tree)
1020 if (ua->argc != 2) {
1023 node = tree_cwd(ua->argk[1], tree->root, tree->node);
1025 bsendmsg(ua, _("Invalid path given.\n"));
1029 tree_getpath(tree->node, cwd, sizeof(cwd));
1030 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1034 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
1037 tree_getpath(tree->node, cwd, sizeof(cwd));
1038 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1043 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
1049 if (!tree->node->child) {
1052 for (node = tree->node->child; node; node=node->sibling) {
1053 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1054 set_extract(node, 0);
1060 static int quitcmd(UAContext *ua, TREE_CTX *tree)
1067 * Called here with each name to be added to the list. The name is
1068 * added to the list if it is not already in the list.
1070 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1072 NAME_LIST *name = (NAME_LIST *)ctx;
1074 if (name->num_ids == MAX_ID_LIST_LEN) {
1077 if (name->num_ids == name->max_ids) {
1078 if (name->max_ids == 0) {
1079 name->max_ids = 1000;
1080 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1082 name->max_ids = (name->max_ids * 3) / 2;
1083 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1086 for (int i=0; i<name->num_ids; i++) {
1087 if (strcmp(name->name[i], row[0]) == 0) {
1088 return 0; /* already in list, return */
1091 /* Add new name to list */
1092 name->name[name->num_ids++] = bstrdup(row[0]);
1098 * Print names in the list
1100 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1104 for (i=0; i < name_list->num_ids; i++) {
1105 bsendmsg(ua, "%s\n", name_list->name[i]);
1111 * Free names in the list
1113 static void free_name_list(NAME_LIST *name_list)
1117 for (i=0; i < name_list->num_ids; i++) {
1118 free(name_list->name[i]);
1120 free(name_list->name);
1121 name_list->max_ids = 0;
1122 name_list->num_ids = 0;
1125 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JobIds *ji)
1127 char name[MAX_NAME_LENGTH];
1128 STORE *store = NULL;
1130 if (name_list->num_ids > 1) {
1131 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1132 "Restore is not possible. The MediaTypes used are:\n"));
1133 print_name_list(ua, name_list);
1134 ji->store = select_storage_resource(ua);
1138 start_prompt(ua, _("The defined Storage resources are:\n"));
1140 while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
1141 if (strcmp(store->hdr.name, name_list->name[0]) == 0) {
1142 add_prompt(ua, store->hdr.name);
1146 do_prompt(ua, _("Select Storage resource"), name);
1147 ji->store = (STORE *)GetResWithName(R_STORAGE, name);
1149 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1150 "MediaType %s, needed by the Jobs you selected.\n"
1151 "You will be allowed to select a Storage device later.\n"),
1152 name_list->name[0]);