]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
Constrain BSR indexes, fix bscan, add some new alist code
[bacula/bacula] / bacula / src / dird / ua_restore.c
1 /*
2  *
3  *   Bacula Director -- User Agent Database restore Command
4  *      Creates a bootstrap file for restoring files and
5  *      starts the restore job.
6  *
7  *      Tree handling routines split into ua_tree.c July MMIII.
8  *      BSR (bootstrap record) handling routines split into
9  *        bsr.c July MMIII
10  *
11  *     Kern Sibbald, July MMII
12  *
13  *   Version $Id$
14  */
15
16 /*
17    Copyright (C) 2002-2003 Kern Sibbald and John Walker
18
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.
23
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.
28
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,
32    MA 02111-1307, USA.
33
34  */
35
36 #include "bacula.h"
37 #include "dird.h"
38
39
40 /* Imported functions */
41 extern int runcmd(UAContext *ua, char *cmd);
42
43 /* Imported variables */
44 extern char *uar_list_jobs,     *uar_file,        *uar_sel_files;
45 extern char *uar_del_temp,      *uar_del_temp1,   *uar_create_temp;
46 extern char *uar_create_temp1,  *uar_last_full,   *uar_full;
47 extern char *uar_inc_dec,       *uar_list_temp,   *uar_sel_jobid_temp;
48 extern char *uar_sel_all_temp1, *uar_sel_fileset, *uar_mediatype;
49
50
51 /* Main structure for obtaining JobIds */
52 struct JOBIDS {
53    utime_t JobTDate;
54    uint32_t TotalFiles;
55    char ClientName[MAX_NAME_LENGTH];
56    char JobIds[200];                  /* User entered string of JobIds */
57    STORE  *store;
58 };
59
60 struct NAME_LIST {
61    char **name;                       /* list of names */
62    int num_ids;                       /* ids stored */
63    int max_ids;                       /* size of array */
64    int num_del;                       /* number deleted */
65    int tot_ids;                       /* total to process */
66 };
67
68 #define MAX_ID_LIST_LEN 1000000
69
70
71 /* Forward referenced functions */
72 static int last_full_handler(void *ctx, int num_fields, char **row);
73 static int jobid_handler(void *ctx, int num_fields, char **row);
74 static int next_jobid_from_list(char **p, uint32_t *JobId);
75 static int user_select_jobids(UAContext *ua, JOBIDS *ji);
76 static int fileset_handler(void *ctx, int num_fields, char **row);
77 static void print_name_list(UAContext *ua, NAME_LIST *name_list);
78 static int unique_name_list_handler(void *ctx, int num_fields, char **row);
79 static void free_name_list(NAME_LIST *name_list);
80 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JOBIDS *ji);
81 static int select_backups_before_date(UAContext *ua, JOBIDS *ji, char *date);
82
83 /*
84  *   Restore files
85  *
86  */
87 int restorecmd(UAContext *ua, char *cmd)
88 {
89    POOLMEM *query;
90    TREE_CTX tree;
91    JobId_t JobId, last_JobId;
92    char *p;
93    RBSR *bsr;
94    char *nofname = "";
95    JOBIDS ji;
96    JOB *job = NULL;
97    JOB *restore_job = NULL;
98    int restore_jobs = 0;
99    NAME_LIST name_list;
100    uint32_t selected_files = 0;
101    char *where = NULL;
102    int i;
103
104    i = find_arg_with_value(ua, "where");
105    if (i >= 0) {
106       where = ua->argv[i];
107    }
108
109    if (!open_db(ua)) {
110       return 0;
111    }
112
113    memset(&tree, 0, sizeof(TREE_CTX));
114    memset(&name_list, 0, sizeof(name_list));
115    memset(&ji, 0, sizeof(ji));
116
117    /* Ensure there is at least one Restore Job */
118    LockRes();
119    while ( (job = (JOB *)GetNextRes(R_JOB, (RES *)job)) ) {
120       if (job->JobType == JT_RESTORE) {
121          if (!restore_job) {
122             restore_job = job;
123          }
124          restore_jobs++;
125       }
126    }
127    UnlockRes();
128    if (!restore_jobs) {
129       bsendmsg(ua, _(
130          "No Restore Job Resource found. You must create at least\n"
131          "one before running this command.\n"));
132       return 0;
133    }
134
135    /* 
136     * Request user to select JobIds by various different methods
137     *  last 20 jobs, where File saved, most recent backup, ...
138     */
139    if (!user_select_jobids(ua, &ji)) {
140       return 0;
141    }
142
143    /* 
144     * Build the directory tree containing JobIds user selected
145     */
146    tree.root = new_tree(ji.TotalFiles);
147    tree.root->fname = nofname;
148    tree.ua = ua;
149    query = get_pool_memory(PM_MESSAGE);
150    last_JobId = 0;
151    /*
152     * For display purposes, the same JobId, with different volumes may
153     * appear more than once, however, we only insert it once.
154     */
155    int items = 0;
156    for (p=ji.JobIds; next_jobid_from_list(&p, &JobId) > 0; ) {
157
158       if (JobId == last_JobId) {             
159          continue;                    /* eliminate duplicate JobIds */
160       }
161       last_JobId = JobId;
162       bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
163       items++;
164       /*
165        * Find files for this JobId and insert them in the tree
166        */
167       Mmsg(&query, uar_sel_files, JobId);
168       if (!db_sql_query(ua->db, query, insert_tree_handler, (void *)&tree)) {
169          bsendmsg(ua, "%s", db_strerror(ua->db));
170       }
171       /*
172        * Find the FileSets for this JobId and add to the name_list
173        */
174       Mmsg(&query, uar_mediatype, JobId);
175       if (!db_sql_query(ua->db, query, unique_name_list_handler, (void *)&name_list)) {
176          bsendmsg(ua, "%s", db_strerror(ua->db));
177       }
178    }
179    bsendmsg(ua, "%d item%s inserted into the tree and marked for extraction.\n", 
180       items, items==1?"":"s");
181    free_pool_memory(query);
182
183    /* Check MediaType and select storage that corresponds */
184    get_storage_from_mediatype(ua, &name_list, &ji);
185    free_name_list(&name_list);
186
187    if (find_arg(ua, _("all")) < 0) {
188       /* Let the user select which files to restore */
189       user_select_files_from_tree(&tree);
190    }
191
192    /*
193     * Walk down through the tree finding all files marked to be 
194     *  extracted making a bootstrap file.
195     */
196    bsr = new_bsr();
197    for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
198       Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
199       if (node->extract) {
200          Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
201          add_findex(bsr, node->JobId, node->FileIndex);
202          selected_files++;
203       }
204    }
205
206    free_tree(tree.root);              /* free the directory tree */
207
208    if (bsr->JobId) {
209       if (!complete_bsr(ua, bsr)) {   /* find Vol, SessId, SessTime from JobIds */
210          bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
211          free_bsr(bsr);
212          return 0;
213       }
214 //    print_bsr(ua, bsr);
215       write_bsr_file(ua, bsr);
216       bsendmsg(ua, _("\n%u files selected to restore.\n\n"), selected_files);
217    } else {
218       bsendmsg(ua, _("No files selected to restore.\n"));
219    }
220    free_bsr(bsr);
221
222    if (restore_jobs == 1) {
223       job = restore_job;
224    } else {
225       job = select_restore_job_resource(ua);
226    }
227    if (!job) {
228       bsendmsg(ua, _("No Restore Job resource found!\n"));
229       return 0;
230    }
231
232    /* If no client name specified yet, get it now */
233    if (!ji.ClientName[0]) {
234       CLIENT_DBR cr;
235       memset(&cr, 0, sizeof(cr));
236       if (!get_client_dbr(ua, &cr)) {
237          return 0;
238       }
239       bstrncpy(ji.ClientName, cr.Name, sizeof(ji.ClientName));
240    }
241
242    /* Build run command */
243    if (where) {
244       Mmsg(&ua->cmd, 
245           "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\""
246           " where=\"%s\"",
247           job->hdr.name, ji.ClientName, ji.store?ji.store->hdr.name:"",
248           working_directory, where);
249    } else {
250       Mmsg(&ua->cmd, 
251           "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
252           job->hdr.name, ji.ClientName, ji.store?ji.store->hdr.name:"",
253           working_directory);
254    }
255    Dmsg1(400, "Submitting: %s\n", ua->cmd);
256    
257    parse_ua_args(ua);
258    runcmd(ua, ua->cmd);
259
260    bsendmsg(ua, _("Restore command done.\n"));
261    return 1;
262 }
263
264 /*
265  * The first step in the restore process is for the user to 
266  *  select a list of JobIds from which he will subsequently
267  *  select which files are to be restored.
268  */
269 static int user_select_jobids(UAContext *ua, JOBIDS *ji)
270 {
271    char *p;
272    char date[MAX_TIME_LENGTH];
273    JobId_t JobId;
274    JOB_DBR jr;
275    POOLMEM *query;
276    bool done = false;
277    int i;
278    char *list[] = { 
279       "List last 20 Jobs run",
280       "List Jobs where a given File is saved",
281       "Enter list of JobIds to select",
282       "Enter SQL list command", 
283       "Select the most recent backup for a client",
284       "Select backup for a client before a specified time",
285       "Cancel",
286       NULL };
287
288    char *kw[] = {
289       "jobid",     /* 0 */
290       "current",   /* 1 */
291       "before",    /* 2 */
292       NULL
293    };
294
295    switch (find_arg_keyword(ua, kw)) {
296    case 0:                            /* jobid */
297       i = find_arg_with_value(ua, _("jobid"));
298       if (i < 0) {
299          return 0;
300       }
301       bstrncpy(ji->JobIds, ua->argv[i], sizeof(ji->JobIds));
302       done = true;
303       break;
304    case 1:                            /* current */
305       bstrutime(date, sizeof(date), time(NULL));
306       if (!select_backups_before_date(ua, ji, date)) {
307          return 0;
308       }
309       done = true;
310       break;
311    case 2:                            /* before */
312       i = find_arg_with_value(ua, _("before"));
313       if (i < 0) {
314          return 0;
315       }
316       if (str_to_utime(ua->argv[i]) == 0) {
317          bsendmsg(ua, _("Improper date format: %s\n"), ua->argv[i]);
318          return 0;
319       }
320       bstrncpy(date, ua->argv[i], sizeof(date));
321       if (!select_backups_before_date(ua, ji, date)) {
322          return 0;
323       }
324       done = true;
325       break;
326    default:
327       break;
328    }
329        
330    if (!done) {
331       bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
332                   "to be restored. You will be presented several methods\n"
333                   "of specifying the JobIds. Then you will be allowed to\n"
334                   "select which files from those JobIds are to be restored.\n\n"));
335    }
336
337    /* If choice not already made above, prompt */
338    for ( ; !done; ) {
339       start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
340       for (int i=0; list[i]; i++) {
341          add_prompt(ua, list[i]);
342       }
343       done = true;
344       switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
345       case -1:                        /* error */
346          return 0;
347       case 0:                         /* list last 20 Jobs run */
348          db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
349          done = false;
350          break;
351       case 1:                         /* list where a file is saved */
352          char *fname;
353          int len;
354          if (!get_cmd(ua, _("Enter Filename: "))) {
355             return 0;
356          }
357          len = strlen(ua->cmd);
358          fname = (char *)malloc(len * 2 + 1);
359          db_escape_string(fname, ua->cmd, len);
360          query = get_pool_memory(PM_MESSAGE);
361          Mmsg(&query, uar_file, fname);
362          free(fname);
363          db_list_sql_query(ua->jcr, ua->db, query, prtit, ua, 1, HORZ_LIST);
364          free_pool_memory(query);
365          done = false;
366          break;
367       case 2:                         /* enter a list of JobIds */
368          if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
369             return 0;
370          }
371          bstrncpy(ji->JobIds, ua->cmd, sizeof(ji->JobIds));
372          break;
373       case 3:                         /* Enter an SQL list command */
374          if (!get_cmd(ua, _("Enter SQL list command: "))) {
375             return 0;
376          }
377          db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
378          done = false;
379          break;
380       case 4:                         /* Select the most recent backups */
381          bstrutime(date, sizeof(date), time(NULL));
382          if (!select_backups_before_date(ua, ji, date)) {
383             return 0;
384          }
385          break;
386       case 5:                         /* select backup at specified time */
387          bsendmsg(ua, _("The restored files will the most current backup\n"
388                         "BEFORE the date you specify below.\n\n"));
389          for ( ;; ) {
390             if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
391                return 0;
392             }
393             if (str_to_utime(ua->cmd) != 0) {
394                break;
395             }
396             bsendmsg(ua, _("Improper date format.\n"));
397          }              
398          bstrncpy(date, ua->cmd, sizeof(date));
399          if (!select_backups_before_date(ua, ji, date)) {
400             return 0;
401          }
402
403       case 6:                         /* Cancel or quit */
404          return 0;
405       }
406    }
407
408    if (*ji->JobIds == 0) {
409       bsendmsg(ua, _("No Jobs selected.\n"));
410       return 0;
411    }
412    bsendmsg(ua, _("You have selected the following JobId: %s\n"), ji->JobIds);
413
414    memset(&jr, 0, sizeof(JOB_DBR));
415
416    ji->TotalFiles = 0;
417    for (p=ji->JobIds; ; ) {
418       int stat = next_jobid_from_list(&p, &JobId);
419       if (stat < 0) {
420          bsendmsg(ua, _("Invalid JobId in list.\n"));
421          return 0;
422       }
423       if (stat == 0) {
424          break;
425       }
426       jr.JobId = JobId;
427       if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
428          bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
429          return 0;
430       }
431       ji->TotalFiles += jr.JobFiles;
432    }
433    return 1;
434 }
435
436 /*
437  * This routine is used to get the current backup or a backup
438  *   before the specified date.
439  */
440 static int select_backups_before_date(UAContext *ua, JOBIDS *ji, char *date)
441 {
442    int stat = 0;
443    POOLMEM *query;
444    FILESET_DBR fsr;
445    CLIENT_DBR cr;
446    char fileset_name[MAX_NAME_LENGTH];
447    char ed1[50];
448
449    query = get_pool_memory(PM_MESSAGE);
450
451    /* Create temp tables */
452    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
453    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
454    if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
455       bsendmsg(ua, "%s\n", db_strerror(ua->db));
456    }
457    if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
458       bsendmsg(ua, "%s\n", db_strerror(ua->db));
459    }
460    /*
461     * Select Client from the Catalog
462     */
463    memset(&cr, 0, sizeof(cr));
464    if (!get_client_dbr(ua, &cr)) {
465       goto bail_out;
466    }
467    bstrncpy(ji->ClientName, cr.Name, sizeof(ji->ClientName));
468
469    /*
470     * Select FileSet 
471     */
472    Mmsg(&query, uar_sel_fileset, cr.ClientId, cr.ClientId);
473    start_prompt(ua, _("The defined FileSet resources are:\n"));
474    if (!db_sql_query(ua->db, query, fileset_handler, (void *)ua)) {
475       bsendmsg(ua, "%s\n", db_strerror(ua->db));
476    }
477    if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"), 
478                  fileset_name, sizeof(fileset_name)) < 0) {
479       goto bail_out;
480    }
481    fsr.FileSetId = atoi(fileset_name);  /* Id is first part of name */
482    if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
483       bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
484       bsendmsg(ua, _("This probably means you modified the FileSet.\n"
485                      "Continuing anyway.\n"));
486    }
487
488
489    /* Find JobId of last Full backup for this client, fileset */
490    Mmsg(&query, uar_last_full, cr.ClientId, cr.ClientId, date, fsr.FileSetId);
491    if (!db_sql_query(ua->db, query, NULL, NULL)) {
492       bsendmsg(ua, "%s\n", db_strerror(ua->db));
493       goto bail_out;
494    }
495
496    /* Find all Volumes used by that JobId */
497    if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
498       bsendmsg(ua, "%s\n", db_strerror(ua->db));
499       goto bail_out;
500    }
501    /* Note, this is needed as I don't seem to get the callback
502     * from the call just above.
503     */
504    ji->JobTDate = 0;
505    if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)ji)) {
506       bsendmsg(ua, "%s\n", db_strerror(ua->db));
507    }
508    if (ji->JobTDate == 0) {
509       bsendmsg(ua, _("No Full backup before %s found.\n"), date);
510       goto bail_out;
511    }
512
513    /* Now find all Incremental/Decremental Jobs after Full save */
514    Mmsg(&query, uar_inc_dec, edit_uint64(ji->JobTDate, ed1), date,
515         cr.ClientId, fsr.FileSetId);
516    if (!db_sql_query(ua->db, query, NULL, NULL)) {
517       bsendmsg(ua, "%s\n", db_strerror(ua->db));
518    }
519
520    /* Get the JobIds from that list */
521    ji->JobIds[0] = 0;
522    if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)ji)) {
523       bsendmsg(ua, "%s\n", db_strerror(ua->db));
524    }
525
526    if (ji->JobIds[0] != 0) {
527       /* Display a list of Jobs selected for this restore */
528       db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
529    } else {
530       bsendmsg(ua, _("No jobs found.\n")); 
531    }
532
533    stat = 1;
534  
535 bail_out:
536    free_pool_memory(query);
537    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
538    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
539    return stat;
540 }
541
542 /* Return next JobId from comma separated list */
543 static int next_jobid_from_list(char **p, uint32_t *JobId)
544 {
545    char jobid[30];
546    char *q = *p;
547
548    jobid[0] = 0;
549    for (int i=0; i<(int)sizeof(jobid); i++) {
550       if (*q == ',' || *q == 0) {
551          q++;
552          break;
553       }
554       jobid[i] = *q++;
555       jobid[i+1] = 0;
556    }
557    if (jobid[0] == 0 || !is_a_number(jobid)) {
558       return 0;
559    }
560    *p = q;
561    *JobId = strtoul(jobid, NULL, 10);
562    return 1;
563 }
564
565 /*
566  * Callback handler make list of JobIds
567  */
568 static int jobid_handler(void *ctx, int num_fields, char **row)
569 {
570    JOBIDS *ji = (JOBIDS *)ctx;
571
572    /* Concatenate a JobId if it does not exceed array size */
573    if (strlen(ji->JobIds)+strlen(row[0])+2 < sizeof(ji->JobIds)) {
574       if (ji->JobIds[0] != 0) {
575          strcat(ji->JobIds, ",");
576       }
577       strcat(ji->JobIds, row[0]);
578    }
579    return 0;
580 }
581
582
583 /*
584  * Callback handler to pickup last Full backup JobTDate
585  */
586 static int last_full_handler(void *ctx, int num_fields, char **row)
587 {
588    JOBIDS *ji = (JOBIDS *)ctx;
589
590    ji->JobTDate = strtoll(row[1], NULL, 10);
591
592    return 0;
593 }
594
595 /*
596  * Callback handler build fileset prompt list
597  */
598 static int fileset_handler(void *ctx, int num_fields, char **row)
599 {
600    char prompt[MAX_NAME_LENGTH+200];
601
602    snprintf(prompt, sizeof(prompt), "%s  %s  %s", row[0], row[1], row[2]);
603    add_prompt((UAContext *)ctx, prompt);
604    return 0;
605 }
606
607 /*
608  * Called here with each name to be added to the list. The name is
609  *   added to the list if it is not already in the list.
610  *
611  * Used to make unique list of FileSets and MediaTypes
612  */
613 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
614 {
615    NAME_LIST *name = (NAME_LIST *)ctx;
616
617    if (name->num_ids == MAX_ID_LIST_LEN) {  
618       return 1;
619    }
620    if (name->num_ids == name->max_ids) {
621       if (name->max_ids == 0) {
622          name->max_ids = 1000;
623          name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
624       } else {
625          name->max_ids = (name->max_ids * 3) / 2;
626          name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
627       }
628    }
629    for (int i=0; i<name->num_ids; i++) {
630       if (strcmp(name->name[i], row[0]) == 0) {
631          return 0;                    /* already in list, return */
632       }
633    }
634    /* Add new name to list */
635    name->name[name->num_ids++] = bstrdup(row[0]);
636    return 0;
637 }
638
639
640 /*
641  * Print names in the list
642  */
643 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
644
645    for (int i=0; i < name_list->num_ids; i++) {
646       bsendmsg(ua, "%s\n", name_list->name[i]);
647    }
648 }
649
650
651 /*
652  * Free names in the list
653  */
654 static void free_name_list(NAME_LIST *name_list)
655
656    for (int i=0; i < name_list->num_ids; i++) {
657       free(name_list->name[i]);
658    }
659    if (name_list->name) {
660       free(name_list->name);
661    }
662    name_list->max_ids = 0;
663    name_list->num_ids = 0;
664 }
665
666 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JOBIDS *ji)
667 {
668    char name[MAX_NAME_LENGTH];
669    STORE *store = NULL;
670
671    if (name_list->num_ids > 1) {
672       bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
673          "Restore is not possible. The MediaTypes used are:\n"));
674       print_name_list(ua, name_list);
675       ji->store = select_storage_resource(ua);
676       return;
677    }
678
679    if (name_list->num_ids == 0) {
680       bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
681       ji->store = select_storage_resource(ua);
682       return;
683    }
684
685    start_prompt(ua, _("The defined Storage resources are:\n"));
686    LockRes();
687    while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
688       if (strcmp(store->media_type, name_list->name[0]) == 0) {
689          add_prompt(ua, store->hdr.name);
690       }
691    }
692    UnlockRes();
693    do_prompt(ua, _("Storage"),  _("Select Storage resource"), name, sizeof(name));
694    ji->store = (STORE *)GetResWithName(R_STORAGE, name);
695    if (!ji->store) {
696       bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
697          "MediaType %s, needed by the Jobs you selected.\n"
698          "You will be allowed to select a Storage device later.\n"),
699          name_list->name[0]); 
700    }
701 }