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 */
83 bool all; /* mark all as default */
88 #define MAX_ID_LIST_LEN 1000000
91 /* Forward referenced functions */
92 static int last_full_handler(void *ctx, int num_fields, char **row);
93 static int jobid_handler(void *ctx, int num_fields, char **row);
94 static int get_next_jobid_from_list(char **p, uint32_t *JobId);
95 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx);
96 static int fileset_handler(void *ctx, int num_fields, char **row);
97 static void print_name_list(UAContext *ua, NAME_LIST *name_list);
98 static int unique_name_list_handler(void *ctx, int num_fields, char **row);
99 static void free_name_list(NAME_LIST *name_list);
100 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, RESTORE_CTX *rx);
101 static int select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date);
102 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx);
103 static void free_rx(RESTORE_CTX *rx);
104 static void split_path_and_filename(RESTORE_CTX *rx, char *fname);
105 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row);
106 static int insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
108 static void insert_one_file(UAContext *ua, RESTORE_CTX *rx, char *date);
109 static int get_client_name(UAContext *ua, RESTORE_CTX *rx);
110 static int get_date(UAContext *ua, char *date, int date_len);
116 int restore_cmd(UAContext *ua, char *cmd)
118 RESTORE_CTX rx; /* restore context */
122 memset(&rx, 0, sizeof(rx));
123 rx.path = get_pool_memory(PM_FNAME);
124 rx.fname = get_pool_memory(PM_FNAME);
125 rx.JobIds = get_pool_memory(PM_FNAME);
126 rx.query = get_pool_memory(PM_FNAME);
129 i = find_arg_with_value(ua, "where");
131 rx.where = ua->argv[i];
138 /* Ensure there is at least one Restore Job */
140 foreach_res(job, R_JOB) {
141 if (job->JobType == JT_RESTORE) {
142 if (!rx.restore_job) {
143 rx.restore_job = job;
149 if (!rx.restore_jobs) {
151 "No Restore Job Resource found. You must create at least\n"
152 "one before running this command.\n"));
157 * Request user to select JobIds or files by various different methods
158 * last 20 jobs, where File saved, most recent backup, ...
159 * In the end, a list of files are pumped into
162 switch (user_select_jobids_or_files(ua, &rx)) {
165 case 1: /* select by jobid */
166 if (!build_directory_tree(ua, &rx)) {
167 bsendmsg(ua, _("Restore not done.\n"));
171 case 2: /* select by filename, no tree needed */
176 if (!complete_bsr(ua, rx.bsr)) { /* find Vol, SessId, SessTime from JobIds */
177 bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
180 if (!write_bsr_file(ua, rx.bsr)) {
183 bsendmsg(ua, _("\n%u file%s selected to be restored.\n\n"), rx.selected_files,
184 rx.selected_files==1?"":"s");
186 bsendmsg(ua, _("No files selected to be restored.\n"));
190 if (rx.restore_jobs == 1) {
191 job = rx.restore_job;
193 job = select_restore_job_resource(ua);
199 get_client_name(ua, &rx);
200 if (!rx.ClientName) {
201 bsendmsg(ua, _("No Restore Job resource found!\n"));
205 /* Build run command */
208 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\""
209 " where=\"%s\" files=%d",
210 job->hdr.name, rx.ClientName, rx.store?rx.store->hdr.name:"",
211 working_directory, rx.where, rx.selected_files);
214 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
215 job->hdr.name, rx.ClientName, rx.store?rx.store->hdr.name:"",
218 if (find_arg(ua, _("yes")) > 0) {
219 pm_strcat(&ua->cmd, " yes"); /* pass it on to the run command */
221 Dmsg1(400, "Submitting: %s\n", ua->cmd);
223 run_cmd(ua, ua->cmd);
225 bsendmsg(ua, _("Restore command done.\n"));
235 static void free_rx(RESTORE_CTX *rx)
240 free_pool_memory(rx->JobIds);
244 free_pool_memory(rx->fname);
248 free_pool_memory(rx->path);
252 free_pool_memory(rx->query);
255 free_name_list(&rx->name_list);
258 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
260 /* If no client name specified yet, get it now */
261 if (!rx->ClientName[0]) {
263 /* try command line argument */
264 int i = find_arg_with_value(ua, _("client"));
266 bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
269 memset(&cr, 0, sizeof(cr));
270 if (!get_client_dbr(ua, &cr)) {
273 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
279 * The first step in the restore process is for the user to
280 * select a list of JobIds from which he will subsequently
281 * select which files are to be restored.
283 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
286 char date[MAX_TIME_LENGTH];
287 bool have_date = false;
293 "List last 20 Jobs run",
294 "List Jobs where a given File is saved",
295 "Enter list of comma separated JobIds to select",
296 "Enter SQL list command",
297 "Select the most recent backup for a client",
298 "Select backup for a client before a specified time",
299 "Enter a list of files to restore",
300 "Enter a list of files to restore before a specified time",
323 for (i=1; i<ua->argc; i++) { /* loop through arguments */
324 bool found_kw = false;
325 for (j=0; kw[j]; j++) { /* loop through keywords */
326 if (strcasecmp(kw[j], ua->argk[i]) == 0) {
332 bsendmsg(ua, _("Unknown keyword: %s\n"), ua->argk[i]);
335 /* Found keyword in kw[] list, process it */
338 if (*rx->JobIds != 0) {
339 pm_strcat(&rx->JobIds, ",");
341 pm_strcat(&rx->JobIds, ua->argv[i]);
344 case 1: /* current */
345 bstrutime(date, sizeof(date), time(NULL));
349 if (str_to_utime(ua->argv[i]) == 0) {
350 bsendmsg(ua, _("Improper date format: %s\n"), ua->argv[i]);
353 bstrncpy(date, ua->argv[i], sizeof(date));
358 bstrutime(date, sizeof(date), time(NULL));
360 if (!get_client_name(ua, rx)) {
363 pm_strcpy(&ua->cmd, ua->argv[i]);
364 insert_one_file(ua, rx, date);
365 if (rx->name_list.num_ids) {
366 /* Check MediaType and select storage that corresponds */
367 get_storage_from_mediatype(ua, &rx->name_list, rx);
373 bstrutime(date, sizeof(date), time(NULL));
375 if (!select_backups_before_date(ua, rx, date)) {
380 case 5: /* pool specified */
381 rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
383 bsendmsg(ua, _("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
386 if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
388 bsendmsg(ua, _("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
392 case 6: /* all specified */
396 * All keywords 7 or greater are ignored or handled by a select prompt
402 if (rx->name_list.num_ids) {
403 return 2; /* filename list made */
407 bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
408 "to be restored. You will be presented several methods\n"
409 "of specifying the JobIds. Then you will be allowed to\n"
410 "select which files from those JobIds are to be restored.\n\n"));
413 /* If choice not already made above, prompt */
418 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
419 for (int i=0; list[i]; i++) {
420 add_prompt(ua, list[i]);
423 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
426 case 0: /* list last 20 Jobs run */
427 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
430 case 1: /* list where a file is saved */
431 if (!get_cmd(ua, _("Enter Filename: "))) {
434 len = strlen(ua->cmd);
435 fname = (char *)malloc(len * 2 + 1);
436 db_escape_string(fname, ua->cmd, len);
437 Mmsg(&rx->query, uar_file, fname);
439 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
442 case 2: /* enter a list of JobIds */
443 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
446 pm_strcpy(&rx->JobIds, ua->cmd);
448 case 3: /* Enter an SQL list command */
449 if (!get_cmd(ua, _("Enter SQL list command: "))) {
452 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
455 case 4: /* Select the most recent backups */
456 bstrutime(date, sizeof(date), time(NULL));
457 if (!select_backups_before_date(ua, rx, date)) {
461 case 5: /* select backup at specified time */
462 if (!get_date(ua, date, sizeof(date))) {
465 if (!select_backups_before_date(ua, rx, date)) {
469 case 6: /* Enter files */
470 bstrutime(date, sizeof(date), time(NULL));
471 if (!get_client_name(ua, rx)) {
474 bsendmsg(ua, _("Enter file names, or < to enter a filename\n"
475 "containg a list of file names, and terminate\n"
476 "them with a blank line.\n"));
478 if (!get_cmd(ua, _("Enter filename: "))) {
481 len = strlen(ua->cmd);
485 insert_one_file(ua, rx, date);
487 /* Check MediaType and select storage that corresponds */
488 if (rx->name_list.num_ids) {
489 get_storage_from_mediatype(ua, &rx->name_list, rx);
492 case 7: /* enter files backed up before specified time */
493 if (!get_date(ua, date, sizeof(date))) {
496 if (!get_client_name(ua, rx)) {
499 bsendmsg(ua, _("Enter file names, or < to enter a filename\n"
500 "containg a list of file names, and terminate\n"
501 "them with a blank line.\n"));
503 if (!get_cmd(ua, _("Enter filename: "))) {
506 len = strlen(ua->cmd);
510 insert_one_file(ua, rx, date);
512 /* Check MediaType and select storage that corresponds */
513 if (rx->name_list.num_ids) {
514 get_storage_from_mediatype(ua, &rx->name_list, rx);
519 case 8: /* Cancel or quit */
524 if (*rx->JobIds == 0) {
525 bsendmsg(ua, _("No Jobs selected.\n"));
528 bsendmsg(ua, _("You have selected the following JobId%s: %s\n"),
529 strchr(rx->JobIds,',')?"s":"",rx->JobIds);
531 memset(&jr, 0, sizeof(JOB_DBR));
534 for (p=rx->JobIds; ; ) {
535 int stat = get_next_jobid_from_list(&p, &JobId);
537 bsendmsg(ua, _("Invalid JobId in list.\n"));
543 if (jr.JobId == JobId) {
544 continue; /* duplicate of last JobId */
547 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
548 bsendmsg(ua, _("Unable to get Job record for JobId=%u: ERR=%s\n"),
549 JobId, db_strerror(ua->db));
552 if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
553 bsendmsg(ua, _("No authorization. Job \"%s\" not selected.\n"),
557 rx->TotalFiles += jr.JobFiles;
565 static int get_date(UAContext *ua, char *date, int date_len)
567 bsendmsg(ua, _("The restored files will the most current backup\n"
568 "BEFORE the date you specify below.\n\n"));
570 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
573 if (str_to_utime(ua->cmd) != 0) {
576 bsendmsg(ua, _("Improper date format.\n"));
578 bstrncpy(date, ua->cmd, date_len);
583 * Insert a single file, or read a list of files from a file
585 static void insert_one_file(UAContext *ua, RESTORE_CTX *rx, char *date)
595 if ((ffd = fopen(p, "r")) == NULL) {
596 bsendmsg(ua, _("Cannot open file %s: ERR=%s\n"),
600 while (fgets(file, sizeof(file), ffd)) {
602 if (!insert_file_into_findex_list(ua, rx, file, date)) {
603 bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
609 insert_file_into_findex_list(ua, rx, ua->cmd, date);
615 * For a given file (path+filename), split into path and file, then
616 * lookup the most recent backup in the catalog to get the JobId
617 * and FileIndex, then insert them into the findex list.
619 static int insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
622 strip_trailing_junk(file);
623 split_path_and_filename(rx, file);
624 Mmsg(&rx->query, uar_jobid_fileindex, date, rx->path, rx->fname, rx->ClientName);
626 /* Find and insert jobid and File Index */
627 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
628 bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
629 rx->query, db_strerror(ua->db));
632 bsendmsg(ua, _("No database record found for: %s\n"), file);
635 rx->selected_files++;
637 * Find the MediaTypes for this JobId and add to the name_list
639 Mmsg(&rx->query, uar_mediatype, rx->JobId);
640 if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
641 bsendmsg(ua, "%s", db_strerror(ua->db));
647 static void split_path_and_filename(RESTORE_CTX *rx, char *name)
651 /* Find path without the filename.
652 * I.e. everything after the last / is a "filename".
653 * OK, maybe it is a directory name, but we treat it like
654 * a filename. If we don't find a / then the whole name
655 * must be a path name (e.g. c:).
657 for (p=f=name; *p; p++) {
659 f = p; /* set pos of last slash */
662 if (*f == '/') { /* did we find a slash? */
663 f++; /* yes, point to filename */
664 } else { /* no, whole thing must be path name */
668 /* If filename doesn't exist (i.e. root directory), we
669 * simply create a blank name consisting of a single
670 * space. This makes handling zero length filenames
675 rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
676 memcpy(rx->fname, f, rx->fnl); /* copy filename */
677 rx->fname[rx->fnl] = 0;
679 rx->fname[0] = ' '; /* blank filename */
686 rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
687 memcpy(rx->path, name, rx->pnl);
688 rx->path[rx->pnl] = 0;
695 Dmsg2(100, "sllit path=%s file=%s\n", rx->path, rx->fname);
698 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
701 JobId_t JobId, last_JobId;
706 memset(&tree, 0, sizeof(TREE_CTX));
708 * Build the directory tree containing JobIds user selected
710 tree.root = new_tree(rx->TotalFiles);
711 tree.root->fname = nofname;
716 * For display purposes, the same JobId, with different volumes may
717 * appear more than once, however, we only insert it once.
720 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
722 if (JobId == last_JobId) {
723 continue; /* eliminate duplicate JobIds */
726 bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
729 * Find files for this JobId and insert them in the tree
731 Mmsg(&rx->query, uar_sel_files, JobId);
732 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
733 bsendmsg(ua, "%s", db_strerror(ua->db));
736 * Find the MediaTypes for this JobId and add to the name_list
738 Mmsg(&rx->query, uar_mediatype, JobId);
739 if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
740 bsendmsg(ua, "%s", db_strerror(ua->db));
743 bsendmsg(ua, "%d Job%s inserted into the tree%s.\n",
744 items, items==1?"":"s", tree.all?" and marked for extraction":"");
746 /* Check MediaType and select storage that corresponds */
747 get_storage_from_mediatype(ua, &rx->name_list, rx);
749 if (find_arg(ua, _("done")) < 0) {
750 /* Let the user interact in selecting which files to restore */
751 OK = user_select_files_from_tree(&tree);
755 * Walk down through the tree finding all files marked to be
756 * extracted making a bootstrap file.
759 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
760 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
761 if (node->extract || node->extract_dir) {
762 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
763 add_findex(rx->bsr, node->JobId, node->FileIndex);
764 rx->selected_files++;
769 free_tree(tree.root); /* free the directory tree */
775 * This routine is used to get the current backup or a backup
776 * before the specified date.
778 static int select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
783 char fileset_name[MAX_NAME_LENGTH];
785 char pool_select[MAX_NAME_LENGTH];
789 /* Create temp tables */
790 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
791 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
792 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
793 bsendmsg(ua, "%s\n", db_strerror(ua->db));
795 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
796 bsendmsg(ua, "%s\n", db_strerror(ua->db));
799 * Select Client from the Catalog
801 memset(&cr, 0, sizeof(cr));
802 if (!get_client_dbr(ua, &cr)) {
805 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
810 memset(&fsr, 0, sizeof(fsr));
811 i = find_arg_with_value(ua, "FileSet");
813 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
814 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
815 bsendmsg(ua, _("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
816 db_strerror(ua->db));
820 if (i < 0) { /* fileset not found */
821 Mmsg(&rx->query, uar_sel_fileset, cr.ClientId, cr.ClientId);
822 start_prompt(ua, _("The defined FileSet resources are:\n"));
823 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
824 bsendmsg(ua, "%s\n", db_strerror(ua->db));
826 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
827 fileset_name, sizeof(fileset_name)) < 0) {
831 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
832 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
833 bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
834 bsendmsg(ua, _("This probably means you modified the FileSet.\n"
835 "Continuing anyway.\n"));
839 /* If Pool specified, add PoolId specification */
843 memset(&pr, 0, sizeof(pr));
844 bstrncpy(pr.Name, rx->pool->hdr.name, sizeof(pr.Name));
845 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
846 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%u ", pr.PoolId);
848 bsendmsg(ua, _("Pool \"%s\" not found, using any pool.\n"), pr.Name);
852 /* Find JobId of last Full backup for this client, fileset */
853 Mmsg(&rx->query, uar_last_full, cr.ClientId, cr.ClientId, date, fsr.FileSet,
855 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
856 bsendmsg(ua, "%s\n", db_strerror(ua->db));
860 /* Find all Volumes used by that JobId */
861 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
862 bsendmsg(ua, "%s\n", db_strerror(ua->db));
865 /* Note, this is needed because I don't seem to get the callback
866 * from the call just above.
869 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
870 bsendmsg(ua, "%s\n", db_strerror(ua->db));
872 if (rx->JobTDate == 0) {
873 bsendmsg(ua, _("No Full backup before %s found.\n"), date);
877 /* Now find most recent Differental Job after Full save, if any */
878 Mmsg(&rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
879 cr.ClientId, fsr.FileSet, pool_select);
880 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
881 bsendmsg(ua, "%s\n", db_strerror(ua->db));
883 /* Now update JobTDate to lock onto Differental, if any */
885 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
886 bsendmsg(ua, "%s\n", db_strerror(ua->db));
888 if (rx->JobTDate == 0) {
889 bsendmsg(ua, _("No Full backup before %s found.\n"), date);
893 /* Now find all Incremental Jobs after Full/dif save */
894 Mmsg(&rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
895 cr.ClientId, fsr.FileSet, pool_select);
896 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
897 bsendmsg(ua, "%s\n", db_strerror(ua->db));
900 /* Get the JobIds from that list */
902 rx->last_jobid[0] = 0;
903 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
904 bsendmsg(ua, "%s\n", db_strerror(ua->db));
907 if (rx->JobIds[0] != 0) {
908 /* Display a list of Jobs selected for this restore */
909 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
911 bsendmsg(ua, _("No jobs found.\n"));
917 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
918 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
923 /* Return next JobId from comma separated list */
924 static int get_next_jobid_from_list(char **p, uint32_t *JobId)
930 for (int i=0; i<(int)sizeof(jobid); i++) {
931 if (*q == ',' || *q == 0) {
938 if (jobid[0] == 0 || !is_a_number(jobid)) {
942 *JobId = strtoul(jobid, NULL, 10);
947 * Callback handler to get JobId and FileIndex for files
949 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
951 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
952 rx->JobId = atoi(row[0]);
953 add_findex(rx->bsr, rx->JobId, atoi(row[1]));
959 * Callback handler make list of JobIds
961 static int jobid_handler(void *ctx, int num_fields, char **row)
963 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
965 if (strcmp(rx->last_jobid, row[0]) == 0) {
966 return 0; /* duplicate id */
968 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
969 if (rx->JobIds[0] != 0) {
970 pm_strcat(&rx->JobIds, ",");
972 pm_strcat(&rx->JobIds, row[0]);
978 * Callback handler to pickup last Full backup JobTDate
980 static int last_full_handler(void *ctx, int num_fields, char **row)
982 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
984 rx->JobTDate = str_to_int64(row[1]);
989 * Callback handler build FileSet name prompt list
991 static int fileset_handler(void *ctx, int num_fields, char **row)
993 /* row[0] = FileSet (name) */
995 add_prompt((UAContext *)ctx, row[0]);
1001 * Called here with each name to be added to the list. The name is
1002 * added to the list if it is not already in the list.
1004 * Used to make unique list of FileSets and MediaTypes
1006 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1008 NAME_LIST *name = (NAME_LIST *)ctx;
1010 if (name->num_ids == MAX_ID_LIST_LEN) {
1013 if (name->num_ids == name->max_ids) {
1014 if (name->max_ids == 0) {
1015 name->max_ids = 1000;
1016 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1018 name->max_ids = (name->max_ids * 3) / 2;
1019 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1022 for (int i=0; i<name->num_ids; i++) {
1023 if (strcmp(name->name[i], row[0]) == 0) {
1024 return 0; /* already in list, return */
1027 /* Add new name to list */
1028 name->name[name->num_ids++] = bstrdup(row[0]);
1034 * Print names in the list
1036 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1038 for (int i=0; i < name_list->num_ids; i++) {
1039 bsendmsg(ua, "%s\n", name_list->name[i]);
1045 * Free names in the list
1047 static void free_name_list(NAME_LIST *name_list)
1049 for (int i=0; i < name_list->num_ids; i++) {
1050 free(name_list->name[i]);
1052 if (name_list->name) {
1053 free(name_list->name);
1054 name_list->name = NULL;
1056 name_list->max_ids = 0;
1057 name_list->num_ids = 0;
1060 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, RESTORE_CTX *rx)
1064 if (name_list->num_ids > 1) {
1065 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1066 "Restore is not possible. The MediaTypes used are:\n"));
1067 print_name_list(ua, name_list);
1068 rx->store = select_storage_resource(ua);
1072 if (name_list->num_ids == 0) {
1073 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1074 rx->store = select_storage_resource(ua);
1081 * We have a single MediaType, look it up in our Storage resource
1084 foreach_res(store, R_STORAGE) {
1085 if (strcmp(name_list->name[0], store->media_type) == 0) {
1093 /* Try asking user */
1094 rx->store = get_storage_resource(ua, false /* don't use default */);
1097 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1098 "MediaType \"%s\", needed by the Jobs you selected.\n"
1099 "You will be allowed to select a Storage device later.\n"),
1100 name_list->name[0]);