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
17 Copyright (C) 2002-2004 Kern Sibbald and John Walker
19 This program is free software; you can redistribute it and/or
20 modify it under the terms of the GNU General Public License as
21 published by the Free Software Foundation; either version 2 of
22 the License, or (at your option) any later version.
24 This program is distributed in the hope that it will be useful,
25 but WITHOUT ANY WARRANTY; without even the implied warranty of
26 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 General Public License for more details.
29 You should have received a copy of the GNU General Public
30 License along with this program; if not, write to the Free
31 Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
40 /* Imported functions */
41 extern int run_cmd(UAContext *ua, char *cmd);
42 extern void print_bsr(UAContext *ua, RBSR *bsr);
44 /* Imported variables */
45 extern char *uar_list_jobs, *uar_file, *uar_sel_files;
46 extern char *uar_del_temp, *uar_del_temp1, *uar_create_temp;
47 extern char *uar_create_temp1, *uar_last_full, *uar_full;
48 extern char *uar_inc, *uar_list_temp, *uar_sel_jobid_temp;
49 extern char *uar_sel_all_temp1, *uar_sel_fileset, *uar_mediatype;
50 extern char *uar_jobid_fileindex, *uar_dif, *uar_sel_all_temp;
54 char **name; /* list of names */
55 int num_ids; /* ids stored */
56 int max_ids; /* size of array */
57 int num_del; /* number deleted */
58 int tot_ids; /* total to process */
62 /* Main structure for obtaining JobIds or Files to be restored */
67 char ClientName[MAX_NAME_LENGTH];
69 POOLMEM *JobIds; /* User entered string of JobIds */
74 uint32_t selected_files;
77 POOLMEM *fname; /* filename only */
78 POOLMEM *path; /* path only */
80 int fnl; /* filename length */
81 int pnl; /* path length */
87 #define MAX_ID_LIST_LEN 1000000
90 /* Forward referenced functions */
91 static int last_full_handler(void *ctx, int num_fields, char **row);
92 static int jobid_handler(void *ctx, int num_fields, char **row);
93 static int get_next_jobid_from_list(char **p, uint32_t *JobId);
94 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx);
95 static int fileset_handler(void *ctx, int num_fields, char **row);
96 static void print_name_list(UAContext *ua, NAME_LIST *name_list);
97 static int unique_name_list_handler(void *ctx, int num_fields, char **row);
98 static void free_name_list(NAME_LIST *name_list);
99 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, RESTORE_CTX *rx);
100 static int select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date);
101 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx);
102 static void free_rx(RESTORE_CTX *rx);
103 static void split_path_and_filename(RESTORE_CTX *rx, char *fname);
104 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row);
105 static int insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
107 static void insert_one_file(UAContext *ua, RESTORE_CTX *rx, char *date);
108 static int get_client_name(UAContext *ua, RESTORE_CTX *rx);
109 static int get_date(UAContext *ua, char *date, int date_len);
115 int restore_cmd(UAContext *ua, char *cmd)
117 RESTORE_CTX rx; /* restore context */
121 memset(&rx, 0, sizeof(rx));
122 rx.path = get_pool_memory(PM_FNAME);
123 rx.fname = get_pool_memory(PM_FNAME);
124 rx.JobIds = get_pool_memory(PM_FNAME);
125 rx.query = get_pool_memory(PM_FNAME);
128 i = find_arg_with_value(ua, "where");
130 rx.where = ua->argv[i];
137 /* Ensure there is at least one Restore Job */
139 foreach_res(job, R_JOB) {
140 if (job->JobType == JT_RESTORE) {
141 if (!rx.restore_job) {
142 rx.restore_job = job;
148 if (!rx.restore_jobs) {
150 "No Restore Job Resource found. You must create at least\n"
151 "one before running this command.\n"));
156 * Request user to select JobIds or files by various different methods
157 * last 20 jobs, where File saved, most recent backup, ...
158 * In the end, a list of files are pumped into
161 switch (user_select_jobids_or_files(ua, &rx)) {
164 case 1: /* select by jobid */
165 if (!build_directory_tree(ua, &rx)) {
166 bsendmsg(ua, _("Restore not done.\n"));
170 case 2: /* select by filename, no tree needed */
175 if (!complete_bsr(ua, rx.bsr)) { /* find Vol, SessId, SessTime from JobIds */
176 bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
179 if (!write_bsr_file(ua, rx.bsr)) {
182 bsendmsg(ua, _("\n%u file%s selected to be restored.\n\n"), rx.selected_files,
183 rx.selected_files==1?"":"s");
185 bsendmsg(ua, _("No files selected to be restored.\n"));
189 if (rx.restore_jobs == 1) {
190 job = rx.restore_job;
192 job = select_restore_job_resource(ua);
198 get_client_name(ua, &rx);
199 if (!rx.ClientName) {
200 bsendmsg(ua, _("No Restore Job resource found!\n"));
204 /* Build run command */
207 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\""
208 " where=\"%s\" files=%d",
209 job->hdr.name, rx.ClientName, rx.store?rx.store->hdr.name:"",
210 working_directory, rx.where, rx.selected_files);
213 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
214 job->hdr.name, rx.ClientName, rx.store?rx.store->hdr.name:"",
217 if (find_arg(ua, _("yes")) > 0) {
218 pm_strcat(&ua->cmd, " yes"); /* pass it on to the run command */
220 Dmsg1(400, "Submitting: %s\n", ua->cmd);
222 run_cmd(ua, ua->cmd);
224 bsendmsg(ua, _("Restore command done.\n"));
234 static void free_rx(RESTORE_CTX *rx)
239 free_pool_memory(rx->JobIds);
243 free_pool_memory(rx->fname);
247 free_pool_memory(rx->path);
251 free_pool_memory(rx->query);
254 free_name_list(&rx->name_list);
257 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
259 /* If no client name specified yet, get it now */
260 if (!rx->ClientName[0]) {
262 /* try command line argument */
263 int i = find_arg_with_value(ua, _("client"));
265 bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
268 memset(&cr, 0, sizeof(cr));
269 if (!get_client_dbr(ua, &cr)) {
272 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
278 * The first step in the restore process is for the user to
279 * select a list of JobIds from which he will subsequently
280 * select which files are to be restored.
282 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
285 char date[MAX_TIME_LENGTH];
286 bool have_date = false;
292 "List last 20 Jobs run",
293 "List Jobs where a given File is saved",
294 "Enter list of comma separated JobIds to select",
295 "Enter SQL list command",
296 "Select the most recent backup for a client",
297 "Select backup for a client before a specified time",
298 "Enter a list of files to restore",
299 "Enter a list of files to restore before a specified time",
320 for (i=1; i<ua->argc; i++) { /* loop through arguments */
321 bool found_kw = false;
322 for (j=0; kw[j]; j++) { /* loop through keywords */
323 if (strcasecmp(kw[j], ua->argk[i]) == 0) {
329 bsendmsg(ua, _("Unknown keyword: %s\n"), ua->argk[i]);
332 /* Found keyword in kw[] list, process it */
335 if (*rx->JobIds != 0) {
336 pm_strcat(&rx->JobIds, ",");
338 pm_strcat(&rx->JobIds, ua->argv[i]);
341 case 1: /* current */
342 bstrutime(date, sizeof(date), time(NULL));
346 if (str_to_utime(ua->argv[i]) == 0) {
347 bsendmsg(ua, _("Improper date format: %s\n"), ua->argv[i]);
350 bstrncpy(date, ua->argv[i], sizeof(date));
355 bstrutime(date, sizeof(date), time(NULL));
357 if (!get_client_name(ua, rx)) {
360 pm_strcpy(&ua->cmd, ua->argv[i]);
361 insert_one_file(ua, rx, date);
362 if (rx->name_list.num_ids) {
363 /* Check MediaType and select storage that corresponds */
364 get_storage_from_mediatype(ua, &rx->name_list, rx);
370 bstrutime(date, sizeof(date), time(NULL));
372 if (!select_backups_before_date(ua, rx, date)) {
377 case 5: /* pool specified */
378 rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
380 bsendmsg(ua, _("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
383 if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
385 bsendmsg(ua, _("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
390 * All keywords 6 or greater are ignored or handled by a select prompt
396 if (rx->name_list.num_ids) {
397 return 2; /* filename list made */
401 bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
402 "to be restored. You will be presented several methods\n"
403 "of specifying the JobIds. Then you will be allowed to\n"
404 "select which files from those JobIds are to be restored.\n\n"));
407 /* If choice not already made above, prompt */
412 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
413 for (int i=0; list[i]; i++) {
414 add_prompt(ua, list[i]);
417 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
420 case 0: /* list last 20 Jobs run */
421 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
424 case 1: /* list where a file is saved */
425 if (!get_cmd(ua, _("Enter Filename: "))) {
428 len = strlen(ua->cmd);
429 fname = (char *)malloc(len * 2 + 1);
430 db_escape_string(fname, ua->cmd, len);
431 Mmsg(&rx->query, uar_file, fname);
433 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
436 case 2: /* enter a list of JobIds */
437 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
440 pm_strcpy(&rx->JobIds, ua->cmd);
442 case 3: /* Enter an SQL list command */
443 if (!get_cmd(ua, _("Enter SQL list command: "))) {
446 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
449 case 4: /* Select the most recent backups */
450 bstrutime(date, sizeof(date), time(NULL));
451 if (!select_backups_before_date(ua, rx, date)) {
455 case 5: /* select backup at specified time */
456 if (!get_date(ua, date, sizeof(date))) {
459 if (!select_backups_before_date(ua, rx, date)) {
463 case 6: /* Enter files */
464 bstrutime(date, sizeof(date), time(NULL));
465 if (!get_client_name(ua, rx)) {
468 bsendmsg(ua, _("Enter file names, or < to enter a filename\n"
469 "containg a list of file names, and terminate\n"
470 "them with a blank line.\n"));
472 if (!get_cmd(ua, _("Enter filename: "))) {
475 len = strlen(ua->cmd);
479 insert_one_file(ua, rx, date);
481 /* Check MediaType and select storage that corresponds */
482 if (rx->name_list.num_ids) {
483 get_storage_from_mediatype(ua, &rx->name_list, rx);
486 case 7: /* enter files backed up before specified time */
487 if (!get_date(ua, date, sizeof(date))) {
490 if (!get_client_name(ua, rx)) {
493 bsendmsg(ua, _("Enter file names, or < to enter a filename\n"
494 "containg a list of file names, and terminate\n"
495 "them with a blank line.\n"));
497 if (!get_cmd(ua, _("Enter filename: "))) {
500 len = strlen(ua->cmd);
504 insert_one_file(ua, rx, date);
506 /* Check MediaType and select storage that corresponds */
507 if (rx->name_list.num_ids) {
508 get_storage_from_mediatype(ua, &rx->name_list, rx);
513 case 8: /* Cancel or quit */
518 if (*rx->JobIds == 0) {
519 bsendmsg(ua, _("No Jobs selected.\n"));
522 bsendmsg(ua, _("You have selected the following JobId%s: %s\n"),
523 strchr(rx->JobIds,',')?"s":"",rx->JobIds);
525 memset(&jr, 0, sizeof(JOB_DBR));
528 for (p=rx->JobIds; ; ) {
529 int stat = get_next_jobid_from_list(&p, &JobId);
531 bsendmsg(ua, _("Invalid JobId in list.\n"));
537 if (jr.JobId == JobId) {
538 continue; /* duplicate of last JobId */
541 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
542 bsendmsg(ua, _("Unable to get Job record for JobId=%u: ERR=%s\n"),
543 JobId, db_strerror(ua->db));
546 if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
547 bsendmsg(ua, _("No authorization. Job \"%s\" not selected.\n"),
551 rx->TotalFiles += jr.JobFiles;
559 static int get_date(UAContext *ua, char *date, int date_len)
561 bsendmsg(ua, _("The restored files will the most current backup\n"
562 "BEFORE the date you specify below.\n\n"));
564 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
567 if (str_to_utime(ua->cmd) != 0) {
570 bsendmsg(ua, _("Improper date format.\n"));
572 bstrncpy(date, ua->cmd, date_len);
577 * Insert a single file, or read a list of files from a file
579 static void insert_one_file(UAContext *ua, RESTORE_CTX *rx, char *date)
589 if ((ffd = fopen(p, "r")) == NULL) {
590 bsendmsg(ua, _("Cannot open file %s: ERR=%s\n"),
594 while (fgets(file, sizeof(file), ffd)) {
596 if (!insert_file_into_findex_list(ua, rx, file, date)) {
597 bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
603 insert_file_into_findex_list(ua, rx, ua->cmd, date);
609 * For a given file (path+filename), split into path and file, then
610 * lookup the most recent backup in the catalog to get the JobId
611 * and FileIndex, then insert them into the findex list.
613 static int insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
616 strip_trailing_junk(file);
617 split_path_and_filename(rx, file);
618 Mmsg(&rx->query, uar_jobid_fileindex, date, rx->path, rx->fname, rx->ClientName);
620 /* Find and insert jobid and File Index */
621 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
622 bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
623 rx->query, db_strerror(ua->db));
626 bsendmsg(ua, _("No database record found for: %s\n"), file);
629 rx->selected_files++;
631 * Find the MediaTypes for this JobId and add to the name_list
633 Mmsg(&rx->query, uar_mediatype, rx->JobId);
634 if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
635 bsendmsg(ua, "%s", db_strerror(ua->db));
641 static void split_path_and_filename(RESTORE_CTX *rx, char *name)
645 /* Find path without the filename.
646 * I.e. everything after the last / is a "filename".
647 * OK, maybe it is a directory name, but we treat it like
648 * a filename. If we don't find a / then the whole name
649 * must be a path name (e.g. c:).
651 for (p=f=name; *p; p++) {
653 f = p; /* set pos of last slash */
656 if (*f == '/') { /* did we find a slash? */
657 f++; /* yes, point to filename */
658 } else { /* no, whole thing must be path name */
662 /* If filename doesn't exist (i.e. root directory), we
663 * simply create a blank name consisting of a single
664 * space. This makes handling zero length filenames
669 rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
670 memcpy(rx->fname, f, rx->fnl); /* copy filename */
671 rx->fname[rx->fnl] = 0;
673 rx->fname[0] = ' '; /* blank filename */
680 rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
681 memcpy(rx->path, name, rx->pnl);
682 rx->path[rx->pnl] = 0;
689 Dmsg2(100, "sllit path=%s file=%s\n", rx->path, rx->fname);
692 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
695 JobId_t JobId, last_JobId;
700 memset(&tree, 0, sizeof(TREE_CTX));
702 * Build the directory tree containing JobIds user selected
704 tree.root = new_tree(rx->TotalFiles);
705 tree.root->fname = nofname;
709 * For display purposes, the same JobId, with different volumes may
710 * appear more than once, however, we only insert it once.
713 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
715 if (JobId == last_JobId) {
716 continue; /* eliminate duplicate JobIds */
719 bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
722 * Find files for this JobId and insert them in the tree
724 Mmsg(&rx->query, uar_sel_files, JobId);
725 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
726 bsendmsg(ua, "%s", db_strerror(ua->db));
729 * Find the MediaTypes for this JobId and add to the name_list
731 Mmsg(&rx->query, uar_mediatype, JobId);
732 if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
733 bsendmsg(ua, "%s", db_strerror(ua->db));
736 bsendmsg(ua, "%d Job%s inserted into the tree and marked for extraction.\n",
737 items, items==1?"":"s");
739 /* Check MediaType and select storage that corresponds */
740 get_storage_from_mediatype(ua, &rx->name_list, rx);
742 if (find_arg(ua, _("all")) < 0) {
743 /* Let the user interact in selecting which files to restore */
744 OK = user_select_files_from_tree(&tree);
748 * Walk down through the tree finding all files marked to be
749 * extracted making a bootstrap file.
752 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
753 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
754 if (node->extract || node->extract_dir) {
755 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
756 add_findex(rx->bsr, node->JobId, node->FileIndex);
757 rx->selected_files++;
762 free_tree(tree.root); /* free the directory tree */
768 * This routine is used to get the current backup or a backup
769 * before the specified date.
771 static int select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
776 char fileset_name[MAX_NAME_LENGTH];
778 char pool_select[MAX_NAME_LENGTH];
782 /* Create temp tables */
783 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
784 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
785 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
786 bsendmsg(ua, "%s\n", db_strerror(ua->db));
788 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
789 bsendmsg(ua, "%s\n", db_strerror(ua->db));
792 * Select Client from the Catalog
794 memset(&cr, 0, sizeof(cr));
795 if (!get_client_dbr(ua, &cr)) {
798 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
803 memset(&fsr, 0, sizeof(fsr));
804 i = find_arg_with_value(ua, "FileSet");
806 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
807 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
808 bsendmsg(ua, _("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
809 db_strerror(ua->db));
813 if (i < 0) { /* fileset not found */
814 Mmsg(&rx->query, uar_sel_fileset, cr.ClientId, cr.ClientId);
815 start_prompt(ua, _("The defined FileSet resources are:\n"));
816 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
817 bsendmsg(ua, "%s\n", db_strerror(ua->db));
819 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
820 fileset_name, sizeof(fileset_name)) < 0) {
824 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
825 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
826 bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
827 bsendmsg(ua, _("This probably means you modified the FileSet.\n"
828 "Continuing anyway.\n"));
832 /* If Pool specified, add PoolId specification */
836 memset(&pr, 0, sizeof(pr));
837 bstrncpy(pr.Name, rx->pool->hdr.name, sizeof(pr.Name));
838 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
839 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%u ", pr.PoolId);
841 bsendmsg(ua, _("Pool \"%s\" not found, using any pool.\n"), pr.Name);
845 /* Find JobId of last Full backup for this client, fileset */
846 Mmsg(&rx->query, uar_last_full, cr.ClientId, cr.ClientId, date, fsr.FileSet,
848 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
849 bsendmsg(ua, "%s\n", db_strerror(ua->db));
853 /* Find all Volumes used by that JobId */
854 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
855 bsendmsg(ua, "%s\n", db_strerror(ua->db));
858 /* Note, this is needed because I don't seem to get the callback
859 * from the call just above.
862 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
863 bsendmsg(ua, "%s\n", db_strerror(ua->db));
865 if (rx->JobTDate == 0) {
866 bsendmsg(ua, _("No Full backup before %s found.\n"), date);
870 /* Now find most recent Differental Job after Full save, if any */
871 Mmsg(&rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
872 cr.ClientId, fsr.FileSet, pool_select);
873 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
874 bsendmsg(ua, "%s\n", db_strerror(ua->db));
876 /* Now update JobTDate to lock onto Differental, if any */
878 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
879 bsendmsg(ua, "%s\n", db_strerror(ua->db));
881 if (rx->JobTDate == 0) {
882 bsendmsg(ua, _("No Full backup before %s found.\n"), date);
886 /* Now find all Incremental Jobs after Full/dif save */
887 Mmsg(&rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
888 cr.ClientId, fsr.FileSet, pool_select);
889 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
890 bsendmsg(ua, "%s\n", db_strerror(ua->db));
893 /* Get the JobIds from that list */
895 rx->last_jobid[0] = 0;
896 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
897 bsendmsg(ua, "%s\n", db_strerror(ua->db));
900 if (rx->JobIds[0] != 0) {
901 /* Display a list of Jobs selected for this restore */
902 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
904 bsendmsg(ua, _("No jobs found.\n"));
910 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
911 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
916 /* Return next JobId from comma separated list */
917 static int get_next_jobid_from_list(char **p, uint32_t *JobId)
923 for (int i=0; i<(int)sizeof(jobid); i++) {
924 if (*q == ',' || *q == 0) {
931 if (jobid[0] == 0 || !is_a_number(jobid)) {
935 *JobId = strtoul(jobid, NULL, 10);
940 * Callback handler to get JobId and FileIndex for files
942 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
944 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
945 rx->JobId = atoi(row[0]);
946 add_findex(rx->bsr, rx->JobId, atoi(row[1]));
952 * Callback handler make list of JobIds
954 static int jobid_handler(void *ctx, int num_fields, char **row)
956 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
958 if (strcmp(rx->last_jobid, row[0]) == 0) {
959 return 0; /* duplicate id */
961 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
962 if (rx->JobIds[0] != 0) {
963 pm_strcat(&rx->JobIds, ",");
965 pm_strcat(&rx->JobIds, row[0]);
971 * Callback handler to pickup last Full backup JobTDate
973 static int last_full_handler(void *ctx, int num_fields, char **row)
975 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
977 rx->JobTDate = str_to_int64(row[1]);
982 * Callback handler build FileSet name prompt list
984 static int fileset_handler(void *ctx, int num_fields, char **row)
986 /* row[0] = FileSet (name) */
988 add_prompt((UAContext *)ctx, row[0]);
994 * Called here with each name to be added to the list. The name is
995 * added to the list if it is not already in the list.
997 * Used to make unique list of FileSets and MediaTypes
999 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1001 NAME_LIST *name = (NAME_LIST *)ctx;
1003 if (name->num_ids == MAX_ID_LIST_LEN) {
1006 if (name->num_ids == name->max_ids) {
1007 if (name->max_ids == 0) {
1008 name->max_ids = 1000;
1009 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1011 name->max_ids = (name->max_ids * 3) / 2;
1012 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1015 for (int i=0; i<name->num_ids; i++) {
1016 if (strcmp(name->name[i], row[0]) == 0) {
1017 return 0; /* already in list, return */
1020 /* Add new name to list */
1021 name->name[name->num_ids++] = bstrdup(row[0]);
1027 * Print names in the list
1029 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1031 for (int i=0; i < name_list->num_ids; i++) {
1032 bsendmsg(ua, "%s\n", name_list->name[i]);
1038 * Free names in the list
1040 static void free_name_list(NAME_LIST *name_list)
1042 for (int i=0; i < name_list->num_ids; i++) {
1043 free(name_list->name[i]);
1045 if (name_list->name) {
1046 free(name_list->name);
1047 name_list->name = NULL;
1049 name_list->max_ids = 0;
1050 name_list->num_ids = 0;
1053 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, RESTORE_CTX *rx)
1057 if (name_list->num_ids > 1) {
1058 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1059 "Restore is not possible. The MediaTypes used are:\n"));
1060 print_name_list(ua, name_list);
1061 rx->store = select_storage_resource(ua);
1065 if (name_list->num_ids == 0) {
1066 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1067 rx->store = select_storage_resource(ua);
1074 * We have a single MediaType, look it up in our Storage resource
1077 foreach_res(store, R_STORAGE) {
1078 if (strcmp(name_list->name[0], store->media_type) == 0) {
1086 /* Try asking user */
1087 rx->store = get_storage_resource(ua, false /* don't use default */);
1090 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1091 "MediaType \"%s\", needed by the Jobs you selected.\n"
1092 "You will be allowed to select a Storage device later.\n"),
1093 name_list->name[0]);