]> 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 a02ff7f0e7e8abaa835ed8deda81a2be2e7fc483..85979b8d62a76da3b431400f8ea1a9507f3c3a55 100644 (file)
@@ -1,32 +1,22 @@
 /*
-   Bacula® - The Network Backup Solution
-
-   Copyright (C) 2002-2010 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 three of the GNU Affero General Public
-   License as published by the Free Software Foundation and included
-   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 Affero 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 Kern Sibbald.
-   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.
@@ -46,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);
@@ -55,7 +44,6 @@ 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(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,
@@ -67,9 +55,35 @@ static int get_client_name(UAContext *ua, RESTORE_CTX *rx);
 static int get_restore_client_name(UAContext *ua, RESTORE_CTX &rx);
 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
  *
@@ -86,46 +100,55 @@ 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.JobIds[0] = 0;
-   rx.BaseJobIds = get_pool_memory(PM_FNAME);
-   rx.query = get_pool_memory(PM_FNAME);
-   rx.bsr = new_bsr();
+   new_rx(&rx);                 /* Initialize RESTORE_CTX */
+   
+   if (!open_new_client_db(ua)) {
+      goto bail_out;
+   }
 
-   i = find_arg_with_value(ua, "comment");
-   if (i >= 0) {
-      rx.comment = ua->argv[i];
-      if (!is_comment_legal(ua, rx.comment)) {
-         goto bail_out;
+   for (i = 0; i < ua->argc ; i++) {
+      if (strcasecmp(ua->argk[i], "fdcalled") == 0) {
+         rx.fdcalled = true;
+
+      } 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, "where");
-   if (i >= 0) {
-      rx.where = ua->argv[i];
-   }
+      } else if (strcasecmp(ua->argk[i], "where") == 0) {
+         rx.where = ua->argv[i];
 
-   i = find_arg_with_value(ua, "strip_prefix");
-   if (i >= 0) {
-      strip_prefix = ua->argv[i];
-   }
+      } else if (strcasecmp(ua->argk[i], "when") == 0) {
+         rx.when = ua->argv[i];
 
-   i = find_arg_with_value(ua, "add_prefix");
-   if (i >= 0) {
-      add_prefix = ua->argv[i];
-   }
+      } else if (strcasecmp(ua->argk[i], "replace") == 0) {
+         rx.replace = ua->argv[i];
 
-   i = find_arg_with_value(ua, "add_suffix");
-   if (i >= 0) {
-      add_suffix = ua->argv[i];
-   }
+      } else if (strcasecmp(ua->argk[i], "strip_prefix") == 0) {
+         strip_prefix = ua->argv[i];
 
-   i = find_arg_with_value(ua, "regexwhere");
-   if (i >= 0) {
-      rx.RegexWhere = 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) {
@@ -152,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) {
@@ -194,9 +213,9 @@ int restore_cmd(UAContext *ua, const char *cmd)
       break;
    }
 
-   if (rx.bsr->JobId) {
+   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;
       }
@@ -204,12 +223,14 @@ int restore_cmd(UAContext *ua, const char *cmd)
          ua->warning_msg(_("No files selected to be restored.\n"));
          goto bail_out;
       }
+
+      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"), 
+         ua->info_msg(_("\n%s files selected to be restored.\n\n"),
             edit_uint64_with_commas(rx.selected_files, ed1));
       }
    } else {
@@ -220,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;
    }
@@ -245,18 +266,37 @@ int restore_cmd(UAContext *ua, const char *cmd)
 
    /* 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(buf, " regexwhere=\"%s\"", 
+      Mmsg(buf, " regexwhere=\"%s\"",
            escaped_where_name ? escaped_where_name : rx.RegexWhere);
 
    } else if (rx.where) {
       escaped_where_name = escape_filename(rx.where);
-      Mmsg(buf," where=\"%s\"", 
+      Mmsg(buf," where=\"%s\"",
            escaped_where_name ? escaped_where_name : rx.where);
    }
    pm_strcat(ua->cmd, buf);
 
+   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);
@@ -269,7 +309,7 @@ int restore_cmd(UAContext *ua, const char *cmd)
    if (escaped_where_name != NULL) {
       bfree(escaped_where_name);
    }
-   
+
    if (regexp) {
       bfree(regexp);
    }
@@ -278,12 +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 to jcr to for picking up restore objects */
+   /*
+    * 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:
@@ -299,12 +353,19 @@ 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;
 
 }
 
-/* 
+/*
  * Fill the rx->BaseJobIds and display the list
  */
 static void get_and_display_basejobs(UAContext *ua, RESTORE_CTX *rx)
@@ -314,7 +375,7 @@ static void get_and_display_basejobs(UAContext *ua, RESTORE_CTX *rx)
    if (!db_get_used_base_jobids(ua->jcr, ua->db, rx->JobIds, &jobids)) {
       ua->warning_msg("%s", db_strerror(ua->db));
    }
-   
+
    if (jobids.count) {
       POOL_MEM q;
       Mmsg(q, uar_print_jobs, jobids.list);
@@ -324,15 +385,27 @@ static void get_and_display_basejobs(UAContext *ua, RESTORE_CTX *rx)
    pm_strcpy(rx->BaseJobIds, jobids.list);
 }
 
-static void free_rx(RESTORE_CTX *rx)
+void free_rx(RESTORE_CTX *rx)
 {
-   free_bsr(rx->bsr);
-   rx->bsr = NULL;
+   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);
 }
 
@@ -359,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));
@@ -380,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));
@@ -458,6 +534,12 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
       "restoreclient", /* 19 */
       "copies",        /* 20 */
       "comment",       /* 21 */
+      "restorejob",    /* 22 */
+      "replace",       /* 23 */
+      "xxxxxxxxx",     /* 24 */
+      "fdcalled",      /* 25 */
+      "when",          /* 26 */
+      "noautoparent",  /* 27 */
       NULL
    };
 
@@ -471,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;
@@ -599,7 +682,7 @@ 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(ua->jcr, ua->db, fname, ua->cmd, len);
-         Mmsg(rx->query, uar_file[db_type], rx->ClientName, fname);
+         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;
@@ -753,7 +836,7 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
 
       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)) 
+             !is_an_integer(ua->cmd))
          {
             return 0;
          }
@@ -768,7 +851,7 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
          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_accurate_get_jobids(ua->jcr, ua->db, &jr, &jobids)) {
+         if (!db_get_accurate_jobids(ua->jcr, ua->db, &jr, &jobids)) {
             return 0;
          }
          pm_strcpy(rx->JobIds, jobids.list);
@@ -783,7 +866,7 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
    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.
     */
@@ -820,8 +903,8 @@ 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;
@@ -868,7 +951,7 @@ 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.bstrerror());
@@ -913,11 +996,18 @@ static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *f
    strip_trailing_newline(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 */
@@ -939,13 +1029,13 @@ static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *f
  */
 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
                                         char *date)
-{  
+{
    strip_trailing_junk(dir);
    if (*rx->JobIds == 0) {
       ua->error_msg(_("No JobId specified cannot continue.\n"));
       return false;
    } else {
-      Mmsg(rx->query, uar_jobid_fileindex_from_dir[db_type], 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 */
@@ -963,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);
@@ -1028,14 +1118,41 @@ static void split_path_and_filename(UAContext *ua, 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)
 {
-   if (find_arg(ua, NT_("all")) >= 0) {  /* if user enters all on command line */
+   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;
@@ -1057,7 +1174,7 @@ static bool ask_for_fileregex(UAContext *ua, RESTORE_CTX *rx)
             if (*errmsg) {
                ua->send_msg(_("Regex compile error: %s\n"), errmsg);
             } else {
-               rx->bsr->fileregex = bstrdup(ua->cmd);
+               rx->fileregex = bstrdup(ua->cmd);
                return true;
             }
          }
@@ -1080,7 +1197,45 @@ static void add_delta_list_findex(RESTORE_CTX *rx, struct delta_list *lst)
    if (lst->next) {
       add_delta_list_findex(rx, lst->next);
    }
-   add_findex(rx->bsr, lst->JobId, lst->FileIndex);
+   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)
@@ -1098,7 +1253,10 @@ 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.
@@ -1123,8 +1281,8 @@ static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
 
 #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 */, 
+   if (!db_get_file_list(ua->jcr, ua->db,
+                         rx->JobIds, false /* do not use md5 */,
                          true /* get delta */,
                          insert_tree_handler, (void *)&tree))
    {
@@ -1151,6 +1309,13 @@ static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
       }
    }
 #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
@@ -1175,7 +1340,7 @@ static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
              if (JobId == last_JobId) {
                 continue;                    /* eliminate duplicate JobIds */
              }
-             add_findex_all(rx->bsr, JobId);
+             add_findex_all(rx->bsr_list, JobId, rx->fileregex);
          }
       }
    } else {
@@ -1198,13 +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) {
                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, node->JobId, node->FileIndex);
+               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 */
                }
@@ -1212,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;
 }
@@ -1233,19 +1415,19 @@ static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *dat
    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[db_type], 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[db_type], 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));
@@ -1255,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);
@@ -1291,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);
@@ -1358,7 +1544,7 @@ static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *dat
    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, 
+         db_list_copies_records(ua->jcr, ua->db, 0, rx->JobIds,
                                 prtit, ua, HORZ_LIST);
       }
       /* Display a list of Jobs selected for this restore */
@@ -1370,8 +1556,8 @@ static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *dat
    }
 
 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;
 }
 
@@ -1390,10 +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;
+   JobId_t JobId = str_to_int64(row[0]);
 
-   Dmsg2(200, "JobId=%s FileIndex=%s\n", row[0], row[1]);
-   rx->JobId = str_to_int64(row[0]);
-   add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
+   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;
@@ -1454,7 +1654,7 @@ static void free_name_list(NAME_LIST *name_list)
    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;
 
@@ -1487,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;
    }
@@ -1503,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;