]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
Cache path in tree.c
[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: %s\n"), 
414       strchr(ji->JobIds,',')?"s":"",ji->JobIds);
415
416    memset(&jr, 0, sizeof(JOB_DBR));
417
418    ji->TotalFiles = 0;
419    for (p=ji->JobIds; ; ) {
420       int stat = next_jobid_from_list(&p, &JobId);
421       if (stat < 0) {
422          bsendmsg(ua, _("Invalid JobId in list.\n"));
423          return 0;
424       }
425       if (stat == 0) {
426          break;
427       }
428       if (jr.JobId == JobId) {
429          continue;                    /* duplicate of last JobId */
430       }
431       jr.JobId = JobId;
432       if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
433          bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
434          return 0;
435       }
436       ji->TotalFiles += jr.JobFiles;
437    }
438    return 1;
439 }
440
441 /*
442  * This routine is used to get the current backup or a backup
443  *   before the specified date.
444  */
445 static int select_backups_before_date(UAContext *ua, JOBIDS *ji, char *date)
446 {
447    int stat = 0;
448    POOLMEM *query;
449    FILESET_DBR fsr;
450    CLIENT_DBR cr;
451    char fileset_name[MAX_NAME_LENGTH];
452    char ed1[50];
453
454    query = get_pool_memory(PM_MESSAGE);
455
456    /* Create temp tables */
457    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
458    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
459    if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
460       bsendmsg(ua, "%s\n", db_strerror(ua->db));
461    }
462    if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
463       bsendmsg(ua, "%s\n", db_strerror(ua->db));
464    }
465    /*
466     * Select Client from the Catalog
467     */
468    memset(&cr, 0, sizeof(cr));
469    if (!get_client_dbr(ua, &cr)) {
470       goto bail_out;
471    }
472    bstrncpy(ji->ClientName, cr.Name, sizeof(ji->ClientName));
473
474    /*
475     * Select FileSet 
476     */
477    Mmsg(&query, uar_sel_fileset, cr.ClientId, cr.ClientId);
478    start_prompt(ua, _("The defined FileSet resources are:\n"));
479    if (!db_sql_query(ua->db, query, fileset_handler, (void *)ua)) {
480       bsendmsg(ua, "%s\n", db_strerror(ua->db));
481    }
482    if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"), 
483                  fileset_name, sizeof(fileset_name)) < 0) {
484       goto bail_out;
485    }
486    fsr.FileSetId = atoi(fileset_name);  /* Id is first part of name */
487    if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
488       bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
489       bsendmsg(ua, _("This probably means you modified the FileSet.\n"
490                      "Continuing anyway.\n"));
491    }
492
493
494    /* Find JobId of last Full backup for this client, fileset */
495    Mmsg(&query, uar_last_full, cr.ClientId, cr.ClientId, date, fsr.FileSetId);
496    if (!db_sql_query(ua->db, query, NULL, NULL)) {
497       bsendmsg(ua, "%s\n", db_strerror(ua->db));
498       goto bail_out;
499    }
500
501    /* Find all Volumes used by that JobId */
502    if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
503       bsendmsg(ua, "%s\n", db_strerror(ua->db));
504       goto bail_out;
505    }
506    /* Note, this is needed as I don't seem to get the callback
507     * from the call just above.
508     */
509    ji->JobTDate = 0;
510    if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)ji)) {
511       bsendmsg(ua, "%s\n", db_strerror(ua->db));
512    }
513    if (ji->JobTDate == 0) {
514       bsendmsg(ua, _("No Full backup before %s found.\n"), date);
515       goto bail_out;
516    }
517
518    /* Now find all Incremental/Decremental Jobs after Full save */
519    Mmsg(&query, uar_inc_dec, edit_uint64(ji->JobTDate, ed1), date,
520         cr.ClientId, fsr.FileSetId);
521    if (!db_sql_query(ua->db, query, NULL, NULL)) {
522       bsendmsg(ua, "%s\n", db_strerror(ua->db));
523    }
524
525    /* Get the JobIds from that list */
526    ji->JobIds[0] = 0;
527    ji->last_jobid[0] = 0;
528    if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)ji)) {
529       bsendmsg(ua, "%s\n", db_strerror(ua->db));
530    }
531
532    if (ji->JobIds[0] != 0) {
533       /* Display a list of Jobs selected for this restore */
534       db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
535    } else {
536       bsendmsg(ua, _("No jobs found.\n")); 
537    }
538
539    stat = 1;
540  
541 bail_out:
542    free_pool_memory(query);
543    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
544    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
545    return stat;
546 }
547
548 /* Return next JobId from comma separated list */
549 static int next_jobid_from_list(char **p, uint32_t *JobId)
550 {
551    char jobid[30];
552    char *q = *p;
553
554    jobid[0] = 0;
555    for (int i=0; i<(int)sizeof(jobid); i++) {
556       if (*q == ',' || *q == 0) {
557          q++;
558          break;
559       }
560       jobid[i] = *q++;
561       jobid[i+1] = 0;
562    }
563    if (jobid[0] == 0 || !is_a_number(jobid)) {
564       return 0;
565    }
566    *p = q;
567    *JobId = strtoul(jobid, NULL, 10);
568    return 1;
569 }
570
571 /*
572  * Callback handler make list of JobIds
573  */
574 static int jobid_handler(void *ctx, int num_fields, char **row)
575 {
576    JOBIDS *ji = (JOBIDS *)ctx;
577
578    if (strcmp(ji->last_jobid, row[0]) == 0) {           
579       return 0;                       /* duplicate id */
580    }
581    bstrncpy(ji->last_jobid, row[0], sizeof(ji->last_jobid));
582    /* Concatenate a JobId if it does not exceed array size */
583    if (strlen(ji->JobIds)+strlen(row[0])+2 < sizeof(ji->JobIds)) {
584       if (ji->JobIds[0] != 0) {
585          strcat(ji->JobIds, ",");
586       }
587       strcat(ji->JobIds, row[0]);
588    }
589    return 0;
590 }
591
592
593 /*
594  * Callback handler to pickup last Full backup JobTDate
595  */
596 static int last_full_handler(void *ctx, int num_fields, char **row)
597 {
598    JOBIDS *ji = (JOBIDS *)ctx;
599
600    ji->JobTDate = strtoll(row[1], NULL, 10);
601
602    return 0;
603 }
604
605 /*
606  * Callback handler build fileset prompt list
607  */
608 static int fileset_handler(void *ctx, int num_fields, char **row)
609 {
610    char prompt[MAX_NAME_LENGTH+200];
611
612    snprintf(prompt, sizeof(prompt), "%s  %s  %s", row[0], row[1], row[2]);
613    add_prompt((UAContext *)ctx, prompt);
614    return 0;
615 }
616
617 /*
618  * Called here with each name to be added to the list. The name is
619  *   added to the list if it is not already in the list.
620  *
621  * Used to make unique list of FileSets and MediaTypes
622  */
623 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
624 {
625    NAME_LIST *name = (NAME_LIST *)ctx;
626
627    if (name->num_ids == MAX_ID_LIST_LEN) {  
628       return 1;
629    }
630    if (name->num_ids == name->max_ids) {
631       if (name->max_ids == 0) {
632          name->max_ids = 1000;
633          name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
634       } else {
635          name->max_ids = (name->max_ids * 3) / 2;
636          name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
637       }
638    }
639    for (int i=0; i<name->num_ids; i++) {
640       if (strcmp(name->name[i], row[0]) == 0) {
641          return 0;                    /* already in list, return */
642       }
643    }
644    /* Add new name to list */
645    name->name[name->num_ids++] = bstrdup(row[0]);
646    return 0;
647 }
648
649
650 /*
651  * Print names in the list
652  */
653 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
654
655    for (int i=0; i < name_list->num_ids; i++) {
656       bsendmsg(ua, "%s\n", name_list->name[i]);
657    }
658 }
659
660
661 /*
662  * Free names in the list
663  */
664 static void free_name_list(NAME_LIST *name_list)
665
666    for (int i=0; i < name_list->num_ids; i++) {
667       free(name_list->name[i]);
668    }
669    if (name_list->name) {
670       free(name_list->name);
671    }
672    name_list->max_ids = 0;
673    name_list->num_ids = 0;
674 }
675
676 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JOBIDS *ji)
677 {
678    char name[MAX_NAME_LENGTH];
679    STORE *store = NULL;
680
681    if (name_list->num_ids > 1) {
682       bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
683          "Restore is not possible. The MediaTypes used are:\n"));
684       print_name_list(ua, name_list);
685       ji->store = select_storage_resource(ua);
686       return;
687    }
688
689    if (name_list->num_ids == 0) {
690       bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
691       ji->store = select_storage_resource(ua);
692       return;
693    }
694
695    start_prompt(ua, _("The defined Storage resources are:\n"));
696    LockRes();
697    while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
698       if (strcmp(store->media_type, name_list->name[0]) == 0) {
699          add_prompt(ua, store->hdr.name);
700       }
701    }
702    UnlockRes();
703    do_prompt(ua, _("Storage"),  _("Select Storage resource"), name, sizeof(name));
704    ji->store = (STORE *)GetResWithName(R_STORAGE, name);
705    if (!ji->store) {
706       bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
707          "MediaType %s, needed by the Jobs you selected.\n"
708          "You will be allowed to select a Storage device later.\n"),
709          name_list->name[0]); 
710    }
711 }