]> git.sur5r.net Git - bacula/bacula/blobdiff - 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
index 2403a8bd0caf13699390bb8a6a43532340875dca..96697b3524b040c4ab2765086328c3dca0ad70ee 100644 (file)
@@ -1,7 +1,12 @@
 /*
  *
  *   Bacula Director -- User Agent Database restore Command
- *     Creates a bootstrap file for restoring files
+ *     Creates a bootstrap file for restoring files and
+ *     starts the restore job.
+ *
+ *     Tree handling routines split into ua_tree.c July MMIII.
+ *     BSR (bootstrap record) handling routines split into
+ *       bsr.c July MMIII
  *
  *     Kern Sibbald, July MMII
  *
@@ -9,7 +14,7 @@
  */
 
 /*
-   Copyright (C) 2002 Kern Sibbald and John Walker
+   Copyright (C) 2002-2003 Kern Sibbald and John Walker
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
 
 #include "bacula.h"
 #include "dird.h"
-#include "ua.h"
-#include <fnmatch.h>
-
 
 
 /* Imported functions */
 extern int runcmd(UAContext *ua, char *cmd);
 
 /* Imported variables */
-extern char *uar_list_jobs;
-extern char *uar_file;
-extern char *uar_sel_files;
-extern char *uar_del_temp;
-extern char *uar_del_temp1;
-extern char *uar_create_temp;
-extern char *uar_create_temp1;
-extern char *uar_last_full;
-extern char *uar_full;
-extern char *uar_inc;
-extern char *uar_list_temp;
-extern char *uar_sel_jobid_temp;
-extern char *uar_sel_all_temp1;
-
-/* Context for insert_tree_handler() */
-typedef struct s_tree_ctx {
-   TREE_ROOT *root;                  /* root */
-   TREE_NODE *node;                  /* current node */
-   TREE_NODE *avail_node;            /* unused node last insert */
-   int cnt;                          /* count for user feedback */
-   UAContext *ua;
-} TREE_CTX;
-
-typedef struct s_jobids {
-   btime_t JobTDate;
-   uint32_t ClientId;
+extern char *uar_list_jobs,    *uar_file,        *uar_sel_files;
+extern char *uar_del_temp,     *uar_del_temp1,   *uar_create_temp;
+extern char *uar_create_temp1, *uar_last_full,   *uar_full;
+extern char *uar_inc_dec,      *uar_list_temp,   *uar_sel_jobid_temp;
+extern char *uar_sel_all_temp1, *uar_sel_fileset, *uar_mediatype;
+
+
+/* Main structure for obtaining JobIds or Files to be restored */
+struct RESTORE_CTX {
+   utime_t JobTDate;
    uint32_t TotalFiles;
-   char JobIds[200];
-   CLIENT *client;
-} JobIds;
-
-
-/* FileIndex entry in bootstrap record */
-typedef struct s_rbsr_findex {
-   struct s_rbsr_findex *next;
-   int32_t findex;
-   int32_t findex2;
-} RBSR_FINDEX;
-
-/* Restore bootstrap record -- not the real one, but useful here */
-typedef struct s_rbsr {
-   struct s_rbsr *next;              /* next JobId */
-   uint32_t JobId;                   /* JobId this bsr */
-   uint32_t VolSessionId;                  
-   uint32_t VolSessionTime;
-   char *VolumeName;                 /* Volume name */
-   RBSR_FINDEX *fi;                  /* File indexes this JobId */
-} RBSR;
+   char ClientName[MAX_NAME_LENGTH];
+   char last_jobid[10];
+   char JobIds[200];                 /* User entered string of JobIds */
+   STORE  *store;
+   JOB *restore_job;
+   int restore_jobs;
+   uint32_t selected_files;
+   char *where;
+   RBSR *bsr;
+};
+
+struct NAME_LIST {
+   char **name;                      /* list of names */
+   int num_ids;                      /* ids stored */
+   int max_ids;                      /* size of array */
+   int num_del;                      /* number deleted */
+   int tot_ids;                      /* total to process */
+};
+
+#define MAX_ID_LIST_LEN 1000000
+
 
 /* Forward referenced functions */
-static RBSR *new_bsr();
-static void free_bsr(RBSR *bsr);
-static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd);
-static int  write_bsr_file(UAContext *ua, RBSR *bsr);
-static void print_bsr(UAContext *ua, RBSR *bsr);
-static int  complete_bsr(UAContext *ua, RBSR *bsr);
-static int insert_tree_handler(void *ctx, int num_fields, char **row);
-static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex);
 static int last_full_handler(void *ctx, int num_fields, char **row);
 static int jobid_handler(void *ctx, int num_fields, char **row);
 static int next_jobid_from_list(char **p, uint32_t *JobId);
-static int user_select_jobids(UAContext *ua, JobIds *ji);
-static void user_select_files(TREE_CTX *tree);
-
+static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx);
+static int fileset_handler(void *ctx, int num_fields, char **row);
+static void print_name_list(UAContext *ua, NAME_LIST *name_list);
+static int unique_name_list_handler(void *ctx, int num_fields, char **row);
+static void free_name_list(NAME_LIST *name_list);
+static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, RESTORE_CTX *rx);
+static int select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date);
+static void build_directory_tree(UAContext *ua, RESTORE_CTX *rx);
 
 /*
  *   Restore files
@@ -110,102 +93,73 @@ static void user_select_files(TREE_CTX *tree);
  */
 int restorecmd(UAContext *ua, char *cmd)
 {
-   POOLMEM *query;
-   TREE_CTX tree;
-   JobId_t JobId, last_JobId;
-   char *p;
-   RBSR *bsr;
-   char *nofname = "";
-   JobIds ji;
+   RESTORE_CTX rx;                   /* restore context */
    JOB *job = NULL;
-   JOB *restore_job = NULL;
-   int restore_jobs = 0;
+   int i;
+
+   memset(&rx, 0, sizeof(rx));
+
+   i = find_arg_with_value(ua, "where");
+   if (i >= 0) {
+      rx.where = ua->argv[i];
+   }
 
    if (!open_db(ua)) {
       return 0;
    }
 
-   memset(&tree, 0, sizeof(TREE_CTX));
-   memset(&ji, 0, sizeof(ji));
-
    /* Ensure there is at least one Restore Job */
    LockRes();
    while ( (job = (JOB *)GetNextRes(R_JOB, (RES *)job)) ) {
       if (job->JobType == JT_RESTORE) {
-        if (!restore_job) {
-           restore_job = job;
+        if (!rx.restore_job) {
+           rx.restore_job = job;
         }
-        restore_jobs++;
+        rx.restore_jobs++;
       }
    }
    UnlockRes();
-   if (!restore_jobs) {
+   if (!rx.restore_jobs) {
       bsendmsg(ua, _(
          "No Restore Job Resource found. You must create at least\n"
          "one before running this command.\n"));
       return 0;
    }
 
-
-   if (!user_select_jobids(ua, &ji)) {
-      return 0;
-   }
-
+   rx.bsr = new_bsr();
    /* 
-    * Build the directory tree 
-    */
-   tree.root = new_tree(ji.TotalFiles);
-   tree.root->fname = nofname;
-   tree.ua = ua;
-   query = get_pool_memory(PM_MESSAGE);
-   last_JobId = 0;
-   /*
-    * For display purposes, the same JobId, with different volumes may
-    * appear more than once, however, we only insert it once.
+    * Request user to select JobIds or files by various different methods
+    *  last 20 jobs, where File saved, most recent backup, ...
+    *  In the end, a list of files are pumped into
+    *  add_findex()
     */
-   for (p=ji.JobIds; next_jobid_from_list(&p, &JobId) > 0; ) {
-      if (JobId == last_JobId) {            
-        continue;                    /* eliminate duplicate JobIds */
-      }
-      last_JobId = JobId;
-      bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
-      Mmsg(&query, uar_sel_files, JobId);
-      if (!db_sql_query(ua->db, query, insert_tree_handler, (void *)&tree)) {
-         bsendmsg(ua, "%s", db_strerror(ua->db));
-      }
-   }
-   bsendmsg(ua, "\n");
-   free_pool_memory(query);
-
-   /* Let the user select which files to restore */
-   user_select_files(&tree);
-
-   /*
-    * Walk down through the tree finding all files marked to be 
-    *  extracted making a bootstrap file.
-    */
-   bsr = new_bsr();
-   for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
-      Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
-      if (node->extract) {
-         Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
-        add_findex(bsr, node->JobId, node->FileIndex);
+   switch (user_select_jobids_or_files(ua, &rx)) {
+   case 0:
+      free_bsr(rx.bsr);
+      return 0;                      /* error */
+   case 1:                           /* select by jobid */
+      build_directory_tree(ua, &rx);
+      break;
+   case 2:
+      break;
+   }
+
+   if (rx.bsr->JobId) {
+      if (!complete_bsr(ua, rx.bsr)) {  /* find Vol, SessId, SessTime from JobIds */
+         bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
+        free_bsr(rx.bsr);
+        return 0;
       }
-   }
-
-   free_tree(tree.root);             /* free the directory tree */
-
-   if (bsr->JobId) {
-      complete_bsr(ua, bsr);         /* find Vol, SessId, SessTime from JobIds */
-      print_bsr(ua, bsr);
-      write_bsr_file(ua, bsr);
+//    print_bsr(ua, rx.bsr);
+      write_bsr_file(ua, rx.bsr);
+      bsendmsg(ua, _("\n%u files selected to restore.\n\n"), rx.selected_files);
    } else {
       bsendmsg(ua, _("No files selected to restore.\n"));
    }
-   free_bsr(bsr);
+   free_bsr(rx.bsr);
 
-   if (restore_jobs == 1) {
-      job = restore_job;
+   if (rx.restore_jobs == 1) {
+      job = rx.restore_job;
    } else {
       job = select_restore_job_resource(ua);
    }
@@ -214,17 +168,32 @@ int restorecmd(UAContext *ua, char *cmd)
       return 0;
    }
 
-   if (ji.client) {
-      Mmsg(&ua->cmd, "run job=%s client=%s bootstrap=%s/restore.bsr",
-        job->hdr.name, ji.client->hdr.name, working_directory);
-   } else {
-      Mmsg(&ua->cmd, "run job=%s bootstrap=%s/restore.bsr",
-        job->hdr.name, working_directory);
+   /* If no client name specified yet, get it now */
+   if (!rx.ClientName[0]) {
+      CLIENT_DBR cr;
+      memset(&cr, 0, sizeof(cr));
+      if (!get_client_dbr(ua, &cr)) {
+        return 0;
+      }
+      bstrncpy(rx.ClientName, cr.Name, sizeof(rx.ClientName));
    }
 
-   Dmsg1(400, "Submitting: %s\n", ua->cmd);
+   /* Build run command */
+   if (rx.where) {
+      Mmsg(&ua->cmd, 
+          "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\""
+          " where=\"%s\"",
+          job->hdr.name, rx.ClientName, rx.store?rx.store->hdr.name:"",
+         working_directory, rx.where);
+   } else {
+      Mmsg(&ua->cmd, 
+          "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
+          job->hdr.name, rx.ClientName, rx.store?rx.store->hdr.name:"",
+         working_directory);
+   }
    
-   parse_command_args(ua);
+   Dmsg1(400, "Submitting: %s\n", ua->cmd);
+   parse_ua_args(ua);
    runcmd(ua, ua->cmd);
 
    bsendmsg(ua, _("Restore command done.\n"));
@@ -236,120 +205,161 @@ int restorecmd(UAContext *ua, char *cmd)
  *  select a list of JobIds from which he will subsequently
  *  select which files are to be restored.
  */
-static int user_select_jobids(UAContext *ua, JobIds *ji)
+static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
 {
    char *p;
+   char date[MAX_TIME_LENGTH];
    JobId_t JobId;
    JOB_DBR jr;
    POOLMEM *query;
-   int done = 0;
+   bool done = false;
+   int i;
    char *list[] = { 
       "List last 20 Jobs run",
       "List Jobs where a given File is saved",
       "Enter list of JobIds to select",
       "Enter SQL list command", 
       "Select the most recent backup for a client",
+      "Select backup for a client before a specified time",
+      "Enter a list of files to restore",
       "Cancel",
       NULL };
 
-   bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
+   char *kw[] = {
+      "jobid",     /* 0 */
+      "current",   /* 1 */
+      "before",    /* 2 */
+      NULL
+   };
+
+   switch (find_arg_keyword(ua, kw)) {
+   case 0:                           /* jobid */
+      i = find_arg_with_value(ua, _("jobid"));
+      if (i < 0) {
+        return 0;
+      }
+      bstrncpy(rx->JobIds, ua->argv[i], sizeof(rx->JobIds));
+      done = true;
+      break;
+   case 1:                           /* current */
+      bstrutime(date, sizeof(date), time(NULL));
+      if (!select_backups_before_date(ua, rx, date)) {
+        return 0;
+      }
+      done = true;
+      break;
+   case 2:                           /* before */
+      i = find_arg_with_value(ua, _("before"));
+      if (i < 0) {
+        return 0;
+      }
+      if (str_to_utime(ua->argv[i]) == 0) {
+         bsendmsg(ua, _("Improper date format: %s\n"), ua->argv[i]);
+        return 0;
+      }
+      bstrncpy(date, ua->argv[i], sizeof(date));
+      if (!select_backups_before_date(ua, rx, date)) {
+        return 0;
+      }
+      done = true;
+      break;
+   default:
+      break;
+   }
+       
+   if (!done) {
+      bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
                   "to be restored. You will be presented several methods\n"
                   "of specifying the JobIds. Then you will be allowed to\n"
                   "select which files from those JobIds are to be restored.\n\n"));
+   }
 
+   /* If choice not already made above, prompt */
    for ( ; !done; ) {
       start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
       for (int i=0; list[i]; i++) {
         add_prompt(ua, list[i]);
       }
-      done = 1;
-      switch (do_prompt(ua, "Select item: ", NULL)) {
+      done = true;
+      switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
       case -1:                       /* error */
         return 0;
       case 0:                        /* list last 20 Jobs run */
-        db_list_sql_query(ua->db, uar_list_jobs, prtit, ua, 1);
-        done = 0;
+        db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
+        done = false;
         break;
       case 1:                        /* list where a file is saved */
+        char *fname;
+        int len;
          if (!get_cmd(ua, _("Enter Filename: "))) {
            return 0;
         }
+        len = strlen(ua->cmd);
+        fname = (char *)malloc(len * 2 + 1);
+        db_escape_string(fname, ua->cmd, len);
         query = get_pool_memory(PM_MESSAGE);
-        Mmsg(&query, uar_file, ua->cmd);
-        db_list_sql_query(ua->db, query, prtit, ua, 1);
+        Mmsg(&query, uar_file, fname);
+        free(fname);
+        db_list_sql_query(ua->jcr, ua->db, query, prtit, ua, 1, HORZ_LIST);
         free_pool_memory(query);
-        done = 0;
+        done = false;
         break;
       case 2:                        /* enter a list of JobIds */
          if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
            return 0;
         }
-        bstrncpy(ji->JobIds, ua->cmd, sizeof(ji->JobIds));
+        bstrncpy(rx->JobIds, ua->cmd, sizeof(rx->JobIds));
         break;
       case 3:                        /* Enter an SQL list command */
          if (!get_cmd(ua, _("Enter SQL list command: "))) {
            return 0;
         }
-        db_list_sql_query(ua->db, ua->cmd, prtit, ua, 1);
-        done = 0;
+        db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
+        done = false;
         break;
       case 4:                        /* Select the most recent backups */
-        db_sql_query(ua->db, uar_del_temp, NULL, NULL);
-        db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
-        if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
-            bsendmsg(ua, "%s\n", db_strerror(ua->db));
-        }
-        if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
-            bsendmsg(ua, "%s\n", db_strerror(ua->db));
-        }
-        if (!(ji->client = get_client_resource(ua))) {
+        bstrutime(date, sizeof(date), time(NULL));
+        if (!select_backups_before_date(ua, rx, date)) {
            return 0;
         }
-        query = get_pool_memory(PM_MESSAGE);
-        Mmsg(&query, uar_last_full, ji->client->hdr.name);
-        /* Find JobId of full Backup of system */
-        if (!db_sql_query(ua->db, query, NULL, NULL)) {
-            bsendmsg(ua, "%s\n", db_strerror(ua->db));
-        }
-        /* Find all Volumes used by that JobId */
-        if (!db_sql_query(ua->db, uar_full, NULL,NULL)) {
-            bsendmsg(ua, "%s\n", db_strerror(ua->db));
-        }
-         /* Note, this is needed as I don't seem to get the callback
-         * from the call just above.
-         */
-        if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)ji)) {
-            bsendmsg(ua, "%s\n", db_strerror(ua->db));
-        }
-        /* Now find all Incremental Jobs */
-        Mmsg(&query, uar_inc, (uint32_t)ji->JobTDate, ji->ClientId);
-        if (!db_sql_query(ua->db, query, NULL, NULL)) {
-            bsendmsg(ua, "%s\n", db_strerror(ua->db));
-        }
-        free_pool_memory(query);
-        db_list_sql_query(ua->db, uar_list_temp, prtit, ua, 1);
-
-        if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)ji)) {
-            bsendmsg(ua, "%s\n", db_strerror(ua->db));
+        break;
+      case 5:                        /* select backup at specified time */
+         bsendmsg(ua, _("The restored files will the most current backup\n"
+                        "BEFORE the date you specify below.\n\n"));
+        for ( ;; ) {
+            if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
+              return 0;
+           }
+           if (str_to_utime(ua->cmd) != 0) {
+              break;
+           }
+            bsendmsg(ua, _("Improper date format.\n"));
+        }              
+        bstrncpy(date, ua->cmd, sizeof(date));
+        if (!select_backups_before_date(ua, rx, date)) {
+           return 0;
         }
-        db_sql_query(ua->db, uar_del_temp, NULL, NULL);
-        db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
         break;
-      case 5:
+      case 6:                        /* Enter files */
+         bsendmsg(ua, "Not yet implemented\n");
+        return 0;
+      
+      case 7:                        /* Cancel or quit */
         return 0;
       }
    }
 
-   if (*ji->JobIds == 0) {
+   if (*rx->JobIds == 0) {
       bsendmsg(ua, _("No Jobs selected.\n"));
       return 0;
    }
-   bsendmsg(ua, _("You have selected the following JobId: %s\n"), ji->JobIds);
+   bsendmsg(ua, _("You have selected the following JobId%s: %s\n"), 
+      strchr(rx->JobIds,',')?"s":"",rx->JobIds);
 
    memset(&jr, 0, sizeof(JOB_DBR));
 
-   ji->TotalFiles = 0;
-   for (p=ji->JobIds; ; ) {
+   rx->TotalFiles = 0;
+   for (p=rx->JobIds; ; ) {
       int stat = next_jobid_from_list(&p, &JobId);
       if (stat < 0) {
          bsendmsg(ua, _("Invalid JobId in list.\n"));
@@ -358,581 +368,364 @@ static int user_select_jobids(UAContext *ua, JobIds *ji)
       if (stat == 0) {
         break;
       }
+      if (jr.JobId == JobId) {
+        continue;                    /* duplicate of last JobId */
+      }
       jr.JobId = JobId;
-      if (!db_get_job_record(ua->db, &jr)) {
+      if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
          bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
         return 0;
       }
-      ji->TotalFiles += jr.JobFiles;
+      rx->TotalFiles += jr.JobFiles;
    }
    return 1;
 }
 
-static int next_jobid_from_list(char **p, uint32_t *JobId)
+static void build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
 {
-   char jobid[30];
-   int i;
-   char *q = *p;
+   TREE_CTX tree;
+   NAME_LIST name_list;
+   JobId_t JobId, last_JobId;
+   char *p;
+   POOLMEM *query;
+   char *nofname = "";
 
-   jobid[0] = 0;
-   for (i=0; i<(int)sizeof(jobid); i++) {
-      if (*q == ',' || *q == 0) {
-        q++;
-        break;
+   memset(&tree, 0, sizeof(TREE_CTX));
+   memset(&name_list, 0, sizeof(name_list));
+   /* 
+    * Build the directory tree containing JobIds user selected
+    */
+   tree.root = new_tree(rx->TotalFiles);
+   tree.root->fname = nofname;
+   tree.ua = ua;
+   query = get_pool_memory(PM_MESSAGE);
+   last_JobId = 0;
+   /*
+    * For display purposes, the same JobId, with different volumes may
+    * appear more than once, however, we only insert it once.
+    */
+   int items = 0;
+   for (p=rx->JobIds; next_jobid_from_list(&p, &JobId) > 0; ) {
+
+      if (JobId == last_JobId) {            
+        continue;                    /* eliminate duplicate JobIds */
+      }
+      last_JobId = JobId;
+      bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
+      items++;
+      /*
+       * Find files for this JobId and insert them in the tree
+       */
+      Mmsg(&query, uar_sel_files, JobId);
+      if (!db_sql_query(ua->db, query, insert_tree_handler, (void *)&tree)) {
+         bsendmsg(ua, "%s", db_strerror(ua->db));
+      }
+      /*
+       * Find the FileSets for this JobId and add to the name_list
+       */
+      Mmsg(&query, uar_mediatype, JobId);
+      if (!db_sql_query(ua->db, query, unique_name_list_handler, (void *)&name_list)) {
+         bsendmsg(ua, "%s", db_strerror(ua->db));
       }
-      jobid[i] = *q++;
-      jobid[i+1] = 0;
-   }
-   if (jobid[0] == 0 || !is_a_number(jobid)) {
-      return 0;
    }
-   *p = q;
-   *JobId = strtoul(jobid, NULL, 10);
-   return 1;
-}
+   bsendmsg(ua, "%d Job%s inserted into the tree and marked for extraction.\n", 
+      items, items==1?"":"s");
+   free_pool_memory(query);
 
-/*
- * Callback handler make list of JobIds
- */
-static int jobid_handler(void *ctx, int num_fields, char **row)
-{
-   JobIds *ji = (JobIds *)ctx;
+   /* Check MediaType and select storage that corresponds */
+   get_storage_from_mediatype(ua, &name_list, rx);
+   free_name_list(&name_list);
+
+   if (find_arg(ua, _("all")) < 0) {
+      /* Let the user select which files to restore */
+      user_select_files_from_tree(&tree);
+   }
 
-   if (strlen(ji->JobIds)+strlen(row[0])+2 < sizeof(ji->JobIds)) {
-      if (ji->JobIds[0] != 0) {
-         strcat(ji->JobIds, ",");
+   /*
+    * Walk down through the tree finding all files marked to be 
+    *  extracted making a bootstrap file.
+    */
+   for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
+      Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
+      if (node->extract) {
+         Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
+        add_findex(rx->bsr, node->JobId, node->FileIndex);
+        rx->selected_files++;
       }
-      strcat(ji->JobIds, row[0]);
    }
 
-   return 0;
+   free_tree(tree.root);             /* free the directory tree */
 }
 
 
 /*
- * Callback handler to pickup last Full backup JobId and ClientId
+ * This routine is used to get the current backup or a backup
+ *   before the specified date.
  */
-static int last_full_handler(void *ctx, int num_fields, char **row)
+static int select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
 {
-   JobIds *ji = (JobIds *)ctx;
-
-   ji->JobTDate = atoi(row[1]);
-   ji->ClientId = atoi(row[2]);
-
-   return 0;
-}
-
-
-
-
-/* Forward referenced commands */
-
-static int markcmd(UAContext *ua, TREE_CTX *tree);
-static int countcmd(UAContext *ua, TREE_CTX *tree);
-static int findcmd(UAContext *ua, TREE_CTX *tree);
-static int lscmd(UAContext *ua, TREE_CTX *tree);
-static int helpcmd(UAContext *ua, TREE_CTX *tree);
-static int cdcmd(UAContext *ua, TREE_CTX *tree);
-static int pwdcmd(UAContext *ua, TREE_CTX *tree);
-static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
-static int quitcmd(UAContext *ua, TREE_CTX *tree);
-
-
-struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; }; 
-static struct cmdstruct commands[] = {
- { N_("mark"),       markcmd,      _("mark file for extraction")},
- { N_("unmark"),     unmarkcmd,    _("unmark file for extraction")},
- { N_("cd"),         cdcmd,        _("change current directory")},
- { N_("pwd"),        pwdcmd,       _("print current working directory")},
- { N_("ls"),         lscmd,        _("list current directory")},    
- { N_("dir"),        lscmd,        _("list current directory")},    
- { N_("count"),      countcmd,     _("count marked files")},
- { N_("find"),       findcmd,      _("find files")},
- { N_("done"),       quitcmd,      _("leave file selection mode")},
- { N_("exit"),       quitcmd,      _("exit = done")},
- { N_("help"),       helpcmd,      _("print help")},
- { N_("?"),          helpcmd,      _("print help")},    
-            };
-#define comsize (sizeof(commands)/sizeof(struct cmdstruct))
-
+   int stat = 0;
+   POOLMEM *query;
+   FILESET_DBR fsr;
+   CLIENT_DBR cr;
+   char fileset_name[MAX_NAME_LENGTH];
+   char ed1[50];
 
-/*
- * Enter a prompt mode where the user can select/deselect
- *  files to be restored. This is sort of like a mini-shell
- *  that allows "cd", "pwd", "add", "rm", ...
- */
-static void user_select_files(TREE_CTX *tree)
-{
-   char cwd[2000];
+   query = get_pool_memory(PM_MESSAGE);
 
-   bsendmsg(tree->ua, _( 
-      "You are now entering file selection mode where you add and\n"
-      "remove files to be restored. All files are initially added.\n"
-      "Enter done to leave this mode.\n\n"));
+   /* Create temp tables */
+   db_sql_query(ua->db, uar_del_temp, NULL, NULL);
+   db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
+   if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
+      bsendmsg(ua, "%s\n", db_strerror(ua->db));
+   }
+   if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
+      bsendmsg(ua, "%s\n", db_strerror(ua->db));
+   }
    /*
-    * Enter interactive command handler allowing selection
-    *  of individual files.
+    * Select Client from the Catalog
     */
-   tree->node = (TREE_NODE *)tree->root;
-   tree_getpath(tree->node, cwd, sizeof(cwd));
-   bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
-   for ( ;; ) {       
-      int found, len, stat, i;
-      if (!get_cmd(tree->ua, "$ ")) {
-        break;
-      }
-      parse_command_args(tree->ua);
-      if (tree->ua->argc == 0) {
-        return;
-      }
+   memset(&cr, 0, sizeof(cr));
+   if (!get_client_dbr(ua, &cr)) {
+      goto bail_out;
+   }
+   bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
 
-      len = strlen(tree->ua->argk[0]);
-      found = 0;
-      for (i=0; i<(int)comsize; i++)      /* search for command */
-        if (strncasecmp(tree->ua->argk[0],  _(commands[i].key), len) == 0) {
-           stat = (*commands[i].func)(tree->ua, tree);   /* go execute command */
-           found = 1;
-           break;
-        }
-      if (!found) {
-         bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
-      }
-      if (!stat) {
-        break;
-      }
+   /*
+    * Select FileSet 
+    */
+   Mmsg(&query, uar_sel_fileset, cr.ClientId, cr.ClientId);
+   start_prompt(ua, _("The defined FileSet resources are:\n"));
+   if (!db_sql_query(ua->db, query, fileset_handler, (void *)ua)) {
+      bsendmsg(ua, "%s\n", db_strerror(ua->db));
+   }
+   if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"), 
+                fileset_name, sizeof(fileset_name)) < 0) {
+      goto bail_out;
+   }
+   fsr.FileSetId = atoi(fileset_name); /* Id is first part of name */
+   if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
+      bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
+      bsendmsg(ua, _("This probably means you modified the FileSet.\n"
+                     "Continuing anyway.\n"));
    }
-}
 
-/*
- * Create new FileIndex entry for BSR 
- */
-static RBSR_FINDEX *new_findex() 
-{
-   RBSR_FINDEX *fi = (RBSR_FINDEX *)malloc(sizeof(RBSR_FINDEX));
-   memset(fi, 0, sizeof(RBSR_FINDEX));
-   return fi;
-}
 
-/* Free all BSR FileIndex entries */
-static void free_findex(RBSR_FINDEX *fi)
-{
-   if (fi) {
-      free_findex(fi->next);
-      free(fi);
+   /* Find JobId of last Full backup for this client, fileset */
+   Mmsg(&query, uar_last_full, cr.ClientId, cr.ClientId, date, fsr.FileSetId);
+   if (!db_sql_query(ua->db, query, NULL, NULL)) {
+      bsendmsg(ua, "%s\n", db_strerror(ua->db));
+      goto bail_out;
    }
-}
 
-static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd) 
-{
-   if (fi) {
-      if (fi->findex == fi->findex2) {
-         fprintf(fd, "FileIndex=%d\n", fi->findex);
-      } else {
-         fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
-      }
-      write_findex(ua, fi->next, fd);
+   /* Find all Volumes used by that JobId */
+   if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
+      bsendmsg(ua, "%s\n", db_strerror(ua->db));
+      goto bail_out;
+   }
+   /* Note, this is needed as I don't seem to get the callback
+    * from the call just above.
+    */
+   rx->JobTDate = 0;
+   if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
+      bsendmsg(ua, "%s\n", db_strerror(ua->db));
+   }
+   if (rx->JobTDate == 0) {
+      bsendmsg(ua, _("No Full backup before %s found.\n"), date);
+      goto bail_out;
    }
-}
-
 
-static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
-{
-   if (fi) {
-      if (fi->findex == fi->findex2) {
-         bsendmsg(ua, "FileIndex=%d\n", fi->findex);
-      } else {
-         bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
-      }
-      print_findex(ua, fi->next);
+   /* Now find all Incremental/Decremental Jobs after Full save */
+   Mmsg(&query, uar_inc_dec, edit_uint64(rx->JobTDate, ed1), date,
+       cr.ClientId, fsr.FileSetId);
+   if (!db_sql_query(ua->db, query, NULL, NULL)) {
+      bsendmsg(ua, "%s\n", db_strerror(ua->db));
    }
-}
 
-/* Create a new bootstrap record */
-static RBSR *new_bsr()
-{
-   RBSR *bsr = (RBSR *)malloc(sizeof(RBSR));
-   memset(bsr, 0, sizeof(RBSR));
-   return bsr;
-}
+   /* Get the JobIds from that list */
+   rx->JobIds[0] = 0;
+   rx->last_jobid[0] = 0;
+   if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
+      bsendmsg(ua, "%s\n", db_strerror(ua->db));
+   }
 
-/* Free the entire BSR */
-static void free_bsr(RBSR *bsr)
-{
-   if (bsr) {
-      free_findex(bsr->fi);
-      free_bsr(bsr->next);
-      if (bsr->VolumeName) {
-        free(bsr->VolumeName);
-      }
-      free(bsr);
+   if (rx->JobIds[0] != 0) {
+      /* Display a list of Jobs selected for this restore */
+      db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
+   } else {
+      bsendmsg(ua, _("No jobs found.\n")); 
    }
+
+   stat = 1;
+bail_out:
+   free_pool_memory(query);
+   db_sql_query(ua->db, uar_del_temp, NULL, NULL);
+   db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
+   return stat;
 }
 
-/*
- * Complete the BSR by filling in the VolumeName and
- *  VolSessionId and VolSessionTime using the JobId
- */
-static int complete_bsr(UAContext *ua, RBSR *bsr)
+/* Return next JobId from comma separated list */
+static int next_jobid_from_list(char **p, uint32_t *JobId)
 {
-   JOB_DBR jr;
-   char VolumeNames[1000];           /* ****FIXME**** */
+   char jobid[30];
+   char *q = *p;
 
-   if (bsr) {
-      memset(&jr, 0, sizeof(jr));
-      jr.JobId = bsr->JobId;
-      if (!db_get_job_record(ua->db, &jr)) {
-         bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
-        return 0;
-      }
-      bsr->VolSessionId = jr.VolSessionId;
-      bsr->VolSessionTime = jr.VolSessionTime;
-      if (!db_get_job_volume_names(ua->db, bsr->JobId, VolumeNames)) {
-         bsendmsg(ua, _("Unable to get Job Volumes. ERR=%s\n"), db_strerror(ua->db));
-        return 0;
+   jobid[0] = 0;
+   for (int i=0; i<(int)sizeof(jobid); i++) {
+      if (*q == ',' || *q == 0) {
+        q++;
+        break;
       }
-      bsr->VolumeName = bstrdup(VolumeNames);
-      return complete_bsr(ua, bsr->next);
+      jobid[i] = *q++;
+      jobid[i+1] = 0;
+   }
+   if (jobid[0] == 0 || !is_a_number(jobid)) {
+      return 0;
    }
+   *p = q;
+   *JobId = strtoul(jobid, NULL, 10);
    return 1;
 }
 
 /*
- * Write the bootstrap record to file
+ * Callback handler make list of JobIds
  */
-static int write_bsr_file(UAContext *ua, RBSR *bsr)
+static int jobid_handler(void *ctx, int num_fields, char **row)
 {
-   FILE *fd;
-   POOLMEM *fname = get_pool_memory(PM_MESSAGE);
-   int stat;
-
-   Mmsg(&fname, "%s/restore.bsr", working_directory);
-   fd = fopen(fname, "w+");
-   if (!fd) {
-      bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"), 
-        fname, strerror(errno));
-      free_pool_memory(fname);
-      return 0;
-   }
-   write_bsr(ua, bsr, fd);
-   stat = !ferror(fd);
-   fclose(fd);
-   bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
-   free_pool_memory(fname);
-   return stat;
-}
+   RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
 
-static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
-{
-   if (bsr) {
-      if (bsr->VolumeName) {
-         fprintf(fd, "VolumeName=%s\n", bsr->VolumeName);
-      }
-      fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
-      fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
-      write_findex(ua, bsr->fi, fd);
-      write_bsr(ua, bsr->next, fd);
+   if (strcmp(rx->last_jobid, row[0]) == 0) {          
+      return 0;                      /* duplicate id */
    }
-}
-
-static void print_bsr(UAContext *ua, RBSR *bsr)
-{
-   if (bsr) {
-      if (bsr->VolumeName) {
-         bsendmsg(ua, "VolumeName=%s\n", bsr->VolumeName);
+   bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
+   /* Concatenate a JobId if it does not exceed array size */
+   if (strlen(rx->JobIds)+strlen(row[0])+2 < sizeof(rx->JobIds)) {
+      if (rx->JobIds[0] != 0) {
+         strcat(rx->JobIds, ",");
       }
-      bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
-      bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
-      print_findex(ua, bsr->fi);
-      print_bsr(ua, bsr->next);
+      strcat(rx->JobIds, row[0]);
    }
+   return 0;
 }
 
 
 /*
- * Add a FileIndex to the list of BootStrap records.
- *  Here we are only dealing with JobId's and the FileIndexes
- *  associated with those JobIds.
+ * Callback handler to pickup last Full backup JobTDate
  */
-static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
+static int last_full_handler(void *ctx, int num_fields, char **row)
 {
-   RBSR *nbsr;
-   RBSR_FINDEX *fi, *lfi;
+   RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
 
-   if (findex == 0) {
-      return;                        /* probably a dummy directory */
-   }
-   
-   if (!bsr->fi) {                   /* if no FI add one */
-      /* This is the first FileIndex item in the chain */
-      bsr->fi = new_findex();
-      bsr->JobId = JobId;
-      bsr->fi->findex = findex;
-      bsr->fi->findex2 = findex;
-      return;
-   }
-   /* Walk down list of bsrs until we find the JobId */
-   if (bsr->JobId != JobId) {
-      for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
-        if (nbsr->JobId == JobId) {
-           bsr = nbsr;
-           break;
-        }
-      }
+   rx->JobTDate = strtoll(row[1], NULL, 10);
 
-      if (!nbsr) {                   /* Must add new JobId */
-        /* Add new JobId at end of chain */
-        for (nbsr=bsr; nbsr->next; nbsr=nbsr->next) 
-           {  }
-        nbsr->next = new_bsr();
-        nbsr->next->JobId = JobId;
-        nbsr->next->fi = new_findex();
-        nbsr->next->fi->findex = findex;
-        nbsr->next->fi->findex2 = findex;
-        return;
-      }
-   }
-
-   /* 
-    * At this point, bsr points to bsr containing JobId,
-    *  and we are sure that there is at least one fi record.
-    */
-   lfi = fi = bsr->fi;
-   /* Check if this findex is smaller than first item */
-   if (findex < fi->findex) {
-      if ((findex+1) == fi->findex) {
-        fi->findex = findex;         /* extend down */
-        return;
-      }
-      fi = new_findex();             /* yes, insert before first item */
-      fi->findex = findex;
-      fi->findex2 = findex;
-      fi->next = lfi;
-      bsr->fi = fi;
-      return;
-   }
-   /* Walk down fi chain and find where to insert insert new FileIndex */
-   for ( ; fi; fi=fi->next) {
-      if (findex == (fi->findex2 + 1)) {  /* extend up */
-        RBSR_FINDEX *nfi;     
-        fi->findex2 = findex;
-        if (fi->next && ((findex+1) == fi->next->findex)) { 
-           nfi = fi->next;
-           fi->findex2 = nfi->findex2;
-           fi->next = nfi->next;
-           free(nfi);
-        }
-        return;
-      }
-      if (findex < fi->findex) {      /* add before */
-        if ((findex+1) == fi->findex) {
-           fi->findex = findex;
-           return;
-        }
-        break;
-      }
-      lfi = fi;
-   }
-   /* Add to last place found */
-   fi = new_findex();
-   fi->findex = findex;
-   fi->findex2 = findex;
-   fi->next = lfi->next;
-   lfi->next = fi;
-   return;
+   return 0;
 }
 
 /*
- * This callback routine is responsible for inserting the
- *  items it gets into the directory tree. For each JobId selected
- *  this routine is called once for each file. We do not allow
- *  duplicate filenames, but instead keep the info from the most
- *  recent file entered (i.e. the JobIds are assumed to be sorted)
+ * Callback handler build fileset prompt list
  */
-static int insert_tree_handler(void *ctx, int num_fields, char **row)
+static int fileset_handler(void *ctx, int num_fields, char **row)
 {
-   TREE_CTX *tree = (TREE_CTX *)ctx;
-   char fname[2000];
-   TREE_NODE *node, *new_node;
-   int type;
-
-   strip_trailing_junk(row[1]);
-   if (*row[1] == 0) {
-      type = TN_DIR;
-   } else {
-      type = TN_FILE;
-   }
-   sprintf(fname, "%s%s", row[0], row[1]);
-   if (tree->avail_node) {
-      node = tree->avail_node;
-   } else {
-      node = new_tree_node(tree->root, type);
-      tree->avail_node = node;
-   }
-   Dmsg2(400, "FI=%d fname=%s\n", node->FileIndex, fname);
-   new_node = insert_tree_node(fname, node, tree->root, NULL);
-   /* Note, if node already exists, save new one for next time */
-   if (new_node != node) {
-      tree->avail_node = node;
-   } else {
-      tree->avail_node = NULL;
-   }
-   new_node->FileIndex = atoi(row[2]);
-   new_node->JobId = atoi(row[3]);
-   new_node->type = type;
-   new_node->extract = 1;            /* extract all by default */
-   tree->cnt++;
+   char prompt[MAX_NAME_LENGTH+200];
+
+   snprintf(prompt, sizeof(prompt), "%s  %s  %s", row[0], row[1], row[2]);
+   add_prompt((UAContext *)ctx, prompt);
    return 0;
 }
 
-
 /*
- * Set extract to value passed. We recursively walk
- *  down the tree setting all children if the 
- *  node is a directory.
+ * Called here with each name to be added to the list. The name is
+ *   added to the list if it is not already in the list.
+ *
+ * Used to make unique list of FileSets and MediaTypes
  */
-static void set_extract(TREE_NODE *node, int value)
+static int unique_name_list_handler(void *ctx, int num_fields, char **row)
 {
-   TREE_NODE *n;
-
-   node->extract = value;
-   if (node->type != TN_FILE) {
-      for (n=node->child; n; n=n->sibling) {
-        set_extract(n, value);
-      }
-   }
-}
+   NAME_LIST *name = (NAME_LIST *)ctx;
 
-static int markcmd(UAContext *ua, TREE_CTX *tree)
-{
-   TREE_NODE *node;
-
-   if (ua->argc < 2)
-      return 1;
-   if (!tree->node->child) {    
+   if (name->num_ids == MAX_ID_LIST_LEN) {  
       return 1;
    }
-   for (node = tree->node->child; node; node=node->sibling) {
-      if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
-        set_extract(node, 1);
+   if (name->num_ids == name->max_ids) {
+      if (name->max_ids == 0) {
+        name->max_ids = 1000;
+        name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
+      } else {
+        name->max_ids = (name->max_ids * 3) / 2;
+        name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
       }
    }
-   return 1;
-}
-
-static int countcmd(UAContext *ua, TREE_CTX *tree)
-{
-   int total, extract;
-
-   total = extract = 0;
-   for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
-      if (node->type != TN_NEWDIR) {
-        total++;
-        if (node->extract) {
-           extract++;
-        }
+   for (int i=0; i<name->num_ids; i++) {
+      if (strcmp(name->name[i], row[0]) == 0) {
+        return 0;                    /* already in list, return */
       }
    }
-   bsendmsg(ua, "%d total files. %d marked for extraction.\n", total, extract);
-   return 1;
+   /* Add new name to list */
+   name->name[name->num_ids++] = bstrdup(row[0]);
+   return 0;
 }
 
-static int findcmd(UAContext *ua, TREE_CTX *tree)
-{
-   char cwd[2000];
 
-   if (ua->argc == 1) {
-      bsendmsg(ua, _("No file specification given.\n"));
-      return 0;
-   }
-   
-   for (int i=1; i < ua->argc; i++) {
-      for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
-        if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
-           tree_getpath(node, cwd, sizeof(cwd));
-            bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
-        }
-      }
+/*
+ * Print names in the list
+ */
+static void print_name_list(UAContext *ua, NAME_LIST *name_list)
+{ 
+   for (int i=0; i < name_list->num_ids; i++) {
+      bsendmsg(ua, "%s\n", name_list->name[i]);
    }
-   return 1;
 }
 
 
-
-static int lscmd(UAContext *ua, TREE_CTX *tree)
-{
-   TREE_NODE *node;
-
-   if (!tree->node->child) {    
-      return 1;
+/*
+ * Free names in the list
+ */
+static void free_name_list(NAME_LIST *name_list)
+{ 
+   for (int i=0; i < name_list->num_ids; i++) {
+      free(name_list->name[i]);
    }
-   for (node = tree->node->child; node; node=node->sibling) {
-      if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
-         bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
-            (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
-      }
+   if (name_list->name) {
+      free(name_list->name);
    }
-   return 1;
+   name_list->max_ids = 0;
+   name_list->num_ids = 0;
 }
 
-static int helpcmd(UAContext *ua, TREE_CTX *tree) 
+static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, RESTORE_CTX *rx)
 {
-   unsigned int i;
-
-/* usage(); */
-   bsendmsg(ua, _("  Command    Description\n  =======    ===========\n"));
-   for (i=0; i<comsize; i++) {
-      bsendmsg(ua, _("  %-10s %s\n"), _(commands[i].key), _(commands[i].help));
+   char name[MAX_NAME_LENGTH];
+   STORE *store = NULL;
+
+   if (name_list->num_ids > 1) {
+      bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
+         "Restore is not possible. The MediaTypes used are:\n"));
+      print_name_list(ua, name_list);
+      rx->store = select_storage_resource(ua);
+      return;
    }
-   bsendmsg(ua, "\n");
-   return 1;
-}
 
-static int cdcmd(UAContext *ua, TREE_CTX *tree) 
-{
-   TREE_NODE *node;
-   char cwd[2000];
-
-   if (ua->argc != 2) {
-      return 1;
-   }
-   node = tree_cwd(ua->argk[1], tree->root, tree->node);
-   if (!node) {
-      bsendmsg(ua, _("Invalid path given.\n"));
-   } else {
-      tree->node = node;
+   if (name_list->num_ids == 0) {
+      bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
+      rx->store = select_storage_resource(ua);
+      return;
    }
-   tree_getpath(tree->node, cwd, sizeof(cwd));
-   bsendmsg(ua, _("cwd is: %s\n"), cwd);
-   return 1;
-}
 
-static int pwdcmd(UAContext *ua, TREE_CTX *tree) 
-{
-   char cwd[2000];
-   tree_getpath(tree->node, cwd, sizeof(cwd));
-   bsendmsg(ua, _("cwd is: %s\n"), cwd);
-   return 1;
-}
-
-
-static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
-{
-   TREE_NODE *node;
-
-   if (ua->argc < 2)
-      return 1;
-   if (!tree->node->child) {    
-      return 1;
-   }
-   for (node = tree->node->child; node; node=node->sibling) {
-      if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
-        set_extract(node, 0);
+   start_prompt(ua, _("The defined Storage resources are:\n"));
+   LockRes();
+   while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
+      if (strcmp(store->media_type, name_list->name[0]) == 0) {
+        add_prompt(ua, store->hdr.name);
       }
    }
-   return 1;
-}
-
-static int quitcmd(UAContext *ua, TREE_CTX *tree) 
-{
-   return 0;
+   UnlockRes();
+   do_prompt(ua, _("Storage"),  _("Select Storage resource"), name, sizeof(name));
+   rx->store = (STORE *)GetResWithName(R_STORAGE, name);
+   if (!rx->store) {
+      bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
+         "MediaType %s, needed by the Jobs you selected.\n"
+         "You will be allowed to select a Storage device later.\n"),
+        name_list->name[0]); 
+   }
 }