2 Bacula® - The Network Backup Solution
4 Copyright (C) 2002-2008 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 Kern Sibbald.
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 display_bsr_info(ua, rx); /* display vols needed, etc */
199 /* If no count of files, use bsr generated value (often wrong) */
200 if (rx.selected_files == 0) {
201 rx.selected_files = selected_files;
203 if (rx.selected_files==1) {
204 ua->info_msg(_("\n1 file selected to be restored.\n\n"));
207 ua->info_msg(_("\n%s files selected to be restored.\n\n"),
208 edit_uint64_with_commas(rx.selected_files, ed1));
211 ua->warning_msg(_("No files selected to be restored.\n"));
215 if (rx.restore_jobs == 1) {
216 job = rx.restore_job;
218 job = select_restore_job_resource(ua);
224 get_client_name(ua, &rx);
225 if (!rx.ClientName) {
226 ua->error_msg(_("No Client resource found!\n"));
229 get_restore_client_name(ua, rx);
231 escaped_bsr_name = escape_filename(jcr->RestoreBootstrap);
233 /* Build run command */
235 escaped_where_name = escape_filename(rx.RegexWhere);
237 "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
238 " bootstrap=\"%s\" regexwhere=\"%s\" files=%u catalog=\"%s\"",
239 job->name(), rx.ClientName, rx.RestoreClientName,
240 rx.store?rx.store->name():"",
241 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
242 escaped_where_name ? escaped_where_name : rx.RegexWhere,
243 rx.selected_files, ua->catalog->name());
245 } else if (rx.where) {
246 escaped_where_name = escape_filename(rx.where);
248 "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
249 " bootstrap=\"%s\" where=\"%s\" files=%u catalog=\"%s\"",
250 job->name(), rx.ClientName, rx.RestoreClientName,
251 rx.store?rx.store->name():"",
252 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
253 escaped_where_name ? escaped_where_name : rx.where,
254 rx.selected_files, ua->catalog->name());
258 "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
259 " bootstrap=\"%s\" files=%u catalog=\"%s\"",
260 job->name(), rx.ClientName, rx.RestoreClientName,
261 rx.store?rx.store->name():"",
262 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
263 rx.selected_files, ua->catalog->name());
266 if (escaped_bsr_name != NULL) {
267 bfree(escaped_bsr_name);
270 if (escaped_where_name != NULL) {
271 bfree(escaped_where_name);
278 if (find_arg(ua, NT_("yes")) > 0) {
279 pm_strcat(ua->cmd, " yes"); /* pass it on to the run command */
281 Dmsg1(200, "Submitting: %s\n", ua->cmd);
283 run_cmd(ua, ua->cmd);
288 if (escaped_bsr_name != NULL) {
289 bfree(escaped_bsr_name);
292 if (escaped_where_name != NULL) {
293 bfree(escaped_where_name);
305 static void free_rx(RESTORE_CTX *rx)
310 free_pool_memory(rx->JobIds);
314 free_pool_memory(rx->fname);
318 free_pool_memory(rx->path);
322 free_pool_memory(rx->query);
325 free_name_list(&rx->name_list);
328 static bool has_value(UAContext *ua, int i)
331 ua->error_msg(_("Missing value for keyword: %s\n"), ua->argk[i]);
338 * This gets the client name from which the backup was made
340 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
342 /* If no client name specified yet, get it now */
343 if (!rx->ClientName[0]) {
345 /* try command line argument */
346 int i = find_arg_with_value(ua, NT_("client"));
348 i = find_arg_with_value(ua, NT_("backupclient"));
351 if (!has_value(ua, i)) {
354 bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
357 memset(&cr, 0, sizeof(cr));
358 if (!get_client_dbr(ua, &cr)) {
361 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
367 * This is where we pick up a client name to restore to.
369 static int get_restore_client_name(UAContext *ua, RESTORE_CTX &rx)
371 /* Start with same name as backup client */
372 bstrncpy(rx.RestoreClientName, rx.ClientName, sizeof(rx.RestoreClientName));
374 /* try command line argument */
375 int i = find_arg_with_value(ua, NT_("restoreclient"));
377 if (!has_value(ua, i)) {
380 bstrncpy(rx.RestoreClientName, ua->argv[i], sizeof(rx.RestoreClientName));
389 * The first step in the restore process is for the user to
390 * select a list of JobIds from which he will subsequently
391 * select which files are to be restored.
393 * Returns: 2 if filename list made
394 * 1 if jobid list made
397 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
400 char date[MAX_TIME_LENGTH];
401 bool have_date = false;
402 /* Include current second if using current time */
403 utime_t now = time(NULL) + 1;
405 JOB_DBR jr = { (JobId_t)-1 };
408 const char *list[] = {
409 _("List last 20 Jobs run"),
410 _("List Jobs where a given File is saved"),
411 _("Enter list of comma separated JobIds to select"),
412 _("Enter SQL list command"),
413 _("Select the most recent backup for a client"),
414 _("Select backup for a client before a specified time"),
415 _("Enter a list of files to restore"),
416 _("Enter a list of files to restore before a specified time"),
417 _("Find the JobIds of the most recent backup for a client"),
418 _("Find the JobIds for a backup for a client before a specified time"),
419 _("Enter a list of directories to restore for found JobIds"),
424 /* These keywords are handled in a for loop */
434 /* The keyword below are handled by individual arg lookups */
440 "bootstrap", /* 13 */
442 "strip_prefix", /* 15 */
443 "add_prefix", /* 16 */
444 "add_suffix", /* 17 */
445 "regexwhere", /* 18 */
446 "restoreclient", /* 19 */
453 for (i=1; i<ua->argc; i++) { /* loop through arguments */
454 bool found_kw = false;
455 for (j=0; kw[j]; j++) { /* loop through keywords */
456 if (strcasecmp(kw[j], ua->argk[i]) == 0) {
462 ua->error_msg(_("Unknown keyword: %s\n"), ua->argk[i]);
465 /* Found keyword in kw[] list, process it */
468 if (!has_value(ua, i)) {
471 if (*rx->JobIds != 0) {
472 pm_strcat(rx->JobIds, ",");
474 pm_strcat(rx->JobIds, ua->argv[i]);
477 case 1: /* current */
479 * Note, we add one second here just to include any job
480 * that may have finished within the current second,
481 * which happens a lot in scripting small jobs.
483 bstrutime(date, sizeof(date), now);
487 if (have_date || !has_value(ua, i)) {
490 if (str_to_utime(ua->argv[i]) == 0) {
491 ua->error_msg(_("Improper date format: %s\n"), ua->argv[i]);
494 bstrncpy(date, ua->argv[i], sizeof(date));
499 if (!has_value(ua, i)) {
503 bstrutime(date, sizeof(date), now);
505 if (!get_client_name(ua, rx)) {
508 pm_strcpy(ua->cmd, ua->argv[i]);
509 insert_one_file_or_dir(ua, rx, date, j==4);
513 bstrutime(date, sizeof(date), now);
515 if (!select_backups_before_date(ua, rx, date)) {
520 case 6: /* pool specified */
521 if (!has_value(ua, i)) {
524 rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
526 ua->error_msg(_("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
529 if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
531 ua->error_msg(_("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
535 case 7: /* all specified */
539 * All keywords 7 or greater are ignored or handled by a select prompt
547 ua->send_msg(_("\nFirst you select one or more JobIds that contain files\n"
548 "to be restored. You will be presented several methods\n"
549 "of specifying the JobIds. Then you will be allowed to\n"
550 "select which files from those JobIds are to be restored.\n\n"));
553 /* If choice not already made above, prompt */
559 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
560 for (int i=0; list[i]; i++) {
561 add_prompt(ua, list[i]);
564 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
565 case -1: /* error or cancel */
567 case 0: /* list last 20 Jobs run */
568 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
569 ua->error_msg(_("SQL query not authorized.\n"));
572 gui_save = ua->jcr->gui;
574 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
575 ua->jcr->gui = gui_save;
578 case 1: /* list where a file is saved */
579 if (!get_client_name(ua, rx)) {
582 if (!get_cmd(ua, _("Enter Filename (no path):"))) {
585 len = strlen(ua->cmd);
586 fname = (char *)malloc(len * 2 + 1);
587 db_escape_string(ua->jcr, ua->db, fname, ua->cmd, len);
588 Mmsg(rx->query, uar_file[db_type], rx->ClientName, fname);
590 gui_save = ua->jcr->gui;
592 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
593 ua->jcr->gui = gui_save;
596 case 2: /* enter a list of JobIds */
597 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
600 pm_strcpy(rx->JobIds, ua->cmd);
602 case 3: /* Enter an SQL list command */
603 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
604 ua->error_msg(_("SQL query not authorized.\n"));
607 if (!get_cmd(ua, _("Enter SQL list command: "))) {
610 gui_save = ua->jcr->gui;
612 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
613 ua->jcr->gui = gui_save;
616 case 4: /* Select the most recent backups */
618 bstrutime(date, sizeof(date), now);
620 if (!select_backups_before_date(ua, rx, date)) {
624 case 5: /* select backup at specified time */
626 if (!get_date(ua, date, sizeof(date))) {
630 if (!select_backups_before_date(ua, rx, date)) {
634 case 6: /* Enter files */
636 bstrutime(date, sizeof(date), now);
638 if (!get_client_name(ua, rx)) {
641 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
642 "containing a list of file names with paths, and terminate\n"
643 "them with a blank line.\n"));
645 if (!get_cmd(ua, _("Enter full filename: "))) {
648 len = strlen(ua->cmd);
652 insert_one_file_or_dir(ua, rx, date, false);
655 case 7: /* enter files backed up before specified time */
657 if (!get_date(ua, date, sizeof(date))) {
661 if (!get_client_name(ua, rx)) {
664 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
665 "containing a list of file names with paths, and terminate\n"
666 "them with a blank line.\n"));
668 if (!get_cmd(ua, _("Enter full filename: "))) {
671 len = strlen(ua->cmd);
675 insert_one_file_or_dir(ua, rx, date, false);
679 case 8: /* Find JobIds for current backup */
681 bstrutime(date, sizeof(date), now);
683 if (!select_backups_before_date(ua, rx, date)) {
689 case 9: /* Find JobIds for give date */
691 if (!get_date(ua, date, sizeof(date))) {
695 if (!select_backups_before_date(ua, rx, date)) {
701 case 10: /* Enter directories */
702 if (*rx->JobIds != 0) {
703 ua->send_msg(_("You have already selected the following JobIds: %s\n"),
705 } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
706 if (*rx->JobIds != 0 && *ua->cmd) {
707 pm_strcat(rx->JobIds, ",");
709 pm_strcat(rx->JobIds, ua->cmd);
711 if (*rx->JobIds == 0 || *rx->JobIds == '.') {
712 return 0; /* nothing entered, return */
715 bstrutime(date, sizeof(date), now);
717 if (!get_client_name(ua, rx)) {
720 ua->send_msg(_("Enter full directory names or start the name\n"
721 "with a < to indicate it is a filename containing a list\n"
722 "of directories and terminate them with a blank line.\n"));
724 if (!get_cmd(ua, _("Enter directory name: "))) {
727 len = strlen(ua->cmd);
731 /* Add trailing slash to end of directory names */
732 if (ua->cmd[0] != '<' && !IsPathSeparator(ua->cmd[len-1])) {
733 strcat(ua->cmd, "/");
735 insert_one_file_or_dir(ua, rx, date, true);
739 case 11: /* Cancel or quit */
744 POOLMEM *JobIds = get_pool_memory(PM_FNAME);
748 * Find total number of files to be restored, and filter the JobId
749 * list to contain only ones permitted by the ACL conditions.
751 for (p=rx->JobIds; ; ) {
753 int stat = get_next_jobid_from_list(&p, &JobId);
755 ua->error_msg(_("Invalid JobId in list.\n"));
756 free_pool_memory(JobIds);
762 if (jr.JobId == JobId) {
763 continue; /* duplicate of last JobId */
765 memset(&jr, 0, sizeof(JOB_DBR));
767 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
768 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
769 edit_int64(JobId, ed1), db_strerror(ua->db));
770 free_pool_memory(JobIds);
773 if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
774 ua->error_msg(_("Access to JobId=%s (Job \"%s\") not authorized. Not selected.\n"),
775 edit_int64(JobId, ed1), jr.Name);
779 pm_strcat(JobIds, ",");
781 pm_strcat(JobIds, edit_int64(JobId, ed1));
782 rx->TotalFiles += jr.JobFiles;
784 free_pool_memory(rx->JobIds);
785 rx->JobIds = JobIds; /* Set ACL filtered list */
786 if (*rx->JobIds == 0) {
787 ua->warning_msg(_("No Jobs selected.\n"));
790 if (strchr(rx->JobIds,',')) {
791 ua->info_msg(_("You have selected the following JobIds: %s\n"), rx->JobIds);
793 ua->info_msg(_("You have selected the following JobId: %s\n"), rx->JobIds);
801 static int get_date(UAContext *ua, char *date, int date_len)
803 ua->send_msg(_("The restored files will the most current backup\n"
804 "BEFORE the date you specify below.\n\n"));
806 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
809 if (str_to_utime(ua->cmd) != 0) {
812 ua->error_msg(_("Improper date format.\n"));
814 bstrncpy(date, ua->cmd, date_len);
819 * Insert a single file, or read a list of files from a file
821 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
831 if ((ffd = fopen(p, "rb")) == NULL) {
833 ua->error_msg(_("Cannot open file %s: ERR=%s\n"),
837 while (fgets(file, sizeof(file), ffd)) {
840 if (!insert_dir_into_findex_list(ua, rx, file, date)) {
841 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
844 if (!insert_file_into_findex_list(ua, rx, file, date)) {
845 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
853 insert_table_into_findex_list(ua, rx, p);
857 insert_dir_into_findex_list(ua, rx, ua->cmd, date);
859 insert_file_into_findex_list(ua, rx, ua->cmd, date);
866 * For a given file (path+filename), split into path and file, then
867 * lookup the most recent backup in the catalog to get the JobId
868 * and FileIndex, then insert them into the findex list.
870 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
873 strip_trailing_newline(file);
874 split_path_and_filename(ua, rx, file);
875 if (*rx->JobIds == 0) {
876 Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname,
879 Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
880 rx->path, rx->fname, rx->ClientName);
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 database record found for: %s\n"), file);
890 // ua->error_msg("Query=%s\n", rx->query);
897 * For a given path lookup the most recent backup in the catalog
898 * to get the JobId and FileIndexes of all files in that directory.
900 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
903 strip_trailing_junk(dir);
904 if (*rx->JobIds == 0) {
905 ua->error_msg(_("No JobId specified cannot continue.\n"));
908 Mmsg(rx->query, uar_jobid_fileindex_from_dir[db_type], rx->JobIds, dir, rx->ClientName);
911 /* Find and insert jobid and File Index */
912 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
913 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
914 rx->query, db_strerror(ua->db));
917 ua->error_msg(_("No database record found for: %s\n"), dir);
924 * Get the JobId and FileIndexes of all files in the specified table
926 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
928 strip_trailing_junk(table);
929 Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
932 /* Find and insert jobid and File Index */
933 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
934 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
935 rx->query, db_strerror(ua->db));
938 ua->error_msg(_("No table found: %s\n"), table);
944 static void split_path_and_filename(UAContext *ua, RESTORE_CTX *rx, char *name)
948 /* Find path without the filename.
949 * I.e. everything after the last / is a "filename".
950 * OK, maybe it is a directory name, but we treat it like
951 * a filename. If we don't find a / then the whole name
952 * must be a path name (e.g. c:).
954 for (p=f=name; *p; p++) {
955 if (IsPathSeparator(*p)) {
956 f = p; /* set pos of last slash */
959 if (IsPathSeparator(*f)) { /* did we find a slash? */
960 f++; /* yes, point to filename */
961 } else { /* no, whole thing must be path name */
965 /* If filename doesn't exist (i.e. root directory), we
966 * simply create a blank name consisting of a single
967 * space. This makes handling zero length filenames
972 rx->fname = check_pool_memory_size(rx->fname, 2*(rx->fnl)+1);
973 db_escape_string(ua->jcr, ua->db, rx->fname, f, rx->fnl);
981 rx->path = check_pool_memory_size(rx->path, 2*(rx->pnl)+1);
982 db_escape_string(ua->jcr, ua->db, rx->path, name, rx->pnl);
988 Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
991 static bool ask_for_fileregex(UAContext *ua, RESTORE_CTX *rx)
993 if (find_arg(ua, NT_("all")) >= 0) { /* if user enters all on command line */
994 return true; /* select everything */
996 ua->send_msg(_("\n\nFor one or more of the JobIds selected, no files were found,\n"
997 "so file selection is not possible.\n"
998 "Most likely your retention policy pruned the files.\n"));
999 if (get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
1000 if (ua->pint32_val == 1)
1002 while (get_cmd(ua, _("\nRegexp matching files to restore? (empty to abort): "))) {
1003 if (ua->cmd[0] == '\0') {
1006 regex_t *fileregex_re = NULL;
1008 char errmsg[500] = "";
1010 fileregex_re = (regex_t *)bmalloc(sizeof(regex_t));
1011 rc = regcomp(fileregex_re, ua->cmd, REG_EXTENDED|REG_NOSUB);
1013 regerror(rc, fileregex_re, errmsg, sizeof(errmsg));
1015 regfree(fileregex_re);
1018 ua->send_msg(_("Regex compile error: %s\n"), errmsg);
1020 rx->bsr->fileregex = bstrdup(ua->cmd);
1029 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
1032 JobId_t JobId, last_JobId;
1037 memset(&tree, 0, sizeof(TREE_CTX));
1039 * Build the directory tree containing JobIds user selected
1041 tree.root = new_tree(rx->TotalFiles);
1046 * For display purposes, the same JobId, with different volumes may
1047 * appear more than once, however, we only insert it once.
1050 tree.FileEstimate = 0;
1051 if (get_next_jobid_from_list(&p, &JobId) > 0) {
1052 /* Use first JobId as estimate of the number of files to restore */
1053 Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
1054 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1055 ua->error_msg("%s\n", db_strerror(ua->db));
1058 /* Add about 25% more than this job for over estimate */
1059 tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
1060 tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
1064 ua->info_msg(_("\nBuilding directory tree for JobId(s) %s ... "),
1067 #define new_get_file_list
1068 #ifdef new_get_file_list
1069 if (!db_get_file_list(ua->jcr, ua->db, rx->JobIds, insert_tree_handler, (void *)&tree)) {
1070 ua->error_msg("%s", db_strerror(ua->db));
1073 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1076 if (JobId == last_JobId) {
1077 continue; /* eliminate duplicate JobIds */
1081 * Find files for this JobId and insert them in the tree
1083 Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
1084 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
1085 ua->error_msg("%s", db_strerror(ua->db));
1090 * Look at the first JobId on the list (presumably the oldest) and
1091 * if it is marked purged, don't do the manual selection because
1092 * the Job was pruned, so the tree is incomplete.
1094 if (tree.FileCount != 0) {
1095 /* Find out if any Job is purged */
1096 Mmsg(rx->query, "SELECT SUM(PurgedFiles) FROM Job WHERE JobId IN (%s)", rx->JobIds);
1097 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1098 ua->error_msg("%s\n", db_strerror(ua->db));
1100 /* rx->JobId is the PurgedFiles flag */
1101 if (rx->found && rx->JobId > 0) {
1102 tree.FileCount = 0; /* set count to zero, no tree selection */
1105 if (tree.FileCount == 0) {
1106 OK = ask_for_fileregex(ua, rx);
1109 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1110 if (JobId == last_JobId) {
1111 continue; /* eliminate duplicate JobIds */
1113 add_findex_all(rx->bsr, JobId);
1119 ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
1120 edit_uint64_with_commas(tree.FileCount, ec1));
1122 ua->info_msg(_("\n%s files inserted into the tree.\n"),
1123 edit_uint64_with_commas(tree.FileCount, ec1));
1126 if (find_arg(ua, NT_("done")) < 0) {
1127 /* Let the user interact in selecting which files to restore */
1128 OK = user_select_files_from_tree(&tree);
1132 * Walk down through the tree finding all files marked to be
1133 * extracted making a bootstrap file.
1136 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
1137 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
1138 if (node->extract || node->extract_dir) {
1139 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
1140 add_findex(rx->bsr, node->JobId, node->FileIndex);
1141 if (node->extract && node->type != TN_NEWDIR) {
1142 rx->selected_files++; /* count only saved files */
1149 free_tree(tree.root); /* free the directory tree */
1155 * This routine is used to get the current backup or a backup
1156 * before the specified date.
1158 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
1163 char fileset_name[MAX_NAME_LENGTH];
1164 char ed1[50], ed2[50];
1165 char pool_select[MAX_NAME_LENGTH];
1168 /* Create temp tables */
1169 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1170 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1171 if (!db_sql_query(ua->db, uar_create_temp[db_type], NULL, NULL)) {
1172 ua->error_msg("%s\n", db_strerror(ua->db));
1174 if (!db_sql_query(ua->db, uar_create_temp1[db_type], NULL, NULL)) {
1175 ua->error_msg("%s\n", db_strerror(ua->db));
1178 * Select Client from the Catalog
1180 memset(&cr, 0, sizeof(cr));
1181 if (!get_client_dbr(ua, &cr)) {
1184 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1189 memset(&fsr, 0, sizeof(fsr));
1190 i = find_arg_with_value(ua, "FileSet");
1192 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1193 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1194 ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1195 db_strerror(ua->db));
1199 if (i < 0) { /* fileset not found */
1200 edit_int64(cr.ClientId, ed1);
1201 Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1202 start_prompt(ua, _("The defined FileSet resources are:\n"));
1203 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1204 ua->error_msg("%s\n", db_strerror(ua->db));
1206 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1207 fileset_name, sizeof(fileset_name)) < 0) {
1208 ua->error_msg(_("No FileSet found for client \"%s\".\n"), cr.Name);
1212 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1213 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1214 ua->warning_msg(_("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1215 ua->send_msg(_("This probably means you modified the FileSet.\n"
1216 "Continuing anyway.\n"));
1220 /* If Pool specified, add PoolId specification */
1224 memset(&pr, 0, sizeof(pr));
1225 bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1226 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1227 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
1228 edit_int64(pr.PoolId, ed1));
1230 ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1234 /* Find JobId of last Full backup for this client, fileset */
1235 edit_int64(cr.ClientId, ed1);
1236 Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1238 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1239 ua->error_msg("%s\n", db_strerror(ua->db));
1243 /* Find all Volumes used by that JobId */
1244 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1245 ua->error_msg("%s\n", db_strerror(ua->db));
1249 /* Note, this is needed because I don't seem to get the callback
1250 * from the call just above.
1253 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1254 ua->warning_msg("%s\n", db_strerror(ua->db));
1256 if (rx->JobTDate == 0) {
1257 ua->error_msg(_("No Full backup before %s found.\n"), date);
1261 /* Now find most recent Differental Job after Full save, if any */
1262 Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1263 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1264 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1265 ua->warning_msg("%s\n", db_strerror(ua->db));
1267 /* Now update JobTDate to lock onto Differental, if any */
1269 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1270 ua->warning_msg("%s\n", db_strerror(ua->db));
1272 if (rx->JobTDate == 0) {
1273 ua->error_msg(_("No Full backup before %s found.\n"), date);
1277 /* Now find all Incremental Jobs after Full/dif save */
1278 Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1279 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1280 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1281 ua->warning_msg("%s\n", db_strerror(ua->db));
1284 /* Get the JobIds from that list */
1286 rx->last_jobid[0] = 0;
1287 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1288 ua->warning_msg("%s\n", db_strerror(ua->db));
1291 if (rx->JobIds[0] != 0) {
1292 if (find_arg(ua, NT_("copies")) > 0) {
1293 /* Display a list of all copies */
1294 db_list_copies_records(ua->jcr, ua->db, 0, rx->JobIds,
1295 prtit, ua, HORZ_LIST);
1297 /* Display a list of Jobs selected for this restore */
1298 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
1301 ua->warning_msg(_("No jobs found.\n"));
1305 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1306 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1312 * Return next JobId from comma separated list
1315 * 1 if next JobId returned
1316 * 0 if no more JobIds are in list
1317 * -1 there is an error
1319 int get_next_jobid_from_list(char **p, JobId_t *JobId)
1321 const int maxlen = 30;
1322 char jobid[maxlen+1];
1326 for (int i=0; i<maxlen; i++) {
1329 } else if (*q == ',') {
1336 if (jobid[0] == 0) {
1338 } else if (!is_a_number(jobid)) {
1339 return -1; /* error */
1342 *JobId = str_to_int64(jobid);
1346 static int restore_count_handler(void *ctx, int num_fields, char **row)
1348 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1349 rx->JobId = str_to_int64(row[0]);
1355 * Callback handler to get JobId and FileIndex for files
1356 * can insert more than one depending on the caller.
1358 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1360 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1362 Dmsg2(200, "JobId=%s FileIndex=%s\n", row[0], row[1]);
1363 rx->JobId = str_to_int64(row[0]);
1364 add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1366 rx->selected_files++;
1371 * Callback handler make list of JobIds
1373 static int jobid_handler(void *ctx, int num_fields, char **row)
1375 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1377 if (strcmp(rx->last_jobid, row[0]) == 0) {
1378 return 0; /* duplicate id */
1380 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1381 if (rx->JobIds[0] != 0) {
1382 pm_strcat(rx->JobIds, ",");
1384 pm_strcat(rx->JobIds, row[0]);
1390 * Callback handler to pickup last Full backup JobTDate
1392 static int last_full_handler(void *ctx, int num_fields, char **row)
1394 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1396 rx->JobTDate = str_to_int64(row[1]);
1401 * Callback handler build FileSet name prompt list
1403 static int fileset_handler(void *ctx, int num_fields, char **row)
1405 /* row[0] = FileSet (name) */
1407 add_prompt((UAContext *)ctx, row[0]);
1413 * Free names in the list
1415 static void free_name_list(NAME_LIST *name_list)
1417 for (int i=0; i < name_list->num_ids; i++) {
1418 free(name_list->name[i]);
1420 if (name_list->name) {
1421 free(name_list->name);
1422 name_list->name = NULL;
1424 name_list->max_ids = 0;
1425 name_list->num_ids = 0;
1428 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType)
1433 Dmsg1(200, "Already have store=%s\n", rx.store->name());
1437 * Try looking up Storage by name
1440 foreach_res(store, R_STORAGE) {
1441 if (strcmp(Storage, store->name()) == 0) {
1442 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1451 /* Check if an explicit storage resource is given */
1453 int i = find_arg_with_value(ua, "storage");
1455 store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1456 if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1460 if (store && (store != rx.store)) {
1461 ua->info_msg(_("Warning default storage overridden by \"%s\" on command line.\n"),
1464 Dmsg1(200, "Set store=%s\n", rx.store->name());
1469 /* If no storage resource, try to find one from MediaType */
1472 foreach_res(store, R_STORAGE) {
1473 if (strcmp(MediaType, store->media_type) == 0) {
1474 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1476 Dmsg1(200, "Set store=%s\n", rx.store->name());
1477 ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1478 Storage, store->name(), MediaType);
1485 ua->warning_msg(_("\nUnable to find Storage resource for\n"
1486 "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1489 /* Take command line arg, or ask user if none */
1490 rx.store = get_storage_resource(ua, false /* don't use default */);
1492 Dmsg1(200, "Set store=%s\n", rx.store->name());