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_dec, *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;
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 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));
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 while ( (job = (JOB *)GetNextRes(R_JOB, (RES *)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 write_bsr_file(ua, rx.bsr);
181 bsendmsg(ua, _("\n%u file%s selected to be restored.\n\n"), rx.selected_files,
182 rx.selected_files==1?"":"s");
184 bsendmsg(ua, _("No files selected to be restored.\n"));
188 if (rx.restore_jobs == 1) {
189 job = rx.restore_job;
191 job = select_restore_job_resource(ua);
197 get_client_name(ua, &rx);
198 if (!rx.ClientName) {
199 bsendmsg(ua, _("No Restore Job resource found!\n"));
203 /* Build run command */
206 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\""
207 " where=\"%s\" files=%d",
208 job->hdr.name, rx.ClientName, rx.store?rx.store->hdr.name:"",
209 working_directory, rx.where, rx.selected_files);
212 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
213 job->hdr.name, rx.ClientName, rx.store?rx.store->hdr.name:"",
216 if (find_arg(ua, _("yes")) > 0) {
217 pm_strcat(&ua->cmd, " yes"); /* pass it on to the run command */
219 Dmsg1(400, "Submitting: %s\n", ua->cmd);
221 run_cmd(ua, ua->cmd);
223 bsendmsg(ua, _("Restore command done.\n"));
233 static void free_rx(RESTORE_CTX *rx)
238 free_pool_memory(rx->JobIds);
242 free_pool_memory(rx->fname);
246 free_pool_memory(rx->path);
250 free_pool_memory(rx->query);
253 free_name_list(&rx->name_list);
256 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
258 /* If no client name specified yet, get it now */
259 if (!rx->ClientName[0]) {
261 /* try command line argument */
262 int i = find_arg_with_value(ua, _("client"));
264 bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
267 memset(&cr, 0, sizeof(cr));
268 if (!get_client_dbr(ua, &cr)) {
271 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
277 * The first step in the restore process is for the user to
278 * select a list of JobIds from which he will subsequently
279 * select which files are to be restored.
281 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
284 char date[MAX_TIME_LENGTH];
285 bool have_date = false;
291 "List last 20 Jobs run",
292 "List Jobs where a given File is saved",
293 "Enter list of JobIds to select",
294 "Enter SQL list command",
295 "Select the most recent backup for a client",
296 "Select backup for a client before a specified time",
297 "Enter a list of files to restore",
298 "Enter a list of files to restore before a specified time",
313 switch (find_arg_keyword(ua, kw)) {
316 i = find_arg_with_value(ua, _("jobid"));
320 pm_strcpy(&rx->JobIds, ua->argv[i]);
321 ua->argk[i][0] = 0; /* "consume" jobid= */
325 case 1: /* current */
326 bstrutime(date, sizeof(date), time(NULL));
330 i = find_arg_with_value(ua, _("before"));
334 if (str_to_utime(ua->argv[i]) == 0) {
335 bsendmsg(ua, _("Improper date format: %s\n"), ua->argv[i]);
338 bstrncpy(date, ua->argv[i], sizeof(date));
343 bstrutime(date, sizeof(date), time(NULL));
345 if (!get_client_name(ua, rx)) {
349 i = find_arg_with_value(ua, _("file"));
353 pm_strcpy(&ua->cmd, ua->argv[i]);
354 insert_one_file(ua, rx, date);
355 ua->argk[i][0] = 0; /* "consume" the file= */
357 /* Check MediaType and select storage that corresponds */
358 get_storage_from_mediatype(ua, &rx->name_list, rx);
362 bstrutime(date, sizeof(date), time(NULL));
364 if (!select_backups_before_date(ua, rx, date)) {
369 case 5: /* pool specified */
370 i = find_arg_with_value(ua, "pool");
372 rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
374 bsendmsg(ua, _("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
382 bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
383 "to be restored. You will be presented several methods\n"
384 "of specifying the JobIds. Then you will be allowed to\n"
385 "select which files from those JobIds are to be restored.\n\n"));
388 /* If choice not already made above, prompt */
393 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
394 for (int i=0; list[i]; i++) {
395 add_prompt(ua, list[i]);
398 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
401 case 0: /* list last 20 Jobs run */
402 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
405 case 1: /* list where a file is saved */
406 if (!get_cmd(ua, _("Enter Filename: "))) {
409 len = strlen(ua->cmd);
410 fname = (char *)malloc(len * 2 + 1);
411 db_escape_string(fname, ua->cmd, len);
412 Mmsg(&rx->query, uar_file, fname);
414 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
417 case 2: /* enter a list of JobIds */
418 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
421 pm_strcpy(&rx->JobIds, ua->cmd);
423 case 3: /* Enter an SQL list command */
424 if (!get_cmd(ua, _("Enter SQL list command: "))) {
427 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
430 case 4: /* Select the most recent backups */
431 bstrutime(date, sizeof(date), time(NULL));
432 if (!select_backups_before_date(ua, rx, date)) {
436 case 5: /* select backup at specified time */
437 if (!get_date(ua, date, sizeof(date))) {
440 if (!select_backups_before_date(ua, rx, date)) {
444 case 6: /* Enter files */
445 bstrutime(date, sizeof(date), time(NULL));
446 if (!get_client_name(ua, rx)) {
449 bsendmsg(ua, _("Enter file names, or < to enter a filename\n"
450 "containg a list of file names, and terminate\n"
451 "them with a blank line.\n"));
453 if (!get_cmd(ua, _("Enter filename: "))) {
456 len = strlen(ua->cmd);
460 insert_one_file(ua, rx, date);
462 /* Check MediaType and select storage that corresponds */
463 get_storage_from_mediatype(ua, &rx->name_list, rx);
465 case 7: /* enter files backed up before specified time */
466 if (!get_date(ua, date, sizeof(date))) {
469 if (!get_client_name(ua, rx)) {
472 bsendmsg(ua, _("Enter file names, or < to enter a filename\n"
473 "containg a list of file names, and terminate\n"
474 "them with a blank line.\n"));
476 if (!get_cmd(ua, _("Enter filename: "))) {
479 len = strlen(ua->cmd);
483 insert_one_file(ua, rx, date);
485 /* Check MediaType and select storage that corresponds */
486 get_storage_from_mediatype(ua, &rx->name_list, rx);
490 case 8: /* Cancel or quit */
495 if (*rx->JobIds == 0) {
496 bsendmsg(ua, _("No Jobs selected.\n"));
499 bsendmsg(ua, _("You have selected the following JobId%s: %s\n"),
500 strchr(rx->JobIds,',')?"s":"",rx->JobIds);
502 memset(&jr, 0, sizeof(JOB_DBR));
505 for (p=rx->JobIds; ; ) {
506 int stat = next_jobid_from_list(&p, &JobId);
508 bsendmsg(ua, _("Invalid JobId in list.\n"));
514 if (jr.JobId == JobId) {
515 continue; /* duplicate of last JobId */
518 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
519 bsendmsg(ua, _("Unable to get Job record for JobId=%u: ERR=%s\n"),
520 JobId, db_strerror(ua->db));
523 rx->TotalFiles += jr.JobFiles;
531 static int get_date(UAContext *ua, char *date, int date_len)
533 bsendmsg(ua, _("The restored files will the most current backup\n"
534 "BEFORE the date you specify below.\n\n"));
536 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
539 if (str_to_utime(ua->cmd) != 0) {
542 bsendmsg(ua, _("Improper date format.\n"));
544 bstrncpy(date, ua->cmd, date_len);
549 * Insert a single file, or read a list of files from a file
551 static void insert_one_file(UAContext *ua, RESTORE_CTX *rx, char *date)
561 if ((ffd = fopen(p, "r")) == NULL) {
562 bsendmsg(ua, _("Cannot open file %s: ERR=%s\n"),
566 while (fgets(file, sizeof(file), ffd)) {
568 if (!insert_file_into_findex_list(ua, rx, file, date)) {
569 bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
575 insert_file_into_findex_list(ua, rx, ua->cmd, date);
581 * For a given file (path+filename), split into path and file, then
582 * lookup the most recent backup in the catalog to get the JobId
583 * and FileIndex, then insert them into the findex list.
585 static int insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
588 strip_trailing_junk(file);
589 split_path_and_filename(rx, file);
590 Mmsg(&rx->query, uar_jobid_fileindex, date, rx->path, rx->fname, rx->ClientName);
592 /* Find and insert jobid and File Index */
593 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
594 bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
595 rx->query, db_strerror(ua->db));
598 bsendmsg(ua, _("No database record found for: %s\n"), file);
601 rx->selected_files++;
603 * Find the MediaTypes for this JobId and add to the name_list
605 Mmsg(&rx->query, uar_mediatype, rx->JobId);
606 if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
607 bsendmsg(ua, "%s", db_strerror(ua->db));
613 static void split_path_and_filename(RESTORE_CTX *rx, char *name)
617 /* Find path without the filename.
618 * I.e. everything after the last / is a "filename".
619 * OK, maybe it is a directory name, but we treat it like
620 * a filename. If we don't find a / then the whole name
621 * must be a path name (e.g. c:).
623 for (p=f=name; *p; p++) {
625 f = p; /* set pos of last slash */
628 if (*f == '/') { /* did we find a slash? */
629 f++; /* yes, point to filename */
630 } else { /* no, whole thing must be path name */
634 /* If filename doesn't exist (i.e. root directory), we
635 * simply create a blank name consisting of a single
636 * space. This makes handling zero length filenames
641 rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
642 memcpy(rx->fname, f, rx->fnl); /* copy filename */
643 rx->fname[rx->fnl] = 0;
645 rx->fname[0] = ' '; /* blank filename */
652 rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
653 memcpy(rx->path, name, rx->pnl);
654 rx->path[rx->pnl] = 0;
661 Dmsg2(100, "sllit path=%s file=%s\n", rx->path, rx->fname);
664 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
667 JobId_t JobId, last_JobId;
672 memset(&tree, 0, sizeof(TREE_CTX));
674 * Build the directory tree containing JobIds user selected
676 tree.root = new_tree(rx->TotalFiles);
677 tree.root->fname = nofname;
681 * For display purposes, the same JobId, with different volumes may
682 * appear more than once, however, we only insert it once.
685 for (p=rx->JobIds; next_jobid_from_list(&p, &JobId) > 0; ) {
687 if (JobId == last_JobId) {
688 continue; /* eliminate duplicate JobIds */
691 bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
694 * Find files for this JobId and insert them in the tree
696 Mmsg(&rx->query, uar_sel_files, JobId);
697 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
698 bsendmsg(ua, "%s", db_strerror(ua->db));
701 * Find the MediaTypes for this JobId and add to the name_list
703 Mmsg(&rx->query, uar_mediatype, JobId);
704 if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
705 bsendmsg(ua, "%s", db_strerror(ua->db));
708 bsendmsg(ua, "%d Job%s inserted into the tree and marked for extraction.\n",
709 items, items==1?"":"s");
711 /* Check MediaType and select storage that corresponds */
712 get_storage_from_mediatype(ua, &rx->name_list, rx);
714 if (find_arg(ua, _("all")) < 0) {
715 /* Let the user interact in selecting which files to restore */
716 OK = user_select_files_from_tree(&tree);
720 * Walk down through the tree finding all files marked to be
721 * extracted making a bootstrap file.
724 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
725 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
726 if (node->extract || node->extract_dir) {
727 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
728 add_findex(rx->bsr, node->JobId, node->FileIndex);
729 rx->selected_files++;
734 free_tree(tree.root); /* free the directory tree */
740 * This routine is used to get the current backup or a backup
741 * before the specified date.
743 static int select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
748 char fileset_name[MAX_NAME_LENGTH];
750 char pool_select[MAX_NAME_LENGTH];
754 /* Create temp tables */
755 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
756 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
757 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
758 bsendmsg(ua, "%s\n", db_strerror(ua->db));
760 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
761 bsendmsg(ua, "%s\n", db_strerror(ua->db));
764 * Select Client from the Catalog
766 memset(&cr, 0, sizeof(cr));
767 if (!get_client_dbr(ua, &cr)) {
770 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
775 memset(&fsr, 0, sizeof(fsr));
776 i = find_arg_with_value(ua, "FileSet");
778 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
779 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
780 bsendmsg(ua, _("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
781 db_strerror(ua->db));
785 if (i < 0) { /* fileset not found */
786 Mmsg(&rx->query, uar_sel_fileset, cr.ClientId, cr.ClientId);
787 start_prompt(ua, _("The defined FileSet resources are:\n"));
788 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
789 bsendmsg(ua, "%s\n", db_strerror(ua->db));
791 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
792 fileset_name, sizeof(fileset_name)) < 0) {
796 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
797 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
798 bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
799 bsendmsg(ua, _("This probably means you modified the FileSet.\n"
800 "Continuing anyway.\n"));
804 /* If Pool specified, add PoolId specification */
808 memset(&pr, 0, sizeof(pr));
809 bstrncpy(pr.Name, rx->pool->hdr.name, sizeof(pr.Name));
810 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
811 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%u ", pr.PoolId);
813 bsendmsg(ua, _("Pool \"%s\" not found, using any pool.\n"), pr.Name);
817 /* Find JobId of last Full backup for this client, fileset */
818 Mmsg(&rx->query, uar_last_full, cr.ClientId, cr.ClientId, date, fsr.FileSet,
820 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
821 bsendmsg(ua, "%s\n", db_strerror(ua->db));
825 /* Find all Volumes used by that JobId */
826 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
827 bsendmsg(ua, "%s\n", db_strerror(ua->db));
830 /* Note, this is needed because I don't seem to get the callback
831 * from the call just above.
834 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
835 bsendmsg(ua, "%s\n", db_strerror(ua->db));
837 if (rx->JobTDate == 0) {
838 bsendmsg(ua, _("No Full backup before %s found.\n"), date);
842 /* Now find most recent Decremental Job after Full save, if any */
843 Mmsg(&rx->query, uar_dec, edit_uint64(rx->JobTDate, ed1), date,
844 cr.ClientId, fsr.FileSet, pool_select);
845 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
846 bsendmsg(ua, "%s\n", db_strerror(ua->db));
848 /* Now update JobTDate to lock onto Decremental, if any */
850 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
851 bsendmsg(ua, "%s\n", db_strerror(ua->db));
853 if (rx->JobTDate == 0) {
854 bsendmsg(ua, _("No Full backup before %s found.\n"), date);
858 /* Now find all Incremental Jobs after Full/Dec save */
859 Mmsg(&rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
860 cr.ClientId, fsr.FileSet, pool_select);
861 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
862 bsendmsg(ua, "%s\n", db_strerror(ua->db));
865 /* Get the JobIds from that list */
867 rx->last_jobid[0] = 0;
868 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
869 bsendmsg(ua, "%s\n", db_strerror(ua->db));
872 if (rx->JobIds[0] != 0) {
873 /* Display a list of Jobs selected for this restore */
874 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
876 bsendmsg(ua, _("No jobs found.\n"));
882 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
883 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
888 /* Return next JobId from comma separated list */
889 static int next_jobid_from_list(char **p, uint32_t *JobId)
895 for (int i=0; i<(int)sizeof(jobid); i++) {
896 if (*q == ',' || *q == 0) {
903 if (jobid[0] == 0 || !is_a_number(jobid)) {
907 *JobId = strtoul(jobid, NULL, 10);
912 * Callback handler to get JobId and FileIndex for files
914 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
916 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
917 rx->JobId = atoi(row[0]);
918 add_findex(rx->bsr, rx->JobId, atoi(row[1]));
924 * Callback handler make list of JobIds
926 static int jobid_handler(void *ctx, int num_fields, char **row)
928 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
930 if (strcmp(rx->last_jobid, row[0]) == 0) {
931 return 0; /* duplicate id */
933 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
934 if (rx->JobIds[0] != 0) {
935 pm_strcat(&rx->JobIds, ",");
937 pm_strcat(&rx->JobIds, row[0]);
943 * Callback handler to pickup last Full backup JobTDate
945 static int last_full_handler(void *ctx, int num_fields, char **row)
947 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
949 rx->JobTDate = str_to_int64(row[1]);
954 * Callback handler build FileSet name prompt list
956 static int fileset_handler(void *ctx, int num_fields, char **row)
958 /* row[0] = FileSet (name) */
960 add_prompt((UAContext *)ctx, row[0]);
966 * Called here with each name to be added to the list. The name is
967 * added to the list if it is not already in the list.
969 * Used to make unique list of FileSets and MediaTypes
971 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
973 NAME_LIST *name = (NAME_LIST *)ctx;
975 if (name->num_ids == MAX_ID_LIST_LEN) {
978 if (name->num_ids == name->max_ids) {
979 if (name->max_ids == 0) {
980 name->max_ids = 1000;
981 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
983 name->max_ids = (name->max_ids * 3) / 2;
984 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
987 for (int i=0; i<name->num_ids; i++) {
988 if (strcmp(name->name[i], row[0]) == 0) {
989 return 0; /* already in list, return */
992 /* Add new name to list */
993 name->name[name->num_ids++] = bstrdup(row[0]);
999 * Print names in the list
1001 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1003 for (int i=0; i < name_list->num_ids; i++) {
1004 bsendmsg(ua, "%s\n", name_list->name[i]);
1010 * Free names in the list
1012 static void free_name_list(NAME_LIST *name_list)
1014 for (int i=0; i < name_list->num_ids; i++) {
1015 free(name_list->name[i]);
1017 if (name_list->name) {
1018 free(name_list->name);
1019 name_list->name = NULL;
1021 name_list->max_ids = 0;
1022 name_list->num_ids = 0;
1025 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, RESTORE_CTX *rx)
1027 if (name_list->num_ids > 1) {
1028 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1029 "Restore is not possible. The MediaTypes used are:\n"));
1030 print_name_list(ua, name_list);
1031 rx->store = select_storage_resource(ua);
1035 if (name_list->num_ids == 0) {
1036 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1037 rx->store = select_storage_resource(ua);
1041 rx->store = get_storage_resource(ua, false /* don't use default */);
1044 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1045 "MediaType %s, needed by the Jobs you selected.\n"
1046 "You will be allowed to select a Storage device later.\n"),
1047 name_list->name[0]);