3 * Bacula Director -- User Agent Database restore Command
4 * Creates a bootstrap file for restoring files and
5 * starts the restore job.
7 * Tree handling routines split into ua_tree.c July MMIII.
8 * BSR (bootstrap record) handling routines split into
11 * Kern Sibbald, July MMII
16 Bacula® - The Network Backup Solution
18 Copyright (C) 2002-2006 Free Software Foundation Europe e.V.
20 The main author of Bacula is Kern Sibbald, with contributions from
21 many others, a complete list can be found in the file AUTHORS.
22 This program is Free Software; you can redistribute it and/or
23 modify it under the terms of version two of the GNU General Public
24 License as published by the Free Software Foundation plus additions
25 that are listed in the file LICENSE.
27 This program is distributed in the hope that it will be useful, but
28 WITHOUT ANY WARRANTY; without even the implied warranty of
29 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
30 General Public License for more details.
32 You should have received a copy of the GNU General Public License
33 along with this program; if not, write to the Free Software
34 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
37 Bacula® is a registered trademark of John Walker.
38 The licensor of Bacula is the Free Software Foundation Europe
39 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
40 Switzerland, email:ftf@fsfeurope.org.
48 /* Imported functions */
49 extern void print_bsr(UAContext *ua, RBSR *bsr);
53 /* Forward referenced functions */
54 static int last_full_handler(void *ctx, int num_fields, char **row);
55 static int jobid_handler(void *ctx, int num_fields, char **row);
56 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx);
57 static int fileset_handler(void *ctx, int num_fields, char **row);
58 static void free_name_list(NAME_LIST *name_list);
59 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date);
60 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx);
61 static void free_rx(RESTORE_CTX *rx);
62 static void split_path_and_filename(RESTORE_CTX *rx, char *fname);
63 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row);
64 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
66 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
68 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir);
69 static int get_client_name(UAContext *ua, RESTORE_CTX *rx);
70 static int get_date(UAContext *ua, char *date, int date_len);
71 static int 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;
87 memset(&rx, 0, sizeof(rx));
88 rx.path = get_pool_memory(PM_FNAME);
89 rx.fname = get_pool_memory(PM_FNAME);
90 rx.JobIds = get_pool_memory(PM_FNAME);
91 rx.query = get_pool_memory(PM_FNAME);
94 i = find_arg_with_value(ua, "where");
96 rx.where = ua->argv[i];
97 if (!acl_access_ok(ua, Where_ACL, rx.where)) {
98 bsendmsg(ua, _("Forbidden \"where\" specified.\n"));
107 /* Ensure there is at least one Restore Job */
109 foreach_res(job, R_JOB) {
110 if (job->JobType == JT_RESTORE) {
111 if (!rx.restore_job) {
112 rx.restore_job = job;
118 if (!rx.restore_jobs) {
120 "No Restore Job Resource found in bacula-dir.conf.\n"
121 "You must create at least one before running this command.\n"));
126 * Request user to select JobIds or files by various different methods
127 * last 20 jobs, where File saved, most recent backup, ...
128 * In the end, a list of files are pumped into
131 switch (user_select_jobids_or_files(ua, &rx)) {
134 case 1: /* selected by jobid */
135 if (!build_directory_tree(ua, &rx)) {
136 bsendmsg(ua, _("Restore not done.\n"));
140 case 2: /* selected by filename, no tree needed */
145 uint32_t selected_files;
147 if (!complete_bsr(ua, rx.bsr)) { /* find Vol, SessId, SessTime from JobIds */
148 bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
151 if (!(selected_files = write_bsr_file(ua, rx))) {
152 bsendmsg(ua, _("No files selected to be restored.\n"));
155 /* If no count of files, use bsr generated value (often wrong) */
156 if (rx.selected_files == 0) {
157 rx.selected_files = selected_files;
159 if (rx.selected_files==1) {
160 bsendmsg(ua, _("\n1 file selected to be restored.\n\n"));
163 bsendmsg(ua, _("\n%s files selected to be restored.\n\n"),
164 edit_uint64_with_commas(rx.selected_files, ed1));
167 bsendmsg(ua, _("No files selected to be restored.\n"));
171 if (rx.restore_jobs == 1) {
172 job = rx.restore_job;
174 job = select_restore_job_resource(ua);
180 get_client_name(ua, &rx);
181 if (!rx.ClientName) {
182 bsendmsg(ua, _("No Restore Job resource found!\n"));
186 escaped_bsr_name = escape_filename(jcr->RestoreBootstrap);
187 escaped_where_name = escape_filename(rx.where);
189 /* Build run command */
191 if (!acl_access_ok(ua, Where_ACL, rx.where)) {
192 bsendmsg(ua, _("Forbidden \"where\" specified.\n"));
197 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s\""
198 " where=\"%s\" files=%d catalog=\"%s\"",
199 job->name(), rx.ClientName, rx.store?rx.store->name():"",
200 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
201 escaped_where_name ? escaped_where_name : rx.where,
202 rx.selected_files, ua->catalog->name());
205 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s\""
206 " files=%d catalog=\"%s\"",
207 job->name(), rx.ClientName, rx.store?rx.store->name():"",
208 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
209 rx.selected_files, ua->catalog->name());
212 if (escaped_bsr_name != NULL) {
213 bfree(escaped_bsr_name);
216 if (escaped_where_name != NULL) {
217 bfree(escaped_where_name);
220 if (find_arg(ua, NT_("yes")) > 0) {
221 pm_strcat(ua->cmd, " yes"); /* pass it on to the run command */
223 Dmsg1(200, "Submitting: %s\n", ua->cmd);
225 run_cmd(ua, ua->cmd);
230 if (escaped_bsr_name != NULL) {
231 bfree(escaped_bsr_name);
234 if (escaped_where_name != NULL) {
235 bfree(escaped_where_name);
243 static void free_rx(RESTORE_CTX *rx)
248 free_pool_memory(rx->JobIds);
252 free_pool_memory(rx->fname);
256 free_pool_memory(rx->path);
260 free_pool_memory(rx->query);
263 free_name_list(&rx->name_list);
266 static bool has_value(UAContext *ua, int i)
269 bsendmsg(ua, _("Missing value for keyword: %s\n"), ua->argk[i]);
275 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
277 /* If no client name specified yet, get it now */
278 if (!rx->ClientName[0]) {
280 /* try command line argument */
281 int i = find_arg_with_value(ua, NT_("client"));
283 if (!has_value(ua, i)) {
286 bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
289 memset(&cr, 0, sizeof(cr));
290 if (!get_client_dbr(ua, &cr)) {
293 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
300 * The first step in the restore process is for the user to
301 * select a list of JobIds from which he will subsequently
302 * select which files are to be restored.
304 * Returns: 2 if filename list made
305 * 1 if jobid list made
308 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
311 char date[MAX_TIME_LENGTH];
312 bool have_date = false;
314 JOB_DBR jr = { (JobId_t)-1 };
317 const char *list[] = {
318 _("List last 20 Jobs run"),
319 _("List Jobs where a given File is saved"),
320 _("Enter list of comma separated JobIds to select"),
321 _("Enter SQL list command"),
322 _("Select the most recent backup for a client"),
323 _("Select backup for a client before a specified time"),
324 _("Enter a list of files to restore"),
325 _("Enter a list of files to restore before a specified time"),
326 _("Find the JobIds of the most recent backup for a client"),
327 _("Find the JobIds for a backup for a client before a specified time"),
328 _("Enter a list of directories to restore for found JobIds"),
333 /* These keywords are handled in a for loop */
343 /* The keyword below are handled by individual arg lookups */
349 "bootstrap", /* 13 */
356 for (i=1; i<ua->argc; i++) { /* loop through arguments */
357 bool found_kw = false;
358 for (j=0; kw[j]; j++) { /* loop through keywords */
359 if (strcasecmp(kw[j], ua->argk[i]) == 0) {
365 bsendmsg(ua, _("Unknown keyword: %s\n"), ua->argk[i]);
368 /* Found keyword in kw[] list, process it */
371 if (!has_value(ua, i)) {
374 if (*rx->JobIds != 0) {
375 pm_strcat(rx->JobIds, ",");
377 pm_strcat(rx->JobIds, ua->argv[i]);
380 case 1: /* current */
381 bstrutime(date, sizeof(date), time(NULL));
385 if (!has_value(ua, i)) {
388 if (str_to_utime(ua->argv[i]) == 0) {
389 bsendmsg(ua, _("Improper date format: %s\n"), ua->argv[i]);
392 bstrncpy(date, ua->argv[i], sizeof(date));
397 if (!has_value(ua, i)) {
401 bstrutime(date, sizeof(date), time(NULL));
403 if (!get_client_name(ua, rx)) {
406 pm_strcpy(ua->cmd, ua->argv[i]);
407 insert_one_file_or_dir(ua, rx, date, j==4);
411 bstrutime(date, sizeof(date), time(NULL));
413 if (!select_backups_before_date(ua, rx, date)) {
418 case 6: /* pool specified */
419 if (!has_value(ua, i)) {
422 rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
424 bsendmsg(ua, _("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
427 if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
429 bsendmsg(ua, _("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
433 case 7: /* all specified */
437 * All keywords 7 or greater are ignored or handled by a select prompt
445 bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
446 "to be restored. You will be presented several methods\n"
447 "of specifying the JobIds. Then you will be allowed to\n"
448 "select which files from those JobIds are to be restored.\n\n"));
451 /* If choice not already made above, prompt */
457 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
458 for (int i=0; list[i]; i++) {
459 add_prompt(ua, list[i]);
462 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
463 case -1: /* error or cancel */
465 case 0: /* list last 20 Jobs run */
466 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
467 bsendmsg(ua, _("SQL query not authorized.\n"));
470 gui_save = ua->jcr->gui;
472 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
473 ua->jcr->gui = gui_save;
476 case 1: /* list where a file is saved */
477 if (!get_client_name(ua, rx)) {
480 if (!get_cmd(ua, _("Enter Filename (no path):"))) {
483 len = strlen(ua->cmd);
484 fname = (char *)malloc(len * 2 + 1);
485 db_escape_string(fname, ua->cmd, len);
486 Mmsg(rx->query, uar_file, rx->ClientName, fname);
488 gui_save = ua->jcr->gui;
490 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
491 ua->jcr->gui = gui_save;
494 case 2: /* enter a list of JobIds */
495 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
498 pm_strcpy(rx->JobIds, ua->cmd);
500 case 3: /* Enter an SQL list command */
501 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
502 bsendmsg(ua, _("SQL query not authorized.\n"));
505 if (!get_cmd(ua, _("Enter SQL list command: "))) {
508 gui_save = ua->jcr->gui;
510 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
511 ua->jcr->gui = gui_save;
514 case 4: /* Select the most recent backups */
515 bstrutime(date, sizeof(date), time(NULL));
516 if (!select_backups_before_date(ua, rx, date)) {
520 case 5: /* select backup at specified time */
521 if (!get_date(ua, date, sizeof(date))) {
524 if (!select_backups_before_date(ua, rx, date)) {
528 case 6: /* Enter files */
529 bstrutime(date, sizeof(date), time(NULL));
530 if (!get_client_name(ua, rx)) {
533 bsendmsg(ua, _("Enter file names with paths, or < to enter a filename\n"
534 "containing a list of file names with paths, and terminate\n"
535 "them with a blank line.\n"));
537 if (!get_cmd(ua, _("Enter full filename: "))) {
540 len = strlen(ua->cmd);
544 insert_one_file_or_dir(ua, rx, date, false);
547 case 7: /* enter files backed up before specified time */
548 if (!get_date(ua, date, sizeof(date))) {
551 if (!get_client_name(ua, rx)) {
554 bsendmsg(ua, _("Enter file names with paths, or < to enter a filename\n"
555 "containing a list of file names with paths, and terminate\n"
556 "them with a blank line.\n"));
558 if (!get_cmd(ua, _("Enter full filename: "))) {
561 len = strlen(ua->cmd);
565 insert_one_file_or_dir(ua, rx, date, false);
569 case 8: /* Find JobIds for current backup */
570 bstrutime(date, sizeof(date), time(NULL));
571 if (!select_backups_before_date(ua, rx, date)) {
577 case 9: /* Find JobIds for give date */
578 if (!get_date(ua, date, sizeof(date))) {
581 if (!select_backups_before_date(ua, rx, date)) {
587 case 10: /* Enter directories */
588 if (*rx->JobIds != 0) {
589 bsendmsg(ua, _("You have already seleted the following JobIds: %s\n"),
591 } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
592 if (*rx->JobIds != 0 && *ua->cmd) {
593 pm_strcat(rx->JobIds, ",");
595 pm_strcat(rx->JobIds, ua->cmd);
597 if (*rx->JobIds == 0 || *rx->JobIds == '.') {
598 return 0; /* nothing entered, return */
600 bstrutime(date, sizeof(date), time(NULL));
601 if (!get_client_name(ua, rx)) {
604 bsendmsg(ua, _("Enter full directory names or start the name\n"
605 "with a < to indicate it is a filename containing a list\n"
606 "of directories and terminate them with a blank line.\n"));
608 if (!get_cmd(ua, _("Enter directory name: "))) {
611 len = strlen(ua->cmd);
615 /* Add trailing slash to end of directory names */
616 if (ua->cmd[0] != '<' && !IsPathSeparator(ua->cmd[len-1])) {
617 strcat(ua->cmd, "/");
619 insert_one_file_or_dir(ua, rx, date, true);
623 case 11: /* Cancel or quit */
628 if (*rx->JobIds == 0) {
629 bsendmsg(ua, _("No Jobs selected.\n"));
632 if (strchr(rx->JobIds,',')) {
633 bsendmsg(ua, _("You have selected the following JobIds: %s\n"), rx->JobIds);
636 bsendmsg(ua, _("You have selected the following JobId: %s\n"), rx->JobIds);
641 for (p=rx->JobIds; ; ) {
642 int stat = get_next_jobid_from_list(&p, &JobId);
644 bsendmsg(ua, _("Invalid JobId in list.\n"));
650 if (jr.JobId == JobId) {
651 continue; /* duplicate of last JobId */
653 memset(&jr, 0, sizeof(JOB_DBR));
655 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
657 bsendmsg(ua, _("Unable to get Job record for JobId=%s: ERR=%s\n"),
658 edit_int64(JobId, ed1), db_strerror(ua->db));
661 if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
662 bsendmsg(ua, _("No authorization. Job \"%s\" not selected.\n"),
666 rx->TotalFiles += jr.JobFiles;
674 static int get_date(UAContext *ua, char *date, int date_len)
676 bsendmsg(ua, _("The restored files will the most current backup\n"
677 "BEFORE the date you specify below.\n\n"));
679 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
682 if (str_to_utime(ua->cmd) != 0) {
685 bsendmsg(ua, _("Improper date format.\n"));
687 bstrncpy(date, ua->cmd, date_len);
692 * Insert a single file, or read a list of files from a file
694 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
704 if ((ffd = fopen(p, "rb")) == NULL) {
706 bsendmsg(ua, _("Cannot open file %s: ERR=%s\n"),
710 while (fgets(file, sizeof(file), ffd)) {
713 if (!insert_dir_into_findex_list(ua, rx, file, date)) {
714 bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
717 if (!insert_file_into_findex_list(ua, rx, file, date)) {
718 bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
726 insert_table_into_findex_list(ua, rx, p);
730 insert_dir_into_findex_list(ua, rx, ua->cmd, date);
732 insert_file_into_findex_list(ua, rx, ua->cmd, date);
739 * For a given file (path+filename), split into path and file, then
740 * lookup the most recent backup in the catalog to get the JobId
741 * and FileIndex, then insert them into the findex list.
743 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
746 strip_trailing_newline(file);
747 split_path_and_filename(rx, file);
748 if (*rx->JobIds == 0) {
749 Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname,
752 Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
753 rx->path, rx->fname, rx->ClientName);
756 /* Find and insert jobid and File Index */
757 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
758 bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
759 rx->query, db_strerror(ua->db));
762 bsendmsg(ua, _("No database record found for: %s\n"), file);
769 * For a given path lookup the most recent backup in the catalog
770 * to get the JobId and FileIndexes of all files in that directory.
772 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
775 strip_trailing_junk(dir);
776 if (*rx->JobIds == 0) {
777 bsendmsg(ua, _("No JobId specified cannot continue.\n"));
780 Mmsg(rx->query, uar_jobid_fileindex_from_dir, rx->JobIds,
781 dir, rx->ClientName);
784 /* Find and insert jobid and File Index */
785 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
786 bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
787 rx->query, db_strerror(ua->db));
790 bsendmsg(ua, _("No database record found for: %s\n"), dir);
797 * Get the JobId and FileIndexes of all files in the specified table
799 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
801 strip_trailing_junk(table);
802 Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
805 /* Find and insert jobid and File Index */
806 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
807 bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
808 rx->query, db_strerror(ua->db));
811 bsendmsg(ua, _("No table found: %s\n"), table);
817 static void split_path_and_filename(RESTORE_CTX *rx, char *name)
821 /* Find path without the filename.
822 * I.e. everything after the last / is a "filename".
823 * OK, maybe it is a directory name, but we treat it like
824 * a filename. If we don't find a / then the whole name
825 * must be a path name (e.g. c:).
827 for (p=f=name; *p; p++) {
828 if (IsPathSeparator(*p)) {
829 f = p; /* set pos of last slash */
832 if (IsPathSeparator(*f)) { /* did we find a slash? */
833 f++; /* yes, point to filename */
834 } else { /* no, whole thing must be path name */
838 /* If filename doesn't exist (i.e. root directory), we
839 * simply create a blank name consisting of a single
840 * space. This makes handling zero length filenames
845 rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
846 memcpy(rx->fname, f, rx->fnl); /* copy filename */
847 rx->fname[rx->fnl] = 0;
855 rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
856 memcpy(rx->path, name, rx->pnl);
857 rx->path[rx->pnl] = 0;
863 Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
866 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
869 JobId_t JobId, last_JobId;
874 memset(&tree, 0, sizeof(TREE_CTX));
876 * Build the directory tree containing JobIds user selected
878 tree.root = new_tree(rx->TotalFiles);
883 * For display purposes, the same JobId, with different volumes may
884 * appear more than once, however, we only insert it once.
888 tree.FileEstimate = 0;
889 if (get_next_jobid_from_list(&p, &JobId) > 0) {
890 /* Use first JobId as estimate of the number of files to restore */
891 Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
892 if (!db_sql_query(ua->db, rx->query, count_handler, (void *)rx)) {
893 bsendmsg(ua, "%s\n", db_strerror(ua->db));
896 /* Add about 25% more than this job for over estimate */
897 tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
898 tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
901 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
904 if (JobId == last_JobId) {
905 continue; /* eliminate duplicate JobIds */
908 bsendmsg(ua, _("\nBuilding directory tree for JobId %s ... "),
909 edit_int64(JobId, ed1));
912 * Find files for this JobId and insert them in the tree
914 Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
915 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
916 bsendmsg(ua, "%s", db_strerror(ua->db));
919 if (tree.FileCount == 0) {
920 bsendmsg(ua, _("\nThere were no files inserted into the tree, so file selection\n"
921 "is not possible.Most likely your retention policy pruned the files\n"));
922 if (!get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
926 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
927 if (JobId == last_JobId) {
928 continue; /* eliminate duplicate JobIds */
930 add_findex_all(rx->bsr, JobId);
938 bsendmsg(ua, _("\n1 Job, %s files inserted into the tree and marked for extraction.\n"),
939 edit_uint64_with_commas(tree.FileCount, ec1));
942 bsendmsg(ua, _("\n1 Job, %s files inserted into the tree.\n"),
943 edit_uint64_with_commas(tree.FileCount, ec1));
948 bsendmsg(ua, _("\n%d Jobs, %s files inserted into the tree and marked for extraction.\n"),
949 items, edit_uint64_with_commas(tree.FileCount, ec1));
952 bsendmsg(ua, _("\n%d Jobs, %s files inserted into the tree.\n"),
953 items, edit_uint64_with_commas(tree.FileCount, ec1));
957 if (find_arg(ua, NT_("done")) < 0) {
958 /* Let the user interact in selecting which files to restore */
959 OK = user_select_files_from_tree(&tree);
963 * Walk down through the tree finding all files marked to be
964 * extracted making a bootstrap file.
967 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
968 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
969 if (node->extract || node->extract_dir) {
970 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
971 add_findex(rx->bsr, node->JobId, node->FileIndex);
972 if (node->extract && node->type != TN_NEWDIR) {
973 rx->selected_files++; /* count only saved files */
980 free_tree(tree.root); /* free the directory tree */
986 * This routine is used to get the current backup or a backup
987 * before the specified date.
989 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
994 char fileset_name[MAX_NAME_LENGTH];
995 char ed1[50], ed2[50];
996 char pool_select[MAX_NAME_LENGTH];
1000 /* Create temp tables */
1001 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1002 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1003 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
1004 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1006 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
1007 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1010 * Select Client from the Catalog
1012 memset(&cr, 0, sizeof(cr));
1013 if (!get_client_dbr(ua, &cr)) {
1016 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1021 memset(&fsr, 0, sizeof(fsr));
1022 i = find_arg_with_value(ua, "FileSet");
1024 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1025 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1026 bsendmsg(ua, _("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1027 db_strerror(ua->db));
1031 if (i < 0) { /* fileset not found */
1032 edit_int64(cr.ClientId, ed1);
1033 Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1034 start_prompt(ua, _("The defined FileSet resources are:\n"));
1035 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1036 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1038 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1039 fileset_name, sizeof(fileset_name)) < 0) {
1043 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1044 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1045 bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1046 bsendmsg(ua, _("This probably means you modified the FileSet.\n"
1047 "Continuing anyway.\n"));
1051 /* If Pool specified, add PoolId specification */
1055 memset(&pr, 0, sizeof(pr));
1056 bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1057 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1058 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
1059 edit_int64(pr.PoolId, ed1));
1061 bsendmsg(ua, _("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1065 /* Find JobId of last Full backup for this client, fileset */
1066 edit_int64(cr.ClientId, ed1);
1067 Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1069 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1070 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1074 /* Find all Volumes used by that JobId */
1075 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1076 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1079 /* Note, this is needed because I don't seem to get the callback
1080 * from the call just above.
1083 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1084 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1086 if (rx->JobTDate == 0) {
1087 bsendmsg(ua, _("No Full backup before %s found.\n"), date);
1091 /* Now find most recent Differental Job after Full save, if any */
1092 Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1093 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1094 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1095 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1097 /* Now update JobTDate to lock onto Differental, if any */
1099 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1100 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1102 if (rx->JobTDate == 0) {
1103 bsendmsg(ua, _("No Full backup before %s found.\n"), date);
1107 /* Now find all Incremental Jobs after Full/dif save */
1108 Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1109 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1110 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1111 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1114 /* Get the JobIds from that list */
1116 rx->last_jobid[0] = 0;
1117 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1118 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1121 if (rx->JobIds[0] != 0) {
1122 /* Display a list of Jobs selected for this restore */
1123 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
1126 bsendmsg(ua, _("No jobs found.\n"));
1130 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1131 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1137 * Return next JobId from comma separated list
1140 * 1 if next JobId returned
1141 * 0 if no more JobIds are in list
1142 * -1 there is an error
1144 int get_next_jobid_from_list(char **p, JobId_t *JobId)
1150 for (int i=0; i<(int)sizeof(jobid); i++) {
1153 } else if (*q == ',') {
1160 if (jobid[0] == 0) {
1162 } else if (!is_a_number(jobid)) {
1163 return -1; /* error */
1166 *JobId = str_to_int64(jobid);
1170 static int count_handler(void *ctx, int num_fields, char **row)
1172 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1173 rx->JobId = str_to_int64(row[0]);
1179 * Callback handler to get JobId and FileIndex for files
1180 * can insert more than one depending on the caller.
1182 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1184 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1185 rx->JobId = str_to_int64(row[0]);
1186 add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1188 rx->selected_files++;
1193 * Callback handler make list of JobIds
1195 static int jobid_handler(void *ctx, int num_fields, char **row)
1197 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1199 if (strcmp(rx->last_jobid, row[0]) == 0) {
1200 return 0; /* duplicate id */
1202 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1203 if (rx->JobIds[0] != 0) {
1204 pm_strcat(rx->JobIds, ",");
1206 pm_strcat(rx->JobIds, row[0]);
1212 * Callback handler to pickup last Full backup JobTDate
1214 static int last_full_handler(void *ctx, int num_fields, char **row)
1216 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1218 rx->JobTDate = str_to_int64(row[1]);
1223 * Callback handler build FileSet name prompt list
1225 static int fileset_handler(void *ctx, int num_fields, char **row)
1227 /* row[0] = FileSet (name) */
1229 add_prompt((UAContext *)ctx, row[0]);
1235 * Free names in the list
1237 static void free_name_list(NAME_LIST *name_list)
1239 for (int i=0; i < name_list->num_ids; i++) {
1240 free(name_list->name[i]);
1242 if (name_list->name) {
1243 free(name_list->name);
1244 name_list->name = NULL;
1246 name_list->max_ids = 0;
1247 name_list->num_ids = 0;
1250 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType)
1255 Dmsg1(200, "Already have store=%s\n", rx.store->name());
1259 * Try looking up Storage by name
1262 foreach_res(store, R_STORAGE) {
1263 if (strcmp(Storage, store->name()) == 0) {
1264 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1273 /* Check if an explicit storage resource is given */
1275 int i = find_arg_with_value(ua, "storage");
1277 store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1278 if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1282 if (store && (store != rx.store)) {
1283 bsendmsg(ua, _("Warning default storage overridden by \"%s\" on command line.\n"),
1286 Dmsg1(200, "Set store=%s\n", rx.store->name());
1291 /* If no storage resource, try to find one from MediaType */
1294 foreach_res(store, R_STORAGE) {
1295 if (strcmp(MediaType, store->media_type) == 0) {
1296 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1298 Dmsg1(200, "Set store=%s\n", rx.store->name());
1299 bsendmsg(ua, _("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1300 Storage, store->name(), MediaType);
1307 bsendmsg(ua, _("\nUnable to find Storage resource for\n"
1308 "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1311 /* Take command line arg, or ask user if none */
1312 rx.store = get_storage_resource(ua, false /* don't use default */);
1313 Dmsg1(200, "Set store=%s\n", rx.store->name());