]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/dird/ua_restore.c
Fix bug #2212 where restore jobid=nn file=xxx restores the files twice
[bacula/bacula] / bacula / src / dird / ua_restore.c
index e39db95151108a21f074becbbdfe37362a0e7ecf..85979b8d62a76da3b431400f8ea1a9507f3c3a55 100644 (file)
@@ -1,32 +1,22 @@
 /*
-   Bacula® - The Network Backup Solution
-
-   Copyright (C) 2002-2007 Free Software Foundation Europe e.V.
-
-   The main author of Bacula is Kern Sibbald, with contributions from
-   many others, a complete list can be found in the file AUTHORS.
-   This program is Free Software; you can redistribute it and/or
-   modify it under the terms of version two of the GNU General Public
-   License as published by the Free Software Foundation plus additions
-   that are listed in the file LICENSE.
-
-   This program is distributed in the hope that it will be useful, but
-   WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-   General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-   02110-1301, USA.
-
-   Bacula® is a registered trademark of John Walker.
-   The licensor of Bacula is the Free Software Foundation Europe
-   (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
-   Switzerland, email:ftf@fsfeurope.org.
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2017 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
 */
 /*
- *
  *   Bacula Director -- User Agent Database restore Command
  *      Creates a bootstrap file for restoring files and
  *      starts the restore job.
@@ -36,8 +26,6 @@
  *        bsr.c July MMIII
  *
  *     Kern Sibbald, July MMII
- *
- *   Version $Id$
  */
 
 
@@ -48,7 +36,6 @@
 extern void print_bsr(UAContext *ua, RBSR *bsr);
 
 
-
 /* Forward referenced functions */
 static int last_full_handler(void *ctx, int num_fields, char **row);
 static int jobid_handler(void *ctx, int num_fields, char **row);
@@ -57,8 +44,7 @@ static int fileset_handler(void *ctx, int num_fields, char **row);
 static void free_name_list(NAME_LIST *name_list);
 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date);
 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx);
-static void free_rx(RESTORE_CTX *rx);
-static void split_path_and_filename(RESTORE_CTX *rx, char *fname);
+static void split_path_and_filename(UAContext *ua, RESTORE_CTX *rx, char *fname);
 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row);
 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
                                          char *date);
@@ -67,9 +53,36 @@ static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *di
 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir);
 static int get_client_name(UAContext *ua, RESTORE_CTX *rx);
 static int get_restore_client_name(UAContext *ua, RESTORE_CTX &rx);
-static int get_date(UAContext *ua, char *date, int date_len);
+static bool get_date(UAContext *ua, char *date, int date_len);
 static int restore_count_handler(void *ctx, int num_fields, char **row);
-static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table);
+static void get_and_display_basejobs(UAContext *ua, RESTORE_CTX *rx);
+
+void new_rx(RESTORE_CTX *rx)
+{
+   RBSR *bsr = NULL;
+   memset(rx, 0, sizeof(*rx));
+   rx->path = get_pool_memory(PM_FNAME);
+   rx->path[0] = 0;
+
+   rx->fname = get_pool_memory(PM_FNAME);
+   rx->fname[0] = 0;
+
+   rx->JobIds = get_pool_memory(PM_FNAME);
+   rx->JobIds[0] = 0;
+
+   rx->component_fname = get_pool_memory(PM_FNAME);
+   rx->component_fname[0] = 0;
+
+   rx->BaseJobIds = get_pool_memory(PM_FNAME);
+   rx->BaseJobIds[0] = 0;
+
+   rx->query = get_pool_memory(PM_FNAME);
+   rx->query[0] = 0;
+
+   rx->bsr_list = New(rblist(bsr, &bsr->link));
+   rx->hardlinks_in_mem = true;
+}
+
 
 /*
  *   Restore files
@@ -78,6 +91,7 @@ static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *
 int restore_cmd(UAContext *ua, const char *cmd)
 {
    RESTORE_CTX rx;                    /* restore context */
+   POOL_MEM buf;
    JOB *job;
    int i;
    JCR *jcr = ua->jcr;
@@ -86,41 +100,60 @@ int restore_cmd(UAContext *ua, const char *cmd)
    char *strip_prefix, *add_prefix, *add_suffix, *regexp;
    strip_prefix = add_prefix = add_suffix = regexp = NULL;
 
-   memset(&rx, 0, sizeof(rx));
-   rx.path = get_pool_memory(PM_FNAME);
-   rx.fname = get_pool_memory(PM_FNAME);
-   rx.JobIds = get_pool_memory(PM_FNAME);
-   rx.query = get_pool_memory(PM_FNAME);
-   rx.bsr = new_bsr();
-
-   i = find_arg_with_value(ua, "where");
-   if (i >= 0) {
-      rx.where = ua->argv[i];
+   new_rx(&rx);                 /* Initialize RESTORE_CTX */
+   
+   if (!open_new_client_db(ua)) {
+      goto bail_out;
    }
 
-   i = find_arg_with_value(ua, "strip_prefix");
-   if (i >= 0) {
-      strip_prefix = ua->argv[i];
-   }
+   for (i = 0; i < ua->argc ; i++) {
+      if (strcasecmp(ua->argk[i], "fdcalled") == 0) {
+         rx.fdcalled = true;
 
-   i = find_arg_with_value(ua, "add_prefix");
-   if (i >= 0) {
-      add_prefix = ua->argv[i];
-   }
+      } else if (strcasecmp(ua->argk[i], "noautoparent") == 0) {
+         rx.no_auto_parent = true;
+      }
+      if (!ua->argv[i]) {
+         continue;           /* skip if no value given */
+      }
+      if (strcasecmp(ua->argk[i], "comment") == 0) {
+         rx.comment = ua->argv[i];
+         if (!is_comment_legal(ua, rx.comment)) {
+            goto bail_out;
+         }
 
-   i = find_arg_with_value(ua, "add_suffix");
-   if (i >= 0) {
-      add_suffix = ua->argv[i];
-   }
+      } else if (strcasecmp(ua->argk[i], "where") == 0) {
+         rx.where = ua->argv[i];
 
-   i = find_arg_with_value(ua, "regexwhere");
-   if (i >= 0) {
-      rx.RegexWhere = ua->argv[i];
+      } else if (strcasecmp(ua->argk[i], "when") == 0) {
+         rx.when = ua->argv[i];
+
+      } else if (strcasecmp(ua->argk[i], "replace") == 0) {
+         rx.replace = ua->argv[i];
+
+      } else if (strcasecmp(ua->argk[i], "strip_prefix") == 0) {
+         strip_prefix = ua->argv[i];
+
+      } else if (strcasecmp(ua->argk[i], "add_prefix") == 0) {
+         add_prefix = ua->argv[i];
+
+      } else if (strcasecmp(ua->argk[i], "add_suffix") == 0) {
+         add_suffix = ua->argv[i];
+
+      } else if (strcasecmp(ua->argk[i], "regexwhere") == 0) {
+         rx.RegexWhere = ua->argv[i];
+
+      } else if (strcasecmp(ua->argk[i], "optimizespeed") == 0) {
+         if (strcasecmp(ua->argv[i], "0") || strcasecmp(ua->argv[i], "no") ||
+             strcasecmp(ua->argv[i], "false")) {
+            rx.hardlinks_in_mem = false;
+         }
+     }
    }
 
    if (strip_prefix || add_suffix || add_prefix) {
       int len = bregexp_get_build_where_size(strip_prefix, add_prefix, add_suffix);
-      regexp = (char *) bmalloc (len * sizeof(char));
+      regexp = (char *)bmalloc(len * sizeof(char));
 
       bregexp_build_where(regexp, len, strip_prefix, add_prefix, add_suffix);
       rx.RegexWhere = regexp;
@@ -142,10 +175,6 @@ int restore_cmd(UAContext *ua, const char *cmd)
       }
    }
 
-   if (!open_client_db(ua)) {
-      goto bail_out;
-   }
-
    /* Ensure there is at least one Restore Job */
    LockRes();
    foreach_res(job, R_JOB) {
@@ -174,6 +203,7 @@ int restore_cmd(UAContext *ua, const char *cmd)
    case 0:                            /* error */
       goto bail_out;
    case 1:                            /* selected by jobid */
+      get_and_display_basejobs(ua, &rx);
       if (!build_directory_tree(ua, &rx)) {
          ua->send_msg(_("Restore not done.\n"));
          goto bail_out;
@@ -183,26 +213,24 @@ int restore_cmd(UAContext *ua, const char *cmd)
       break;
    }
 
-   if (rx.bsr->JobId) {
-      uint32_t selected_files;
+   if (rx.bsr_list->size() > 0) {
       char ed1[50];
-      if (!complete_bsr(ua, rx.bsr)) {   /* find Vol, SessId, SessTime from JobIds */
+      if (!complete_bsr(ua, rx.bsr_list)) {   /* find Vol, SessId, SessTime from JobIds */
          ua->error_msg(_("Unable to construct a valid BSR. Cannot continue.\n"));
          goto bail_out;
       }
-      if (!(selected_files = write_bsr_file(ua, rx))) {
+      if (!(rx.selected_files = write_bsr_file(ua, rx))) {
          ua->warning_msg(_("No files selected to be restored.\n"));
          goto bail_out;
       }
-      /* If no count of files, use bsr generated value (often wrong) */
-      if (rx.selected_files == 0) {
-         rx.selected_files = selected_files;
-      }
+
+      ua->send_msg(_("Bootstrap records written to %s\n"), ua->jcr->RestoreBootstrap);
+      display_bsr_info(ua, rx);          /* display vols needed, etc */
+
       if (rx.selected_files==1) {
          ua->info_msg(_("\n1 file selected to be restored.\n\n"));
-      }
-      else {
-         ua->info_msg(_("\n%s files selected to be restored.\n\n"), 
+      } else {
+         ua->info_msg(_("\n%s files selected to be restored.\n\n"),
             edit_uint64_with_commas(rx.selected_files, ed1));
       }
    } else {
@@ -213,14 +241,14 @@ int restore_cmd(UAContext *ua, const char *cmd)
    if (rx.restore_jobs == 1) {
       job = rx.restore_job;
    } else {
-      job = select_restore_job_resource(ua);
+      job = get_restore_job(ua);
    }
    if (!job) {
       goto bail_out;
    }
 
    get_client_name(ua, &rx);
-   if (!rx.ClientName) {
+   if (!rx.ClientName[0]) {
       ua->error_msg(_("No Client resource found!\n"));
       goto bail_out;
    }
@@ -228,37 +256,50 @@ int restore_cmd(UAContext *ua, const char *cmd)
 
    escaped_bsr_name = escape_filename(jcr->RestoreBootstrap);
 
+   Mmsg(ua->cmd,
+        "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
+        " bootstrap=\"%s\" files=%u catalog=\"%s\"",
+        job->name(), rx.ClientName, rx.RestoreClientName,
+        rx.store?rx.store->name():"",
+        escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
+        rx.selected_files, ua->catalog->name());
+
    /* Build run command */
+   pm_strcpy(buf, "");
+   if (rx.RestoreMediaType[0]) {
+      Mmsg(buf, " mediatype=\"%s\"", rx.RestoreMediaType);
+      pm_strcat(ua->cmd, buf);
+      pm_strcpy(buf, "");
+   }
    if (rx.RegexWhere) {
       escaped_where_name = escape_filename(rx.RegexWhere);
-      Mmsg(ua->cmd,
-          "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
-          " bootstrap=\"%s\" regexwhere=\"%s\" files=%d catalog=\"%s\"",
-          job->name(), rx.ClientName, rx.RestoreClientName, 
-          rx.store?rx.store->name():"",
-          escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
-          escaped_where_name ? escaped_where_name : rx.RegexWhere,
-          rx.selected_files, ua->catalog->name());
+      Mmsg(buf, " regexwhere=\"%s\"",
+           escaped_where_name ? escaped_where_name : rx.RegexWhere);
 
    } else if (rx.where) {
       escaped_where_name = escape_filename(rx.where);
-      Mmsg(ua->cmd,
-          "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
-          " bootstrap=\"%s\" where=\"%s\" files=%d catalog=\"%s\"",
-          job->name(), rx.ClientName, rx.RestoreClientName,
-          rx.store?rx.store->name():"",
-          escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
-          escaped_where_name ? escaped_where_name : rx.where,
-          rx.selected_files, ua->catalog->name());
+      Mmsg(buf," where=\"%s\"",
+           escaped_where_name ? escaped_where_name : rx.where);
+   }
+   pm_strcat(ua->cmd, buf);
 
-   } else {
-      Mmsg(ua->cmd,
-          "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
-          " bootstrap=\"%s\" files=%d catalog=\"%s\"",
-          job->name(), rx.ClientName, rx.RestoreClientName,
-          rx.store?rx.store->name():"",
-          escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
-          rx.selected_files, ua->catalog->name());
+   if (rx.replace) {
+      Mmsg(buf, " replace=%s", rx.replace);
+      pm_strcat(ua->cmd, buf);
+   }
+
+   if (rx.fdcalled) {
+      pm_strcat(ua->cmd, " fdcalled=yes");
+   }
+
+   if (rx.when) {
+      Mmsg(buf, " when=\"%s\"", rx.when);
+      pm_strcat(ua->cmd, buf);
+   }
+
+   if (rx.comment) {
+      Mmsg(buf, " comment=\"%s\"", rx.comment);
+      pm_strcat(ua->cmd, buf);
    }
 
    if (escaped_bsr_name != NULL) {
@@ -268,7 +309,7 @@ int restore_cmd(UAContext *ua, const char *cmd)
    if (escaped_where_name != NULL) {
       bfree(escaped_where_name);
    }
-   
+
    if (regexp) {
       bfree(regexp);
    }
@@ -277,9 +318,26 @@ int restore_cmd(UAContext *ua, const char *cmd)
       pm_strcat(ua->cmd, " yes");    /* pass it on to the run command */
    }
    Dmsg1(200, "Submitting: %s\n", ua->cmd);
+   /*
+    * Transfer jobids, component stuff to jcr to
+    *  pass to run_cmd().  Note, these are fields and
+    *  other things that are not passed on the command
+    *  line.
+    */
+   /* ***FIXME*** pass jobids on command line */
+   if (jcr->JobIds) {
+      free_pool_memory(jcr->JobIds);
+   }
+   jcr->JobIds = rx.JobIds;
+   rx.JobIds = NULL;
+   jcr->component_fname = rx.component_fname;
+   rx.component_fname = NULL;
+   jcr->component_fd = rx.component_fd;
+   rx.component_fd = NULL;
    parse_ua_args(ua);
    run_cmd(ua, ua->cmd);
    free_rx(&rx);
+   garbage_collect_memory();       /* release unused memory */
    return 1;
 
 bail_out:
@@ -295,31 +353,59 @@ bail_out:
       bfree(regexp);
    }
 
+   /* Free the plugin config if needed, we don't want to re-use
+    * this part of the next try
+    */
+   free_plugin_config_items(jcr->plugin_config);
+   jcr->plugin_config = NULL;
+
    free_rx(&rx);
+   garbage_collect_memory();       /* release unused memory */
    return 0;
 
 }
 
-static void free_rx(RESTORE_CTX *rx)
+/*
+ * Fill the rx->BaseJobIds and display the list
+ */
+static void get_and_display_basejobs(UAContext *ua, RESTORE_CTX *rx)
 {
-   free_bsr(rx->bsr);
-   rx->bsr = NULL;
-   if (rx->JobIds) {
-      free_pool_memory(rx->JobIds);
-      rx->JobIds = NULL;
-   }
-   if (rx->fname) {
-      free_pool_memory(rx->fname);
-      rx->fname = NULL;
-   }
-   if (rx->path) {
-      free_pool_memory(rx->path);
-      rx->path = NULL;
+   db_list_ctx jobids;
+
+   if (!db_get_used_base_jobids(ua->jcr, ua->db, rx->JobIds, &jobids)) {
+      ua->warning_msg("%s", db_strerror(ua->db));
    }
-   if (rx->query) {
-      free_pool_memory(rx->query);
-      rx->query = NULL;
+
+   if (jobids.count) {
+      POOL_MEM q;
+      Mmsg(q, uar_print_jobs, jobids.list);
+      ua->send_msg(_("The restore will use the following job(s) as Base\n"));
+      db_list_sql_query(ua->jcr, ua->db, q.c_str(), prtit, ua, 1, HORZ_LIST);
    }
+   pm_strcpy(rx->BaseJobIds, jobids.list);
+}
+
+void free_rx(RESTORE_CTX *rx)
+{
+   free_bsr(rx->bsr_list);
+   rx->bsr_list = NULL;
+   free_and_null_pool_memory(rx->JobIds);
+   free_and_null_pool_memory(rx->BaseJobIds);
+   free_and_null_pool_memory(rx->fname);
+   free_and_null_pool_memory(rx->path);
+   free_and_null_pool_memory(rx->query);
+   if (rx->fileregex) {
+      free(rx->fileregex);
+      rx->fileregex = NULL;
+   }
+   if (rx->component_fd) {
+      fclose(rx->component_fd);
+      rx->component_fd = NULL;
+   }
+   if (rx->component_fname) {
+      unlink(rx->component_fname);
+   }
+   free_and_null_pool_memory(rx->component_fname);
    free_name_list(&rx->name_list);
 }
 
@@ -346,14 +432,16 @@ static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
          i = find_arg_with_value(ua, NT_("backupclient"));
       }
       if (i >= 0) {
-         if (!has_value(ua, i)) {
+         if (!is_name_valid(ua->argv[i], &ua->errmsg)) {
+            ua->error_msg("%s argument: %s", ua->argk[i], ua->errmsg);
             return 0;
          }
          bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
          return 1;
       }
       memset(&cr, 0, sizeof(cr));
-      if (!get_client_dbr(ua, &cr)) {
+      /* We want the name of the client where the backup was made */
+      if (!get_client_dbr(ua, &cr, JT_BACKUP_RESTORE)) {
          return 0;
       }
       bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
@@ -367,12 +455,13 @@ static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
 static int get_restore_client_name(UAContext *ua, RESTORE_CTX &rx)
 {
    /* Start with same name as backup client */
-   bstrncpy(rx.RestoreClientName, rx.ClientName, sizeof(rx.RestoreClientName));    
+   bstrncpy(rx.RestoreClientName, rx.ClientName, sizeof(rx.RestoreClientName));
 
    /* try command line argument */
    int i = find_arg_with_value(ua, NT_("restoreclient"));
    if (i >= 0) {
-      if (!has_value(ua, i)) {
+      if (!is_name_valid(ua->argv[i], &ua->errmsg)) {
+         ua->error_msg("%s argument: %s", ua->argk[i], ua->errmsg);
          return 0;
       }
       bstrncpy(rx.RestoreClientName, ua->argv[i], sizeof(rx.RestoreClientName));
@@ -415,6 +504,7 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
       _("Find the JobIds of the most recent backup for a client"),
       _("Find the JobIds for a backup for a client before a specified time"),
       _("Enter a list of directories to restore for found JobIds"),
+      _("Select full restore to a specified Job date"),
       _("Cancel"),
       NULL };
 
@@ -441,10 +531,19 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
       "add_prefix",   /* 16 */
       "add_suffix",   /* 17 */
       "regexwhere",   /* 18 */
+      "restoreclient", /* 19 */
+      "copies",        /* 20 */
+      "comment",       /* 21 */
+      "restorejob",    /* 22 */
+      "replace",       /* 23 */
+      "xxxxxxxxx",     /* 24 */
+      "fdcalled",      /* 25 */
+      "when",          /* 26 */
+      "noautoparent",  /* 27 */
       NULL
    };
 
-   *rx->JobIds = 0;
+   rx->JobIds[0] = 0;
 
    for (i=1; i<ua->argc; i++) {       /* loop through arguments */
       bool found_kw = false;
@@ -454,6 +553,7 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
             break;
          }
       }
+
       if (!found_kw) {
          ua->error_msg(_("Unknown keyword: %s\n"), ua->argk[i]);
          return 0;
@@ -480,7 +580,7 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
          have_date = true;
          break;
       case 2:                            /* before */
-         if (!has_value(ua, i)) {
+         if (have_date || !has_value(ua, i)) {
             return 0;
          }
          if (str_to_utime(ua->argv[i]) == 0) {
@@ -551,6 +651,7 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
       char *fname;
       int len;
       bool gui_save;
+      db_list_ctx jobids;
 
       start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
       for (int i=0; list[i]; i++) {
@@ -580,8 +681,8 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
          }
          len = strlen(ua->cmd);
          fname = (char *)malloc(len * 2 + 1);
-         db_escape_string(fname, ua->cmd, len);
-         Mmsg(rx->query, uar_file, rx->ClientName, fname);
+         db_escape_string(ua->jcr, ua->db, fname, ua->cmd, len);
+         Mmsg(rx->query, uar_file[db_get_type_index(ua->db)], rx->ClientName, fname);
          free(fname);
          gui_save = ua->jcr->gui;
          ua->jcr->gui = true;
@@ -610,21 +711,27 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
          done = false;
          break;
       case 4:                         /* Select the most recent backups */
-         bstrutime(date, sizeof(date), now);
+         if (!have_date) {
+            bstrutime(date, sizeof(date), now);
+         }
          if (!select_backups_before_date(ua, rx, date)) {
             return 0;
          }
          break;
       case 5:                         /* select backup at specified time */
-         if (!get_date(ua, date, sizeof(date))) {
-            return 0;
+         if (!have_date) {
+            if (!get_date(ua, date, sizeof(date))) {
+               return 0;
+            }
          }
          if (!select_backups_before_date(ua, rx, date)) {
             return 0;
          }
          break;
       case 6:                         /* Enter files */
-         bstrutime(date, sizeof(date), now);
+         if (!have_date) {
+            bstrutime(date, sizeof(date), now);
+         }
          if (!get_client_name(ua, rx)) {
             return 0;
          }
@@ -643,8 +750,10 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
          }
          return 2;
        case 7:                        /* enter files backed up before specified time */
-         if (!get_date(ua, date, sizeof(date))) {
-            return 0;
+         if (!have_date) {
+            if (!get_date(ua, date, sizeof(date))) {
+               return 0;
+            }
          }
          if (!get_client_name(ua, rx)) {
             return 0;
@@ -665,7 +774,9 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
          return 2;
 
       case 8:                         /* Find JobIds for current backup */
-         bstrutime(date, sizeof(date), now);
+         if (!have_date) {
+            bstrutime(date, sizeof(date), now);
+         }
          if (!select_backups_before_date(ua, rx, date)) {
             return 0;
          }
@@ -673,8 +784,10 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
          break;
 
       case 9:                         /* Find JobIds for give date */
-         if (!get_date(ua, date, sizeof(date))) {
-            return 0;
+         if (!have_date) {
+            if (!get_date(ua, date, sizeof(date))) {
+               return 0;
+            }
          }
          if (!select_backups_before_date(ua, rx, date)) {
             return 0;
@@ -693,9 +806,12 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
             pm_strcat(rx->JobIds, ua->cmd);
          }
          if (*rx->JobIds == 0 || *rx->JobIds == '.') {
+            *rx->JobIds = 0;
             return 0;                 /* nothing entered, return */
          }
-         bstrutime(date, sizeof(date), now);
+         if (!have_date) {
+            bstrutime(date, sizeof(date), now);
+         }
          if (!get_client_name(ua, rx)) {
             return 0;
          }
@@ -718,15 +834,39 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
          }
          return 2;
 
-      case 11:                        /* Cancel or quit */
+      case 11:                        /* Choose a jobid and select jobs */
+         if (!get_cmd(ua, _("Enter JobId to get the state to restore: ")) ||
+             !is_an_integer(ua->cmd))
+         {
+            return 0;
+         }
+
+         memset(&jr, 0, sizeof(JOB_DBR));
+         jr.JobId = str_to_int64(ua->cmd);
+         if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
+            ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
+                          ua->cmd, db_strerror(ua->db));
+            return 0;
+         }
+         ua->send_msg(_("Selecting jobs to build the Full state at %s\n"),
+                      jr.cStartTime);
+         jr.JobLevel = L_INCREMENTAL; /* Take Full+Diff+Incr */
+         if (!db_get_accurate_jobids(ua->jcr, ua->db, &jr, &jobids)) {
+            return 0;
+         }
+         pm_strcpy(rx->JobIds, jobids.list);
+         Dmsg1(30, "Item 12: jobids = %s\n", rx->JobIds);
+         break;
+      case 12:                        /* Cancel or quit */
          return 0;
       }
    }
 
+   memset(&jr, 0, sizeof(JOB_DBR));
    POOLMEM *JobIds = get_pool_memory(PM_FNAME);
    *JobIds = 0;
    rx->TotalFiles = 0;
-   /*        
+   /*
     * Find total number of files to be restored, and filter the JobId
     *  list to contain only ones permitted by the ACL conditions.
     */
@@ -763,12 +903,13 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
       pm_strcat(JobIds, edit_int64(JobId, ed1));
       rx->TotalFiles += jr.JobFiles;
    }
-   free_pool_memory(rx->JobIds);
-   rx->JobIds = JobIds;               /* Set ACL filtered list */
+   pm_strcpy(rx->JobIds, JobIds);     /* Set ACL filtered list */
+   free_pool_memory(JobIds);
    if (*rx->JobIds == 0) {
       ua->warning_msg(_("No Jobs selected.\n"));
       return 0;
    }
+
    if (strchr(rx->JobIds,',')) {
       ua->info_msg(_("You have selected the following JobIds: %s\n"), rx->JobIds);
    } else {
@@ -780,13 +921,13 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
 /*
  * Get date from user
  */
-static int get_date(UAContext *ua, char *date, int date_len)
+static bool get_date(UAContext *ua, char *date, int date_len)
 {
    ua->send_msg(_("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;
+         return false;
       }
       if (str_to_utime(ua->cmd) != 0) {
          break;
@@ -794,7 +935,7 @@ static int get_date(UAContext *ua, char *date, int date_len)
       ua->error_msg(_("Improper date format.\n"));
    }
    bstrncpy(date, ua->cmd, date_len);
-   return 1;
+   return true;
 }
 
 /*
@@ -810,10 +951,10 @@ static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, b
    switch (*p) {
    case '<':
       p++;
-      if ((ffd = fopen(p, "rb")) == NULL) {
+      if ((ffd = bfopen(p, "rb")) == NULL) {
          berrno be;
          ua->error_msg(_("Cannot open file %s: ERR=%s\n"),
-            p, be.strerror());
+            p, be.bstrerror());
          break;
       }
       while (fgets(file, sizeof(file), ffd)) {
@@ -853,13 +994,20 @@ static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *f
                                         char *date)
 {
    strip_trailing_newline(file);
-   split_path_and_filename(rx, file);
+   split_path_and_filename(ua, rx, file);
    if (*rx->JobIds == 0) {
-      Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname, 
+      Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname,
            rx->ClientName);
    } else {
       Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
            rx->path, rx->fname, rx->ClientName);
+      /*
+       * Note: we have just edited the JobIds into the query, so
+       *  we need to clear JobIds, or they will be added
+       *  back into JobIds with the query below, and then
+       *  restored twice.  Fixes bug #2212.
+       */
+      rx->JobIds[0] = 0;
    }
    rx->found = false;
    /* Find and insert jobid and File Index */
@@ -887,8 +1035,7 @@ static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *di
       ua->error_msg(_("No JobId specified cannot continue.\n"));
       return false;
    } else {
-      Mmsg(rx->query, uar_jobid_fileindex_from_dir, rx->JobIds, 
-           dir, rx->ClientName);
+      Mmsg(rx->query, uar_jobid_fileindex_from_dir[db_get_type_index(ua->db)], rx->JobIds, dir, rx->ClientName);
    }
    rx->found = false;
    /* Find and insert jobid and File Index */
@@ -906,7 +1053,7 @@ static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *di
 /*
  * Get the JobId and FileIndexes of all files in the specified table
  */
-static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
+bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
 {
    strip_trailing_junk(table);
    Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
@@ -924,7 +1071,7 @@ static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *
    return true;
 }
 
-static void split_path_and_filename(RESTORE_CTX *rx, char *name)
+static void split_path_and_filename(UAContext *ua, RESTORE_CTX *rx, char *name)
 {
    char *p, *f;
 
@@ -952,9 +1099,8 @@ static void split_path_and_filename(RESTORE_CTX *rx, char *name)
     */
    rx->fnl = p - f;
    if (rx->fnl > 0) {
-      rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
-      memcpy(rx->fname, f, rx->fnl);    /* copy filename */
-      rx->fname[rx->fnl] = 0;
+      rx->fname = check_pool_memory_size(rx->fname, 2*(rx->fnl)+1);
+      db_escape_string(ua->jcr, ua->db, rx->fname, f, rx->fnl);
    } else {
       rx->fname[0] = 0;
       rx->fnl = 0;
@@ -962,9 +1108,8 @@ static void split_path_and_filename(RESTORE_CTX *rx, char *name)
 
    rx->pnl = f - name;
    if (rx->pnl > 0) {
-      rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
-      memcpy(rx->path, name, rx->pnl);
-      rx->path[rx->pnl] = 0;
+      rx->path = check_pool_memory_size(rx->path, 2*(rx->pnl)+1);
+      db_escape_string(ua->jcr, ua->db, rx->path, name, rx->pnl);
    } else {
       rx->path[0] = 0;
       rx->pnl = 0;
@@ -973,6 +1118,126 @@ static void split_path_and_filename(RESTORE_CTX *rx, char *name)
    Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
 }
 
+static bool can_restore_all_files(UAContext *ua)
+{
+   alist *lst;
+   if (ua->cons) {
+      lst = ua->cons->ACL_lists[Directory_ACL];
+      /* ACL not defined, or the first entry is not *all* */
+      /* TODO: See if we search for *all* in all the list */
+      if (!lst || strcasecmp((char*)lst->get(0), "*all*") != 0) {
+         return false;
+      }
+      if (!lst || strcasecmp((char *)lst->get(0), "*all*") != 0) {
+         return false;
+      }
+   }
+   return true;
+}
+
+static bool ask_for_fileregex(UAContext *ua, RESTORE_CTX *rx)
+{
+   bool can_restore=can_restore_all_files(ua);
+
+   if (can_restore && find_arg(ua, NT_("all")) >= 0) {  /* if user enters all on command line */
+      return true;                       /* select everything */
+   }
+
+   ua->send_msg(_("\n\nFor one or more of the JobIds selected, no files were found,\n"
+                 "so file selection is not possible.\n"
+                 "Most likely your retention policy pruned the files.\n"));
+
+   if (!can_restore) {
+      ua->error_msg(_("\nThe current Console has UserId or Directory restrictions. "
+                      "The full restore is not allowed.\n"));
+      return false;
+   }
+
+   if (get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
+      if (ua->pint32_val == 1)
+         return true;
+      while (get_cmd(ua, _("\nRegexp matching files to restore? (empty to abort): "))) {
+         if (ua->cmd[0] == '\0') {
+            break;
+         } else {
+            regex_t *fileregex_re = NULL;
+            int rc;
+            char errmsg[500] = "";
+
+            fileregex_re = (regex_t *)bmalloc(sizeof(regex_t));
+            rc = regcomp(fileregex_re, ua->cmd, REG_EXTENDED|REG_NOSUB);
+            if (rc != 0) {
+               regerror(rc, fileregex_re, errmsg, sizeof(errmsg));
+            }
+            regfree(fileregex_re);
+            free(fileregex_re);
+            if (*errmsg) {
+               ua->send_msg(_("Regex compile error: %s\n"), errmsg);
+            } else {
+               rx->fileregex = bstrdup(ua->cmd);
+               return true;
+            }
+         }
+      }
+   }
+   return false;
+}
+
+/* Walk on the delta_list of a TREE_NODE item and insert all parts
+ * TODO: Optimize for bootstrap creation, remove recursion
+ * 6 -> 5 -> 4 -> 3 -> 2 -> 1 -> 0
+ * should insert as
+ * 0, 1, 2, 3, 4, 5, 6
+ */
+static void add_delta_list_findex(RESTORE_CTX *rx, struct delta_list *lst)
+{
+   if (lst == NULL) {
+      return;
+   }
+   if (lst->next) {
+      add_delta_list_findex(rx, lst->next);
+   }
+   add_findex(rx->bsr_list, lst->JobId, lst->FileIndex);
+}
+
+/*
+ * This is a list of all the files (components) that the
+ *  user has requested for restore. It is requested by
+ *  the plugin (for now hard coded only for VSS).
+ *  In the future, this will be requested by a RestoreObject
+ *  and the plugin name will be sent to the FD.
+ */
+static bool write_component_file(UAContext *ua, RESTORE_CTX *rx, char *fname)
+{
+   int fd;
+   if (!rx->component_fd) {
+      Mmsg(rx->component_fname, "%s/%s.restore.sel.XXXXXX", working_directory, my_name);
+      fd = mkstemp(rx->component_fname);
+      if (fd < 0) {
+         berrno be;
+         ua->error_msg(_("Unable to create component file %s. ERR=%s\n"),
+            rx->component_fname, be.bstrerror());
+         return false;
+      }
+      rx->component_fd = fdopen(fd, "w+");
+      if (!rx->component_fd) {
+         berrno be;
+         ua->error_msg(_("Unable to fdopen component file %s. ERR=%s\n"),
+            rx->component_fname, be.bstrerror());
+         return false;
+      }
+   }
+   fprintf(rx->component_fd, "%s\n", fname);
+   if (ferror(rx->component_fd)) {
+      ua->error_msg(_("Error writing component file.\n"));
+     fclose(rx->component_fd);
+     unlink(rx->component_fname);
+     rx->component_fd = NULL;
+     return false;
+   }
+   return true;
+}
+
 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
 {
    TREE_CTX tree;
@@ -988,12 +1253,14 @@ static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
    tree.root = new_tree(rx->TotalFiles);
    tree.ua = ua;
    tree.all = rx->all;
+   tree.hardlinks_in_mem = rx->hardlinks_in_mem;
+   tree.no_auto_parent = rx->no_auto_parent;
    last_JobId = 0;
+   tree.last_dir_acl = NULL;
    /*
     * For display purposes, the same JobId, with different volumes may
     * appear more than once, however, we only insert it once.
     */
-   int items = 0;
    p = rx->JobIds;
    tree.FileEstimate = 0;
    if (get_next_jobid_from_list(&p, &JobId) > 0) {
@@ -1008,6 +1275,24 @@ static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
          tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
       }
    }
+
+   ua->info_msg(_("\nBuilding directory tree for JobId(s) %s ...  "),
+                rx->JobIds);
+
+#define new_get_file_list
+#ifdef new_get_file_list
+   if (!db_get_file_list(ua->jcr, ua->db,
+                         rx->JobIds, false /* do not use md5 */,
+                         true /* get delta */,
+                         insert_tree_handler, (void *)&tree))
+   {
+      ua->error_msg("%s", db_strerror(ua->db));
+   }
+   if (*rx->BaseJobIds) {
+      pm_strcat(rx->JobIds, ",");
+      pm_strcat(rx->JobIds, rx->BaseJobIds);
+   }
+#else
    for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
       char ed1[50];
 
@@ -1015,9 +1300,6 @@ static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
          continue;                    /* eliminate duplicate JobIds */
       }
       last_JobId = JobId;
-      ua->info_msg(_("\nBuilding directory tree for JobId %s ...  "), 
-         edit_int64(JobId, ed1));
-      items++;
       /*
        * Find files for this JobId and insert them in the tree
        */
@@ -1026,42 +1308,49 @@ static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
          ua->error_msg("%s", db_strerror(ua->db));
       }
    }
+#endif
+   /*
+    * At this point, the tree is built, so we can garbage collect
+    * any memory released by the SQL engine that RedHat has
+    * not returned to the OS :-(
+    */
+    garbage_collect_memory();
+
+   /*
+    * Look at the first JobId on the list (presumably the oldest) and
+    *  if it is marked purged, don't do the manual selection because
+    *  the Job was pruned, so the tree is incomplete.
+    */
+   if (tree.FileCount != 0) {
+      /* Find out if any Job is purged */
+      Mmsg(rx->query, "SELECT SUM(PurgedFiles) FROM Job WHERE JobId IN (%s)", rx->JobIds);
+      if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
+         ua->error_msg("%s\n", db_strerror(ua->db));
+      }
+      /* rx->JobId is the PurgedFiles flag */
+      if (rx->found && rx->JobId > 0) {
+         tree.FileCount = 0;           /* set count to zero, no tree selection */
+      }
+   }
    if (tree.FileCount == 0) {
-      ua->send_msg(_("\nThere were no files inserted into the tree, so file selection\n"
-         "is not possible.Most likely your retention policy pruned the files\n"));
-      if (!get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
-         OK = false;
-      } else {
+      OK = ask_for_fileregex(ua, rx);
+      if (OK) {
          last_JobId = 0;
          for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
              if (JobId == last_JobId) {
                 continue;                    /* eliminate duplicate JobIds */
              }
-             add_findex_all(rx->bsr, JobId);
-          }
-          OK = true;
+             add_findex_all(rx->bsr_list, JobId, rx->fileregex);
+         }
       }
    } else {
       char ec1[50];
-      if (items==1) {
-         if (tree.all) {
-            ua->info_msg(_("\n1 Job, %s files inserted into the tree and marked for extraction.\n"),
-              edit_uint64_with_commas(tree.FileCount, ec1));
-         }
-         else {
-            ua->info_msg(_("\n1 Job, %s files inserted into the tree.\n"),
-              edit_uint64_with_commas(tree.FileCount, ec1));
-         }
-      }
-      else {
-         if (tree.all) {
-            ua->info_msg(_("\n%d Jobs, %s files inserted into the tree and marked for extraction.\n"),
-              items, edit_uint64_with_commas(tree.FileCount, ec1));
-         }
-         else {
-            ua->info_msg(_("\n%d Jobs, %s files inserted into the tree.\n"),
-              items, edit_uint64_with_commas(tree.FileCount, ec1));
-         }
+      if (tree.all) {
+         ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
+                      edit_uint64_with_commas(tree.FileCount, ec1));
+      } else {
+         ua->info_msg(_("\n%s files inserted into the tree.\n"),
+                      edit_uint64_with_commas(tree.FileCount, ec1));
       }
 
       if (find_arg(ua, NT_("done")) < 0) {
@@ -1074,11 +1363,26 @@ static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
        *  extracted making a bootstrap file.
        */
       if (OK) {
+         char cwd[2000];
          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 || node->extract_dir) {
-               Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
-               add_findex(rx->bsr, node->JobId, node->FileIndex);
+               Dmsg3(400, "JobId=%lld type=%d FI=%d\n", (uint64_t)node->JobId, node->type, node->FileIndex);
+               /* TODO: optimize bsr insertion when jobid are non sorted */
+               add_delta_list_findex(rx, node->delta_list);
+               add_findex(rx->bsr_list, node->JobId, node->FileIndex);
+               /*
+                * Special VSS plugin code to return selected
+                *   components. For the moment, it is hard coded
+                *   for the VSS plugin.
+                */
+               if (fnmatch(":component_info_*", node->fname, 0) == 0) {
+                  tree_getpath(node, cwd, sizeof(cwd));
+                  if (!write_component_file(ua, rx, cwd)) {
+                     OK = false;
+                     break;
+                  }
+               }
                if (node->extract && node->type != TN_NEWDIR) {
                   rx->selected_files++;  /* count only saved files */
                }
@@ -1086,7 +1390,11 @@ static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
          }
       }
    }
-
+   if (tree.uid_acl) {
+      delete tree.uid_acl;
+      delete tree.gid_acl;
+      delete tree.dir_acl;
+   }
    free_tree(tree.root);              /* free the directory tree */
    return OK;
 }
@@ -1106,21 +1414,20 @@ static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *dat
    char pool_select[MAX_NAME_LENGTH];
    int i;
 
-
    /* 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)) {
+  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[db_get_type_index(ua->db)], NULL, NULL)) {
       ua->error_msg("%s\n", db_strerror(ua->db));
    }
-   if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
+   if (!db_sql_query(ua->db, uar_create_temp1[db_get_type_index(ua->db)], NULL, NULL)) {
       ua->error_msg("%s\n", db_strerror(ua->db));
    }
    /*
     * Select Client from the Catalog
     */
    memset(&cr, 0, sizeof(cr));
-   if (!get_client_dbr(ua, &cr)) {
+   if (!get_client_dbr(ua, &cr, JT_BACKUP_RESTORE)) {
       goto bail_out;
    }
    bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
@@ -1130,14 +1437,18 @@ static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *dat
     */
    memset(&fsr, 0, sizeof(fsr));
    i = find_arg_with_value(ua, "FileSet");
-   if (i >= 0) {
+
+   if (i >= 0 && is_name_valid(ua->argv[i], &ua->errmsg)) {
       bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
          ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
             db_strerror(ua->db));
          i = -1;
       }
+   } else if (i >= 0) {         /* name is invalid */
+      ua->error_msg(_("FileSet argument: %s\n"), ua->errmsg);
    }
+
    if (i < 0) {                       /* fileset not found */
       edit_int64(cr.ClientId, ed1);
       Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
@@ -1166,7 +1477,7 @@ static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *dat
       memset(&pr, 0, sizeof(pr));
       bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
       if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
-         bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ", 
+         bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
             edit_int64(pr.PoolId, ed1));
       } else {
          ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
@@ -1187,6 +1498,7 @@ static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *dat
       ua->error_msg("%s\n", db_strerror(ua->db));
       goto bail_out;
    }
+
    /* Note, this is needed because I don't seem to get the callback
     * from the call just above.
     */
@@ -1205,7 +1517,7 @@ static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *dat
    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
       ua->warning_msg("%s\n", db_strerror(ua->db));
    }
-   /* Now update JobTDate to lock onto Differental, if any */
+   /* Now update JobTDate to look into Differental, if any */
    rx->JobTDate = 0;
    if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
       ua->warning_msg("%s\n", db_strerror(ua->db));
@@ -1223,61 +1535,32 @@ static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *dat
    }
 
    /* Get the JobIds from that list */
-   rx->JobIds[0] = 0;
-   rx->last_jobid[0] = 0;
+   rx->last_jobid[0] = rx->JobIds[0] = 0;
+
    if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
       ua->warning_msg("%s\n", db_strerror(ua->db));
    }
 
    if (rx->JobIds[0] != 0) {
+      if (find_arg(ua, NT_("copies")) > 0) {
+         /* Display a list of all copies */
+         db_list_copies_records(ua->jcr, ua->db, 0, rx->JobIds,
+                                prtit, ua, HORZ_LIST);
+      }
       /* 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);
+      db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1,HORZ_LIST);
       ok = true;
+
    } else {
       ua->warning_msg(_("No jobs found.\n"));
    }
 
 bail_out:
-   db_sql_query(ua->db, uar_del_temp, NULL, NULL);
-   db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
+  db_sql_query(ua->db, uar_del_temp, NULL, NULL);
+  db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
    return ok;
 }
 
-
-/* 
- * Return next JobId from comma separated list   
- *
- * Returns:
- *   1 if next JobId returned
- *   0 if no more JobIds are in list
- *  -1 there is an error
- */
-int get_next_jobid_from_list(char **p, JobId_t *JobId)
-{
-   char jobid[30];
-   char *q = *p;
-
-   jobid[0] = 0;
-   for (int i=0; i<(int)sizeof(jobid); i++) {
-      if (*q == 0) {
-         break;
-      } else if (*q == ',') {
-         q++;
-         break;
-      }
-      jobid[i] = *q++;
-      jobid[i+1] = 0;
-   }
-   if (jobid[0] == 0) {
-      return 0;
-   } else if (!is_a_number(jobid)) {
-      return -1;                      /* error */
-   }
-   *p = q;
-   *JobId = str_to_int64(jobid);
-   return 1;
-}
-
 static int restore_count_handler(void *ctx, int num_fields, char **row)
 {
    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
@@ -1293,8 +1576,24 @@ static int restore_count_handler(void *ctx, int num_fields, char **row)
 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
 {
    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
-   rx->JobId = str_to_int64(row[0]);
-   add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
+   JobId_t JobId = str_to_int64(row[0]);
+
+   Dmsg3(200, "JobId=%s JobIds=%s FileIndex=%s\n", row[0], rx->JobIds, row[1]);
+
+   /* New JobId, add it to JobIds
+    * The list is sorted by JobId, so we need a cache for the previous value
+    *
+    * It will permit to find restore objects to send during the restore
+    */
+   if (rx->JobId != JobId) {
+      if (*rx->JobIds) {
+         pm_strcat(rx->JobIds, ",");
+      }
+      pm_strcat(rx->JobIds, row[0]);
+      rx->JobId = JobId;
+   }
+
+   add_findex(rx->bsr_list, rx->JobId, str_to_int64(row[1]));
    rx->found = true;
    rx->selected_files++;
    return 0;
@@ -1350,15 +1649,12 @@ static void free_name_list(NAME_LIST *name_list)
    for (int i=0; i < name_list->num_ids; i++) {
       free(name_list->name[i]);
    }
-   if (name_list->name) {
-      free(name_list->name);
-      name_list->name = NULL;
-   }
+   bfree_and_null(name_list->name);
    name_list->max_ids = 0;
    name_list->num_ids = 0;
 }
 
-void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType) 
+void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType)
 {
    STORE *store;
 
@@ -1391,10 +1687,17 @@ void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *
          }
       }
       if (store && (store != rx.store)) {
-         ua->info_msg(_("Warning default storage overridden by \"%s\" on command line.\n"),
+         ua->info_msg(_("\nWarning Storage is overridden by \"%s\" on the command line.\n"),
             store->name());
          rx.store = store;
-         Dmsg1(200, "Set store=%s\n", rx.store->name());
+         bstrncpy(rx.RestoreMediaType, MediaType, sizeof(rx.RestoreMediaType));
+         if (strcmp(MediaType, store->media_type) != 0) {
+            ua->info_msg(_("This may not work because of two different MediaTypes:\n"
+               "  Storage MediaType=\"%s\"\n"
+               "  Volume  MediaType=\"%s\".\n\n"),
+               store->media_type, MediaType);
+         }
+         Dmsg2(200, "Set store=%s MediaType=%s\n", rx.store->name(), rx.RestoreMediaType);
       }
       return;
    }
@@ -1407,8 +1710,13 @@ void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *
             if (acl_access_ok(ua, Storage_ACL, store->name())) {
                rx.store = store;
                Dmsg1(200, "Set store=%s\n", rx.store->name());
-               ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
-                  Storage, store->name(), MediaType);
+               if (Storage == NULL || Storage[0] == 0) {
+                  ua->warning_msg(_("Using Storage \"%s\" from MediaType \"%s\".\n"),
+                     store->name(), MediaType);
+               } else {
+                  ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
+                     Storage, store->name(), MediaType);
+               }
             }
             UnlockRes();
             return;
@@ -1421,6 +1729,8 @@ void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *
 
    /* Take command line arg, or ask user if none */
    rx.store = get_storage_resource(ua, false /* don't use default */);
-   Dmsg1(200, "Set store=%s\n", rx.store->name());
+   if (rx.store) {
+      Dmsg1(200, "Set store=%s\n", rx.store->name());
+   }
 
 }