2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2015 Kern Sibbald
5 Copyright (C) 2002-2014 Free Software Foundation Europe e.V.
7 The original author of Bacula is Kern Sibbald, with contributions
8 from many others, a complete list can be found in the file AUTHORS.
10 You may use this file and others of this release according to the
11 license defined in the LICENSE file, which includes the Affero General
12 Public License, v3.0 ("AGPLv3") and some additional permissions and
13 terms pursuant to its AGPLv3 Section 7.
15 This notice must be preserved when any source code is
16 conveyed and/or propagated.
18 Bacula(R) is a registered trademark of Kern Sibbald.
22 * Bacula Director -- User Agent Database restore Command
23 * Creates a bootstrap file for restoring files and
24 * starts the restore job.
26 * Tree handling routines split into ua_tree.c July MMIII.
27 * BSR (bootstrap record) handling routines split into
30 * Kern Sibbald, July MMII
37 /* Imported functions */
38 extern void print_bsr(UAContext *ua, RBSR *bsr);
41 /* Forward referenced functions */
42 static int last_full_handler(void *ctx, int num_fields, char **row);
43 static int jobid_handler(void *ctx, int num_fields, char **row);
44 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx);
45 static int fileset_handler(void *ctx, int num_fields, char **row);
46 static void free_name_list(NAME_LIST *name_list);
47 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date);
48 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx);
49 static void split_path_and_filename(UAContext *ua, RESTORE_CTX *rx, char *fname);
50 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row);
51 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
53 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
55 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir);
56 static int get_client_name(UAContext *ua, RESTORE_CTX *rx);
57 static int get_restore_client_name(UAContext *ua, RESTORE_CTX &rx);
58 static bool get_date(UAContext *ua, char *date, int date_len);
59 static int restore_count_handler(void *ctx, int num_fields, char **row);
60 static void get_and_display_basejobs(UAContext *ua, RESTORE_CTX *rx);
62 void new_rx(RESTORE_CTX *rx)
64 memset(rx, 0, sizeof(*rx));
65 rx->path = get_pool_memory(PM_FNAME);
68 rx->fname = get_pool_memory(PM_FNAME);
71 rx->JobIds = get_pool_memory(PM_FNAME);
74 rx->component_fname = get_pool_memory(PM_FNAME);
75 rx->component_fname[0] = 0;
77 rx->BaseJobIds = get_pool_memory(PM_FNAME);
78 rx->BaseJobIds[0] = 0;
80 rx->query = get_pool_memory(PM_FNAME);
84 rx->hardlinks_in_mem = true;
91 int restore_cmd(UAContext *ua, const char *cmd)
93 RESTORE_CTX rx; /* restore context */
98 char *escaped_bsr_name = NULL;
99 char *escaped_where_name = NULL;
100 char *strip_prefix, *add_prefix, *add_suffix, *regexp;
101 strip_prefix = add_prefix = add_suffix = regexp = NULL;
103 new_rx(&rx); /* Initialize RESTORE_CTX */
105 if (!open_new_client_db(ua)) {
109 for (i = 0; i < ua->argc ; i++) {
111 continue; /* skip if no value given */
113 if (strcasecmp(ua->argk[i], "comment") == 0) {
114 rx.comment = ua->argv[i];
115 if (!is_comment_legal(ua, rx.comment)) {
119 } else if (strcasecmp(ua->argk[i], "where") == 0) {
120 rx.where = ua->argv[i];
122 } else if (strcasecmp(ua->argk[i], "replace") == 0) {
123 rx.replace = ua->argv[i];
125 } else if (strcasecmp(ua->argk[i], "strip_prefix") == 0) {
126 strip_prefix = ua->argv[i];
128 } else if (strcasecmp(ua->argk[i], "add_prefix") == 0) {
129 add_prefix = ua->argv[i];
131 } else if (strcasecmp(ua->argk[i], "add_suffix") == 0) {
132 add_suffix = ua->argv[i];
134 } else if (strcasecmp(ua->argk[i], "regexwhere") == 0) {
135 rx.RegexWhere = ua->argv[i];
137 } else if (strcasecmp(ua->argk[i], "optimizespeed") == 0) {
138 if (strcasecmp(ua->argv[i], "0") || strcasecmp(ua->argv[i], "no") ||
139 strcasecmp(ua->argv[i], "false")) {
140 rx.hardlinks_in_mem = false;
145 if (strip_prefix || add_suffix || add_prefix) {
146 int len = bregexp_get_build_where_size(strip_prefix, add_prefix, add_suffix);
147 regexp = (char *)bmalloc(len * sizeof(char));
149 bregexp_build_where(regexp, len, strip_prefix, add_prefix, add_suffix);
150 rx.RegexWhere = regexp;
153 /* TODO: add acl for regexwhere ? */
156 if (!acl_access_ok(ua, Where_ACL, rx.RegexWhere)) {
157 ua->error_msg(_("\"RegexWhere\" specification not authorized.\n"));
163 if (!acl_access_ok(ua, Where_ACL, rx.where)) {
164 ua->error_msg(_("\"where\" specification not authorized.\n"));
169 /* Ensure there is at least one Restore Job */
171 foreach_res(job, R_JOB) {
172 if (job->JobType == JT_RESTORE) {
173 if (!rx.restore_job) {
174 rx.restore_job = job;
180 if (!rx.restore_jobs) {
182 "No Restore Job Resource found in bacula-dir.conf.\n"
183 "You must create at least one before running this command.\n"));
188 * Request user to select JobIds or files by various different methods
189 * last 20 jobs, where File saved, most recent backup, ...
190 * In the end, a list of files are pumped into
193 switch (user_select_jobids_or_files(ua, &rx)) {
196 case 1: /* selected by jobid */
197 get_and_display_basejobs(ua, &rx);
198 if (!build_directory_tree(ua, &rx)) {
199 ua->send_msg(_("Restore not done.\n"));
203 case 2: /* selected by filename, no tree needed */
209 if (!complete_bsr(ua, rx.bsr)) { /* find Vol, SessId, SessTime from JobIds */
210 ua->error_msg(_("Unable to construct a valid BSR. Cannot continue.\n"));
213 if (!(rx.selected_files = write_bsr_file(ua, rx))) {
214 ua->warning_msg(_("No files selected to be restored.\n"));
218 ua->send_msg(_("Bootstrap records written to %s\n"), ua->jcr->RestoreBootstrap);
219 display_bsr_info(ua, rx); /* display vols needed, etc */
221 if (rx.selected_files==1) {
222 ua->info_msg(_("\n1 file selected to be restored.\n\n"));
224 ua->info_msg(_("\n%s files selected to be restored.\n\n"),
225 edit_uint64_with_commas(rx.selected_files, ed1));
228 ua->warning_msg(_("No files selected to be restored.\n"));
232 if (rx.restore_jobs == 1) {
233 job = rx.restore_job;
235 job = get_restore_job(ua);
241 get_client_name(ua, &rx);
242 if (!rx.ClientName) {
243 ua->error_msg(_("No Client resource found!\n"));
246 get_restore_client_name(ua, rx);
248 escaped_bsr_name = escape_filename(jcr->RestoreBootstrap);
251 "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
252 " bootstrap=\"%s\" files=%u catalog=\"%s\"",
253 job->name(), rx.ClientName, rx.RestoreClientName,
254 rx.store?rx.store->name():"",
255 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
256 rx.selected_files, ua->catalog->name());
258 /* Build run command */
260 if (rx.RestoreMediaType[0]) {
261 Mmsg(buf, " mediatype=\"%s\"", rx.RestoreMediaType);
262 pm_strcat(ua->cmd, buf);
266 escaped_where_name = escape_filename(rx.RegexWhere);
267 Mmsg(buf, " regexwhere=\"%s\"",
268 escaped_where_name ? escaped_where_name : rx.RegexWhere);
270 } else if (rx.where) {
271 escaped_where_name = escape_filename(rx.where);
272 Mmsg(buf," where=\"%s\"",
273 escaped_where_name ? escaped_where_name : rx.where);
275 pm_strcat(ua->cmd, buf);
278 Mmsg(buf, " replace=%s", rx.replace);
279 pm_strcat(ua->cmd, buf);
283 Mmsg(buf, " comment=\"%s\"", rx.comment);
284 pm_strcat(ua->cmd, buf);
287 if (escaped_bsr_name != NULL) {
288 bfree(escaped_bsr_name);
291 if (escaped_where_name != NULL) {
292 bfree(escaped_where_name);
299 if (find_arg(ua, NT_("yes")) > 0) {
300 pm_strcat(ua->cmd, " yes"); /* pass it on to the run command */
302 Dmsg1(200, "Submitting: %s\n", ua->cmd);
304 * Transfer jobids, component stuff to jcr to
305 * pass to run_cmd(). Note, these are fields and
306 * other things that are not passed on the command
309 /* ***FIXME*** pass jobids on command line */
310 jcr->JobIds = rx.JobIds;
312 jcr->component_fname = rx.component_fname;
313 rx.component_fname = NULL;
314 jcr->component_fd = rx.component_fd;
315 rx.component_fd = NULL;
317 run_cmd(ua, ua->cmd);
319 garbage_collect_memory(); /* release unused memory */
323 if (escaped_bsr_name != NULL) {
324 bfree(escaped_bsr_name);
327 if (escaped_where_name != NULL) {
328 bfree(escaped_where_name);
336 garbage_collect_memory(); /* release unused memory */
342 * Fill the rx->BaseJobIds and display the list
344 static void get_and_display_basejobs(UAContext *ua, RESTORE_CTX *rx)
348 if (!db_get_used_base_jobids(ua->jcr, ua->db, rx->JobIds, &jobids)) {
349 ua->warning_msg("%s", db_strerror(ua->db));
354 Mmsg(q, uar_print_jobs, jobids.list);
355 ua->send_msg(_("The restore will use the following job(s) as Base\n"));
356 db_list_sql_query(ua->jcr, ua->db, q.c_str(), prtit, ua, 1, HORZ_LIST);
358 pm_strcpy(rx->BaseJobIds, jobids.list);
361 void free_rx(RESTORE_CTX *rx)
365 free_and_null_pool_memory(rx->JobIds);
366 free_and_null_pool_memory(rx->BaseJobIds);
367 free_and_null_pool_memory(rx->fname);
368 free_and_null_pool_memory(rx->path);
369 free_and_null_pool_memory(rx->query);
370 if (rx->component_fd) {
371 fclose(rx->component_fd);
372 rx->component_fd = NULL;
374 if (rx->component_fname) {
375 unlink(rx->component_fname);
377 free_and_null_pool_memory(rx->component_fname);
378 free_name_list(&rx->name_list);
381 static bool has_value(UAContext *ua, int i)
384 ua->error_msg(_("Missing value for keyword: %s\n"), ua->argk[i]);
391 * This gets the client name from which the backup was made
393 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
395 /* If no client name specified yet, get it now */
396 if (!rx->ClientName[0]) {
398 /* try command line argument */
399 int i = find_arg_with_value(ua, NT_("client"));
401 i = find_arg_with_value(ua, NT_("backupclient"));
404 if (!is_name_valid(ua->argv[i], &ua->errmsg)) {
405 ua->error_msg("%s argument: %s", ua->argk[i], ua->errmsg);
408 bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
411 memset(&cr, 0, sizeof(cr));
412 if (!get_client_dbr(ua, &cr)) {
415 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
421 * This is where we pick up a client name to restore to.
423 static int get_restore_client_name(UAContext *ua, RESTORE_CTX &rx)
425 /* Start with same name as backup client */
426 bstrncpy(rx.RestoreClientName, rx.ClientName, sizeof(rx.RestoreClientName));
428 /* try command line argument */
429 int i = find_arg_with_value(ua, NT_("restoreclient"));
431 if (!is_name_valid(ua->argv[i], &ua->errmsg)) {
432 ua->error_msg("%s argument: %s", ua->argk[i], ua->errmsg);
435 bstrncpy(rx.RestoreClientName, ua->argv[i], sizeof(rx.RestoreClientName));
444 * The first step in the restore process is for the user to
445 * select a list of JobIds from which he will subsequently
446 * select which files are to be restored.
448 * Returns: 2 if filename list made
449 * 1 if jobid list made
452 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
455 char date[MAX_TIME_LENGTH];
456 bool have_date = false;
457 /* Include current second if using current time */
458 utime_t now = time(NULL) + 1;
460 JOB_DBR jr = { (JobId_t)-1 };
463 const char *list[] = {
464 _("List last 20 Jobs run"),
465 _("List Jobs where a given File is saved"),
466 _("Enter list of comma separated JobIds to select"),
467 _("Enter SQL list command"),
468 _("Select the most recent backup for a client"),
469 _("Select backup for a client before a specified time"),
470 _("Enter a list of files to restore"),
471 _("Enter a list of files to restore before a specified time"),
472 _("Find the JobIds of the most recent backup for a client"),
473 _("Find the JobIds for a backup for a client before a specified time"),
474 _("Enter a list of directories to restore for found JobIds"),
475 _("Select full restore to a specified Job date"),
480 /* These keywords are handled in a for loop */
490 /* The keyword below are handled by individual arg lookups */
496 "bootstrap", /* 13 */
498 "strip_prefix", /* 15 */
499 "add_prefix", /* 16 */
500 "add_suffix", /* 17 */
501 "regexwhere", /* 18 */
502 "restoreclient", /* 19 */
505 "restorejob", /* 22 */
512 for (i=1; i<ua->argc; i++) { /* loop through arguments */
513 bool found_kw = false;
514 for (j=0; kw[j]; j++) { /* loop through keywords */
515 if (strcasecmp(kw[j], ua->argk[i]) == 0) {
522 ua->error_msg(_("Unknown keyword: %s\n"), ua->argk[i]);
525 /* Found keyword in kw[] list, process it */
528 if (!has_value(ua, i)) {
531 if (*rx->JobIds != 0) {
532 pm_strcat(rx->JobIds, ",");
534 pm_strcat(rx->JobIds, ua->argv[i]);
537 case 1: /* current */
539 * Note, we add one second here just to include any job
540 * that may have finished within the current second,
541 * which happens a lot in scripting small jobs.
543 bstrutime(date, sizeof(date), now);
547 if (have_date || !has_value(ua, i)) {
550 if (str_to_utime(ua->argv[i]) == 0) {
551 ua->error_msg(_("Improper date format: %s\n"), ua->argv[i]);
554 bstrncpy(date, ua->argv[i], sizeof(date));
559 if (!has_value(ua, i)) {
563 bstrutime(date, sizeof(date), now);
565 if (!get_client_name(ua, rx)) {
568 pm_strcpy(ua->cmd, ua->argv[i]);
569 insert_one_file_or_dir(ua, rx, date, j==4);
573 bstrutime(date, sizeof(date), now);
575 if (!select_backups_before_date(ua, rx, date)) {
580 case 6: /* pool specified */
581 if (!has_value(ua, i)) {
584 rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
586 ua->error_msg(_("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
589 if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
591 ua->error_msg(_("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
595 case 7: /* all specified */
599 * All keywords 7 or greater are ignored or handled by a select prompt
607 ua->send_msg(_("\nFirst you select one or more JobIds that contain files\n"
608 "to be restored. You will be presented several methods\n"
609 "of specifying the JobIds. Then you will be allowed to\n"
610 "select which files from those JobIds are to be restored.\n\n"));
613 /* If choice not already made above, prompt */
620 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
621 for (int i=0; list[i]; i++) {
622 add_prompt(ua, list[i]);
625 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
626 case -1: /* error or cancel */
628 case 0: /* list last 20 Jobs run */
629 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
630 ua->error_msg(_("SQL query not authorized.\n"));
633 gui_save = ua->jcr->gui;
635 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
636 ua->jcr->gui = gui_save;
639 case 1: /* list where a file is saved */
640 if (!get_client_name(ua, rx)) {
643 if (!get_cmd(ua, _("Enter Filename (no path):"))) {
646 len = strlen(ua->cmd);
647 fname = (char *)malloc(len * 2 + 1);
648 db_escape_string(ua->jcr, ua->db, fname, ua->cmd, len);
649 Mmsg(rx->query, uar_file[db_get_type_index(ua->db)], rx->ClientName, fname);
651 gui_save = ua->jcr->gui;
653 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
654 ua->jcr->gui = gui_save;
657 case 2: /* enter a list of JobIds */
658 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
661 pm_strcpy(rx->JobIds, ua->cmd);
663 case 3: /* Enter an SQL list command */
664 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
665 ua->error_msg(_("SQL query not authorized.\n"));
668 if (!get_cmd(ua, _("Enter SQL list command: "))) {
671 gui_save = ua->jcr->gui;
673 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
674 ua->jcr->gui = gui_save;
677 case 4: /* Select the most recent backups */
679 bstrutime(date, sizeof(date), now);
681 if (!select_backups_before_date(ua, rx, date)) {
685 case 5: /* select backup at specified time */
687 if (!get_date(ua, date, sizeof(date))) {
691 if (!select_backups_before_date(ua, rx, date)) {
695 case 6: /* Enter files */
697 bstrutime(date, sizeof(date), now);
699 if (!get_client_name(ua, rx)) {
702 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
703 "containing a list of file names with paths, and terminate\n"
704 "them with a blank line.\n"));
706 if (!get_cmd(ua, _("Enter full filename: "))) {
709 len = strlen(ua->cmd);
713 insert_one_file_or_dir(ua, rx, date, false);
716 case 7: /* enter files backed up before specified time */
718 if (!get_date(ua, date, sizeof(date))) {
722 if (!get_client_name(ua, rx)) {
725 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
726 "containing a list of file names with paths, and terminate\n"
727 "them with a blank line.\n"));
729 if (!get_cmd(ua, _("Enter full filename: "))) {
732 len = strlen(ua->cmd);
736 insert_one_file_or_dir(ua, rx, date, false);
740 case 8: /* Find JobIds for current backup */
742 bstrutime(date, sizeof(date), now);
744 if (!select_backups_before_date(ua, rx, date)) {
750 case 9: /* Find JobIds for give date */
752 if (!get_date(ua, date, sizeof(date))) {
756 if (!select_backups_before_date(ua, rx, date)) {
762 case 10: /* Enter directories */
763 if (*rx->JobIds != 0) {
764 ua->send_msg(_("You have already selected the following JobIds: %s\n"),
766 } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
767 if (*rx->JobIds != 0 && *ua->cmd) {
768 pm_strcat(rx->JobIds, ",");
770 pm_strcat(rx->JobIds, ua->cmd);
772 if (*rx->JobIds == 0 || *rx->JobIds == '.') {
774 return 0; /* nothing entered, return */
777 bstrutime(date, sizeof(date), now);
779 if (!get_client_name(ua, rx)) {
782 ua->send_msg(_("Enter full directory names or start the name\n"
783 "with a < to indicate it is a filename containing a list\n"
784 "of directories and terminate them with a blank line.\n"));
786 if (!get_cmd(ua, _("Enter directory name: "))) {
789 len = strlen(ua->cmd);
793 /* Add trailing slash to end of directory names */
794 if (ua->cmd[0] != '<' && !IsPathSeparator(ua->cmd[len-1])) {
795 strcat(ua->cmd, "/");
797 insert_one_file_or_dir(ua, rx, date, true);
801 case 11: /* Choose a jobid and select jobs */
802 if (!get_cmd(ua, _("Enter JobId to get the state to restore: ")) ||
803 !is_an_integer(ua->cmd))
808 memset(&jr, 0, sizeof(JOB_DBR));
809 jr.JobId = str_to_int64(ua->cmd);
810 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
811 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
812 ua->cmd, db_strerror(ua->db));
815 ua->send_msg(_("Selecting jobs to build the Full state at %s\n"),
817 jr.JobLevel = L_INCREMENTAL; /* Take Full+Diff+Incr */
818 if (!db_get_accurate_jobids(ua->jcr, ua->db, &jr, &jobids)) {
821 pm_strcpy(rx->JobIds, jobids.list);
822 Dmsg1(30, "Item 12: jobids = %s\n", rx->JobIds);
824 case 12: /* Cancel or quit */
829 memset(&jr, 0, sizeof(JOB_DBR));
830 POOLMEM *JobIds = get_pool_memory(PM_FNAME);
834 * Find total number of files to be restored, and filter the JobId
835 * list to contain only ones permitted by the ACL conditions.
837 for (p=rx->JobIds; ; ) {
839 int stat = get_next_jobid_from_list(&p, &JobId);
841 ua->error_msg(_("Invalid JobId in list.\n"));
842 free_pool_memory(JobIds);
848 if (jr.JobId == JobId) {
849 continue; /* duplicate of last JobId */
851 memset(&jr, 0, sizeof(JOB_DBR));
853 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
854 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
855 edit_int64(JobId, ed1), db_strerror(ua->db));
856 free_pool_memory(JobIds);
859 if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
860 ua->error_msg(_("Access to JobId=%s (Job \"%s\") not authorized. Not selected.\n"),
861 edit_int64(JobId, ed1), jr.Name);
865 pm_strcat(JobIds, ",");
867 pm_strcat(JobIds, edit_int64(JobId, ed1));
868 rx->TotalFiles += jr.JobFiles;
870 free_pool_memory(rx->JobIds);
871 rx->JobIds = JobIds; /* Set ACL filtered list */
872 if (*rx->JobIds == 0) {
873 ua->warning_msg(_("No Jobs selected.\n"));
877 if (strchr(rx->JobIds,',')) {
878 ua->info_msg(_("You have selected the following JobIds: %s\n"), rx->JobIds);
880 ua->info_msg(_("You have selected the following JobId: %s\n"), rx->JobIds);
888 static bool get_date(UAContext *ua, char *date, int date_len)
890 ua->send_msg(_("The restored files will the most current backup\n"
891 "BEFORE the date you specify below.\n\n"));
893 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
896 if (str_to_utime(ua->cmd) != 0) {
899 ua->error_msg(_("Improper date format.\n"));
901 bstrncpy(date, ua->cmd, date_len);
906 * Insert a single file, or read a list of files from a file
908 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
918 if ((ffd = fopen(p, "rb")) == NULL) {
920 ua->error_msg(_("Cannot open file %s: ERR=%s\n"),
924 while (fgets(file, sizeof(file), ffd)) {
927 if (!insert_dir_into_findex_list(ua, rx, file, date)) {
928 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
931 if (!insert_file_into_findex_list(ua, rx, file, date)) {
932 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
940 insert_table_into_findex_list(ua, rx, p);
944 insert_dir_into_findex_list(ua, rx, ua->cmd, date);
946 insert_file_into_findex_list(ua, rx, ua->cmd, date);
953 * For a given file (path+filename), split into path and file, then
954 * lookup the most recent backup in the catalog to get the JobId
955 * and FileIndex, then insert them into the findex list.
957 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
960 strip_trailing_newline(file);
961 split_path_and_filename(ua, rx, file);
962 if (*rx->JobIds == 0) {
963 Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname,
966 Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
967 rx->path, rx->fname, rx->ClientName);
970 /* Find and insert jobid and File Index */
971 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
972 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
973 rx->query, db_strerror(ua->db));
976 ua->error_msg(_("No database record found for: %s\n"), file);
977 // ua->error_msg("Query=%s\n", rx->query);
984 * For a given path lookup the most recent backup in the catalog
985 * to get the JobId and FileIndexes of all files in that directory.
987 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
990 strip_trailing_junk(dir);
991 if (*rx->JobIds == 0) {
992 ua->error_msg(_("No JobId specified cannot continue.\n"));
995 Mmsg(rx->query, uar_jobid_fileindex_from_dir[db_get_type_index(ua->db)], rx->JobIds, dir, rx->ClientName);
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 database record found for: %s\n"), dir);
1011 * Get the JobId and FileIndexes of all files in the specified table
1013 bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
1015 strip_trailing_junk(table);
1016 Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
1019 /* Find and insert jobid and File Index */
1020 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
1021 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
1022 rx->query, db_strerror(ua->db));
1025 ua->error_msg(_("No table found: %s\n"), table);
1031 static void split_path_and_filename(UAContext *ua, RESTORE_CTX *rx, char *name)
1035 /* Find path without the filename.
1036 * I.e. everything after the last / is a "filename".
1037 * OK, maybe it is a directory name, but we treat it like
1038 * a filename. If we don't find a / then the whole name
1039 * must be a path name (e.g. c:).
1041 for (p=f=name; *p; p++) {
1042 if (IsPathSeparator(*p)) {
1043 f = p; /* set pos of last slash */
1046 if (IsPathSeparator(*f)) { /* did we find a slash? */
1047 f++; /* yes, point to filename */
1048 } else { /* no, whole thing must be path name */
1052 /* If filename doesn't exist (i.e. root directory), we
1053 * simply create a blank name consisting of a single
1054 * space. This makes handling zero length filenames
1059 rx->fname = check_pool_memory_size(rx->fname, 2*(rx->fnl)+1);
1060 db_escape_string(ua->jcr, ua->db, rx->fname, f, rx->fnl);
1068 rx->path = check_pool_memory_size(rx->path, 2*(rx->pnl)+1);
1069 db_escape_string(ua->jcr, ua->db, rx->path, name, rx->pnl);
1075 Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
1078 static bool ask_for_fileregex(UAContext *ua, RESTORE_CTX *rx)
1080 if (find_arg(ua, NT_("all")) >= 0) { /* if user enters all on command line */
1081 return true; /* select everything */
1083 ua->send_msg(_("\n\nFor one or more of the JobIds selected, no files were found,\n"
1084 "so file selection is not possible.\n"
1085 "Most likely your retention policy pruned the files.\n"));
1086 if (get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
1087 if (ua->pint32_val == 1)
1089 while (get_cmd(ua, _("\nRegexp matching files to restore? (empty to abort): "))) {
1090 if (ua->cmd[0] == '\0') {
1093 regex_t *fileregex_re = NULL;
1095 char errmsg[500] = "";
1097 fileregex_re = (regex_t *)bmalloc(sizeof(regex_t));
1098 rc = regcomp(fileregex_re, ua->cmd, REG_EXTENDED|REG_NOSUB);
1100 regerror(rc, fileregex_re, errmsg, sizeof(errmsg));
1102 regfree(fileregex_re);
1105 ua->send_msg(_("Regex compile error: %s\n"), errmsg);
1107 rx->bsr->fileregex = bstrdup(ua->cmd);
1116 /* Walk on the delta_list of a TREE_NODE item and insert all parts
1117 * TODO: Optimize for bootstrap creation, remove recursion
1118 * 6 -> 5 -> 4 -> 3 -> 2 -> 1 -> 0
1120 * 0, 1, 2, 3, 4, 5, 6
1122 static void add_delta_list_findex(RESTORE_CTX *rx, struct delta_list *lst)
1128 add_delta_list_findex(rx, lst->next);
1130 add_findex(rx->bsr, lst->JobId, lst->FileIndex);
1134 * This is a list of all the files (components) that the
1135 * user has requested for restore. It is requested by
1136 * the plugin (for now hard coded only for VSS).
1137 * In the future, this will be requested by a RestoreObject
1138 * and the plugin name will be sent to the FD.
1140 static bool write_component_file(UAContext *ua, RESTORE_CTX *rx, char *fname)
1143 if (!rx->component_fd) {
1144 Mmsg(rx->component_fname, "%s/%s.restore.sel.XXXXXX", working_directory, my_name);
1145 fd = mkstemp(rx->component_fname);
1148 ua->error_msg(_("Unable to create component file %s. ERR=%s\n"),
1149 rx->component_fname, be.bstrerror());
1152 rx->component_fd = fdopen(fd, "w+");
1153 if (!rx->component_fd) {
1155 ua->error_msg(_("Unable to fdopen component file %s. ERR=%s\n"),
1156 rx->component_fname, be.bstrerror());
1160 fprintf(rx->component_fd, "%s\n", fname);
1161 if (ferror(rx->component_fd)) {
1162 ua->error_msg(_("Error writing component file.\n"));
1163 fclose(rx->component_fd);
1164 unlink(rx->component_fname);
1165 rx->component_fd = NULL;
1171 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
1174 JobId_t JobId, last_JobId;
1179 memset(&tree, 0, sizeof(TREE_CTX));
1181 * Build the directory tree containing JobIds user selected
1183 tree.root = new_tree(rx->TotalFiles);
1186 tree.hardlinks_in_mem = rx->hardlinks_in_mem;
1189 * For display purposes, the same JobId, with different volumes may
1190 * appear more than once, however, we only insert it once.
1193 tree.FileEstimate = 0;
1194 if (get_next_jobid_from_list(&p, &JobId) > 0) {
1195 /* Use first JobId as estimate of the number of files to restore */
1196 Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
1197 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1198 ua->error_msg("%s\n", db_strerror(ua->db));
1201 /* Add about 25% more than this job for over estimate */
1202 tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
1203 tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
1207 ua->info_msg(_("\nBuilding directory tree for JobId(s) %s ... "),
1210 #define new_get_file_list
1211 #ifdef new_get_file_list
1212 if (!db_get_file_list(ua->jcr, ua->db,
1213 rx->JobIds, false /* do not use md5 */,
1214 true /* get delta */,
1215 insert_tree_handler, (void *)&tree))
1217 ua->error_msg("%s", db_strerror(ua->db));
1219 if (*rx->BaseJobIds) {
1220 pm_strcat(rx->JobIds, ",");
1221 pm_strcat(rx->JobIds, rx->BaseJobIds);
1224 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1227 if (JobId == last_JobId) {
1228 continue; /* eliminate duplicate JobIds */
1232 * Find files for this JobId and insert them in the tree
1234 Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
1235 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
1236 ua->error_msg("%s", db_strerror(ua->db));
1241 * At this point, the tree is built, so we can garbage collect
1242 * any memory released by the SQL engine that RedHat has
1243 * not returned to the OS :-(
1245 garbage_collect_memory();
1248 * Look at the first JobId on the list (presumably the oldest) and
1249 * if it is marked purged, don't do the manual selection because
1250 * the Job was pruned, so the tree is incomplete.
1252 if (tree.FileCount != 0) {
1253 /* Find out if any Job is purged */
1254 Mmsg(rx->query, "SELECT SUM(PurgedFiles) FROM Job WHERE JobId IN (%s)", rx->JobIds);
1255 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1256 ua->error_msg("%s\n", db_strerror(ua->db));
1258 /* rx->JobId is the PurgedFiles flag */
1259 if (rx->found && rx->JobId > 0) {
1260 tree.FileCount = 0; /* set count to zero, no tree selection */
1263 if (tree.FileCount == 0) {
1264 OK = ask_for_fileregex(ua, rx);
1267 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1268 if (JobId == last_JobId) {
1269 continue; /* eliminate duplicate JobIds */
1271 add_findex_all(rx->bsr, JobId);
1277 ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
1278 edit_uint64_with_commas(tree.FileCount, ec1));
1280 ua->info_msg(_("\n%s files inserted into the tree.\n"),
1281 edit_uint64_with_commas(tree.FileCount, ec1));
1284 if (find_arg(ua, NT_("done")) < 0) {
1285 /* Let the user interact in selecting which files to restore */
1286 OK = user_select_files_from_tree(&tree);
1290 * Walk down through the tree finding all files marked to be
1291 * extracted making a bootstrap file.
1295 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
1296 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
1297 if (node->extract || node->extract_dir) {
1298 Dmsg3(400, "JobId=%lld type=%d FI=%d\n", (uint64_t)node->JobId, node->type, node->FileIndex);
1299 /* TODO: optimize bsr insertion when jobid are non sorted */
1300 add_delta_list_findex(rx, node->delta_list);
1301 add_findex(rx->bsr, node->JobId, node->FileIndex);
1303 * Special VSS plugin code to return selected
1304 * components. For the moment, it is hard coded
1305 * for the VSS plugin.
1307 if (fnmatch(":component_info_*", node->fname, 0) == 0) {
1308 tree_getpath(node, cwd, sizeof(cwd));
1309 if (!write_component_file(ua, rx, cwd)) {
1314 if (node->extract && node->type != TN_NEWDIR) {
1315 rx->selected_files++; /* count only saved files */
1322 free_tree(tree.root); /* free the directory tree */
1328 * This routine is used to get the current backup or a backup
1329 * before the specified date.
1331 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
1336 char fileset_name[MAX_NAME_LENGTH];
1337 char ed1[50], ed2[50];
1338 char pool_select[MAX_NAME_LENGTH];
1341 /* Create temp tables */
1342 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1343 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1344 if (!db_sql_query(ua->db, uar_create_temp[db_get_type_index(ua->db)], NULL, NULL)) {
1345 ua->error_msg("%s\n", db_strerror(ua->db));
1347 if (!db_sql_query(ua->db, uar_create_temp1[db_get_type_index(ua->db)], NULL, NULL)) {
1348 ua->error_msg("%s\n", db_strerror(ua->db));
1351 * Select Client from the Catalog
1353 memset(&cr, 0, sizeof(cr));
1354 if (!get_client_dbr(ua, &cr)) {
1357 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1362 memset(&fsr, 0, sizeof(fsr));
1363 i = find_arg_with_value(ua, "FileSet");
1365 if (i >= 0 && is_name_valid(ua->argv[i], &ua->errmsg)) {
1366 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1367 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1368 ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1369 db_strerror(ua->db));
1372 } else if (i >= 0) { /* name is invalid */
1373 ua->error_msg(_("FileSet argument: %s\n"), ua->errmsg);
1376 if (i < 0) { /* fileset not found */
1377 edit_int64(cr.ClientId, ed1);
1378 Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1379 start_prompt(ua, _("The defined FileSet resources are:\n"));
1380 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1381 ua->error_msg("%s\n", db_strerror(ua->db));
1383 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1384 fileset_name, sizeof(fileset_name)) < 0) {
1385 ua->error_msg(_("No FileSet found for client \"%s\".\n"), cr.Name);
1389 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1390 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1391 ua->warning_msg(_("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1392 ua->send_msg(_("This probably means you modified the FileSet.\n"
1393 "Continuing anyway.\n"));
1397 /* If Pool specified, add PoolId specification */
1401 memset(&pr, 0, sizeof(pr));
1402 bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1403 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1404 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
1405 edit_int64(pr.PoolId, ed1));
1407 ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1411 /* Find JobId of last Full backup for this client, fileset */
1412 edit_int64(cr.ClientId, ed1);
1413 Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1415 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1416 ua->error_msg("%s\n", db_strerror(ua->db));
1420 /* Find all Volumes used by that JobId */
1421 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1422 ua->error_msg("%s\n", db_strerror(ua->db));
1426 /* Note, this is needed because I don't seem to get the callback
1427 * from the call just above.
1430 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1431 ua->warning_msg("%s\n", db_strerror(ua->db));
1433 if (rx->JobTDate == 0) {
1434 ua->error_msg(_("No Full backup before %s found.\n"), date);
1438 /* Now find most recent Differental Job after Full save, if any */
1439 Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1440 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1441 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1442 ua->warning_msg("%s\n", db_strerror(ua->db));
1444 /* Now update JobTDate to look into Differental, if any */
1446 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1447 ua->warning_msg("%s\n", db_strerror(ua->db));
1449 if (rx->JobTDate == 0) {
1450 ua->error_msg(_("No Full backup before %s found.\n"), date);
1454 /* Now find all Incremental Jobs after Full/dif save */
1455 Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1456 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1457 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1458 ua->warning_msg("%s\n", db_strerror(ua->db));
1461 /* Get the JobIds from that list */
1462 rx->last_jobid[0] = rx->JobIds[0] = 0;
1464 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1465 ua->warning_msg("%s\n", db_strerror(ua->db));
1468 if (rx->JobIds[0] != 0) {
1469 if (find_arg(ua, NT_("copies")) > 0) {
1470 /* Display a list of all copies */
1471 db_list_copies_records(ua->jcr, ua->db, 0, rx->JobIds,
1472 prtit, ua, HORZ_LIST);
1474 /* Display a list of Jobs selected for this restore */
1475 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1,HORZ_LIST);
1479 ua->warning_msg(_("No jobs found.\n"));
1483 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1484 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1488 static int restore_count_handler(void *ctx, int num_fields, char **row)
1490 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1491 rx->JobId = str_to_int64(row[0]);
1497 * Callback handler to get JobId and FileIndex for files
1498 * can insert more than one depending on the caller.
1500 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1502 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1503 JobId_t JobId = str_to_int64(row[0]);
1505 Dmsg3(200, "JobId=%s JobIds=%s FileIndex=%s\n", row[0], rx->JobIds, row[1]);
1507 /* New JobId, add it to JobIds
1508 * The list is sorted by JobId, so we need a cache for the previous value
1510 * It will permit to find restore objects to send during the restore
1512 if (rx->JobId != JobId) {
1514 pm_strcat(rx->JobIds, ",");
1516 pm_strcat(rx->JobIds, row[0]);
1520 add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1522 rx->selected_files++;
1527 * Callback handler make list of JobIds
1529 static int jobid_handler(void *ctx, int num_fields, char **row)
1531 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1533 if (strcmp(rx->last_jobid, row[0]) == 0) {
1534 return 0; /* duplicate id */
1536 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1537 if (rx->JobIds[0] != 0) {
1538 pm_strcat(rx->JobIds, ",");
1540 pm_strcat(rx->JobIds, row[0]);
1546 * Callback handler to pickup last Full backup JobTDate
1548 static int last_full_handler(void *ctx, int num_fields, char **row)
1550 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1552 rx->JobTDate = str_to_int64(row[1]);
1557 * Callback handler build FileSet name prompt list
1559 static int fileset_handler(void *ctx, int num_fields, char **row)
1561 /* row[0] = FileSet (name) */
1563 add_prompt((UAContext *)ctx, row[0]);
1569 * Free names in the list
1571 static void free_name_list(NAME_LIST *name_list)
1573 for (int i=0; i < name_list->num_ids; i++) {
1574 free(name_list->name[i]);
1576 bfree_and_null(name_list->name);
1577 name_list->max_ids = 0;
1578 name_list->num_ids = 0;
1581 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType)
1586 Dmsg1(200, "Already have store=%s\n", rx.store->name());
1590 * Try looking up Storage by name
1593 foreach_res(store, R_STORAGE) {
1594 if (strcmp(Storage, store->name()) == 0) {
1595 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1604 /* Check if an explicit storage resource is given */
1606 int i = find_arg_with_value(ua, "storage");
1608 store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1609 if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1613 if (store && (store != rx.store)) {
1614 ua->info_msg(_("\nWarning Storage is overridden by \"%s\" on the command line.\n"),
1617 bstrncpy(Storage, store->name(), MAX_NAME_LENGTH); /* Return overridden Storage */
1618 if (strcmp(MediaType, store->media_type) != 0) {
1619 ua->info_msg(_("Warning MediaType overridden by Storage Media Type:\n"
1620 " New Storage MediaType=\"%s\"\n"
1621 " Old Volume MediaType=\"%s\".\n\n"),
1622 store->media_type, MediaType);
1623 bstrncpy(MediaType, store->media_type, MAX_NAME_LENGTH); /* Return overridden MediaType */
1625 Dmsg2(200, "Set store=%s MediaType=%s\n", rx.store->name(), rx.RestoreMediaType);
1630 /* If no storage resource, try to find one from MediaType */
1633 foreach_res(store, R_STORAGE) {
1634 if (strcmp(MediaType, store->media_type) == 0) {
1635 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1637 Dmsg1(200, "Set store=%s\n", rx.store->name());
1638 if (Storage == NULL || Storage[0] == 0) {
1639 ua->warning_msg(_("Using Storage \"%s\" from MediaType \"%s\".\n"),
1640 store->name(), MediaType);
1642 ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1643 Storage, store->name(), MediaType);
1651 ua->warning_msg(_("\nUnable to find Storage resource for\n"
1652 "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1655 /* Take command line arg, or ask user if none */
1656 rx.store = get_storage_resource(ua, false /* don't use default */);
1658 Dmsg1(200, "Set store=%s\n", rx.store->name());