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);
38 /* Imported variables */
39 extern char *uar_list_jobs, *uar_file, *uar_sel_files;
40 extern char *uar_del_temp, *uar_del_temp1, *uar_create_temp;
41 extern char *uar_create_temp1, *uar_last_full, *uar_full;
42 extern char *uar_inc, *uar_list_temp, *uar_sel_jobid_temp;
43 extern char *uar_sel_all_temp1, *uar_sel_fileset, *uar_mediatype;
44 extern char *uar_jobid_fileindex, *uar_dif, *uar_sel_all_temp;
45 extern char *uar_count_files, *uar_jobids_fileindex;
46 extern char *uar_jobid_fileindex_from_dir;
47 extern char *uar_jobid_fileindex_from_table;
51 /* Forward referenced functions */
52 static int last_full_handler(void *ctx, int num_fields, char **row);
53 static int jobid_handler(void *ctx, int num_fields, char **row);
54 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx);
55 static int fileset_handler(void *ctx, int num_fields, char **row);
56 static void print_name_list(UAContext *ua, NAME_LIST *name_list);
57 static int unique_name_list_handler(void *ctx, int num_fields, char **row);
58 static void free_name_list(NAME_LIST *name_list);
59 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, RESTORE_CTX *rx);
60 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date);
61 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx);
62 static void free_rx(RESTORE_CTX *rx);
63 static void split_path_and_filename(RESTORE_CTX *rx, char *fname);
64 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row);
65 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
67 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
69 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir);
70 static int get_client_name(UAContext *ua, RESTORE_CTX *rx);
71 static int get_date(UAContext *ua, char *date, int date_len);
72 static int count_handler(void *ctx, int num_fields, char **row);
73 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table);
79 int restore_cmd(UAContext *ua, const char *cmd)
81 RESTORE_CTX rx; /* restore context */
86 memset(&rx, 0, sizeof(rx));
87 rx.path = get_pool_memory(PM_FNAME);
88 rx.fname = get_pool_memory(PM_FNAME);
89 rx.JobIds = get_pool_memory(PM_FNAME);
90 rx.query = get_pool_memory(PM_FNAME);
93 i = find_arg_with_value(ua, "where");
95 rx.where = ua->argv[i];
102 /* Ensure there is at least one Restore Job */
104 foreach_res(job, R_JOB) {
105 if (job->JobType == JT_RESTORE) {
106 if (!rx.restore_job) {
107 rx.restore_job = job;
113 if (!rx.restore_jobs) {
115 "No Restore Job Resource found in bacula-dir.conf.\n"
116 "You must create at least one before running this command.\n"));
121 * Request user to select JobIds or files by various different methods
122 * last 20 jobs, where File saved, most recent backup, ...
123 * In the end, a list of files are pumped into
126 switch (user_select_jobids_or_files(ua, &rx)) {
129 case 1: /* selected by jobid */
130 if (!build_directory_tree(ua, &rx)) {
131 bsendmsg(ua, _("Restore not done.\n"));
135 case 2: /* selected by filename, no tree needed */
140 uint32_t selected_files;
141 if (!complete_bsr(ua, rx.bsr)) { /* find Vol, SessId, SessTime from JobIds */
142 bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
145 if (!(selected_files = write_bsr_file(ua, rx))) {
146 bsendmsg(ua, _("No files selected to be restored.\n"));
149 /* If no count of files, use bsr generated value (often wrong) */
150 if (rx.selected_files == 0) {
151 rx.selected_files = selected_files;
153 if (rx.selected_files==1) {
154 bsendmsg(ua, _("\n1 file selected to be restored.\n\n"));
157 bsendmsg(ua, _("\n%u files selected to be restored.\n\n"), rx.selected_files);
160 bsendmsg(ua, _("No files selected to be restored.\n"));
164 if (rx.restore_jobs == 1) {
165 job = rx.restore_job;
167 job = select_restore_job_resource(ua);
173 get_client_name(ua, &rx);
174 if (!rx.ClientName) {
175 bsendmsg(ua, _("No Restore Job resource found!\n"));
179 /* Build run command */
180 fname = get_pool_memory(PM_MESSAGE);
181 make_unique_restore_filename(ua, &fname);
184 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s\""
185 " where=\"%s\" files=%d catalog=\"%s\"",
186 job->hdr.name, rx.ClientName, rx.store?rx.store->hdr.name:"",
187 fname, rx.where, rx.selected_files, ua->catalog->hdr.name);
190 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s\""
191 " files=%d catalog=\"%s\"",
192 job->hdr.name, rx.ClientName, rx.store?rx.store->hdr.name:"",
193 fname, rx.selected_files, ua->catalog->hdr.name);
195 free_pool_memory(fname);
196 if (find_arg(ua, N_("yes")) > 0) {
197 pm_strcat(ua->cmd, " yes"); /* pass it on to the run command */
199 Dmsg1(100, "Submitting: %s\n", ua->cmd);
201 run_cmd(ua, ua->cmd);
211 static void free_rx(RESTORE_CTX *rx)
216 free_pool_memory(rx->JobIds);
220 free_pool_memory(rx->fname);
224 free_pool_memory(rx->path);
228 free_pool_memory(rx->query);
231 free_name_list(&rx->name_list);
234 static bool has_value(UAContext *ua, int i)
237 bsendmsg(ua, _("Missing value for keyword: %s\n"), ua->argk[i]);
243 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
245 /* If no client name specified yet, get it now */
246 if (!rx->ClientName[0]) {
248 /* try command line argument */
249 int i = find_arg_with_value(ua, N_("client"));
251 if (!has_value(ua, i)) {
254 bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
257 memset(&cr, 0, sizeof(cr));
258 if (!get_client_dbr(ua, &cr)) {
261 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
268 * The first step in the restore process is for the user to
269 * select a list of JobIds from which he will subsequently
270 * select which files are to be restored.
272 * Returns: 2 if filename list made
273 * 1 if jobid list made
276 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
279 char date[MAX_TIME_LENGTH];
280 bool have_date = false;
285 const char *list[] = {
286 _("List last 20 Jobs run"),
287 _("List Jobs where a given File is saved"),
288 _("Enter list of comma separated JobIds to select"),
289 _("Enter SQL list command"),
290 _("Select the most recent backup for a client"),
291 _("Select backup for a client before a specified time"),
292 _("Enter a list of files to restore"),
293 _("Enter a list of files to restore before a specified time"),
294 _("Find the JobIds of the most recent backup for a client"),
295 _("Find the JobIds for a backup for a client before a specified time"),
296 _("Enter a list of directories to restore for found JobIds"),
301 /* These keywords are handled in a for loop */
311 /* The keyword below are handled by individual arg lookups */
317 "bootstrap", /* 13 */
324 for (i=1; i<ua->argc; i++) { /* loop through arguments */
325 bool found_kw = false;
326 for (j=0; kw[j]; j++) { /* loop through keywords */
327 if (strcasecmp(kw[j], ua->argk[i]) == 0) {
333 bsendmsg(ua, _("Unknown keyword: %s\n"), ua->argk[i]);
336 /* Found keyword in kw[] list, process it */
339 if (!has_value(ua, i)) {
342 if (*rx->JobIds != 0) {
343 pm_strcat(rx->JobIds, ",");
345 pm_strcat(rx->JobIds, ua->argv[i]);
348 case 1: /* current */
349 bstrutime(date, sizeof(date), time(NULL));
353 if (!has_value(ua, i)) {
356 if (str_to_utime(ua->argv[i]) == 0) {
357 bsendmsg(ua, _("Improper date format: %s\n"), ua->argv[i]);
360 bstrncpy(date, ua->argv[i], sizeof(date));
365 if (!has_value(ua, i)) {
369 bstrutime(date, sizeof(date), time(NULL));
371 if (!get_client_name(ua, rx)) {
374 pm_strcpy(ua->cmd, ua->argv[i]);
375 insert_one_file_or_dir(ua, rx, date, j==4);
376 if (rx->name_list.num_ids) {
377 /* Check MediaType and select storage that corresponds */
378 get_storage_from_mediatype(ua, &rx->name_list, rx);
384 bstrutime(date, sizeof(date), time(NULL));
386 if (!select_backups_before_date(ua, rx, date)) {
391 case 6: /* pool specified */
392 if (!has_value(ua, i)) {
395 rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
397 bsendmsg(ua, _("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
400 if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
402 bsendmsg(ua, _("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
406 case 7: /* all specified */
410 * All keywords 7 or greater are ignored or handled by a select prompt
416 if (rx->name_list.num_ids) {
417 return 2; /* filename list made */
421 bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
422 "to be restored. You will be presented several methods\n"
423 "of specifying the JobIds. Then you will be allowed to\n"
424 "select which files from those JobIds are to be restored.\n\n"));
427 /* If choice not already made above, prompt */
433 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
434 for (int i=0; list[i]; i++) {
435 add_prompt(ua, list[i]);
438 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
439 case -1: /* error or cancel */
441 case 0: /* list last 20 Jobs run */
442 gui_save = ua->jcr->gui;
444 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
445 ua->jcr->gui = gui_save;
448 case 1: /* list where a file is saved */
449 if (!get_client_name(ua, rx)) {
452 if (!get_cmd(ua, _("Enter Filename (no path):"))) {
455 len = strlen(ua->cmd);
456 fname = (char *)malloc(len * 2 + 1);
457 db_escape_string(fname, ua->cmd, len);
458 Mmsg(rx->query, uar_file, rx->ClientName, fname);
460 gui_save = ua->jcr->gui;
462 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
463 ua->jcr->gui = gui_save;
466 case 2: /* enter a list of JobIds */
467 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
470 pm_strcpy(rx->JobIds, ua->cmd);
472 case 3: /* Enter an SQL list command */
473 if (!get_cmd(ua, _("Enter SQL list command: "))) {
476 gui_save = ua->jcr->gui;
478 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
479 ua->jcr->gui = gui_save;
482 case 4: /* Select the most recent backups */
483 bstrutime(date, sizeof(date), time(NULL));
484 if (!select_backups_before_date(ua, rx, date)) {
488 case 5: /* select backup at specified time */
489 if (!get_date(ua, date, sizeof(date))) {
492 if (!select_backups_before_date(ua, rx, date)) {
496 case 6: /* Enter files */
497 bstrutime(date, sizeof(date), time(NULL));
498 if (!get_client_name(ua, rx)) {
501 bsendmsg(ua, _("Enter file names with paths, or < to enter a filename\n"
502 "containg a list of file names with paths, and terminate\n"
503 "them with a blank line.\n"));
505 if (!get_cmd(ua, _("Enter full filename: "))) {
508 len = strlen(ua->cmd);
512 insert_one_file_or_dir(ua, rx, date, false);
514 /* Check MediaType and select storage that corresponds */
515 if (rx->name_list.num_ids) {
516 get_storage_from_mediatype(ua, &rx->name_list, rx);
519 case 7: /* enter files backed up before specified time */
520 if (!get_date(ua, date, sizeof(date))) {
523 if (!get_client_name(ua, rx)) {
526 bsendmsg(ua, _("Enter file names with paths, or < to enter a filename\n"
527 "containg a list of file names with paths, and terminate\n"
528 "them with a blank line.\n"));
530 if (!get_cmd(ua, _("Enter full filename: "))) {
533 len = strlen(ua->cmd);
537 insert_one_file_or_dir(ua, rx, date, false);
539 /* Check MediaType and select storage that corresponds */
540 if (rx->name_list.num_ids) {
541 get_storage_from_mediatype(ua, &rx->name_list, rx);
545 case 8: /* Find JobIds for current backup */
546 bstrutime(date, sizeof(date), time(NULL));
547 if (!select_backups_before_date(ua, rx, date)) {
553 case 9: /* Find JobIds for give date */
554 if (!get_date(ua, date, sizeof(date))) {
557 if (!select_backups_before_date(ua, rx, date)) {
563 case 10: /* Enter directories */
564 if (*rx->JobIds != 0) {
565 bsendmsg(ua, _("You have already seleted the following JobIds: %s\n"),
567 } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
568 if (*rx->JobIds != 0 && *ua->cmd) {
569 pm_strcat(rx->JobIds, ",");
571 pm_strcat(rx->JobIds, ua->cmd);
573 if (*rx->JobIds == 0 || *rx->JobIds == '.') {
574 return 0; /* nothing entered, return */
576 bstrutime(date, sizeof(date), time(NULL));
577 if (!get_client_name(ua, rx)) {
580 bsendmsg(ua, _("Enter full directory names or start the name\n"
581 "with a < to indicate it is a filename containg a list\n"
582 "of directories and terminate them with a blank line.\n"));
584 if (!get_cmd(ua, _("Enter directory name: "))) {
587 len = strlen(ua->cmd);
591 /* Add trailing slash to end of directory names */
592 if (ua->cmd[0] != '<' && ua->cmd[len-1] != '/') {
593 strcat(ua->cmd, "/");
595 insert_one_file_or_dir(ua, rx, date, true);
597 /* Check MediaType and select storage that corresponds */
598 if (rx->name_list.num_ids) {
599 get_storage_from_mediatype(ua, &rx->name_list, rx);
603 case 11: /* Cancel or quit */
608 if (*rx->JobIds == 0) {
609 bsendmsg(ua, _("No Jobs selected.\n"));
612 if (strchr(rx->JobIds,',')) {
613 bsendmsg(ua, _("You have selected the following JobIds: %s\n"), rx->JobIds);
616 bsendmsg(ua, _("You have selected the following JobId: %s\n"), rx->JobIds);
621 for (p=rx->JobIds; ; ) {
622 int stat = get_next_jobid_from_list(&p, &JobId);
624 bsendmsg(ua, _("Invalid JobId in list.\n"));
630 if (jr.JobId == JobId) {
631 continue; /* duplicate of last JobId */
633 memset(&jr, 0, sizeof(JOB_DBR));
635 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
637 bsendmsg(ua, _("Unable to get Job record for JobId=%s: ERR=%s\n"),
638 edit_int64(JobId, ed1), db_strerror(ua->db));
641 if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
642 bsendmsg(ua, _("No authorization. Job \"%s\" not selected.\n"),
646 rx->TotalFiles += jr.JobFiles;
654 static int get_date(UAContext *ua, char *date, int date_len)
656 bsendmsg(ua, _("The restored files will the most current backup\n"
657 "BEFORE the date you specify below.\n\n"));
659 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
662 if (str_to_utime(ua->cmd) != 0) {
665 bsendmsg(ua, _("Improper date format.\n"));
667 bstrncpy(date, ua->cmd, date_len);
672 * Insert a single file, or read a list of files from a file
674 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
684 if ((ffd = fopen(p, "r")) == NULL) {
686 bsendmsg(ua, _("Cannot open file %s: ERR=%s\n"),
690 while (fgets(file, sizeof(file), ffd)) {
693 if (!insert_dir_into_findex_list(ua, rx, file, date)) {
694 bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
697 if (!insert_file_into_findex_list(ua, rx, file, date)) {
698 bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
706 insert_table_into_findex_list(ua, rx, p);
710 insert_dir_into_findex_list(ua, rx, ua->cmd, date);
712 insert_file_into_findex_list(ua, rx, ua->cmd, date);
719 * For a given file (path+filename), split into path and file, then
720 * lookup the most recent backup in the catalog to get the JobId
721 * and FileIndex, then insert them into the findex list.
723 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
728 strip_trailing_junk(file);
729 split_path_and_filename(rx, file);
730 if (*rx->JobIds == 0) {
731 Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname,
734 Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
735 rx->path, rx->fname, rx->ClientName);
738 /* Find and insert jobid and File Index */
739 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
740 bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
741 rx->query, db_strerror(ua->db));
744 bsendmsg(ua, _("No database record found for: %s\n"), file);
748 * Find the MediaTypes for this JobId and add to the name_list
750 Mmsg(rx->query, uar_mediatype, edit_int64(rx->JobId, ed1));
751 if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
752 bsendmsg(ua, "%s", db_strerror(ua->db));
759 * For a given path lookup the most recent backup in the catalog
760 * to get the JobId and FileIndexes of all files in that directory.
762 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
767 strip_trailing_junk(dir);
768 if (*rx->JobIds == 0) {
769 bsendmsg(ua, _("No JobId specified cannot continue.\n"));
772 Mmsg(rx->query, uar_jobid_fileindex_from_dir, rx->JobIds,
773 dir, rx->ClientName);
776 /* Find and insert jobid and File Index */
777 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
778 bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
779 rx->query, db_strerror(ua->db));
782 bsendmsg(ua, _("No database record found for: %s\n"), dir);
786 * Find the MediaTypes for this JobId and add to the name_list
788 Mmsg(rx->query, uar_mediatype, edit_int64(rx->JobId, ed1));
789 if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
790 bsendmsg(ua, "%s", db_strerror(ua->db));
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)
803 strip_trailing_junk(table);
804 Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
807 /* Find and insert jobid and File Index */
808 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
809 bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
810 rx->query, db_strerror(ua->db));
813 bsendmsg(ua, _("No table found: %s\n"), table);
817 * Find the MediaTypes for this JobId and add to the name_list
819 Mmsg(rx->query, uar_mediatype, edit_int64(rx->JobId, ed1));
820 if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
821 bsendmsg(ua, "%s", db_strerror(ua->db));
827 static void split_path_and_filename(RESTORE_CTX *rx, char *name)
831 /* Find path without the filename.
832 * I.e. everything after the last / is a "filename".
833 * OK, maybe it is a directory name, but we treat it like
834 * a filename. If we don't find a / then the whole name
835 * must be a path name (e.g. c:).
837 for (p=f=name; *p; p++) {
839 f = p; /* set pos of last slash */
842 if (*f == '/') { /* did we find a slash? */
843 f++; /* yes, point to filename */
844 } else { /* no, whole thing must be path name */
848 /* If filename doesn't exist (i.e. root directory), we
849 * simply create a blank name consisting of a single
850 * space. This makes handling zero length filenames
855 rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
856 memcpy(rx->fname, f, rx->fnl); /* copy filename */
857 rx->fname[rx->fnl] = 0;
865 rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
866 memcpy(rx->path, name, rx->pnl);
867 rx->path[rx->pnl] = 0;
873 Dmsg2(100, "sllit path=%s file=%s\n", rx->path, rx->fname);
876 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
879 JobId_t JobId, last_JobId;
884 memset(&tree, 0, sizeof(TREE_CTX));
886 * Build the directory tree containing JobIds user selected
888 tree.root = new_tree(rx->TotalFiles);
893 * For display purposes, the same JobId, with different volumes may
894 * appear more than once, however, we only insert it once.
898 tree.FileEstimate = 0;
899 if (get_next_jobid_from_list(&p, &JobId) > 0) {
900 /* Use first JobId as estimate of the number of files to restore */
901 Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
902 if (!db_sql_query(ua->db, rx->query, count_handler, (void *)rx)) {
903 bsendmsg(ua, "%s\n", db_strerror(ua->db));
906 /* Add about 25% more than this job for over estimate */
907 tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
908 tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
911 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
914 if (JobId == last_JobId) {
915 continue; /* eliminate duplicate JobIds */
918 bsendmsg(ua, _("\nBuilding directory tree for JobId %s ... "),
919 edit_int64(JobId, ed1));
922 * Find files for this JobId and insert them in the tree
924 Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
925 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
926 bsendmsg(ua, "%s", db_strerror(ua->db));
929 * Find the MediaTypes for this JobId and add to the name_list
931 Mmsg(rx->query, uar_mediatype, edit_int64(JobId, ed1));
932 if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
933 bsendmsg(ua, "%s", db_strerror(ua->db));
936 if (tree.FileCount == 0) {
937 bsendmsg(ua, _("\nThere were no files inserted into the tree, so file selection\n"
938 "is not possible.Most likely your retention policy pruned the files\n"));
939 if (!get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
943 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
944 if (JobId == last_JobId) {
945 continue; /* eliminate duplicate JobIds */
947 add_findex_all(rx->bsr, JobId);
955 bsendmsg(ua, _("\n1 Job, %s files inserted into the tree and marked for extraction.\n"),
956 edit_uint64_with_commas(tree.FileCount, ec1));
959 bsendmsg(ua, _("\n1 Job, %s files inserted into the tree.\n"),
960 edit_uint64_with_commas(tree.FileCount, ec1));
965 bsendmsg(ua, _("\n%d Jobs, %s files inserted into the tree and marked for extraction.\n"),
966 items, edit_uint64_with_commas(tree.FileCount, ec1));
969 bsendmsg(ua, _("\n%d Jobs, %s files inserted into the tree.\n"),
970 items, edit_uint64_with_commas(tree.FileCount, ec1));
974 /* Check MediaType and select storage that corresponds */
975 get_storage_from_mediatype(ua, &rx->name_list, rx);
977 if (find_arg(ua, N_("done")) < 0) {
978 /* Let the user interact in selecting which files to restore */
979 OK = user_select_files_from_tree(&tree);
983 * Walk down through the tree finding all files marked to be
984 * extracted making a bootstrap file.
987 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
988 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
989 if (node->extract || node->extract_dir) {
990 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
991 add_findex(rx->bsr, node->JobId, node->FileIndex);
992 if (node->extract && node->type != TN_NEWDIR) {
993 rx->selected_files++; /* count only saved files */
1000 free_tree(tree.root); /* free the directory tree */
1006 * This routine is used to get the current backup or a backup
1007 * before the specified date.
1009 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
1014 char fileset_name[MAX_NAME_LENGTH];
1015 char ed1[50], ed2[50];
1016 char pool_select[MAX_NAME_LENGTH];
1020 /* Create temp tables */
1021 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1022 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1023 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
1024 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1026 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
1027 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1030 * Select Client from the Catalog
1032 memset(&cr, 0, sizeof(cr));
1033 if (!get_client_dbr(ua, &cr)) {
1036 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1041 memset(&fsr, 0, sizeof(fsr));
1042 i = find_arg_with_value(ua, "FileSet");
1044 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1045 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1046 bsendmsg(ua, _("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1047 db_strerror(ua->db));
1051 if (i < 0) { /* fileset not found */
1052 edit_int64(cr.ClientId, ed1);
1053 Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1054 start_prompt(ua, _("The defined FileSet resources are:\n"));
1055 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1056 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1058 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1059 fileset_name, sizeof(fileset_name)) < 0) {
1063 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1064 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1065 bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1066 bsendmsg(ua, _("This probably means you modified the FileSet.\n"
1067 "Continuing anyway.\n"));
1071 /* If Pool specified, add PoolId specification */
1075 memset(&pr, 0, sizeof(pr));
1076 bstrncpy(pr.Name, rx->pool->hdr.name, sizeof(pr.Name));
1077 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1078 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
1079 edit_int64(pr.PoolId, ed1));
1081 bsendmsg(ua, _("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1085 /* Find JobId of last Full backup for this client, fileset */
1086 edit_int64(cr.ClientId, ed1);
1087 Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1089 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1090 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1094 /* Find all Volumes used by that JobId */
1095 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1096 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1099 /* Note, this is needed because I don't seem to get the callback
1100 * from the call just above.
1103 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1104 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1106 if (rx->JobTDate == 0) {
1107 bsendmsg(ua, _("No Full backup before %s found.\n"), date);
1111 /* Now find most recent Differental Job after Full save, if any */
1112 Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1113 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1114 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1115 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1117 /* Now update JobTDate to lock onto Differental, if any */
1119 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1120 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1122 if (rx->JobTDate == 0) {
1123 bsendmsg(ua, _("No Full backup before %s found.\n"), date);
1127 /* Now find all Incremental Jobs after Full/dif save */
1128 Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1129 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1130 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1131 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1134 /* Get the JobIds from that list */
1136 rx->last_jobid[0] = 0;
1137 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1138 bsendmsg(ua, "%s\n", db_strerror(ua->db));
1141 if (rx->JobIds[0] != 0) {
1142 /* Display a list of Jobs selected for this restore */
1143 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
1146 bsendmsg(ua, _("No jobs found.\n"));
1150 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1151 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1157 * Return next JobId from comma separated list
1160 * 1 if next JobId returned
1161 * 0 if no more JobIds are in list
1162 * -1 there is an error
1164 int get_next_jobid_from_list(char **p, JobId_t *JobId)
1170 for (int i=0; i<(int)sizeof(jobid); i++) {
1173 } else if (*q == ',') {
1180 if (jobid[0] == 0) {
1182 } else if (!is_a_number(jobid)) {
1183 return -1; /* error */
1186 *JobId = str_to_int64(jobid);
1190 static int count_handler(void *ctx, int num_fields, char **row)
1192 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1193 rx->JobId = str_to_int64(row[0]);
1199 * Callback handler to get JobId and FileIndex for files
1200 * can insert more than one depending on the caller.
1202 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1204 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1205 rx->JobId = str_to_int64(row[0]);
1206 add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1208 rx->selected_files++;
1213 * Callback handler make list of JobIds
1215 static int jobid_handler(void *ctx, int num_fields, char **row)
1217 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1219 if (strcmp(rx->last_jobid, row[0]) == 0) {
1220 return 0; /* duplicate id */
1222 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1223 if (rx->JobIds[0] != 0) {
1224 pm_strcat(rx->JobIds, ",");
1226 pm_strcat(rx->JobIds, row[0]);
1232 * Callback handler to pickup last Full backup JobTDate
1234 static int last_full_handler(void *ctx, int num_fields, char **row)
1236 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1238 rx->JobTDate = str_to_int64(row[1]);
1243 * Callback handler build FileSet name prompt list
1245 static int fileset_handler(void *ctx, int num_fields, char **row)
1247 /* row[0] = FileSet (name) */
1249 add_prompt((UAContext *)ctx, row[0]);
1255 * Called here with each name to be added to the list. The name is
1256 * added to the list if it is not already in the list.
1258 * Used to make unique list of FileSets and MediaTypes
1260 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1262 NAME_LIST *name = (NAME_LIST *)ctx;
1264 if (name->num_ids == MAX_ID_LIST_LEN) {
1267 if (name->num_ids == name->max_ids) {
1268 if (name->max_ids == 0) {
1269 name->max_ids = 1000;
1270 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1272 name->max_ids = (name->max_ids * 3) / 2;
1273 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1276 for (int i=0; i<name->num_ids; i++) {
1277 if (strcmp(name->name[i], row[0]) == 0) {
1278 return 0; /* already in list, return */
1281 /* Add new name to list */
1282 name->name[name->num_ids++] = bstrdup(row[0]);
1288 * Print names in the list
1290 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1292 for (int i=0; i < name_list->num_ids; i++) {
1293 bsendmsg(ua, "%s\n", name_list->name[i]);
1299 * Free names in the list
1301 static void free_name_list(NAME_LIST *name_list)
1303 for (int i=0; i < name_list->num_ids; i++) {
1304 free(name_list->name[i]);
1306 if (name_list->name) {
1307 free(name_list->name);
1308 name_list->name = NULL;
1310 name_list->max_ids = 0;
1311 name_list->num_ids = 0;
1314 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, RESTORE_CTX *rx)
1318 if (name_list->num_ids > 1) {
1319 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1320 "Restore is not possible. The MediaTypes used are:\n"));
1321 print_name_list(ua, name_list);
1322 rx->store = select_storage_resource(ua);
1326 if (name_list->num_ids == 0) {
1327 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1328 rx->store = select_storage_resource(ua);
1335 * We have a single MediaType, look it up in our Storage resource
1338 foreach_res(store, R_STORAGE) {
1339 if (strcmp(name_list->name[0], store->media_type) == 0) {
1340 if (acl_access_ok(ua, Storage_ACL, store->hdr.name)) {
1349 /* Check if an explicit storage resource is given */
1351 int i = find_arg_with_value(ua, "storage");
1353 store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1354 if (store && !acl_access_ok(ua, Storage_ACL, store->hdr.name)) {
1358 if (store && (store != rx->store)) {
1359 bsendmsg(ua, _("Warning default storage overridden by %s on command line.\n"),
1366 /* Take command line arg, or ask user if none */
1367 rx->store = get_storage_resource(ua, false /* don't use default */);
1370 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1371 "MediaType \"%s\", needed by the Jobs you selected.\n"
1372 "You will be allowed to select a Storage device later.\n"),
1373 name_list->name[0]);