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 if (!complete_bsr(ua, bsr)) { /* find Vol, SessId, SessTime from JobIds */
229 bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
233 // print_bsr(ua, bsr);
234 write_bsr_file(ua, bsr);
236 bsendmsg(ua, _("No files selected to restore.\n"));
240 if (restore_jobs == 1) {
243 job = select_restore_job_resource(ua);
246 bsendmsg(ua, _("No Restore Job resource found!\n"));
252 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
253 job->hdr.name, ji.client->hdr.name, ji.store?ji.store->hdr.name:"",
257 "run job=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
258 job->hdr.name, ji.store?ji.store->hdr.name:"", working_directory);
262 Dmsg1(400, "Submitting: %s\n", ua->cmd);
264 parse_command_args(ua);
267 bsendmsg(ua, _("Restore command done.\n"));
272 * The first step in the restore process is for the user to
273 * select a list of JobIds from which he will subsequently
274 * select which files are to be restored.
276 static int user_select_jobids(UAContext *ua, JobIds *ji)
278 char fileset_name[MAX_NAME_LENGTH];
286 "List last 20 Jobs run",
287 "List Jobs where a given File is saved",
288 "Enter list of JobIds to select",
289 "Enter SQL list command",
290 "Select the most recent backup for a client",
294 bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
295 "to be restored. You will be presented several methods\n"
296 "of specifying the JobIds. Then you will be allowed to\n"
297 "select which files from those JobIds are to be restored.\n\n"));
300 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
301 for (int i=0; list[i]; i++) {
302 add_prompt(ua, list[i]);
305 switch (do_prompt(ua, "Select item: ", NULL)) {
308 case 0: /* list last 20 Jobs run */
309 db_list_sql_query(ua->db, uar_list_jobs, prtit, ua, 1);
312 case 1: /* list where a file is saved */
313 if (!get_cmd(ua, _("Enter Filename: "))) {
316 query = get_pool_memory(PM_MESSAGE);
317 Mmsg(&query, uar_file, ua->cmd);
318 db_list_sql_query(ua->db, query, prtit, ua, 1);
319 free_pool_memory(query);
322 case 2: /* enter a list of JobIds */
323 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
326 bstrncpy(ji->JobIds, ua->cmd, sizeof(ji->JobIds));
328 case 3: /* Enter an SQL list command */
329 if (!get_cmd(ua, _("Enter SQL list command: "))) {
332 db_list_sql_query(ua->db, ua->cmd, prtit, ua, 1);
335 case 4: /* Select the most recent backups */
336 query = get_pool_memory(PM_MESSAGE);
337 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
338 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
339 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
340 bsendmsg(ua, "%s\n", db_strerror(ua->db));
342 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
343 bsendmsg(ua, "%s\n", db_strerror(ua->db));
348 if (!(ji->client = get_client_resource(ua))) {
355 Mmsg(&query, uar_sel_fileset, ji->client->hdr.name);
356 start_prompt(ua, _("The defined FileSet resources are:\n"));
357 if (!db_sql_query(ua->db, query, fileset_handler, (void *)ua)) {
358 bsendmsg(ua, "%s\n", db_strerror(ua->db));
360 if (do_prompt(ua, _("Select FileSet resource"), fileset_name) < 0) {
361 free_pool_memory(query);
365 strcpy(fsr.FileSet, fileset_name);
366 if (!db_get_fileset_record(ua->db, &fsr)) {
367 bsendmsg(ua, "Error getting FileSet record: %s\n", db_strerror(ua->db));
368 bsendmsg(ua, _("This probably means you modified the FileSet.\n"
369 "Continuing anyway.\n"));
372 Mmsg(&query, uar_last_full, ji->client->hdr.name, fsr.FileSetId);
373 /* Find JobId of full Backup of system */
374 if (!db_sql_query(ua->db, query, NULL, NULL)) {
375 bsendmsg(ua, "%s\n", db_strerror(ua->db));
377 /* Find all Volumes used by that JobId */
378 if (!db_sql_query(ua->db, uar_full, NULL,NULL)) {
379 bsendmsg(ua, "%s\n", db_strerror(ua->db));
381 /* Note, this is needed as I don't seem to get the callback
382 * from the call just above.
384 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)ji)) {
385 bsendmsg(ua, "%s\n", db_strerror(ua->db));
387 /* Now find all Incremental Jobs */
388 Mmsg(&query, uar_inc, (uint32_t)ji->JobTDate, ji->ClientId, fsr.FileSetId);
389 if (!db_sql_query(ua->db, query, NULL, NULL)) {
390 bsendmsg(ua, "%s\n", db_strerror(ua->db));
392 free_pool_memory(query);
393 db_list_sql_query(ua->db, uar_list_temp, prtit, ua, 1);
395 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)ji)) {
396 bsendmsg(ua, "%s\n", db_strerror(ua->db));
398 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
399 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
406 if (*ji->JobIds == 0) {
407 bsendmsg(ua, _("No Jobs selected.\n"));
410 bsendmsg(ua, _("You have selected the following JobId: %s\n"), ji->JobIds);
412 memset(&jr, 0, sizeof(JOB_DBR));
415 for (p=ji->JobIds; ; ) {
416 int stat = next_jobid_from_list(&p, &JobId);
418 bsendmsg(ua, _("Invalid JobId in list.\n"));
425 if (!db_get_job_record(ua->db, &jr)) {
426 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
429 ji->TotalFiles += jr.JobFiles;
434 static int next_jobid_from_list(char **p, uint32_t *JobId)
441 for (i=0; i<(int)sizeof(jobid); i++) {
442 if (*q == ',' || *q == 0) {
449 if (jobid[0] == 0 || !is_a_number(jobid)) {
453 *JobId = strtoul(jobid, NULL, 10);
458 * Callback handler make list of JobIds
460 static int jobid_handler(void *ctx, int num_fields, char **row)
462 JobIds *ji = (JobIds *)ctx;
464 if (strlen(ji->JobIds)+strlen(row[0])+2 < sizeof(ji->JobIds)) {
465 if (ji->JobIds[0] != 0) {
466 strcat(ji->JobIds, ",");
468 strcat(ji->JobIds, row[0]);
476 * Callback handler to pickup last Full backup JobId and ClientId
478 static int last_full_handler(void *ctx, int num_fields, char **row)
480 JobIds *ji = (JobIds *)ctx;
482 ji->JobTDate = atoi(row[1]);
483 ji->ClientId = atoi(row[2]);
489 * Callback handler build fileset prompt list
491 static int fileset_handler(void *ctx, int num_fields, char **row)
493 add_prompt((UAContext *)ctx, row[1]);
497 /* Forward referenced commands */
499 static int markcmd(UAContext *ua, TREE_CTX *tree);
500 static int countcmd(UAContext *ua, TREE_CTX *tree);
501 static int findcmd(UAContext *ua, TREE_CTX *tree);
502 static int lscmd(UAContext *ua, TREE_CTX *tree);
503 static int dircmd(UAContext *ua, TREE_CTX *tree);
504 static int helpcmd(UAContext *ua, TREE_CTX *tree);
505 static int cdcmd(UAContext *ua, TREE_CTX *tree);
506 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
507 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
508 static int quitcmd(UAContext *ua, TREE_CTX *tree);
511 struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; };
512 static struct cmdstruct commands[] = {
513 { N_("mark"), markcmd, _("mark file for restoration")},
514 { N_("unmark"), unmarkcmd, _("unmark file for restoration")},
515 { N_("cd"), cdcmd, _("change current directory")},
516 { N_("pwd"), pwdcmd, _("print current working directory")},
517 { N_("ls"), lscmd, _("list current directory")},
518 { N_("dir"), dircmd, _("list current directory")},
519 { N_("count"), countcmd, _("count marked files")},
520 { N_("find"), findcmd, _("find files")},
521 { N_("done"), quitcmd, _("leave file selection mode")},
522 { N_("exit"), quitcmd, _("exit = done")},
523 { N_("help"), helpcmd, _("print help")},
524 { N_("?"), helpcmd, _("print help")},
526 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
530 * Enter a prompt mode where the user can select/deselect
531 * files to be restored. This is sort of like a mini-shell
532 * that allows "cd", "pwd", "add", "rm", ...
534 static void user_select_files(TREE_CTX *tree)
538 bsendmsg(tree->ua, _(
539 "\nYou are now entering file selection mode where you add and\n"
540 "remove files to be restored. All files are initially added.\n"
541 "Enter \"done\" to leave this mode.\n\n"));
543 * Enter interactive command handler allowing selection
544 * of individual files.
546 tree->node = (TREE_NODE *)tree->root;
547 tree_getpath(tree->node, cwd, sizeof(cwd));
548 bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
550 int found, len, stat, i;
551 if (!get_cmd(tree->ua, "$ ")) {
554 parse_command_args(tree->ua);
555 if (tree->ua->argc == 0) {
559 len = strlen(tree->ua->argk[0]);
562 for (i=0; i<(int)comsize; i++) /* search for command */
563 if (strncasecmp(tree->ua->argk[0], _(commands[i].key), len) == 0) {
564 stat = (*commands[i].func)(tree->ua, tree); /* go execute command */
569 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
579 * Create new FileIndex entry for BSR
581 static RBSR_FINDEX *new_findex()
583 RBSR_FINDEX *fi = (RBSR_FINDEX *)bmalloc(sizeof(RBSR_FINDEX));
584 memset(fi, 0, sizeof(RBSR_FINDEX));
588 /* Free all BSR FileIndex entries */
589 static void free_findex(RBSR_FINDEX *fi)
592 free_findex(fi->next);
597 static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd)
600 if (fi->findex == fi->findex2) {
601 fprintf(fd, "FileIndex=%d\n", fi->findex);
603 fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
605 write_findex(ua, fi->next, fd);
610 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
613 if (fi->findex == fi->findex2) {
614 bsendmsg(ua, "FileIndex=%d\n", fi->findex);
616 bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
618 print_findex(ua, fi->next);
622 /* Create a new bootstrap record */
623 static RBSR *new_bsr()
625 RBSR *bsr = (RBSR *)bmalloc(sizeof(RBSR));
626 memset(bsr, 0, sizeof(RBSR));
630 /* Free the entire BSR */
631 static void free_bsr(RBSR *bsr)
634 free_findex(bsr->fi);
636 if (bsr->VolumeName) {
637 free(bsr->VolumeName);
644 * Complete the BSR by filling in the VolumeName and
645 * VolSessionId and VolSessionTime using the JobId
647 static int complete_bsr(UAContext *ua, RBSR *bsr)
650 POOLMEM *VolumeNames;
653 VolumeNames = get_pool_memory(PM_MESSAGE);
654 memset(&jr, 0, sizeof(jr));
655 jr.JobId = bsr->JobId;
656 if (!db_get_job_record(ua->db, &jr)) {
657 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
660 bsr->VolSessionId = jr.VolSessionId;
661 bsr->VolSessionTime = jr.VolSessionTime;
662 if (!db_get_job_volume_names(ua->db, bsr->JobId, &VolumeNames)) {
663 bsendmsg(ua, _("Unable to get Job Volumes. ERR=%s\n"), db_strerror(ua->db));
664 free_pool_memory(VolumeNames);
667 bsr->VolumeName = bstrdup(VolumeNames);
668 free_pool_memory(VolumeNames);
669 return complete_bsr(ua, bsr->next);
675 * Write the bootstrap record to file
677 static int write_bsr_file(UAContext *ua, RBSR *bsr)
680 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
683 Mmsg(&fname, "%s/restore.bsr", working_directory);
684 fd = fopen(fname, "w+");
686 bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"),
687 fname, strerror(errno));
688 free_pool_memory(fname);
691 write_bsr(ua, bsr, fd);
694 // bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
695 free_pool_memory(fname);
699 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
702 if (bsr->VolumeName) {
703 fprintf(fd, "Volume=\"%s\"\n", bsr->VolumeName);
705 fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
706 fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
707 write_findex(ua, bsr->fi, fd);
708 write_bsr(ua, bsr->next, fd);
712 static void print_bsr(UAContext *ua, RBSR *bsr)
715 if (bsr->VolumeName) {
716 bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolumeName);
718 bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
719 bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
720 print_findex(ua, bsr->fi);
721 print_bsr(ua, bsr->next);
727 * Add a FileIndex to the list of BootStrap records.
728 * Here we are only dealing with JobId's and the FileIndexes
729 * associated with those JobIds.
731 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
734 RBSR_FINDEX *fi, *lfi;
737 return; /* probably a dummy directory */
740 if (!bsr->fi) { /* if no FI add one */
741 /* This is the first FileIndex item in the chain */
742 bsr->fi = new_findex();
744 bsr->fi->findex = findex;
745 bsr->fi->findex2 = findex;
748 /* Walk down list of bsrs until we find the JobId */
749 if (bsr->JobId != JobId) {
750 for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
751 if (nbsr->JobId == JobId) {
757 if (!nbsr) { /* Must add new JobId */
758 /* Add new JobId at end of chain */
759 for (nbsr=bsr; nbsr->next; nbsr=nbsr->next)
761 nbsr->next = new_bsr();
762 nbsr->next->JobId = JobId;
763 nbsr->next->fi = new_findex();
764 nbsr->next->fi->findex = findex;
765 nbsr->next->fi->findex2 = findex;
771 * At this point, bsr points to bsr containing JobId,
772 * and we are sure that there is at least one fi record.
775 /* Check if this findex is smaller than first item */
776 if (findex < fi->findex) {
777 if ((findex+1) == fi->findex) {
778 fi->findex = findex; /* extend down */
781 fi = new_findex(); /* yes, insert before first item */
783 fi->findex2 = findex;
788 /* Walk down fi chain and find where to insert insert new FileIndex */
789 for ( ; fi; fi=fi->next) {
790 if (findex == (fi->findex2 + 1)) { /* extend up */
792 fi->findex2 = findex;
793 if (fi->next && ((findex+1) == fi->next->findex)) {
795 fi->findex2 = nfi->findex2;
796 fi->next = nfi->next;
801 if (findex < fi->findex) { /* add before */
802 if ((findex+1) == fi->findex) {
810 /* Add to last place found */
813 fi->findex2 = findex;
814 fi->next = lfi->next;
820 * This callback routine is responsible for inserting the
821 * items it gets into the directory tree. For each JobId selected
822 * this routine is called once for each file. We do not allow
823 * duplicate filenames, but instead keep the info from the most
824 * recent file entered (i.e. the JobIds are assumed to be sorted)
826 static int insert_tree_handler(void *ctx, int num_fields, char **row)
828 TREE_CTX *tree = (TREE_CTX *)ctx;
830 TREE_NODE *node, *new_node;
833 strip_trailing_junk(row[1]);
839 sprintf(fname, "%s%s", row[0], row[1]);
840 if (tree->avail_node) {
841 node = tree->avail_node;
843 node = new_tree_node(tree->root, type);
844 tree->avail_node = node;
846 Dmsg2(400, "FI=%d fname=%s\n", node->FileIndex, fname);
847 new_node = insert_tree_node(fname, node, tree->root, NULL);
848 /* Note, if node already exists, save new one for next time */
849 if (new_node != node) {
850 tree->avail_node = node;
852 tree->avail_node = NULL;
854 new_node->FileIndex = atoi(row[2]);
855 new_node->JobId = atoi(row[3]);
856 new_node->type = type;
857 new_node->extract = 1; /* extract all by default */
864 * Set extract to value passed. We recursively walk
865 * down the tree setting all children if the
866 * node is a directory.
868 static void set_extract(TREE_NODE *node, int value)
872 node->extract = value;
873 if (node->type != TN_FILE) {
874 for (n=node->child; n; n=n->sibling) {
875 set_extract(n, value);
880 static int markcmd(UAContext *ua, TREE_CTX *tree)
886 if (!tree->node->child) {
889 for (node = tree->node->child; node; node=node->sibling) {
890 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
891 set_extract(node, 1);
897 static int countcmd(UAContext *ua, TREE_CTX *tree)
902 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
903 if (node->type != TN_NEWDIR) {
910 bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
914 static int findcmd(UAContext *ua, TREE_CTX *tree)
919 bsendmsg(ua, _("No file specification given.\n"));
923 for (int i=1; i < ua->argc; i++) {
924 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
925 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
926 tree_getpath(node, cwd, sizeof(cwd));
927 bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
936 static int lscmd(UAContext *ua, TREE_CTX *tree)
940 if (!tree->node->child) {
943 for (node = tree->node->child; node; node=node->sibling) {
944 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
945 bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
946 (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
952 extern char *getuser(uid_t uid);
953 extern char *getgroup(gid_t gid);
955 static void ls_output(char *buf, char *fname, struct stat *statp)
961 p = encode_mode(statp->st_mode, buf);
962 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
964 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
966 n = sprintf(p, "%8.8s ", edit_uint64(statp->st_size, ec1));
968 p = encode_time(statp->st_ctime, p);
978 * Like ls command, but give more detail on each file
980 static int dircmd(UAContext *ua, TREE_CTX *tree)
988 if (!tree->node->child) {
991 for (node = tree->node->child; node; node=node->sibling) {
992 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
993 tree_getpath(node, cwd, sizeof(cwd));
995 fdbr.JobId = node->JobId;
996 if (db_get_file_attributes_record(ua->db, cwd, &fdbr)) {
997 decode_stat(fdbr.LStat, &statp); /* decode stat pkt */
998 ls_output(buf, cwd, &statp);
999 bsendmsg(ua, "%s\n", buf);
1007 static int helpcmd(UAContext *ua, TREE_CTX *tree)
1012 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
1013 for (i=0; i<comsize; i++) {
1014 bsendmsg(ua, _(" %-10s %s\n"), _(commands[i].key), _(commands[i].help));
1020 static int cdcmd(UAContext *ua, TREE_CTX *tree)
1025 if (ua->argc != 2) {
1028 node = tree_cwd(ua->argk[1], tree->root, tree->node);
1030 bsendmsg(ua, _("Invalid path given.\n"));
1034 tree_getpath(tree->node, cwd, sizeof(cwd));
1035 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1039 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
1042 tree_getpath(tree->node, cwd, sizeof(cwd));
1043 bsendmsg(ua, _("cwd is: %s\n"), cwd);
1048 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
1054 if (!tree->node->child) {
1057 for (node = tree->node->child; node; node=node->sibling) {
1058 if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1059 set_extract(node, 0);
1065 static int quitcmd(UAContext *ua, TREE_CTX *tree)
1072 * Called here with each name to be added to the list. The name is
1073 * added to the list if it is not already in the list.
1075 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1077 NAME_LIST *name = (NAME_LIST *)ctx;
1079 if (name->num_ids == MAX_ID_LIST_LEN) {
1082 if (name->num_ids == name->max_ids) {
1083 if (name->max_ids == 0) {
1084 name->max_ids = 1000;
1085 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1087 name->max_ids = (name->max_ids * 3) / 2;
1088 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1091 for (int i=0; i<name->num_ids; i++) {
1092 if (strcmp(name->name[i], row[0]) == 0) {
1093 return 0; /* already in list, return */
1096 /* Add new name to list */
1097 name->name[name->num_ids++] = bstrdup(row[0]);
1103 * Print names in the list
1105 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1109 for (i=0; i < name_list->num_ids; i++) {
1110 bsendmsg(ua, "%s\n", name_list->name[i]);
1116 * Free names in the list
1118 static void free_name_list(NAME_LIST *name_list)
1122 for (i=0; i < name_list->num_ids; i++) {
1123 free(name_list->name[i]);
1125 if (name_list->name) {
1126 free(name_list->name);
1128 name_list->max_ids = 0;
1129 name_list->num_ids = 0;
1132 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JobIds *ji)
1134 char name[MAX_NAME_LENGTH];
1135 STORE *store = NULL;
1137 if (name_list->num_ids > 1) {
1138 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1139 "Restore is not possible. The MediaTypes used are:\n"));
1140 print_name_list(ua, name_list);
1141 ji->store = select_storage_resource(ua);
1145 if (name_list->num_ids == 0) {
1146 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1147 ji->store = select_storage_resource(ua);
1151 start_prompt(ua, _("The defined Storage resources are:\n"));
1153 while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
1154 if (strcmp(store->media_type, name_list->name[0]) == 0) {
1155 add_prompt(ua, store->hdr.name);
1159 do_prompt(ua, _("Select Storage resource"), name);
1160 ji->store = (STORE *)GetResWithName(R_STORAGE, name);
1162 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1163 "MediaType %s, needed by the Jobs you selected.\n"
1164 "You will be allowed to select a Storage device later.\n"),
1165 name_list->name[0]);