2 Bacula® - The Network Backup Solution
4 Copyright (C) 2002-2010 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);
50 /* Forward referenced functions */
51 static int last_full_handler(void *ctx, int num_fields, char **row);
52 static int jobid_handler(void *ctx, int num_fields, char **row);
53 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx);
54 static int fileset_handler(void *ctx, int num_fields, char **row);
55 static void free_name_list(NAME_LIST *name_list);
56 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date);
57 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx);
58 static void free_rx(RESTORE_CTX *rx);
59 static void split_path_and_filename(UAContext *ua, RESTORE_CTX *rx, char *fname);
60 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row);
61 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
63 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
65 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir);
66 static int get_client_name(UAContext *ua, RESTORE_CTX *rx);
67 static int get_restore_client_name(UAContext *ua, RESTORE_CTX &rx);
68 static bool get_date(UAContext *ua, char *date, int date_len);
69 static int restore_count_handler(void *ctx, int num_fields, char **row);
70 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table);
71 static void get_and_display_basejobs(UAContext *ua, RESTORE_CTX *rx);
77 int restore_cmd(UAContext *ua, const char *cmd)
79 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);
94 rx.BaseJobIds = get_pool_memory(PM_FNAME);
95 rx.query = get_pool_memory(PM_FNAME);
98 i = find_arg_with_value(ua, "comment");
100 rx.comment = ua->argv[i];
101 if (!is_comment_legal(ua, rx.comment)) {
106 i = find_arg_with_value(ua, "where");
108 rx.where = ua->argv[i];
111 i = find_arg_with_value(ua, "replace");
113 rx.replace = ua->argv[i];
117 i = find_arg_with_value(ua, "strip_prefix");
119 strip_prefix = ua->argv[i];
122 i = find_arg_with_value(ua, "add_prefix");
124 add_prefix = ua->argv[i];
127 i = find_arg_with_value(ua, "add_suffix");
129 add_suffix = ua->argv[i];
132 i = find_arg_with_value(ua, "regexwhere");
134 rx.RegexWhere = ua->argv[i];
137 if (strip_prefix || add_suffix || add_prefix) {
138 int len = bregexp_get_build_where_size(strip_prefix, add_prefix, add_suffix);
139 regexp = (char *)bmalloc(len * sizeof(char));
141 bregexp_build_where(regexp, len, strip_prefix, add_prefix, add_suffix);
142 rx.RegexWhere = regexp;
145 /* TODO: add acl for regexwhere ? */
148 if (!acl_access_ok(ua, Where_ACL, rx.RegexWhere)) {
149 ua->error_msg(_("\"RegexWhere\" specification not authorized.\n"));
155 if (!acl_access_ok(ua, Where_ACL, rx.where)) {
156 ua->error_msg(_("\"where\" specification not authorized.\n"));
161 if (!open_client_db(ua)) {
165 /* Ensure there is at least one Restore Job */
167 foreach_res(job, R_JOB) {
168 if (job->JobType == JT_RESTORE) {
169 if (!rx.restore_job) {
170 rx.restore_job = job;
176 if (!rx.restore_jobs) {
178 "No Restore Job Resource found in bacula-dir.conf.\n"
179 "You must create at least one before running this command.\n"));
184 * Request user to select JobIds or files by various different methods
185 * last 20 jobs, where File saved, most recent backup, ...
186 * In the end, a list of files are pumped into
189 switch (user_select_jobids_or_files(ua, &rx)) {
192 case 1: /* selected by jobid */
193 get_and_display_basejobs(ua, &rx);
194 if (!build_directory_tree(ua, &rx)) {
195 ua->send_msg(_("Restore not done.\n"));
199 case 2: /* selected by filename, no tree needed */
205 if (!complete_bsr(ua, rx.bsr)) { /* find Vol, SessId, SessTime from JobIds */
206 ua->error_msg(_("Unable to construct a valid BSR. Cannot continue.\n"));
209 if (!(rx.selected_files = write_bsr_file(ua, rx))) {
210 ua->warning_msg(_("No files selected to be restored.\n"));
213 display_bsr_info(ua, rx); /* display vols needed, etc */
215 if (rx.selected_files==1) {
216 ua->info_msg(_("\n1 file selected to be restored.\n\n"));
218 ua->info_msg(_("\n%s files selected to be restored.\n\n"),
219 edit_uint64_with_commas(rx.selected_files, ed1));
222 ua->warning_msg(_("No files selected to be restored.\n"));
226 if (rx.restore_jobs == 1) {
227 job = rx.restore_job;
229 job = get_restore_job(ua);
235 get_client_name(ua, &rx);
236 if (!rx.ClientName) {
237 ua->error_msg(_("No Client resource found!\n"));
240 get_restore_client_name(ua, rx);
242 escaped_bsr_name = escape_filename(jcr->RestoreBootstrap);
245 "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
246 " bootstrap=\"%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 rx.selected_files, ua->catalog->name());
252 /* Build run command */
255 escaped_where_name = escape_filename(rx.RegexWhere);
256 Mmsg(buf, " regexwhere=\"%s\"",
257 escaped_where_name ? escaped_where_name : rx.RegexWhere);
259 } else if (rx.where) {
260 escaped_where_name = escape_filename(rx.where);
261 Mmsg(buf," where=\"%s\"",
262 escaped_where_name ? escaped_where_name : rx.where);
264 pm_strcat(ua->cmd, buf);
267 Mmsg(buf, " replace=%s", rx.replace);
268 pm_strcat(ua->cmd, buf);
272 Mmsg(buf, " comment=\"%s\"", rx.comment);
273 pm_strcat(ua->cmd, buf);
276 if (escaped_bsr_name != NULL) {
277 bfree(escaped_bsr_name);
280 if (escaped_where_name != NULL) {
281 bfree(escaped_where_name);
288 if (find_arg(ua, NT_("yes")) > 0) {
289 pm_strcat(ua->cmd, " yes"); /* pass it on to the run command */
291 Dmsg1(200, "Submitting: %s\n", ua->cmd);
292 /* Transfer jobids to jcr to for picking up restore objects */
293 jcr->JobIds = rx.JobIds;
296 run_cmd(ua, ua->cmd);
301 if (escaped_bsr_name != NULL) {
302 bfree(escaped_bsr_name);
305 if (escaped_where_name != NULL) {
306 bfree(escaped_where_name);
319 * Fill the rx->BaseJobIds and display the list
321 static void get_and_display_basejobs(UAContext *ua, RESTORE_CTX *rx)
325 if (!db_get_used_base_jobids(ua->jcr, ua->db, rx->JobIds, &jobids)) {
326 ua->warning_msg("%s", db_strerror(ua->db));
331 Mmsg(q, uar_print_jobs, jobids.list);
332 ua->send_msg(_("The restore will use the following job(s) as Base\n"));
333 db_list_sql_query(ua->jcr, ua->db, q.c_str(), prtit, ua, 1, HORZ_LIST);
335 pm_strcpy(rx->BaseJobIds, jobids.list);
338 static void free_rx(RESTORE_CTX *rx)
342 free_and_null_pool_memory(rx->JobIds);
343 free_and_null_pool_memory(rx->BaseJobIds);
344 free_and_null_pool_memory(rx->fname);
345 free_and_null_pool_memory(rx->path);
346 free_and_null_pool_memory(rx->query);
347 free_name_list(&rx->name_list);
350 static bool has_value(UAContext *ua, int i)
353 ua->error_msg(_("Missing value for keyword: %s\n"), ua->argk[i]);
360 * This gets the client name from which the backup was made
362 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
364 /* If no client name specified yet, get it now */
365 if (!rx->ClientName[0]) {
367 /* try command line argument */
368 int i = find_arg_with_value(ua, NT_("client"));
370 i = find_arg_with_value(ua, NT_("backupclient"));
373 if (!has_value(ua, i)) {
376 bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
379 memset(&cr, 0, sizeof(cr));
380 if (!get_client_dbr(ua, &cr)) {
383 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
389 * This is where we pick up a client name to restore to.
391 static int get_restore_client_name(UAContext *ua, RESTORE_CTX &rx)
393 /* Start with same name as backup client */
394 bstrncpy(rx.RestoreClientName, rx.ClientName, sizeof(rx.RestoreClientName));
396 /* try command line argument */
397 int i = find_arg_with_value(ua, NT_("restoreclient"));
399 if (!has_value(ua, i)) {
402 bstrncpy(rx.RestoreClientName, ua->argv[i], sizeof(rx.RestoreClientName));
411 * The first step in the restore process is for the user to
412 * select a list of JobIds from which he will subsequently
413 * select which files are to be restored.
415 * Returns: 2 if filename list made
416 * 1 if jobid list made
419 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
422 char date[MAX_TIME_LENGTH];
423 bool have_date = false;
424 /* Include current second if using current time */
425 utime_t now = time(NULL) + 1;
427 JOB_DBR jr = { (JobId_t)-1 };
430 const char *list[] = {
431 _("List last 20 Jobs run"),
432 _("List Jobs where a given File is saved"),
433 _("Enter list of comma separated JobIds to select"),
434 _("Enter SQL list command"),
435 _("Select the most recent backup for a client"),
436 _("Select backup for a client before a specified time"),
437 _("Enter a list of files to restore"),
438 _("Enter a list of files to restore before a specified time"),
439 _("Find the JobIds of the most recent backup for a client"),
440 _("Find the JobIds for a backup for a client before a specified time"),
441 _("Enter a list of directories to restore for found JobIds"),
442 _("Select full restore to a specified Job date"),
447 /* These keywords are handled in a for loop */
457 /* The keyword below are handled by individual arg lookups */
463 "bootstrap", /* 13 */
465 "strip_prefix", /* 15 */
466 "add_prefix", /* 16 */
467 "add_suffix", /* 17 */
468 "regexwhere", /* 18 */
469 "restoreclient", /* 19 */
472 "restorejob", /* 22 */
479 for (i=1; i<ua->argc; i++) { /* loop through arguments */
480 bool found_kw = false;
481 for (j=0; kw[j]; j++) { /* loop through keywords */
482 if (strcasecmp(kw[j], ua->argk[i]) == 0) {
488 ua->error_msg(_("Unknown keyword: %s\n"), ua->argk[i]);
491 /* Found keyword in kw[] list, process it */
494 if (!has_value(ua, i)) {
497 if (*rx->JobIds != 0) {
498 pm_strcat(rx->JobIds, ",");
500 pm_strcat(rx->JobIds, ua->argv[i]);
503 case 1: /* current */
505 * Note, we add one second here just to include any job
506 * that may have finished within the current second,
507 * which happens a lot in scripting small jobs.
509 bstrutime(date, sizeof(date), now);
513 if (have_date || !has_value(ua, i)) {
516 if (str_to_utime(ua->argv[i]) == 0) {
517 ua->error_msg(_("Improper date format: %s\n"), ua->argv[i]);
520 bstrncpy(date, ua->argv[i], sizeof(date));
525 if (!has_value(ua, i)) {
529 bstrutime(date, sizeof(date), now);
531 if (!get_client_name(ua, rx)) {
534 pm_strcpy(ua->cmd, ua->argv[i]);
535 insert_one_file_or_dir(ua, rx, date, j==4);
539 bstrutime(date, sizeof(date), now);
541 if (!select_backups_before_date(ua, rx, date)) {
546 case 6: /* pool specified */
547 if (!has_value(ua, i)) {
550 rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
552 ua->error_msg(_("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
555 if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
557 ua->error_msg(_("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
561 case 7: /* all specified */
565 * All keywords 7 or greater are ignored or handled by a select prompt
573 ua->send_msg(_("\nFirst you select one or more JobIds that contain files\n"
574 "to be restored. You will be presented several methods\n"
575 "of specifying the JobIds. Then you will be allowed to\n"
576 "select which files from those JobIds are to be restored.\n\n"));
579 /* If choice not already made above, prompt */
586 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
587 for (int i=0; list[i]; i++) {
588 add_prompt(ua, list[i]);
591 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
592 case -1: /* error or cancel */
594 case 0: /* list last 20 Jobs run */
595 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
596 ua->error_msg(_("SQL query not authorized.\n"));
599 gui_save = ua->jcr->gui;
601 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
602 ua->jcr->gui = gui_save;
605 case 1: /* list where a file is saved */
606 if (!get_client_name(ua, rx)) {
609 if (!get_cmd(ua, _("Enter Filename (no path):"))) {
612 len = strlen(ua->cmd);
613 fname = (char *)malloc(len * 2 + 1);
614 db_escape_string(ua->jcr, ua->db, fname, ua->cmd, len);
615 Mmsg(rx->query, uar_file[db_type], rx->ClientName, fname);
617 gui_save = ua->jcr->gui;
619 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
620 ua->jcr->gui = gui_save;
623 case 2: /* enter a list of JobIds */
624 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
627 pm_strcpy(rx->JobIds, ua->cmd);
629 case 3: /* Enter an SQL list command */
630 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
631 ua->error_msg(_("SQL query not authorized.\n"));
634 if (!get_cmd(ua, _("Enter SQL list command: "))) {
637 gui_save = ua->jcr->gui;
639 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
640 ua->jcr->gui = gui_save;
643 case 4: /* Select the most recent backups */
645 bstrutime(date, sizeof(date), now);
647 if (!select_backups_before_date(ua, rx, date)) {
651 case 5: /* select backup at specified time */
653 if (!get_date(ua, date, sizeof(date))) {
657 if (!select_backups_before_date(ua, rx, date)) {
661 case 6: /* Enter files */
663 bstrutime(date, sizeof(date), now);
665 if (!get_client_name(ua, rx)) {
668 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
669 "containing a list of file names with paths, and terminate\n"
670 "them with a blank line.\n"));
672 if (!get_cmd(ua, _("Enter full filename: "))) {
675 len = strlen(ua->cmd);
679 insert_one_file_or_dir(ua, rx, date, false);
682 case 7: /* enter files backed up before specified time */
684 if (!get_date(ua, date, sizeof(date))) {
688 if (!get_client_name(ua, rx)) {
691 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
692 "containing a list of file names with paths, and terminate\n"
693 "them with a blank line.\n"));
695 if (!get_cmd(ua, _("Enter full filename: "))) {
698 len = strlen(ua->cmd);
702 insert_one_file_or_dir(ua, rx, date, false);
706 case 8: /* Find JobIds for current backup */
708 bstrutime(date, sizeof(date), now);
710 if (!select_backups_before_date(ua, rx, date)) {
716 case 9: /* Find JobIds for give date */
718 if (!get_date(ua, date, sizeof(date))) {
722 if (!select_backups_before_date(ua, rx, date)) {
728 case 10: /* Enter directories */
729 if (*rx->JobIds != 0) {
730 ua->send_msg(_("You have already selected the following JobIds: %s\n"),
732 } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
733 if (*rx->JobIds != 0 && *ua->cmd) {
734 pm_strcat(rx->JobIds, ",");
736 pm_strcat(rx->JobIds, ua->cmd);
738 if (*rx->JobIds == 0 || *rx->JobIds == '.') {
740 return 0; /* nothing entered, return */
743 bstrutime(date, sizeof(date), now);
745 if (!get_client_name(ua, rx)) {
748 ua->send_msg(_("Enter full directory names or start the name\n"
749 "with a < to indicate it is a filename containing a list\n"
750 "of directories and terminate them with a blank line.\n"));
752 if (!get_cmd(ua, _("Enter directory name: "))) {
755 len = strlen(ua->cmd);
759 /* Add trailing slash to end of directory names */
760 if (ua->cmd[0] != '<' && !IsPathSeparator(ua->cmd[len-1])) {
761 strcat(ua->cmd, "/");
763 insert_one_file_or_dir(ua, rx, date, true);
767 case 11: /* Choose a jobid and select jobs */
768 if (!get_cmd(ua, _("Enter JobId to get the state to restore: ")) ||
769 !is_an_integer(ua->cmd))
774 memset(&jr, 0, sizeof(JOB_DBR));
775 jr.JobId = str_to_int64(ua->cmd);
776 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
777 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
778 ua->cmd, db_strerror(ua->db));
781 ua->send_msg(_("Selecting jobs to build the Full state at %s\n"),
783 jr.JobLevel = L_INCREMENTAL; /* Take Full+Diff+Incr */
784 if (!db_accurate_get_jobids(ua->jcr, ua->db, &jr, &jobids)) {
787 pm_strcpy(rx->JobIds, jobids.list);
788 Dmsg1(30, "Item 12: jobids = %s\n", rx->JobIds);
790 case 12: /* Cancel or quit */
795 memset(&jr, 0, sizeof(JOB_DBR));
796 POOLMEM *JobIds = get_pool_memory(PM_FNAME);
800 * Find total number of files to be restored, and filter the JobId
801 * list to contain only ones permitted by the ACL conditions.
803 for (p=rx->JobIds; ; ) {
805 int stat = get_next_jobid_from_list(&p, &JobId);
807 ua->error_msg(_("Invalid JobId in list.\n"));
808 free_pool_memory(JobIds);
814 if (jr.JobId == JobId) {
815 continue; /* duplicate of last JobId */
817 memset(&jr, 0, sizeof(JOB_DBR));
819 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
820 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
821 edit_int64(JobId, ed1), db_strerror(ua->db));
822 free_pool_memory(JobIds);
825 if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
826 ua->error_msg(_("Access to JobId=%s (Job \"%s\") not authorized. Not selected.\n"),
827 edit_int64(JobId, ed1), jr.Name);
831 pm_strcat(JobIds, ",");
833 pm_strcat(JobIds, edit_int64(JobId, ed1));
834 rx->TotalFiles += jr.JobFiles;
836 free_pool_memory(rx->JobIds);
837 rx->JobIds = JobIds; /* Set ACL filtered list */
838 if (*rx->JobIds == 0) {
839 ua->warning_msg(_("No Jobs selected.\n"));
843 if (strchr(rx->JobIds,',')) {
844 ua->info_msg(_("You have selected the following JobIds: %s\n"), rx->JobIds);
846 ua->info_msg(_("You have selected the following JobId: %s\n"), rx->JobIds);
854 static bool get_date(UAContext *ua, char *date, int date_len)
856 ua->send_msg(_("The restored files will the most current backup\n"
857 "BEFORE the date you specify below.\n\n"));
859 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
862 if (str_to_utime(ua->cmd) != 0) {
865 ua->error_msg(_("Improper date format.\n"));
867 bstrncpy(date, ua->cmd, date_len);
872 * Insert a single file, or read a list of files from a file
874 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
884 if ((ffd = fopen(p, "rb")) == NULL) {
886 ua->error_msg(_("Cannot open file %s: ERR=%s\n"),
890 while (fgets(file, sizeof(file), ffd)) {
893 if (!insert_dir_into_findex_list(ua, rx, file, date)) {
894 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
897 if (!insert_file_into_findex_list(ua, rx, file, date)) {
898 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
906 insert_table_into_findex_list(ua, rx, p);
910 insert_dir_into_findex_list(ua, rx, ua->cmd, date);
912 insert_file_into_findex_list(ua, rx, ua->cmd, date);
919 * For a given file (path+filename), split into path and file, then
920 * lookup the most recent backup in the catalog to get the JobId
921 * and FileIndex, then insert them into the findex list.
923 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
926 strip_trailing_newline(file);
927 split_path_and_filename(ua, rx, file);
928 if (*rx->JobIds == 0) {
929 Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname,
932 Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
933 rx->path, rx->fname, rx->ClientName);
936 /* Find and insert jobid and File Index */
937 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
938 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
939 rx->query, db_strerror(ua->db));
942 ua->error_msg(_("No database record found for: %s\n"), file);
943 // ua->error_msg("Query=%s\n", rx->query);
950 * For a given path lookup the most recent backup in the catalog
951 * to get the JobId and FileIndexes of all files in that directory.
953 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
956 strip_trailing_junk(dir);
957 if (*rx->JobIds == 0) {
958 ua->error_msg(_("No JobId specified cannot continue.\n"));
961 Mmsg(rx->query, uar_jobid_fileindex_from_dir[db_type], rx->JobIds, dir, rx->ClientName);
964 /* Find and insert jobid and File Index */
965 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
966 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
967 rx->query, db_strerror(ua->db));
970 ua->error_msg(_("No database record found for: %s\n"), dir);
977 * Get the JobId and FileIndexes of all files in the specified table
979 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
981 strip_trailing_junk(table);
982 Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
985 /* Find and insert jobid and File Index */
986 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
987 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
988 rx->query, db_strerror(ua->db));
991 ua->error_msg(_("No table found: %s\n"), table);
997 static void split_path_and_filename(UAContext *ua, RESTORE_CTX *rx, char *name)
1001 /* Find path without the filename.
1002 * I.e. everything after the last / is a "filename".
1003 * OK, maybe it is a directory name, but we treat it like
1004 * a filename. If we don't find a / then the whole name
1005 * must be a path name (e.g. c:).
1007 for (p=f=name; *p; p++) {
1008 if (IsPathSeparator(*p)) {
1009 f = p; /* set pos of last slash */
1012 if (IsPathSeparator(*f)) { /* did we find a slash? */
1013 f++; /* yes, point to filename */
1014 } else { /* no, whole thing must be path name */
1018 /* If filename doesn't exist (i.e. root directory), we
1019 * simply create a blank name consisting of a single
1020 * space. This makes handling zero length filenames
1025 rx->fname = check_pool_memory_size(rx->fname, 2*(rx->fnl)+1);
1026 db_escape_string(ua->jcr, ua->db, rx->fname, f, rx->fnl);
1034 rx->path = check_pool_memory_size(rx->path, 2*(rx->pnl)+1);
1035 db_escape_string(ua->jcr, ua->db, rx->path, name, rx->pnl);
1041 Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
1044 static bool ask_for_fileregex(UAContext *ua, RESTORE_CTX *rx)
1046 if (find_arg(ua, NT_("all")) >= 0) { /* if user enters all on command line */
1047 return true; /* select everything */
1049 ua->send_msg(_("\n\nFor one or more of the JobIds selected, no files were found,\n"
1050 "so file selection is not possible.\n"
1051 "Most likely your retention policy pruned the files.\n"));
1052 if (get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
1053 if (ua->pint32_val == 1)
1055 while (get_cmd(ua, _("\nRegexp matching files to restore? (empty to abort): "))) {
1056 if (ua->cmd[0] == '\0') {
1059 regex_t *fileregex_re = NULL;
1061 char errmsg[500] = "";
1063 fileregex_re = (regex_t *)bmalloc(sizeof(regex_t));
1064 rc = regcomp(fileregex_re, ua->cmd, REG_EXTENDED|REG_NOSUB);
1066 regerror(rc, fileregex_re, errmsg, sizeof(errmsg));
1068 regfree(fileregex_re);
1071 ua->send_msg(_("Regex compile error: %s\n"), errmsg);
1073 rx->bsr->fileregex = bstrdup(ua->cmd);
1082 /* Walk on the delta_list of a TREE_NODE item and insert all parts
1083 * TODO: Optimize for bootstrap creation, remove recursion
1084 * 6 -> 5 -> 4 -> 3 -> 2 -> 1 -> 0
1086 * 0, 1, 2, 3, 4, 5, 6
1088 static void add_delta_list_findex(RESTORE_CTX *rx, struct delta_list *lst)
1094 add_delta_list_findex(rx, lst->next);
1096 add_findex(rx->bsr, lst->JobId, lst->FileIndex);
1099 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
1102 JobId_t JobId, last_JobId;
1107 memset(&tree, 0, sizeof(TREE_CTX));
1109 * Build the directory tree containing JobIds user selected
1111 tree.root = new_tree(rx->TotalFiles);
1116 * For display purposes, the same JobId, with different volumes may
1117 * appear more than once, however, we only insert it once.
1120 tree.FileEstimate = 0;
1121 if (get_next_jobid_from_list(&p, &JobId) > 0) {
1122 /* Use first JobId as estimate of the number of files to restore */
1123 Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
1124 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1125 ua->error_msg("%s\n", db_strerror(ua->db));
1128 /* Add about 25% more than this job for over estimate */
1129 tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
1130 tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
1134 ua->info_msg(_("\nBuilding directory tree for JobId(s) %s ... "),
1137 #define new_get_file_list
1138 #ifdef new_get_file_list
1139 if (!db_get_file_list(ua->jcr, ua->db,
1140 rx->JobIds, false /* do not use md5 */,
1141 true /* get delta */,
1142 insert_tree_handler, (void *)&tree))
1144 ua->error_msg("%s", db_strerror(ua->db));
1146 if (*rx->BaseJobIds) {
1147 pm_strcat(rx->JobIds, ",");
1148 pm_strcat(rx->JobIds, rx->BaseJobIds);
1151 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1154 if (JobId == last_JobId) {
1155 continue; /* eliminate duplicate JobIds */
1159 * Find files for this JobId and insert them in the tree
1161 Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
1162 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
1163 ua->error_msg("%s", db_strerror(ua->db));
1168 * Look at the first JobId on the list (presumably the oldest) and
1169 * if it is marked purged, don't do the manual selection because
1170 * the Job was pruned, so the tree is incomplete.
1172 if (tree.FileCount != 0) {
1173 /* Find out if any Job is purged */
1174 Mmsg(rx->query, "SELECT SUM(PurgedFiles) FROM Job WHERE JobId IN (%s)", rx->JobIds);
1175 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1176 ua->error_msg("%s\n", db_strerror(ua->db));
1178 /* rx->JobId is the PurgedFiles flag */
1179 if (rx->found && rx->JobId > 0) {
1180 tree.FileCount = 0; /* set count to zero, no tree selection */
1183 if (tree.FileCount == 0) {
1184 OK = ask_for_fileregex(ua, rx);
1187 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1188 if (JobId == last_JobId) {
1189 continue; /* eliminate duplicate JobIds */
1191 add_findex_all(rx->bsr, JobId);
1197 ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
1198 edit_uint64_with_commas(tree.FileCount, ec1));
1200 ua->info_msg(_("\n%s files inserted into the tree.\n"),
1201 edit_uint64_with_commas(tree.FileCount, ec1));
1204 if (find_arg(ua, NT_("done")) < 0) {
1205 /* Let the user interact in selecting which files to restore */
1206 OK = user_select_files_from_tree(&tree);
1210 * Walk down through the tree finding all files marked to be
1211 * extracted making a bootstrap file.
1214 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
1215 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
1216 if (node->extract || node->extract_dir) {
1217 Dmsg3(400, "JobId=%lld type=%d FI=%d\n", (uint64_t)node->JobId, node->type, node->FileIndex);
1218 /* TODO: optimize bsr insertion when jobid are non sorted */
1219 add_delta_list_findex(rx, node->delta_list);
1220 add_findex(rx->bsr, node->JobId, node->FileIndex);
1221 if (node->extract && node->type != TN_NEWDIR) {
1222 rx->selected_files++; /* count only saved files */
1229 free_tree(tree.root); /* free the directory tree */
1235 * This routine is used to get the current backup or a backup
1236 * before the specified date.
1238 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
1243 char fileset_name[MAX_NAME_LENGTH];
1244 char ed1[50], ed2[50];
1245 char pool_select[MAX_NAME_LENGTH];
1248 /* Create temp tables */
1249 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1250 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1251 if (!db_sql_query(ua->db, uar_create_temp[db_type], NULL, NULL)) {
1252 ua->error_msg("%s\n", db_strerror(ua->db));
1254 if (!db_sql_query(ua->db, uar_create_temp1[db_type], NULL, NULL)) {
1255 ua->error_msg("%s\n", db_strerror(ua->db));
1258 * Select Client from the Catalog
1260 memset(&cr, 0, sizeof(cr));
1261 if (!get_client_dbr(ua, &cr)) {
1264 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1269 memset(&fsr, 0, sizeof(fsr));
1270 i = find_arg_with_value(ua, "FileSet");
1272 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1273 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1274 ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1275 db_strerror(ua->db));
1279 if (i < 0) { /* fileset not found */
1280 edit_int64(cr.ClientId, ed1);
1281 Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1282 start_prompt(ua, _("The defined FileSet resources are:\n"));
1283 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1284 ua->error_msg("%s\n", db_strerror(ua->db));
1286 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1287 fileset_name, sizeof(fileset_name)) < 0) {
1288 ua->error_msg(_("No FileSet found for client \"%s\".\n"), cr.Name);
1292 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1293 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1294 ua->warning_msg(_("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1295 ua->send_msg(_("This probably means you modified the FileSet.\n"
1296 "Continuing anyway.\n"));
1300 /* If Pool specified, add PoolId specification */
1304 memset(&pr, 0, sizeof(pr));
1305 bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1306 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1307 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
1308 edit_int64(pr.PoolId, ed1));
1310 ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1314 /* Find JobId of last Full backup for this client, fileset */
1315 edit_int64(cr.ClientId, ed1);
1316 Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1318 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1319 ua->error_msg("%s\n", db_strerror(ua->db));
1323 /* Find all Volumes used by that JobId */
1324 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1325 ua->error_msg("%s\n", db_strerror(ua->db));
1329 /* Note, this is needed because I don't seem to get the callback
1330 * from the call just above.
1333 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1334 ua->warning_msg("%s\n", db_strerror(ua->db));
1336 if (rx->JobTDate == 0) {
1337 ua->error_msg(_("No Full backup before %s found.\n"), date);
1341 /* Now find most recent Differental Job after Full save, if any */
1342 Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1343 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1344 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1345 ua->warning_msg("%s\n", db_strerror(ua->db));
1347 /* Now update JobTDate to look into Differental, if any */
1349 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1350 ua->warning_msg("%s\n", db_strerror(ua->db));
1352 if (rx->JobTDate == 0) {
1353 ua->error_msg(_("No Full backup before %s found.\n"), date);
1357 /* Now find all Incremental Jobs after Full/dif save */
1358 Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1359 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1360 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1361 ua->warning_msg("%s\n", db_strerror(ua->db));
1364 /* Get the JobIds from that list */
1365 rx->last_jobid[0] = rx->JobIds[0] = 0;
1367 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1368 ua->warning_msg("%s\n", db_strerror(ua->db));
1371 if (rx->JobIds[0] != 0) {
1372 if (find_arg(ua, NT_("copies")) > 0) {
1373 /* Display a list of all copies */
1374 db_list_copies_records(ua->jcr, ua->db, 0, rx->JobIds,
1375 prtit, ua, HORZ_LIST);
1377 /* Display a list of Jobs selected for this restore */
1378 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1,HORZ_LIST);
1382 ua->warning_msg(_("No jobs found.\n"));
1386 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1387 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1391 static int restore_count_handler(void *ctx, int num_fields, char **row)
1393 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1394 rx->JobId = str_to_int64(row[0]);
1400 * Callback handler to get JobId and FileIndex for files
1401 * can insert more than one depending on the caller.
1403 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1405 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1407 Dmsg2(200, "JobId=%s FileIndex=%s\n", row[0], row[1]);
1408 rx->JobId = str_to_int64(row[0]);
1409 add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1411 rx->selected_files++;
1416 * Callback handler make list of JobIds
1418 static int jobid_handler(void *ctx, int num_fields, char **row)
1420 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1422 if (strcmp(rx->last_jobid, row[0]) == 0) {
1423 return 0; /* duplicate id */
1425 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1426 if (rx->JobIds[0] != 0) {
1427 pm_strcat(rx->JobIds, ",");
1429 pm_strcat(rx->JobIds, row[0]);
1435 * Callback handler to pickup last Full backup JobTDate
1437 static int last_full_handler(void *ctx, int num_fields, char **row)
1439 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1441 rx->JobTDate = str_to_int64(row[1]);
1446 * Callback handler build FileSet name prompt list
1448 static int fileset_handler(void *ctx, int num_fields, char **row)
1450 /* row[0] = FileSet (name) */
1452 add_prompt((UAContext *)ctx, row[0]);
1458 * Free names in the list
1460 static void free_name_list(NAME_LIST *name_list)
1462 for (int i=0; i < name_list->num_ids; i++) {
1463 free(name_list->name[i]);
1465 bfree_and_null(name_list->name);
1466 name_list->max_ids = 0;
1467 name_list->num_ids = 0;
1470 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType)
1475 Dmsg1(200, "Already have store=%s\n", rx.store->name());
1479 * Try looking up Storage by name
1482 foreach_res(store, R_STORAGE) {
1483 if (strcmp(Storage, store->name()) == 0) {
1484 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1493 /* Check if an explicit storage resource is given */
1495 int i = find_arg_with_value(ua, "storage");
1497 store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1498 if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1502 if (store && (store != rx.store)) {
1503 ua->info_msg(_("Warning default storage overridden by \"%s\" on command line.\n"),
1506 Dmsg1(200, "Set store=%s\n", rx.store->name());
1511 /* If no storage resource, try to find one from MediaType */
1514 foreach_res(store, R_STORAGE) {
1515 if (strcmp(MediaType, store->media_type) == 0) {
1516 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1518 Dmsg1(200, "Set store=%s\n", rx.store->name());
1519 ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1520 Storage, store->name(), MediaType);
1527 ua->warning_msg(_("\nUnable to find Storage resource for\n"
1528 "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1531 /* Take command line arg, or ask user if none */
1532 rx.store = get_storage_resource(ua, false /* don't use default */);
1534 Dmsg1(200, "Set store=%s\n", rx.store->name());