2 Bacula® - The Network Backup Solution
4 Copyright (C) 2002-2009 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);
73 static void get_and_display_basejobs(UAContext *ua, RESTORE_CTX *rx);
79 int restore_cmd(UAContext *ua, const char *cmd)
81 RESTORE_CTX rx; /* restore context */
85 char *escaped_bsr_name = NULL;
86 char *escaped_where_name = NULL;
87 char *strip_prefix, *add_prefix, *add_suffix, *regexp;
88 strip_prefix = add_prefix = add_suffix = regexp = NULL;
90 memset(&rx, 0, sizeof(rx));
91 rx.path = get_pool_memory(PM_FNAME);
92 rx.fname = get_pool_memory(PM_FNAME);
93 rx.JobIds = get_pool_memory(PM_FNAME);
94 rx.BaseJobIds = get_pool_memory(PM_FNAME);
95 rx.query = get_pool_memory(PM_FNAME);
98 i = find_arg_with_value(ua, "where");
100 rx.where = ua->argv[i];
103 i = find_arg_with_value(ua, "strip_prefix");
105 strip_prefix = ua->argv[i];
108 i = find_arg_with_value(ua, "add_prefix");
110 add_prefix = ua->argv[i];
113 i = find_arg_with_value(ua, "add_suffix");
115 add_suffix = ua->argv[i];
118 i = find_arg_with_value(ua, "regexwhere");
120 rx.RegexWhere = ua->argv[i];
123 if (strip_prefix || add_suffix || add_prefix) {
124 int len = bregexp_get_build_where_size(strip_prefix, add_prefix, add_suffix);
125 regexp = (char *)bmalloc(len * sizeof(char));
127 bregexp_build_where(regexp, len, strip_prefix, add_prefix, add_suffix);
128 rx.RegexWhere = regexp;
131 /* TODO: add acl for regexwhere ? */
134 if (!acl_access_ok(ua, Where_ACL, rx.RegexWhere)) {
135 ua->error_msg(_("\"RegexWhere\" specification not authorized.\n"));
141 if (!acl_access_ok(ua, Where_ACL, rx.where)) {
142 ua->error_msg(_("\"where\" specification not authorized.\n"));
147 if (!open_client_db(ua)) {
151 /* Ensure there is at least one Restore Job */
153 foreach_res(job, R_JOB) {
154 if (job->JobType == JT_RESTORE) {
155 if (!rx.restore_job) {
156 rx.restore_job = job;
162 if (!rx.restore_jobs) {
164 "No Restore Job Resource found in bacula-dir.conf.\n"
165 "You must create at least one before running this command.\n"));
170 * Request user to select JobIds or files by various different methods
171 * last 20 jobs, where File saved, most recent backup, ...
172 * In the end, a list of files are pumped into
175 switch (user_select_jobids_or_files(ua, &rx)) {
178 case 1: /* selected by jobid */
179 get_and_display_basejobs(ua, &rx);
180 if (!build_directory_tree(ua, &rx)) {
181 ua->send_msg(_("Restore not done.\n"));
185 case 2: /* selected by filename, no tree needed */
191 if (!complete_bsr(ua, rx.bsr)) { /* find Vol, SessId, SessTime from JobIds */
192 ua->error_msg(_("Unable to construct a valid BSR. Cannot continue.\n"));
195 if (!(rx.selected_files = write_bsr_file(ua, rx))) {
196 ua->warning_msg(_("No files selected to be restored.\n"));
199 display_bsr_info(ua, rx); /* display vols needed, etc */
201 if (rx.selected_files==1) {
202 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"));
226 get_restore_client_name(ua, rx);
228 escaped_bsr_name = escape_filename(jcr->RestoreBootstrap);
230 /* Build run command */
232 escaped_where_name = escape_filename(rx.RegexWhere);
234 "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
235 " bootstrap=\"%s\" regexwhere=\"%s\" files=%u catalog=\"%s\"",
236 job->name(), rx.ClientName, rx.RestoreClientName,
237 rx.store?rx.store->name():"",
238 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
239 escaped_where_name ? escaped_where_name : rx.RegexWhere,
240 rx.selected_files, ua->catalog->name());
242 } else if (rx.where) {
243 escaped_where_name = escape_filename(rx.where);
245 "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
246 " bootstrap=\"%s\" where=\"%s\" files=%u catalog=\"%s\"",
247 job->name(), rx.ClientName, rx.RestoreClientName,
248 rx.store?rx.store->name():"",
249 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
250 escaped_where_name ? escaped_where_name : rx.where,
251 rx.selected_files, ua->catalog->name());
255 "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
256 " bootstrap=\"%s\" files=%u catalog=\"%s\"",
257 job->name(), rx.ClientName, rx.RestoreClientName,
258 rx.store?rx.store->name():"",
259 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
260 rx.selected_files, ua->catalog->name());
263 if (escaped_bsr_name != NULL) {
264 bfree(escaped_bsr_name);
267 if (escaped_where_name != NULL) {
268 bfree(escaped_where_name);
275 if (find_arg(ua, NT_("yes")) > 0) {
276 pm_strcat(ua->cmd, " yes"); /* pass it on to the run command */
278 Dmsg1(200, "Submitting: %s\n", ua->cmd);
280 run_cmd(ua, ua->cmd);
285 if (escaped_bsr_name != NULL) {
286 bfree(escaped_bsr_name);
289 if (escaped_where_name != NULL) {
290 bfree(escaped_where_name);
303 * Fill the rx->BaseJobIds and display the list
305 static void get_and_display_basejobs(UAContext *ua, RESTORE_CTX *rx)
309 if (!db_get_used_base_jobids(ua->jcr, ua->db, rx->JobIds, &jobids)) {
310 ua->warning_msg("%s", db_strerror(ua->db));
315 Mmsg(q, uar_print_jobs, jobids.list);
316 ua->send_msg(_("The restore will use the following job(s) as Base\n"));
317 db_list_sql_query(ua->jcr, ua->db, q.c_str(), prtit, ua, 1, HORZ_LIST);
319 pm_strcpy(rx->BaseJobIds, jobids.list);
322 static void free_rx(RESTORE_CTX *rx)
326 free_and_null_pool_memory(rx->JobIds);
327 free_and_null_pool_memory(rx->BaseJobIds);
328 free_and_null_pool_memory(rx->fname);
329 free_and_null_pool_memory(rx->path);
330 free_and_null_pool_memory(rx->query);
331 free_name_list(&rx->name_list);
334 static bool has_value(UAContext *ua, int i)
337 ua->error_msg(_("Missing value for keyword: %s\n"), ua->argk[i]);
344 * This gets the client name from which the backup was made
346 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
348 /* If no client name specified yet, get it now */
349 if (!rx->ClientName[0]) {
351 /* try command line argument */
352 int i = find_arg_with_value(ua, NT_("client"));
354 i = find_arg_with_value(ua, NT_("backupclient"));
357 if (!has_value(ua, i)) {
360 bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
363 memset(&cr, 0, sizeof(cr));
364 if (!get_client_dbr(ua, &cr)) {
367 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
373 * This is where we pick up a client name to restore to.
375 static int get_restore_client_name(UAContext *ua, RESTORE_CTX &rx)
377 /* Start with same name as backup client */
378 bstrncpy(rx.RestoreClientName, rx.ClientName, sizeof(rx.RestoreClientName));
380 /* try command line argument */
381 int i = find_arg_with_value(ua, NT_("restoreclient"));
383 if (!has_value(ua, i)) {
386 bstrncpy(rx.RestoreClientName, ua->argv[i], sizeof(rx.RestoreClientName));
395 * The first step in the restore process is for the user to
396 * select a list of JobIds from which he will subsequently
397 * select which files are to be restored.
399 * Returns: 2 if filename list made
400 * 1 if jobid list made
403 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
406 char date[MAX_TIME_LENGTH];
407 bool have_date = false;
408 /* Include current second if using current time */
409 utime_t now = time(NULL) + 1;
411 JOB_DBR jr = { (JobId_t)-1 };
414 const char *list[] = {
415 _("List last 20 Jobs run"),
416 _("List Jobs where a given File is saved"),
417 _("Enter list of comma separated JobIds to select"),
418 _("Enter SQL list command"),
419 _("Select the most recent backup for a client"),
420 _("Select backup for a client before a specified time"),
421 _("Enter a list of files to restore"),
422 _("Enter a list of files to restore before a specified time"),
423 _("Find the JobIds of the most recent backup for a client"),
424 _("Find the JobIds for a backup for a client before a specified time"),
425 _("Enter a list of directories to restore for found JobIds"),
426 _("Select full restore to a specified Job date"),
431 /* These keywords are handled in a for loop */
441 /* The keyword below are handled by individual arg lookups */
447 "bootstrap", /* 13 */
449 "strip_prefix", /* 15 */
450 "add_prefix", /* 16 */
451 "add_suffix", /* 17 */
452 "regexwhere", /* 18 */
453 "restoreclient", /* 19 */
460 for (i=1; i<ua->argc; i++) { /* loop through arguments */
461 bool found_kw = false;
462 for (j=0; kw[j]; j++) { /* loop through keywords */
463 if (strcasecmp(kw[j], ua->argk[i]) == 0) {
469 ua->error_msg(_("Unknown keyword: %s\n"), ua->argk[i]);
472 /* Found keyword in kw[] list, process it */
475 if (!has_value(ua, i)) {
478 if (*rx->JobIds != 0) {
479 pm_strcat(rx->JobIds, ",");
481 pm_strcat(rx->JobIds, ua->argv[i]);
484 case 1: /* current */
486 * Note, we add one second here just to include any job
487 * that may have finished within the current second,
488 * which happens a lot in scripting small jobs.
490 bstrutime(date, sizeof(date), now);
494 if (have_date || !has_value(ua, i)) {
497 if (str_to_utime(ua->argv[i]) == 0) {
498 ua->error_msg(_("Improper date format: %s\n"), ua->argv[i]);
501 bstrncpy(date, ua->argv[i], sizeof(date));
506 if (!has_value(ua, i)) {
510 bstrutime(date, sizeof(date), now);
512 if (!get_client_name(ua, rx)) {
515 pm_strcpy(ua->cmd, ua->argv[i]);
516 insert_one_file_or_dir(ua, rx, date, j==4);
520 bstrutime(date, sizeof(date), now);
522 if (!select_backups_before_date(ua, rx, date)) {
527 case 6: /* pool specified */
528 if (!has_value(ua, i)) {
531 rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
533 ua->error_msg(_("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
536 if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
538 ua->error_msg(_("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
542 case 7: /* all specified */
546 * All keywords 7 or greater are ignored or handled by a select prompt
554 ua->send_msg(_("\nFirst you select one or more JobIds that contain files\n"
555 "to be restored. You will be presented several methods\n"
556 "of specifying the JobIds. Then you will be allowed to\n"
557 "select which files from those JobIds are to be restored.\n\n"));
560 /* If choice not already made above, prompt */
567 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
568 for (int i=0; list[i]; i++) {
569 add_prompt(ua, list[i]);
572 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
573 case -1: /* error or cancel */
575 case 0: /* list last 20 Jobs run */
576 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
577 ua->error_msg(_("SQL query not authorized.\n"));
580 gui_save = ua->jcr->gui;
582 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
583 ua->jcr->gui = gui_save;
586 case 1: /* list where a file is saved */
587 if (!get_client_name(ua, rx)) {
590 if (!get_cmd(ua, _("Enter Filename (no path):"))) {
593 len = strlen(ua->cmd);
594 fname = (char *)malloc(len * 2 + 1);
595 db_escape_string(ua->jcr, ua->db, fname, ua->cmd, len);
596 Mmsg(rx->query, uar_file[db_type], rx->ClientName, fname);
598 gui_save = ua->jcr->gui;
600 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
601 ua->jcr->gui = gui_save;
604 case 2: /* enter a list of JobIds */
605 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
608 pm_strcpy(rx->JobIds, ua->cmd);
610 case 3: /* Enter an SQL list command */
611 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
612 ua->error_msg(_("SQL query not authorized.\n"));
615 if (!get_cmd(ua, _("Enter SQL list command: "))) {
618 gui_save = ua->jcr->gui;
620 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
621 ua->jcr->gui = gui_save;
624 case 4: /* Select the most recent backups */
626 bstrutime(date, sizeof(date), now);
628 if (!select_backups_before_date(ua, rx, date)) {
632 case 5: /* select backup at specified time */
634 if (!get_date(ua, date, sizeof(date))) {
638 if (!select_backups_before_date(ua, rx, date)) {
642 case 6: /* Enter files */
644 bstrutime(date, sizeof(date), now);
646 if (!get_client_name(ua, rx)) {
649 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
650 "containing a list of file names with paths, and terminate\n"
651 "them with a blank line.\n"));
653 if (!get_cmd(ua, _("Enter full filename: "))) {
656 len = strlen(ua->cmd);
660 insert_one_file_or_dir(ua, rx, date, false);
663 case 7: /* enter files backed up before specified time */
665 if (!get_date(ua, date, sizeof(date))) {
669 if (!get_client_name(ua, rx)) {
672 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
673 "containing a list of file names with paths, and terminate\n"
674 "them with a blank line.\n"));
676 if (!get_cmd(ua, _("Enter full filename: "))) {
679 len = strlen(ua->cmd);
683 insert_one_file_or_dir(ua, rx, date, false);
687 case 8: /* Find JobIds for current backup */
689 bstrutime(date, sizeof(date), now);
691 if (!select_backups_before_date(ua, rx, date)) {
697 case 9: /* Find JobIds for give date */
699 if (!get_date(ua, date, sizeof(date))) {
703 if (!select_backups_before_date(ua, rx, date)) {
709 case 10: /* Enter directories */
710 if (*rx->JobIds != 0) {
711 ua->send_msg(_("You have already selected the following JobIds: %s\n"),
713 } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
714 if (*rx->JobIds != 0 && *ua->cmd) {
715 pm_strcat(rx->JobIds, ",");
717 pm_strcat(rx->JobIds, ua->cmd);
719 if (*rx->JobIds == 0 || *rx->JobIds == '.') {
720 return 0; /* nothing entered, return */
723 bstrutime(date, sizeof(date), now);
725 if (!get_client_name(ua, rx)) {
728 ua->send_msg(_("Enter full directory names or start the name\n"
729 "with a < to indicate it is a filename containing a list\n"
730 "of directories and terminate them with a blank line.\n"));
732 if (!get_cmd(ua, _("Enter directory name: "))) {
735 len = strlen(ua->cmd);
739 /* Add trailing slash to end of directory names */
740 if (ua->cmd[0] != '<' && !IsPathSeparator(ua->cmd[len-1])) {
741 strcat(ua->cmd, "/");
743 insert_one_file_or_dir(ua, rx, date, true);
747 case 11: /* Choose a jobid and select jobs */
748 if (!get_cmd(ua, _("Enter JobId to get the state to restore: ")) ||
749 !is_an_integer(ua->cmd))
754 memset(&jr, 0, sizeof(JOB_DBR));
755 jr.JobId = str_to_int64(ua->cmd);
756 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
757 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
758 ua->cmd, db_strerror(ua->db));
761 ua->send_msg(_("Selecting jobs to build the Full state at %s\n"),
763 jr.JobLevel = L_INCREMENTAL; /* Take Full+Diff+Incr */
764 if (!db_accurate_get_jobids(ua->jcr, ua->db, &jr, &jobids)) {
767 pm_strcpy(rx->JobIds, jobids.list);
768 Dmsg1(30, "Item 12: jobids = %s\n", rx->JobIds);
770 case 12: /* Cancel or quit */
775 memset(&jr, 0, sizeof(JOB_DBR));
776 POOLMEM *JobIds = get_pool_memory(PM_FNAME);
780 * Find total number of files to be restored, and filter the JobId
781 * list to contain only ones permitted by the ACL conditions.
783 for (p=rx->JobIds; ; ) {
785 int stat = get_next_jobid_from_list(&p, &JobId);
787 ua->error_msg(_("Invalid JobId in list.\n"));
788 free_pool_memory(JobIds);
794 if (jr.JobId == JobId) {
795 continue; /* duplicate of last JobId */
797 memset(&jr, 0, sizeof(JOB_DBR));
799 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
800 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
801 edit_int64(JobId, ed1), db_strerror(ua->db));
802 free_pool_memory(JobIds);
805 if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
806 ua->error_msg(_("Access to JobId=%s (Job \"%s\") not authorized. Not selected.\n"),
807 edit_int64(JobId, ed1), jr.Name);
811 pm_strcat(JobIds, ",");
813 pm_strcat(JobIds, edit_int64(JobId, ed1));
814 rx->TotalFiles += jr.JobFiles;
816 free_pool_memory(rx->JobIds);
817 rx->JobIds = JobIds; /* Set ACL filtered list */
818 if (*rx->JobIds == 0) {
819 ua->warning_msg(_("No Jobs selected.\n"));
823 if (strchr(rx->JobIds,',')) {
824 ua->info_msg(_("You have selected the following JobIds: %s\n"), rx->JobIds);
826 ua->info_msg(_("You have selected the following JobId: %s\n"), rx->JobIds);
834 static int get_date(UAContext *ua, char *date, int date_len)
836 ua->send_msg(_("The restored files will the most current backup\n"
837 "BEFORE the date you specify below.\n\n"));
839 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
842 if (str_to_utime(ua->cmd) != 0) {
845 ua->error_msg(_("Improper date format.\n"));
847 bstrncpy(date, ua->cmd, date_len);
852 * Insert a single file, or read a list of files from a file
854 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
864 if ((ffd = fopen(p, "rb")) == NULL) {
866 ua->error_msg(_("Cannot open file %s: ERR=%s\n"),
870 while (fgets(file, sizeof(file), ffd)) {
873 if (!insert_dir_into_findex_list(ua, rx, file, date)) {
874 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
877 if (!insert_file_into_findex_list(ua, rx, file, date)) {
878 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
886 insert_table_into_findex_list(ua, rx, p);
890 insert_dir_into_findex_list(ua, rx, ua->cmd, date);
892 insert_file_into_findex_list(ua, rx, ua->cmd, date);
899 * For a given file (path+filename), split into path and file, then
900 * lookup the most recent backup in the catalog to get the JobId
901 * and FileIndex, then insert them into the findex list.
903 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
906 strip_trailing_newline(file);
907 split_path_and_filename(ua, rx, file);
908 if (*rx->JobIds == 0) {
909 Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname,
912 Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
913 rx->path, rx->fname, rx->ClientName);
916 /* Find and insert jobid and File Index */
917 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
918 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
919 rx->query, db_strerror(ua->db));
922 ua->error_msg(_("No database record found for: %s\n"), file);
923 // ua->error_msg("Query=%s\n", rx->query);
930 * For a given path lookup the most recent backup in the catalog
931 * to get the JobId and FileIndexes of all files in that directory.
933 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
936 strip_trailing_junk(dir);
937 if (*rx->JobIds == 0) {
938 ua->error_msg(_("No JobId specified cannot continue.\n"));
941 Mmsg(rx->query, uar_jobid_fileindex_from_dir[db_type], rx->JobIds, dir, rx->ClientName);
944 /* Find and insert jobid and File Index */
945 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
946 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
947 rx->query, db_strerror(ua->db));
950 ua->error_msg(_("No database record found for: %s\n"), dir);
957 * Get the JobId and FileIndexes of all files in the specified table
959 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
961 strip_trailing_junk(table);
962 Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
965 /* Find and insert jobid and File Index */
966 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
967 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
968 rx->query, db_strerror(ua->db));
971 ua->error_msg(_("No table found: %s\n"), table);
977 static void split_path_and_filename(UAContext *ua, RESTORE_CTX *rx, char *name)
981 /* Find path without the filename.
982 * I.e. everything after the last / is a "filename".
983 * OK, maybe it is a directory name, but we treat it like
984 * a filename. If we don't find a / then the whole name
985 * must be a path name (e.g. c:).
987 for (p=f=name; *p; p++) {
988 if (IsPathSeparator(*p)) {
989 f = p; /* set pos of last slash */
992 if (IsPathSeparator(*f)) { /* did we find a slash? */
993 f++; /* yes, point to filename */
994 } else { /* no, whole thing must be path name */
998 /* If filename doesn't exist (i.e. root directory), we
999 * simply create a blank name consisting of a single
1000 * space. This makes handling zero length filenames
1005 rx->fname = check_pool_memory_size(rx->fname, 2*(rx->fnl)+1);
1006 db_escape_string(ua->jcr, ua->db, rx->fname, f, rx->fnl);
1014 rx->path = check_pool_memory_size(rx->path, 2*(rx->pnl)+1);
1015 db_escape_string(ua->jcr, ua->db, rx->path, name, rx->pnl);
1021 Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
1024 static bool ask_for_fileregex(UAContext *ua, RESTORE_CTX *rx)
1026 if (find_arg(ua, NT_("all")) >= 0) { /* if user enters all on command line */
1027 return true; /* select everything */
1029 ua->send_msg(_("\n\nFor one or more of the JobIds selected, no files were found,\n"
1030 "so file selection is not possible.\n"
1031 "Most likely your retention policy pruned the files.\n"));
1032 if (get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
1033 if (ua->pint32_val == 1)
1035 while (get_cmd(ua, _("\nRegexp matching files to restore? (empty to abort): "))) {
1036 if (ua->cmd[0] == '\0') {
1039 regex_t *fileregex_re = NULL;
1041 char errmsg[500] = "";
1043 fileregex_re = (regex_t *)bmalloc(sizeof(regex_t));
1044 rc = regcomp(fileregex_re, ua->cmd, REG_EXTENDED|REG_NOSUB);
1046 regerror(rc, fileregex_re, errmsg, sizeof(errmsg));
1048 regfree(fileregex_re);
1051 ua->send_msg(_("Regex compile error: %s\n"), errmsg);
1053 rx->bsr->fileregex = bstrdup(ua->cmd);
1062 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
1065 JobId_t JobId, last_JobId;
1070 memset(&tree, 0, sizeof(TREE_CTX));
1072 * Build the directory tree containing JobIds user selected
1074 tree.root = new_tree(rx->TotalFiles);
1079 * For display purposes, the same JobId, with different volumes may
1080 * appear more than once, however, we only insert it once.
1083 tree.FileEstimate = 0;
1084 if (get_next_jobid_from_list(&p, &JobId) > 0) {
1085 /* Use first JobId as estimate of the number of files to restore */
1086 Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
1087 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1088 ua->error_msg("%s\n", db_strerror(ua->db));
1091 /* Add about 25% more than this job for over estimate */
1092 tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
1093 tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
1097 ua->info_msg(_("\nBuilding directory tree for JobId(s) %s ... "),
1100 #define new_get_file_list
1101 #ifdef new_get_file_list
1102 if (!db_get_file_list(ua->jcr, ua->db, rx->JobIds, insert_tree_handler, (void *)&tree)) {
1103 ua->error_msg("%s", db_strerror(ua->db));
1105 if (*rx->BaseJobIds) {
1106 pm_strcat(rx->JobIds, ",");
1107 pm_strcat(rx->JobIds, rx->BaseJobIds);
1110 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1113 if (JobId == last_JobId) {
1114 continue; /* eliminate duplicate JobIds */
1118 * Find files for this JobId and insert them in the tree
1120 Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
1121 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
1122 ua->error_msg("%s", db_strerror(ua->db));
1127 * Look at the first JobId on the list (presumably the oldest) and
1128 * if it is marked purged, don't do the manual selection because
1129 * the Job was pruned, so the tree is incomplete.
1131 if (tree.FileCount != 0) {
1132 /* Find out if any Job is purged */
1133 Mmsg(rx->query, "SELECT SUM(PurgedFiles) FROM Job WHERE JobId IN (%s)", rx->JobIds);
1134 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1135 ua->error_msg("%s\n", db_strerror(ua->db));
1137 /* rx->JobId is the PurgedFiles flag */
1138 if (rx->found && rx->JobId > 0) {
1139 tree.FileCount = 0; /* set count to zero, no tree selection */
1142 if (tree.FileCount == 0) {
1143 OK = ask_for_fileregex(ua, rx);
1146 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1147 if (JobId == last_JobId) {
1148 continue; /* eliminate duplicate JobIds */
1150 add_findex_all(rx->bsr, JobId);
1156 ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
1157 edit_uint64_with_commas(tree.FileCount, ec1));
1159 ua->info_msg(_("\n%s files inserted into the tree.\n"),
1160 edit_uint64_with_commas(tree.FileCount, ec1));
1163 if (find_arg(ua, NT_("done")) < 0) {
1164 /* Let the user interact in selecting which files to restore */
1165 OK = user_select_files_from_tree(&tree);
1169 * Walk down through the tree finding all files marked to be
1170 * extracted making a bootstrap file.
1173 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
1174 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
1175 if (node->extract || node->extract_dir) {
1176 Dmsg3(400, "JobId=%lld type=%d FI=%d\n", (uint64_t)node->JobId, node->type, node->FileIndex);
1177 add_findex(rx->bsr, node->JobId, node->FileIndex);
1178 if (node->extract && node->type != TN_NEWDIR) {
1179 rx->selected_files++; /* count only saved files */
1186 free_tree(tree.root); /* free the directory tree */
1192 * This routine is used to get the current backup or a backup
1193 * before the specified date.
1195 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
1200 char fileset_name[MAX_NAME_LENGTH];
1201 char ed1[50], ed2[50];
1202 char pool_select[MAX_NAME_LENGTH];
1205 /* Create temp tables */
1206 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1207 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1208 if (!db_sql_query(ua->db, uar_create_temp[db_type], NULL, NULL)) {
1209 ua->error_msg("%s\n", db_strerror(ua->db));
1211 if (!db_sql_query(ua->db, uar_create_temp1[db_type], NULL, NULL)) {
1212 ua->error_msg("%s\n", db_strerror(ua->db));
1215 * Select Client from the Catalog
1217 memset(&cr, 0, sizeof(cr));
1218 if (!get_client_dbr(ua, &cr)) {
1221 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1226 memset(&fsr, 0, sizeof(fsr));
1227 i = find_arg_with_value(ua, "FileSet");
1229 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1230 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1231 ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1232 db_strerror(ua->db));
1236 if (i < 0) { /* fileset not found */
1237 edit_int64(cr.ClientId, ed1);
1238 Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1239 start_prompt(ua, _("The defined FileSet resources are:\n"));
1240 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1241 ua->error_msg("%s\n", db_strerror(ua->db));
1243 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1244 fileset_name, sizeof(fileset_name)) < 0) {
1245 ua->error_msg(_("No FileSet found for client \"%s\".\n"), cr.Name);
1249 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1250 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1251 ua->warning_msg(_("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1252 ua->send_msg(_("This probably means you modified the FileSet.\n"
1253 "Continuing anyway.\n"));
1257 /* If Pool specified, add PoolId specification */
1261 memset(&pr, 0, sizeof(pr));
1262 bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1263 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1264 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
1265 edit_int64(pr.PoolId, ed1));
1267 ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1271 /* Find JobId of last Full backup for this client, fileset */
1272 edit_int64(cr.ClientId, ed1);
1273 Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1275 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1276 ua->error_msg("%s\n", db_strerror(ua->db));
1280 /* Find all Volumes used by that JobId */
1281 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1282 ua->error_msg("%s\n", db_strerror(ua->db));
1286 /* Note, this is needed because I don't seem to get the callback
1287 * from the call just above.
1290 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1291 ua->warning_msg("%s\n", db_strerror(ua->db));
1293 if (rx->JobTDate == 0) {
1294 ua->error_msg(_("No Full backup before %s found.\n"), date);
1298 /* Now find most recent Differental Job after Full save, if any */
1299 Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1300 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1301 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1302 ua->warning_msg("%s\n", db_strerror(ua->db));
1304 /* Now update JobTDate to look into Differental, if any */
1306 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1307 ua->warning_msg("%s\n", db_strerror(ua->db));
1309 if (rx->JobTDate == 0) {
1310 ua->error_msg(_("No Full backup before %s found.\n"), date);
1314 /* Now find all Incremental Jobs after Full/dif save */
1315 Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1316 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1317 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1318 ua->warning_msg("%s\n", db_strerror(ua->db));
1321 /* Get the JobIds from that list */
1322 rx->last_jobid[0] = rx->JobIds[0] = 0;
1324 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1325 ua->warning_msg("%s\n", db_strerror(ua->db));
1328 if (rx->JobIds[0] != 0) {
1329 if (find_arg(ua, NT_("copies")) > 0) {
1330 /* Display a list of all copies */
1331 db_list_copies_records(ua->jcr, ua->db, 0, rx->JobIds,
1332 prtit, ua, HORZ_LIST);
1334 /* Display a list of Jobs selected for this restore */
1335 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1,HORZ_LIST);
1339 ua->warning_msg(_("No jobs found.\n"));
1343 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1344 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1348 static int restore_count_handler(void *ctx, int num_fields, char **row)
1350 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1351 rx->JobId = str_to_int64(row[0]);
1357 * Callback handler to get JobId and FileIndex for files
1358 * can insert more than one depending on the caller.
1360 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1362 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1364 Dmsg2(200, "JobId=%s FileIndex=%s\n", row[0], row[1]);
1365 rx->JobId = str_to_int64(row[0]);
1366 add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1368 rx->selected_files++;
1373 * Callback handler make list of JobIds
1375 static int jobid_handler(void *ctx, int num_fields, char **row)
1377 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1379 if (strcmp(rx->last_jobid, row[0]) == 0) {
1380 return 0; /* duplicate id */
1382 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1383 if (rx->JobIds[0] != 0) {
1384 pm_strcat(rx->JobIds, ",");
1386 pm_strcat(rx->JobIds, row[0]);
1392 * Callback handler to pickup last Full backup JobTDate
1394 static int last_full_handler(void *ctx, int num_fields, char **row)
1396 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1398 rx->JobTDate = str_to_int64(row[1]);
1403 * Callback handler build FileSet name prompt list
1405 static int fileset_handler(void *ctx, int num_fields, char **row)
1407 /* row[0] = FileSet (name) */
1409 add_prompt((UAContext *)ctx, row[0]);
1415 * Free names in the list
1417 static void free_name_list(NAME_LIST *name_list)
1419 for (int i=0; i < name_list->num_ids; i++) {
1420 free(name_list->name[i]);
1422 bfree_and_null(name_list->name);
1423 name_list->max_ids = 0;
1424 name_list->num_ids = 0;
1427 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType)
1432 Dmsg1(200, "Already have store=%s\n", rx.store->name());
1436 * Try looking up Storage by name
1439 foreach_res(store, R_STORAGE) {
1440 if (strcmp(Storage, store->name()) == 0) {
1441 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1450 /* Check if an explicit storage resource is given */
1452 int i = find_arg_with_value(ua, "storage");
1454 store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1455 if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1459 if (store && (store != rx.store)) {
1460 ua->info_msg(_("Warning default storage overridden by \"%s\" on command line.\n"),
1463 Dmsg1(200, "Set store=%s\n", rx.store->name());
1468 /* If no storage resource, try to find one from MediaType */
1471 foreach_res(store, R_STORAGE) {
1472 if (strcmp(MediaType, store->media_type) == 0) {
1473 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1475 Dmsg1(200, "Set store=%s\n", rx.store->name());
1476 ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1477 Storage, store->name(), MediaType);
1484 ua->warning_msg(_("\nUnable to find Storage resource for\n"
1485 "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1488 /* Take command line arg, or ask user if none */
1489 rx.store = get_storage_resource(ua, false /* don't use default */);
1491 Dmsg1(200, "Set store=%s\n", rx.store->name());