2 Bacula® - The Network Backup Solution
4 Copyright (C) 2002-2011 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 three of the GNU Affero 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 Affero 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
45 /* Imported functions */
46 extern void print_bsr(UAContext *ua, RBSR *bsr);
49 /* Forward referenced functions */
50 static int last_full_handler(void *ctx, int num_fields, char **row);
51 static int jobid_handler(void *ctx, int num_fields, char **row);
52 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx);
53 static int fileset_handler(void *ctx, int num_fields, char **row);
54 static void free_name_list(NAME_LIST *name_list);
55 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date);
56 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx);
57 static void free_rx(RESTORE_CTX *rx);
58 static void split_path_and_filename(UAContext *ua, RESTORE_CTX *rx, char *fname);
59 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row);
60 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
62 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
64 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir);
65 static int get_client_name(UAContext *ua, RESTORE_CTX *rx);
66 static int get_restore_client_name(UAContext *ua, RESTORE_CTX &rx);
67 static bool get_date(UAContext *ua, char *date, int date_len);
68 static int restore_count_handler(void *ctx, int num_fields, char **row);
69 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table);
70 static void get_and_display_basejobs(UAContext *ua, RESTORE_CTX *rx);
76 int restore_cmd(UAContext *ua, const char *cmd)
78 RESTORE_CTX rx; /* restore context */
83 char *escaped_bsr_name = NULL;
84 char *escaped_where_name = NULL;
85 char *strip_prefix, *add_prefix, *add_suffix, *regexp;
86 strip_prefix = add_prefix = add_suffix = regexp = NULL;
88 memset(&rx, 0, sizeof(rx));
89 rx.path = get_pool_memory(PM_FNAME);
90 rx.fname = get_pool_memory(PM_FNAME);
91 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, "comment");
99 rx.comment = ua->argv[i];
100 if (!is_comment_legal(ua, rx.comment)) {
105 i = find_arg_with_value(ua, "where");
107 rx.where = ua->argv[i];
110 i = find_arg_with_value(ua, "replace");
112 rx.replace = ua->argv[i];
116 i = find_arg_with_value(ua, "strip_prefix");
118 strip_prefix = ua->argv[i];
121 i = find_arg_with_value(ua, "add_prefix");
123 add_prefix = ua->argv[i];
126 i = find_arg_with_value(ua, "add_suffix");
128 add_suffix = ua->argv[i];
131 i = find_arg_with_value(ua, "regexwhere");
133 rx.RegexWhere = ua->argv[i];
136 if (strip_prefix || add_suffix || add_prefix) {
137 int len = bregexp_get_build_where_size(strip_prefix, add_prefix, add_suffix);
138 regexp = (char *)bmalloc(len * sizeof(char));
140 bregexp_build_where(regexp, len, strip_prefix, add_prefix, add_suffix);
141 rx.RegexWhere = regexp;
144 /* TODO: add acl for regexwhere ? */
147 if (!acl_access_ok(ua, Where_ACL, rx.RegexWhere)) {
148 ua->error_msg(_("\"RegexWhere\" specification not authorized.\n"));
154 if (!acl_access_ok(ua, Where_ACL, rx.where)) {
155 ua->error_msg(_("\"where\" specification not authorized.\n"));
160 if (!open_client_db(ua)) {
164 /* Ensure there is at least one Restore Job */
166 foreach_res(job, R_JOB) {
167 if (job->JobType == JT_RESTORE) {
168 if (!rx.restore_job) {
169 rx.restore_job = job;
175 if (!rx.restore_jobs) {
177 "No Restore Job Resource found in bacula-dir.conf.\n"
178 "You must create at least one before running this command.\n"));
183 * Request user to select JobIds or files by various different methods
184 * last 20 jobs, where File saved, most recent backup, ...
185 * In the end, a list of files are pumped into
188 switch (user_select_jobids_or_files(ua, &rx)) {
191 case 1: /* selected by jobid */
192 get_and_display_basejobs(ua, &rx);
193 if (!build_directory_tree(ua, &rx)) {
194 ua->send_msg(_("Restore not done.\n"));
198 case 2: /* selected by filename, no tree needed */
204 if (!complete_bsr(ua, rx.bsr)) { /* find Vol, SessId, SessTime from JobIds */
205 ua->error_msg(_("Unable to construct a valid BSR. Cannot continue.\n"));
208 if (!(rx.selected_files = write_bsr_file(ua, rx))) {
209 ua->warning_msg(_("No files selected to be restored.\n"));
212 display_bsr_info(ua, rx); /* display vols needed, etc */
214 if (rx.selected_files==1) {
215 ua->info_msg(_("\n1 file selected to be restored.\n\n"));
217 ua->info_msg(_("\n%s files selected to be restored.\n\n"),
218 edit_uint64_with_commas(rx.selected_files, ed1));
221 ua->warning_msg(_("No files selected to be restored.\n"));
225 if (rx.restore_jobs == 1) {
226 job = rx.restore_job;
228 job = get_restore_job(ua);
234 get_client_name(ua, &rx);
235 if (!rx.ClientName) {
236 ua->error_msg(_("No Client resource found!\n"));
239 get_restore_client_name(ua, rx);
241 escaped_bsr_name = escape_filename(jcr->RestoreBootstrap);
244 "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
245 " bootstrap=\"%s\" files=%u catalog=\"%s\"",
246 job->name(), rx.ClientName, rx.RestoreClientName,
247 rx.store?rx.store->name():"",
248 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
249 rx.selected_files, ua->catalog->name());
251 /* Build run command */
254 escaped_where_name = escape_filename(rx.RegexWhere);
255 Mmsg(buf, " regexwhere=\"%s\"",
256 escaped_where_name ? escaped_where_name : rx.RegexWhere);
258 } else if (rx.where) {
259 escaped_where_name = escape_filename(rx.where);
260 Mmsg(buf," where=\"%s\"",
261 escaped_where_name ? escaped_where_name : rx.where);
263 pm_strcat(ua->cmd, buf);
266 Mmsg(buf, " replace=%s", rx.replace);
267 pm_strcat(ua->cmd, buf);
271 Mmsg(buf, " comment=\"%s\"", rx.comment);
272 pm_strcat(ua->cmd, buf);
275 if (escaped_bsr_name != NULL) {
276 bfree(escaped_bsr_name);
279 if (escaped_where_name != NULL) {
280 bfree(escaped_where_name);
287 if (find_arg(ua, NT_("yes")) > 0) {
288 pm_strcat(ua->cmd, " yes"); /* pass it on to the run command */
290 Dmsg1(200, "Submitting: %s\n", ua->cmd);
291 /* Transfer jobids to jcr to for picking up restore objects */
292 jcr->JobIds = rx.JobIds;
295 run_cmd(ua, ua->cmd);
297 garbage_collect_memory(); /* release unused memory */
301 if (escaped_bsr_name != NULL) {
302 bfree(escaped_bsr_name);
305 if (escaped_where_name != NULL) {
306 bfree(escaped_where_name);
314 garbage_collect_memory(); /* release unused memory */
320 * Fill the rx->BaseJobIds and display the list
322 static void get_and_display_basejobs(UAContext *ua, RESTORE_CTX *rx)
326 if (!db_get_used_base_jobids(ua->jcr, ua->db, rx->JobIds, &jobids)) {
327 ua->warning_msg("%s", db_strerror(ua->db));
332 Mmsg(q, uar_print_jobs, jobids.list);
333 ua->send_msg(_("The restore will use the following job(s) as Base\n"));
334 db_list_sql_query(ua->jcr, ua->db, q.c_str(), prtit, ua, 1, HORZ_LIST);
336 pm_strcpy(rx->BaseJobIds, jobids.list);
339 static void free_rx(RESTORE_CTX *rx)
343 free_and_null_pool_memory(rx->JobIds);
344 free_and_null_pool_memory(rx->BaseJobIds);
345 free_and_null_pool_memory(rx->fname);
346 free_and_null_pool_memory(rx->path);
347 free_and_null_pool_memory(rx->query);
348 free_name_list(&rx->name_list);
351 static bool has_value(UAContext *ua, int i)
354 ua->error_msg(_("Missing value for keyword: %s\n"), ua->argk[i]);
361 * This gets the client name from which the backup was made
363 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
365 /* If no client name specified yet, get it now */
366 if (!rx->ClientName[0]) {
368 /* try command line argument */
369 int i = find_arg_with_value(ua, NT_("client"));
371 i = find_arg_with_value(ua, NT_("backupclient"));
374 if (!is_name_valid(ua->argv[i], &ua->errmsg)) {
375 ua->error_msg("%s argument: %s", ua->argk[i], ua->errmsg);
378 bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
381 memset(&cr, 0, sizeof(cr));
382 if (!get_client_dbr(ua, &cr)) {
385 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
391 * This is where we pick up a client name to restore to.
393 static int get_restore_client_name(UAContext *ua, RESTORE_CTX &rx)
395 /* Start with same name as backup client */
396 bstrncpy(rx.RestoreClientName, rx.ClientName, sizeof(rx.RestoreClientName));
398 /* try command line argument */
399 int i = find_arg_with_value(ua, NT_("restoreclient"));
401 if (!is_name_valid(ua->argv[i], &ua->errmsg)) {
402 ua->error_msg("%s argument: %s", ua->argk[i], ua->errmsg);
405 bstrncpy(rx.RestoreClientName, ua->argv[i], sizeof(rx.RestoreClientName));
414 * The first step in the restore process is for the user to
415 * select a list of JobIds from which he will subsequently
416 * select which files are to be restored.
418 * Returns: 2 if filename list made
419 * 1 if jobid list made
422 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
425 char date[MAX_TIME_LENGTH];
426 bool have_date = false;
427 /* Include current second if using current time */
428 utime_t now = time(NULL) + 1;
430 JOB_DBR jr = { (JobId_t)-1 };
433 const char *list[] = {
434 _("List last 20 Jobs run"),
435 _("List Jobs where a given File is saved"),
436 _("Enter list of comma separated JobIds to select"),
437 _("Enter SQL list command"),
438 _("Select the most recent backup for a client"),
439 _("Select backup for a client before a specified time"),
440 _("Enter a list of files to restore"),
441 _("Enter a list of files to restore before a specified time"),
442 _("Find the JobIds of the most recent backup for a client"),
443 _("Find the JobIds for a backup for a client before a specified time"),
444 _("Enter a list of directories to restore for found JobIds"),
445 _("Select full restore to a specified Job date"),
450 /* These keywords are handled in a for loop */
460 /* The keyword below are handled by individual arg lookups */
466 "bootstrap", /* 13 */
468 "strip_prefix", /* 15 */
469 "add_prefix", /* 16 */
470 "add_suffix", /* 17 */
471 "regexwhere", /* 18 */
472 "restoreclient", /* 19 */
475 "restorejob", /* 22 */
482 for (i=1; i<ua->argc; i++) { /* loop through arguments */
483 bool found_kw = false;
484 for (j=0; kw[j]; j++) { /* loop through keywords */
485 if (strcasecmp(kw[j], ua->argk[i]) == 0) {
491 ua->error_msg(_("Unknown keyword: %s\n"), ua->argk[i]);
494 /* Found keyword in kw[] list, process it */
497 if (!has_value(ua, i)) {
500 if (*rx->JobIds != 0) {
501 pm_strcat(rx->JobIds, ",");
503 pm_strcat(rx->JobIds, ua->argv[i]);
506 case 1: /* current */
508 * Note, we add one second here just to include any job
509 * that may have finished within the current second,
510 * which happens a lot in scripting small jobs.
512 bstrutime(date, sizeof(date), now);
516 if (have_date || !has_value(ua, i)) {
519 if (str_to_utime(ua->argv[i]) == 0) {
520 ua->error_msg(_("Improper date format: %s\n"), ua->argv[i]);
523 bstrncpy(date, ua->argv[i], sizeof(date));
528 if (!has_value(ua, i)) {
532 bstrutime(date, sizeof(date), now);
534 if (!get_client_name(ua, rx)) {
537 pm_strcpy(ua->cmd, ua->argv[i]);
538 insert_one_file_or_dir(ua, rx, date, j==4);
542 bstrutime(date, sizeof(date), now);
544 if (!select_backups_before_date(ua, rx, date)) {
549 case 6: /* pool specified */
550 if (!has_value(ua, i)) {
553 rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
555 ua->error_msg(_("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
558 if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
560 ua->error_msg(_("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
564 case 7: /* all specified */
568 * All keywords 7 or greater are ignored or handled by a select prompt
576 ua->send_msg(_("\nFirst you select one or more JobIds that contain files\n"
577 "to be restored. You will be presented several methods\n"
578 "of specifying the JobIds. Then you will be allowed to\n"
579 "select which files from those JobIds are to be restored.\n\n"));
582 /* If choice not already made above, prompt */
589 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
590 for (int i=0; list[i]; i++) {
591 add_prompt(ua, list[i]);
594 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
595 case -1: /* error or cancel */
597 case 0: /* list last 20 Jobs run */
598 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
599 ua->error_msg(_("SQL query not authorized.\n"));
602 gui_save = ua->jcr->gui;
604 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
605 ua->jcr->gui = gui_save;
608 case 1: /* list where a file is saved */
609 if (!get_client_name(ua, rx)) {
612 if (!get_cmd(ua, _("Enter Filename (no path):"))) {
615 len = strlen(ua->cmd);
616 fname = (char *)malloc(len * 2 + 1);
617 db_escape_string(ua->jcr, ua->db, fname, ua->cmd, len);
618 Mmsg(rx->query, uar_file[db_get_type_index(ua->db)], rx->ClientName, fname);
620 gui_save = ua->jcr->gui;
622 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
623 ua->jcr->gui = gui_save;
626 case 2: /* enter a list of JobIds */
627 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
630 pm_strcpy(rx->JobIds, ua->cmd);
632 case 3: /* Enter an SQL list command */
633 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
634 ua->error_msg(_("SQL query not authorized.\n"));
637 if (!get_cmd(ua, _("Enter SQL list command: "))) {
640 gui_save = ua->jcr->gui;
642 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
643 ua->jcr->gui = gui_save;
646 case 4: /* Select the most recent backups */
648 bstrutime(date, sizeof(date), now);
650 if (!select_backups_before_date(ua, rx, date)) {
654 case 5: /* select backup at specified time */
656 if (!get_date(ua, date, sizeof(date))) {
660 if (!select_backups_before_date(ua, rx, date)) {
664 case 6: /* Enter files */
666 bstrutime(date, sizeof(date), now);
668 if (!get_client_name(ua, rx)) {
671 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
672 "containing a list of file names with paths, and terminate\n"
673 "them with a blank line.\n"));
675 if (!get_cmd(ua, _("Enter full filename: "))) {
678 len = strlen(ua->cmd);
682 insert_one_file_or_dir(ua, rx, date, false);
685 case 7: /* enter files backed up before specified time */
687 if (!get_date(ua, date, sizeof(date))) {
691 if (!get_client_name(ua, rx)) {
694 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
695 "containing a list of file names with paths, and terminate\n"
696 "them with a blank line.\n"));
698 if (!get_cmd(ua, _("Enter full filename: "))) {
701 len = strlen(ua->cmd);
705 insert_one_file_or_dir(ua, rx, date, false);
709 case 8: /* Find JobIds for current backup */
711 bstrutime(date, sizeof(date), now);
713 if (!select_backups_before_date(ua, rx, date)) {
719 case 9: /* Find JobIds for give date */
721 if (!get_date(ua, date, sizeof(date))) {
725 if (!select_backups_before_date(ua, rx, date)) {
731 case 10: /* Enter directories */
732 if (*rx->JobIds != 0) {
733 ua->send_msg(_("You have already selected the following JobIds: %s\n"),
735 } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
736 if (*rx->JobIds != 0 && *ua->cmd) {
737 pm_strcat(rx->JobIds, ",");
739 pm_strcat(rx->JobIds, ua->cmd);
741 if (*rx->JobIds == 0 || *rx->JobIds == '.') {
743 return 0; /* nothing entered, return */
746 bstrutime(date, sizeof(date), now);
748 if (!get_client_name(ua, rx)) {
751 ua->send_msg(_("Enter full directory names or start the name\n"
752 "with a < to indicate it is a filename containing a list\n"
753 "of directories and terminate them with a blank line.\n"));
755 if (!get_cmd(ua, _("Enter directory name: "))) {
758 len = strlen(ua->cmd);
762 /* Add trailing slash to end of directory names */
763 if (ua->cmd[0] != '<' && !IsPathSeparator(ua->cmd[len-1])) {
764 strcat(ua->cmd, "/");
766 insert_one_file_or_dir(ua, rx, date, true);
770 case 11: /* Choose a jobid and select jobs */
771 if (!get_cmd(ua, _("Enter JobId to get the state to restore: ")) ||
772 !is_an_integer(ua->cmd))
777 memset(&jr, 0, sizeof(JOB_DBR));
778 jr.JobId = str_to_int64(ua->cmd);
779 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
780 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
781 ua->cmd, db_strerror(ua->db));
784 ua->send_msg(_("Selecting jobs to build the Full state at %s\n"),
786 jr.JobLevel = L_INCREMENTAL; /* Take Full+Diff+Incr */
787 if (!db_accurate_get_jobids(ua->jcr, ua->db, &jr, &jobids)) {
790 pm_strcpy(rx->JobIds, jobids.list);
791 Dmsg1(30, "Item 12: jobids = %s\n", rx->JobIds);
793 case 12: /* Cancel or quit */
798 memset(&jr, 0, sizeof(JOB_DBR));
799 POOLMEM *JobIds = get_pool_memory(PM_FNAME);
803 * Find total number of files to be restored, and filter the JobId
804 * list to contain only ones permitted by the ACL conditions.
806 for (p=rx->JobIds; ; ) {
808 int stat = get_next_jobid_from_list(&p, &JobId);
810 ua->error_msg(_("Invalid JobId in list.\n"));
811 free_pool_memory(JobIds);
817 if (jr.JobId == JobId) {
818 continue; /* duplicate of last JobId */
820 memset(&jr, 0, sizeof(JOB_DBR));
822 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
823 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
824 edit_int64(JobId, ed1), db_strerror(ua->db));
825 free_pool_memory(JobIds);
828 if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
829 ua->error_msg(_("Access to JobId=%s (Job \"%s\") not authorized. Not selected.\n"),
830 edit_int64(JobId, ed1), jr.Name);
834 pm_strcat(JobIds, ",");
836 pm_strcat(JobIds, edit_int64(JobId, ed1));
837 rx->TotalFiles += jr.JobFiles;
839 free_pool_memory(rx->JobIds);
840 rx->JobIds = JobIds; /* Set ACL filtered list */
841 if (*rx->JobIds == 0) {
842 ua->warning_msg(_("No Jobs selected.\n"));
846 if (strchr(rx->JobIds,',')) {
847 ua->info_msg(_("You have selected the following JobIds: %s\n"), rx->JobIds);
849 ua->info_msg(_("You have selected the following JobId: %s\n"), rx->JobIds);
857 static bool get_date(UAContext *ua, char *date, int date_len)
859 ua->send_msg(_("The restored files will the most current backup\n"
860 "BEFORE the date you specify below.\n\n"));
862 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
865 if (str_to_utime(ua->cmd) != 0) {
868 ua->error_msg(_("Improper date format.\n"));
870 bstrncpy(date, ua->cmd, date_len);
875 * Insert a single file, or read a list of files from a file
877 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
887 if ((ffd = fopen(p, "rb")) == NULL) {
889 ua->error_msg(_("Cannot open file %s: ERR=%s\n"),
893 while (fgets(file, sizeof(file), ffd)) {
896 if (!insert_dir_into_findex_list(ua, rx, file, date)) {
897 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
900 if (!insert_file_into_findex_list(ua, rx, file, date)) {
901 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
909 insert_table_into_findex_list(ua, rx, p);
913 insert_dir_into_findex_list(ua, rx, ua->cmd, date);
915 insert_file_into_findex_list(ua, rx, ua->cmd, date);
922 * For a given file (path+filename), split into path and file, then
923 * lookup the most recent backup in the catalog to get the JobId
924 * and FileIndex, then insert them into the findex list.
926 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
929 strip_trailing_newline(file);
930 split_path_and_filename(ua, rx, file);
931 if (*rx->JobIds == 0) {
932 Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname,
935 Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
936 rx->path, rx->fname, 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"), file);
946 // ua->error_msg("Query=%s\n", rx->query);
953 * For a given path lookup the most recent backup in the catalog
954 * to get the JobId and FileIndexes of all files in that directory.
956 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
959 strip_trailing_junk(dir);
960 if (*rx->JobIds == 0) {
961 ua->error_msg(_("No JobId specified cannot continue.\n"));
964 Mmsg(rx->query, uar_jobid_fileindex_from_dir[db_get_type_index(ua->db)], rx->JobIds, dir, rx->ClientName);
967 /* Find and insert jobid and File Index */
968 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
969 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
970 rx->query, db_strerror(ua->db));
973 ua->error_msg(_("No database record found for: %s\n"), dir);
980 * Get the JobId and FileIndexes of all files in the specified table
982 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
984 strip_trailing_junk(table);
985 Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
988 /* Find and insert jobid and File Index */
989 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
990 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
991 rx->query, db_strerror(ua->db));
994 ua->error_msg(_("No table found: %s\n"), table);
1000 static void split_path_and_filename(UAContext *ua, RESTORE_CTX *rx, char *name)
1004 /* Find path without the filename.
1005 * I.e. everything after the last / is a "filename".
1006 * OK, maybe it is a directory name, but we treat it like
1007 * a filename. If we don't find a / then the whole name
1008 * must be a path name (e.g. c:).
1010 for (p=f=name; *p; p++) {
1011 if (IsPathSeparator(*p)) {
1012 f = p; /* set pos of last slash */
1015 if (IsPathSeparator(*f)) { /* did we find a slash? */
1016 f++; /* yes, point to filename */
1017 } else { /* no, whole thing must be path name */
1021 /* If filename doesn't exist (i.e. root directory), we
1022 * simply create a blank name consisting of a single
1023 * space. This makes handling zero length filenames
1028 rx->fname = check_pool_memory_size(rx->fname, 2*(rx->fnl)+1);
1029 db_escape_string(ua->jcr, ua->db, rx->fname, f, rx->fnl);
1037 rx->path = check_pool_memory_size(rx->path, 2*(rx->pnl)+1);
1038 db_escape_string(ua->jcr, ua->db, rx->path, name, rx->pnl);
1044 Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
1047 static bool ask_for_fileregex(UAContext *ua, RESTORE_CTX *rx)
1049 if (find_arg(ua, NT_("all")) >= 0) { /* if user enters all on command line */
1050 return true; /* select everything */
1052 ua->send_msg(_("\n\nFor one or more of the JobIds selected, no files were found,\n"
1053 "so file selection is not possible.\n"
1054 "Most likely your retention policy pruned the files.\n"));
1055 if (get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
1056 if (ua->pint32_val == 1)
1058 while (get_cmd(ua, _("\nRegexp matching files to restore? (empty to abort): "))) {
1059 if (ua->cmd[0] == '\0') {
1062 regex_t *fileregex_re = NULL;
1064 char errmsg[500] = "";
1066 fileregex_re = (regex_t *)bmalloc(sizeof(regex_t));
1067 rc = regcomp(fileregex_re, ua->cmd, REG_EXTENDED|REG_NOSUB);
1069 regerror(rc, fileregex_re, errmsg, sizeof(errmsg));
1071 regfree(fileregex_re);
1074 ua->send_msg(_("Regex compile error: %s\n"), errmsg);
1076 rx->bsr->fileregex = bstrdup(ua->cmd);
1085 /* Walk on the delta_list of a TREE_NODE item and insert all parts
1086 * TODO: Optimize for bootstrap creation, remove recursion
1087 * 6 -> 5 -> 4 -> 3 -> 2 -> 1 -> 0
1089 * 0, 1, 2, 3, 4, 5, 6
1091 static void add_delta_list_findex(RESTORE_CTX *rx, struct delta_list *lst)
1097 add_delta_list_findex(rx, lst->next);
1099 add_findex(rx->bsr, lst->JobId, lst->FileIndex);
1102 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
1105 JobId_t JobId, last_JobId;
1110 memset(&tree, 0, sizeof(TREE_CTX));
1112 * Build the directory tree containing JobIds user selected
1114 tree.root = new_tree(rx->TotalFiles);
1119 * For display purposes, the same JobId, with different volumes may
1120 * appear more than once, however, we only insert it once.
1123 tree.FileEstimate = 0;
1124 if (get_next_jobid_from_list(&p, &JobId) > 0) {
1125 /* Use first JobId as estimate of the number of files to restore */
1126 Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
1127 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1128 ua->error_msg("%s\n", db_strerror(ua->db));
1131 /* Add about 25% more than this job for over estimate */
1132 tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
1133 tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
1137 ua->info_msg(_("\nBuilding directory tree for JobId(s) %s ... "),
1140 #define new_get_file_list
1141 #ifdef new_get_file_list
1142 if (!db_get_file_list(ua->jcr, ua->db,
1143 rx->JobIds, false /* do not use md5 */,
1144 true /* get delta */,
1145 insert_tree_handler, (void *)&tree))
1147 ua->error_msg("%s", db_strerror(ua->db));
1149 if (*rx->BaseJobIds) {
1150 pm_strcat(rx->JobIds, ",");
1151 pm_strcat(rx->JobIds, rx->BaseJobIds);
1154 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1157 if (JobId == last_JobId) {
1158 continue; /* eliminate duplicate JobIds */
1162 * Find files for this JobId and insert them in the tree
1164 Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
1165 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
1166 ua->error_msg("%s", db_strerror(ua->db));
1171 * At this point, the tree is built, so we can garbage collect
1172 * any memory released by the SQL engine that RedHat has
1173 * not returned to the OS :-(
1175 garbage_collect_memory();
1178 * Look at the first JobId on the list (presumably the oldest) and
1179 * if it is marked purged, don't do the manual selection because
1180 * the Job was pruned, so the tree is incomplete.
1182 if (tree.FileCount != 0) {
1183 /* Find out if any Job is purged */
1184 Mmsg(rx->query, "SELECT SUM(PurgedFiles) FROM Job WHERE JobId IN (%s)", rx->JobIds);
1185 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1186 ua->error_msg("%s\n", db_strerror(ua->db));
1188 /* rx->JobId is the PurgedFiles flag */
1189 if (rx->found && rx->JobId > 0) {
1190 tree.FileCount = 0; /* set count to zero, no tree selection */
1193 if (tree.FileCount == 0) {
1194 OK = ask_for_fileregex(ua, rx);
1197 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1198 if (JobId == last_JobId) {
1199 continue; /* eliminate duplicate JobIds */
1201 add_findex_all(rx->bsr, JobId);
1207 ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
1208 edit_uint64_with_commas(tree.FileCount, ec1));
1210 ua->info_msg(_("\n%s files inserted into the tree.\n"),
1211 edit_uint64_with_commas(tree.FileCount, ec1));
1214 if (find_arg(ua, NT_("done")) < 0) {
1215 /* Let the user interact in selecting which files to restore */
1216 OK = user_select_files_from_tree(&tree);
1220 * Walk down through the tree finding all files marked to be
1221 * extracted making a bootstrap file.
1224 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
1225 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
1226 if (node->extract || node->extract_dir) {
1227 Dmsg3(400, "JobId=%lld type=%d FI=%d\n", (uint64_t)node->JobId, node->type, node->FileIndex);
1228 /* TODO: optimize bsr insertion when jobid are non sorted */
1229 add_delta_list_findex(rx, node->delta_list);
1230 add_findex(rx->bsr, node->JobId, node->FileIndex);
1231 if (node->extract && node->type != TN_NEWDIR) {
1232 rx->selected_files++; /* count only saved files */
1239 free_tree(tree.root); /* free the directory tree */
1245 * This routine is used to get the current backup or a backup
1246 * before the specified date.
1248 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
1253 char fileset_name[MAX_NAME_LENGTH];
1254 char ed1[50], ed2[50];
1255 char pool_select[MAX_NAME_LENGTH];
1258 /* Create temp tables */
1259 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1260 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1261 if (!db_sql_query(ua->db, uar_create_temp[db_get_type_index(ua->db)], NULL, NULL)) {
1262 ua->error_msg("%s\n", db_strerror(ua->db));
1264 if (!db_sql_query(ua->db, uar_create_temp1[db_get_type_index(ua->db)], NULL, NULL)) {
1265 ua->error_msg("%s\n", db_strerror(ua->db));
1268 * Select Client from the Catalog
1270 memset(&cr, 0, sizeof(cr));
1271 if (!get_client_dbr(ua, &cr)) {
1274 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1279 memset(&fsr, 0, sizeof(fsr));
1280 i = find_arg_with_value(ua, "FileSet");
1282 if (i >= 0 && is_name_valid(ua->argv[i], &ua->errmsg)) {
1283 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1284 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1285 ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1286 db_strerror(ua->db));
1289 } else if (i >= 0) { /* name is invalid */
1290 ua->error_msg(_("FileSet argument: %s\n"), ua->errmsg);
1293 if (i < 0) { /* fileset not found */
1294 edit_int64(cr.ClientId, ed1);
1295 Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1296 start_prompt(ua, _("The defined FileSet resources are:\n"));
1297 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1298 ua->error_msg("%s\n", db_strerror(ua->db));
1300 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1301 fileset_name, sizeof(fileset_name)) < 0) {
1302 ua->error_msg(_("No FileSet found for client \"%s\".\n"), cr.Name);
1306 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1307 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1308 ua->warning_msg(_("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1309 ua->send_msg(_("This probably means you modified the FileSet.\n"
1310 "Continuing anyway.\n"));
1314 /* If Pool specified, add PoolId specification */
1318 memset(&pr, 0, sizeof(pr));
1319 bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1320 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1321 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
1322 edit_int64(pr.PoolId, ed1));
1324 ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1328 /* Find JobId of last Full backup for this client, fileset */
1329 edit_int64(cr.ClientId, ed1);
1330 Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1332 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1333 ua->error_msg("%s\n", db_strerror(ua->db));
1337 /* Find all Volumes used by that JobId */
1338 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1339 ua->error_msg("%s\n", db_strerror(ua->db));
1343 /* Note, this is needed because I don't seem to get the callback
1344 * from the call just above.
1347 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1348 ua->warning_msg("%s\n", db_strerror(ua->db));
1350 if (rx->JobTDate == 0) {
1351 ua->error_msg(_("No Full backup before %s found.\n"), date);
1355 /* Now find most recent Differental Job after Full save, if any */
1356 Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1357 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1358 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1359 ua->warning_msg("%s\n", db_strerror(ua->db));
1361 /* Now update JobTDate to look into Differental, if any */
1363 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1364 ua->warning_msg("%s\n", db_strerror(ua->db));
1366 if (rx->JobTDate == 0) {
1367 ua->error_msg(_("No Full backup before %s found.\n"), date);
1371 /* Now find all Incremental Jobs after Full/dif save */
1372 Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1373 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1374 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1375 ua->warning_msg("%s\n", db_strerror(ua->db));
1378 /* Get the JobIds from that list */
1379 rx->last_jobid[0] = rx->JobIds[0] = 0;
1381 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1382 ua->warning_msg("%s\n", db_strerror(ua->db));
1385 if (rx->JobIds[0] != 0) {
1386 if (find_arg(ua, NT_("copies")) > 0) {
1387 /* Display a list of all copies */
1388 db_list_copies_records(ua->jcr, ua->db, 0, rx->JobIds,
1389 prtit, ua, HORZ_LIST);
1391 /* Display a list of Jobs selected for this restore */
1392 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1,HORZ_LIST);
1396 ua->warning_msg(_("No jobs found.\n"));
1400 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1401 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1405 static int restore_count_handler(void *ctx, int num_fields, char **row)
1407 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1408 rx->JobId = str_to_int64(row[0]);
1414 * Callback handler to get JobId and FileIndex for files
1415 * can insert more than one depending on the caller.
1417 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1419 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1421 Dmsg2(200, "JobId=%s FileIndex=%s\n", row[0], row[1]);
1422 rx->JobId = str_to_int64(row[0]);
1423 add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1425 rx->selected_files++;
1430 * Callback handler make list of JobIds
1432 static int jobid_handler(void *ctx, int num_fields, char **row)
1434 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1436 if (strcmp(rx->last_jobid, row[0]) == 0) {
1437 return 0; /* duplicate id */
1439 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1440 if (rx->JobIds[0] != 0) {
1441 pm_strcat(rx->JobIds, ",");
1443 pm_strcat(rx->JobIds, row[0]);
1449 * Callback handler to pickup last Full backup JobTDate
1451 static int last_full_handler(void *ctx, int num_fields, char **row)
1453 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1455 rx->JobTDate = str_to_int64(row[1]);
1460 * Callback handler build FileSet name prompt list
1462 static int fileset_handler(void *ctx, int num_fields, char **row)
1464 /* row[0] = FileSet (name) */
1466 add_prompt((UAContext *)ctx, row[0]);
1472 * Free names in the list
1474 static void free_name_list(NAME_LIST *name_list)
1476 for (int i=0; i < name_list->num_ids; i++) {
1477 free(name_list->name[i]);
1479 bfree_and_null(name_list->name);
1480 name_list->max_ids = 0;
1481 name_list->num_ids = 0;
1484 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType)
1489 Dmsg1(200, "Already have store=%s\n", rx.store->name());
1493 * Try looking up Storage by name
1496 foreach_res(store, R_STORAGE) {
1497 if (strcmp(Storage, store->name()) == 0) {
1498 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1507 /* Check if an explicit storage resource is given */
1509 int i = find_arg_with_value(ua, "storage");
1511 store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1512 if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1516 if (store && (store != rx.store)) {
1517 ua->info_msg(_("Warning default storage overridden by \"%s\" on command line.\n"),
1520 Dmsg1(200, "Set store=%s\n", rx.store->name());
1525 /* If no storage resource, try to find one from MediaType */
1528 foreach_res(store, R_STORAGE) {
1529 if (strcmp(MediaType, store->media_type) == 0) {
1530 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1532 Dmsg1(200, "Set store=%s\n", rx.store->name());
1533 ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1534 Storage, store->name(), MediaType);
1541 ua->warning_msg(_("\nUnable to find Storage resource for\n"
1542 "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1545 /* Take command line arg, or ask user if none */
1546 rx.store = get_storage_resource(ua, false /* don't use default */);
1548 Dmsg1(200, "Set store=%s\n", rx.store->name());