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_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=%d 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=%d 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=%d 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 */
449 for (i=1; i<ua->argc; i++) { /* loop through arguments */
450 bool found_kw = false;
451 for (j=0; kw[j]; j++) { /* loop through keywords */
452 if (strcasecmp(kw[j], ua->argk[i]) == 0) {
458 ua->error_msg(_("Unknown keyword: %s\n"), ua->argk[i]);
461 /* Found keyword in kw[] list, process it */
464 if (!has_value(ua, i)) {
467 if (*rx->JobIds != 0) {
468 pm_strcat(rx->JobIds, ",");
470 pm_strcat(rx->JobIds, ua->argv[i]);
473 case 1: /* current */
475 * Note, we add one second here just to include any job
476 * that may have finished within the current second,
477 * which happens a lot in scripting small jobs.
479 bstrutime(date, sizeof(date), now);
483 if (!has_value(ua, i)) {
486 if (str_to_utime(ua->argv[i]) == 0) {
487 ua->error_msg(_("Improper date format: %s\n"), ua->argv[i]);
490 bstrncpy(date, ua->argv[i], sizeof(date));
495 if (!has_value(ua, i)) {
499 bstrutime(date, sizeof(date), now);
501 if (!get_client_name(ua, rx)) {
504 pm_strcpy(ua->cmd, ua->argv[i]);
505 insert_one_file_or_dir(ua, rx, date, j==4);
509 bstrutime(date, sizeof(date), now);
511 if (!select_backups_before_date(ua, rx, date)) {
516 case 6: /* pool specified */
517 if (!has_value(ua, i)) {
520 rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
522 ua->error_msg(_("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
525 if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
527 ua->error_msg(_("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
531 case 7: /* all specified */
535 * All keywords 7 or greater are ignored or handled by a select prompt
543 ua->send_msg(_("\nFirst you select one or more JobIds that contain files\n"
544 "to be restored. You will be presented several methods\n"
545 "of specifying the JobIds. Then you will be allowed to\n"
546 "select which files from those JobIds are to be restored.\n\n"));
549 /* If choice not already made above, prompt */
555 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
556 for (int i=0; list[i]; i++) {
557 add_prompt(ua, list[i]);
560 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
561 case -1: /* error or cancel */
563 case 0: /* list last 20 Jobs run */
564 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
565 ua->error_msg(_("SQL query not authorized.\n"));
568 gui_save = ua->jcr->gui;
570 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
571 ua->jcr->gui = gui_save;
574 case 1: /* list where a file is saved */
575 if (!get_client_name(ua, rx)) {
578 if (!get_cmd(ua, _("Enter Filename (no path):"))) {
581 len = strlen(ua->cmd);
582 fname = (char *)malloc(len * 2 + 1);
583 db_escape_string(fname, ua->cmd, len);
584 Mmsg(rx->query, uar_file, rx->ClientName, fname);
586 gui_save = ua->jcr->gui;
588 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
589 ua->jcr->gui = gui_save;
592 case 2: /* enter a list of JobIds */
593 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
596 pm_strcpy(rx->JobIds, ua->cmd);
598 case 3: /* Enter an SQL list command */
599 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
600 ua->error_msg(_("SQL query not authorized.\n"));
603 if (!get_cmd(ua, _("Enter SQL list command: "))) {
606 gui_save = ua->jcr->gui;
608 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
609 ua->jcr->gui = gui_save;
612 case 4: /* Select the most recent backups */
613 bstrutime(date, sizeof(date), now);
614 if (!select_backups_before_date(ua, rx, date)) {
618 case 5: /* select backup at specified time */
619 if (!get_date(ua, date, sizeof(date))) {
622 if (!select_backups_before_date(ua, rx, date)) {
626 case 6: /* Enter files */
627 bstrutime(date, sizeof(date), now);
628 if (!get_client_name(ua, rx)) {
631 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
632 "containing a list of file names with paths, and terminate\n"
633 "them with a blank line.\n"));
635 if (!get_cmd(ua, _("Enter full filename: "))) {
638 len = strlen(ua->cmd);
642 insert_one_file_or_dir(ua, rx, date, false);
645 case 7: /* enter files backed up before specified time */
646 if (!get_date(ua, date, sizeof(date))) {
649 if (!get_client_name(ua, rx)) {
652 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
653 "containing a list of file names with paths, and terminate\n"
654 "them with a blank line.\n"));
656 if (!get_cmd(ua, _("Enter full filename: "))) {
659 len = strlen(ua->cmd);
663 insert_one_file_or_dir(ua, rx, date, false);
667 case 8: /* Find JobIds for current backup */
668 bstrutime(date, sizeof(date), now);
669 if (!select_backups_before_date(ua, rx, date)) {
675 case 9: /* Find JobIds for give date */
676 if (!get_date(ua, date, sizeof(date))) {
679 if (!select_backups_before_date(ua, rx, date)) {
685 case 10: /* Enter directories */
686 if (*rx->JobIds != 0) {
687 ua->send_msg(_("You have already selected the following JobIds: %s\n"),
689 } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
690 if (*rx->JobIds != 0 && *ua->cmd) {
691 pm_strcat(rx->JobIds, ",");
693 pm_strcat(rx->JobIds, ua->cmd);
695 if (*rx->JobIds == 0 || *rx->JobIds == '.') {
696 return 0; /* nothing entered, return */
698 bstrutime(date, sizeof(date), now);
699 if (!get_client_name(ua, rx)) {
702 ua->send_msg(_("Enter full directory names or start the name\n"
703 "with a < to indicate it is a filename containing a list\n"
704 "of directories and terminate them with a blank line.\n"));
706 if (!get_cmd(ua, _("Enter directory name: "))) {
709 len = strlen(ua->cmd);
713 /* Add trailing slash to end of directory names */
714 if (ua->cmd[0] != '<' && !IsPathSeparator(ua->cmd[len-1])) {
715 strcat(ua->cmd, "/");
717 insert_one_file_or_dir(ua, rx, date, true);
721 case 11: /* Cancel or quit */
726 POOLMEM *JobIds = get_pool_memory(PM_FNAME);
730 * Find total number of files to be restored, and filter the JobId
731 * list to contain only ones permitted by the ACL conditions.
733 for (p=rx->JobIds; ; ) {
735 int stat = get_next_jobid_from_list(&p, &JobId);
737 ua->error_msg(_("Invalid JobId in list.\n"));
738 free_pool_memory(JobIds);
744 if (jr.JobId == JobId) {
745 continue; /* duplicate of last JobId */
747 memset(&jr, 0, sizeof(JOB_DBR));
749 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
750 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
751 edit_int64(JobId, ed1), db_strerror(ua->db));
752 free_pool_memory(JobIds);
755 if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
756 ua->error_msg(_("Access to JobId=%s (Job \"%s\") not authorized. Not selected.\n"),
757 edit_int64(JobId, ed1), jr.Name);
761 pm_strcat(JobIds, ",");
763 pm_strcat(JobIds, edit_int64(JobId, ed1));
764 rx->TotalFiles += jr.JobFiles;
766 free_pool_memory(rx->JobIds);
767 rx->JobIds = JobIds; /* Set ACL filtered list */
768 if (*rx->JobIds == 0) {
769 ua->warning_msg(_("No Jobs selected.\n"));
772 if (strchr(rx->JobIds,',')) {
773 ua->info_msg(_("You have selected the following JobIds: %s\n"), rx->JobIds);
775 ua->info_msg(_("You have selected the following JobId: %s\n"), rx->JobIds);
783 static int get_date(UAContext *ua, char *date, int date_len)
785 ua->send_msg(_("The restored files will the most current backup\n"
786 "BEFORE the date you specify below.\n\n"));
788 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
791 if (str_to_utime(ua->cmd) != 0) {
794 ua->error_msg(_("Improper date format.\n"));
796 bstrncpy(date, ua->cmd, date_len);
801 * Insert a single file, or read a list of files from a file
803 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
813 if ((ffd = fopen(p, "rb")) == NULL) {
815 ua->error_msg(_("Cannot open file %s: ERR=%s\n"),
819 while (fgets(file, sizeof(file), ffd)) {
822 if (!insert_dir_into_findex_list(ua, rx, file, date)) {
823 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
826 if (!insert_file_into_findex_list(ua, rx, file, date)) {
827 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
835 insert_table_into_findex_list(ua, rx, p);
839 insert_dir_into_findex_list(ua, rx, ua->cmd, date);
841 insert_file_into_findex_list(ua, rx, ua->cmd, date);
848 * For a given file (path+filename), split into path and file, then
849 * lookup the most recent backup in the catalog to get the JobId
850 * and FileIndex, then insert them into the findex list.
852 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
855 strip_trailing_newline(file);
856 split_path_and_filename(rx, file);
857 if (*rx->JobIds == 0) {
858 Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname,
861 Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
862 rx->path, rx->fname, rx->ClientName);
865 /* Find and insert jobid and File Index */
866 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
867 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
868 rx->query, db_strerror(ua->db));
871 ua->error_msg(_("No database record found for: %s\n"), file);
872 // ua->error_msg("Query=%s\n", rx->query);
879 * For a given path lookup the most recent backup in the catalog
880 * to get the JobId and FileIndexes of all files in that directory.
882 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
885 strip_trailing_junk(dir);
886 if (*rx->JobIds == 0) {
887 ua->error_msg(_("No JobId specified cannot continue.\n"));
890 Mmsg(rx->query, uar_jobid_fileindex_from_dir, rx->JobIds,
891 dir, rx->ClientName);
894 /* Find and insert jobid and File Index */
895 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
896 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
897 rx->query, db_strerror(ua->db));
900 ua->error_msg(_("No database record found for: %s\n"), dir);
907 * Get the JobId and FileIndexes of all files in the specified table
909 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
911 strip_trailing_junk(table);
912 Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
915 /* Find and insert jobid and File Index */
916 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
917 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
918 rx->query, db_strerror(ua->db));
921 ua->error_msg(_("No table found: %s\n"), table);
927 static void split_path_and_filename(RESTORE_CTX *rx, char *name)
931 /* Find path without the filename.
932 * I.e. everything after the last / is a "filename".
933 * OK, maybe it is a directory name, but we treat it like
934 * a filename. If we don't find a / then the whole name
935 * must be a path name (e.g. c:).
937 for (p=f=name; *p; p++) {
938 if (IsPathSeparator(*p)) {
939 f = p; /* set pos of last slash */
942 if (IsPathSeparator(*f)) { /* did we find a slash? */
943 f++; /* yes, point to filename */
944 } else { /* no, whole thing must be path name */
948 /* If filename doesn't exist (i.e. root directory), we
949 * simply create a blank name consisting of a single
950 * space. This makes handling zero length filenames
955 rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
956 memcpy(rx->fname, f, rx->fnl); /* copy filename */
957 rx->fname[rx->fnl] = 0;
965 rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
966 memcpy(rx->path, name, rx->pnl);
967 rx->path[rx->pnl] = 0;
973 Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
976 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
979 JobId_t JobId, last_JobId;
984 memset(&tree, 0, sizeof(TREE_CTX));
986 * Build the directory tree containing JobIds user selected
988 tree.root = new_tree(rx->TotalFiles);
993 * For display purposes, the same JobId, with different volumes may
994 * appear more than once, however, we only insert it once.
998 tree.FileEstimate = 0;
999 if (get_next_jobid_from_list(&p, &JobId) > 0) {
1000 /* Use first JobId as estimate of the number of files to restore */
1001 Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
1002 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1003 ua->error_msg("%s\n", db_strerror(ua->db));
1006 /* Add about 25% more than this job for over estimate */
1007 tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
1008 tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
1011 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1014 if (JobId == last_JobId) {
1015 continue; /* eliminate duplicate JobIds */
1018 ua->info_msg(_("\nBuilding directory tree for JobId %s ... "),
1019 edit_int64(JobId, ed1));
1022 * Find files for this JobId and insert them in the tree
1024 Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
1025 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
1026 ua->error_msg("%s", db_strerror(ua->db));
1029 if (tree.FileCount == 0) {
1030 ua->send_msg(_("\nThere were no files inserted into the tree, so file selection\n"
1031 "is not possible.Most likely your retention policy pruned the files\n"));
1032 if (!get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
1036 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1037 if (JobId == last_JobId) {
1038 continue; /* eliminate duplicate JobIds */
1040 add_findex_all(rx->bsr, JobId);
1048 ua->info_msg(_("\n1 Job, %s files inserted into the tree and marked for extraction.\n"),
1049 edit_uint64_with_commas(tree.FileCount, ec1));
1052 ua->info_msg(_("\n1 Job, %s files inserted into the tree.\n"),
1053 edit_uint64_with_commas(tree.FileCount, ec1));
1058 ua->info_msg(_("\n%d Jobs, %s files inserted into the tree and marked for extraction.\n"),
1059 items, edit_uint64_with_commas(tree.FileCount, ec1));
1062 ua->info_msg(_("\n%d Jobs, %s files inserted into the tree.\n"),
1063 items, edit_uint64_with_commas(tree.FileCount, ec1));
1067 if (find_arg(ua, NT_("done")) < 0) {
1068 /* Let the user interact in selecting which files to restore */
1069 OK = user_select_files_from_tree(&tree);
1073 * Walk down through the tree finding all files marked to be
1074 * extracted making a bootstrap file.
1077 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
1078 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
1079 if (node->extract || node->extract_dir) {
1080 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
1081 add_findex(rx->bsr, node->JobId, node->FileIndex);
1082 if (node->extract && node->type != TN_NEWDIR) {
1083 rx->selected_files++; /* count only saved files */
1090 free_tree(tree.root); /* free the directory tree */
1096 * This routine is used to get the current backup or a backup
1097 * before the specified date.
1099 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
1104 char fileset_name[MAX_NAME_LENGTH];
1105 char ed1[50], ed2[50];
1106 char pool_select[MAX_NAME_LENGTH];
1110 /* Create temp tables */
1111 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1112 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1113 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
1114 ua->error_msg("%s\n", db_strerror(ua->db));
1116 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
1117 ua->error_msg("%s\n", db_strerror(ua->db));
1120 * Select Client from the Catalog
1122 memset(&cr, 0, sizeof(cr));
1123 if (!get_client_dbr(ua, &cr)) {
1126 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1131 memset(&fsr, 0, sizeof(fsr));
1132 i = find_arg_with_value(ua, "FileSet");
1134 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1135 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1136 ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1137 db_strerror(ua->db));
1141 if (i < 0) { /* fileset not found */
1142 edit_int64(cr.ClientId, ed1);
1143 Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1144 start_prompt(ua, _("The defined FileSet resources are:\n"));
1145 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1146 ua->error_msg("%s\n", db_strerror(ua->db));
1148 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1149 fileset_name, sizeof(fileset_name)) < 0) {
1150 ua->error_msg(_("No FileSet found for client \"%s\".\n"), cr.Name);
1154 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1155 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1156 ua->warning_msg(_("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1157 ua->send_msg(_("This probably means you modified the FileSet.\n"
1158 "Continuing anyway.\n"));
1162 /* If Pool specified, add PoolId specification */
1166 memset(&pr, 0, sizeof(pr));
1167 bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1168 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1169 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
1170 edit_int64(pr.PoolId, ed1));
1172 ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1176 /* Find JobId of last Full backup for this client, fileset */
1177 edit_int64(cr.ClientId, ed1);
1178 Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1180 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1181 ua->error_msg("%s\n", db_strerror(ua->db));
1185 /* Find all Volumes used by that JobId */
1186 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1187 ua->error_msg("%s\n", db_strerror(ua->db));
1190 /* Note, this is needed because I don't seem to get the callback
1191 * from the call just above.
1194 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1195 ua->warning_msg("%s\n", db_strerror(ua->db));
1197 if (rx->JobTDate == 0) {
1198 ua->error_msg(_("No Full backup before %s found.\n"), date);
1202 /* Now find most recent Differental Job after Full save, if any */
1203 Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1204 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1205 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1206 ua->warning_msg("%s\n", db_strerror(ua->db));
1208 /* Now update JobTDate to lock onto Differental, if any */
1210 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1211 ua->warning_msg("%s\n", db_strerror(ua->db));
1213 if (rx->JobTDate == 0) {
1214 ua->error_msg(_("No Full backup before %s found.\n"), date);
1218 /* Now find all Incremental Jobs after Full/dif save */
1219 Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1220 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1221 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1222 ua->warning_msg("%s\n", db_strerror(ua->db));
1225 /* Get the JobIds from that list */
1227 rx->last_jobid[0] = 0;
1228 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1229 ua->warning_msg("%s\n", db_strerror(ua->db));
1232 if (rx->JobIds[0] != 0) {
1233 /* Display a list of Jobs selected for this restore */
1234 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
1237 ua->warning_msg(_("No jobs found.\n"));
1241 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1242 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1248 * Return next JobId from comma separated list
1251 * 1 if next JobId returned
1252 * 0 if no more JobIds are in list
1253 * -1 there is an error
1255 int get_next_jobid_from_list(char **p, JobId_t *JobId)
1261 for (int i=0; i<(int)sizeof(jobid); i++) {
1264 } else if (*q == ',') {
1271 if (jobid[0] == 0) {
1273 } else if (!is_a_number(jobid)) {
1274 return -1; /* error */
1277 *JobId = str_to_int64(jobid);
1281 static int restore_count_handler(void *ctx, int num_fields, char **row)
1283 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1284 rx->JobId = str_to_int64(row[0]);
1290 * Callback handler to get JobId and FileIndex for files
1291 * can insert more than one depending on the caller.
1293 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1295 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1296 rx->JobId = str_to_int64(row[0]);
1297 add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1299 rx->selected_files++;
1304 * Callback handler make list of JobIds
1306 static int jobid_handler(void *ctx, int num_fields, char **row)
1308 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1310 if (strcmp(rx->last_jobid, row[0]) == 0) {
1311 return 0; /* duplicate id */
1313 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1314 if (rx->JobIds[0] != 0) {
1315 pm_strcat(rx->JobIds, ",");
1317 pm_strcat(rx->JobIds, row[0]);
1323 * Callback handler to pickup last Full backup JobTDate
1325 static int last_full_handler(void *ctx, int num_fields, char **row)
1327 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1329 rx->JobTDate = str_to_int64(row[1]);
1334 * Callback handler build FileSet name prompt list
1336 static int fileset_handler(void *ctx, int num_fields, char **row)
1338 /* row[0] = FileSet (name) */
1340 add_prompt((UAContext *)ctx, row[0]);
1346 * Free names in the list
1348 static void free_name_list(NAME_LIST *name_list)
1350 for (int i=0; i < name_list->num_ids; i++) {
1351 free(name_list->name[i]);
1353 if (name_list->name) {
1354 free(name_list->name);
1355 name_list->name = NULL;
1357 name_list->max_ids = 0;
1358 name_list->num_ids = 0;
1361 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType)
1366 Dmsg1(200, "Already have store=%s\n", rx.store->name());
1370 * Try looking up Storage by name
1373 foreach_res(store, R_STORAGE) {
1374 if (strcmp(Storage, store->name()) == 0) {
1375 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1384 /* Check if an explicit storage resource is given */
1386 int i = find_arg_with_value(ua, "storage");
1388 store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1389 if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1393 if (store && (store != rx.store)) {
1394 ua->info_msg(_("Warning default storage overridden by \"%s\" on command line.\n"),
1397 Dmsg1(200, "Set store=%s\n", rx.store->name());
1402 /* If no storage resource, try to find one from MediaType */
1405 foreach_res(store, R_STORAGE) {
1406 if (strcmp(MediaType, store->media_type) == 0) {
1407 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1409 Dmsg1(200, "Set store=%s\n", rx.store->name());
1410 ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1411 Storage, store->name(), MediaType);
1418 ua->warning_msg(_("\nUnable to find Storage resource for\n"
1419 "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1422 /* Take command line arg, or ask user if none */
1423 rx.store = get_storage_resource(ua, false /* don't use default */);
1424 Dmsg1(200, "Set store=%s\n", rx.store->name());