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-2003 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 runcmd(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_dec, *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;
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 */
73 uint32_t selected_files;
86 #define MAX_ID_LIST_LEN 1000000
89 /* Forward referenced functions */
90 static int last_full_handler(void *ctx, int num_fields, char **row);
91 static int jobid_handler(void *ctx, int num_fields, char **row);
92 static int next_jobid_from_list(char **p, uint32_t *JobId);
93 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx);
94 static int fileset_handler(void *ctx, int num_fields, char **row);
95 static void print_name_list(UAContext *ua, NAME_LIST *name_list);
96 static int unique_name_list_handler(void *ctx, int num_fields, char **row);
97 static void free_name_list(NAME_LIST *name_list);
98 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, RESTORE_CTX *rx);
99 static int select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date);
100 static void build_directory_tree(UAContext *ua, RESTORE_CTX *rx);
101 static void free_rx(RESTORE_CTX *rx);
102 static void split_path_and_filename(RESTORE_CTX *rx, char *fname);
103 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row);
104 static int insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file);
105 static void insert_one_file(UAContext *ua, RESTORE_CTX *rx);
106 static int get_client_name(UAContext *ua, RESTORE_CTX *rx);
112 int restore_cmd(UAContext *ua, char *cmd)
114 RESTORE_CTX rx; /* restore context */
118 memset(&rx, 0, sizeof(rx));
120 rx.path = get_pool_memory(PM_FNAME);
121 rx.fname = get_pool_memory(PM_FNAME);
122 rx.JobIds = get_pool_memory(PM_FNAME);
123 rx.query = get_pool_memory(PM_FNAME);
126 i = find_arg_with_value(ua, "where");
128 rx.where = ua->argv[i];
136 /* Ensure there is at least one Restore Job */
138 while ( (job = (JOB *)GetNextRes(R_JOB, (RES *)job)) ) {
139 if (job->JobType == JT_RESTORE) {
140 if (!rx.restore_job) {
141 rx.restore_job = job;
147 if (!rx.restore_jobs) {
149 "No Restore Job Resource found. You must create at least\n"
150 "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 return 0; /* error */
165 case 1: /* select by jobid */
166 build_directory_tree(ua, &rx);
173 if (!complete_bsr(ua, rx.bsr)) { /* find Vol, SessId, SessTime from JobIds */
174 bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
178 write_bsr_file(ua, rx.bsr);
179 bsendmsg(ua, _("\n%u file%s selected to restore.\n\n"), rx.selected_files,
180 rx.selected_files==1?"":"s");
182 bsendmsg(ua, _("No files selected to restore.\n"));
187 if (rx.restore_jobs == 1) {
188 job = rx.restore_job;
190 job = select_restore_job_resource(ua);
193 bsendmsg(ua, _("No Restore Job resource found!\n"));
198 get_client_name(ua, &rx);
200 /* Build run command */
203 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\""
205 job->hdr.name, rx.ClientName, rx.store?rx.store->hdr.name:"",
206 working_directory, rx.where);
209 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
210 job->hdr.name, rx.ClientName, rx.store?rx.store->hdr.name:"",
214 Dmsg1(400, "Submitting: %s\n", ua->cmd);
218 bsendmsg(ua, _("Restore command done.\n"));
223 static void free_rx(RESTORE_CTX *rx)
228 free_pool_memory(rx->JobIds);
232 free_pool_memory(rx->fname);
236 free_pool_memory(rx->path);
240 free_pool_memory(rx->query);
243 free_name_list(&rx->name_list);
246 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
248 /* If no client name specified yet, get it now */
249 if (!rx->ClientName[0]) {
251 /* try command line argument */
252 int i = find_arg_with_value(ua, _("client"));
254 bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
257 memset(&cr, 0, sizeof(cr));
258 if (!get_client_dbr(ua, &cr)) {
262 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
268 * The first step in the restore process is for the user to
269 * select a list of JobIds from which he will subsequently
270 * select which files are to be restored.
272 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
275 char date[MAX_TIME_LENGTH];
281 "List last 20 Jobs run",
282 "List Jobs where a given File is saved",
283 "Enter list of JobIds to select",
284 "Enter SQL list command",
285 "Select the most recent backup for a client",
286 "Select backup for a client before a specified time",
287 "Enter a list of files to restore",
299 switch (find_arg_keyword(ua, kw)) {
301 i = find_arg_with_value(ua, _("jobid"));
305 pm_strcpy(&rx->JobIds, ua->argv[i]);
308 case 1: /* current */
309 bstrutime(date, sizeof(date), time(NULL));
310 if (!select_backups_before_date(ua, rx, date)) {
316 i = find_arg_with_value(ua, _("before"));
320 if (str_to_utime(ua->argv[i]) == 0) {
321 bsendmsg(ua, _("Improper date format: %s\n"), ua->argv[i]);
324 bstrncpy(date, ua->argv[i], sizeof(date));
325 if (!select_backups_before_date(ua, rx, date)) {
331 if (!get_client_name(ua, rx)) {
335 i = find_arg_with_value(ua, _("file"));
339 pm_strcpy(&ua->cmd, ua->argv[i]);
340 insert_one_file(ua, rx);
343 /* Check MediaType and select storage that corresponds */
344 get_storage_from_mediatype(ua, &rx->name_list, rx);
351 bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
352 "to be restored. You will be presented several methods\n"
353 "of specifying the JobIds. Then you will be allowed to\n"
354 "select which files from those JobIds are to be restored.\n\n"));
357 /* If choice not already made above, prompt */
362 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
363 for (int i=0; list[i]; i++) {
364 add_prompt(ua, list[i]);
367 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
370 case 0: /* list last 20 Jobs run */
371 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
374 case 1: /* list where a file is saved */
375 if (!get_cmd(ua, _("Enter Filename: "))) {
378 len = strlen(ua->cmd);
379 fname = (char *)malloc(len * 2 + 1);
380 db_escape_string(fname, ua->cmd, len);
381 Mmsg(&rx->query, uar_file, fname);
383 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
386 case 2: /* enter a list of JobIds */
387 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
390 pm_strcpy(&rx->JobIds, ua->cmd);
392 case 3: /* Enter an SQL list command */
393 if (!get_cmd(ua, _("Enter SQL list command: "))) {
396 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
399 case 4: /* Select the most recent backups */
400 bstrutime(date, sizeof(date), time(NULL));
401 if (!select_backups_before_date(ua, rx, date)) {
405 case 5: /* select backup at specified time */
406 bsendmsg(ua, _("The restored files will the most current backup\n"
407 "BEFORE the date you specify below.\n\n"));
409 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
412 if (str_to_utime(ua->cmd) != 0) {
415 bsendmsg(ua, _("Improper date format.\n"));
417 bstrncpy(date, ua->cmd, sizeof(date));
418 if (!select_backups_before_date(ua, rx, date)) {
422 case 6: /* Enter files */
423 if (!get_client_name(ua, rx)) {
426 bsendmsg(ua, _("Enter file names, or < to enter a filename\n"
427 "containg a list of file names, and terminate\n"
428 "them with a blank line.\n"));
430 if (!get_cmd(ua, _("Enter filename: "))) {
433 len = strlen(ua->cmd);
437 insert_one_file(ua, rx);
439 /* Check MediaType and select storage that corresponds */
440 get_storage_from_mediatype(ua, &rx->name_list, rx);
443 case 7: /* Cancel or quit */
448 if (*rx->JobIds == 0) {
449 bsendmsg(ua, _("No Jobs selected.\n"));
452 bsendmsg(ua, _("You have selected the following JobId%s: %s\n"),
453 strchr(rx->JobIds,',')?"s":"",rx->JobIds);
455 memset(&jr, 0, sizeof(JOB_DBR));
458 for (p=rx->JobIds; ; ) {
459 int stat = next_jobid_from_list(&p, &JobId);
461 bsendmsg(ua, _("Invalid JobId in list.\n"));
467 if (jr.JobId == JobId) {
468 continue; /* duplicate of last JobId */
471 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
472 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
475 rx->TotalFiles += jr.JobFiles;
480 static void insert_one_file(UAContext *ua, RESTORE_CTX *rx)
490 if ((ffd = fopen(p, "r")) == NULL) {
491 bsendmsg(ua, _("Cannot open file %s: ERR=%s\n"),
495 while (fgets(file, sizeof(file), ffd)) {
497 if (!insert_file_into_findex_list(ua, rx, file)) {
498 bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
504 insert_file_into_findex_list(ua, rx, ua->cmd);
510 * For a given file (path+filename), split into path and file, then
511 * lookup the most recent backup in the catalog to get the JobId
512 * and FileIndex, then insert them into the findex list.
514 static int insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file)
516 strip_trailing_junk(file);
517 split_path_and_filename(rx, file);
518 Mmsg(&rx->query, uar_jobid_fileindex, rx->path, rx->fname, rx->ClientName);
520 /* Find and insert jobid and File Index */
521 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
522 bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
523 rx->query, db_strerror(ua->db));
526 bsendmsg(ua, _("No database record found for: %s\n"), file);
529 rx->selected_files++;
531 * Find the FileSets for this JobId and add to the name_list
533 Mmsg(&rx->query, uar_mediatype, rx->JobId);
534 if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
535 bsendmsg(ua, "%s", db_strerror(ua->db));
541 static void split_path_and_filename(RESTORE_CTX *rx, char *name)
545 /* Find path without the filename.
546 * I.e. everything after the last / is a "filename".
547 * OK, maybe it is a directory name, but we treat it like
548 * a filename. If we don't find a / then the whole name
549 * must be a path name (e.g. c:).
551 for (p=f=name; *p; p++) {
553 f = p; /* set pos of last slash */
556 if (*f == '/') { /* did we find a slash? */
557 f++; /* yes, point to filename */
558 } else { /* no, whole thing must be path name */
562 /* If filename doesn't exist (i.e. root directory), we
563 * simply create a blank name consisting of a single
564 * space. This makes handling zero length filenames
569 rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
570 memcpy(rx->fname, f, rx->fnl); /* copy filename */
571 rx->fname[rx->fnl] = 0;
573 rx->fname[0] = ' '; /* blank filename */
580 rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
581 memcpy(rx->path, name, rx->pnl);
582 rx->path[rx->pnl] = 0;
589 Dmsg2(100, "sllit path=%s file=%s\n", rx->path, rx->fname);
592 static void build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
595 JobId_t JobId, last_JobId;
599 memset(&tree, 0, sizeof(TREE_CTX));
601 * Build the directory tree containing JobIds user selected
603 tree.root = new_tree(rx->TotalFiles);
604 tree.root->fname = nofname;
608 * For display purposes, the same JobId, with different volumes may
609 * appear more than once, however, we only insert it once.
612 for (p=rx->JobIds; next_jobid_from_list(&p, &JobId) > 0; ) {
614 if (JobId == last_JobId) {
615 continue; /* eliminate duplicate JobIds */
618 bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
621 * Find files for this JobId and insert them in the tree
623 Mmsg(&rx->query, uar_sel_files, JobId);
624 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
625 bsendmsg(ua, "%s", db_strerror(ua->db));
628 * Find the FileSets for this JobId and add to the name_list
630 Mmsg(&rx->query, uar_mediatype, JobId);
631 if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
632 bsendmsg(ua, "%s", db_strerror(ua->db));
635 bsendmsg(ua, "%d Job%s inserted into the tree and marked for extraction.\n",
636 items, items==1?"":"s");
638 /* Check MediaType and select storage that corresponds */
639 get_storage_from_mediatype(ua, &rx->name_list, rx);
641 if (find_arg(ua, _("all")) < 0) {
642 /* Let the user select which files to restore */
643 user_select_files_from_tree(&tree);
647 * Walk down through the tree finding all files marked to be
648 * extracted making a bootstrap file.
650 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
651 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
653 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
654 add_findex(rx->bsr, node->JobId, node->FileIndex);
655 rx->selected_files++;
659 free_tree(tree.root); /* free the directory tree */
664 * This routine is used to get the current backup or a backup
665 * before the specified date.
667 static int select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
672 char fileset_name[MAX_NAME_LENGTH];
676 /* Create temp tables */
677 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
678 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
679 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
680 bsendmsg(ua, "%s\n", db_strerror(ua->db));
682 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
683 bsendmsg(ua, "%s\n", db_strerror(ua->db));
686 * Select Client from the Catalog
688 memset(&cr, 0, sizeof(cr));
689 if (!get_client_dbr(ua, &cr)) {
692 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
697 Mmsg(&rx->query, uar_sel_fileset, cr.ClientId, cr.ClientId);
698 start_prompt(ua, _("The defined FileSet resources are:\n"));
699 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
700 bsendmsg(ua, "%s\n", db_strerror(ua->db));
702 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
703 fileset_name, sizeof(fileset_name)) < 0) {
706 fsr.FileSetId = atoi(fileset_name); /* Id is first part of name */
707 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
708 bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
709 bsendmsg(ua, _("This probably means you modified the FileSet.\n"
710 "Continuing anyway.\n"));
714 /* Find JobId of last Full backup for this client, fileset */
715 Mmsg(&rx->query, uar_last_full, cr.ClientId, cr.ClientId, date, fsr.FileSetId);
716 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
717 bsendmsg(ua, "%s\n", db_strerror(ua->db));
721 /* Find all Volumes used by that JobId */
722 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
723 bsendmsg(ua, "%s\n", db_strerror(ua->db));
726 /* Note, this is needed as I don't seem to get the callback
727 * from the call just above.
730 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
731 bsendmsg(ua, "%s\n", db_strerror(ua->db));
733 if (rx->JobTDate == 0) {
734 bsendmsg(ua, _("No Full backup before %s found.\n"), date);
738 /* Now find all Incremental/Decremental Jobs after Full save */
739 Mmsg(&rx->query, uar_inc_dec, edit_uint64(rx->JobTDate, ed1), date,
740 cr.ClientId, fsr.FileSetId);
741 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
742 bsendmsg(ua, "%s\n", db_strerror(ua->db));
745 /* Get the JobIds from that list */
747 rx->last_jobid[0] = 0;
748 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
749 bsendmsg(ua, "%s\n", db_strerror(ua->db));
752 if (rx->JobIds[0] != 0) {
753 /* Display a list of Jobs selected for this restore */
754 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
756 bsendmsg(ua, _("No jobs found.\n"));
762 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
763 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
767 /* Return next JobId from comma separated list */
768 static int next_jobid_from_list(char **p, uint32_t *JobId)
774 for (int i=0; i<(int)sizeof(jobid); i++) {
775 if (*q == ',' || *q == 0) {
782 if (jobid[0] == 0 || !is_a_number(jobid)) {
786 *JobId = strtoul(jobid, NULL, 10);
791 * Callback handler to get JobId and FileIndex for files
793 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
795 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
796 rx->JobId = atoi(row[0]);
797 add_findex(rx->bsr, rx->JobId, atoi(row[1]));
803 * Callback handler make list of JobIds
805 static int jobid_handler(void *ctx, int num_fields, char **row)
807 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
809 if (strcmp(rx->last_jobid, row[0]) == 0) {
810 return 0; /* duplicate id */
812 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
813 if (rx->JobIds[0] != 0) {
814 pm_strcat(&rx->JobIds, ",");
816 pm_strcat(&rx->JobIds, row[0]);
822 * Callback handler to pickup last Full backup JobTDate
824 static int last_full_handler(void *ctx, int num_fields, char **row)
826 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
828 rx->JobTDate = strtoll(row[1], NULL, 10);
834 * Callback handler build fileset prompt list
836 static int fileset_handler(void *ctx, int num_fields, char **row)
838 char prompt[MAX_NAME_LENGTH+200];
840 snprintf(prompt, sizeof(prompt), "%s %s %s", row[0], row[1], row[2]);
841 add_prompt((UAContext *)ctx, prompt);
846 * Called here with each name to be added to the list. The name is
847 * added to the list if it is not already in the list.
849 * Used to make unique list of FileSets and MediaTypes
851 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
853 NAME_LIST *name = (NAME_LIST *)ctx;
855 if (name->num_ids == MAX_ID_LIST_LEN) {
858 if (name->num_ids == name->max_ids) {
859 if (name->max_ids == 0) {
860 name->max_ids = 1000;
861 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
863 name->max_ids = (name->max_ids * 3) / 2;
864 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
867 for (int i=0; i<name->num_ids; i++) {
868 if (strcmp(name->name[i], row[0]) == 0) {
869 return 0; /* already in list, return */
872 /* Add new name to list */
873 name->name[name->num_ids++] = bstrdup(row[0]);
879 * Print names in the list
881 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
883 for (int i=0; i < name_list->num_ids; i++) {
884 bsendmsg(ua, "%s\n", name_list->name[i]);
890 * Free names in the list
892 static void free_name_list(NAME_LIST *name_list)
894 for (int i=0; i < name_list->num_ids; i++) {
895 free(name_list->name[i]);
897 if (name_list->name) {
898 free(name_list->name);
899 name_list->name = NULL;
901 name_list->max_ids = 0;
902 name_list->num_ids = 0;
905 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, RESTORE_CTX *rx)
907 char name[MAX_NAME_LENGTH];
910 if (name_list->num_ids > 1) {
911 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
912 "Restore is not possible. The MediaTypes used are:\n"));
913 print_name_list(ua, name_list);
914 rx->store = select_storage_resource(ua);
918 if (name_list->num_ids == 0) {
919 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
920 rx->store = select_storage_resource(ua);
924 start_prompt(ua, _("The defined Storage resources are:\n"));
926 while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
927 if (strcmp(store->media_type, name_list->name[0]) == 0) {
928 add_prompt(ua, store->hdr.name);
932 do_prompt(ua, _("Storage"), _("Select Storage resource"), name, sizeof(name));
933 rx->store = (STORE *)GetResWithName(R_STORAGE, name);
935 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
936 "MediaType %s, needed by the Jobs you selected.\n"
937 "You will be allowed to select a Storage device later.\n"),