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",
321 for (i=1; i<ua->argc; i++) { /* loop through arguments */
322 bool found_kw = false;
323 for (j=0; kw[j]; j++) { /* loop through keywords */
324 if (strcasecmp(kw[j], ua->argk[i]) == 0) {
330 bsendmsg(ua, _("Unknown keyword: %s\n"), ua->argk[i]);
333 /* Found keyword in kw[] list, process it */
336 if (*rx->JobIds != 0) {
337 pm_strcat(&rx->JobIds, ",");
339 pm_strcat(&rx->JobIds, ua->argv[i]);
342 case 1: /* current */
343 bstrutime(date, sizeof(date), time(NULL));
347 if (str_to_utime(ua->argv[i]) == 0) {
348 bsendmsg(ua, _("Improper date format: %s\n"), ua->argv[i]);
351 bstrncpy(date, ua->argv[i], sizeof(date));
356 bstrutime(date, sizeof(date), time(NULL));
358 if (!get_client_name(ua, rx)) {
361 pm_strcpy(&ua->cmd, ua->argv[i]);
362 insert_one_file(ua, rx, date);
363 if (rx->name_list.num_ids) {
364 /* Check MediaType and select storage that corresponds */
365 get_storage_from_mediatype(ua, &rx->name_list, rx);
371 bstrutime(date, sizeof(date), time(NULL));
373 if (!select_backups_before_date(ua, rx, date)) {
378 case 5: /* pool specified */
379 rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
381 bsendmsg(ua, _("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
384 if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
386 bsendmsg(ua, _("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
391 * All keywords 6 or greater are ignored or handled by a select prompt
397 if (rx->name_list.num_ids) {
398 return 2; /* filename list made */
402 bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
403 "to be restored. You will be presented several methods\n"
404 "of specifying the JobIds. Then you will be allowed to\n"
405 "select which files from those JobIds are to be restored.\n\n"));
408 /* If choice not already made above, prompt */
413 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
414 for (int i=0; list[i]; i++) {
415 add_prompt(ua, list[i]);
418 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
421 case 0: /* list last 20 Jobs run */
422 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
425 case 1: /* list where a file is saved */
426 if (!get_cmd(ua, _("Enter Filename: "))) {
429 len = strlen(ua->cmd);
430 fname = (char *)malloc(len * 2 + 1);
431 db_escape_string(fname, ua->cmd, len);
432 Mmsg(&rx->query, uar_file, fname);
434 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
437 case 2: /* enter a list of JobIds */
438 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
441 pm_strcpy(&rx->JobIds, ua->cmd);
443 case 3: /* Enter an SQL list command */
444 if (!get_cmd(ua, _("Enter SQL list command: "))) {
447 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
450 case 4: /* Select the most recent backups */
451 bstrutime(date, sizeof(date), time(NULL));
452 if (!select_backups_before_date(ua, rx, date)) {
456 case 5: /* select backup at specified time */
457 if (!get_date(ua, date, sizeof(date))) {
460 if (!select_backups_before_date(ua, rx, date)) {
464 case 6: /* Enter files */
465 bstrutime(date, sizeof(date), time(NULL));
466 if (!get_client_name(ua, rx)) {
469 bsendmsg(ua, _("Enter file names, or < to enter a filename\n"
470 "containg a list of file names, and terminate\n"
471 "them with a blank line.\n"));
473 if (!get_cmd(ua, _("Enter filename: "))) {
476 len = strlen(ua->cmd);
480 insert_one_file(ua, rx, date);
482 /* Check MediaType and select storage that corresponds */
483 if (rx->name_list.num_ids) {
484 get_storage_from_mediatype(ua, &rx->name_list, rx);
487 case 7: /* enter files backed up before specified time */
488 if (!get_date(ua, date, sizeof(date))) {
491 if (!get_client_name(ua, rx)) {
494 bsendmsg(ua, _("Enter file names, or < to enter a filename\n"
495 "containg a list of file names, and terminate\n"
496 "them with a blank line.\n"));
498 if (!get_cmd(ua, _("Enter filename: "))) {
501 len = strlen(ua->cmd);
505 insert_one_file(ua, rx, date);
507 /* Check MediaType and select storage that corresponds */
508 if (rx->name_list.num_ids) {
509 get_storage_from_mediatype(ua, &rx->name_list, rx);
514 case 8: /* Cancel or quit */
519 if (*rx->JobIds == 0) {
520 bsendmsg(ua, _("No Jobs selected.\n"));
523 bsendmsg(ua, _("You have selected the following JobId%s: %s\n"),
524 strchr(rx->JobIds,',')?"s":"",rx->JobIds);
526 memset(&jr, 0, sizeof(JOB_DBR));
529 for (p=rx->JobIds; ; ) {
530 int stat = get_next_jobid_from_list(&p, &JobId);
532 bsendmsg(ua, _("Invalid JobId in list.\n"));
538 if (jr.JobId == JobId) {
539 continue; /* duplicate of last JobId */
542 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
543 bsendmsg(ua, _("Unable to get Job record for JobId=%u: ERR=%s\n"),
544 JobId, db_strerror(ua->db));
547 if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
548 bsendmsg(ua, _("No authorization. Job \"%s\" not selected.\n"),
552 rx->TotalFiles += jr.JobFiles;
560 static int get_date(UAContext *ua, char *date, int date_len)
562 bsendmsg(ua, _("The restored files will the most current backup\n"
563 "BEFORE the date you specify below.\n\n"));
565 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
568 if (str_to_utime(ua->cmd) != 0) {
571 bsendmsg(ua, _("Improper date format.\n"));
573 bstrncpy(date, ua->cmd, date_len);
578 * Insert a single file, or read a list of files from a file
580 static void insert_one_file(UAContext *ua, RESTORE_CTX *rx, char *date)
590 if ((ffd = fopen(p, "r")) == NULL) {
591 bsendmsg(ua, _("Cannot open file %s: ERR=%s\n"),
595 while (fgets(file, sizeof(file), ffd)) {
597 if (!insert_file_into_findex_list(ua, rx, file, date)) {
598 bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
604 insert_file_into_findex_list(ua, rx, ua->cmd, date);
610 * For a given file (path+filename), split into path and file, then
611 * lookup the most recent backup in the catalog to get the JobId
612 * and FileIndex, then insert them into the findex list.
614 static int insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
617 strip_trailing_junk(file);
618 split_path_and_filename(rx, file);
619 Mmsg(&rx->query, uar_jobid_fileindex, date, rx->path, rx->fname, rx->ClientName);
621 /* Find and insert jobid and File Index */
622 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
623 bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
624 rx->query, db_strerror(ua->db));
627 bsendmsg(ua, _("No database record found for: %s\n"), file);
630 rx->selected_files++;
632 * Find the MediaTypes for this JobId and add to the name_list
634 Mmsg(&rx->query, uar_mediatype, rx->JobId);
635 if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
636 bsendmsg(ua, "%s", db_strerror(ua->db));
642 static void split_path_and_filename(RESTORE_CTX *rx, char *name)
646 /* Find path without the filename.
647 * I.e. everything after the last / is a "filename".
648 * OK, maybe it is a directory name, but we treat it like
649 * a filename. If we don't find a / then the whole name
650 * must be a path name (e.g. c:).
652 for (p=f=name; *p; p++) {
654 f = p; /* set pos of last slash */
657 if (*f == '/') { /* did we find a slash? */
658 f++; /* yes, point to filename */
659 } else { /* no, whole thing must be path name */
663 /* If filename doesn't exist (i.e. root directory), we
664 * simply create a blank name consisting of a single
665 * space. This makes handling zero length filenames
670 rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
671 memcpy(rx->fname, f, rx->fnl); /* copy filename */
672 rx->fname[rx->fnl] = 0;
674 rx->fname[0] = ' '; /* blank filename */
681 rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
682 memcpy(rx->path, name, rx->pnl);
683 rx->path[rx->pnl] = 0;
690 Dmsg2(100, "sllit path=%s file=%s\n", rx->path, rx->fname);
693 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
696 JobId_t JobId, last_JobId;
701 memset(&tree, 0, sizeof(TREE_CTX));
703 * Build the directory tree containing JobIds user selected
705 tree.root = new_tree(rx->TotalFiles);
706 tree.root->fname = nofname;
710 * For display purposes, the same JobId, with different volumes may
711 * appear more than once, however, we only insert it once.
714 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
716 if (JobId == last_JobId) {
717 continue; /* eliminate duplicate JobIds */
720 bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
723 * Find files for this JobId and insert them in the tree
725 Mmsg(&rx->query, uar_sel_files, JobId);
726 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
727 bsendmsg(ua, "%s", db_strerror(ua->db));
730 * Find the MediaTypes for this JobId and add to the name_list
732 Mmsg(&rx->query, uar_mediatype, JobId);
733 if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
734 bsendmsg(ua, "%s", db_strerror(ua->db));
737 bsendmsg(ua, "%d Job%s inserted into the tree and marked for extraction.\n",
738 items, items==1?"":"s");
740 /* Check MediaType and select storage that corresponds */
741 get_storage_from_mediatype(ua, &rx->name_list, rx);
743 if (find_arg(ua, _("all")) < 0) {
744 /* Let the user interact in selecting which files to restore */
745 OK = user_select_files_from_tree(&tree);
749 * Walk down through the tree finding all files marked to be
750 * extracted making a bootstrap file.
753 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
754 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
755 if (node->extract || node->extract_dir) {
756 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
757 add_findex(rx->bsr, node->JobId, node->FileIndex);
758 rx->selected_files++;
763 free_tree(tree.root); /* free the directory tree */
769 * This routine is used to get the current backup or a backup
770 * before the specified date.
772 static int select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
777 char fileset_name[MAX_NAME_LENGTH];
779 char pool_select[MAX_NAME_LENGTH];
783 /* Create temp tables */
784 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
785 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
786 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
787 bsendmsg(ua, "%s\n", db_strerror(ua->db));
789 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
790 bsendmsg(ua, "%s\n", db_strerror(ua->db));
793 * Select Client from the Catalog
795 memset(&cr, 0, sizeof(cr));
796 if (!get_client_dbr(ua, &cr)) {
799 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
804 memset(&fsr, 0, sizeof(fsr));
805 i = find_arg_with_value(ua, "FileSet");
807 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
808 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
809 bsendmsg(ua, _("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
810 db_strerror(ua->db));
814 if (i < 0) { /* fileset not found */
815 Mmsg(&rx->query, uar_sel_fileset, cr.ClientId, cr.ClientId);
816 start_prompt(ua, _("The defined FileSet resources are:\n"));
817 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
818 bsendmsg(ua, "%s\n", db_strerror(ua->db));
820 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
821 fileset_name, sizeof(fileset_name)) < 0) {
825 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
826 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
827 bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
828 bsendmsg(ua, _("This probably means you modified the FileSet.\n"
829 "Continuing anyway.\n"));
833 /* If Pool specified, add PoolId specification */
837 memset(&pr, 0, sizeof(pr));
838 bstrncpy(pr.Name, rx->pool->hdr.name, sizeof(pr.Name));
839 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
840 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%u ", pr.PoolId);
842 bsendmsg(ua, _("Pool \"%s\" not found, using any pool.\n"), pr.Name);
846 /* Find JobId of last Full backup for this client, fileset */
847 Mmsg(&rx->query, uar_last_full, cr.ClientId, cr.ClientId, date, fsr.FileSet,
849 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
850 bsendmsg(ua, "%s\n", db_strerror(ua->db));
854 /* Find all Volumes used by that JobId */
855 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
856 bsendmsg(ua, "%s\n", db_strerror(ua->db));
859 /* Note, this is needed because I don't seem to get the callback
860 * from the call just above.
863 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
864 bsendmsg(ua, "%s\n", db_strerror(ua->db));
866 if (rx->JobTDate == 0) {
867 bsendmsg(ua, _("No Full backup before %s found.\n"), date);
871 /* Now find most recent Differental Job after Full save, if any */
872 Mmsg(&rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
873 cr.ClientId, fsr.FileSet, pool_select);
874 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
875 bsendmsg(ua, "%s\n", db_strerror(ua->db));
877 /* Now update JobTDate to lock onto Differental, if any */
879 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
880 bsendmsg(ua, "%s\n", db_strerror(ua->db));
882 if (rx->JobTDate == 0) {
883 bsendmsg(ua, _("No Full backup before %s found.\n"), date);
887 /* Now find all Incremental Jobs after Full/dif save */
888 Mmsg(&rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
889 cr.ClientId, fsr.FileSet, pool_select);
890 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
891 bsendmsg(ua, "%s\n", db_strerror(ua->db));
894 /* Get the JobIds from that list */
896 rx->last_jobid[0] = 0;
897 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
898 bsendmsg(ua, "%s\n", db_strerror(ua->db));
901 if (rx->JobIds[0] != 0) {
902 /* Display a list of Jobs selected for this restore */
903 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
905 bsendmsg(ua, _("No jobs found.\n"));
911 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
912 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
917 /* Return next JobId from comma separated list */
918 static int get_next_jobid_from_list(char **p, uint32_t *JobId)
924 for (int i=0; i<(int)sizeof(jobid); i++) {
925 if (*q == ',' || *q == 0) {
932 if (jobid[0] == 0 || !is_a_number(jobid)) {
936 *JobId = strtoul(jobid, NULL, 10);
941 * Callback handler to get JobId and FileIndex for files
943 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
945 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
946 rx->JobId = atoi(row[0]);
947 add_findex(rx->bsr, rx->JobId, atoi(row[1]));
953 * Callback handler make list of JobIds
955 static int jobid_handler(void *ctx, int num_fields, char **row)
957 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
959 if (strcmp(rx->last_jobid, row[0]) == 0) {
960 return 0; /* duplicate id */
962 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
963 if (rx->JobIds[0] != 0) {
964 pm_strcat(&rx->JobIds, ",");
966 pm_strcat(&rx->JobIds, row[0]);
972 * Callback handler to pickup last Full backup JobTDate
974 static int last_full_handler(void *ctx, int num_fields, char **row)
976 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
978 rx->JobTDate = str_to_int64(row[1]);
983 * Callback handler build FileSet name prompt list
985 static int fileset_handler(void *ctx, int num_fields, char **row)
987 /* row[0] = FileSet (name) */
989 add_prompt((UAContext *)ctx, row[0]);
995 * Called here with each name to be added to the list. The name is
996 * added to the list if it is not already in the list.
998 * Used to make unique list of FileSets and MediaTypes
1000 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1002 NAME_LIST *name = (NAME_LIST *)ctx;
1004 if (name->num_ids == MAX_ID_LIST_LEN) {
1007 if (name->num_ids == name->max_ids) {
1008 if (name->max_ids == 0) {
1009 name->max_ids = 1000;
1010 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1012 name->max_ids = (name->max_ids * 3) / 2;
1013 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1016 for (int i=0; i<name->num_ids; i++) {
1017 if (strcmp(name->name[i], row[0]) == 0) {
1018 return 0; /* already in list, return */
1021 /* Add new name to list */
1022 name->name[name->num_ids++] = bstrdup(row[0]);
1028 * Print names in the list
1030 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1032 for (int i=0; i < name_list->num_ids; i++) {
1033 bsendmsg(ua, "%s\n", name_list->name[i]);
1039 * Free names in the list
1041 static void free_name_list(NAME_LIST *name_list)
1043 for (int i=0; i < name_list->num_ids; i++) {
1044 free(name_list->name[i]);
1046 if (name_list->name) {
1047 free(name_list->name);
1048 name_list->name = NULL;
1050 name_list->max_ids = 0;
1051 name_list->num_ids = 0;
1054 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, RESTORE_CTX *rx)
1058 if (name_list->num_ids > 1) {
1059 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1060 "Restore is not possible. The MediaTypes used are:\n"));
1061 print_name_list(ua, name_list);
1062 rx->store = select_storage_resource(ua);
1066 if (name_list->num_ids == 0) {
1067 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1068 rx->store = select_storage_resource(ua);
1075 * We have a single MediaType, look it up in our Storage resource
1078 foreach_res(store, R_STORAGE) {
1079 if (strcmp(name_list->name[0], store->media_type) == 0) {
1087 /* Try asking user */
1088 rx->store = get_storage_resource(ua, false /* don't use default */);
1091 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1092 "MediaType \"%s\", needed by the Jobs you selected.\n"
1093 "You will be allowed to select a Storage device later.\n"),
1094 name_list->name[0]);