3 * Bacula Director -- User Agent Database restore Command
4 * Creates a bootstrap file for restoring files and
5 * starts the restore job.
7 * Tree handling routines split into ua_tree.c July MMIII.
8 * BSR (bootstrap record) handling routines split into
11 * Kern Sibbald, July MMII
17 Copyright (C) 2002-2005 Kern Sibbald
19 This program is free software; you can redistribute it and/or
20 modify it under the terms of the GNU General Public License as
21 published by the Free Software Foundation; either version 2 of
22 the License, or (at your option) any later version.
24 This program is distributed in the hope that it will be useful,
25 but WITHOUT ANY WARRANTY; without even the implied warranty of
26 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 General Public License for more details.
29 You should have received a copy of the GNU General Public
30 License along with this program; if not, write to the Free
31 Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
40 /* Imported functions */
41 extern void print_bsr(UAContext *ua, RBSR *bsr);
43 /* Imported variables */
44 extern char *uar_list_jobs, *uar_file, *uar_sel_files;
45 extern char *uar_del_temp, *uar_del_temp1, *uar_create_temp;
46 extern char *uar_create_temp1, *uar_last_full, *uar_full;
47 extern char *uar_inc, *uar_list_temp, *uar_sel_jobid_temp;
48 extern char *uar_sel_all_temp1, *uar_sel_fileset, *uar_mediatype;
49 extern char *uar_jobid_fileindex, *uar_dif, *uar_sel_all_temp;
50 extern char *uar_count_files, *uar_jobids_fileindex;
51 extern char *uar_jobid_fileindex_from_dir;
55 char **name; /* list of names */
56 int num_ids; /* ids stored */
57 int max_ids; /* size of array */
58 int num_del; /* number deleted */
59 int tot_ids; /* total to process */
63 /* Main structure for obtaining JobIds or Files to be restored */
68 char ClientName[MAX_NAME_LENGTH];
70 POOLMEM *JobIds; /* User entered string of JobIds */
75 uint32_t selected_files;
78 POOLMEM *fname; /* filename only */
79 POOLMEM *path; /* path only */
81 int fnl; /* filename length */
82 int pnl; /* path length */
84 bool all; /* mark all as default */
89 #define MAX_ID_LIST_LEN 1000000
92 /* Forward referenced functions */
93 static int last_full_handler(void *ctx, int num_fields, char **row);
94 static int jobid_handler(void *ctx, int num_fields, char **row);
95 static int get_next_jobid_from_list(char **p, uint32_t *JobId);
96 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx);
97 static int fileset_handler(void *ctx, int num_fields, char **row);
98 static void print_name_list(UAContext *ua, NAME_LIST *name_list);
99 static int unique_name_list_handler(void *ctx, int num_fields, char **row);
100 static void free_name_list(NAME_LIST *name_list);
101 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, RESTORE_CTX *rx);
102 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date);
103 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx);
104 static void free_rx(RESTORE_CTX *rx);
105 static void split_path_and_filename(RESTORE_CTX *rx, char *fname);
106 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row);
107 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
109 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
111 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir);
112 static int get_client_name(UAContext *ua, RESTORE_CTX *rx);
113 static int get_date(UAContext *ua, char *date, int date_len);
114 static int count_handler(void *ctx, int num_fields, char **row);
120 int restore_cmd(UAContext *ua, const char *cmd)
122 RESTORE_CTX rx; /* restore context */
126 memset(&rx, 0, sizeof(rx));
127 rx.path = get_pool_memory(PM_FNAME);
128 rx.fname = get_pool_memory(PM_FNAME);
129 rx.JobIds = get_pool_memory(PM_FNAME);
130 rx.query = get_pool_memory(PM_FNAME);
133 i = find_arg_with_value(ua, "where");
135 rx.where = ua->argv[i];
142 /* Ensure there is at least one Restore Job */
144 foreach_res(job, R_JOB) {
145 if (job->JobType == JT_RESTORE) {
146 if (!rx.restore_job) {
147 rx.restore_job = job;
153 if (!rx.restore_jobs) {
155 "No Restore Job Resource found in bacula-dir.conf.\n"
156 "You must create at least one before running this command.\n"));
161 * Request user to select JobIds or files by various different methods
162 * last 20 jobs, where File saved, most recent backup, ...
163 * In the end, a list of files are pumped into
166 switch (user_select_jobids_or_files(ua, &rx)) {
169 case 1: /* selected by jobid */
170 if (!build_directory_tree(ua, &rx)) {
171 bsendmsg(ua, _("Restore not done.\n"));
175 case 2: /* selected by filename, no tree needed */
180 if (!complete_bsr(ua, rx.bsr)) { /* find Vol, SessId, SessTime from JobIds */
181 bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
184 if (!(rx.selected_files = write_bsr_file(ua, rx.bsr))) {
185 bsendmsg(ua, _("No files selected to be restored.\n"));
188 bsendmsg(ua, _("\n%u file%s selected to be restored.\n\n"), rx.selected_files,
189 rx.selected_files==1?"":"s");
191 bsendmsg(ua, _("No files selected to be restored.\n"));
195 if (rx.restore_jobs == 1) {
196 job = rx.restore_job;
198 job = select_restore_job_resource(ua);
204 get_client_name(ua, &rx);
205 if (!rx.ClientName) {
206 bsendmsg(ua, _("No Restore Job resource found!\n"));
210 /* Build run command */
213 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\""
214 " where=\"%s\" files=%d catalog=\"%s\"",
215 job->hdr.name, rx.ClientName, rx.store?rx.store->hdr.name:"",
216 working_directory, rx.where, rx.selected_files, ua->catalog->hdr.name);
219 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\""
220 " files=%d catalog=\"%s\"",
221 job->hdr.name, rx.ClientName, rx.store?rx.store->hdr.name:"",
222 working_directory, rx.selected_files, ua->catalog->hdr.name);
224 if (find_arg(ua, _("yes")) > 0) {
225 pm_strcat(ua->cmd, " yes"); /* pass it on to the run command */
227 Dmsg1(400, "Submitting: %s\n", ua->cmd);
229 run_cmd(ua, ua->cmd);
239 static void free_rx(RESTORE_CTX *rx)
244 free_pool_memory(rx->JobIds);
248 free_pool_memory(rx->fname);
252 free_pool_memory(rx->path);
256 free_pool_memory(rx->query);
259 free_name_list(&rx->name_list);
262 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
264 /* If no client name specified yet, get it now */
265 if (!rx->ClientName[0]) {
267 /* try command line argument */
268 int i = find_arg_with_value(ua, _("client"));
270 bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
273 memset(&cr, 0, sizeof(cr));
274 if (!get_client_dbr(ua, &cr)) {
277 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
283 * The first step in the restore process is for the user to
284 * select a list of JobIds from which he will subsequently
285 * select which files are to be restored.
287 * Returns: 2 if filename list made
288 * 1 if jobid list made
291 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
294 char date[MAX_TIME_LENGTH];
295 bool have_date = false;
300 const char *list[] = {
301 "List last 20 Jobs run",
302 "List Jobs where a given File is saved",
303 "Enter list of comma separated JobIds to select",
304 "Enter SQL list command",
305 "Select the most recent backup for a client",
306 "Select backup for a client before a specified time",
307 "Enter a list of files to restore",
308 "Enter a list of files to restore before a specified time",
309 "Find the JobIds of the most recent backup for a client",
310 "Find the JobIds for a backup for a client before a specified time",
311 "Enter a list of directories to restore for given JobIds",
316 /* These keywords are handled in a for loop */
326 /* The keyword below are handled by individual arg lookups */
338 for (i=1; i<ua->argc; i++) { /* loop through arguments */
339 bool found_kw = false;
340 for (j=0; kw[j]; j++) { /* loop through keywords */
341 if (strcasecmp(kw[j], ua->argk[i]) == 0) {
347 bsendmsg(ua, _("Unknown keyword: %s\n"), ua->argk[i]);
350 /* Found keyword in kw[] list, process it */
353 if (*rx->JobIds != 0) {
354 pm_strcat(rx->JobIds, ",");
356 pm_strcat(rx->JobIds, ua->argv[i]);
359 case 1: /* current */
360 bstrutime(date, sizeof(date), time(NULL));
364 if (str_to_utime(ua->argv[i]) == 0) {
365 bsendmsg(ua, _("Improper date format: %s\n"), ua->argv[i]);
368 bstrncpy(date, ua->argv[i], sizeof(date));
374 bstrutime(date, sizeof(date), time(NULL));
376 if (!get_client_name(ua, rx)) {
379 pm_strcpy(ua->cmd, ua->argv[i]);
380 insert_one_file_or_dir(ua, rx, date, j==4);
381 if (rx->name_list.num_ids) {
382 /* Check MediaType and select storage that corresponds */
383 get_storage_from_mediatype(ua, &rx->name_list, rx);
389 bstrutime(date, sizeof(date), time(NULL));
391 if (!select_backups_before_date(ua, rx, date)) {
396 case 6: /* pool specified */
397 rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
399 bsendmsg(ua, _("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
402 if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
404 bsendmsg(ua, _("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
408 case 7: /* all specified */
412 * All keywords 7 or greater are ignored or handled by a select prompt
418 if (rx->name_list.num_ids) {
419 return 2; /* filename list made */
423 bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
424 "to be restored. You will be presented several methods\n"
425 "of specifying the JobIds. Then you will be allowed to\n"
426 "select which files from those JobIds are to be restored.\n\n"));
429 /* If choice not already made above, prompt */
435 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
436 for (int i=0; list[i]; i++) {
437 add_prompt(ua, list[i]);
440 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
443 case 0: /* list last 20 Jobs run */
444 gui_save = ua->jcr->gui;
446 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
447 ua->jcr->gui = gui_save;
450 case 1: /* list where a file is saved */
451 if (!get_client_name(ua, rx)) {
454 if (!get_cmd(ua, _("Enter Filename (no path):"))) {
457 len = strlen(ua->cmd);
458 fname = (char *)malloc(len * 2 + 1);
459 db_escape_string(fname, ua->cmd, len);
460 Mmsg(rx->query, uar_file, rx->ClientName, fname);
462 gui_save = ua->jcr->gui;
464 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
465 ua->jcr->gui = gui_save;
468 case 2: /* enter a list of JobIds */
469 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
472 pm_strcpy(rx->JobIds, ua->cmd);
474 case 3: /* Enter an SQL list command */
475 if (!get_cmd(ua, _("Enter SQL list command: "))) {
478 gui_save = ua->jcr->gui;
480 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
481 ua->jcr->gui = gui_save;
484 case 4: /* Select the most recent backups */
485 bstrutime(date, sizeof(date), time(NULL));
486 if (!select_backups_before_date(ua, rx, date)) {
490 case 5: /* select backup at specified time */
491 if (!get_date(ua, date, sizeof(date))) {
494 if (!select_backups_before_date(ua, rx, date)) {
498 case 6: /* Enter files */
499 bstrutime(date, sizeof(date), time(NULL));
500 if (!get_client_name(ua, rx)) {
503 bsendmsg(ua, _("Enter file names with paths, or < to enter a filename\n"
504 "containg a list of file names with paths, and terminate\n"
505 "them with a blank line.\n"));
507 if (!get_cmd(ua, _("Enter full filename: "))) {
510 len = strlen(ua->cmd);
514 insert_one_file_or_dir(ua, rx, date, false);
516 /* Check MediaType and select storage that corresponds */
517 if (rx->name_list.num_ids) {
518 get_storage_from_mediatype(ua, &rx->name_list, rx);
521 case 7: /* enter files backed up before specified time */
522 if (!get_date(ua, date, sizeof(date))) {
525 if (!get_client_name(ua, rx)) {
528 bsendmsg(ua, _("Enter file names with paths, or < to enter a filename\n"
529 "containg a list of file names with paths, and terminate\n"
530 "them with a blank line.\n"));
532 if (!get_cmd(ua, _("Enter full filename: "))) {
535 len = strlen(ua->cmd);
539 insert_one_file_or_dir(ua, rx, date, false);
541 /* Check MediaType and select storage that corresponds */
542 if (rx->name_list.num_ids) {
543 get_storage_from_mediatype(ua, &rx->name_list, rx);
547 case 8: /* Find JobIds for current backup */
548 bstrutime(date, sizeof(date), time(NULL));
549 if (!select_backups_before_date(ua, rx, date)) {
555 case 9: /* Find JobIds for give date */
556 if (!get_date(ua, date, sizeof(date))) {
559 if (!select_backups_before_date(ua, rx, date)) {
565 case 10: /* Enter directories */
566 if (*rx->JobIds != 0) {
567 bsendmsg(ua, _("You have already seleted the following JobIds: %s\n"),
569 } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
570 if (*rx->JobIds != 0 && *ua->cmd) {
571 pm_strcat(rx->JobIds, ",");
573 pm_strcat(rx->JobIds, ua->cmd);
575 if (*rx->JobIds == 0 || *rx->JobIds == '.') {
576 return 0; /* nothing entered, return */
578 bstrutime(date, sizeof(date), time(NULL));
579 if (!get_client_name(ua, rx)) {
582 bsendmsg(ua, _("Enter directory names with a trailing /, or < to enter a filename\n"
583 "containg a list of directories and terminate\n"
584 "them with a blank line.\n"));
586 if (!get_cmd(ua, _("Enter directory name: "))) {
589 len = strlen(ua->cmd);
593 if (ua->cmd[len-1] != '/') {
594 strcat(ua->cmd, "/");
596 insert_one_file_or_dir(ua, rx, date, true);
598 /* Check MediaType and select storage that corresponds */
599 if (rx->name_list.num_ids) {
600 get_storage_from_mediatype(ua, &rx->name_list, rx);
604 case 11: /* Cancel or quit */
609 if (*rx->JobIds == 0) {
610 bsendmsg(ua, _("No Jobs selected.\n"));
613 bsendmsg(ua, _("You have selected the following JobId%s: %s\n"),
614 strchr(rx->JobIds,',')?"s":"",rx->JobIds);
616 memset(&jr, 0, sizeof(JOB_DBR));
619 for (p=rx->JobIds; ; ) {
620 int stat = get_next_jobid_from_list(&p, &JobId);
622 bsendmsg(ua, _("Invalid JobId in list.\n"));
628 if (jr.JobId == JobId) {
629 continue; /* duplicate of last JobId */
632 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
634 bsendmsg(ua, _("Unable to get Job record for JobId=%s: ERR=%s\n"),
635 edit_int64(JobId, ed1), db_strerror(ua->db));
638 if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
639 bsendmsg(ua, _("No authorization. Job \"%s\" not selected.\n"),
643 rx->TotalFiles += jr.JobFiles;
651 static int get_date(UAContext *ua, char *date, int date_len)
653 bsendmsg(ua, _("The restored files will the most current backup\n"
654 "BEFORE the date you specify below.\n\n"));
656 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
659 if (str_to_utime(ua->cmd) != 0) {
662 bsendmsg(ua, _("Improper date format.\n"));
664 bstrncpy(date, ua->cmd, date_len);
669 * Insert a single file, or read a list of files from a file
671 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
681 if ((ffd = fopen(p, "r")) == NULL) {
683 bsendmsg(ua, _("Cannot open file %s: ERR=%s\n"),
687 while (fgets(file, sizeof(file), ffd)) {
690 if (!insert_dir_into_findex_list(ua, rx, file, date)) {
691 bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
694 if (!insert_file_into_findex_list(ua, rx, file, date)) {
695 bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
703 insert_dir_into_findex_list(ua, rx, ua->cmd, date);
705 insert_file_into_findex_list(ua, rx, ua->cmd, date);
712 * For a given file (path+filename), split into path and file, then
713 * lookup the most recent backup in the catalog to get the JobId
714 * and FileIndex, then insert them into the findex list.
716 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
721 strip_trailing_junk(file);
722 split_path_and_filename(rx, file);
723 if (*rx->JobIds == 0) {
724 Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname,
727 Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
728 rx->path, rx->fname, rx->ClientName);
731 /* Find and insert jobid and File Index */
732 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
733 bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
734 rx->query, db_strerror(ua->db));
737 bsendmsg(ua, _("No database record found for: %s\n"), file);
741 * Find the MediaTypes for this JobId and add to the name_list
743 Mmsg(rx->query, uar_mediatype, edit_int64(rx->JobId, ed1));
744 if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
745 bsendmsg(ua, "%s", db_strerror(ua->db));
752 * For a given path lookup the most recent backup in the catalog
753 * to get the JobId and FileIndexes of all files in that directory.
755 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
760 strip_trailing_junk(dir);
761 if (*rx->JobIds == 0) {
762 bsendmsg(ua, _("No JobId specified cannot continue.\n"));
765 Mmsg(rx->query, uar_jobid_fileindex_from_dir, rx->JobIds,
766 dir, rx->ClientName);
769 /* Find and insert jobid and File Index */
770 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
771 bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
772 rx->query, db_strerror(ua->db));
775 bsendmsg(ua, _("No database record found for: %s\n"), dir);
779 * Find the MediaTypes for this JobId and add to the name_list
781 Mmsg(rx->query, uar_mediatype, edit_int64(rx->JobId, ed1));
782 if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
783 bsendmsg(ua, "%s", db_strerror(ua->db));
790 static void split_path_and_filename(RESTORE_CTX *rx, char *name)
794 /* Find path without the filename.
795 * I.e. everything after the last / is a "filename".
796 * OK, maybe it is a directory name, but we treat it like
797 * a filename. If we don't find a / then the whole name
798 * must be a path name (e.g. c:).
800 for (p=f=name; *p; p++) {
802 f = p; /* set pos of last slash */
805 if (*f == '/') { /* did we find a slash? */
806 f++; /* yes, point to filename */
807 } else { /* no, whole thing must be path name */
811 /* If filename doesn't exist (i.e. root directory), we
812 * simply create a blank name consisting of a single
813 * space. This makes handling zero length filenames
818 rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
819 memcpy(rx->fname, f, rx->fnl); /* copy filename */
820 rx->fname[rx->fnl] = 0;
828 rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
829 memcpy(rx->path, name, rx->pnl);
830 rx->path[rx->pnl] = 0;
836 Dmsg2(100, "sllit path=%s file=%s\n", rx->path, rx->fname);
839 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
842 JobId_t JobId, last_JobId;
847 memset(&tree, 0, sizeof(TREE_CTX));
849 * Build the directory tree containing JobIds user selected
851 tree.root = new_tree(rx->TotalFiles);
856 * For display purposes, the same JobId, with different volumes may
857 * appear more than once, however, we only insert it once.
861 tree.FileEstimate = 0;
862 if (get_next_jobid_from_list(&p, &JobId) > 0) {
863 /* Use first JobId as estimate of the number of files to restore */
864 Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
865 if (!db_sql_query(ua->db, rx->query, count_handler, (void *)rx)) {
866 bsendmsg(ua, "%s\n", db_strerror(ua->db));
869 /* Add about 25% more than this job for over estimate */
870 tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
871 tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
874 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
877 if (JobId == last_JobId) {
878 continue; /* eliminate duplicate JobIds */
881 bsendmsg(ua, _("\nBuilding directory tree for JobId %s ... "),
882 edit_int64(JobId, ed1));
885 * Find files for this JobId and insert them in the tree
887 Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
888 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
889 bsendmsg(ua, "%s", db_strerror(ua->db));
892 * Find the MediaTypes for this JobId and add to the name_list
894 Mmsg(rx->query, uar_mediatype, edit_int64(JobId, ed1));
895 if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
896 bsendmsg(ua, "%s", db_strerror(ua->db));
899 if (tree.FileCount == 0) {
900 bsendmsg(ua, "\nThere were no files inserted into the tree, so file selection\n"
901 "is not possible.Most likely your retention policy pruned the files\n");
902 if (!get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
906 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
907 if (JobId == last_JobId) {
908 continue; /* eliminate duplicate JobIds */
910 add_findex_all(rx->bsr, JobId);
916 bsendmsg(ua, "\n%d Job%s, %s files inserted into the tree%s.\n",
917 items, items==1?"":"s", edit_uint64_with_commas(tree.FileCount, ec1),
918 tree.all?" and marked for extraction":"");
920 /* Check MediaType and select storage that corresponds */
921 get_storage_from_mediatype(ua, &rx->name_list, rx);
923 if (find_arg(ua, _("done")) < 0) {
924 /* Let the user interact in selecting which files to restore */
925 OK = user_select_files_from_tree(&tree);
929 * Walk down through the tree finding all files marked to be
930 * extracted making a bootstrap file.
933 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
934 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
935 if (node->extract || node->extract_dir) {
936 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
937 add_findex(rx->bsr, node->JobId, node->FileIndex);
938 if (node->extract && node->type != TN_NEWDIR) {
939 rx->selected_files++; /* count only saved files */
946 free_tree(tree.root); /* free the directory tree */
952 * This routine is used to get the current backup or a backup
953 * before the specified date.
955 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
960 char fileset_name[MAX_NAME_LENGTH];
961 char ed1[50], ed2[50];
962 char pool_select[MAX_NAME_LENGTH];
966 /* Create temp tables */
967 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
968 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
969 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
970 bsendmsg(ua, "%s\n", db_strerror(ua->db));
972 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
973 bsendmsg(ua, "%s\n", db_strerror(ua->db));
976 * Select Client from the Catalog
978 memset(&cr, 0, sizeof(cr));
979 if (!get_client_dbr(ua, &cr)) {
982 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
987 memset(&fsr, 0, sizeof(fsr));
988 i = find_arg_with_value(ua, "FileSet");
990 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
991 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
992 bsendmsg(ua, _("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
993 db_strerror(ua->db));
997 if (i < 0) { /* fileset not found */
998 edit_int64(cr.ClientId, ed1);
999 Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1000 start_prompt(ua, _("The defined FileSet resources are:\n"));
1001 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1002 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1004 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1005 fileset_name, sizeof(fileset_name)) < 0) {
1009 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1010 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1011 bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1012 bsendmsg(ua, _("This probably means you modified the FileSet.\n"
1013 "Continuing anyway.\n"));
1017 /* If Pool specified, add PoolId specification */
1021 memset(&pr, 0, sizeof(pr));
1022 bstrncpy(pr.Name, rx->pool->hdr.name, sizeof(pr.Name));
1023 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1024 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
1025 edit_int64(pr.PoolId, ed1));
1027 bsendmsg(ua, _("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1031 /* Find JobId of last Full backup for this client, fileset */
1032 edit_int64(cr.ClientId, ed1);
1033 Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1035 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1036 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1040 /* Find all Volumes used by that JobId */
1041 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1042 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1045 /* Note, this is needed because I don't seem to get the callback
1046 * from the call just above.
1049 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1050 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1052 if (rx->JobTDate == 0) {
1053 bsendmsg(ua, _("No Full backup before %s found.\n"), date);
1057 /* Now find most recent Differental Job after Full save, if any */
1058 Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1059 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1060 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1061 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1063 /* Now update JobTDate to lock onto Differental, if any */
1065 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1066 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1068 if (rx->JobTDate == 0) {
1069 bsendmsg(ua, _("No Full backup before %s found.\n"), date);
1073 /* Now find all Incremental Jobs after Full/dif save */
1074 Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1075 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1076 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1077 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1080 /* Get the JobIds from that list */
1082 rx->last_jobid[0] = 0;
1083 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1084 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1087 if (rx->JobIds[0] != 0) {
1088 /* Display a list of Jobs selected for this restore */
1089 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
1092 bsendmsg(ua, _("No jobs found.\n"));
1096 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1097 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1102 /* Return next JobId from comma separated list */
1103 static int get_next_jobid_from_list(char **p, uint32_t *JobId)
1109 for (int i=0; i<(int)sizeof(jobid); i++) {
1112 } else if (*q == ',') {
1119 if (jobid[0] == 0) {
1121 } else if (!is_a_number(jobid)) {
1122 return -1; /* error */
1125 *JobId = str_to_int64(jobid);
1129 static int count_handler(void *ctx, int num_fields, char **row)
1131 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1132 rx->JobId = str_to_int64(row[0]);
1138 * Callback handler to get JobId and FileIndex for files
1139 * can insert more than one depending on the caller.
1141 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1143 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1144 rx->JobId = str_to_int64(row[0]);
1145 add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1147 rx->selected_files++;
1152 * Callback handler make list of JobIds
1154 static int jobid_handler(void *ctx, int num_fields, char **row)
1156 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1158 if (strcmp(rx->last_jobid, row[0]) == 0) {
1159 return 0; /* duplicate id */
1161 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1162 if (rx->JobIds[0] != 0) {
1163 pm_strcat(rx->JobIds, ",");
1165 pm_strcat(rx->JobIds, row[0]);
1171 * Callback handler to pickup last Full backup JobTDate
1173 static int last_full_handler(void *ctx, int num_fields, char **row)
1175 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1177 rx->JobTDate = str_to_int64(row[1]);
1182 * Callback handler build FileSet name prompt list
1184 static int fileset_handler(void *ctx, int num_fields, char **row)
1186 /* row[0] = FileSet (name) */
1188 add_prompt((UAContext *)ctx, row[0]);
1194 * Called here with each name to be added to the list. The name is
1195 * added to the list if it is not already in the list.
1197 * Used to make unique list of FileSets and MediaTypes
1199 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1201 NAME_LIST *name = (NAME_LIST *)ctx;
1203 if (name->num_ids == MAX_ID_LIST_LEN) {
1206 if (name->num_ids == name->max_ids) {
1207 if (name->max_ids == 0) {
1208 name->max_ids = 1000;
1209 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1211 name->max_ids = (name->max_ids * 3) / 2;
1212 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1215 for (int i=0; i<name->num_ids; i++) {
1216 if (strcmp(name->name[i], row[0]) == 0) {
1217 return 0; /* already in list, return */
1220 /* Add new name to list */
1221 name->name[name->num_ids++] = bstrdup(row[0]);
1227 * Print names in the list
1229 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1231 for (int i=0; i < name_list->num_ids; i++) {
1232 bsendmsg(ua, "%s\n", name_list->name[i]);
1238 * Free names in the list
1240 static void free_name_list(NAME_LIST *name_list)
1242 for (int i=0; i < name_list->num_ids; i++) {
1243 free(name_list->name[i]);
1245 if (name_list->name) {
1246 free(name_list->name);
1247 name_list->name = NULL;
1249 name_list->max_ids = 0;
1250 name_list->num_ids = 0;
1253 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, RESTORE_CTX *rx)
1257 if (name_list->num_ids > 1) {
1258 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1259 "Restore is not possible. The MediaTypes used are:\n"));
1260 print_name_list(ua, name_list);
1261 rx->store = select_storage_resource(ua);
1265 if (name_list->num_ids == 0) {
1266 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1267 rx->store = select_storage_resource(ua);
1274 * We have a single MediaType, look it up in our Storage resource
1277 foreach_res(store, R_STORAGE) {
1278 if (strcmp(name_list->name[0], store->media_type) == 0) {
1279 if (acl_access_ok(ua, Storage_ACL, store->hdr.name)) {
1288 /* Check if an explicit storage resource is given */
1290 int i = find_arg_with_value(ua, "storage");
1292 store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1293 if (store && !acl_access_ok(ua, Storage_ACL, store->hdr.name)) {
1297 if (store && (store != rx->store)) {
1298 bsendmsg(ua, _("Warning default storage overridden by %s on command line.\n"),
1305 /* Take command line arg, or ask user if none */
1306 rx->store = get_storage_resource(ua, false /* don't use default */);
1309 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1310 "MediaType \"%s\", needed by the Jobs you selected.\n"
1311 "You will be allowed to select a Storage device later.\n"),
1312 name_list->name[0]);