2 Bacula® - The Network Backup Solution
4 Copyright (C) 2002-2007 Free Software Foundation Europe e.V.
6 The main author of Bacula is Kern Sibbald, with contributions from
7 many others, a complete list can be found in the file AUTHORS.
8 This program is Free Software; you can redistribute it and/or
9 modify it under the terms of version two of the GNU General Public
10 License as published by the Free Software Foundation plus additions
11 that are listed in the file LICENSE.
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23 Bacula® is a registered trademark of John Walker.
24 The licensor of Bacula is the Free Software Foundation Europe
25 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26 Switzerland, email:ftf@fsfeurope.org.
30 * Bacula Director -- User Agent Database restore Command
31 * Creates a bootstrap file for restoring files and
32 * starts the restore job.
34 * Tree handling routines split into ua_tree.c July MMIII.
35 * BSR (bootstrap record) handling routines split into
38 * Kern Sibbald, July MMII
47 /* Imported functions */
48 extern void print_bsr(UAContext *ua, RBSR *bsr);
52 /* Forward referenced functions */
53 static int last_full_handler(void *ctx, int num_fields, char **row);
54 static int jobid_handler(void *ctx, int num_fields, char **row);
55 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx);
56 static int fileset_handler(void *ctx, int num_fields, char **row);
57 static void free_name_list(NAME_LIST *name_list);
58 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date);
59 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx);
60 static void free_rx(RESTORE_CTX *rx);
61 static void split_path_and_filename(RESTORE_CTX *rx, char *fname);
62 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row);
63 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
65 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
67 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir);
68 static int get_client_name(UAContext *ua, RESTORE_CTX *rx);
69 static int get_date(UAContext *ua, char *date, int date_len);
70 static int restore_count_handler(void *ctx, int num_fields, char **row);
71 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table);
77 int restore_cmd(UAContext *ua, const char *cmd)
79 RESTORE_CTX rx; /* restore context */
83 char *escaped_bsr_name = NULL;
84 char *escaped_where_name = NULL;
85 bool where_use_regexp = false;
86 char *strip_prefix, *add_prefix, *add_suffix, *regexp;
87 strip_prefix = add_prefix = add_suffix = regexp = NULL;
89 memset(&rx, 0, sizeof(rx));
90 rx.path = get_pool_memory(PM_FNAME);
91 rx.fname = get_pool_memory(PM_FNAME);
92 rx.JobIds = get_pool_memory(PM_FNAME);
93 rx.query = get_pool_memory(PM_FNAME);
96 i = find_arg_with_value(ua, "where");
98 rx.where = ua->argv[i];
101 i = find_arg_with_value(ua, "strip_prefix");
103 strip_prefix = ua->argv[i];
106 i = find_arg_with_value(ua, "add_prefix");
108 add_prefix = ua->argv[i];
111 i = find_arg_with_value(ua, "add_suffix");
113 add_suffix = ua->argv[i];
116 i = find_arg(ua, "where_use_regexp");
118 where_use_regexp = true;
121 i = find_arg_with_value(ua, "regexwhere");
123 where_use_regexp = true;
124 rx.where = ua->argv[i];
127 if (strip_prefix || add_suffix || add_prefix) {
128 int len = bregexp_get_build_where_size(strip_prefix, add_prefix, add_suffix);
129 regexp = (char *) bmalloc (len * sizeof(char));
131 bregexp_build_where(regexp, len, strip_prefix, add_prefix, add_suffix);
132 where_use_regexp = true;
138 if (!acl_access_ok(ua, Where_ACL, rx.where)) {
139 ua->error_msg(_("\"where\" specification not authorized.\n"));
144 if (!open_client_db(ua)) {
148 /* Ensure there is at least one Restore Job */
150 foreach_res(job, R_JOB) {
151 if (job->JobType == JT_RESTORE) {
152 if (!rx.restore_job) {
153 rx.restore_job = job;
159 if (!rx.restore_jobs) {
161 "No Restore Job Resource found in bacula-dir.conf.\n"
162 "You must create at least one before running this command.\n"));
167 * Request user to select JobIds or files by various different methods
168 * last 20 jobs, where File saved, most recent backup, ...
169 * In the end, a list of files are pumped into
172 switch (user_select_jobids_or_files(ua, &rx)) {
175 case 1: /* selected by jobid */
176 if (!build_directory_tree(ua, &rx)) {
177 ua->send_msg(_("Restore not done.\n"));
181 case 2: /* selected by filename, no tree needed */
186 uint32_t selected_files;
188 if (!complete_bsr(ua, rx.bsr)) { /* find Vol, SessId, SessTime from JobIds */
189 ua->error_msg(_("Unable to construct a valid BSR. Cannot continue.\n"));
192 if (!(selected_files = write_bsr_file(ua, rx))) {
193 ua->warning_msg(_("No files selected to be restored.\n"));
196 /* If no count of files, use bsr generated value (often wrong) */
197 if (rx.selected_files == 0) {
198 rx.selected_files = selected_files;
200 if (rx.selected_files==1) {
201 ua->info_msg(_("\n1 file selected to be restored.\n\n"));
204 ua->info_msg(_("\n%s files selected to be restored.\n\n"),
205 edit_uint64_with_commas(rx.selected_files, ed1));
208 ua->warning_msg(_("No files selected to be restored.\n"));
212 if (rx.restore_jobs == 1) {
213 job = rx.restore_job;
215 job = select_restore_job_resource(ua);
221 get_client_name(ua, &rx);
222 if (!rx.ClientName) {
223 ua->error_msg(_("No Client resource found!\n"));
227 escaped_bsr_name = escape_filename(jcr->RestoreBootstrap);
228 escaped_where_name = escape_filename(rx.where);
230 /* Build run command */
232 if (!acl_access_ok(ua, Where_ACL, rx.where)) {
233 ua->error_msg(_("\"where\" specification not authorized.\n"));
238 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s\""
239 " %swhere=\"%s\" files=%d catalog=\"%s\"",
240 job->name(), rx.ClientName, rx.store?rx.store->name():"",
241 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
242 where_use_regexp ? "regex" : "",
243 escaped_where_name ? escaped_where_name : rx.where,
244 rx.selected_files, ua->catalog->name());
247 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s\""
248 " files=%d catalog=\"%s\"",
249 job->name(), rx.ClientName, rx.store?rx.store->name():"",
250 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
251 rx.selected_files, ua->catalog->name());
254 if (escaped_bsr_name != NULL) {
255 bfree(escaped_bsr_name);
258 if (escaped_where_name != NULL) {
259 bfree(escaped_where_name);
266 if (find_arg(ua, NT_("yes")) > 0) {
267 pm_strcat(ua->cmd, " yes"); /* pass it on to the run command */
269 Dmsg1(200, "Submitting: %s\n", ua->cmd);
271 run_cmd(ua, ua->cmd);
276 if (escaped_bsr_name != NULL) {
277 bfree(escaped_bsr_name);
280 if (escaped_where_name != NULL) {
281 bfree(escaped_where_name);
293 static void free_rx(RESTORE_CTX *rx)
298 free_pool_memory(rx->JobIds);
302 free_pool_memory(rx->fname);
306 free_pool_memory(rx->path);
310 free_pool_memory(rx->query);
313 free_name_list(&rx->name_list);
316 static bool has_value(UAContext *ua, int i)
319 ua->error_msg(_("Missing value for keyword: %s\n"), ua->argk[i]);
325 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
327 /* If no client name specified yet, get it now */
328 if (!rx->ClientName[0]) {
330 /* try command line argument */
331 int i = find_arg_with_value(ua, NT_("client"));
333 if (!has_value(ua, i)) {
336 bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
339 memset(&cr, 0, sizeof(cr));
340 if (!get_client_dbr(ua, &cr)) {
343 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
350 * The first step in the restore process is for the user to
351 * select a list of JobIds from which he will subsequently
352 * select which files are to be restored.
354 * Returns: 2 if filename list made
355 * 1 if jobid list made
358 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
361 char date[MAX_TIME_LENGTH];
362 bool have_date = false;
363 /* Include current second if using current time */
364 utime_t now = time(NULL) + 1;
366 JOB_DBR jr = { (JobId_t)-1 };
369 const char *list[] = {
370 _("List last 20 Jobs run"),
371 _("List Jobs where a given File is saved"),
372 _("Enter list of comma separated JobIds to select"),
373 _("Enter SQL list command"),
374 _("Select the most recent backup for a client"),
375 _("Select backup for a client before a specified time"),
376 _("Enter a list of files to restore"),
377 _("Enter a list of files to restore before a specified time"),
378 _("Find the JobIds of the most recent backup for a client"),
379 _("Find the JobIds for a backup for a client before a specified time"),
380 _("Enter a list of directories to restore for found JobIds"),
385 /* These keywords are handled in a for loop */
395 /* The keyword below are handled by individual arg lookups */
401 "bootstrap", /* 13 */
403 "strip_prefix", /* 15 */
404 "add_prefix", /* 16 */
405 "add_suffix", /* 17 */
406 "where_use_regexp",/* 18 */
407 "regexwhere", /* 19 like where + where_use_regexp */
413 for (i=1; i<ua->argc; i++) { /* loop through arguments */
414 bool found_kw = false;
415 for (j=0; kw[j]; j++) { /* loop through keywords */
416 if (strcasecmp(kw[j], ua->argk[i]) == 0) {
422 ua->error_msg(_("Unknown keyword: %s\n"), ua->argk[i]);
425 /* Found keyword in kw[] list, process it */
428 if (!has_value(ua, i)) {
431 if (*rx->JobIds != 0) {
432 pm_strcat(rx->JobIds, ",");
434 pm_strcat(rx->JobIds, ua->argv[i]);
437 case 1: /* current */
439 * Note, we add one second here just to include any job
440 * that may have finished within the current second,
441 * which happens a lot in scripting small jobs.
443 bstrutime(date, sizeof(date), now);
447 if (!has_value(ua, i)) {
450 if (str_to_utime(ua->argv[i]) == 0) {
451 ua->error_msg(_("Improper date format: %s\n"), ua->argv[i]);
454 bstrncpy(date, ua->argv[i], sizeof(date));
459 if (!has_value(ua, i)) {
463 bstrutime(date, sizeof(date), now);
465 if (!get_client_name(ua, rx)) {
468 pm_strcpy(ua->cmd, ua->argv[i]);
469 insert_one_file_or_dir(ua, rx, date, j==4);
473 bstrutime(date, sizeof(date), now);
475 if (!select_backups_before_date(ua, rx, date)) {
480 case 6: /* pool specified */
481 if (!has_value(ua, i)) {
484 rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
486 ua->error_msg(_("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
489 if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
491 ua->error_msg(_("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
495 case 7: /* all specified */
499 * All keywords 7 or greater are ignored or handled by a select prompt
507 ua->send_msg(_("\nFirst you select one or more JobIds that contain files\n"
508 "to be restored. You will be presented several methods\n"
509 "of specifying the JobIds. Then you will be allowed to\n"
510 "select which files from those JobIds are to be restored.\n\n"));
513 /* If choice not already made above, prompt */
519 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
520 for (int i=0; list[i]; i++) {
521 add_prompt(ua, list[i]);
524 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
525 case -1: /* error or cancel */
527 case 0: /* list last 20 Jobs run */
528 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
529 ua->error_msg(_("SQL query not authorized.\n"));
532 gui_save = ua->jcr->gui;
534 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
535 ua->jcr->gui = gui_save;
538 case 1: /* list where a file is saved */
539 if (!get_client_name(ua, rx)) {
542 if (!get_cmd(ua, _("Enter Filename (no path):"))) {
545 len = strlen(ua->cmd);
546 fname = (char *)malloc(len * 2 + 1);
547 db_escape_string(fname, ua->cmd, len);
548 Mmsg(rx->query, uar_file, rx->ClientName, fname);
550 gui_save = ua->jcr->gui;
552 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
553 ua->jcr->gui = gui_save;
556 case 2: /* enter a list of JobIds */
557 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
560 pm_strcpy(rx->JobIds, ua->cmd);
562 case 3: /* Enter an SQL list command */
563 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
564 ua->error_msg(_("SQL query not authorized.\n"));
567 if (!get_cmd(ua, _("Enter SQL list command: "))) {
570 gui_save = ua->jcr->gui;
572 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
573 ua->jcr->gui = gui_save;
576 case 4: /* Select the most recent backups */
577 bstrutime(date, sizeof(date), now);
578 if (!select_backups_before_date(ua, rx, date)) {
582 case 5: /* select backup at specified time */
583 if (!get_date(ua, date, sizeof(date))) {
586 if (!select_backups_before_date(ua, rx, date)) {
590 case 6: /* Enter files */
591 bstrutime(date, sizeof(date), now);
592 if (!get_client_name(ua, rx)) {
595 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
596 "containing a list of file names with paths, and terminate\n"
597 "them with a blank line.\n"));
599 if (!get_cmd(ua, _("Enter full filename: "))) {
602 len = strlen(ua->cmd);
606 insert_one_file_or_dir(ua, rx, date, false);
609 case 7: /* enter files backed up before specified time */
610 if (!get_date(ua, date, sizeof(date))) {
613 if (!get_client_name(ua, rx)) {
616 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
617 "containing a list of file names with paths, and terminate\n"
618 "them with a blank line.\n"));
620 if (!get_cmd(ua, _("Enter full filename: "))) {
623 len = strlen(ua->cmd);
627 insert_one_file_or_dir(ua, rx, date, false);
631 case 8: /* Find JobIds for current backup */
632 bstrutime(date, sizeof(date), now);
633 if (!select_backups_before_date(ua, rx, date)) {
639 case 9: /* Find JobIds for give date */
640 if (!get_date(ua, date, sizeof(date))) {
643 if (!select_backups_before_date(ua, rx, date)) {
649 case 10: /* Enter directories */
650 if (*rx->JobIds != 0) {
651 ua->send_msg(_("You have already selected the following JobIds: %s\n"),
653 } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
654 if (*rx->JobIds != 0 && *ua->cmd) {
655 pm_strcat(rx->JobIds, ",");
657 pm_strcat(rx->JobIds, ua->cmd);
659 if (*rx->JobIds == 0 || *rx->JobIds == '.') {
660 return 0; /* nothing entered, return */
662 bstrutime(date, sizeof(date), now);
663 if (!get_client_name(ua, rx)) {
666 ua->send_msg(_("Enter full directory names or start the name\n"
667 "with a < to indicate it is a filename containing a list\n"
668 "of directories and terminate them with a blank line.\n"));
670 if (!get_cmd(ua, _("Enter directory name: "))) {
673 len = strlen(ua->cmd);
677 /* Add trailing slash to end of directory names */
678 if (ua->cmd[0] != '<' && !IsPathSeparator(ua->cmd[len-1])) {
679 strcat(ua->cmd, "/");
681 insert_one_file_or_dir(ua, rx, date, true);
685 case 11: /* Cancel or quit */
690 POOLMEM *JobIds = get_pool_memory(PM_FNAME);
694 * Find total number of files to be restored, and filter the JobId
695 * list to contain only ones permitted by the ACL conditions.
697 for (p=rx->JobIds; ; ) {
699 int stat = get_next_jobid_from_list(&p, &JobId);
701 ua->error_msg(_("Invalid JobId in list.\n"));
702 free_pool_memory(JobIds);
708 if (jr.JobId == JobId) {
709 continue; /* duplicate of last JobId */
711 memset(&jr, 0, sizeof(JOB_DBR));
713 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
714 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
715 edit_int64(JobId, ed1), db_strerror(ua->db));
716 free_pool_memory(JobIds);
719 if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
720 ua->error_msg(_("Access to JobId=%s (Job \"%s\") not authorized. Not selected.\n"),
721 edit_int64(JobId, ed1), jr.Name);
725 pm_strcat(JobIds, ",");
727 pm_strcat(JobIds, edit_int64(JobId, ed1));
728 rx->TotalFiles += jr.JobFiles;
730 free_pool_memory(rx->JobIds);
731 rx->JobIds = JobIds; /* Set ACL filtered list */
732 if (*rx->JobIds == 0) {
733 ua->warning_msg(_("No Jobs selected.\n"));
736 if (strchr(rx->JobIds,',')) {
737 ua->info_msg(_("You have selected the following JobIds: %s\n"), rx->JobIds);
739 ua->info_msg(_("You have selected the following JobId: %s\n"), rx->JobIds);
747 static int get_date(UAContext *ua, char *date, int date_len)
749 ua->send_msg(_("The restored files will the most current backup\n"
750 "BEFORE the date you specify below.\n\n"));
752 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
755 if (str_to_utime(ua->cmd) != 0) {
758 ua->error_msg(_("Improper date format.\n"));
760 bstrncpy(date, ua->cmd, date_len);
765 * Insert a single file, or read a list of files from a file
767 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
777 if ((ffd = fopen(p, "rb")) == NULL) {
779 ua->error_msg(_("Cannot open file %s: ERR=%s\n"),
783 while (fgets(file, sizeof(file), ffd)) {
786 if (!insert_dir_into_findex_list(ua, rx, file, date)) {
787 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
790 if (!insert_file_into_findex_list(ua, rx, file, date)) {
791 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
799 insert_table_into_findex_list(ua, rx, p);
803 insert_dir_into_findex_list(ua, rx, ua->cmd, date);
805 insert_file_into_findex_list(ua, rx, ua->cmd, date);
812 * For a given file (path+filename), split into path and file, then
813 * lookup the most recent backup in the catalog to get the JobId
814 * and FileIndex, then insert them into the findex list.
816 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
819 strip_trailing_newline(file);
820 split_path_and_filename(rx, file);
821 if (*rx->JobIds == 0) {
822 Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname,
825 Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
826 rx->path, rx->fname, rx->ClientName);
829 /* Find and insert jobid and File Index */
830 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
831 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
832 rx->query, db_strerror(ua->db));
835 ua->error_msg(_("No database record found for: %s\n"), file);
836 // ua->error_msg("Query=%s\n", rx->query);
843 * For a given path lookup the most recent backup in the catalog
844 * to get the JobId and FileIndexes of all files in that directory.
846 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
849 strip_trailing_junk(dir);
850 if (*rx->JobIds == 0) {
851 ua->error_msg(_("No JobId specified cannot continue.\n"));
854 Mmsg(rx->query, uar_jobid_fileindex_from_dir, rx->JobIds,
855 dir, rx->ClientName);
858 /* Find and insert jobid and File Index */
859 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
860 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
861 rx->query, db_strerror(ua->db));
864 ua->error_msg(_("No database record found for: %s\n"), dir);
871 * Get the JobId and FileIndexes of all files in the specified table
873 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
875 strip_trailing_junk(table);
876 Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
879 /* Find and insert jobid and File Index */
880 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
881 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
882 rx->query, db_strerror(ua->db));
885 ua->error_msg(_("No table found: %s\n"), table);
891 static void split_path_and_filename(RESTORE_CTX *rx, char *name)
895 /* Find path without the filename.
896 * I.e. everything after the last / is a "filename".
897 * OK, maybe it is a directory name, but we treat it like
898 * a filename. If we don't find a / then the whole name
899 * must be a path name (e.g. c:).
901 for (p=f=name; *p; p++) {
902 if (IsPathSeparator(*p)) {
903 f = p; /* set pos of last slash */
906 if (IsPathSeparator(*f)) { /* did we find a slash? */
907 f++; /* yes, point to filename */
908 } else { /* no, whole thing must be path name */
912 /* If filename doesn't exist (i.e. root directory), we
913 * simply create a blank name consisting of a single
914 * space. This makes handling zero length filenames
919 rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
920 memcpy(rx->fname, f, rx->fnl); /* copy filename */
921 rx->fname[rx->fnl] = 0;
929 rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
930 memcpy(rx->path, name, rx->pnl);
931 rx->path[rx->pnl] = 0;
937 Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
940 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
943 JobId_t JobId, last_JobId;
948 memset(&tree, 0, sizeof(TREE_CTX));
950 * Build the directory tree containing JobIds user selected
952 tree.root = new_tree(rx->TotalFiles);
957 * For display purposes, the same JobId, with different volumes may
958 * appear more than once, however, we only insert it once.
962 tree.FileEstimate = 0;
963 if (get_next_jobid_from_list(&p, &JobId) > 0) {
964 /* Use first JobId as estimate of the number of files to restore */
965 Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
966 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
967 ua->error_msg("%s\n", db_strerror(ua->db));
970 /* Add about 25% more than this job for over estimate */
971 tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
972 tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
975 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
978 if (JobId == last_JobId) {
979 continue; /* eliminate duplicate JobIds */
982 ua->info_msg(_("\nBuilding directory tree for JobId %s ... "),
983 edit_int64(JobId, ed1));
986 * Find files for this JobId and insert them in the tree
988 Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
989 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
990 ua->error_msg("%s", db_strerror(ua->db));
993 if (tree.FileCount == 0) {
994 ua->send_msg(_("\nThere were no files inserted into the tree, so file selection\n"
995 "is not possible.Most likely your retention policy pruned the files\n"));
996 if (!get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
1000 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1001 if (JobId == last_JobId) {
1002 continue; /* eliminate duplicate JobIds */
1004 add_findex_all(rx->bsr, JobId);
1012 ua->info_msg(_("\n1 Job, %s files inserted into the tree and marked for extraction.\n"),
1013 edit_uint64_with_commas(tree.FileCount, ec1));
1016 ua->info_msg(_("\n1 Job, %s files inserted into the tree.\n"),
1017 edit_uint64_with_commas(tree.FileCount, ec1));
1022 ua->info_msg(_("\n%d Jobs, %s files inserted into the tree and marked for extraction.\n"),
1023 items, edit_uint64_with_commas(tree.FileCount, ec1));
1026 ua->info_msg(_("\n%d Jobs, %s files inserted into the tree.\n"),
1027 items, edit_uint64_with_commas(tree.FileCount, ec1));
1031 if (find_arg(ua, NT_("done")) < 0) {
1032 /* Let the user interact in selecting which files to restore */
1033 OK = user_select_files_from_tree(&tree);
1037 * Walk down through the tree finding all files marked to be
1038 * extracted making a bootstrap file.
1041 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
1042 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
1043 if (node->extract || node->extract_dir) {
1044 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
1045 add_findex(rx->bsr, node->JobId, node->FileIndex);
1046 if (node->extract && node->type != TN_NEWDIR) {
1047 rx->selected_files++; /* count only saved files */
1054 free_tree(tree.root); /* free the directory tree */
1060 * This routine is used to get the current backup or a backup
1061 * before the specified date.
1063 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
1068 char fileset_name[MAX_NAME_LENGTH];
1069 char ed1[50], ed2[50];
1070 char pool_select[MAX_NAME_LENGTH];
1074 /* Create temp tables */
1075 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1076 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1077 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
1078 ua->error_msg("%s\n", db_strerror(ua->db));
1080 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
1081 ua->error_msg("%s\n", db_strerror(ua->db));
1084 * Select Client from the Catalog
1086 memset(&cr, 0, sizeof(cr));
1087 if (!get_client_dbr(ua, &cr)) {
1090 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1095 memset(&fsr, 0, sizeof(fsr));
1096 i = find_arg_with_value(ua, "FileSet");
1098 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1099 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1100 ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1101 db_strerror(ua->db));
1105 if (i < 0) { /* fileset not found */
1106 edit_int64(cr.ClientId, ed1);
1107 Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1108 start_prompt(ua, _("The defined FileSet resources are:\n"));
1109 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1110 ua->error_msg("%s\n", db_strerror(ua->db));
1112 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1113 fileset_name, sizeof(fileset_name)) < 0) {
1114 ua->error_msg(_("No FileSet found for client \"%s\".\n"), cr.Name);
1118 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1119 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1120 ua->warning_msg(_("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1121 ua->send_msg(_("This probably means you modified the FileSet.\n"
1122 "Continuing anyway.\n"));
1126 /* If Pool specified, add PoolId specification */
1130 memset(&pr, 0, sizeof(pr));
1131 bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1132 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1133 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
1134 edit_int64(pr.PoolId, ed1));
1136 ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1140 /* Find JobId of last Full backup for this client, fileset */
1141 edit_int64(cr.ClientId, ed1);
1142 Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1144 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1145 ua->error_msg("%s\n", db_strerror(ua->db));
1149 /* Find all Volumes used by that JobId */
1150 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1151 ua->error_msg("%s\n", db_strerror(ua->db));
1154 /* Note, this is needed because I don't seem to get the callback
1155 * from the call just above.
1158 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1159 ua->warning_msg("%s\n", db_strerror(ua->db));
1161 if (rx->JobTDate == 0) {
1162 ua->error_msg(_("No Full backup before %s found.\n"), date);
1166 /* Now find most recent Differental Job after Full save, if any */
1167 Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1168 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1169 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1170 ua->warning_msg("%s\n", db_strerror(ua->db));
1172 /* Now update JobTDate to lock onto Differental, if any */
1174 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1175 ua->warning_msg("%s\n", db_strerror(ua->db));
1177 if (rx->JobTDate == 0) {
1178 ua->error_msg(_("No Full backup before %s found.\n"), date);
1182 /* Now find all Incremental Jobs after Full/dif save */
1183 Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1184 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1185 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1186 ua->warning_msg("%s\n", db_strerror(ua->db));
1189 /* Get the JobIds from that list */
1191 rx->last_jobid[0] = 0;
1192 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1193 ua->warning_msg("%s\n", db_strerror(ua->db));
1196 if (rx->JobIds[0] != 0) {
1197 /* Display a list of Jobs selected for this restore */
1198 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
1201 ua->warning_msg(_("No jobs found.\n"));
1205 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1206 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1212 * Return next JobId from comma separated list
1215 * 1 if next JobId returned
1216 * 0 if no more JobIds are in list
1217 * -1 there is an error
1219 int get_next_jobid_from_list(char **p, JobId_t *JobId)
1225 for (int i=0; i<(int)sizeof(jobid); i++) {
1228 } else if (*q == ',') {
1235 if (jobid[0] == 0) {
1237 } else if (!is_a_number(jobid)) {
1238 return -1; /* error */
1241 *JobId = str_to_int64(jobid);
1245 static int restore_count_handler(void *ctx, int num_fields, char **row)
1247 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1248 rx->JobId = str_to_int64(row[0]);
1254 * Callback handler to get JobId and FileIndex for files
1255 * can insert more than one depending on the caller.
1257 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1259 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1260 rx->JobId = str_to_int64(row[0]);
1261 add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1263 rx->selected_files++;
1268 * Callback handler make list of JobIds
1270 static int jobid_handler(void *ctx, int num_fields, char **row)
1272 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1274 if (strcmp(rx->last_jobid, row[0]) == 0) {
1275 return 0; /* duplicate id */
1277 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1278 if (rx->JobIds[0] != 0) {
1279 pm_strcat(rx->JobIds, ",");
1281 pm_strcat(rx->JobIds, row[0]);
1287 * Callback handler to pickup last Full backup JobTDate
1289 static int last_full_handler(void *ctx, int num_fields, char **row)
1291 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1293 rx->JobTDate = str_to_int64(row[1]);
1298 * Callback handler build FileSet name prompt list
1300 static int fileset_handler(void *ctx, int num_fields, char **row)
1302 /* row[0] = FileSet (name) */
1304 add_prompt((UAContext *)ctx, row[0]);
1310 * Free names in the list
1312 static void free_name_list(NAME_LIST *name_list)
1314 for (int i=0; i < name_list->num_ids; i++) {
1315 free(name_list->name[i]);
1317 if (name_list->name) {
1318 free(name_list->name);
1319 name_list->name = NULL;
1321 name_list->max_ids = 0;
1322 name_list->num_ids = 0;
1325 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType)
1330 Dmsg1(200, "Already have store=%s\n", rx.store->name());
1334 * Try looking up Storage by name
1337 foreach_res(store, R_STORAGE) {
1338 if (strcmp(Storage, store->name()) == 0) {
1339 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1348 /* Check if an explicit storage resource is given */
1350 int i = find_arg_with_value(ua, "storage");
1352 store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1353 if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1357 if (store && (store != rx.store)) {
1358 ua->info_msg(_("Warning default storage overridden by \"%s\" on command line.\n"),
1361 Dmsg1(200, "Set store=%s\n", rx.store->name());
1366 /* If no storage resource, try to find one from MediaType */
1369 foreach_res(store, R_STORAGE) {
1370 if (strcmp(MediaType, store->media_type) == 0) {
1371 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1373 Dmsg1(200, "Set store=%s\n", rx.store->name());
1374 ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1375 Storage, store->name(), MediaType);
1382 ua->warning_msg(_("\nUnable to find Storage resource for\n"
1383 "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1386 /* Take command line arg, or ask user if none */
1387 rx.store = get_storage_resource(ua, false /* don't use default */);
1388 Dmsg1(200, "Set store=%s\n", rx.store->name());