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