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 and included
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU 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(UAContext *ua, 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_restore_client_name(UAContext *ua, RESTORE_CTX &rx);
70 static int get_date(UAContext *ua, char *date, int date_len);
71 static int restore_count_handler(void *ctx, int num_fields, char **row);
72 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table);
78 int restore_cmd(UAContext *ua, const char *cmd)
80 RESTORE_CTX rx; /* restore context */
84 char *escaped_bsr_name = NULL;
85 char *escaped_where_name = NULL;
86 char *strip_prefix, *add_prefix, *add_suffix, *regexp;
87 strip_prefix = add_prefix = add_suffix = regexp = NULL;
89 memset(&rx, 0, sizeof(rx));
90 rx.path = get_pool_memory(PM_FNAME);
91 rx.fname = get_pool_memory(PM_FNAME);
92 rx.JobIds = get_pool_memory(PM_FNAME);
93 rx.query = get_pool_memory(PM_FNAME);
96 i = find_arg_with_value(ua, "where");
98 rx.where = ua->argv[i];
101 i = find_arg_with_value(ua, "strip_prefix");
103 strip_prefix = ua->argv[i];
106 i = find_arg_with_value(ua, "add_prefix");
108 add_prefix = ua->argv[i];
111 i = find_arg_with_value(ua, "add_suffix");
113 add_suffix = ua->argv[i];
116 i = find_arg_with_value(ua, "regexwhere");
118 rx.RegexWhere = ua->argv[i];
121 if (strip_prefix || add_suffix || add_prefix) {
122 int len = bregexp_get_build_where_size(strip_prefix, add_prefix, add_suffix);
123 regexp = (char *)bmalloc(len * sizeof(char));
125 bregexp_build_where(regexp, len, strip_prefix, add_prefix, add_suffix);
126 rx.RegexWhere = regexp;
129 /* TODO: add acl for regexwhere ? */
132 if (!acl_access_ok(ua, Where_ACL, rx.RegexWhere)) {
133 ua->error_msg(_("\"RegexWhere\" specification not authorized.\n"));
139 if (!acl_access_ok(ua, Where_ACL, rx.where)) {
140 ua->error_msg(_("\"where\" specification not authorized.\n"));
145 if (!open_client_db(ua)) {
149 /* Ensure there is at least one Restore Job */
151 foreach_res(job, R_JOB) {
152 if (job->JobType == JT_RESTORE) {
153 if (!rx.restore_job) {
154 rx.restore_job = job;
160 if (!rx.restore_jobs) {
162 "No Restore Job Resource found in bacula-dir.conf.\n"
163 "You must create at least one before running this command.\n"));
168 * Request user to select JobIds or files by various different methods
169 * last 20 jobs, where File saved, most recent backup, ...
170 * In the end, a list of files are pumped into
173 switch (user_select_jobids_or_files(ua, &rx)) {
176 case 1: /* selected by jobid */
177 if (!build_directory_tree(ua, &rx)) {
178 ua->send_msg(_("Restore not done.\n"));
182 case 2: /* selected by filename, no tree needed */
187 uint32_t selected_files;
189 if (!complete_bsr(ua, rx.bsr)) { /* find Vol, SessId, SessTime from JobIds */
190 ua->error_msg(_("Unable to construct a valid BSR. Cannot continue.\n"));
193 if (!(selected_files = write_bsr_file(ua, rx))) {
194 ua->warning_msg(_("No files selected to be restored.\n"));
197 /* If no count of files, use bsr generated value (often wrong) */
198 if (rx.selected_files == 0) {
199 rx.selected_files = selected_files;
201 if (rx.selected_files==1) {
202 ua->info_msg(_("\n1 file selected to be restored.\n\n"));
205 ua->info_msg(_("\n%s files selected to be restored.\n\n"),
206 edit_uint64_with_commas(rx.selected_files, ed1));
209 ua->warning_msg(_("No files selected to be restored.\n"));
213 if (rx.restore_jobs == 1) {
214 job = rx.restore_job;
216 job = select_restore_job_resource(ua);
222 get_client_name(ua, &rx);
223 if (!rx.ClientName) {
224 ua->error_msg(_("No Client resource found!\n"));
227 get_restore_client_name(ua, rx);
229 escaped_bsr_name = escape_filename(jcr->RestoreBootstrap);
231 /* Build run command */
233 escaped_where_name = escape_filename(rx.RegexWhere);
235 "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
236 " bootstrap=\"%s\" regexwhere=\"%s\" files=%u catalog=\"%s\"",
237 job->name(), rx.ClientName, rx.RestoreClientName,
238 rx.store?rx.store->name():"",
239 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
240 escaped_where_name ? escaped_where_name : rx.RegexWhere,
241 rx.selected_files, ua->catalog->name());
243 } else if (rx.where) {
244 escaped_where_name = escape_filename(rx.where);
246 "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
247 " bootstrap=\"%s\" where=\"%s\" files=%u catalog=\"%s\"",
248 job->name(), rx.ClientName, rx.RestoreClientName,
249 rx.store?rx.store->name():"",
250 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
251 escaped_where_name ? escaped_where_name : rx.where,
252 rx.selected_files, ua->catalog->name());
256 "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
257 " bootstrap=\"%s\" files=%u catalog=\"%s\"",
258 job->name(), rx.ClientName, rx.RestoreClientName,
259 rx.store?rx.store->name():"",
260 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
261 rx.selected_files, ua->catalog->name());
264 if (escaped_bsr_name != NULL) {
265 bfree(escaped_bsr_name);
268 if (escaped_where_name != NULL) {
269 bfree(escaped_where_name);
276 if (find_arg(ua, NT_("yes")) > 0) {
277 pm_strcat(ua->cmd, " yes"); /* pass it on to the run command */
279 Dmsg1(200, "Submitting: %s\n", ua->cmd);
281 run_cmd(ua, ua->cmd);
286 if (escaped_bsr_name != NULL) {
287 bfree(escaped_bsr_name);
290 if (escaped_where_name != NULL) {
291 bfree(escaped_where_name);
303 static void free_rx(RESTORE_CTX *rx)
308 free_pool_memory(rx->JobIds);
312 free_pool_memory(rx->fname);
316 free_pool_memory(rx->path);
320 free_pool_memory(rx->query);
323 free_name_list(&rx->name_list);
326 static bool has_value(UAContext *ua, int i)
329 ua->error_msg(_("Missing value for keyword: %s\n"), ua->argk[i]);
336 * This gets the client name from which the backup was made
338 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
340 /* If no client name specified yet, get it now */
341 if (!rx->ClientName[0]) {
343 /* try command line argument */
344 int i = find_arg_with_value(ua, NT_("client"));
346 i = find_arg_with_value(ua, NT_("backupclient"));
349 if (!has_value(ua, i)) {
352 bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
355 memset(&cr, 0, sizeof(cr));
356 if (!get_client_dbr(ua, &cr)) {
359 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
365 * This is where we pick up a client name to restore to.
367 static int get_restore_client_name(UAContext *ua, RESTORE_CTX &rx)
369 /* Start with same name as backup client */
370 bstrncpy(rx.RestoreClientName, rx.ClientName, sizeof(rx.RestoreClientName));
372 /* try command line argument */
373 int i = find_arg_with_value(ua, NT_("restoreclient"));
375 if (!has_value(ua, i)) {
378 bstrncpy(rx.RestoreClientName, ua->argv[i], sizeof(rx.RestoreClientName));
387 * The first step in the restore process is for the user to
388 * select a list of JobIds from which he will subsequently
389 * select which files are to be restored.
391 * Returns: 2 if filename list made
392 * 1 if jobid list made
395 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
398 char date[MAX_TIME_LENGTH];
399 bool have_date = false;
400 /* Include current second if using current time */
401 utime_t now = time(NULL) + 1;
403 JOB_DBR jr = { (JobId_t)-1 };
406 const char *list[] = {
407 _("List last 20 Jobs run"),
408 _("List Jobs where a given File is saved"),
409 _("Enter list of comma separated JobIds to select"),
410 _("Enter SQL list command"),
411 _("Select the most recent backup for a client"),
412 _("Select backup for a client before a specified time"),
413 _("Enter a list of files to restore"),
414 _("Enter a list of files to restore before a specified time"),
415 _("Find the JobIds of the most recent backup for a client"),
416 _("Find the JobIds for a backup for a client before a specified time"),
417 _("Enter a list of directories to restore for found JobIds"),
422 /* These keywords are handled in a for loop */
432 /* The keyword below are handled by individual arg lookups */
438 "bootstrap", /* 13 */
440 "strip_prefix", /* 15 */
441 "add_prefix", /* 16 */
442 "add_suffix", /* 17 */
443 "regexwhere", /* 18 */
444 "restoreclient", /* 19 */
450 for (i=1; i<ua->argc; i++) { /* loop through arguments */
451 bool found_kw = false;
452 for (j=0; kw[j]; j++) { /* loop through keywords */
453 if (strcasecmp(kw[j], ua->argk[i]) == 0) {
459 ua->error_msg(_("Unknown keyword: %s\n"), ua->argk[i]);
462 /* Found keyword in kw[] list, process it */
465 if (!has_value(ua, i)) {
468 if (*rx->JobIds != 0) {
469 pm_strcat(rx->JobIds, ",");
471 pm_strcat(rx->JobIds, ua->argv[i]);
474 case 1: /* current */
476 * Note, we add one second here just to include any job
477 * that may have finished within the current second,
478 * which happens a lot in scripting small jobs.
480 bstrutime(date, sizeof(date), now);
484 if (have_date || !has_value(ua, i)) {
487 if (str_to_utime(ua->argv[i]) == 0) {
488 ua->error_msg(_("Improper date format: %s\n"), ua->argv[i]);
491 bstrncpy(date, ua->argv[i], sizeof(date));
496 if (!has_value(ua, i)) {
500 bstrutime(date, sizeof(date), now);
502 if (!get_client_name(ua, rx)) {
505 pm_strcpy(ua->cmd, ua->argv[i]);
506 insert_one_file_or_dir(ua, rx, date, j==4);
510 bstrutime(date, sizeof(date), now);
512 if (!select_backups_before_date(ua, rx, date)) {
517 case 6: /* pool specified */
518 if (!has_value(ua, i)) {
521 rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
523 ua->error_msg(_("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
526 if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
528 ua->error_msg(_("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
532 case 7: /* all specified */
536 * All keywords 7 or greater are ignored or handled by a select prompt
544 ua->send_msg(_("\nFirst you select one or more JobIds that contain files\n"
545 "to be restored. You will be presented several methods\n"
546 "of specifying the JobIds. Then you will be allowed to\n"
547 "select which files from those JobIds are to be restored.\n\n"));
550 /* If choice not already made above, prompt */
556 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
557 for (int i=0; list[i]; i++) {
558 add_prompt(ua, list[i]);
561 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
562 case -1: /* error or cancel */
564 case 0: /* list last 20 Jobs run */
565 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
566 ua->error_msg(_("SQL query not authorized.\n"));
569 gui_save = ua->jcr->gui;
571 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
572 ua->jcr->gui = gui_save;
575 case 1: /* list where a file is saved */
576 if (!get_client_name(ua, rx)) {
579 if (!get_cmd(ua, _("Enter Filename (no path):"))) {
582 len = strlen(ua->cmd);
583 fname = (char *)malloc(len * 2 + 1);
584 db_escape_string(ua->jcr, ua->db, fname, ua->cmd, len);
585 Mmsg(rx->query, uar_file, rx->ClientName, fname);
587 gui_save = ua->jcr->gui;
589 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
590 ua->jcr->gui = gui_save;
593 case 2: /* enter a list of JobIds */
594 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
597 pm_strcpy(rx->JobIds, ua->cmd);
599 case 3: /* Enter an SQL list command */
600 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
601 ua->error_msg(_("SQL query not authorized.\n"));
604 if (!get_cmd(ua, _("Enter SQL list command: "))) {
607 gui_save = ua->jcr->gui;
609 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
610 ua->jcr->gui = gui_save;
613 case 4: /* Select the most recent backups */
615 bstrutime(date, sizeof(date), now);
617 if (!select_backups_before_date(ua, rx, date)) {
621 case 5: /* select backup at specified time */
623 if (!get_date(ua, date, sizeof(date))) {
627 if (!select_backups_before_date(ua, rx, date)) {
631 case 6: /* Enter files */
633 bstrutime(date, sizeof(date), now);
635 if (!get_client_name(ua, rx)) {
638 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
639 "containing a list of file names with paths, and terminate\n"
640 "them with a blank line.\n"));
642 if (!get_cmd(ua, _("Enter full filename: "))) {
645 len = strlen(ua->cmd);
649 insert_one_file_or_dir(ua, rx, date, false);
652 case 7: /* enter files backed up before specified time */
654 if (!get_date(ua, date, sizeof(date))) {
658 if (!get_client_name(ua, rx)) {
661 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
662 "containing a list of file names with paths, and terminate\n"
663 "them with a blank line.\n"));
665 if (!get_cmd(ua, _("Enter full filename: "))) {
668 len = strlen(ua->cmd);
672 insert_one_file_or_dir(ua, rx, date, false);
676 case 8: /* Find JobIds for current backup */
678 bstrutime(date, sizeof(date), now);
680 if (!select_backups_before_date(ua, rx, date)) {
686 case 9: /* Find JobIds for give date */
688 if (!get_date(ua, date, sizeof(date))) {
692 if (!select_backups_before_date(ua, rx, date)) {
698 case 10: /* Enter directories */
699 if (*rx->JobIds != 0) {
700 ua->send_msg(_("You have already selected the following JobIds: %s\n"),
702 } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
703 if (*rx->JobIds != 0 && *ua->cmd) {
704 pm_strcat(rx->JobIds, ",");
706 pm_strcat(rx->JobIds, ua->cmd);
708 if (*rx->JobIds == 0 || *rx->JobIds == '.') {
709 return 0; /* nothing entered, return */
712 bstrutime(date, sizeof(date), now);
714 if (!get_client_name(ua, rx)) {
717 ua->send_msg(_("Enter full directory names or start the name\n"
718 "with a < to indicate it is a filename containing a list\n"
719 "of directories and terminate them with a blank line.\n"));
721 if (!get_cmd(ua, _("Enter directory name: "))) {
724 len = strlen(ua->cmd);
728 /* Add trailing slash to end of directory names */
729 if (ua->cmd[0] != '<' && !IsPathSeparator(ua->cmd[len-1])) {
730 strcat(ua->cmd, "/");
732 insert_one_file_or_dir(ua, rx, date, true);
736 case 11: /* Cancel or quit */
741 POOLMEM *JobIds = get_pool_memory(PM_FNAME);
745 * Find total number of files to be restored, and filter the JobId
746 * list to contain only ones permitted by the ACL conditions.
748 for (p=rx->JobIds; ; ) {
750 int stat = get_next_jobid_from_list(&p, &JobId);
752 ua->error_msg(_("Invalid JobId in list.\n"));
753 free_pool_memory(JobIds);
759 if (jr.JobId == JobId) {
760 continue; /* duplicate of last JobId */
762 memset(&jr, 0, sizeof(JOB_DBR));
764 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
765 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
766 edit_int64(JobId, ed1), db_strerror(ua->db));
767 free_pool_memory(JobIds);
770 if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
771 ua->error_msg(_("Access to JobId=%s (Job \"%s\") not authorized. Not selected.\n"),
772 edit_int64(JobId, ed1), jr.Name);
776 pm_strcat(JobIds, ",");
778 pm_strcat(JobIds, edit_int64(JobId, ed1));
779 rx->TotalFiles += jr.JobFiles;
781 free_pool_memory(rx->JobIds);
782 rx->JobIds = JobIds; /* Set ACL filtered list */
783 if (*rx->JobIds == 0) {
784 ua->warning_msg(_("No Jobs selected.\n"));
787 if (strchr(rx->JobIds,',')) {
788 ua->info_msg(_("You have selected the following JobIds: %s\n"), rx->JobIds);
790 ua->info_msg(_("You have selected the following JobId: %s\n"), rx->JobIds);
798 static int get_date(UAContext *ua, char *date, int date_len)
800 ua->send_msg(_("The restored files will the most current backup\n"
801 "BEFORE the date you specify below.\n\n"));
803 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
806 if (str_to_utime(ua->cmd) != 0) {
809 ua->error_msg(_("Improper date format.\n"));
811 bstrncpy(date, ua->cmd, date_len);
816 * Insert a single file, or read a list of files from a file
818 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
828 if ((ffd = fopen(p, "rb")) == NULL) {
830 ua->error_msg(_("Cannot open file %s: ERR=%s\n"),
834 while (fgets(file, sizeof(file), ffd)) {
837 if (!insert_dir_into_findex_list(ua, rx, file, date)) {
838 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
841 if (!insert_file_into_findex_list(ua, rx, file, date)) {
842 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
850 insert_table_into_findex_list(ua, rx, p);
854 insert_dir_into_findex_list(ua, rx, ua->cmd, date);
856 insert_file_into_findex_list(ua, rx, ua->cmd, date);
863 * For a given file (path+filename), split into path and file, then
864 * lookup the most recent backup in the catalog to get the JobId
865 * and FileIndex, then insert them into the findex list.
867 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
870 strip_trailing_newline(file);
871 split_path_and_filename(ua, rx, file);
872 if (*rx->JobIds == 0) {
873 Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname,
876 Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
877 rx->path, rx->fname, rx->ClientName);
880 /* Find and insert jobid and File Index */
881 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
882 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
883 rx->query, db_strerror(ua->db));
886 ua->error_msg(_("No database record found for: %s\n"), file);
887 // ua->error_msg("Query=%s\n", rx->query);
894 * For a given path lookup the most recent backup in the catalog
895 * to get the JobId and FileIndexes of all files in that directory.
897 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
900 strip_trailing_junk(dir);
901 if (*rx->JobIds == 0) {
902 ua->error_msg(_("No JobId specified cannot continue.\n"));
905 Mmsg(rx->query, uar_jobid_fileindex_from_dir, rx->JobIds,
906 dir, rx->ClientName);
909 /* Find and insert jobid and File Index */
910 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
911 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
912 rx->query, db_strerror(ua->db));
915 ua->error_msg(_("No database record found for: %s\n"), dir);
922 * Get the JobId and FileIndexes of all files in the specified table
924 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
926 strip_trailing_junk(table);
927 Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
930 /* Find and insert jobid and File Index */
931 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
932 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
933 rx->query, db_strerror(ua->db));
936 ua->error_msg(_("No table found: %s\n"), table);
942 static void split_path_and_filename(UAContext *ua, RESTORE_CTX *rx, char *name)
946 /* Find path without the filename.
947 * I.e. everything after the last / is a "filename".
948 * OK, maybe it is a directory name, but we treat it like
949 * a filename. If we don't find a / then the whole name
950 * must be a path name (e.g. c:).
952 for (p=f=name; *p; p++) {
953 if (IsPathSeparator(*p)) {
954 f = p; /* set pos of last slash */
957 if (IsPathSeparator(*f)) { /* did we find a slash? */
958 f++; /* yes, point to filename */
959 } else { /* no, whole thing must be path name */
963 /* If filename doesn't exist (i.e. root directory), we
964 * simply create a blank name consisting of a single
965 * space. This makes handling zero length filenames
970 rx->fname = check_pool_memory_size(rx->fname, 2*(rx->fnl)+1);
971 db_escape_string(ua->jcr, ua->db, rx->fname, f, rx->fnl);
979 rx->path = check_pool_memory_size(rx->path, 2*(rx->pnl)+1);
980 db_escape_string(ua->jcr, ua->db, rx->path, name, rx->pnl);
986 Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
989 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
992 JobId_t JobId, last_JobId;
997 memset(&tree, 0, sizeof(TREE_CTX));
999 * Build the directory tree containing JobIds user selected
1001 tree.root = new_tree(rx->TotalFiles);
1006 * For display purposes, the same JobId, with different volumes may
1007 * appear more than once, however, we only insert it once.
1011 tree.FileEstimate = 0;
1012 if (get_next_jobid_from_list(&p, &JobId) > 0) {
1013 /* Use first JobId as estimate of the number of files to restore */
1014 Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
1015 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1016 ua->error_msg("%s\n", db_strerror(ua->db));
1019 /* Add about 25% more than this job for over estimate */
1020 tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
1021 tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
1024 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1027 if (JobId == last_JobId) {
1028 continue; /* eliminate duplicate JobIds */
1031 ua->info_msg(_("\nBuilding directory tree for JobId %s ... "),
1032 edit_int64(JobId, ed1));
1035 * Find files for this JobId and insert them in the tree
1037 Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
1038 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
1039 ua->error_msg("%s", db_strerror(ua->db));
1042 if (tree.FileCount == 0) {
1043 ua->send_msg(_("\nThere were no files inserted into the tree, so file selection\n"
1044 "is not possible.Most likely your retention policy pruned the files\n"));
1045 if (!get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
1049 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1050 if (JobId == last_JobId) {
1051 continue; /* eliminate duplicate JobIds */
1053 add_findex_all(rx->bsr, JobId);
1061 ua->info_msg(_("\n1 Job, %s files inserted into the tree and marked for extraction.\n"),
1062 edit_uint64_with_commas(tree.FileCount, ec1));
1065 ua->info_msg(_("\n1 Job, %s files inserted into the tree.\n"),
1066 edit_uint64_with_commas(tree.FileCount, ec1));
1071 ua->info_msg(_("\n%d Jobs, %s files inserted into the tree and marked for extraction.\n"),
1072 items, edit_uint64_with_commas(tree.FileCount, ec1));
1075 ua->info_msg(_("\n%d Jobs, %s files inserted into the tree.\n"),
1076 items, edit_uint64_with_commas(tree.FileCount, ec1));
1080 if (find_arg(ua, NT_("done")) < 0) {
1081 /* Let the user interact in selecting which files to restore */
1082 OK = user_select_files_from_tree(&tree);
1086 * Walk down through the tree finding all files marked to be
1087 * extracted making a bootstrap file.
1090 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
1091 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
1092 if (node->extract || node->extract_dir) {
1093 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
1094 add_findex(rx->bsr, node->JobId, node->FileIndex);
1095 if (node->extract && node->type != TN_NEWDIR) {
1096 rx->selected_files++; /* count only saved files */
1103 free_tree(tree.root); /* free the directory tree */
1109 * This routine is used to get the current backup or a backup
1110 * before the specified date.
1112 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
1117 char fileset_name[MAX_NAME_LENGTH];
1118 char ed1[50], ed2[50];
1119 char pool_select[MAX_NAME_LENGTH];
1122 /* Create temp tables */
1123 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1124 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1125 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
1126 ua->error_msg("%s\n", db_strerror(ua->db));
1128 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
1129 ua->error_msg("%s\n", db_strerror(ua->db));
1132 * Select Client from the Catalog
1134 memset(&cr, 0, sizeof(cr));
1135 if (!get_client_dbr(ua, &cr)) {
1138 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1143 memset(&fsr, 0, sizeof(fsr));
1144 i = find_arg_with_value(ua, "FileSet");
1146 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1147 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1148 ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1149 db_strerror(ua->db));
1153 if (i < 0) { /* fileset not found */
1154 edit_int64(cr.ClientId, ed1);
1155 Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1156 start_prompt(ua, _("The defined FileSet resources are:\n"));
1157 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1158 ua->error_msg("%s\n", db_strerror(ua->db));
1160 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1161 fileset_name, sizeof(fileset_name)) < 0) {
1162 ua->error_msg(_("No FileSet found for client \"%s\".\n"), cr.Name);
1166 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1167 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1168 ua->warning_msg(_("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1169 ua->send_msg(_("This probably means you modified the FileSet.\n"
1170 "Continuing anyway.\n"));
1174 /* If Pool specified, add PoolId specification */
1178 memset(&pr, 0, sizeof(pr));
1179 bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1180 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1181 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
1182 edit_int64(pr.PoolId, ed1));
1184 ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1188 /* Find JobId of last Full backup for this client, fileset */
1189 edit_int64(cr.ClientId, ed1);
1190 Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1192 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1193 ua->error_msg("%s\n", db_strerror(ua->db));
1197 /* Find all Volumes used by that JobId */
1198 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1199 ua->error_msg("%s\n", db_strerror(ua->db));
1203 /* Note, this is needed because I don't seem to get the callback
1204 * from the call just above.
1207 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1208 ua->warning_msg("%s\n", db_strerror(ua->db));
1210 if (rx->JobTDate == 0) {
1211 ua->error_msg(_("No Full backup before %s found.\n"), date);
1215 /* Now find most recent Differental Job after Full save, if any */
1216 Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1217 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1218 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1219 ua->warning_msg("%s\n", db_strerror(ua->db));
1221 /* Now update JobTDate to lock onto Differental, if any */
1223 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1224 ua->warning_msg("%s\n", db_strerror(ua->db));
1226 if (rx->JobTDate == 0) {
1227 ua->error_msg(_("No Full backup before %s found.\n"), date);
1231 /* Now find all Incremental Jobs after Full/dif save */
1232 Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1233 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1234 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1235 ua->warning_msg("%s\n", db_strerror(ua->db));
1238 /* Get the JobIds from that list */
1240 rx->last_jobid[0] = 0;
1241 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1242 ua->warning_msg("%s\n", db_strerror(ua->db));
1245 if (rx->JobIds[0] != 0) {
1246 /* Display a list of Jobs selected for this restore */
1247 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
1250 ua->warning_msg(_("No jobs found.\n"));
1254 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1255 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1261 * Return next JobId from comma separated list
1264 * 1 if next JobId returned
1265 * 0 if no more JobIds are in list
1266 * -1 there is an error
1268 int get_next_jobid_from_list(char **p, JobId_t *JobId)
1274 for (int i=0; i<(int)sizeof(jobid); i++) {
1277 } else if (*q == ',') {
1284 if (jobid[0] == 0) {
1286 } else if (!is_a_number(jobid)) {
1287 return -1; /* error */
1290 *JobId = str_to_int64(jobid);
1294 static int restore_count_handler(void *ctx, int num_fields, char **row)
1296 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1297 rx->JobId = str_to_int64(row[0]);
1303 * Callback handler to get JobId and FileIndex for files
1304 * can insert more than one depending on the caller.
1306 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1308 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1310 Dmsg2(200, "JobId=%s FileIndex=%s\n", row[0], row[1]);
1311 rx->JobId = str_to_int64(row[0]);
1312 add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1314 rx->selected_files++;
1319 * Callback handler make list of JobIds
1321 static int jobid_handler(void *ctx, int num_fields, char **row)
1323 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1325 if (strcmp(rx->last_jobid, row[0]) == 0) {
1326 return 0; /* duplicate id */
1328 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1329 if (rx->JobIds[0] != 0) {
1330 pm_strcat(rx->JobIds, ",");
1332 pm_strcat(rx->JobIds, row[0]);
1338 * Callback handler to pickup last Full backup JobTDate
1340 static int last_full_handler(void *ctx, int num_fields, char **row)
1342 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1344 rx->JobTDate = str_to_int64(row[1]);
1349 * Callback handler build FileSet name prompt list
1351 static int fileset_handler(void *ctx, int num_fields, char **row)
1353 /* row[0] = FileSet (name) */
1355 add_prompt((UAContext *)ctx, row[0]);
1361 * Free names in the list
1363 static void free_name_list(NAME_LIST *name_list)
1365 for (int i=0; i < name_list->num_ids; i++) {
1366 free(name_list->name[i]);
1368 if (name_list->name) {
1369 free(name_list->name);
1370 name_list->name = NULL;
1372 name_list->max_ids = 0;
1373 name_list->num_ids = 0;
1376 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType)
1381 Dmsg1(200, "Already have store=%s\n", rx.store->name());
1385 * Try looking up Storage by name
1388 foreach_res(store, R_STORAGE) {
1389 if (strcmp(Storage, store->name()) == 0) {
1390 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1399 /* Check if an explicit storage resource is given */
1401 int i = find_arg_with_value(ua, "storage");
1403 store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1404 if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1408 if (store && (store != rx.store)) {
1409 ua->info_msg(_("Warning default storage overridden by \"%s\" on command line.\n"),
1412 Dmsg1(200, "Set store=%s\n", rx.store->name());
1417 /* If no storage resource, try to find one from MediaType */
1420 foreach_res(store, R_STORAGE) {
1421 if (strcmp(MediaType, store->media_type) == 0) {
1422 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1424 Dmsg1(200, "Set store=%s\n", rx.store->name());
1425 ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1426 Storage, store->name(), MediaType);
1433 ua->warning_msg(_("\nUnable to find Storage resource for\n"
1434 "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1437 /* Take command line arg, or ask user if none */
1438 rx.store = get_storage_resource(ua, false /* don't use default */);
1439 Dmsg1(200, "Set store=%s\n", rx.store->name());