2 Bacula® - The Network Backup Solution
4 Copyright (C) 2002-2007 Free Software Foundation Europe e.V.
6 The main author of Bacula is Kern Sibbald, with contributions from
7 many others, a complete list can be found in the file AUTHORS.
8 This program is Free Software; you can redistribute it and/or
9 modify it under the terms of version two of the GNU General Public
10 License as published by the Free Software Foundation plus additions
11 that are listed in the file LICENSE.
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23 Bacula® is a registered trademark of John Walker.
24 The licensor of Bacula is the Free Software Foundation Europe
25 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26 Switzerland, email:ftf@fsfeurope.org.
30 * Bacula Director -- User Agent Database restore Command
31 * Creates a bootstrap file for restoring files and
32 * starts the restore job.
34 * Tree handling routines split into ua_tree.c July MMIII.
35 * BSR (bootstrap record) handling routines split into
38 * Kern Sibbald, July MMII
47 /* Imported functions */
48 extern void print_bsr(UAContext *ua, RBSR *bsr);
52 /* Forward referenced functions */
53 static int last_full_handler(void *ctx, int num_fields, char **row);
54 static int jobid_handler(void *ctx, int num_fields, char **row);
55 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx);
56 static int fileset_handler(void *ctx, int num_fields, char **row);
57 static void free_name_list(NAME_LIST *name_list);
58 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date);
59 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx);
60 static void free_rx(RESTORE_CTX *rx);
61 static void split_path_and_filename(RESTORE_CTX *rx, char *fname);
62 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row);
63 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
65 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
67 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir);
68 static int get_client_name(UAContext *ua, RESTORE_CTX *rx);
69 static int get_date(UAContext *ua, char *date, int date_len);
70 static int restore_count_handler(void *ctx, int num_fields, char **row);
71 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table);
77 int restore_cmd(UAContext *ua, const char *cmd)
79 RESTORE_CTX rx; /* restore context */
83 char *escaped_bsr_name = NULL;
84 char *escaped_where_name = NULL;
85 char *strip_prefix, *add_prefix, *add_suffix, *regexp;
86 strip_prefix = add_prefix = add_suffix = regexp = NULL;
88 memset(&rx, 0, sizeof(rx));
89 rx.path = get_pool_memory(PM_FNAME);
90 rx.fname = get_pool_memory(PM_FNAME);
91 rx.JobIds = get_pool_memory(PM_FNAME);
92 rx.query = get_pool_memory(PM_FNAME);
95 i = find_arg_with_value(ua, "where");
97 rx.where = ua->argv[i];
100 i = find_arg_with_value(ua, "strip_prefix");
102 strip_prefix = ua->argv[i];
105 i = find_arg_with_value(ua, "add_prefix");
107 add_prefix = ua->argv[i];
110 i = find_arg_with_value(ua, "add_suffix");
112 add_suffix = ua->argv[i];
115 i = find_arg_with_value(ua, "regexwhere");
117 rx.RegexWhere = ua->argv[i];
120 if (strip_prefix || add_suffix || add_prefix) {
121 int len = bregexp_get_build_where_size(strip_prefix, add_prefix, add_suffix);
122 regexp = (char *) bmalloc (len * sizeof(char));
124 bregexp_build_where(regexp, len, strip_prefix, add_prefix, add_suffix);
125 rx.RegexWhere = regexp;
128 /* TODO: add acl for regexwhere ? */
131 if (!acl_access_ok(ua, Where_ACL, rx.RegexWhere)) {
132 ua->error_msg(_("\"RegexWhere\" specification not authorized.\n"));
138 if (!acl_access_ok(ua, Where_ACL, rx.where)) {
139 ua->error_msg(_("\"where\" specification not authorized.\n"));
144 if (!open_client_db(ua)) {
148 /* Ensure there is at least one Restore Job */
150 foreach_res(job, R_JOB) {
151 if (job->JobType == JT_RESTORE) {
152 if (!rx.restore_job) {
153 rx.restore_job = job;
159 if (!rx.restore_jobs) {
161 "No Restore Job Resource found in bacula-dir.conf.\n"
162 "You must create at least one before running this command.\n"));
167 * Request user to select JobIds or files by various different methods
168 * last 20 jobs, where File saved, most recent backup, ...
169 * In the end, a list of files are pumped into
172 switch (user_select_jobids_or_files(ua, &rx)) {
175 case 1: /* selected by jobid */
176 if (!build_directory_tree(ua, &rx)) {
177 ua->send_msg(_("Restore not done.\n"));
181 case 2: /* selected by filename, no tree needed */
186 uint32_t selected_files;
188 if (!complete_bsr(ua, rx.bsr)) { /* find Vol, SessId, SessTime from JobIds */
189 ua->error_msg(_("Unable to construct a valid BSR. Cannot continue.\n"));
192 if (!(selected_files = write_bsr_file(ua, rx))) {
193 ua->warning_msg(_("No files selected to be restored.\n"));
196 /* If no count of files, use bsr generated value (often wrong) */
197 if (rx.selected_files == 0) {
198 rx.selected_files = selected_files;
200 if (rx.selected_files==1) {
201 ua->info_msg(_("\n1 file selected to be restored.\n\n"));
204 ua->info_msg(_("\n%s files selected to be restored.\n\n"),
205 edit_uint64_with_commas(rx.selected_files, ed1));
208 ua->warning_msg(_("No files selected to be restored.\n"));
212 if (rx.restore_jobs == 1) {
213 job = rx.restore_job;
215 job = select_restore_job_resource(ua);
221 get_client_name(ua, &rx);
222 if (!rx.ClientName) {
223 ua->error_msg(_("No Client resource found!\n"));
227 escaped_bsr_name = escape_filename(jcr->RestoreBootstrap);
229 /* Build run command */
231 escaped_where_name = escape_filename(rx.RegexWhere);
233 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s\""
234 " regexwhere=\"%s\" files=%d catalog=\"%s\"",
235 job->name(), rx.ClientName, rx.store?rx.store->name():"",
236 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
237 escaped_where_name ? escaped_where_name : rx.RegexWhere,
238 rx.selected_files, ua->catalog->name());
240 } else if (rx.where) {
241 escaped_where_name = escape_filename(rx.where);
243 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s\""
244 " where=\"%s\" files=%d catalog=\"%s\"",
245 job->name(), rx.ClientName, rx.store?rx.store->name():"",
246 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
247 escaped_where_name ? escaped_where_name : rx.where,
248 rx.selected_files, ua->catalog->name());
252 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s\""
253 " files=%d catalog=\"%s\"",
254 job->name(), rx.ClientName, rx.store?rx.store->name():"",
255 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
256 rx.selected_files, ua->catalog->name());
259 if (escaped_bsr_name != NULL) {
260 bfree(escaped_bsr_name);
263 if (escaped_where_name != NULL) {
264 bfree(escaped_where_name);
271 if (find_arg(ua, NT_("yes")) > 0) {
272 pm_strcat(ua->cmd, " yes"); /* pass it on to the run command */
274 Dmsg1(200, "Submitting: %s\n", ua->cmd);
276 run_cmd(ua, ua->cmd);
281 if (escaped_bsr_name != NULL) {
282 bfree(escaped_bsr_name);
285 if (escaped_where_name != NULL) {
286 bfree(escaped_where_name);
298 static void free_rx(RESTORE_CTX *rx)
303 free_pool_memory(rx->JobIds);
307 free_pool_memory(rx->fname);
311 free_pool_memory(rx->path);
315 free_pool_memory(rx->query);
318 free_name_list(&rx->name_list);
321 static bool has_value(UAContext *ua, int i)
324 ua->error_msg(_("Missing value for keyword: %s\n"), ua->argk[i]);
330 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
332 /* If no client name specified yet, get it now */
333 if (!rx->ClientName[0]) {
335 /* try command line argument */
336 int i = find_arg_with_value(ua, NT_("client"));
338 if (!has_value(ua, i)) {
341 bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
344 memset(&cr, 0, sizeof(cr));
345 if (!get_client_dbr(ua, &cr)) {
348 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
355 * The first step in the restore process is for the user to
356 * select a list of JobIds from which he will subsequently
357 * select which files are to be restored.
359 * Returns: 2 if filename list made
360 * 1 if jobid list made
363 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
366 char date[MAX_TIME_LENGTH];
367 bool have_date = false;
368 /* Include current second if using current time */
369 utime_t now = time(NULL) + 1;
371 JOB_DBR jr = { (JobId_t)-1 };
374 const char *list[] = {
375 _("List last 20 Jobs run"),
376 _("List Jobs where a given File is saved"),
377 _("Enter list of comma separated JobIds to select"),
378 _("Enter SQL list command"),
379 _("Select the most recent backup for a client"),
380 _("Select backup for a client before a specified time"),
381 _("Enter a list of files to restore"),
382 _("Enter a list of files to restore before a specified time"),
383 _("Find the JobIds of the most recent backup for a client"),
384 _("Find the JobIds for a backup for a client before a specified time"),
385 _("Enter a list of directories to restore for found JobIds"),
390 /* These keywords are handled in a for loop */
400 /* The keyword below are handled by individual arg lookups */
406 "bootstrap", /* 13 */
408 "strip_prefix", /* 15 */
409 "add_prefix", /* 16 */
410 "add_suffix", /* 17 */
411 "regexwhere", /* 18 */
417 for (i=1; i<ua->argc; i++) { /* loop through arguments */
418 bool found_kw = false;
419 for (j=0; kw[j]; j++) { /* loop through keywords */
420 if (strcasecmp(kw[j], ua->argk[i]) == 0) {
426 ua->error_msg(_("Unknown keyword: %s\n"), ua->argk[i]);
429 /* Found keyword in kw[] list, process it */
432 if (!has_value(ua, i)) {
435 if (*rx->JobIds != 0) {
436 pm_strcat(rx->JobIds, ",");
438 pm_strcat(rx->JobIds, ua->argv[i]);
441 case 1: /* current */
443 * Note, we add one second here just to include any job
444 * that may have finished within the current second,
445 * which happens a lot in scripting small jobs.
447 bstrutime(date, sizeof(date), now);
451 if (!has_value(ua, i)) {
454 if (str_to_utime(ua->argv[i]) == 0) {
455 ua->error_msg(_("Improper date format: %s\n"), ua->argv[i]);
458 bstrncpy(date, ua->argv[i], sizeof(date));
463 if (!has_value(ua, i)) {
467 bstrutime(date, sizeof(date), now);
469 if (!get_client_name(ua, rx)) {
472 pm_strcpy(ua->cmd, ua->argv[i]);
473 insert_one_file_or_dir(ua, rx, date, j==4);
477 bstrutime(date, sizeof(date), now);
479 if (!select_backups_before_date(ua, rx, date)) {
484 case 6: /* pool specified */
485 if (!has_value(ua, i)) {
488 rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
490 ua->error_msg(_("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
493 if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
495 ua->error_msg(_("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
499 case 7: /* all specified */
503 * All keywords 7 or greater are ignored or handled by a select prompt
511 ua->send_msg(_("\nFirst you select one or more JobIds that contain files\n"
512 "to be restored. You will be presented several methods\n"
513 "of specifying the JobIds. Then you will be allowed to\n"
514 "select which files from those JobIds are to be restored.\n\n"));
517 /* If choice not already made above, prompt */
523 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
524 for (int i=0; list[i]; i++) {
525 add_prompt(ua, list[i]);
528 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
529 case -1: /* error or cancel */
531 case 0: /* list last 20 Jobs run */
532 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
533 ua->error_msg(_("SQL query not authorized.\n"));
536 gui_save = ua->jcr->gui;
538 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
539 ua->jcr->gui = gui_save;
542 case 1: /* list where a file is saved */
543 if (!get_client_name(ua, rx)) {
546 if (!get_cmd(ua, _("Enter Filename (no path):"))) {
549 len = strlen(ua->cmd);
550 fname = (char *)malloc(len * 2 + 1);
551 db_escape_string(fname, ua->cmd, len);
552 Mmsg(rx->query, uar_file, rx->ClientName, fname);
554 gui_save = ua->jcr->gui;
556 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
557 ua->jcr->gui = gui_save;
560 case 2: /* enter a list of JobIds */
561 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
564 pm_strcpy(rx->JobIds, ua->cmd);
566 case 3: /* Enter an SQL list command */
567 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
568 ua->error_msg(_("SQL query not authorized.\n"));
571 if (!get_cmd(ua, _("Enter SQL list command: "))) {
574 gui_save = ua->jcr->gui;
576 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
577 ua->jcr->gui = gui_save;
580 case 4: /* Select the most recent backups */
581 bstrutime(date, sizeof(date), now);
582 if (!select_backups_before_date(ua, rx, date)) {
586 case 5: /* select backup at specified time */
587 if (!get_date(ua, date, sizeof(date))) {
590 if (!select_backups_before_date(ua, rx, date)) {
594 case 6: /* Enter files */
595 bstrutime(date, sizeof(date), now);
596 if (!get_client_name(ua, rx)) {
599 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
600 "containing a list of file names with paths, and terminate\n"
601 "them with a blank line.\n"));
603 if (!get_cmd(ua, _("Enter full filename: "))) {
606 len = strlen(ua->cmd);
610 insert_one_file_or_dir(ua, rx, date, false);
613 case 7: /* enter files backed up before specified time */
614 if (!get_date(ua, date, sizeof(date))) {
617 if (!get_client_name(ua, rx)) {
620 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
621 "containing a list of file names with paths, and terminate\n"
622 "them with a blank line.\n"));
624 if (!get_cmd(ua, _("Enter full filename: "))) {
627 len = strlen(ua->cmd);
631 insert_one_file_or_dir(ua, rx, date, false);
635 case 8: /* Find JobIds for current backup */
636 bstrutime(date, sizeof(date), now);
637 if (!select_backups_before_date(ua, rx, date)) {
643 case 9: /* Find JobIds for give date */
644 if (!get_date(ua, date, sizeof(date))) {
647 if (!select_backups_before_date(ua, rx, date)) {
653 case 10: /* Enter directories */
654 if (*rx->JobIds != 0) {
655 ua->send_msg(_("You have already selected the following JobIds: %s\n"),
657 } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
658 if (*rx->JobIds != 0 && *ua->cmd) {
659 pm_strcat(rx->JobIds, ",");
661 pm_strcat(rx->JobIds, ua->cmd);
663 if (*rx->JobIds == 0 || *rx->JobIds == '.') {
664 return 0; /* nothing entered, return */
666 bstrutime(date, sizeof(date), now);
667 if (!get_client_name(ua, rx)) {
670 ua->send_msg(_("Enter full directory names or start the name\n"
671 "with a < to indicate it is a filename containing a list\n"
672 "of directories and terminate them with a blank line.\n"));
674 if (!get_cmd(ua, _("Enter directory name: "))) {
677 len = strlen(ua->cmd);
681 /* Add trailing slash to end of directory names */
682 if (ua->cmd[0] != '<' && !IsPathSeparator(ua->cmd[len-1])) {
683 strcat(ua->cmd, "/");
685 insert_one_file_or_dir(ua, rx, date, true);
689 case 11: /* Cancel or quit */
694 POOLMEM *JobIds = get_pool_memory(PM_FNAME);
698 * Find total number of files to be restored, and filter the JobId
699 * list to contain only ones permitted by the ACL conditions.
701 for (p=rx->JobIds; ; ) {
703 int stat = get_next_jobid_from_list(&p, &JobId);
705 ua->error_msg(_("Invalid JobId in list.\n"));
706 free_pool_memory(JobIds);
712 if (jr.JobId == JobId) {
713 continue; /* duplicate of last JobId */
715 memset(&jr, 0, sizeof(JOB_DBR));
717 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
718 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
719 edit_int64(JobId, ed1), db_strerror(ua->db));
720 free_pool_memory(JobIds);
723 if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
724 ua->error_msg(_("Access to JobId=%s (Job \"%s\") not authorized. Not selected.\n"),
725 edit_int64(JobId, ed1), jr.Name);
729 pm_strcat(JobIds, ",");
731 pm_strcat(JobIds, edit_int64(JobId, ed1));
732 rx->TotalFiles += jr.JobFiles;
734 free_pool_memory(rx->JobIds);
735 rx->JobIds = JobIds; /* Set ACL filtered list */
736 if (*rx->JobIds == 0) {
737 ua->warning_msg(_("No Jobs selected.\n"));
740 if (strchr(rx->JobIds,',')) {
741 ua->info_msg(_("You have selected the following JobIds: %s\n"), rx->JobIds);
743 ua->info_msg(_("You have selected the following JobId: %s\n"), rx->JobIds);
751 static int get_date(UAContext *ua, char *date, int date_len)
753 ua->send_msg(_("The restored files will the most current backup\n"
754 "BEFORE the date you specify below.\n\n"));
756 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
759 if (str_to_utime(ua->cmd) != 0) {
762 ua->error_msg(_("Improper date format.\n"));
764 bstrncpy(date, ua->cmd, date_len);
769 * Insert a single file, or read a list of files from a file
771 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
781 if ((ffd = fopen(p, "rb")) == NULL) {
783 ua->error_msg(_("Cannot open file %s: ERR=%s\n"),
787 while (fgets(file, sizeof(file), ffd)) {
790 if (!insert_dir_into_findex_list(ua, rx, file, date)) {
791 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
794 if (!insert_file_into_findex_list(ua, rx, file, date)) {
795 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
803 insert_table_into_findex_list(ua, rx, p);
807 insert_dir_into_findex_list(ua, rx, ua->cmd, date);
809 insert_file_into_findex_list(ua, rx, ua->cmd, date);
816 * For a given file (path+filename), split into path and file, then
817 * lookup the most recent backup in the catalog to get the JobId
818 * and FileIndex, then insert them into the findex list.
820 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
823 strip_trailing_newline(file);
824 split_path_and_filename(rx, file);
825 if (*rx->JobIds == 0) {
826 Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname,
829 Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
830 rx->path, rx->fname, rx->ClientName);
833 /* Find and insert jobid and File Index */
834 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
835 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
836 rx->query, db_strerror(ua->db));
839 ua->error_msg(_("No database record found for: %s\n"), file);
840 // ua->error_msg("Query=%s\n", rx->query);
847 * For a given path lookup the most recent backup in the catalog
848 * to get the JobId and FileIndexes of all files in that directory.
850 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
853 strip_trailing_junk(dir);
854 if (*rx->JobIds == 0) {
855 ua->error_msg(_("No JobId specified cannot continue.\n"));
858 Mmsg(rx->query, uar_jobid_fileindex_from_dir, rx->JobIds,
859 dir, rx->ClientName);
862 /* Find and insert jobid and File Index */
863 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
864 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
865 rx->query, db_strerror(ua->db));
868 ua->error_msg(_("No database record found for: %s\n"), dir);
875 * Get the JobId and FileIndexes of all files in the specified table
877 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
879 strip_trailing_junk(table);
880 Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
883 /* Find and insert jobid and File Index */
884 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
885 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
886 rx->query, db_strerror(ua->db));
889 ua->error_msg(_("No table found: %s\n"), table);
895 static void split_path_and_filename(RESTORE_CTX *rx, char *name)
899 /* Find path without the filename.
900 * I.e. everything after the last / is a "filename".
901 * OK, maybe it is a directory name, but we treat it like
902 * a filename. If we don't find a / then the whole name
903 * must be a path name (e.g. c:).
905 for (p=f=name; *p; p++) {
906 if (IsPathSeparator(*p)) {
907 f = p; /* set pos of last slash */
910 if (IsPathSeparator(*f)) { /* did we find a slash? */
911 f++; /* yes, point to filename */
912 } else { /* no, whole thing must be path name */
916 /* If filename doesn't exist (i.e. root directory), we
917 * simply create a blank name consisting of a single
918 * space. This makes handling zero length filenames
923 rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
924 memcpy(rx->fname, f, rx->fnl); /* copy filename */
925 rx->fname[rx->fnl] = 0;
933 rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
934 memcpy(rx->path, name, rx->pnl);
935 rx->path[rx->pnl] = 0;
941 Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
944 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
947 JobId_t JobId, last_JobId;
952 memset(&tree, 0, sizeof(TREE_CTX));
954 * Build the directory tree containing JobIds user selected
956 tree.root = new_tree(rx->TotalFiles);
961 * For display purposes, the same JobId, with different volumes may
962 * appear more than once, however, we only insert it once.
966 tree.FileEstimate = 0;
967 if (get_next_jobid_from_list(&p, &JobId) > 0) {
968 /* Use first JobId as estimate of the number of files to restore */
969 Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
970 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
971 ua->error_msg("%s\n", db_strerror(ua->db));
974 /* Add about 25% more than this job for over estimate */
975 tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
976 tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
979 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
982 if (JobId == last_JobId) {
983 continue; /* eliminate duplicate JobIds */
986 ua->info_msg(_("\nBuilding directory tree for JobId %s ... "),
987 edit_int64(JobId, ed1));
990 * Find files for this JobId and insert them in the tree
992 Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
993 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
994 ua->error_msg("%s", db_strerror(ua->db));
997 if (tree.FileCount == 0) {
998 ua->send_msg(_("\nThere were no files inserted into the tree, so file selection\n"
999 "is not possible.Most likely your retention policy pruned the files\n"));
1000 if (!get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
1004 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1005 if (JobId == last_JobId) {
1006 continue; /* eliminate duplicate JobIds */
1008 add_findex_all(rx->bsr, JobId);
1016 ua->info_msg(_("\n1 Job, %s files inserted into the tree and marked for extraction.\n"),
1017 edit_uint64_with_commas(tree.FileCount, ec1));
1020 ua->info_msg(_("\n1 Job, %s files inserted into the tree.\n"),
1021 edit_uint64_with_commas(tree.FileCount, ec1));
1026 ua->info_msg(_("\n%d Jobs, %s files inserted into the tree and marked for extraction.\n"),
1027 items, edit_uint64_with_commas(tree.FileCount, ec1));
1030 ua->info_msg(_("\n%d Jobs, %s files inserted into the tree.\n"),
1031 items, edit_uint64_with_commas(tree.FileCount, ec1));
1035 if (find_arg(ua, NT_("done")) < 0) {
1036 /* Let the user interact in selecting which files to restore */
1037 OK = user_select_files_from_tree(&tree);
1041 * Walk down through the tree finding all files marked to be
1042 * extracted making a bootstrap file.
1045 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
1046 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
1047 if (node->extract || node->extract_dir) {
1048 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
1049 add_findex(rx->bsr, node->JobId, node->FileIndex);
1050 if (node->extract && node->type != TN_NEWDIR) {
1051 rx->selected_files++; /* count only saved files */
1058 free_tree(tree.root); /* free the directory tree */
1064 * This routine is used to get the current backup or a backup
1065 * before the specified date.
1067 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
1072 char fileset_name[MAX_NAME_LENGTH];
1073 char ed1[50], ed2[50];
1074 char pool_select[MAX_NAME_LENGTH];
1078 /* Create temp tables */
1079 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1080 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1081 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
1082 ua->error_msg("%s\n", db_strerror(ua->db));
1084 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
1085 ua->error_msg("%s\n", db_strerror(ua->db));
1088 * Select Client from the Catalog
1090 memset(&cr, 0, sizeof(cr));
1091 if (!get_client_dbr(ua, &cr)) {
1094 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1099 memset(&fsr, 0, sizeof(fsr));
1100 i = find_arg_with_value(ua, "FileSet");
1102 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1103 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1104 ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1105 db_strerror(ua->db));
1109 if (i < 0) { /* fileset not found */
1110 edit_int64(cr.ClientId, ed1);
1111 Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1112 start_prompt(ua, _("The defined FileSet resources are:\n"));
1113 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1114 ua->error_msg("%s\n", db_strerror(ua->db));
1116 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1117 fileset_name, sizeof(fileset_name)) < 0) {
1118 ua->error_msg(_("No FileSet found for client \"%s\".\n"), cr.Name);
1122 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1123 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1124 ua->warning_msg(_("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1125 ua->send_msg(_("This probably means you modified the FileSet.\n"
1126 "Continuing anyway.\n"));
1130 /* If Pool specified, add PoolId specification */
1134 memset(&pr, 0, sizeof(pr));
1135 bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1136 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1137 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
1138 edit_int64(pr.PoolId, ed1));
1140 ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1144 /* Find JobId of last Full backup for this client, fileset */
1145 edit_int64(cr.ClientId, ed1);
1146 Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1148 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1149 ua->error_msg("%s\n", db_strerror(ua->db));
1153 /* Find all Volumes used by that JobId */
1154 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1155 ua->error_msg("%s\n", db_strerror(ua->db));
1158 /* Note, this is needed because I don't seem to get the callback
1159 * from the call just above.
1162 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1163 ua->warning_msg("%s\n", db_strerror(ua->db));
1165 if (rx->JobTDate == 0) {
1166 ua->error_msg(_("No Full backup before %s found.\n"), date);
1170 /* Now find most recent Differental Job after Full save, if any */
1171 Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1172 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1173 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1174 ua->warning_msg("%s\n", db_strerror(ua->db));
1176 /* Now update JobTDate to lock onto Differental, if any */
1178 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1179 ua->warning_msg("%s\n", db_strerror(ua->db));
1181 if (rx->JobTDate == 0) {
1182 ua->error_msg(_("No Full backup before %s found.\n"), date);
1186 /* Now find all Incremental Jobs after Full/dif save */
1187 Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1188 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1189 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1190 ua->warning_msg("%s\n", db_strerror(ua->db));
1193 /* Get the JobIds from that list */
1195 rx->last_jobid[0] = 0;
1196 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1197 ua->warning_msg("%s\n", db_strerror(ua->db));
1200 if (rx->JobIds[0] != 0) {
1201 /* Display a list of Jobs selected for this restore */
1202 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
1205 ua->warning_msg(_("No jobs found.\n"));
1209 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1210 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1216 * Return next JobId from comma separated list
1219 * 1 if next JobId returned
1220 * 0 if no more JobIds are in list
1221 * -1 there is an error
1223 int get_next_jobid_from_list(char **p, JobId_t *JobId)
1229 for (int i=0; i<(int)sizeof(jobid); i++) {
1232 } else if (*q == ',') {
1239 if (jobid[0] == 0) {
1241 } else if (!is_a_number(jobid)) {
1242 return -1; /* error */
1245 *JobId = str_to_int64(jobid);
1249 static int restore_count_handler(void *ctx, int num_fields, char **row)
1251 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1252 rx->JobId = str_to_int64(row[0]);
1258 * Callback handler to get JobId and FileIndex for files
1259 * can insert more than one depending on the caller.
1261 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1263 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1264 rx->JobId = str_to_int64(row[0]);
1265 add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1267 rx->selected_files++;
1272 * Callback handler make list of JobIds
1274 static int jobid_handler(void *ctx, int num_fields, char **row)
1276 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1278 if (strcmp(rx->last_jobid, row[0]) == 0) {
1279 return 0; /* duplicate id */
1281 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1282 if (rx->JobIds[0] != 0) {
1283 pm_strcat(rx->JobIds, ",");
1285 pm_strcat(rx->JobIds, row[0]);
1291 * Callback handler to pickup last Full backup JobTDate
1293 static int last_full_handler(void *ctx, int num_fields, char **row)
1295 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1297 rx->JobTDate = str_to_int64(row[1]);
1302 * Callback handler build FileSet name prompt list
1304 static int fileset_handler(void *ctx, int num_fields, char **row)
1306 /* row[0] = FileSet (name) */
1308 add_prompt((UAContext *)ctx, row[0]);
1314 * Free names in the list
1316 static void free_name_list(NAME_LIST *name_list)
1318 for (int i=0; i < name_list->num_ids; i++) {
1319 free(name_list->name[i]);
1321 if (name_list->name) {
1322 free(name_list->name);
1323 name_list->name = NULL;
1325 name_list->max_ids = 0;
1326 name_list->num_ids = 0;
1329 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType)
1334 Dmsg1(200, "Already have store=%s\n", rx.store->name());
1338 * Try looking up Storage by name
1341 foreach_res(store, R_STORAGE) {
1342 if (strcmp(Storage, store->name()) == 0) {
1343 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1352 /* Check if an explicit storage resource is given */
1354 int i = find_arg_with_value(ua, "storage");
1356 store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1357 if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1361 if (store && (store != rx.store)) {
1362 ua->info_msg(_("Warning default storage overridden by \"%s\" on command line.\n"),
1365 Dmsg1(200, "Set store=%s\n", rx.store->name());
1370 /* If no storage resource, try to find one from MediaType */
1373 foreach_res(store, R_STORAGE) {
1374 if (strcmp(MediaType, store->media_type) == 0) {
1375 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1377 Dmsg1(200, "Set store=%s\n", rx.store->name());
1378 ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1379 Storage, store->name(), MediaType);
1386 ua->warning_msg(_("\nUnable to find Storage resource for\n"
1387 "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1390 /* Take command line arg, or ask user if none */
1391 rx.store = get_storage_resource(ua, false /* don't use default */);
1392 Dmsg1(200, "Set store=%s\n", rx.store->name());