2 Bacula® - The Network Backup Solution
4 Copyright (C) 2002-2008 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 and included
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 Kern Sibbald.
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(UAContext *ua, 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_restore_client_name(UAContext *ua, RESTORE_CTX &rx);
70 static int get_date(UAContext *ua, char *date, int date_len);
71 static int restore_count_handler(void *ctx, int num_fields, char **row);
72 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table);
78 int restore_cmd(UAContext *ua, const char *cmd)
80 RESTORE_CTX rx; /* restore context */
84 char *escaped_bsr_name = NULL;
85 char *escaped_where_name = NULL;
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.BaseJobIds = get_pool_memory(PM_FNAME);
94 rx.query = get_pool_memory(PM_FNAME);
97 i = find_arg_with_value(ua, "where");
99 rx.where = ua->argv[i];
102 i = find_arg_with_value(ua, "strip_prefix");
104 strip_prefix = ua->argv[i];
107 i = find_arg_with_value(ua, "add_prefix");
109 add_prefix = ua->argv[i];
112 i = find_arg_with_value(ua, "add_suffix");
114 add_suffix = ua->argv[i];
117 i = find_arg_with_value(ua, "regexwhere");
119 rx.RegexWhere = ua->argv[i];
122 if (strip_prefix || add_suffix || add_prefix) {
123 int len = bregexp_get_build_where_size(strip_prefix, add_prefix, add_suffix);
124 regexp = (char *)bmalloc(len * sizeof(char));
126 bregexp_build_where(regexp, len, strip_prefix, add_prefix, add_suffix);
127 rx.RegexWhere = regexp;
130 /* TODO: add acl for regexwhere ? */
133 if (!acl_access_ok(ua, Where_ACL, rx.RegexWhere)) {
134 ua->error_msg(_("\"RegexWhere\" specification not authorized.\n"));
140 if (!acl_access_ok(ua, Where_ACL, rx.where)) {
141 ua->error_msg(_("\"where\" specification not authorized.\n"));
146 if (!open_client_db(ua)) {
150 /* Ensure there is at least one Restore Job */
152 foreach_res(job, R_JOB) {
153 if (job->JobType == JT_RESTORE) {
154 if (!rx.restore_job) {
155 rx.restore_job = job;
161 if (!rx.restore_jobs) {
163 "No Restore Job Resource found in bacula-dir.conf.\n"
164 "You must create at least one before running this command.\n"));
169 * Request user to select JobIds or files by various different methods
170 * last 20 jobs, where File saved, most recent backup, ...
171 * In the end, a list of files are pumped into
174 switch (user_select_jobids_or_files(ua, &rx)) {
177 case 1: /* selected by jobid */
178 if (!build_directory_tree(ua, &rx)) {
179 ua->send_msg(_("Restore not done.\n"));
183 case 2: /* selected by filename, no tree needed */
188 uint32_t selected_files;
190 if (!complete_bsr(ua, rx.bsr)) { /* find Vol, SessId, SessTime from JobIds */
191 ua->error_msg(_("Unable to construct a valid BSR. Cannot continue.\n"));
194 if (!(selected_files = write_bsr_file(ua, rx))) {
195 ua->warning_msg(_("No files selected to be restored.\n"));
198 display_bsr_info(ua, rx); /* display vols needed, etc */
200 /* If no count of files, use bsr generated value (often wrong) */
201 if (rx.selected_files == 0) {
202 rx.selected_files = selected_files;
204 if (rx.selected_files==1) {
205 ua->info_msg(_("\n1 file selected to be restored.\n\n"));
208 ua->info_msg(_("\n%s files selected to be restored.\n\n"),
209 edit_uint64_with_commas(rx.selected_files, ed1));
212 ua->warning_msg(_("No files selected to be restored.\n"));
216 if (rx.restore_jobs == 1) {
217 job = rx.restore_job;
219 job = select_restore_job_resource(ua);
225 get_client_name(ua, &rx);
226 if (!rx.ClientName) {
227 ua->error_msg(_("No Client resource found!\n"));
230 get_restore_client_name(ua, rx);
232 escaped_bsr_name = escape_filename(jcr->RestoreBootstrap);
234 /* Build run command */
236 escaped_where_name = escape_filename(rx.RegexWhere);
238 "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
239 " bootstrap=\"%s\" regexwhere=\"%s\" files=%u catalog=\"%s\"",
240 job->name(), rx.ClientName, rx.RestoreClientName,
241 rx.store?rx.store->name():"",
242 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
243 escaped_where_name ? escaped_where_name : rx.RegexWhere,
244 rx.selected_files, ua->catalog->name());
246 } else if (rx.where) {
247 escaped_where_name = escape_filename(rx.where);
249 "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
250 " bootstrap=\"%s\" where=\"%s\" files=%u catalog=\"%s\"",
251 job->name(), rx.ClientName, rx.RestoreClientName,
252 rx.store?rx.store->name():"",
253 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
254 escaped_where_name ? escaped_where_name : rx.where,
255 rx.selected_files, ua->catalog->name());
259 "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
260 " bootstrap=\"%s\" files=%u catalog=\"%s\"",
261 job->name(), rx.ClientName, rx.RestoreClientName,
262 rx.store?rx.store->name():"",
263 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
264 rx.selected_files, ua->catalog->name());
267 if (escaped_bsr_name != NULL) {
268 bfree(escaped_bsr_name);
271 if (escaped_where_name != NULL) {
272 bfree(escaped_where_name);
279 if (find_arg(ua, NT_("yes")) > 0) {
280 pm_strcat(ua->cmd, " yes"); /* pass it on to the run command */
282 Dmsg1(200, "Submitting: %s\n", ua->cmd);
284 run_cmd(ua, ua->cmd);
289 if (escaped_bsr_name != NULL) {
290 bfree(escaped_bsr_name);
293 if (escaped_where_name != NULL) {
294 bfree(escaped_where_name);
306 static void free_rx(RESTORE_CTX *rx)
311 free_pool_memory(rx->JobIds);
314 if (rx->BaseJobIds) {
315 free_pool_memory(rx->BaseJobIds);
316 rx->BaseJobIds = NULL;
319 free_pool_memory(rx->fname);
323 free_pool_memory(rx->path);
327 free_pool_memory(rx->query);
330 free_name_list(&rx->name_list);
333 static bool has_value(UAContext *ua, int i)
336 ua->error_msg(_("Missing value for keyword: %s\n"), ua->argk[i]);
343 * This gets the client name from which the backup was made
345 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
347 /* If no client name specified yet, get it now */
348 if (!rx->ClientName[0]) {
350 /* try command line argument */
351 int i = find_arg_with_value(ua, NT_("client"));
353 i = find_arg_with_value(ua, NT_("backupclient"));
356 if (!has_value(ua, i)) {
359 bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
362 memset(&cr, 0, sizeof(cr));
363 if (!get_client_dbr(ua, &cr)) {
366 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
372 * This is where we pick up a client name to restore to.
374 static int get_restore_client_name(UAContext *ua, RESTORE_CTX &rx)
376 /* Start with same name as backup client */
377 bstrncpy(rx.RestoreClientName, rx.ClientName, sizeof(rx.RestoreClientName));
379 /* try command line argument */
380 int i = find_arg_with_value(ua, NT_("restoreclient"));
382 if (!has_value(ua, i)) {
385 bstrncpy(rx.RestoreClientName, ua->argv[i], sizeof(rx.RestoreClientName));
394 * The first step in the restore process is for the user to
395 * select a list of JobIds from which he will subsequently
396 * select which files are to be restored.
398 * Returns: 2 if filename list made
399 * 1 if jobid list made
402 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
405 char date[MAX_TIME_LENGTH];
406 bool have_date = false;
407 /* Include current second if using current time */
408 utime_t now = time(NULL) + 1;
410 JOB_DBR jr = { (JobId_t)-1 };
413 const char *list[] = {
414 _("List last 20 Jobs run"),
415 _("List Jobs where a given File is saved"),
416 _("Enter list of comma separated JobIds to select"),
417 _("Enter SQL list command"),
418 _("Select the most recent backup for a client"),
419 _("Select backup for a client before a specified time"),
420 _("Enter a list of files to restore"),
421 _("Enter a list of files to restore before a specified time"),
422 _("Find the JobIds of the most recent backup for a client"),
423 _("Find the JobIds for a backup for a client before a specified time"),
424 _("Enter a list of directories to restore for found JobIds"),
425 _("Select full restore to a specified JobId"),
430 /* These keywords are handled in a for loop */
440 /* The keyword below are handled by individual arg lookups */
446 "bootstrap", /* 13 */
448 "strip_prefix", /* 15 */
449 "add_prefix", /* 16 */
450 "add_suffix", /* 17 */
451 "regexwhere", /* 18 */
452 "restoreclient", /* 19 */
457 rx->BaseJobIds[0] = rx->JobIds[0] = 0;
459 for (i=1; i<ua->argc; i++) { /* loop through arguments */
460 bool found_kw = false;
461 for (j=0; kw[j]; j++) { /* loop through keywords */
462 if (strcasecmp(kw[j], ua->argk[i]) == 0) {
468 ua->error_msg(_("Unknown keyword: %s\n"), ua->argk[i]);
471 /* Found keyword in kw[] list, process it */
474 if (!has_value(ua, i)) {
477 if (*rx->JobIds != 0) {
478 pm_strcat(rx->JobIds, ",");
480 pm_strcat(rx->JobIds, ua->argv[i]);
483 case 1: /* current */
485 * Note, we add one second here just to include any job
486 * that may have finished within the current second,
487 * which happens a lot in scripting small jobs.
489 bstrutime(date, sizeof(date), now);
493 if (have_date || !has_value(ua, i)) {
496 if (str_to_utime(ua->argv[i]) == 0) {
497 ua->error_msg(_("Improper date format: %s\n"), ua->argv[i]);
500 bstrncpy(date, ua->argv[i], sizeof(date));
505 if (!has_value(ua, i)) {
509 bstrutime(date, sizeof(date), now);
511 if (!get_client_name(ua, rx)) {
514 pm_strcpy(ua->cmd, ua->argv[i]);
515 insert_one_file_or_dir(ua, rx, date, j==4);
519 bstrutime(date, sizeof(date), now);
521 if (!select_backups_before_date(ua, rx, date)) {
526 case 6: /* pool specified */
527 if (!has_value(ua, i)) {
530 rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
532 ua->error_msg(_("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
535 if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
537 ua->error_msg(_("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
541 case 7: /* all specified */
545 * All keywords 7 or greater are ignored or handled by a select prompt
553 ua->send_msg(_("\nFirst you select one or more JobIds that contain files\n"
554 "to be restored. You will be presented several methods\n"
555 "of specifying the JobIds. Then you will be allowed to\n"
556 "select which files from those JobIds are to be restored.\n\n"));
559 /* If choice not already made above, prompt */
565 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
566 for (int i=0; list[i]; i++) {
567 add_prompt(ua, list[i]);
570 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
571 case -1: /* error or cancel */
573 case 0: /* list last 20 Jobs run */
574 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
575 ua->error_msg(_("SQL query not authorized.\n"));
578 gui_save = ua->jcr->gui;
580 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
581 ua->jcr->gui = gui_save;
584 case 1: /* list where a file is saved */
585 if (!get_client_name(ua, rx)) {
588 if (!get_cmd(ua, _("Enter Filename (no path):"))) {
591 len = strlen(ua->cmd);
592 fname = (char *)malloc(len * 2 + 1);
593 db_escape_string(ua->jcr, ua->db, fname, ua->cmd, len);
594 Mmsg(rx->query, uar_file[db_type], rx->ClientName, fname);
596 gui_save = ua->jcr->gui;
598 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
599 ua->jcr->gui = gui_save;
602 case 2: /* enter a list of JobIds */
603 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
606 pm_strcpy(rx->JobIds, ua->cmd);
608 case 3: /* Enter an SQL list command */
609 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
610 ua->error_msg(_("SQL query not authorized.\n"));
613 if (!get_cmd(ua, _("Enter SQL list command: "))) {
616 gui_save = ua->jcr->gui;
618 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
619 ua->jcr->gui = gui_save;
622 case 4: /* Select the most recent backups */
624 bstrutime(date, sizeof(date), now);
626 if (!select_backups_before_date(ua, rx, date)) {
630 case 5: /* select backup at specified time */
632 if (!get_date(ua, date, sizeof(date))) {
636 if (!select_backups_before_date(ua, rx, date)) {
640 case 6: /* Enter files */
642 bstrutime(date, sizeof(date), now);
644 if (!get_client_name(ua, rx)) {
647 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
648 "containing a list of file names with paths, and terminate\n"
649 "them with a blank line.\n"));
651 if (!get_cmd(ua, _("Enter full filename: "))) {
654 len = strlen(ua->cmd);
658 insert_one_file_or_dir(ua, rx, date, false);
661 case 7: /* enter files backed up before specified time */
663 if (!get_date(ua, date, sizeof(date))) {
667 if (!get_client_name(ua, rx)) {
670 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
671 "containing a list of file names with paths, and terminate\n"
672 "them with a blank line.\n"));
674 if (!get_cmd(ua, _("Enter full filename: "))) {
677 len = strlen(ua->cmd);
681 insert_one_file_or_dir(ua, rx, date, false);
685 case 8: /* Find JobIds for current backup */
687 bstrutime(date, sizeof(date), now);
689 if (!select_backups_before_date(ua, rx, date)) {
695 case 9: /* Find JobIds for give date */
697 if (!get_date(ua, date, sizeof(date))) {
701 if (!select_backups_before_date(ua, rx, date)) {
707 case 10: /* Enter directories */
708 if (*rx->JobIds != 0) {
709 ua->send_msg(_("You have already selected the following JobIds: %s\n"),
711 } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
712 if (*rx->JobIds != 0 && *ua->cmd) {
713 pm_strcat(rx->JobIds, ",");
715 pm_strcat(rx->JobIds, ua->cmd);
717 if (*rx->JobIds == 0 || *rx->JobIds == '.') {
718 return 0; /* nothing entered, return */
721 bstrutime(date, sizeof(date), now);
723 if (!get_client_name(ua, rx)) {
726 ua->send_msg(_("Enter full directory names or start the name\n"
727 "with a < to indicate it is a filename containing a list\n"
728 "of directories and terminate them with a blank line.\n"));
730 if (!get_cmd(ua, _("Enter directory name: "))) {
733 len = strlen(ua->cmd);
737 /* Add trailing slash to end of directory names */
738 if (ua->cmd[0] != '<' && !IsPathSeparator(ua->cmd[len-1])) {
739 strcat(ua->cmd, "/");
741 insert_one_file_or_dir(ua, rx, date, true);
745 case 11: /* Choose a jobid and select jobs */
746 if (!get_cmd(ua, _("Enter JobId to restore: ")) ||
747 !is_an_integer(ua->cmd))
752 memset(&jr, 0, sizeof(JOB_DBR));
753 jr.JobId = str_to_int64(ua->cmd);
754 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
755 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
756 ua->cmd, db_strerror(ua->db));
759 jr.JobLevel = L_INCREMENTAL; /* Take Full+Diff+Incr */
760 if (!db_accurate_get_jobids(ua->jcr, ua->db, &jr, rx->JobIds)) {
763 Dmsg1(30, "Item 12: jobids = %s\n", rx->JobIds);
765 case 12: /* Cancel or quit */
770 memset(&jr, 0, sizeof(JOB_DBR));
771 POOLMEM *JobIds = get_pool_memory(PM_FNAME);
775 * Find total number of files to be restored, and filter the JobId
776 * list to contain only ones permitted by the ACL conditions.
778 for (p=rx->JobIds; ; ) {
780 int stat = get_next_jobid_from_list(&p, &JobId);
782 ua->error_msg(_("Invalid JobId in list.\n"));
783 free_pool_memory(JobIds);
789 if (jr.JobId == JobId) {
790 continue; /* duplicate of last JobId */
792 memset(&jr, 0, sizeof(JOB_DBR));
794 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
795 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
796 edit_int64(JobId, ed1), db_strerror(ua->db));
797 free_pool_memory(JobIds);
800 if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
801 ua->error_msg(_("Access to JobId=%s (Job \"%s\") not authorized. Not selected.\n"),
802 edit_int64(JobId, ed1), jr.Name);
806 pm_strcat(JobIds, ",");
808 pm_strcat(JobIds, edit_int64(JobId, ed1));
809 rx->TotalFiles += jr.JobFiles;
811 free_pool_memory(rx->JobIds);
812 rx->JobIds = JobIds; /* Set ACL filtered list */
813 if (*rx->JobIds == 0) {
814 ua->warning_msg(_("No Jobs selected.\n"));
818 if (strchr(rx->JobIds,',')) {
819 ua->info_msg(_("You have selected the following JobIds: %s\n"), rx->JobIds);
821 ua->info_msg(_("You have selected the following JobId: %s\n"), rx->JobIds);
829 static int get_date(UAContext *ua, char *date, int date_len)
831 ua->send_msg(_("The restored files will the most current backup\n"
832 "BEFORE the date you specify below.\n\n"));
834 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
837 if (str_to_utime(ua->cmd) != 0) {
840 ua->error_msg(_("Improper date format.\n"));
842 bstrncpy(date, ua->cmd, date_len);
847 * Insert a single file, or read a list of files from a file
849 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
859 if ((ffd = fopen(p, "rb")) == NULL) {
861 ua->error_msg(_("Cannot open file %s: ERR=%s\n"),
865 while (fgets(file, sizeof(file), ffd)) {
868 if (!insert_dir_into_findex_list(ua, rx, file, date)) {
869 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
872 if (!insert_file_into_findex_list(ua, rx, file, date)) {
873 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
881 insert_table_into_findex_list(ua, rx, p);
885 insert_dir_into_findex_list(ua, rx, ua->cmd, date);
887 insert_file_into_findex_list(ua, rx, ua->cmd, date);
894 * For a given file (path+filename), split into path and file, then
895 * lookup the most recent backup in the catalog to get the JobId
896 * and FileIndex, then insert them into the findex list.
898 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
901 strip_trailing_newline(file);
902 split_path_and_filename(ua, rx, file);
903 if (*rx->JobIds == 0) {
904 Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname,
907 Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
908 rx->path, rx->fname, rx->ClientName);
911 /* Find and insert jobid and File Index */
912 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
913 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
914 rx->query, db_strerror(ua->db));
917 ua->error_msg(_("No database record found for: %s\n"), file);
918 // ua->error_msg("Query=%s\n", rx->query);
925 * For a given path lookup the most recent backup in the catalog
926 * to get the JobId and FileIndexes of all files in that directory.
928 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
931 strip_trailing_junk(dir);
932 if (*rx->JobIds == 0) {
933 ua->error_msg(_("No JobId specified cannot continue.\n"));
936 Mmsg(rx->query, uar_jobid_fileindex_from_dir[db_type], rx->JobIds, dir, rx->ClientName);
939 /* Find and insert jobid and File Index */
940 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
941 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
942 rx->query, db_strerror(ua->db));
945 ua->error_msg(_("No database record found for: %s\n"), dir);
952 * Get the JobId and FileIndexes of all files in the specified table
954 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
956 strip_trailing_junk(table);
957 Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
960 /* Find and insert jobid and File Index */
961 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
962 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
963 rx->query, db_strerror(ua->db));
966 ua->error_msg(_("No table found: %s\n"), table);
972 static void split_path_and_filename(UAContext *ua, RESTORE_CTX *rx, char *name)
976 /* Find path without the filename.
977 * I.e. everything after the last / is a "filename".
978 * OK, maybe it is a directory name, but we treat it like
979 * a filename. If we don't find a / then the whole name
980 * must be a path name (e.g. c:).
982 for (p=f=name; *p; p++) {
983 if (IsPathSeparator(*p)) {
984 f = p; /* set pos of last slash */
987 if (IsPathSeparator(*f)) { /* did we find a slash? */
988 f++; /* yes, point to filename */
989 } else { /* no, whole thing must be path name */
993 /* If filename doesn't exist (i.e. root directory), we
994 * simply create a blank name consisting of a single
995 * space. This makes handling zero length filenames
1000 rx->fname = check_pool_memory_size(rx->fname, 2*(rx->fnl)+1);
1001 db_escape_string(ua->jcr, ua->db, rx->fname, f, rx->fnl);
1009 rx->path = check_pool_memory_size(rx->path, 2*(rx->pnl)+1);
1010 db_escape_string(ua->jcr, ua->db, rx->path, name, rx->pnl);
1016 Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
1019 static bool ask_for_fileregex(UAContext *ua, RESTORE_CTX *rx)
1021 if (find_arg(ua, NT_("all")) >= 0) { /* if user enters all on command line */
1022 return true; /* select everything */
1024 ua->send_msg(_("\n\nFor one or more of the JobIds selected, no files were found,\n"
1025 "so file selection is not possible.\n"
1026 "Most likely your retention policy pruned the files.\n"));
1027 if (get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
1028 if (ua->pint32_val == 1)
1030 while (get_cmd(ua, _("\nRegexp matching files to restore? (empty to abort): "))) {
1031 if (ua->cmd[0] == '\0') {
1034 regex_t *fileregex_re = NULL;
1036 char errmsg[500] = "";
1038 fileregex_re = (regex_t *)bmalloc(sizeof(regex_t));
1039 rc = regcomp(fileregex_re, ua->cmd, REG_EXTENDED|REG_NOSUB);
1041 regerror(rc, fileregex_re, errmsg, sizeof(errmsg));
1043 regfree(fileregex_re);
1046 ua->send_msg(_("Regex compile error: %s\n"), errmsg);
1048 rx->bsr->fileregex = bstrdup(ua->cmd);
1057 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
1060 JobId_t JobId, last_JobId;
1065 memset(&tree, 0, sizeof(TREE_CTX));
1067 * Build the directory tree containing JobIds user selected
1069 tree.root = new_tree(rx->TotalFiles);
1074 * For display purposes, the same JobId, with different volumes may
1075 * appear more than once, however, we only insert it once.
1078 tree.FileEstimate = 0;
1079 if (get_next_jobid_from_list(&p, &JobId) > 0) {
1080 /* Use first JobId as estimate of the number of files to restore */
1081 Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
1082 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1083 ua->error_msg("%s\n", db_strerror(ua->db));
1086 /* Add about 25% more than this job for over estimate */
1087 tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
1088 tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
1092 ua->info_msg(_("\nBuilding directory tree for JobId(s) %s ... "),
1095 #define new_get_file_list
1096 #ifdef new_get_file_list
1097 if (!db_get_file_list(ua->jcr, ua->db, rx->JobIds, insert_tree_handler, (void *)&tree)) {
1098 ua->error_msg("%s", db_strerror(ua->db));
1100 if (*rx->BaseJobIds) {
1101 pm_strcat(rx->JobIds, ",");
1102 pm_strcat(rx->JobIds, rx->BaseJobIds);
1105 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1108 if (JobId == last_JobId) {
1109 continue; /* eliminate duplicate JobIds */
1113 * Find files for this JobId and insert them in the tree
1115 Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
1116 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
1117 ua->error_msg("%s", db_strerror(ua->db));
1122 * Look at the first JobId on the list (presumably the oldest) and
1123 * if it is marked purged, don't do the manual selection because
1124 * the Job was pruned, so the tree is incomplete.
1126 if (tree.FileCount != 0) {
1127 /* Find out if any Job is purged */
1128 Mmsg(rx->query, "SELECT SUM(PurgedFiles) FROM Job WHERE JobId IN (%s)", rx->JobIds);
1129 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1130 ua->error_msg("%s\n", db_strerror(ua->db));
1132 /* rx->JobId is the PurgedFiles flag */
1133 if (rx->found && rx->JobId > 0) {
1134 tree.FileCount = 0; /* set count to zero, no tree selection */
1137 if (tree.FileCount == 0) {
1138 OK = ask_for_fileregex(ua, rx);
1141 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1142 if (JobId == last_JobId) {
1143 continue; /* eliminate duplicate JobIds */
1145 add_findex_all(rx->bsr, JobId);
1151 ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
1152 edit_uint64_with_commas(tree.FileCount, ec1));
1154 ua->info_msg(_("\n%s files inserted into the tree.\n"),
1155 edit_uint64_with_commas(tree.FileCount, ec1));
1158 if (find_arg(ua, NT_("done")) < 0) {
1159 /* Let the user interact in selecting which files to restore */
1160 OK = user_select_files_from_tree(&tree);
1164 * Walk down through the tree finding all files marked to be
1165 * extracted making a bootstrap file.
1168 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
1169 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
1170 if (node->extract || node->extract_dir) {
1171 Dmsg3(400, "JobId=%lld type=%d FI=%d\n", (uint64_t)node->JobId, node->type, node->FileIndex);
1172 add_findex(rx->bsr, node->JobId, node->FileIndex);
1173 if (node->extract && node->type != TN_NEWDIR) {
1174 rx->selected_files++; /* count only saved files */
1181 free_tree(tree.root); /* free the directory tree */
1187 * This routine is used to get the current backup or a backup
1188 * before the specified date.
1190 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
1195 char fileset_name[MAX_NAME_LENGTH];
1196 char ed1[50], ed2[50];
1197 char pool_select[MAX_NAME_LENGTH];
1200 /* Create temp tables */
1201 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1202 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1203 if (!db_sql_query(ua->db, uar_create_temp[db_type], NULL, NULL)) {
1204 ua->error_msg("%s\n", db_strerror(ua->db));
1206 if (!db_sql_query(ua->db, uar_create_temp1[db_type], NULL, NULL)) {
1207 ua->error_msg("%s\n", db_strerror(ua->db));
1210 * Select Client from the Catalog
1212 memset(&cr, 0, sizeof(cr));
1213 if (!get_client_dbr(ua, &cr)) {
1216 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1221 memset(&fsr, 0, sizeof(fsr));
1222 i = find_arg_with_value(ua, "FileSet");
1224 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1225 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1226 ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1227 db_strerror(ua->db));
1231 if (i < 0) { /* fileset not found */
1232 edit_int64(cr.ClientId, ed1);
1233 Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1234 start_prompt(ua, _("The defined FileSet resources are:\n"));
1235 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1236 ua->error_msg("%s\n", db_strerror(ua->db));
1238 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1239 fileset_name, sizeof(fileset_name)) < 0) {
1240 ua->error_msg(_("No FileSet found for client \"%s\".\n"), cr.Name);
1244 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1245 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1246 ua->warning_msg(_("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1247 ua->send_msg(_("This probably means you modified the FileSet.\n"
1248 "Continuing anyway.\n"));
1252 /* If Pool specified, add PoolId specification */
1256 memset(&pr, 0, sizeof(pr));
1257 bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1258 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1259 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
1260 edit_int64(pr.PoolId, ed1));
1262 ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1266 /* Find JobId of last Full backup for this client, fileset */
1267 edit_int64(cr.ClientId, ed1);
1268 Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1270 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1271 ua->error_msg("%s\n", db_strerror(ua->db));
1275 /* Find all Volumes used by that JobId */
1276 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1277 ua->error_msg("%s\n", db_strerror(ua->db));
1281 /* Note, this is needed because I don't seem to get the callback
1282 * from the call just above.
1285 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1286 ua->warning_msg("%s\n", db_strerror(ua->db));
1288 if (rx->JobTDate == 0) {
1289 ua->error_msg(_("No Full backup before %s found.\n"), date);
1293 /* Now find most recent Differental Job after Full save, if any */
1294 Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1295 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1296 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1297 ua->warning_msg("%s\n", db_strerror(ua->db));
1299 /* Now update JobTDate to look into Differental, if any */
1301 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1302 ua->warning_msg("%s\n", db_strerror(ua->db));
1304 if (rx->JobTDate == 0) {
1305 ua->error_msg(_("No Full backup before %s found.\n"), date);
1309 /* Now find all Incremental Jobs after Full/dif save */
1310 Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1311 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1312 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1313 ua->warning_msg("%s\n", db_strerror(ua->db));
1316 /* Get the JobIds from that list */
1317 rx->last_jobid[0] = rx->BaseJobIds[0] = rx->JobIds[0] = 0;
1319 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1320 ua->warning_msg("%s\n", db_strerror(ua->db));
1323 if (rx->JobIds[0] != 0) {
1324 if (find_arg(ua, NT_("copies")) > 0) {
1325 /* Display a list of all copies */
1326 db_list_copies_records(ua->jcr, ua->db, 0, rx->JobIds,
1327 prtit, ua, HORZ_LIST);
1329 /* Display a list of Jobs selected for this restore */
1330 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
1333 if (!db_get_used_base_jobids(ua->jcr, ua->db, rx->JobIds, rx->BaseJobIds)) {
1334 ua->warning_msg("%s", db_strerror(ua->db));
1337 if (*rx->BaseJobIds) {
1339 Mmsg(buf, uar_print_jobs, rx->BaseJobIds);
1340 ua->send_msg(_("The restore will use the following jobs as Base\n"));
1341 db_list_sql_query(ua->jcr, ua->db, buf.c_str(), prtit, ua, 1, HORZ_LIST);
1345 ua->warning_msg(_("No jobs found.\n"));
1349 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1350 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1356 * Return next JobId from comma separated list
1359 * 1 if next JobId returned
1360 * 0 if no more JobIds are in list
1361 * -1 there is an error
1363 int get_next_jobid_from_list(char **p, JobId_t *JobId)
1365 const int maxlen = 30;
1366 char jobid[maxlen+1];
1370 for (int i=0; i<maxlen; i++) {
1373 } else if (*q == ',') {
1380 if (jobid[0] == 0) {
1382 } else if (!is_a_number(jobid)) {
1383 return -1; /* error */
1386 *JobId = str_to_int64(jobid);
1390 static int restore_count_handler(void *ctx, int num_fields, char **row)
1392 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1393 rx->JobId = str_to_int64(row[0]);
1399 * Callback handler to get JobId and FileIndex for files
1400 * can insert more than one depending on the caller.
1402 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1404 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1406 Dmsg2(200, "JobId=%s FileIndex=%s\n", row[0], row[1]);
1407 rx->JobId = str_to_int64(row[0]);
1408 add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1410 rx->selected_files++;
1415 * Callback handler make list of JobIds
1417 static int jobid_handler(void *ctx, int num_fields, char **row)
1419 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1421 if (strcmp(rx->last_jobid, row[0]) == 0) {
1422 return 0; /* duplicate id */
1424 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1425 if (rx->JobIds[0] != 0) {
1426 pm_strcat(rx->JobIds, ",");
1428 pm_strcat(rx->JobIds, row[0]);
1434 * Callback handler to pickup last Full backup JobTDate
1436 static int last_full_handler(void *ctx, int num_fields, char **row)
1438 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1440 rx->JobTDate = str_to_int64(row[1]);
1445 * Callback handler build FileSet name prompt list
1447 static int fileset_handler(void *ctx, int num_fields, char **row)
1449 /* row[0] = FileSet (name) */
1451 add_prompt((UAContext *)ctx, row[0]);
1457 * Free names in the list
1459 static void free_name_list(NAME_LIST *name_list)
1461 for (int i=0; i < name_list->num_ids; i++) {
1462 free(name_list->name[i]);
1464 if (name_list->name) {
1465 free(name_list->name);
1466 name_list->name = NULL;
1468 name_list->max_ids = 0;
1469 name_list->num_ids = 0;
1472 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType)
1477 Dmsg1(200, "Already have store=%s\n", rx.store->name());
1481 * Try looking up Storage by name
1484 foreach_res(store, R_STORAGE) {
1485 if (strcmp(Storage, store->name()) == 0) {
1486 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1495 /* Check if an explicit storage resource is given */
1497 int i = find_arg_with_value(ua, "storage");
1499 store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1500 if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1504 if (store && (store != rx.store)) {
1505 ua->info_msg(_("Warning default storage overridden by \"%s\" on command line.\n"),
1508 Dmsg1(200, "Set store=%s\n", rx.store->name());
1513 /* If no storage resource, try to find one from MediaType */
1516 foreach_res(store, R_STORAGE) {
1517 if (strcmp(MediaType, store->media_type) == 0) {
1518 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1520 Dmsg1(200, "Set store=%s\n", rx.store->name());
1521 ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1522 Storage, store->name(), MediaType);
1529 ua->warning_msg(_("\nUnable to find Storage resource for\n"
1530 "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1533 /* Take command line arg, or ask user if none */
1534 rx.store = get_storage_resource(ua, false /* don't use default */);
1536 Dmsg1(200, "Set store=%s\n", rx.store->name());