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