2 Bacula® - The Network Backup Solution
4 Copyright (C) 2002-2014 Free Software Foundation Europe e.V.
6 The main author of Bacula is Kern Sibbald, with contributions from many
7 others, a complete list can be found in the file AUTHORS.
9 You may use this file and others of this release according to the
10 license defined in the LICENSE file, which includes the Affero General
11 Public License, v3.0 ("AGPLv3") and some additional permissions and
12 terms pursuant to its AGPLv3 Section 7.
14 Bacula® is a registered trademark of Kern Sibbald.
18 * Bacula Director -- User Agent Database restore Command
19 * Creates a bootstrap file for restoring files and
20 * starts the restore job.
22 * Tree handling routines split into ua_tree.c July MMIII.
23 * BSR (bootstrap record) handling routines split into
26 * Kern Sibbald, July MMII
33 /* Imported functions */
34 extern void print_bsr(UAContext *ua, RBSR *bsr);
37 /* Forward referenced functions */
38 static int last_full_handler(void *ctx, int num_fields, char **row);
39 static int jobid_handler(void *ctx, int num_fields, char **row);
40 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx);
41 static int fileset_handler(void *ctx, int num_fields, char **row);
42 static void free_name_list(NAME_LIST *name_list);
43 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date);
44 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx);
45 static void free_rx(RESTORE_CTX *rx);
46 static void split_path_and_filename(UAContext *ua, RESTORE_CTX *rx, char *fname);
47 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row);
48 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
50 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
52 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir);
53 static int get_client_name(UAContext *ua, RESTORE_CTX *rx);
54 static int get_restore_client_name(UAContext *ua, RESTORE_CTX &rx);
55 static bool get_date(UAContext *ua, char *date, int date_len);
56 static int restore_count_handler(void *ctx, int num_fields, char **row);
57 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table);
58 static void get_and_display_basejobs(UAContext *ua, RESTORE_CTX *rx);
64 int restore_cmd(UAContext *ua, const char *cmd)
66 RESTORE_CTX rx; /* restore context */
71 char *escaped_bsr_name = NULL;
72 char *escaped_where_name = NULL;
73 char *strip_prefix, *add_prefix, *add_suffix, *regexp;
74 strip_prefix = add_prefix = add_suffix = regexp = NULL;
76 memset(&rx, 0, sizeof(rx));
77 rx.path = get_pool_memory(PM_FNAME);
80 rx.fname = get_pool_memory(PM_FNAME);
83 rx.JobIds = get_pool_memory(PM_FNAME);
86 rx.component_fname = get_pool_memory(PM_FNAME);
87 rx.component_fname[0] = 0;
89 rx.BaseJobIds = get_pool_memory(PM_FNAME);
92 rx.query = get_pool_memory(PM_FNAME);
96 rx.hardlinks_in_mem = true;
98 if (!open_new_client_db(ua)) {
102 for (i = 0; i < ua->argc ; i++) {
104 continue; /* skip if no value given */
106 if (strcasecmp(ua->argk[i], "comment") == 0) {
107 rx.comment = ua->argv[i];
108 if (!is_comment_legal(ua, rx.comment)) {
112 } else if (strcasecmp(ua->argk[i], "where") == 0) {
113 rx.where = ua->argv[i];
115 } else if (strcasecmp(ua->argk[i], "replace") == 0) {
116 rx.replace = ua->argv[i];
118 } else if (strcasecmp(ua->argk[i], "strip_prefix") == 0) {
119 strip_prefix = ua->argv[i];
121 } else if (strcasecmp(ua->argk[i], "add_prefix") == 0) {
122 add_prefix = ua->argv[i];
124 } else if (strcasecmp(ua->argk[i], "add_suffix") == 0) {
125 add_suffix = ua->argv[i];
127 } else if (strcasecmp(ua->argk[i], "regexwhere") == 0) {
128 rx.RegexWhere = ua->argv[i];
130 } else if (strcasecmp(ua->argk[i], "optimizespeed") == 0) {
131 if (strcasecmp(ua->argv[i], "0") || strcasecmp(ua->argv[i], "no") ||
132 strcasecmp(ua->argv[i], "false")) {
133 rx.hardlinks_in_mem = false;
138 if (strip_prefix || add_suffix || add_prefix) {
139 int len = bregexp_get_build_where_size(strip_prefix, add_prefix, add_suffix);
140 regexp = (char *)bmalloc(len * sizeof(char));
142 bregexp_build_where(regexp, len, strip_prefix, add_prefix, add_suffix);
143 rx.RegexWhere = regexp;
146 /* TODO: add acl for regexwhere ? */
149 if (!acl_access_ok(ua, Where_ACL, rx.RegexWhere)) {
150 ua->error_msg(_("\"RegexWhere\" specification not authorized.\n"));
156 if (!acl_access_ok(ua, Where_ACL, rx.where)) {
157 ua->error_msg(_("\"where\" specification not authorized.\n"));
162 /* Ensure there is at least one Restore Job */
164 foreach_res(job, R_JOB) {
165 if (job->JobType == JT_RESTORE) {
166 if (!rx.restore_job) {
167 rx.restore_job = job;
173 if (!rx.restore_jobs) {
175 "No Restore Job Resource found in bacula-dir.conf.\n"
176 "You must create at least one before running this command.\n"));
181 * Request user to select JobIds or files by various different methods
182 * last 20 jobs, where File saved, most recent backup, ...
183 * In the end, a list of files are pumped into
186 switch (user_select_jobids_or_files(ua, &rx)) {
189 case 1: /* selected by jobid */
190 get_and_display_basejobs(ua, &rx);
191 if (!build_directory_tree(ua, &rx)) {
192 ua->send_msg(_("Restore not done.\n"));
196 case 2: /* selected by filename, no tree needed */
202 if (!complete_bsr(ua, rx.bsr)) { /* find Vol, SessId, SessTime from JobIds */
203 ua->error_msg(_("Unable to construct a valid BSR. Cannot continue.\n"));
206 if (!(rx.selected_files = write_bsr_file(ua, rx))) {
207 ua->warning_msg(_("No files selected to be restored.\n"));
210 display_bsr_info(ua, rx); /* display vols needed, etc */
212 if (rx.selected_files==1) {
213 ua->info_msg(_("\n1 file selected to be restored.\n\n"));
215 ua->info_msg(_("\n%s files selected to be restored.\n\n"),
216 edit_uint64_with_commas(rx.selected_files, ed1));
219 ua->warning_msg(_("No files selected to be restored.\n"));
223 if (rx.restore_jobs == 1) {
224 job = rx.restore_job;
226 job = get_restore_job(ua);
232 get_client_name(ua, &rx);
233 if (!rx.ClientName) {
234 ua->error_msg(_("No Client resource found!\n"));
237 get_restore_client_name(ua, rx);
239 escaped_bsr_name = escape_filename(jcr->RestoreBootstrap);
242 "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
243 " bootstrap=\"%s\" files=%u catalog=\"%s\"",
244 job->name(), rx.ClientName, rx.RestoreClientName,
245 rx.store?rx.store->name():"",
246 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
247 rx.selected_files, ua->catalog->name());
249 /* Build run command */
251 if (rx.RestoreMediaType[0]) {
252 Mmsg(buf, " mediatype=\"%s\"", rx.RestoreMediaType);
253 pm_strcat(ua->cmd, buf);
257 escaped_where_name = escape_filename(rx.RegexWhere);
258 Mmsg(buf, " regexwhere=\"%s\"",
259 escaped_where_name ? escaped_where_name : rx.RegexWhere);
261 } else if (rx.where) {
262 escaped_where_name = escape_filename(rx.where);
263 Mmsg(buf," where=\"%s\"",
264 escaped_where_name ? escaped_where_name : rx.where);
266 pm_strcat(ua->cmd, buf);
269 Mmsg(buf, " replace=%s", rx.replace);
270 pm_strcat(ua->cmd, buf);
274 Mmsg(buf, " comment=\"%s\"", rx.comment);
275 pm_strcat(ua->cmd, buf);
278 if (escaped_bsr_name != NULL) {
279 bfree(escaped_bsr_name);
282 if (escaped_where_name != NULL) {
283 bfree(escaped_where_name);
290 if (find_arg(ua, NT_("yes")) > 0) {
291 pm_strcat(ua->cmd, " yes"); /* pass it on to the run command */
293 Dmsg1(200, "Submitting: %s\n", ua->cmd);
295 * Transfer jobids, to jcr to
296 * pass to run_cmd(). Note, these are fields and
297 * other things that are not passed on the command
300 /* ***FIXME*** pass jobids on command line */
301 jcr->JobIds = rx.JobIds;
304 run_cmd(ua, ua->cmd);
306 garbage_collect_memory(); /* release unused memory */
310 if (escaped_bsr_name != NULL) {
311 bfree(escaped_bsr_name);
314 if (escaped_where_name != NULL) {
315 bfree(escaped_where_name);
323 garbage_collect_memory(); /* release unused memory */
329 * Fill the rx->BaseJobIds and display the list
331 static void get_and_display_basejobs(UAContext *ua, RESTORE_CTX *rx)
335 if (!db_get_used_base_jobids(ua->jcr, ua->db, rx->JobIds, &jobids)) {
336 ua->warning_msg("%s", db_strerror(ua->db));
341 Mmsg(q, uar_print_jobs, jobids.list);
342 ua->send_msg(_("The restore will use the following job(s) as Base\n"));
343 db_list_sql_query(ua->jcr, ua->db, q.c_str(), prtit, ua, 1, HORZ_LIST);
345 pm_strcpy(rx->BaseJobIds, jobids.list);
348 static void free_rx(RESTORE_CTX *rx)
352 free_and_null_pool_memory(rx->JobIds);
353 free_and_null_pool_memory(rx->BaseJobIds);
354 free_and_null_pool_memory(rx->fname);
355 free_and_null_pool_memory(rx->path);
356 free_and_null_pool_memory(rx->query);
357 free_name_list(&rx->name_list);
360 static bool has_value(UAContext *ua, int i)
363 ua->error_msg(_("Missing value for keyword: %s\n"), ua->argk[i]);
370 * This gets the client name from which the backup was made
372 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
374 /* If no client name specified yet, get it now */
375 if (!rx->ClientName[0]) {
377 /* try command line argument */
378 int i = find_arg_with_value(ua, NT_("client"));
380 i = find_arg_with_value(ua, NT_("backupclient"));
383 if (!is_name_valid(ua->argv[i], &ua->errmsg)) {
384 ua->error_msg("%s argument: %s", ua->argk[i], ua->errmsg);
387 bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
390 memset(&cr, 0, sizeof(cr));
391 if (!get_client_dbr(ua, &cr)) {
394 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
400 * This is where we pick up a client name to restore to.
402 static int get_restore_client_name(UAContext *ua, RESTORE_CTX &rx)
404 /* Start with same name as backup client */
405 bstrncpy(rx.RestoreClientName, rx.ClientName, sizeof(rx.RestoreClientName));
407 /* try command line argument */
408 int i = find_arg_with_value(ua, NT_("restoreclient"));
410 if (!is_name_valid(ua->argv[i], &ua->errmsg)) {
411 ua->error_msg("%s argument: %s", ua->argk[i], ua->errmsg);
414 bstrncpy(rx.RestoreClientName, ua->argv[i], sizeof(rx.RestoreClientName));
423 * The first step in the restore process is for the user to
424 * select a list of JobIds from which he will subsequently
425 * select which files are to be restored.
427 * Returns: 2 if filename list made
428 * 1 if jobid list made
431 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
434 char date[MAX_TIME_LENGTH];
435 bool have_date = false;
436 /* Include current second if using current time */
437 utime_t now = time(NULL) + 1;
439 JOB_DBR jr = { (JobId_t)-1 };
442 const char *list[] = {
443 _("List last 20 Jobs run"),
444 _("List Jobs where a given File is saved"),
445 _("Enter list of comma separated JobIds to select"),
446 _("Enter SQL list command"),
447 _("Select the most recent backup for a client"),
448 _("Select backup for a client before a specified time"),
449 _("Enter a list of files to restore"),
450 _("Enter a list of files to restore before a specified time"),
451 _("Find the JobIds of the most recent backup for a client"),
452 _("Find the JobIds for a backup for a client before a specified time"),
453 _("Enter a list of directories to restore for found JobIds"),
454 _("Select full restore to a specified Job date"),
459 /* These keywords are handled in a for loop */
469 /* The keyword below are handled by individual arg lookups */
475 "bootstrap", /* 13 */
477 "strip_prefix", /* 15 */
478 "add_prefix", /* 16 */
479 "add_suffix", /* 17 */
480 "regexwhere", /* 18 */
481 "restoreclient", /* 19 */
484 "restorejob", /* 22 */
491 for (i=1; i<ua->argc; i++) { /* loop through arguments */
492 bool found_kw = false;
493 for (j=0; kw[j]; j++) { /* loop through keywords */
494 if (strcasecmp(kw[j], ua->argk[i]) == 0) {
501 ua->error_msg(_("Unknown keyword: %s\n"), ua->argk[i]);
504 /* Found keyword in kw[] list, process it */
507 if (!has_value(ua, i)) {
510 if (*rx->JobIds != 0) {
511 pm_strcat(rx->JobIds, ",");
513 pm_strcat(rx->JobIds, ua->argv[i]);
516 case 1: /* current */
518 * Note, we add one second here just to include any job
519 * that may have finished within the current second,
520 * which happens a lot in scripting small jobs.
522 bstrutime(date, sizeof(date), now);
526 if (have_date || !has_value(ua, i)) {
529 if (str_to_utime(ua->argv[i]) == 0) {
530 ua->error_msg(_("Improper date format: %s\n"), ua->argv[i]);
533 bstrncpy(date, ua->argv[i], sizeof(date));
538 if (!has_value(ua, i)) {
542 bstrutime(date, sizeof(date), now);
544 if (!get_client_name(ua, rx)) {
547 pm_strcpy(ua->cmd, ua->argv[i]);
548 insert_one_file_or_dir(ua, rx, date, j==4);
552 bstrutime(date, sizeof(date), now);
554 if (!select_backups_before_date(ua, rx, date)) {
559 case 6: /* pool specified */
560 if (!has_value(ua, i)) {
563 rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
565 ua->error_msg(_("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
568 if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
570 ua->error_msg(_("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
574 case 7: /* all specified */
578 * All keywords 7 or greater are ignored or handled by a select prompt
586 ua->send_msg(_("\nFirst you select one or more JobIds that contain files\n"
587 "to be restored. You will be presented several methods\n"
588 "of specifying the JobIds. Then you will be allowed to\n"
589 "select which files from those JobIds are to be restored.\n\n"));
592 /* If choice not already made above, prompt */
599 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
600 for (int i=0; list[i]; i++) {
601 add_prompt(ua, list[i]);
604 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
605 case -1: /* error or cancel */
607 case 0: /* list last 20 Jobs run */
608 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
609 ua->error_msg(_("SQL query not authorized.\n"));
612 gui_save = ua->jcr->gui;
614 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
615 ua->jcr->gui = gui_save;
618 case 1: /* list where a file is saved */
619 if (!get_client_name(ua, rx)) {
622 if (!get_cmd(ua, _("Enter Filename (no path):"))) {
625 len = strlen(ua->cmd);
626 fname = (char *)malloc(len * 2 + 1);
627 db_escape_string(ua->jcr, ua->db, fname, ua->cmd, len);
628 Mmsg(rx->query, uar_file[db_get_type_index(ua->db)], rx->ClientName, fname);
630 gui_save = ua->jcr->gui;
632 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
633 ua->jcr->gui = gui_save;
636 case 2: /* enter a list of JobIds */
637 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
640 pm_strcpy(rx->JobIds, ua->cmd);
642 case 3: /* Enter an SQL list command */
643 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
644 ua->error_msg(_("SQL query not authorized.\n"));
647 if (!get_cmd(ua, _("Enter SQL list command: "))) {
650 gui_save = ua->jcr->gui;
652 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
653 ua->jcr->gui = gui_save;
656 case 4: /* Select the most recent backups */
658 bstrutime(date, sizeof(date), now);
660 if (!select_backups_before_date(ua, rx, date)) {
664 case 5: /* select backup at specified time */
666 if (!get_date(ua, date, sizeof(date))) {
670 if (!select_backups_before_date(ua, rx, date)) {
674 case 6: /* Enter files */
676 bstrutime(date, sizeof(date), now);
678 if (!get_client_name(ua, rx)) {
681 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
682 "containing a list of file names with paths, and terminate\n"
683 "them with a blank line.\n"));
685 if (!get_cmd(ua, _("Enter full filename: "))) {
688 len = strlen(ua->cmd);
692 insert_one_file_or_dir(ua, rx, date, false);
695 case 7: /* enter files backed up before specified time */
697 if (!get_date(ua, date, sizeof(date))) {
701 if (!get_client_name(ua, rx)) {
704 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
705 "containing a list of file names with paths, and terminate\n"
706 "them with a blank line.\n"));
708 if (!get_cmd(ua, _("Enter full filename: "))) {
711 len = strlen(ua->cmd);
715 insert_one_file_or_dir(ua, rx, date, false);
719 case 8: /* Find JobIds for current backup */
721 bstrutime(date, sizeof(date), now);
723 if (!select_backups_before_date(ua, rx, date)) {
729 case 9: /* Find JobIds for give date */
731 if (!get_date(ua, date, sizeof(date))) {
735 if (!select_backups_before_date(ua, rx, date)) {
741 case 10: /* Enter directories */
742 if (*rx->JobIds != 0) {
743 ua->send_msg(_("You have already selected the following JobIds: %s\n"),
745 } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
746 if (*rx->JobIds != 0 && *ua->cmd) {
747 pm_strcat(rx->JobIds, ",");
749 pm_strcat(rx->JobIds, ua->cmd);
751 if (*rx->JobIds == 0 || *rx->JobIds == '.') {
753 return 0; /* nothing entered, return */
756 bstrutime(date, sizeof(date), now);
758 if (!get_client_name(ua, rx)) {
761 ua->send_msg(_("Enter full directory names or start the name\n"
762 "with a < to indicate it is a filename containing a list\n"
763 "of directories and terminate them with a blank line.\n"));
765 if (!get_cmd(ua, _("Enter directory name: "))) {
768 len = strlen(ua->cmd);
772 /* Add trailing slash to end of directory names */
773 if (ua->cmd[0] != '<' && !IsPathSeparator(ua->cmd[len-1])) {
774 strcat(ua->cmd, "/");
776 insert_one_file_or_dir(ua, rx, date, true);
780 case 11: /* Choose a jobid and select jobs */
781 if (!get_cmd(ua, _("Enter JobId to get the state to restore: ")) ||
782 !is_an_integer(ua->cmd))
787 memset(&jr, 0, sizeof(JOB_DBR));
788 jr.JobId = str_to_int64(ua->cmd);
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 ua->cmd, db_strerror(ua->db));
794 ua->send_msg(_("Selecting jobs to build the Full state at %s\n"),
796 jr.JobLevel = L_INCREMENTAL; /* Take Full+Diff+Incr */
797 if (!db_accurate_get_jobids(ua->jcr, ua->db, &jr, &jobids)) {
800 pm_strcpy(rx->JobIds, jobids.list);
801 Dmsg1(30, "Item 12: jobids = %s\n", rx->JobIds);
803 case 12: /* Cancel or quit */
808 memset(&jr, 0, sizeof(JOB_DBR));
809 POOLMEM *JobIds = get_pool_memory(PM_FNAME);
813 * Find total number of files to be restored, and filter the JobId
814 * list to contain only ones permitted by the ACL conditions.
816 for (p=rx->JobIds; ; ) {
818 int stat = get_next_jobid_from_list(&p, &JobId);
820 ua->error_msg(_("Invalid JobId in list.\n"));
821 free_pool_memory(JobIds);
827 if (jr.JobId == JobId) {
828 continue; /* duplicate of last JobId */
830 memset(&jr, 0, sizeof(JOB_DBR));
832 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
833 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
834 edit_int64(JobId, ed1), db_strerror(ua->db));
835 free_pool_memory(JobIds);
838 if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
839 ua->error_msg(_("Access to JobId=%s (Job \"%s\") not authorized. Not selected.\n"),
840 edit_int64(JobId, ed1), jr.Name);
844 pm_strcat(JobIds, ",");
846 pm_strcat(JobIds, edit_int64(JobId, ed1));
847 rx->TotalFiles += jr.JobFiles;
849 free_pool_memory(rx->JobIds);
850 rx->JobIds = JobIds; /* Set ACL filtered list */
851 if (*rx->JobIds == 0) {
852 ua->warning_msg(_("No Jobs selected.\n"));
856 if (strchr(rx->JobIds,',')) {
857 ua->info_msg(_("You have selected the following JobIds: %s\n"), rx->JobIds);
859 ua->info_msg(_("You have selected the following JobId: %s\n"), rx->JobIds);
867 static bool get_date(UAContext *ua, char *date, int date_len)
869 ua->send_msg(_("The restored files will the most current backup\n"
870 "BEFORE the date you specify below.\n\n"));
872 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
875 if (str_to_utime(ua->cmd) != 0) {
878 ua->error_msg(_("Improper date format.\n"));
880 bstrncpy(date, ua->cmd, date_len);
885 * Insert a single file, or read a list of files from a file
887 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
897 if ((ffd = fopen(p, "rb")) == NULL) {
899 ua->error_msg(_("Cannot open file %s: ERR=%s\n"),
903 while (fgets(file, sizeof(file), ffd)) {
906 if (!insert_dir_into_findex_list(ua, rx, file, date)) {
907 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
910 if (!insert_file_into_findex_list(ua, rx, file, date)) {
911 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
919 insert_table_into_findex_list(ua, rx, p);
923 insert_dir_into_findex_list(ua, rx, ua->cmd, date);
925 insert_file_into_findex_list(ua, rx, ua->cmd, date);
932 * For a given file (path+filename), split into path and file, then
933 * lookup the most recent backup in the catalog to get the JobId
934 * and FileIndex, then insert them into the findex list.
936 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
939 strip_trailing_newline(file);
940 split_path_and_filename(ua, rx, file);
941 if (*rx->JobIds == 0) {
942 Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname,
945 Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
946 rx->path, rx->fname, rx->ClientName);
949 /* Find and insert jobid and File Index */
950 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
951 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
952 rx->query, db_strerror(ua->db));
955 ua->error_msg(_("No database record found for: %s\n"), file);
956 // ua->error_msg("Query=%s\n", rx->query);
963 * For a given path lookup the most recent backup in the catalog
964 * to get the JobId and FileIndexes of all files in that directory.
966 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
969 strip_trailing_junk(dir);
970 if (*rx->JobIds == 0) {
971 ua->error_msg(_("No JobId specified cannot continue.\n"));
974 Mmsg(rx->query, uar_jobid_fileindex_from_dir[db_get_type_index(ua->db)], rx->JobIds, dir, rx->ClientName);
977 /* Find and insert jobid and File Index */
978 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
979 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
980 rx->query, db_strerror(ua->db));
983 ua->error_msg(_("No database record found for: %s\n"), dir);
990 * Get the JobId and FileIndexes of all files in the specified table
992 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
994 strip_trailing_junk(table);
995 Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
998 /* Find and insert jobid and File Index */
999 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
1000 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
1001 rx->query, db_strerror(ua->db));
1004 ua->error_msg(_("No table found: %s\n"), table);
1010 static void split_path_and_filename(UAContext *ua, RESTORE_CTX *rx, char *name)
1014 /* Find path without the filename.
1015 * I.e. everything after the last / is a "filename".
1016 * OK, maybe it is a directory name, but we treat it like
1017 * a filename. If we don't find a / then the whole name
1018 * must be a path name (e.g. c:).
1020 for (p=f=name; *p; p++) {
1021 if (IsPathSeparator(*p)) {
1022 f = p; /* set pos of last slash */
1025 if (IsPathSeparator(*f)) { /* did we find a slash? */
1026 f++; /* yes, point to filename */
1027 } else { /* no, whole thing must be path name */
1031 /* If filename doesn't exist (i.e. root directory), we
1032 * simply create a blank name consisting of a single
1033 * space. This makes handling zero length filenames
1038 rx->fname = check_pool_memory_size(rx->fname, 2*(rx->fnl)+1);
1039 db_escape_string(ua->jcr, ua->db, rx->fname, f, rx->fnl);
1047 rx->path = check_pool_memory_size(rx->path, 2*(rx->pnl)+1);
1048 db_escape_string(ua->jcr, ua->db, rx->path, name, rx->pnl);
1054 Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
1057 static bool ask_for_fileregex(UAContext *ua, RESTORE_CTX *rx)
1059 if (find_arg(ua, NT_("all")) >= 0) { /* if user enters all on command line */
1060 return true; /* select everything */
1062 ua->send_msg(_("\n\nFor one or more of the JobIds selected, no files were found,\n"
1063 "so file selection is not possible.\n"
1064 "Most likely your retention policy pruned the files.\n"));
1065 if (get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
1066 if (ua->pint32_val == 1)
1068 while (get_cmd(ua, _("\nRegexp matching files to restore? (empty to abort): "))) {
1069 if (ua->cmd[0] == '\0') {
1072 regex_t *fileregex_re = NULL;
1074 char errmsg[500] = "";
1076 fileregex_re = (regex_t *)bmalloc(sizeof(regex_t));
1077 rc = regcomp(fileregex_re, ua->cmd, REG_EXTENDED|REG_NOSUB);
1079 regerror(rc, fileregex_re, errmsg, sizeof(errmsg));
1081 regfree(fileregex_re);
1084 ua->send_msg(_("Regex compile error: %s\n"), errmsg);
1086 rx->bsr->fileregex = bstrdup(ua->cmd);
1095 /* Walk on the delta_list of a TREE_NODE item and insert all parts
1096 * TODO: Optimize for bootstrap creation, remove recursion
1097 * 6 -> 5 -> 4 -> 3 -> 2 -> 1 -> 0
1099 * 0, 1, 2, 3, 4, 5, 6
1101 static void add_delta_list_findex(RESTORE_CTX *rx, struct delta_list *lst)
1107 add_delta_list_findex(rx, lst->next);
1109 add_findex(rx->bsr, lst->JobId, lst->FileIndex);
1113 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
1116 JobId_t JobId, last_JobId;
1121 memset(&tree, 0, sizeof(TREE_CTX));
1123 * Build the directory tree containing JobIds user selected
1125 tree.root = new_tree(rx->TotalFiles);
1128 tree.hardlinks_in_mem = rx->hardlinks_in_mem;
1131 * For display purposes, the same JobId, with different volumes may
1132 * appear more than once, however, we only insert it once.
1135 tree.FileEstimate = 0;
1136 if (get_next_jobid_from_list(&p, &JobId) > 0) {
1137 /* Use first JobId as estimate of the number of files to restore */
1138 Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
1139 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1140 ua->error_msg("%s\n", db_strerror(ua->db));
1143 /* Add about 25% more than this job for over estimate */
1144 tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
1145 tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
1149 ua->info_msg(_("\nBuilding directory tree for JobId(s) %s ... "),
1152 #define new_get_file_list
1153 #ifdef new_get_file_list
1154 if (!db_get_file_list(ua->jcr, ua->db,
1155 rx->JobIds, false /* do not use md5 */,
1156 true /* get delta */,
1157 insert_tree_handler, (void *)&tree))
1159 ua->error_msg("%s", db_strerror(ua->db));
1161 if (*rx->BaseJobIds) {
1162 pm_strcat(rx->JobIds, ",");
1163 pm_strcat(rx->JobIds, rx->BaseJobIds);
1166 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1169 if (JobId == last_JobId) {
1170 continue; /* eliminate duplicate JobIds */
1174 * Find files for this JobId and insert them in the tree
1176 Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
1177 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
1178 ua->error_msg("%s", db_strerror(ua->db));
1183 * At this point, the tree is built, so we can garbage collect
1184 * any memory released by the SQL engine that RedHat has
1185 * not returned to the OS :-(
1187 garbage_collect_memory();
1190 * Look at the first JobId on the list (presumably the oldest) and
1191 * if it is marked purged, don't do the manual selection because
1192 * the Job was pruned, so the tree is incomplete.
1194 if (tree.FileCount != 0) {
1195 /* Find out if any Job is purged */
1196 Mmsg(rx->query, "SELECT SUM(PurgedFiles) FROM Job WHERE JobId IN (%s)", rx->JobIds);
1197 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1198 ua->error_msg("%s\n", db_strerror(ua->db));
1200 /* rx->JobId is the PurgedFiles flag */
1201 if (rx->found && rx->JobId > 0) {
1202 tree.FileCount = 0; /* set count to zero, no tree selection */
1205 if (tree.FileCount == 0) {
1206 OK = ask_for_fileregex(ua, rx);
1209 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1210 if (JobId == last_JobId) {
1211 continue; /* eliminate duplicate JobIds */
1213 add_findex_all(rx->bsr, JobId);
1219 ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
1220 edit_uint64_with_commas(tree.FileCount, ec1));
1222 ua->info_msg(_("\n%s files inserted into the tree.\n"),
1223 edit_uint64_with_commas(tree.FileCount, ec1));
1226 if (find_arg(ua, NT_("done")) < 0) {
1227 /* Let the user interact in selecting which files to restore */
1228 OK = user_select_files_from_tree(&tree);
1232 * Walk down through the tree finding all files marked to be
1233 * extracted making a bootstrap file.
1236 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
1237 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
1238 if (node->extract || node->extract_dir) {
1239 Dmsg3(400, "JobId=%lld type=%d FI=%d\n", (uint64_t)node->JobId, node->type, node->FileIndex);
1240 /* TODO: optimize bsr insertion when jobid are non sorted */
1241 add_delta_list_findex(rx, node->delta_list);
1242 add_findex(rx->bsr, node->JobId, node->FileIndex);
1243 if (node->extract && node->type != TN_NEWDIR) {
1244 rx->selected_files++; /* count only saved files */
1251 free_tree(tree.root); /* free the directory tree */
1257 * This routine is used to get the current backup or a backup
1258 * before the specified date.
1260 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
1265 char fileset_name[MAX_NAME_LENGTH];
1266 char ed1[50], ed2[50];
1267 char pool_select[MAX_NAME_LENGTH];
1270 /* Create temp tables */
1271 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1272 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1273 if (!db_sql_query(ua->db, uar_create_temp[db_get_type_index(ua->db)], NULL, NULL)) {
1274 ua->error_msg("%s\n", db_strerror(ua->db));
1276 if (!db_sql_query(ua->db, uar_create_temp1[db_get_type_index(ua->db)], NULL, NULL)) {
1277 ua->error_msg("%s\n", db_strerror(ua->db));
1280 * Select Client from the Catalog
1282 memset(&cr, 0, sizeof(cr));
1283 if (!get_client_dbr(ua, &cr)) {
1286 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1291 memset(&fsr, 0, sizeof(fsr));
1292 i = find_arg_with_value(ua, "FileSet");
1294 if (i >= 0 && is_name_valid(ua->argv[i], &ua->errmsg)) {
1295 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1296 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1297 ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1298 db_strerror(ua->db));
1301 } else if (i >= 0) { /* name is invalid */
1302 ua->error_msg(_("FileSet argument: %s\n"), ua->errmsg);
1305 if (i < 0) { /* fileset not found */
1306 edit_int64(cr.ClientId, ed1);
1307 Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1308 start_prompt(ua, _("The defined FileSet resources are:\n"));
1309 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1310 ua->error_msg("%s\n", db_strerror(ua->db));
1312 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1313 fileset_name, sizeof(fileset_name)) < 0) {
1314 ua->error_msg(_("No FileSet found for client \"%s\".\n"), cr.Name);
1318 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1319 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1320 ua->warning_msg(_("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1321 ua->send_msg(_("This probably means you modified the FileSet.\n"
1322 "Continuing anyway.\n"));
1326 /* If Pool specified, add PoolId specification */
1330 memset(&pr, 0, sizeof(pr));
1331 bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1332 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1333 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
1334 edit_int64(pr.PoolId, ed1));
1336 ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1340 /* Find JobId of last Full backup for this client, fileset */
1341 edit_int64(cr.ClientId, ed1);
1342 Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1344 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1345 ua->error_msg("%s\n", db_strerror(ua->db));
1349 /* Find all Volumes used by that JobId */
1350 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1351 ua->error_msg("%s\n", db_strerror(ua->db));
1355 /* Note, this is needed because I don't seem to get the callback
1356 * from the call just above.
1359 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1360 ua->warning_msg("%s\n", db_strerror(ua->db));
1362 if (rx->JobTDate == 0) {
1363 ua->error_msg(_("No Full backup before %s found.\n"), date);
1367 /* Now find most recent Differental Job after Full save, if any */
1368 Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1369 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1370 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1371 ua->warning_msg("%s\n", db_strerror(ua->db));
1373 /* Now update JobTDate to look into Differental, if any */
1375 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1376 ua->warning_msg("%s\n", db_strerror(ua->db));
1378 if (rx->JobTDate == 0) {
1379 ua->error_msg(_("No Full backup before %s found.\n"), date);
1383 /* Now find all Incremental Jobs after Full/dif save */
1384 Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1385 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1386 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1387 ua->warning_msg("%s\n", db_strerror(ua->db));
1390 /* Get the JobIds from that list */
1391 rx->last_jobid[0] = rx->JobIds[0] = 0;
1393 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1394 ua->warning_msg("%s\n", db_strerror(ua->db));
1397 if (rx->JobIds[0] != 0) {
1398 if (find_arg(ua, NT_("copies")) > 0) {
1399 /* Display a list of all copies */
1400 db_list_copies_records(ua->jcr, ua->db, 0, rx->JobIds,
1401 prtit, ua, HORZ_LIST);
1403 /* Display a list of Jobs selected for this restore */
1404 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1,HORZ_LIST);
1408 ua->warning_msg(_("No jobs found.\n"));
1412 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1413 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1417 static int restore_count_handler(void *ctx, int num_fields, char **row)
1419 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1420 rx->JobId = str_to_int64(row[0]);
1426 * Callback handler to get JobId and FileIndex for files
1427 * can insert more than one depending on the caller.
1429 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1431 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1432 JobId_t JobId = str_to_int64(row[0]);
1434 Dmsg3(200, "JobId=%s JobIds=%s FileIndex=%s\n", row[0], rx->JobIds, row[1]);
1436 /* New JobId, add it to JobIds
1437 * The list is sorted by JobId, so we need a cache for the previous value
1439 * It will permit to find restore objects to send during the restore
1441 if (rx->JobId != JobId) {
1443 pm_strcat(rx->JobIds, ",");
1445 pm_strcat(rx->JobIds, row[0]);
1449 add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1451 rx->selected_files++;
1456 * Callback handler make list of JobIds
1458 static int jobid_handler(void *ctx, int num_fields, char **row)
1460 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1462 if (strcmp(rx->last_jobid, row[0]) == 0) {
1463 return 0; /* duplicate id */
1465 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1466 if (rx->JobIds[0] != 0) {
1467 pm_strcat(rx->JobIds, ",");
1469 pm_strcat(rx->JobIds, row[0]);
1475 * Callback handler to pickup last Full backup JobTDate
1477 static int last_full_handler(void *ctx, int num_fields, char **row)
1479 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1481 rx->JobTDate = str_to_int64(row[1]);
1486 * Callback handler build FileSet name prompt list
1488 static int fileset_handler(void *ctx, int num_fields, char **row)
1490 /* row[0] = FileSet (name) */
1492 add_prompt((UAContext *)ctx, row[0]);
1498 * Free names in the list
1500 static void free_name_list(NAME_LIST *name_list)
1502 for (int i=0; i < name_list->num_ids; i++) {
1503 free(name_list->name[i]);
1505 bfree_and_null(name_list->name);
1506 name_list->max_ids = 0;
1507 name_list->num_ids = 0;
1510 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType)
1515 Dmsg1(200, "Already have store=%s\n", rx.store->name());
1519 * Try looking up Storage by name
1522 foreach_res(store, R_STORAGE) {
1523 if (strcmp(Storage, store->name()) == 0) {
1524 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1533 /* Check if an explicit storage resource is given */
1535 int i = find_arg_with_value(ua, "storage");
1537 store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1538 if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1542 if (store && (store != rx.store)) {
1543 ua->info_msg(_("\nWarning Storage is overridden by \"%s\" on the command line.\n"),
1546 bstrncpy(rx.RestoreMediaType, MediaType, sizeof(rx.RestoreMediaType));
1547 if (strcmp(MediaType, store->media_type) != 0) {
1548 ua->info_msg(_("This may not work because of two different MediaTypes:\n"
1549 " Storage MediaType=\"%s\"\n"
1550 " Volume MediaType=\"%s\".\n\n"),
1551 store->media_type, MediaType);
1553 Dmsg2(200, "Set store=%s MediaType=%s\n", rx.store->name(), rx.RestoreMediaType);
1558 /* If no storage resource, try to find one from MediaType */
1561 foreach_res(store, R_STORAGE) {
1562 if (strcmp(MediaType, store->media_type) == 0) {
1563 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1565 Dmsg1(200, "Set store=%s\n", rx.store->name());
1566 if (Storage == NULL) {
1567 ua->warning_msg(_("Using Storage \"%s\" from MediaType \"%s\".\n"),
1568 store->name(), MediaType);
1570 ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1571 Storage, store->name(), MediaType);
1579 ua->warning_msg(_("\nUnable to find Storage resource for\n"
1580 "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1583 /* Take command line arg, or ask user if none */
1584 rx.store = get_storage_resource(ua, false /* don't use default */);
1586 Dmsg1(200, "Set store=%s\n", rx.store->name());