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.query = get_pool_memory(PM_FNAME);
96 i = find_arg_with_value(ua, "where");
98 rx.where = ua->argv[i];
101 i = find_arg_with_value(ua, "strip_prefix");
103 strip_prefix = ua->argv[i];
106 i = find_arg_with_value(ua, "add_prefix");
108 add_prefix = ua->argv[i];
111 i = find_arg_with_value(ua, "add_suffix");
113 add_suffix = ua->argv[i];
116 i = find_arg_with_value(ua, "regexwhere");
118 rx.RegexWhere = ua->argv[i];
121 if (strip_prefix || add_suffix || add_prefix) {
122 int len = bregexp_get_build_where_size(strip_prefix, add_prefix, add_suffix);
123 regexp = (char *)bmalloc(len * sizeof(char));
125 bregexp_build_where(regexp, len, strip_prefix, add_prefix, add_suffix);
126 rx.RegexWhere = regexp;
129 /* TODO: add acl for regexwhere ? */
132 if (!acl_access_ok(ua, Where_ACL, rx.RegexWhere)) {
133 ua->error_msg(_("\"RegexWhere\" specification not authorized.\n"));
139 if (!acl_access_ok(ua, Where_ACL, rx.where)) {
140 ua->error_msg(_("\"where\" specification not authorized.\n"));
145 if (!open_client_db(ua)) {
149 /* Ensure there is at least one Restore Job */
151 foreach_res(job, R_JOB) {
152 if (job->JobType == JT_RESTORE) {
153 if (!rx.restore_job) {
154 rx.restore_job = job;
160 if (!rx.restore_jobs) {
162 "No Restore Job Resource found in bacula-dir.conf.\n"
163 "You must create at least one before running this command.\n"));
168 * Request user to select JobIds or files by various different methods
169 * last 20 jobs, where File saved, most recent backup, ...
170 * In the end, a list of files are pumped into
173 switch (user_select_jobids_or_files(ua, &rx)) {
176 case 1: /* selected by jobid */
177 if (!build_directory_tree(ua, &rx)) {
178 ua->send_msg(_("Restore not done.\n"));
182 case 2: /* selected by filename, no tree needed */
187 uint32_t selected_files;
189 if (!complete_bsr(ua, rx.bsr)) { /* find Vol, SessId, SessTime from JobIds */
190 ua->error_msg(_("Unable to construct a valid BSR. Cannot continue.\n"));
193 if (!(selected_files = write_bsr_file(ua, rx))) {
194 ua->warning_msg(_("No files selected to be restored.\n"));
197 display_bsr_info(ua, rx); /* display vols needed, etc */
199 /* If no count of files, use bsr generated value (often wrong) */
200 if (rx.selected_files == 0) {
201 rx.selected_files = selected_files;
203 if (rx.selected_files==1) {
204 ua->info_msg(_("\n1 file selected to be restored.\n\n"));
207 ua->info_msg(_("\n%s files selected to be restored.\n\n"),
208 edit_uint64_with_commas(rx.selected_files, ed1));
211 ua->warning_msg(_("No files selected to be restored.\n"));
215 if (rx.restore_jobs == 1) {
216 job = rx.restore_job;
218 job = select_restore_job_resource(ua);
224 get_client_name(ua, &rx);
225 if (!rx.ClientName) {
226 ua->error_msg(_("No Client resource found!\n"));
229 get_restore_client_name(ua, rx);
231 escaped_bsr_name = escape_filename(jcr->RestoreBootstrap);
233 /* Build run command */
235 escaped_where_name = escape_filename(rx.RegexWhere);
237 "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
238 " bootstrap=\"%s\" regexwhere=\"%s\" files=%u catalog=\"%s\"",
239 job->name(), rx.ClientName, rx.RestoreClientName,
240 rx.store?rx.store->name():"",
241 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
242 escaped_where_name ? escaped_where_name : rx.RegexWhere,
243 rx.selected_files, ua->catalog->name());
245 } else if (rx.where) {
246 escaped_where_name = escape_filename(rx.where);
248 "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
249 " bootstrap=\"%s\" where=\"%s\" files=%u catalog=\"%s\"",
250 job->name(), rx.ClientName, rx.RestoreClientName,
251 rx.store?rx.store->name():"",
252 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
253 escaped_where_name ? escaped_where_name : rx.where,
254 rx.selected_files, ua->catalog->name());
258 "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
259 " bootstrap=\"%s\" files=%u catalog=\"%s\"",
260 job->name(), rx.ClientName, rx.RestoreClientName,
261 rx.store?rx.store->name():"",
262 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
263 rx.selected_files, ua->catalog->name());
266 if (escaped_bsr_name != NULL) {
267 bfree(escaped_bsr_name);
270 if (escaped_where_name != NULL) {
271 bfree(escaped_where_name);
278 if (find_arg(ua, NT_("yes")) > 0) {
279 pm_strcat(ua->cmd, " yes"); /* pass it on to the run command */
281 Dmsg1(200, "Submitting: %s\n", ua->cmd);
283 run_cmd(ua, ua->cmd);
288 if (escaped_bsr_name != NULL) {
289 bfree(escaped_bsr_name);
292 if (escaped_where_name != NULL) {
293 bfree(escaped_where_name);
305 static void free_rx(RESTORE_CTX *rx)
310 free_pool_memory(rx->JobIds);
314 free_pool_memory(rx->fname);
318 free_pool_memory(rx->path);
322 free_pool_memory(rx->query);
325 free_name_list(&rx->name_list);
328 static bool has_value(UAContext *ua, int i)
331 ua->error_msg(_("Missing value for keyword: %s\n"), ua->argk[i]);
338 * This gets the client name from which the backup was made
340 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
342 /* If no client name specified yet, get it now */
343 if (!rx->ClientName[0]) {
345 /* try command line argument */
346 int i = find_arg_with_value(ua, NT_("client"));
348 i = find_arg_with_value(ua, NT_("backupclient"));
351 if (!has_value(ua, i)) {
354 bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
357 memset(&cr, 0, sizeof(cr));
358 if (!get_client_dbr(ua, &cr)) {
361 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
367 * This is where we pick up a client name to restore to.
369 static int get_restore_client_name(UAContext *ua, RESTORE_CTX &rx)
371 /* Start with same name as backup client */
372 bstrncpy(rx.RestoreClientName, rx.ClientName, sizeof(rx.RestoreClientName));
374 /* try command line argument */
375 int i = find_arg_with_value(ua, NT_("restoreclient"));
377 if (!has_value(ua, i)) {
380 bstrncpy(rx.RestoreClientName, ua->argv[i], sizeof(rx.RestoreClientName));
389 * The first step in the restore process is for the user to
390 * select a list of JobIds from which he will subsequently
391 * select which files are to be restored.
393 * Returns: 2 if filename list made
394 * 1 if jobid list made
397 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
400 char date[MAX_TIME_LENGTH];
401 bool have_date = false;
402 /* Include current second if using current time */
403 utime_t now = time(NULL) + 1;
405 JOB_DBR jr = { (JobId_t)-1 };
408 const char *list[] = {
409 _("List last 20 Jobs run"),
410 _("List Jobs where a given File is saved"),
411 _("Enter list of comma separated JobIds to select"),
412 _("Enter SQL list command"),
413 _("Select the most recent backup for a client"),
414 _("Select backup for a client before a specified time"),
415 _("Enter a list of files to restore"),
416 _("Enter a list of files to restore before a specified time"),
417 _("Find the JobIds of the most recent backup for a client"),
418 _("Find the JobIds for a backup for a client before a specified time"),
419 _("Enter a list of directories to restore for found JobIds"),
420 _("Select full restore to a specified JobId"),
425 /* These keywords are handled in a for loop */
435 /* The keyword below are handled by individual arg lookups */
441 "bootstrap", /* 13 */
443 "strip_prefix", /* 15 */
444 "add_prefix", /* 16 */
445 "add_suffix", /* 17 */
446 "regexwhere", /* 18 */
447 "restoreclient", /* 19 */
454 for (i=1; i<ua->argc; i++) { /* loop through arguments */
455 bool found_kw = false;
456 for (j=0; kw[j]; j++) { /* loop through keywords */
457 if (strcasecmp(kw[j], ua->argk[i]) == 0) {
463 ua->error_msg(_("Unknown keyword: %s\n"), ua->argk[i]);
466 /* Found keyword in kw[] list, process it */
469 if (!has_value(ua, i)) {
472 if (*rx->JobIds != 0) {
473 pm_strcat(rx->JobIds, ",");
475 pm_strcat(rx->JobIds, ua->argv[i]);
478 case 1: /* current */
480 * Note, we add one second here just to include any job
481 * that may have finished within the current second,
482 * which happens a lot in scripting small jobs.
484 bstrutime(date, sizeof(date), now);
488 if (have_date || !has_value(ua, i)) {
491 if (str_to_utime(ua->argv[i]) == 0) {
492 ua->error_msg(_("Improper date format: %s\n"), ua->argv[i]);
495 bstrncpy(date, ua->argv[i], sizeof(date));
500 if (!has_value(ua, i)) {
504 bstrutime(date, sizeof(date), now);
506 if (!get_client_name(ua, rx)) {
509 pm_strcpy(ua->cmd, ua->argv[i]);
510 insert_one_file_or_dir(ua, rx, date, j==4);
514 bstrutime(date, sizeof(date), now);
516 if (!select_backups_before_date(ua, rx, date)) {
521 case 6: /* pool specified */
522 if (!has_value(ua, i)) {
525 rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
527 ua->error_msg(_("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
530 if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
532 ua->error_msg(_("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
536 case 7: /* all specified */
540 * All keywords 7 or greater are ignored or handled by a select prompt
548 ua->send_msg(_("\nFirst you select one or more JobIds that contain files\n"
549 "to be restored. You will be presented several methods\n"
550 "of specifying the JobIds. Then you will be allowed to\n"
551 "select which files from those JobIds are to be restored.\n\n"));
554 /* If choice not already made above, prompt */
560 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
561 for (int i=0; list[i]; i++) {
562 add_prompt(ua, list[i]);
565 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
566 case -1: /* error or cancel */
568 case 0: /* list last 20 Jobs run */
569 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
570 ua->error_msg(_("SQL query not authorized.\n"));
573 gui_save = ua->jcr->gui;
575 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
576 ua->jcr->gui = gui_save;
579 case 1: /* list where a file is saved */
580 if (!get_client_name(ua, rx)) {
583 if (!get_cmd(ua, _("Enter Filename (no path):"))) {
586 len = strlen(ua->cmd);
587 fname = (char *)malloc(len * 2 + 1);
588 db_escape_string(ua->jcr, ua->db, fname, ua->cmd, len);
589 Mmsg(rx->query, uar_file[db_type], rx->ClientName, fname);
591 gui_save = ua->jcr->gui;
593 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
594 ua->jcr->gui = gui_save;
597 case 2: /* enter a list of JobIds */
598 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
601 pm_strcpy(rx->JobIds, ua->cmd);
603 case 3: /* Enter an SQL list command */
604 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
605 ua->error_msg(_("SQL query not authorized.\n"));
608 if (!get_cmd(ua, _("Enter SQL list command: "))) {
611 gui_save = ua->jcr->gui;
613 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
614 ua->jcr->gui = gui_save;
617 case 4: /* Select the most recent backups */
619 bstrutime(date, sizeof(date), now);
621 if (!select_backups_before_date(ua, rx, date)) {
625 case 5: /* select backup at specified time */
627 if (!get_date(ua, date, sizeof(date))) {
631 if (!select_backups_before_date(ua, rx, date)) {
635 case 6: /* Enter files */
637 bstrutime(date, sizeof(date), now);
639 if (!get_client_name(ua, rx)) {
642 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
643 "containing a list of file names with paths, and terminate\n"
644 "them with a blank line.\n"));
646 if (!get_cmd(ua, _("Enter full filename: "))) {
649 len = strlen(ua->cmd);
653 insert_one_file_or_dir(ua, rx, date, false);
656 case 7: /* enter files backed up before specified time */
658 if (!get_date(ua, date, sizeof(date))) {
662 if (!get_client_name(ua, rx)) {
665 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
666 "containing a list of file names with paths, and terminate\n"
667 "them with a blank line.\n"));
669 if (!get_cmd(ua, _("Enter full filename: "))) {
672 len = strlen(ua->cmd);
676 insert_one_file_or_dir(ua, rx, date, false);
680 case 8: /* Find JobIds for current backup */
682 bstrutime(date, sizeof(date), now);
684 if (!select_backups_before_date(ua, rx, date)) {
690 case 9: /* Find JobIds for give date */
692 if (!get_date(ua, date, sizeof(date))) {
696 if (!select_backups_before_date(ua, rx, date)) {
702 case 10: /* Enter directories */
703 if (*rx->JobIds != 0) {
704 ua->send_msg(_("You have already selected the following JobIds: %s\n"),
706 } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
707 if (*rx->JobIds != 0 && *ua->cmd) {
708 pm_strcat(rx->JobIds, ",");
710 pm_strcat(rx->JobIds, ua->cmd);
712 if (*rx->JobIds == 0 || *rx->JobIds == '.') {
713 return 0; /* nothing entered, return */
716 bstrutime(date, sizeof(date), now);
718 if (!get_client_name(ua, rx)) {
721 ua->send_msg(_("Enter full directory names or start the name\n"
722 "with a < to indicate it is a filename containing a list\n"
723 "of directories and terminate them with a blank line.\n"));
725 if (!get_cmd(ua, _("Enter directory name: "))) {
728 len = strlen(ua->cmd);
732 /* Add trailing slash to end of directory names */
733 if (ua->cmd[0] != '<' && !IsPathSeparator(ua->cmd[len-1])) {
734 strcat(ua->cmd, "/");
736 insert_one_file_or_dir(ua, rx, date, true);
740 case 11: /* Choose a jobid and select jobs */
741 if (!get_cmd(ua, _("Enter JobId to restore: ")) ||
742 !is_an_integer(ua->cmd))
747 memset(&jr, 0, sizeof(JOB_DBR));
748 jr.JobId = str_to_int64(ua->cmd);
749 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
750 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
751 ua->cmd, db_strerror(ua->db));
754 jr.JobLevel = L_INCREMENTAL; /* Take Full+Diff+Incr */
755 if (!db_accurate_get_jobids(ua->jcr, ua->db, &jr, rx->JobIds)) {
758 Dmsg1(30, "Item 12: jobids = %s\n", rx->JobIds);
760 case 12: /* Cancel or quit */
765 memset(&jr, 0, sizeof(JOB_DBR));
766 POOLMEM *JobIds = get_pool_memory(PM_FNAME);
770 * Find total number of files to be restored, and filter the JobId
771 * list to contain only ones permitted by the ACL conditions.
773 for (p=rx->JobIds; ; ) {
775 int stat = get_next_jobid_from_list(&p, &JobId);
777 ua->error_msg(_("Invalid JobId in list.\n"));
778 free_pool_memory(JobIds);
784 if (jr.JobId == JobId) {
785 continue; /* duplicate of last JobId */
787 memset(&jr, 0, sizeof(JOB_DBR));
789 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
790 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
791 edit_int64(JobId, ed1), db_strerror(ua->db));
792 free_pool_memory(JobIds);
795 if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
796 ua->error_msg(_("Access to JobId=%s (Job \"%s\") not authorized. Not selected.\n"),
797 edit_int64(JobId, ed1), jr.Name);
801 pm_strcat(JobIds, ",");
803 pm_strcat(JobIds, edit_int64(JobId, ed1));
804 rx->TotalFiles += jr.JobFiles;
806 free_pool_memory(rx->JobIds);
807 rx->JobIds = JobIds; /* Set ACL filtered list */
808 if (*rx->JobIds == 0) {
809 ua->warning_msg(_("No Jobs selected.\n"));
812 if (strchr(rx->JobIds,',')) {
813 ua->info_msg(_("You have selected the following JobIds: %s\n"), rx->JobIds);
815 ua->info_msg(_("You have selected the following JobId: %s\n"), rx->JobIds);
823 static int get_date(UAContext *ua, char *date, int date_len)
825 ua->send_msg(_("The restored files will the most current backup\n"
826 "BEFORE the date you specify below.\n\n"));
828 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
831 if (str_to_utime(ua->cmd) != 0) {
834 ua->error_msg(_("Improper date format.\n"));
836 bstrncpy(date, ua->cmd, date_len);
841 * Insert a single file, or read a list of files from a file
843 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
853 if ((ffd = fopen(p, "rb")) == NULL) {
855 ua->error_msg(_("Cannot open file %s: ERR=%s\n"),
859 while (fgets(file, sizeof(file), ffd)) {
862 if (!insert_dir_into_findex_list(ua, rx, file, date)) {
863 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
866 if (!insert_file_into_findex_list(ua, rx, file, date)) {
867 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
875 insert_table_into_findex_list(ua, rx, p);
879 insert_dir_into_findex_list(ua, rx, ua->cmd, date);
881 insert_file_into_findex_list(ua, rx, ua->cmd, date);
888 * For a given file (path+filename), split into path and file, then
889 * lookup the most recent backup in the catalog to get the JobId
890 * and FileIndex, then insert them into the findex list.
892 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
895 strip_trailing_newline(file);
896 split_path_and_filename(ua, rx, file);
897 if (*rx->JobIds == 0) {
898 Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname,
901 Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
902 rx->path, rx->fname, rx->ClientName);
905 /* Find and insert jobid and File Index */
906 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
907 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
908 rx->query, db_strerror(ua->db));
911 ua->error_msg(_("No database record found for: %s\n"), file);
912 // ua->error_msg("Query=%s\n", rx->query);
919 * For a given path lookup the most recent backup in the catalog
920 * to get the JobId and FileIndexes of all files in that directory.
922 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
925 strip_trailing_junk(dir);
926 if (*rx->JobIds == 0) {
927 ua->error_msg(_("No JobId specified cannot continue.\n"));
930 Mmsg(rx->query, uar_jobid_fileindex_from_dir[db_type], rx->JobIds, dir, rx->ClientName);
933 /* Find and insert jobid and File Index */
934 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
935 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
936 rx->query, db_strerror(ua->db));
939 ua->error_msg(_("No database record found for: %s\n"), dir);
946 * Get the JobId and FileIndexes of all files in the specified table
948 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
950 strip_trailing_junk(table);
951 Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
954 /* Find and insert jobid and File Index */
955 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
956 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
957 rx->query, db_strerror(ua->db));
960 ua->error_msg(_("No table found: %s\n"), table);
966 static void split_path_and_filename(UAContext *ua, RESTORE_CTX *rx, char *name)
970 /* Find path without the filename.
971 * I.e. everything after the last / is a "filename".
972 * OK, maybe it is a directory name, but we treat it like
973 * a filename. If we don't find a / then the whole name
974 * must be a path name (e.g. c:).
976 for (p=f=name; *p; p++) {
977 if (IsPathSeparator(*p)) {
978 f = p; /* set pos of last slash */
981 if (IsPathSeparator(*f)) { /* did we find a slash? */
982 f++; /* yes, point to filename */
983 } else { /* no, whole thing must be path name */
987 /* If filename doesn't exist (i.e. root directory), we
988 * simply create a blank name consisting of a single
989 * space. This makes handling zero length filenames
994 rx->fname = check_pool_memory_size(rx->fname, 2*(rx->fnl)+1);
995 db_escape_string(ua->jcr, ua->db, rx->fname, f, rx->fnl);
1003 rx->path = check_pool_memory_size(rx->path, 2*(rx->pnl)+1);
1004 db_escape_string(ua->jcr, ua->db, rx->path, name, rx->pnl);
1010 Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
1013 static bool ask_for_fileregex(UAContext *ua, RESTORE_CTX *rx)
1015 if (find_arg(ua, NT_("all")) >= 0) { /* if user enters all on command line */
1016 return true; /* select everything */
1018 ua->send_msg(_("\n\nFor one or more of the JobIds selected, no files were found,\n"
1019 "so file selection is not possible.\n"
1020 "Most likely your retention policy pruned the files.\n"));
1021 if (get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
1022 if (ua->pint32_val == 1)
1024 while (get_cmd(ua, _("\nRegexp matching files to restore? (empty to abort): "))) {
1025 if (ua->cmd[0] == '\0') {
1028 regex_t *fileregex_re = NULL;
1030 char errmsg[500] = "";
1032 fileregex_re = (regex_t *)bmalloc(sizeof(regex_t));
1033 rc = regcomp(fileregex_re, ua->cmd, REG_EXTENDED|REG_NOSUB);
1035 regerror(rc, fileregex_re, errmsg, sizeof(errmsg));
1037 regfree(fileregex_re);
1040 ua->send_msg(_("Regex compile error: %s\n"), errmsg);
1042 rx->bsr->fileregex = bstrdup(ua->cmd);
1051 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
1054 JobId_t JobId, last_JobId;
1059 memset(&tree, 0, sizeof(TREE_CTX));
1061 * Build the directory tree containing JobIds user selected
1063 tree.root = new_tree(rx->TotalFiles);
1068 * For display purposes, the same JobId, with different volumes may
1069 * appear more than once, however, we only insert it once.
1072 tree.FileEstimate = 0;
1073 if (get_next_jobid_from_list(&p, &JobId) > 0) {
1074 /* Use first JobId as estimate of the number of files to restore */
1075 Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
1076 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1077 ua->error_msg("%s\n", db_strerror(ua->db));
1080 /* Add about 25% more than this job for over estimate */
1081 tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
1082 tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
1086 ua->info_msg(_("\nBuilding directory tree for JobId(s) %s ... "),
1089 #define new_get_file_list
1090 #ifdef new_get_file_list
1091 if (!db_get_file_list(ua->jcr, ua->db, rx->JobIds, insert_tree_handler, (void *)&tree)) {
1092 ua->error_msg("%s", db_strerror(ua->db));
1095 if (!db_get_used_base_jobids(ua->jcr, ua->db, rx->JobIds, rx->JobIds)) {
1096 ua->error_msg("%s", db_strerror(ua->db));
1099 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1102 if (JobId == last_JobId) {
1103 continue; /* eliminate duplicate JobIds */
1107 * Find files for this JobId and insert them in the tree
1109 Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
1110 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
1111 ua->error_msg("%s", db_strerror(ua->db));
1116 * Look at the first JobId on the list (presumably the oldest) and
1117 * if it is marked purged, don't do the manual selection because
1118 * the Job was pruned, so the tree is incomplete.
1120 if (tree.FileCount != 0) {
1121 /* Find out if any Job is purged */
1122 Mmsg(rx->query, "SELECT SUM(PurgedFiles) FROM Job WHERE JobId IN (%s)", rx->JobIds);
1123 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1124 ua->error_msg("%s\n", db_strerror(ua->db));
1126 /* rx->JobId is the PurgedFiles flag */
1127 if (rx->found && rx->JobId > 0) {
1128 tree.FileCount = 0; /* set count to zero, no tree selection */
1131 if (tree.FileCount == 0) {
1132 OK = ask_for_fileregex(ua, rx);
1135 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1136 if (JobId == last_JobId) {
1137 continue; /* eliminate duplicate JobIds */
1139 add_findex_all(rx->bsr, JobId);
1145 ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
1146 edit_uint64_with_commas(tree.FileCount, ec1));
1148 ua->info_msg(_("\n%s files inserted into the tree.\n"),
1149 edit_uint64_with_commas(tree.FileCount, ec1));
1152 if (find_arg(ua, NT_("done")) < 0) {
1153 /* Let the user interact in selecting which files to restore */
1154 OK = user_select_files_from_tree(&tree);
1158 * Walk down through the tree finding all files marked to be
1159 * extracted making a bootstrap file.
1162 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
1163 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
1164 if (node->extract || node->extract_dir) {
1165 Dmsg3(400, "JobId=%lld type=%d FI=%d\n", (uint64_t)node->JobId, node->type, node->FileIndex);
1166 add_findex(rx->bsr, node->JobId, node->FileIndex);
1167 if (node->extract && node->type != TN_NEWDIR) {
1168 rx->selected_files++; /* count only saved files */
1175 free_tree(tree.root); /* free the directory tree */
1181 * This routine is used to get the current backup or a backup
1182 * before the specified date.
1184 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
1189 char fileset_name[MAX_NAME_LENGTH];
1190 char ed1[50], ed2[50];
1191 char pool_select[MAX_NAME_LENGTH];
1194 /* Create temp tables */
1195 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1196 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1197 if (!db_sql_query(ua->db, uar_create_temp[db_type], NULL, NULL)) {
1198 ua->error_msg("%s\n", db_strerror(ua->db));
1200 if (!db_sql_query(ua->db, uar_create_temp1[db_type], NULL, NULL)) {
1201 ua->error_msg("%s\n", db_strerror(ua->db));
1204 * Select Client from the Catalog
1206 memset(&cr, 0, sizeof(cr));
1207 if (!get_client_dbr(ua, &cr)) {
1210 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1215 memset(&fsr, 0, sizeof(fsr));
1216 i = find_arg_with_value(ua, "FileSet");
1218 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1219 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1220 ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1221 db_strerror(ua->db));
1225 if (i < 0) { /* fileset not found */
1226 edit_int64(cr.ClientId, ed1);
1227 Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1228 start_prompt(ua, _("The defined FileSet resources are:\n"));
1229 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1230 ua->error_msg("%s\n", db_strerror(ua->db));
1232 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1233 fileset_name, sizeof(fileset_name)) < 0) {
1234 ua->error_msg(_("No FileSet found for client \"%s\".\n"), cr.Name);
1238 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1239 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1240 ua->warning_msg(_("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1241 ua->send_msg(_("This probably means you modified the FileSet.\n"
1242 "Continuing anyway.\n"));
1246 /* If Pool specified, add PoolId specification */
1250 memset(&pr, 0, sizeof(pr));
1251 bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1252 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1253 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
1254 edit_int64(pr.PoolId, ed1));
1256 ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1260 /* Find JobId of last Full backup for this client, fileset */
1261 edit_int64(cr.ClientId, ed1);
1262 Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1264 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1265 ua->error_msg("%s\n", db_strerror(ua->db));
1269 /* Find all Volumes used by that JobId */
1270 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1271 ua->error_msg("%s\n", db_strerror(ua->db));
1275 /* Note, this is needed because I don't seem to get the callback
1276 * from the call just above.
1279 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1280 ua->warning_msg("%s\n", db_strerror(ua->db));
1282 if (rx->JobTDate == 0) {
1283 ua->error_msg(_("No Full backup before %s found.\n"), date);
1287 /* Now find most recent Differental Job after Full save, if any */
1288 Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1289 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1290 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1291 ua->warning_msg("%s\n", db_strerror(ua->db));
1293 /* Now update JobTDate to look into Differental, if any */
1295 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1296 ua->warning_msg("%s\n", db_strerror(ua->db));
1298 if (rx->JobTDate == 0) {
1299 ua->error_msg(_("No Full backup before %s found.\n"), date);
1303 /* Now find all Incremental Jobs after Full/dif save */
1304 Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1305 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1306 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1307 ua->warning_msg("%s\n", db_strerror(ua->db));
1310 /* Get the JobIds from that list */
1312 rx->last_jobid[0] = 0;
1313 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1314 ua->warning_msg("%s\n", db_strerror(ua->db));
1317 if (rx->JobIds[0] != 0) {
1318 if (find_arg(ua, NT_("copies")) > 0) {
1319 /* Display a list of all copies */
1320 db_list_copies_records(ua->jcr, ua->db, 0, rx->JobIds,
1321 prtit, ua, HORZ_LIST);
1323 /* Display a list of Jobs selected for this restore */
1324 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
1327 ua->warning_msg(_("No jobs found.\n"));
1331 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1332 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1338 * Return next JobId from comma separated list
1341 * 1 if next JobId returned
1342 * 0 if no more JobIds are in list
1343 * -1 there is an error
1345 int get_next_jobid_from_list(char **p, JobId_t *JobId)
1347 const int maxlen = 30;
1348 char jobid[maxlen+1];
1352 for (int i=0; i<maxlen; i++) {
1355 } else if (*q == ',') {
1362 if (jobid[0] == 0) {
1364 } else if (!is_a_number(jobid)) {
1365 return -1; /* error */
1368 *JobId = str_to_int64(jobid);
1372 static int restore_count_handler(void *ctx, int num_fields, char **row)
1374 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1375 rx->JobId = str_to_int64(row[0]);
1381 * Callback handler to get JobId and FileIndex for files
1382 * can insert more than one depending on the caller.
1384 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1386 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1388 Dmsg2(200, "JobId=%s FileIndex=%s\n", row[0], row[1]);
1389 rx->JobId = str_to_int64(row[0]);
1390 add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1392 rx->selected_files++;
1397 * Callback handler make list of JobIds
1399 static int jobid_handler(void *ctx, int num_fields, char **row)
1401 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1403 if (strcmp(rx->last_jobid, row[0]) == 0) {
1404 return 0; /* duplicate id */
1406 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1407 if (rx->JobIds[0] != 0) {
1408 pm_strcat(rx->JobIds, ",");
1410 pm_strcat(rx->JobIds, row[0]);
1416 * Callback handler to pickup last Full backup JobTDate
1418 static int last_full_handler(void *ctx, int num_fields, char **row)
1420 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1422 rx->JobTDate = str_to_int64(row[1]);
1427 * Callback handler build FileSet name prompt list
1429 static int fileset_handler(void *ctx, int num_fields, char **row)
1431 /* row[0] = FileSet (name) */
1433 add_prompt((UAContext *)ctx, row[0]);
1439 * Free names in the list
1441 static void free_name_list(NAME_LIST *name_list)
1443 for (int i=0; i < name_list->num_ids; i++) {
1444 free(name_list->name[i]);
1446 if (name_list->name) {
1447 free(name_list->name);
1448 name_list->name = NULL;
1450 name_list->max_ids = 0;
1451 name_list->num_ids = 0;
1454 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType)
1459 Dmsg1(200, "Already have store=%s\n", rx.store->name());
1463 * Try looking up Storage by name
1466 foreach_res(store, R_STORAGE) {
1467 if (strcmp(Storage, store->name()) == 0) {
1468 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1477 /* Check if an explicit storage resource is given */
1479 int i = find_arg_with_value(ua, "storage");
1481 store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1482 if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1486 if (store && (store != rx.store)) {
1487 ua->info_msg(_("Warning default storage overridden by \"%s\" on command line.\n"),
1490 Dmsg1(200, "Set store=%s\n", rx.store->name());
1495 /* If no storage resource, try to find one from MediaType */
1498 foreach_res(store, R_STORAGE) {
1499 if (strcmp(MediaType, store->media_type) == 0) {
1500 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1502 Dmsg1(200, "Set store=%s\n", rx.store->name());
1503 ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1504 Storage, store->name(), MediaType);
1511 ua->warning_msg(_("\nUnable to find Storage resource for\n"
1512 "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1515 /* Take command line arg, or ask user if none */
1516 rx.store = get_storage_resource(ua, false /* don't use default */);
1518 Dmsg1(200, "Set store=%s\n", rx.store->name());