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)
65 memset(rx, 0, sizeof(*rx));
66 rx->path = get_pool_memory(PM_FNAME);
69 rx->fname = get_pool_memory(PM_FNAME);
72 rx->JobIds = get_pool_memory(PM_FNAME);
75 rx->component_fname = get_pool_memory(PM_FNAME);
76 rx->component_fname[0] = 0;
78 rx->BaseJobIds = get_pool_memory(PM_FNAME);
79 rx->BaseJobIds[0] = 0;
81 rx->query = get_pool_memory(PM_FNAME);
85 rx->hardlinks_in_mem = true;
92 int restore_cmd(UAContext *ua, const char *cmd)
94 RESTORE_CTX rx; /* restore context */
99 char *escaped_bsr_name = NULL;
100 char *escaped_where_name = NULL;
101 char *strip_prefix, *add_prefix, *add_suffix, *regexp;
102 strip_prefix = add_prefix = add_suffix = regexp = NULL;
104 new_rx(&rx); /* Initialize RESTORE_CTX */
106 if (!open_new_client_db(ua)) {
110 for (i = 0; i < ua->argc ; i++) {
112 continue; /* skip if no value given */
114 if (strcasecmp(ua->argk[i], "comment") == 0) {
115 rx.comment = ua->argv[i];
116 if (!is_comment_legal(ua, rx.comment)) {
120 } else if (strcasecmp(ua->argk[i], "where") == 0) {
121 rx.where = ua->argv[i];
123 } else if (strcasecmp(ua->argk[i], "replace") == 0) {
124 rx.replace = ua->argv[i];
126 } else if (strcasecmp(ua->argk[i], "strip_prefix") == 0) {
127 strip_prefix = ua->argv[i];
129 } else if (strcasecmp(ua->argk[i], "add_prefix") == 0) {
130 add_prefix = ua->argv[i];
132 } else if (strcasecmp(ua->argk[i], "add_suffix") == 0) {
133 add_suffix = ua->argv[i];
135 } else if (strcasecmp(ua->argk[i], "regexwhere") == 0) {
136 rx.RegexWhere = ua->argv[i];
138 } else if (strcasecmp(ua->argk[i], "optimizespeed") == 0) {
139 if (strcasecmp(ua->argv[i], "0") || strcasecmp(ua->argv[i], "no") ||
140 strcasecmp(ua->argv[i], "false")) {
141 rx.hardlinks_in_mem = false;
146 if (strip_prefix || add_suffix || add_prefix) {
147 int len = bregexp_get_build_where_size(strip_prefix, add_prefix, add_suffix);
148 regexp = (char *)bmalloc(len * sizeof(char));
150 bregexp_build_where(regexp, len, strip_prefix, add_prefix, add_suffix);
151 rx.RegexWhere = regexp;
154 /* TODO: add acl for regexwhere ? */
157 if (!acl_access_ok(ua, Where_ACL, rx.RegexWhere)) {
158 ua->error_msg(_("\"RegexWhere\" specification not authorized.\n"));
164 if (!acl_access_ok(ua, Where_ACL, rx.where)) {
165 ua->error_msg(_("\"where\" specification not authorized.\n"));
170 /* Ensure there is at least one Restore Job */
172 foreach_res(job, R_JOB) {
173 if (job->JobType == JT_RESTORE) {
174 if (!rx.restore_job) {
175 rx.restore_job = job;
181 if (!rx.restore_jobs) {
183 "No Restore Job Resource found in bacula-dir.conf.\n"
184 "You must create at least one before running this command.\n"));
189 * Request user to select JobIds or files by various different methods
190 * last 20 jobs, where File saved, most recent backup, ...
191 * In the end, a list of files are pumped into
194 switch (user_select_jobids_or_files(ua, &rx)) {
197 case 1: /* selected by jobid */
198 get_and_display_basejobs(ua, &rx);
199 if (!build_directory_tree(ua, &rx)) {
200 ua->send_msg(_("Restore not done.\n"));
204 case 2: /* selected by filename, no tree needed */
210 if (!complete_bsr(ua, rx.bsr)) { /* find Vol, SessId, SessTime from JobIds */
211 ua->error_msg(_("Unable to construct a valid BSR. Cannot continue.\n"));
214 if (!(rx.selected_files = write_bsr_file(ua, rx))) {
215 ua->warning_msg(_("No files selected to be restored.\n"));
219 ua->send_msg(_("Bootstrap records written to %s\n"), ua->jcr->RestoreBootstrap);
220 display_bsr_info(ua, rx); /* display vols needed, etc */
222 if (rx.selected_files==1) {
223 ua->info_msg(_("\n1 file selected to be restored.\n\n"));
225 ua->info_msg(_("\n%s files selected to be restored.\n\n"),
226 edit_uint64_with_commas(rx.selected_files, ed1));
229 ua->warning_msg(_("No files selected to be restored.\n"));
233 if (rx.restore_jobs == 1) {
234 job = rx.restore_job;
236 job = get_restore_job(ua);
242 get_client_name(ua, &rx);
243 if (!rx.ClientName) {
244 ua->error_msg(_("No Client resource found!\n"));
247 get_restore_client_name(ua, rx);
249 escaped_bsr_name = escape_filename(jcr->RestoreBootstrap);
252 "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
253 " bootstrap=\"%s\" files=%u catalog=\"%s\"",
254 job->name(), rx.ClientName, rx.RestoreClientName,
255 rx.store?rx.store->name():"",
256 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
257 rx.selected_files, ua->catalog->name());
259 /* Build run command */
261 if (rx.RestoreMediaType[0]) {
262 Mmsg(buf, " mediatype=\"%s\"", rx.RestoreMediaType);
263 pm_strcat(ua->cmd, buf);
267 escaped_where_name = escape_filename(rx.RegexWhere);
268 Mmsg(buf, " regexwhere=\"%s\"",
269 escaped_where_name ? escaped_where_name : rx.RegexWhere);
271 } else if (rx.where) {
272 escaped_where_name = escape_filename(rx.where);
273 Mmsg(buf," where=\"%s\"",
274 escaped_where_name ? escaped_where_name : rx.where);
276 pm_strcat(ua->cmd, buf);
279 Mmsg(buf, " replace=%s", rx.replace);
280 pm_strcat(ua->cmd, buf);
284 Mmsg(buf, " comment=\"%s\"", rx.comment);
285 pm_strcat(ua->cmd, buf);
288 if (escaped_bsr_name != NULL) {
289 bfree(escaped_bsr_name);
292 if (escaped_where_name != NULL) {
293 bfree(escaped_where_name);
300 if (find_arg(ua, NT_("yes")) > 0) {
301 pm_strcat(ua->cmd, " yes"); /* pass it on to the run command */
303 Dmsg1(200, "Submitting: %s\n", ua->cmd);
305 * Transfer jobids, component stuff to jcr to
306 * pass to run_cmd(). Note, these are fields and
307 * other things that are not passed on the command
310 /* ***FIXME*** pass jobids on command line */
311 jcr->JobIds = rx.JobIds;
313 jcr->component_fname = rx.component_fname;
314 rx.component_fname = NULL;
315 jcr->component_fd = rx.component_fd;
316 rx.component_fd = NULL;
318 run_cmd(ua, ua->cmd);
320 garbage_collect_memory(); /* release unused memory */
324 if (escaped_bsr_name != NULL) {
325 bfree(escaped_bsr_name);
328 if (escaped_where_name != NULL) {
329 bfree(escaped_where_name);
337 garbage_collect_memory(); /* release unused memory */
343 * Fill the rx->BaseJobIds and display the list
345 static void get_and_display_basejobs(UAContext *ua, RESTORE_CTX *rx)
349 if (!db_get_used_base_jobids(ua->jcr, ua->db, rx->JobIds, &jobids)) {
350 ua->warning_msg("%s", db_strerror(ua->db));
355 Mmsg(q, uar_print_jobs, jobids.list);
356 ua->send_msg(_("The restore will use the following job(s) as Base\n"));
357 db_list_sql_query(ua->jcr, ua->db, q.c_str(), prtit, ua, 1, HORZ_LIST);
359 pm_strcpy(rx->BaseJobIds, jobids.list);
362 void free_rx(RESTORE_CTX *rx)
366 free_and_null_pool_memory(rx->JobIds);
367 free_and_null_pool_memory(rx->BaseJobIds);
368 free_and_null_pool_memory(rx->fname);
369 free_and_null_pool_memory(rx->path);
370 free_and_null_pool_memory(rx->query);
371 if (rx->component_fd) {
372 fclose(rx->component_fd);
373 rx->component_fd = NULL;
375 if (rx->component_fname) {
376 unlink(rx->component_fname);
378 free_and_null_pool_memory(rx->component_fname);
379 free_name_list(&rx->name_list);
382 static bool has_value(UAContext *ua, int i)
385 ua->error_msg(_("Missing value for keyword: %s\n"), ua->argk[i]);
392 * This gets the client name from which the backup was made
394 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
396 /* If no client name specified yet, get it now */
397 if (!rx->ClientName[0]) {
399 /* try command line argument */
400 int i = find_arg_with_value(ua, NT_("client"));
402 i = find_arg_with_value(ua, NT_("backupclient"));
405 if (!is_name_valid(ua->argv[i], &ua->errmsg)) {
406 ua->error_msg("%s argument: %s", ua->argk[i], ua->errmsg);
409 bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
412 memset(&cr, 0, sizeof(cr));
413 if (!get_client_dbr(ua, &cr)) {
416 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
422 * This is where we pick up a client name to restore to.
424 static int get_restore_client_name(UAContext *ua, RESTORE_CTX &rx)
426 /* Start with same name as backup client */
427 bstrncpy(rx.RestoreClientName, rx.ClientName, sizeof(rx.RestoreClientName));
429 /* try command line argument */
430 int i = find_arg_with_value(ua, NT_("restoreclient"));
432 if (!is_name_valid(ua->argv[i], &ua->errmsg)) {
433 ua->error_msg("%s argument: %s", ua->argk[i], ua->errmsg);
436 bstrncpy(rx.RestoreClientName, ua->argv[i], sizeof(rx.RestoreClientName));
445 * The first step in the restore process is for the user to
446 * select a list of JobIds from which he will subsequently
447 * select which files are to be restored.
449 * Returns: 2 if filename list made
450 * 1 if jobid list made
453 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
456 char date[MAX_TIME_LENGTH];
457 bool have_date = false;
458 /* Include current second if using current time */
459 utime_t now = time(NULL) + 1;
461 JOB_DBR jr = { (JobId_t)-1 };
464 const char *list[] = {
465 _("List last 20 Jobs run"),
466 _("List Jobs where a given File is saved"),
467 _("Enter list of comma separated JobIds to select"),
468 _("Enter SQL list command"),
469 _("Select the most recent backup for a client"),
470 _("Select backup for a client before a specified time"),
471 _("Enter a list of files to restore"),
472 _("Enter a list of files to restore before a specified time"),
473 _("Find the JobIds of the most recent backup for a client"),
474 _("Find the JobIds for a backup for a client before a specified time"),
475 _("Enter a list of directories to restore for found JobIds"),
476 _("Select full restore to a specified Job date"),
481 /* These keywords are handled in a for loop */
491 /* The keyword below are handled by individual arg lookups */
497 "bootstrap", /* 13 */
499 "strip_prefix", /* 15 */
500 "add_prefix", /* 16 */
501 "add_suffix", /* 17 */
502 "regexwhere", /* 18 */
503 "restoreclient", /* 19 */
506 "restorejob", /* 22 */
513 for (i=1; i<ua->argc; i++) { /* loop through arguments */
514 bool found_kw = false;
515 for (j=0; kw[j]; j++) { /* loop through keywords */
516 if (strcasecmp(kw[j], ua->argk[i]) == 0) {
523 ua->error_msg(_("Unknown keyword: %s\n"), ua->argk[i]);
526 /* Found keyword in kw[] list, process it */
529 if (!has_value(ua, i)) {
532 if (*rx->JobIds != 0) {
533 pm_strcat(rx->JobIds, ",");
535 pm_strcat(rx->JobIds, ua->argv[i]);
538 case 1: /* current */
540 * Note, we add one second here just to include any job
541 * that may have finished within the current second,
542 * which happens a lot in scripting small jobs.
544 bstrutime(date, sizeof(date), now);
548 if (have_date || !has_value(ua, i)) {
551 if (str_to_utime(ua->argv[i]) == 0) {
552 ua->error_msg(_("Improper date format: %s\n"), ua->argv[i]);
555 bstrncpy(date, ua->argv[i], sizeof(date));
560 if (!has_value(ua, i)) {
564 bstrutime(date, sizeof(date), now);
566 if (!get_client_name(ua, rx)) {
569 pm_strcpy(ua->cmd, ua->argv[i]);
570 insert_one_file_or_dir(ua, rx, date, j==4);
574 bstrutime(date, sizeof(date), now);
576 if (!select_backups_before_date(ua, rx, date)) {
581 case 6: /* pool specified */
582 if (!has_value(ua, i)) {
585 rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
587 ua->error_msg(_("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
590 if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
592 ua->error_msg(_("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
596 case 7: /* all specified */
600 * All keywords 7 or greater are ignored or handled by a select prompt
608 ua->send_msg(_("\nFirst you select one or more JobIds that contain files\n"
609 "to be restored. You will be presented several methods\n"
610 "of specifying the JobIds. Then you will be allowed to\n"
611 "select which files from those JobIds are to be restored.\n\n"));
614 /* If choice not already made above, prompt */
621 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
622 for (int i=0; list[i]; i++) {
623 add_prompt(ua, list[i]);
626 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
627 case -1: /* error or cancel */
629 case 0: /* list last 20 Jobs run */
630 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
631 ua->error_msg(_("SQL query not authorized.\n"));
634 gui_save = ua->jcr->gui;
636 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
637 ua->jcr->gui = gui_save;
640 case 1: /* list where a file is saved */
641 if (!get_client_name(ua, rx)) {
644 if (!get_cmd(ua, _("Enter Filename (no path):"))) {
647 len = strlen(ua->cmd);
648 fname = (char *)malloc(len * 2 + 1);
649 db_escape_string(ua->jcr, ua->db, fname, ua->cmd, len);
650 Mmsg(rx->query, uar_file[db_get_type_index(ua->db)], rx->ClientName, fname);
652 gui_save = ua->jcr->gui;
654 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
655 ua->jcr->gui = gui_save;
658 case 2: /* enter a list of JobIds */
659 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
662 pm_strcpy(rx->JobIds, ua->cmd);
664 case 3: /* Enter an SQL list command */
665 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
666 ua->error_msg(_("SQL query not authorized.\n"));
669 if (!get_cmd(ua, _("Enter SQL list command: "))) {
672 gui_save = ua->jcr->gui;
674 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
675 ua->jcr->gui = gui_save;
678 case 4: /* Select the most recent backups */
680 bstrutime(date, sizeof(date), now);
682 if (!select_backups_before_date(ua, rx, date)) {
686 case 5: /* select backup at specified time */
688 if (!get_date(ua, date, sizeof(date))) {
692 if (!select_backups_before_date(ua, rx, date)) {
696 case 6: /* Enter files */
698 bstrutime(date, sizeof(date), now);
700 if (!get_client_name(ua, rx)) {
703 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
704 "containing a list of file names with paths, and terminate\n"
705 "them with a blank line.\n"));
707 if (!get_cmd(ua, _("Enter full filename: "))) {
710 len = strlen(ua->cmd);
714 insert_one_file_or_dir(ua, rx, date, false);
717 case 7: /* enter files backed up before specified time */
719 if (!get_date(ua, date, sizeof(date))) {
723 if (!get_client_name(ua, rx)) {
726 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
727 "containing a list of file names with paths, and terminate\n"
728 "them with a blank line.\n"));
730 if (!get_cmd(ua, _("Enter full filename: "))) {
733 len = strlen(ua->cmd);
737 insert_one_file_or_dir(ua, rx, date, false);
741 case 8: /* Find JobIds for current backup */
743 bstrutime(date, sizeof(date), now);
745 if (!select_backups_before_date(ua, rx, date)) {
751 case 9: /* Find JobIds for give date */
753 if (!get_date(ua, date, sizeof(date))) {
757 if (!select_backups_before_date(ua, rx, date)) {
763 case 10: /* Enter directories */
764 if (*rx->JobIds != 0) {
765 ua->send_msg(_("You have already selected the following JobIds: %s\n"),
767 } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
768 if (*rx->JobIds != 0 && *ua->cmd) {
769 pm_strcat(rx->JobIds, ",");
771 pm_strcat(rx->JobIds, ua->cmd);
773 if (*rx->JobIds == 0 || *rx->JobIds == '.') {
775 return 0; /* nothing entered, return */
778 bstrutime(date, sizeof(date), now);
780 if (!get_client_name(ua, rx)) {
783 ua->send_msg(_("Enter full directory names or start the name\n"
784 "with a < to indicate it is a filename containing a list\n"
785 "of directories and terminate them with a blank line.\n"));
787 if (!get_cmd(ua, _("Enter directory name: "))) {
790 len = strlen(ua->cmd);
794 /* Add trailing slash to end of directory names */
795 if (ua->cmd[0] != '<' && !IsPathSeparator(ua->cmd[len-1])) {
796 strcat(ua->cmd, "/");
798 insert_one_file_or_dir(ua, rx, date, true);
802 case 11: /* Choose a jobid and select jobs */
803 if (!get_cmd(ua, _("Enter JobId to get the state to restore: ")) ||
804 !is_an_integer(ua->cmd))
809 memset(&jr, 0, sizeof(JOB_DBR));
810 jr.JobId = str_to_int64(ua->cmd);
811 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
812 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
813 ua->cmd, db_strerror(ua->db));
816 ua->send_msg(_("Selecting jobs to build the Full state at %s\n"),
818 jr.JobLevel = L_INCREMENTAL; /* Take Full+Diff+Incr */
819 if (!db_get_accurate_jobids(ua->jcr, ua->db, &jr, &jobids)) {
822 pm_strcpy(rx->JobIds, jobids.list);
823 Dmsg1(30, "Item 12: jobids = %s\n", rx->JobIds);
825 case 12: /* Cancel or quit */
830 memset(&jr, 0, sizeof(JOB_DBR));
831 POOLMEM *JobIds = get_pool_memory(PM_FNAME);
835 * Find total number of files to be restored, and filter the JobId
836 * list to contain only ones permitted by the ACL conditions.
838 for (p=rx->JobIds; ; ) {
840 int stat = get_next_jobid_from_list(&p, &JobId);
842 ua->error_msg(_("Invalid JobId in list.\n"));
843 free_pool_memory(JobIds);
849 if (jr.JobId == JobId) {
850 continue; /* duplicate of last JobId */
852 memset(&jr, 0, sizeof(JOB_DBR));
854 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
855 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
856 edit_int64(JobId, ed1), db_strerror(ua->db));
857 free_pool_memory(JobIds);
860 if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
861 ua->error_msg(_("Access to JobId=%s (Job \"%s\") not authorized. Not selected.\n"),
862 edit_int64(JobId, ed1), jr.Name);
866 pm_strcat(JobIds, ",");
868 pm_strcat(JobIds, edit_int64(JobId, ed1));
869 rx->TotalFiles += jr.JobFiles;
871 free_pool_memory(rx->JobIds);
872 rx->JobIds = JobIds; /* Set ACL filtered list */
873 if (*rx->JobIds == 0) {
874 ua->warning_msg(_("No Jobs selected.\n"));
878 if (strchr(rx->JobIds,',')) {
879 ua->info_msg(_("You have selected the following JobIds: %s\n"), rx->JobIds);
881 ua->info_msg(_("You have selected the following JobId: %s\n"), rx->JobIds);
889 static bool get_date(UAContext *ua, char *date, int date_len)
891 ua->send_msg(_("The restored files will the most current backup\n"
892 "BEFORE the date you specify below.\n\n"));
894 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
897 if (str_to_utime(ua->cmd) != 0) {
900 ua->error_msg(_("Improper date format.\n"));
902 bstrncpy(date, ua->cmd, date_len);
907 * Insert a single file, or read a list of files from a file
909 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
919 if ((ffd = fopen(p, "rb")) == NULL) {
921 ua->error_msg(_("Cannot open file %s: ERR=%s\n"),
925 while (fgets(file, sizeof(file), ffd)) {
928 if (!insert_dir_into_findex_list(ua, rx, file, date)) {
929 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
932 if (!insert_file_into_findex_list(ua, rx, file, date)) {
933 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
941 insert_table_into_findex_list(ua, rx, p);
945 insert_dir_into_findex_list(ua, rx, ua->cmd, date);
947 insert_file_into_findex_list(ua, rx, ua->cmd, date);
954 * For a given file (path+filename), split into path and file, then
955 * lookup the most recent backup in the catalog to get the JobId
956 * and FileIndex, then insert them into the findex list.
958 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
961 strip_trailing_newline(file);
962 split_path_and_filename(ua, rx, file);
963 if (*rx->JobIds == 0) {
964 Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname,
967 Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
968 rx->path, rx->fname, rx->ClientName);
971 /* Find and insert jobid and File Index */
972 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
973 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
974 rx->query, db_strerror(ua->db));
977 ua->error_msg(_("No database record found for: %s\n"), file);
978 // ua->error_msg("Query=%s\n", rx->query);
985 * For a given path lookup the most recent backup in the catalog
986 * to get the JobId and FileIndexes of all files in that directory.
988 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
991 strip_trailing_junk(dir);
992 if (*rx->JobIds == 0) {
993 ua->error_msg(_("No JobId specified cannot continue.\n"));
996 Mmsg(rx->query, uar_jobid_fileindex_from_dir[db_get_type_index(ua->db)], rx->JobIds, dir, rx->ClientName);
999 /* Find and insert jobid and File Index */
1000 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
1001 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
1002 rx->query, db_strerror(ua->db));
1005 ua->error_msg(_("No database record found for: %s\n"), dir);
1012 * Get the JobId and FileIndexes of all files in the specified table
1014 bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
1016 strip_trailing_junk(table);
1017 Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
1020 /* Find and insert jobid and File Index */
1021 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
1022 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
1023 rx->query, db_strerror(ua->db));
1026 ua->error_msg(_("No table found: %s\n"), table);
1032 static void split_path_and_filename(UAContext *ua, RESTORE_CTX *rx, char *name)
1036 /* Find path without the filename.
1037 * I.e. everything after the last / is a "filename".
1038 * OK, maybe it is a directory name, but we treat it like
1039 * a filename. If we don't find a / then the whole name
1040 * must be a path name (e.g. c:).
1042 for (p=f=name; *p; p++) {
1043 if (IsPathSeparator(*p)) {
1044 f = p; /* set pos of last slash */
1047 if (IsPathSeparator(*f)) { /* did we find a slash? */
1048 f++; /* yes, point to filename */
1049 } else { /* no, whole thing must be path name */
1053 /* If filename doesn't exist (i.e. root directory), we
1054 * simply create a blank name consisting of a single
1055 * space. This makes handling zero length filenames
1060 rx->fname = check_pool_memory_size(rx->fname, 2*(rx->fnl)+1);
1061 db_escape_string(ua->jcr, ua->db, rx->fname, f, rx->fnl);
1069 rx->path = check_pool_memory_size(rx->path, 2*(rx->pnl)+1);
1070 db_escape_string(ua->jcr, ua->db, rx->path, name, rx->pnl);
1076 Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
1079 static bool ask_for_fileregex(UAContext *ua, RESTORE_CTX *rx)
1081 if (find_arg(ua, NT_("all")) >= 0) { /* if user enters all on command line */
1082 return true; /* select everything */
1084 ua->send_msg(_("\n\nFor one or more of the JobIds selected, no files were found,\n"
1085 "so file selection is not possible.\n"
1086 "Most likely your retention policy pruned the files.\n"));
1087 if (get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
1088 if (ua->pint32_val == 1)
1090 while (get_cmd(ua, _("\nRegexp matching files to restore? (empty to abort): "))) {
1091 if (ua->cmd[0] == '\0') {
1094 regex_t *fileregex_re = NULL;
1096 char errmsg[500] = "";
1098 fileregex_re = (regex_t *)bmalloc(sizeof(regex_t));
1099 rc = regcomp(fileregex_re, ua->cmd, REG_EXTENDED|REG_NOSUB);
1101 regerror(rc, fileregex_re, errmsg, sizeof(errmsg));
1103 regfree(fileregex_re);
1106 ua->send_msg(_("Regex compile error: %s\n"), errmsg);
1108 rx->bsr->fileregex = bstrdup(ua->cmd);
1117 /* Walk on the delta_list of a TREE_NODE item and insert all parts
1118 * TODO: Optimize for bootstrap creation, remove recursion
1119 * 6 -> 5 -> 4 -> 3 -> 2 -> 1 -> 0
1121 * 0, 1, 2, 3, 4, 5, 6
1123 static void add_delta_list_findex(RESTORE_CTX *rx, struct delta_list *lst)
1129 add_delta_list_findex(rx, lst->next);
1131 add_findex(rx->bsr, lst->JobId, lst->FileIndex);
1135 * This is a list of all the files (components) that the
1136 * user has requested for restore. It is requested by
1137 * the plugin (for now hard coded only for VSS).
1138 * In the future, this will be requested by a RestoreObject
1139 * and the plugin name will be sent to the FD.
1141 static bool write_component_file(UAContext *ua, RESTORE_CTX *rx, char *fname)
1144 if (!rx->component_fd) {
1145 Mmsg(rx->component_fname, "%s/%s.restore.sel.XXXXXX", working_directory, my_name);
1146 fd = mkstemp(rx->component_fname);
1149 ua->error_msg(_("Unable to create component file %s. ERR=%s\n"),
1150 rx->component_fname, be.bstrerror());
1153 rx->component_fd = fdopen(fd, "w+");
1154 if (!rx->component_fd) {
1156 ua->error_msg(_("Unable to fdopen component file %s. ERR=%s\n"),
1157 rx->component_fname, be.bstrerror());
1161 fprintf(rx->component_fd, "%s\n", fname);
1162 if (ferror(rx->component_fd)) {
1163 ua->error_msg(_("Error writing component file.\n"));
1164 fclose(rx->component_fd);
1165 unlink(rx->component_fname);
1166 rx->component_fd = NULL;
1172 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
1175 JobId_t JobId, last_JobId;
1180 memset(&tree, 0, sizeof(TREE_CTX));
1182 * Build the directory tree containing JobIds user selected
1184 tree.root = new_tree(rx->TotalFiles);
1187 tree.hardlinks_in_mem = rx->hardlinks_in_mem;
1190 * For display purposes, the same JobId, with different volumes may
1191 * appear more than once, however, we only insert it once.
1194 tree.FileEstimate = 0;
1195 if (get_next_jobid_from_list(&p, &JobId) > 0) {
1196 /* Use first JobId as estimate of the number of files to restore */
1197 Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
1198 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1199 ua->error_msg("%s\n", db_strerror(ua->db));
1202 /* Add about 25% more than this job for over estimate */
1203 tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
1204 tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
1208 ua->info_msg(_("\nBuilding directory tree for JobId(s) %s ... "),
1211 #define new_get_file_list
1212 #ifdef new_get_file_list
1213 if (!db_get_file_list(ua->jcr, ua->db,
1214 rx->JobIds, false /* do not use md5 */,
1215 true /* get delta */,
1216 insert_tree_handler, (void *)&tree))
1218 ua->error_msg("%s", db_strerror(ua->db));
1220 if (*rx->BaseJobIds) {
1221 pm_strcat(rx->JobIds, ",");
1222 pm_strcat(rx->JobIds, rx->BaseJobIds);
1225 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1228 if (JobId == last_JobId) {
1229 continue; /* eliminate duplicate JobIds */
1233 * Find files for this JobId and insert them in the tree
1235 Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
1236 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
1237 ua->error_msg("%s", db_strerror(ua->db));
1242 * At this point, the tree is built, so we can garbage collect
1243 * any memory released by the SQL engine that RedHat has
1244 * not returned to the OS :-(
1246 garbage_collect_memory();
1249 * Look at the first JobId on the list (presumably the oldest) and
1250 * if it is marked purged, don't do the manual selection because
1251 * the Job was pruned, so the tree is incomplete.
1253 if (tree.FileCount != 0) {
1254 /* Find out if any Job is purged */
1255 Mmsg(rx->query, "SELECT SUM(PurgedFiles) FROM Job WHERE JobId IN (%s)", rx->JobIds);
1256 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1257 ua->error_msg("%s\n", db_strerror(ua->db));
1259 /* rx->JobId is the PurgedFiles flag */
1260 if (rx->found && rx->JobId > 0) {
1261 tree.FileCount = 0; /* set count to zero, no tree selection */
1264 if (tree.FileCount == 0) {
1265 OK = ask_for_fileregex(ua, rx);
1268 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1269 if (JobId == last_JobId) {
1270 continue; /* eliminate duplicate JobIds */
1272 add_findex_all(rx->bsr, JobId);
1278 ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
1279 edit_uint64_with_commas(tree.FileCount, ec1));
1281 ua->info_msg(_("\n%s files inserted into the tree.\n"),
1282 edit_uint64_with_commas(tree.FileCount, ec1));
1285 if (find_arg(ua, NT_("done")) < 0) {
1286 /* Let the user interact in selecting which files to restore */
1287 OK = user_select_files_from_tree(&tree);
1291 * Walk down through the tree finding all files marked to be
1292 * extracted making a bootstrap file.
1296 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
1297 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
1298 if (node->extract || node->extract_dir) {
1299 Dmsg3(400, "JobId=%lld type=%d FI=%d\n", (uint64_t)node->JobId, node->type, node->FileIndex);
1300 /* TODO: optimize bsr insertion when jobid are non sorted */
1301 add_delta_list_findex(rx, node->delta_list);
1302 add_findex(rx->bsr, node->JobId, node->FileIndex);
1304 * Special VSS plugin code to return selected
1305 * components. For the moment, it is hard coded
1306 * for the VSS plugin.
1308 if (fnmatch(":component_info_*", node->fname, 0) == 0) {
1309 tree_getpath(node, cwd, sizeof(cwd));
1310 if (!write_component_file(ua, rx, cwd)) {
1315 if (node->extract && node->type != TN_NEWDIR) {
1316 rx->selected_files++; /* count only saved files */
1323 free_tree(tree.root); /* free the directory tree */
1329 * This routine is used to get the current backup or a backup
1330 * before the specified date.
1332 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
1337 char fileset_name[MAX_NAME_LENGTH];
1338 char ed1[50], ed2[50];
1339 char pool_select[MAX_NAME_LENGTH];
1342 /* Create temp tables */
1343 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1344 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1345 if (!db_sql_query(ua->db, uar_create_temp[db_get_type_index(ua->db)], NULL, NULL)) {
1346 ua->error_msg("%s\n", db_strerror(ua->db));
1348 if (!db_sql_query(ua->db, uar_create_temp1[db_get_type_index(ua->db)], NULL, NULL)) {
1349 ua->error_msg("%s\n", db_strerror(ua->db));
1352 * Select Client from the Catalog
1354 memset(&cr, 0, sizeof(cr));
1355 if (!get_client_dbr(ua, &cr)) {
1358 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1363 memset(&fsr, 0, sizeof(fsr));
1364 i = find_arg_with_value(ua, "FileSet");
1366 if (i >= 0 && is_name_valid(ua->argv[i], &ua->errmsg)) {
1367 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1368 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1369 ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1370 db_strerror(ua->db));
1373 } else if (i >= 0) { /* name is invalid */
1374 ua->error_msg(_("FileSet argument: %s\n"), ua->errmsg);
1377 if (i < 0) { /* fileset not found */
1378 edit_int64(cr.ClientId, ed1);
1379 Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1380 start_prompt(ua, _("The defined FileSet resources are:\n"));
1381 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1382 ua->error_msg("%s\n", db_strerror(ua->db));
1384 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1385 fileset_name, sizeof(fileset_name)) < 0) {
1386 ua->error_msg(_("No FileSet found for client \"%s\".\n"), cr.Name);
1390 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1391 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1392 ua->warning_msg(_("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1393 ua->send_msg(_("This probably means you modified the FileSet.\n"
1394 "Continuing anyway.\n"));
1398 /* If Pool specified, add PoolId specification */
1402 memset(&pr, 0, sizeof(pr));
1403 bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1404 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1405 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
1406 edit_int64(pr.PoolId, ed1));
1408 ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1412 /* Find JobId of last Full backup for this client, fileset */
1413 edit_int64(cr.ClientId, ed1);
1414 Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1416 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1417 ua->error_msg("%s\n", db_strerror(ua->db));
1421 /* Find all Volumes used by that JobId */
1422 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1423 ua->error_msg("%s\n", db_strerror(ua->db));
1427 /* Note, this is needed because I don't seem to get the callback
1428 * from the call just above.
1431 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1432 ua->warning_msg("%s\n", db_strerror(ua->db));
1434 if (rx->JobTDate == 0) {
1435 ua->error_msg(_("No Full backup before %s found.\n"), date);
1439 /* Now find most recent Differental Job after Full save, if any */
1440 Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1441 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1442 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1443 ua->warning_msg("%s\n", db_strerror(ua->db));
1445 /* Now update JobTDate to look into Differental, if any */
1447 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1448 ua->warning_msg("%s\n", db_strerror(ua->db));
1450 if (rx->JobTDate == 0) {
1451 ua->error_msg(_("No Full backup before %s found.\n"), date);
1455 /* Now find all Incremental Jobs after Full/dif save */
1456 Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1457 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1458 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1459 ua->warning_msg("%s\n", db_strerror(ua->db));
1462 /* Get the JobIds from that list */
1463 rx->last_jobid[0] = rx->JobIds[0] = 0;
1465 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1466 ua->warning_msg("%s\n", db_strerror(ua->db));
1469 if (rx->JobIds[0] != 0) {
1470 if (find_arg(ua, NT_("copies")) > 0) {
1471 /* Display a list of all copies */
1472 db_list_copies_records(ua->jcr, ua->db, 0, rx->JobIds,
1473 prtit, ua, HORZ_LIST);
1475 /* Display a list of Jobs selected for this restore */
1476 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1,HORZ_LIST);
1480 ua->warning_msg(_("No jobs found.\n"));
1484 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1485 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1489 static int restore_count_handler(void *ctx, int num_fields, char **row)
1491 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1492 rx->JobId = str_to_int64(row[0]);
1498 * Callback handler to get JobId and FileIndex for files
1499 * can insert more than one depending on the caller.
1501 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1503 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1504 JobId_t JobId = str_to_int64(row[0]);
1506 Dmsg3(200, "JobId=%s JobIds=%s FileIndex=%s\n", row[0], rx->JobIds, row[1]);
1508 /* New JobId, add it to JobIds
1509 * The list is sorted by JobId, so we need a cache for the previous value
1511 * It will permit to find restore objects to send during the restore
1513 if (rx->JobId != JobId) {
1515 pm_strcat(rx->JobIds, ",");
1517 pm_strcat(rx->JobIds, row[0]);
1521 add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1523 rx->selected_files++;
1528 * Callback handler make list of JobIds
1530 static int jobid_handler(void *ctx, int num_fields, char **row)
1532 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1534 if (strcmp(rx->last_jobid, row[0]) == 0) {
1535 return 0; /* duplicate id */
1537 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1538 if (rx->JobIds[0] != 0) {
1539 pm_strcat(rx->JobIds, ",");
1541 pm_strcat(rx->JobIds, row[0]);
1547 * Callback handler to pickup last Full backup JobTDate
1549 static int last_full_handler(void *ctx, int num_fields, char **row)
1551 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1553 rx->JobTDate = str_to_int64(row[1]);
1558 * Callback handler build FileSet name prompt list
1560 static int fileset_handler(void *ctx, int num_fields, char **row)
1562 /* row[0] = FileSet (name) */
1564 add_prompt((UAContext *)ctx, row[0]);
1570 * Free names in the list
1572 static void free_name_list(NAME_LIST *name_list)
1574 for (int i=0; i < name_list->num_ids; i++) {
1575 free(name_list->name[i]);
1577 bfree_and_null(name_list->name);
1578 name_list->max_ids = 0;
1579 name_list->num_ids = 0;
1582 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType)
1587 Dmsg1(200, "Already have store=%s\n", rx.store->name());
1591 * Try looking up Storage by name
1594 foreach_res(store, R_STORAGE) {
1595 if (strcmp(Storage, store->name()) == 0) {
1596 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1605 /* Check if an explicit storage resource is given */
1607 int i = find_arg_with_value(ua, "storage");
1609 store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1610 if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1614 if (store && (store != rx.store)) {
1615 ua->info_msg(_("\nWarning Storage is overridden by \"%s\" on the command line.\n"),
1618 bstrncpy(rx.RestoreMediaType, MediaType, sizeof(rx.RestoreMediaType));
1619 if (strcmp(MediaType, store->media_type) != 0) {
1620 ua->info_msg(_("This may not work because of two different MediaTypes:\n"
1621 " Storage MediaType=\"%s\"\n"
1622 " Volume MediaType=\"%s\".\n\n"),
1623 store->media_type, 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());