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 Copyright (C) 2002-2006 Kern Sibbald
18 This program is free software; you can redistribute it and/or
19 modify it under the terms of the GNU General Public License
20 version 2 as amended with additional clauses defined in the
21 file LICENSE in the main source directory.
23 This program is distributed in the hope that it will be useful,
24 but WITHOUT ANY WARRANTY; without even the implied warranty of
25 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 the file LICENSE for additional details.
35 /* Imported functions */
36 extern void print_bsr(UAContext *ua, RBSR *bsr);
40 /* Forward referenced functions */
41 static int last_full_handler(void *ctx, int num_fields, char **row);
42 static int jobid_handler(void *ctx, int num_fields, char **row);
43 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx);
44 static int fileset_handler(void *ctx, int num_fields, char **row);
45 static void free_name_list(NAME_LIST *name_list);
46 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date);
47 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx);
48 static void free_rx(RESTORE_CTX *rx);
49 static void split_path_and_filename(RESTORE_CTX *rx, char *fname);
50 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row);
51 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
53 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
55 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir);
56 static int get_client_name(UAContext *ua, RESTORE_CTX *rx);
57 static int get_date(UAContext *ua, char *date, int date_len);
58 static int count_handler(void *ctx, int num_fields, char **row);
59 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table);
65 int restore_cmd(UAContext *ua, const char *cmd)
67 RESTORE_CTX rx; /* restore context */
71 char *escaped_bsr_name = NULL;
72 char *escaped_where_name = NULL;
74 memset(&rx, 0, sizeof(rx));
75 rx.path = get_pool_memory(PM_FNAME);
76 rx.fname = get_pool_memory(PM_FNAME);
77 rx.JobIds = get_pool_memory(PM_FNAME);
78 rx.query = get_pool_memory(PM_FNAME);
81 i = find_arg_with_value(ua, "where");
83 rx.where = ua->argv[i];
84 if (!acl_access_ok(ua, Where_ACL, rx.where)) {
85 bsendmsg(ua, _("Forbidden \"where\" specified.\n"));
94 /* Ensure there is at least one Restore Job */
96 foreach_res(job, R_JOB) {
97 if (job->JobType == JT_RESTORE) {
98 if (!rx.restore_job) {
105 if (!rx.restore_jobs) {
107 "No Restore Job Resource found in bacula-dir.conf.\n"
108 "You must create at least one before running this command.\n"));
113 * Request user to select JobIds or files by various different methods
114 * last 20 jobs, where File saved, most recent backup, ...
115 * In the end, a list of files are pumped into
118 switch (user_select_jobids_or_files(ua, &rx)) {
121 case 1: /* selected by jobid */
122 if (!build_directory_tree(ua, &rx)) {
123 bsendmsg(ua, _("Restore not done.\n"));
127 case 2: /* selected by filename, no tree needed */
132 uint32_t selected_files;
134 if (!complete_bsr(ua, rx.bsr)) { /* find Vol, SessId, SessTime from JobIds */
135 bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
138 if (!(selected_files = write_bsr_file(ua, rx))) {
139 bsendmsg(ua, _("No files selected to be restored.\n"));
142 /* If no count of files, use bsr generated value (often wrong) */
143 if (rx.selected_files == 0) {
144 rx.selected_files = selected_files;
146 if (rx.selected_files==1) {
147 bsendmsg(ua, _("\n1 file selected to be restored.\n\n"));
150 bsendmsg(ua, _("\n%s files selected to be restored.\n\n"),
151 edit_uint64_with_commas(rx.selected_files, ed1));
154 bsendmsg(ua, _("No files selected to be restored.\n"));
158 if (rx.restore_jobs == 1) {
159 job = rx.restore_job;
161 job = select_restore_job_resource(ua);
167 get_client_name(ua, &rx);
168 if (!rx.ClientName) {
169 bsendmsg(ua, _("No Restore Job resource found!\n"));
173 escaped_bsr_name = escape_filename(jcr->RestoreBootstrap);
174 escaped_where_name = escape_filename(rx.where);
176 /* Build run command */
178 if (!acl_access_ok(ua, Where_ACL, rx.where)) {
179 bsendmsg(ua, _("Forbidden \"where\" specified.\n"));
184 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s\""
185 " where=\"%s\" files=%d catalog=\"%s\"",
186 job->name(), rx.ClientName, rx.store?rx.store->name():"",
187 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
188 escaped_where_name ? escaped_where_name : rx.where,
189 rx.selected_files, ua->catalog->name());
192 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s\""
193 " files=%d catalog=\"%s\"",
194 job->name(), rx.ClientName, rx.store?rx.store->name():"",
195 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
196 rx.selected_files, ua->catalog->name());
199 if (escaped_bsr_name != NULL) {
200 bfree(escaped_bsr_name);
203 if (escaped_where_name != NULL) {
204 bfree(escaped_where_name);
207 if (find_arg(ua, NT_("yes")) > 0) {
208 pm_strcat(ua->cmd, " yes"); /* pass it on to the run command */
210 Dmsg1(200, "Submitting: %s\n", ua->cmd);
212 run_cmd(ua, ua->cmd);
217 if (escaped_bsr_name != NULL) {
218 bfree(escaped_bsr_name);
221 if (escaped_where_name != NULL) {
222 bfree(escaped_where_name);
230 static void free_rx(RESTORE_CTX *rx)
235 free_pool_memory(rx->JobIds);
239 free_pool_memory(rx->fname);
243 free_pool_memory(rx->path);
247 free_pool_memory(rx->query);
250 free_name_list(&rx->name_list);
253 static bool has_value(UAContext *ua, int i)
256 bsendmsg(ua, _("Missing value for keyword: %s\n"), ua->argk[i]);
262 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
264 /* If no client name specified yet, get it now */
265 if (!rx->ClientName[0]) {
267 /* try command line argument */
268 int i = find_arg_with_value(ua, NT_("client"));
270 if (!has_value(ua, i)) {
273 bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
276 memset(&cr, 0, sizeof(cr));
277 if (!get_client_dbr(ua, &cr)) {
280 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
287 * The first step in the restore process is for the user to
288 * select a list of JobIds from which he will subsequently
289 * select which files are to be restored.
291 * Returns: 2 if filename list made
292 * 1 if jobid list made
295 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
298 char date[MAX_TIME_LENGTH];
299 bool have_date = false;
301 JOB_DBR jr = { (JobId_t)-1 };
304 const char *list[] = {
305 _("List last 20 Jobs run"),
306 _("List Jobs where a given File is saved"),
307 _("Enter list of comma separated JobIds to select"),
308 _("Enter SQL list command"),
309 _("Select the most recent backup for a client"),
310 _("Select backup for a client before a specified time"),
311 _("Enter a list of files to restore"),
312 _("Enter a list of files to restore before a specified time"),
313 _("Find the JobIds of the most recent backup for a client"),
314 _("Find the JobIds for a backup for a client before a specified time"),
315 _("Enter a list of directories to restore for found JobIds"),
320 /* These keywords are handled in a for loop */
330 /* The keyword below are handled by individual arg lookups */
336 "bootstrap", /* 13 */
343 for (i=1; i<ua->argc; i++) { /* loop through arguments */
344 bool found_kw = false;
345 for (j=0; kw[j]; j++) { /* loop through keywords */
346 if (strcasecmp(kw[j], ua->argk[i]) == 0) {
352 bsendmsg(ua, _("Unknown keyword: %s\n"), ua->argk[i]);
355 /* Found keyword in kw[] list, process it */
358 if (!has_value(ua, i)) {
361 if (*rx->JobIds != 0) {
362 pm_strcat(rx->JobIds, ",");
364 pm_strcat(rx->JobIds, ua->argv[i]);
367 case 1: /* current */
368 bstrutime(date, sizeof(date), time(NULL));
372 if (!has_value(ua, i)) {
375 if (str_to_utime(ua->argv[i]) == 0) {
376 bsendmsg(ua, _("Improper date format: %s\n"), ua->argv[i]);
379 bstrncpy(date, ua->argv[i], sizeof(date));
384 if (!has_value(ua, i)) {
388 bstrutime(date, sizeof(date), time(NULL));
390 if (!get_client_name(ua, rx)) {
393 pm_strcpy(ua->cmd, ua->argv[i]);
394 insert_one_file_or_dir(ua, rx, date, j==4);
398 bstrutime(date, sizeof(date), time(NULL));
400 if (!select_backups_before_date(ua, rx, date)) {
405 case 6: /* pool specified */
406 if (!has_value(ua, i)) {
409 rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
411 bsendmsg(ua, _("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
414 if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
416 bsendmsg(ua, _("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
420 case 7: /* all specified */
424 * All keywords 7 or greater are ignored or handled by a select prompt
432 bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
433 "to be restored. You will be presented several methods\n"
434 "of specifying the JobIds. Then you will be allowed to\n"
435 "select which files from those JobIds are to be restored.\n\n"));
438 /* If choice not already made above, prompt */
444 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
445 for (int i=0; list[i]; i++) {
446 add_prompt(ua, list[i]);
449 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
450 case -1: /* error or cancel */
452 case 0: /* list last 20 Jobs run */
453 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
454 bsendmsg(ua, _("SQL query not authorized.\n"));
457 gui_save = ua->jcr->gui;
459 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
460 ua->jcr->gui = gui_save;
463 case 1: /* list where a file is saved */
464 if (!get_client_name(ua, rx)) {
467 if (!get_cmd(ua, _("Enter Filename (no path):"))) {
470 len = strlen(ua->cmd);
471 fname = (char *)malloc(len * 2 + 1);
472 db_escape_string(fname, ua->cmd, len);
473 Mmsg(rx->query, uar_file, rx->ClientName, fname);
475 gui_save = ua->jcr->gui;
477 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
478 ua->jcr->gui = gui_save;
481 case 2: /* enter a list of JobIds */
482 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
485 pm_strcpy(rx->JobIds, ua->cmd);
487 case 3: /* Enter an SQL list command */
488 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
489 bsendmsg(ua, _("SQL query not authorized.\n"));
492 if (!get_cmd(ua, _("Enter SQL list command: "))) {
495 gui_save = ua->jcr->gui;
497 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
498 ua->jcr->gui = gui_save;
501 case 4: /* Select the most recent backups */
502 bstrutime(date, sizeof(date), time(NULL));
503 if (!select_backups_before_date(ua, rx, date)) {
507 case 5: /* select backup at specified time */
508 if (!get_date(ua, date, sizeof(date))) {
511 if (!select_backups_before_date(ua, rx, date)) {
515 case 6: /* Enter files */
516 bstrutime(date, sizeof(date), time(NULL));
517 if (!get_client_name(ua, rx)) {
520 bsendmsg(ua, _("Enter file names with paths, or < to enter a filename\n"
521 "containing a list of file names with paths, and terminate\n"
522 "them with a blank line.\n"));
524 if (!get_cmd(ua, _("Enter full filename: "))) {
527 len = strlen(ua->cmd);
531 insert_one_file_or_dir(ua, rx, date, false);
534 case 7: /* enter files backed up before specified time */
535 if (!get_date(ua, date, sizeof(date))) {
538 if (!get_client_name(ua, rx)) {
541 bsendmsg(ua, _("Enter file names with paths, or < to enter a filename\n"
542 "containing a list of file names with paths, and terminate\n"
543 "them with a blank line.\n"));
545 if (!get_cmd(ua, _("Enter full filename: "))) {
548 len = strlen(ua->cmd);
552 insert_one_file_or_dir(ua, rx, date, false);
556 case 8: /* Find JobIds for current backup */
557 bstrutime(date, sizeof(date), time(NULL));
558 if (!select_backups_before_date(ua, rx, date)) {
564 case 9: /* Find JobIds for give date */
565 if (!get_date(ua, date, sizeof(date))) {
568 if (!select_backups_before_date(ua, rx, date)) {
574 case 10: /* Enter directories */
575 if (*rx->JobIds != 0) {
576 bsendmsg(ua, _("You have already seleted the following JobIds: %s\n"),
578 } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
579 if (*rx->JobIds != 0 && *ua->cmd) {
580 pm_strcat(rx->JobIds, ",");
582 pm_strcat(rx->JobIds, ua->cmd);
584 if (*rx->JobIds == 0 || *rx->JobIds == '.') {
585 return 0; /* nothing entered, return */
587 bstrutime(date, sizeof(date), time(NULL));
588 if (!get_client_name(ua, rx)) {
591 bsendmsg(ua, _("Enter full directory names or start the name\n"
592 "with a < to indicate it is a filename containing a list\n"
593 "of directories and terminate them with a blank line.\n"));
595 if (!get_cmd(ua, _("Enter directory name: "))) {
598 len = strlen(ua->cmd);
602 /* Add trailing slash to end of directory names */
603 if (ua->cmd[0] != '<' && ua->cmd[len-1] != '/') {
604 strcat(ua->cmd, "/");
606 insert_one_file_or_dir(ua, rx, date, true);
610 case 11: /* Cancel or quit */
615 if (*rx->JobIds == 0) {
616 bsendmsg(ua, _("No Jobs selected.\n"));
619 if (strchr(rx->JobIds,',')) {
620 bsendmsg(ua, _("You have selected the following JobIds: %s\n"), rx->JobIds);
623 bsendmsg(ua, _("You have selected the following JobId: %s\n"), rx->JobIds);
628 for (p=rx->JobIds; ; ) {
629 int stat = get_next_jobid_from_list(&p, &JobId);
631 bsendmsg(ua, _("Invalid JobId in list.\n"));
637 if (jr.JobId == JobId) {
638 continue; /* duplicate of last JobId */
640 memset(&jr, 0, sizeof(JOB_DBR));
642 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
644 bsendmsg(ua, _("Unable to get Job record for JobId=%s: ERR=%s\n"),
645 edit_int64(JobId, ed1), db_strerror(ua->db));
648 if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
649 bsendmsg(ua, _("No authorization. Job \"%s\" not selected.\n"),
653 rx->TotalFiles += jr.JobFiles;
661 static int get_date(UAContext *ua, char *date, int date_len)
663 bsendmsg(ua, _("The restored files will the most current backup\n"
664 "BEFORE the date you specify below.\n\n"));
666 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
669 if (str_to_utime(ua->cmd) != 0) {
672 bsendmsg(ua, _("Improper date format.\n"));
674 bstrncpy(date, ua->cmd, date_len);
679 * Insert a single file, or read a list of files from a file
681 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
691 if ((ffd = fopen(p, "rb")) == NULL) {
693 bsendmsg(ua, _("Cannot open file %s: ERR=%s\n"),
697 while (fgets(file, sizeof(file), ffd)) {
700 if (!insert_dir_into_findex_list(ua, rx, file, date)) {
701 bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
704 if (!insert_file_into_findex_list(ua, rx, file, date)) {
705 bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
713 insert_table_into_findex_list(ua, rx, p);
717 insert_dir_into_findex_list(ua, rx, ua->cmd, date);
719 insert_file_into_findex_list(ua, rx, ua->cmd, date);
726 * For a given file (path+filename), split into path and file, then
727 * lookup the most recent backup in the catalog to get the JobId
728 * and FileIndex, then insert them into the findex list.
730 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
733 strip_trailing_newline(file);
734 split_path_and_filename(rx, file);
735 if (*rx->JobIds == 0) {
736 Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname,
739 Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
740 rx->path, rx->fname, rx->ClientName);
743 /* Find and insert jobid and File Index */
744 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
745 bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
746 rx->query, db_strerror(ua->db));
749 bsendmsg(ua, _("No database record found for: %s\n"), file);
756 * For a given path lookup the most recent backup in the catalog
757 * to get the JobId and FileIndexes of all files in that directory.
759 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
762 strip_trailing_junk(dir);
763 if (*rx->JobIds == 0) {
764 bsendmsg(ua, _("No JobId specified cannot continue.\n"));
767 Mmsg(rx->query, uar_jobid_fileindex_from_dir, rx->JobIds,
768 dir, rx->ClientName);
771 /* Find and insert jobid and File Index */
772 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
773 bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
774 rx->query, db_strerror(ua->db));
777 bsendmsg(ua, _("No database record found for: %s\n"), dir);
784 * Get the JobId and FileIndexes of all files in the specified table
786 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
788 strip_trailing_junk(table);
789 Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
792 /* Find and insert jobid and File Index */
793 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
794 bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
795 rx->query, db_strerror(ua->db));
798 bsendmsg(ua, _("No table found: %s\n"), table);
804 static void split_path_and_filename(RESTORE_CTX *rx, char *name)
808 /* Find path without the filename.
809 * I.e. everything after the last / is a "filename".
810 * OK, maybe it is a directory name, but we treat it like
811 * a filename. If we don't find a / then the whole name
812 * must be a path name (e.g. c:).
814 for (p=f=name; *p; p++) {
816 f = p; /* set pos of last slash */
819 if (*f == '/') { /* did we find a slash? */
820 f++; /* yes, point to filename */
821 } else { /* no, whole thing must be path name */
825 /* If filename doesn't exist (i.e. root directory), we
826 * simply create a blank name consisting of a single
827 * space. This makes handling zero length filenames
832 rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
833 memcpy(rx->fname, f, rx->fnl); /* copy filename */
834 rx->fname[rx->fnl] = 0;
842 rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
843 memcpy(rx->path, name, rx->pnl);
844 rx->path[rx->pnl] = 0;
850 Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
853 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
856 JobId_t JobId, last_JobId;
861 memset(&tree, 0, sizeof(TREE_CTX));
863 * Build the directory tree containing JobIds user selected
865 tree.root = new_tree(rx->TotalFiles);
870 * For display purposes, the same JobId, with different volumes may
871 * appear more than once, however, we only insert it once.
875 tree.FileEstimate = 0;
876 if (get_next_jobid_from_list(&p, &JobId) > 0) {
877 /* Use first JobId as estimate of the number of files to restore */
878 Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
879 if (!db_sql_query(ua->db, rx->query, count_handler, (void *)rx)) {
880 bsendmsg(ua, "%s\n", db_strerror(ua->db));
883 /* Add about 25% more than this job for over estimate */
884 tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
885 tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
888 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
891 if (JobId == last_JobId) {
892 continue; /* eliminate duplicate JobIds */
895 bsendmsg(ua, _("\nBuilding directory tree for JobId %s ... "),
896 edit_int64(JobId, ed1));
899 * Find files for this JobId and insert them in the tree
901 Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
902 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
903 bsendmsg(ua, "%s", db_strerror(ua->db));
906 if (tree.FileCount == 0) {
907 bsendmsg(ua, _("\nThere were no files inserted into the tree, so file selection\n"
908 "is not possible.Most likely your retention policy pruned the files\n"));
909 if (!get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
913 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
914 if (JobId == last_JobId) {
915 continue; /* eliminate duplicate JobIds */
917 add_findex_all(rx->bsr, JobId);
925 bsendmsg(ua, _("\n1 Job, %s files inserted into the tree and marked for extraction.\n"),
926 edit_uint64_with_commas(tree.FileCount, ec1));
929 bsendmsg(ua, _("\n1 Job, %s files inserted into the tree.\n"),
930 edit_uint64_with_commas(tree.FileCount, ec1));
935 bsendmsg(ua, _("\n%d Jobs, %s files inserted into the tree and marked for extraction.\n"),
936 items, edit_uint64_with_commas(tree.FileCount, ec1));
939 bsendmsg(ua, _("\n%d Jobs, %s files inserted into the tree.\n"),
940 items, edit_uint64_with_commas(tree.FileCount, ec1));
944 if (find_arg(ua, NT_("done")) < 0) {
945 /* Let the user interact in selecting which files to restore */
946 OK = user_select_files_from_tree(&tree);
950 * Walk down through the tree finding all files marked to be
951 * extracted making a bootstrap file.
954 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
955 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
956 if (node->extract || node->extract_dir) {
957 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
958 add_findex(rx->bsr, node->JobId, node->FileIndex);
959 if (node->extract && node->type != TN_NEWDIR) {
960 rx->selected_files++; /* count only saved files */
967 free_tree(tree.root); /* free the directory tree */
973 * This routine is used to get the current backup or a backup
974 * before the specified date.
976 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
981 char fileset_name[MAX_NAME_LENGTH];
982 char ed1[50], ed2[50];
983 char pool_select[MAX_NAME_LENGTH];
987 /* Create temp tables */
988 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
989 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
990 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
991 bsendmsg(ua, "%s\n", db_strerror(ua->db));
993 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
994 bsendmsg(ua, "%s\n", db_strerror(ua->db));
997 * Select Client from the Catalog
999 memset(&cr, 0, sizeof(cr));
1000 if (!get_client_dbr(ua, &cr)) {
1003 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1008 memset(&fsr, 0, sizeof(fsr));
1009 i = find_arg_with_value(ua, "FileSet");
1011 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1012 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1013 bsendmsg(ua, _("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1014 db_strerror(ua->db));
1018 if (i < 0) { /* fileset not found */
1019 edit_int64(cr.ClientId, ed1);
1020 Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1021 start_prompt(ua, _("The defined FileSet resources are:\n"));
1022 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1023 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1025 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1026 fileset_name, sizeof(fileset_name)) < 0) {
1030 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1031 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1032 bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1033 bsendmsg(ua, _("This probably means you modified the FileSet.\n"
1034 "Continuing anyway.\n"));
1038 /* If Pool specified, add PoolId specification */
1042 memset(&pr, 0, sizeof(pr));
1043 bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1044 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1045 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
1046 edit_int64(pr.PoolId, ed1));
1048 bsendmsg(ua, _("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1052 /* Find JobId of last Full backup for this client, fileset */
1053 edit_int64(cr.ClientId, ed1);
1054 Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1056 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1057 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1061 /* Find all Volumes used by that JobId */
1062 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1063 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1066 /* Note, this is needed because I don't seem to get the callback
1067 * from the call just above.
1070 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1071 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1073 if (rx->JobTDate == 0) {
1074 bsendmsg(ua, _("No Full backup before %s found.\n"), date);
1078 /* Now find most recent Differental Job after Full save, if any */
1079 Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1080 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1081 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1082 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1084 /* Now update JobTDate to lock onto Differental, if any */
1086 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1087 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1089 if (rx->JobTDate == 0) {
1090 bsendmsg(ua, _("No Full backup before %s found.\n"), date);
1094 /* Now find all Incremental Jobs after Full/dif save */
1095 Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1096 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1097 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1098 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1101 /* Get the JobIds from that list */
1103 rx->last_jobid[0] = 0;
1104 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1105 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1108 if (rx->JobIds[0] != 0) {
1109 /* Display a list of Jobs selected for this restore */
1110 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
1113 bsendmsg(ua, _("No jobs found.\n"));
1117 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1118 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1124 * Return next JobId from comma separated list
1127 * 1 if next JobId returned
1128 * 0 if no more JobIds are in list
1129 * -1 there is an error
1131 int get_next_jobid_from_list(char **p, JobId_t *JobId)
1137 for (int i=0; i<(int)sizeof(jobid); i++) {
1140 } else if (*q == ',') {
1147 if (jobid[0] == 0) {
1149 } else if (!is_a_number(jobid)) {
1150 return -1; /* error */
1153 *JobId = str_to_int64(jobid);
1157 static int count_handler(void *ctx, int num_fields, char **row)
1159 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1160 rx->JobId = str_to_int64(row[0]);
1166 * Callback handler to get JobId and FileIndex for files
1167 * can insert more than one depending on the caller.
1169 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1171 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1172 rx->JobId = str_to_int64(row[0]);
1173 add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1175 rx->selected_files++;
1180 * Callback handler make list of JobIds
1182 static int jobid_handler(void *ctx, int num_fields, char **row)
1184 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1186 if (strcmp(rx->last_jobid, row[0]) == 0) {
1187 return 0; /* duplicate id */
1189 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1190 if (rx->JobIds[0] != 0) {
1191 pm_strcat(rx->JobIds, ",");
1193 pm_strcat(rx->JobIds, row[0]);
1199 * Callback handler to pickup last Full backup JobTDate
1201 static int last_full_handler(void *ctx, int num_fields, char **row)
1203 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1205 rx->JobTDate = str_to_int64(row[1]);
1210 * Callback handler build FileSet name prompt list
1212 static int fileset_handler(void *ctx, int num_fields, char **row)
1214 /* row[0] = FileSet (name) */
1216 add_prompt((UAContext *)ctx, row[0]);
1222 * Free names in the list
1224 static void free_name_list(NAME_LIST *name_list)
1226 for (int i=0; i < name_list->num_ids; i++) {
1227 free(name_list->name[i]);
1229 if (name_list->name) {
1230 free(name_list->name);
1231 name_list->name = NULL;
1233 name_list->max_ids = 0;
1234 name_list->num_ids = 0;
1237 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType)
1242 Dmsg1(200, "Already have store=%s\n", rx.store->name());
1246 * Try looking up Storage by name
1249 foreach_res(store, R_STORAGE) {
1250 if (strcmp(Storage, store->name()) == 0) {
1251 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1260 /* Check if an explicit storage resource is given */
1262 int i = find_arg_with_value(ua, "storage");
1264 store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1265 if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1269 if (store && (store != rx.store)) {
1270 bsendmsg(ua, _("Warning default storage overridden by \"%s\" on command line.\n"),
1273 Dmsg1(200, "Set store=%s\n", rx.store->name());
1278 /* If no storage resource, try to find one from MediaType */
1281 foreach_res(store, R_STORAGE) {
1282 if (strcmp(MediaType, store->media_type) == 0) {
1283 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1285 Dmsg1(200, "Set store=%s\n", rx.store->name());
1286 bsendmsg(ua, _("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1287 Storage, store->name(), MediaType);
1294 bsendmsg(ua, _("\nUnable to find Storage resource for\n"
1295 "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1298 /* Take command line arg, or ask user if none */
1299 rx.store = get_storage_resource(ua, false /* don't use default */);
1300 Dmsg1(200, "Set store=%s\n", rx.store->name());