]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/dird/ua_restore.c
ebl Work on copy jobs
[bacula/bacula] / bacula / src / dird / ua_restore.c
index 24d0aba7b7a258c31ef2d994669879cbc88bad57..f9430b9dad099860eba2fbdb3dab2e8e72d165da 100644 (file)
@@ -1,3 +1,30 @@
+/*
+   Bacula® - The Network Backup Solution
+
+   Copyright (C) 2002-2008 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 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 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 Director -- User Agent Database restore Command
  *
  *   Version $Id$
  */
-/*
-   Copyright (C) 2002-2006 Kern Sibbald
-
-   This program is free software; you can redistribute it and/or
-   modify it under the terms of the GNU General Public License
-   version 2 as amended with additional clauses defined in the
-   file LICENSE in the main source directory.
-
-   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 
-   the file LICENSE for additional details.
-
- */
 
 
 #include "bacula.h"
 #include "dird.h"
 
-
 /* Imported functions */
 extern void print_bsr(UAContext *ua, RBSR *bsr);
 
-/* Imported variables */
-extern const char *uar_list_jobs,      *uar_file,        *uar_sel_files;
-extern const char *uar_del_temp,       *uar_del_temp1,   *uar_create_temp;
-extern const char *uar_create_temp1,   *uar_last_full,   *uar_full;
-extern const char *uar_inc,            *uar_list_temp,   *uar_sel_jobid_temp;
-extern const char *uar_sel_all_temp1,  *uar_sel_fileset, *uar_mediatype;
-extern const char *uar_jobid_fileindex, *uar_dif,        *uar_sel_all_temp;
-extern const char *uar_count_files,     *uar_jobids_fileindex;
-extern const char *uar_jobid_fileindex_from_dir;
-extern const char *uar_jobid_fileindex_from_table;
-
 
 
 /* Forward referenced functions */
@@ -57,7 +58,7 @@ 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);
@@ -65,8 +66,9 @@ static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *di
                                         char *date);
 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 int count_handler(void *ctx, int num_fields, char **row);
+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);
 
 /*
@@ -79,6 +81,10 @@ int restore_cmd(UAContext *ua, const char *cmd)
    JOB *job;
    int i;
    JCR *jcr = ua->jcr;
+   char *escaped_bsr_name = NULL;
+   char *escaped_where_name = NULL;
+   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);
@@ -92,7 +98,51 @@ int restore_cmd(UAContext *ua, const char *cmd)
       rx.where = ua->argv[i];
    }
 
-   if (!open_db(ua)) {
+   i = find_arg_with_value(ua, "strip_prefix");
+   if (i >= 0) {
+      strip_prefix = ua->argv[i];
+   }
+
+   i = find_arg_with_value(ua, "add_prefix");
+   if (i >= 0) {
+      add_prefix = ua->argv[i];
+   }
+
+   i = find_arg_with_value(ua, "add_suffix");
+   if (i >= 0) {
+      add_suffix = ua->argv[i];
+   }
+
+   i = find_arg_with_value(ua, "regexwhere");
+   if (i >= 0) {
+      rx.RegexWhere = ua->argv[i];
+   }
+
+   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));
+
+      bregexp_build_where(regexp, len, strip_prefix, add_prefix, add_suffix);
+      rx.RegexWhere = regexp;
+   }
+
+   /* TODO: add acl for regexwhere ? */
+
+   if (rx.RegexWhere) {
+      if (!acl_access_ok(ua, Where_ACL, rx.RegexWhere)) {
+         ua->error_msg(_("\"RegexWhere\" specification not authorized.\n"));
+         goto bail_out;
+      }
+   }
+
+   if (rx.where) {
+      if (!acl_access_ok(ua, Where_ACL, rx.where)) {
+         ua->error_msg(_("\"where\" specification not authorized.\n"));
+         goto bail_out;
+      }
+   }
+
+   if (!open_client_db(ua)) {
       goto bail_out;
    }
 
@@ -108,7 +158,7 @@ int restore_cmd(UAContext *ua, const char *cmd)
    }
    UnlockRes();
    if (!rx.restore_jobs) {
-      bsendmsg(ua, _(
+      ua->error_msg(_(
          "No Restore Job Resource found in bacula-dir.conf.\n"
          "You must create at least one before running this command.\n"));
       goto bail_out;
@@ -125,7 +175,7 @@ int restore_cmd(UAContext *ua, const char *cmd)
       goto bail_out;
    case 1:                            /* selected by jobid */
       if (!build_directory_tree(ua, &rx)) {
-         bsendmsg(ua, _("Restore not done.\n"));
+         ua->send_msg(_("Restore not done.\n"));
          goto bail_out;
       }
       break;
@@ -135,26 +185,30 @@ int restore_cmd(UAContext *ua, const char *cmd)
 
    if (rx.bsr->JobId) {
       uint32_t selected_files;
+      char ed1[50];
       if (!complete_bsr(ua, rx.bsr)) {   /* find Vol, SessId, SessTime from JobIds */
-         bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
+         ua->error_msg(_("Unable to construct a valid BSR. Cannot continue.\n"));
          goto bail_out;
       }
       if (!(selected_files = write_bsr_file(ua, rx))) {
-         bsendmsg(ua, _("No files selected to be restored.\n"));
+         ua->warning_msg(_("No files selected to be restored.\n"));
          goto bail_out;
       }
+      display_bsr_info(ua, rx);          /* display vols needed, etc */
+
       /* If no count of files, use bsr generated value (often wrong) */
       if (rx.selected_files == 0) {
          rx.selected_files = selected_files;
       }
       if (rx.selected_files==1) {
-         bsendmsg(ua, _("\n1 file selected to be restored.\n\n"));
+         ua->info_msg(_("\n1 file selected to be restored.\n\n"));
       }
       else {
-         bsendmsg(ua, _("\n%u files selected to be restored.\n\n"), rx.selected_files);
+         ua->info_msg(_("\n%s files selected to be restored.\n\n"), 
+            edit_uint64_with_commas(rx.selected_files, ed1));
       }
    } else {
-      bsendmsg(ua, _("No files selected to be restored.\n"));
+      ua->warning_msg(_("No files selected to be restored.\n"));
       goto bail_out;
    }
 
@@ -169,24 +223,58 @@ int restore_cmd(UAContext *ua, const char *cmd)
 
    get_client_name(ua, &rx);
    if (!rx.ClientName) {
-      bsendmsg(ua, _("No Restore Job resource found!\n"));
+      ua->error_msg(_("No Client resource found!\n"));
       goto bail_out;
    }
+   get_restore_client_name(ua, rx);
+
+   escaped_bsr_name = escape_filename(jcr->RestoreBootstrap);
 
    /* Build run command */
-   if (rx.where) {
+   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=%u 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());
+
+   } else if (rx.where) {
+      escaped_where_name = escape_filename(rx.where);
       Mmsg(ua->cmd,
-          "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s\""
-          " where=\"%s\" files=%d catalog=\"%s\"",
-          job->name(), rx.ClientName, rx.store?rx.store->name():"",
-          jcr->RestoreBootstrap, rx.where, rx.selected_files, ua->catalog->name());
+          "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
+          " bootstrap=\"%s\" where=\"%s\" files=%u 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());
+
    } else {
       Mmsg(ua->cmd,
-          "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s\""
-          " files=%d catalog=\"%s\"",
-          job->name(), rx.ClientName, rx.store?rx.store->name():"",
-          jcr->RestoreBootstrap, rx.selected_files, ua->catalog->name());
+          "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());
+   }
+
+   if (escaped_bsr_name != NULL) {
+      bfree(escaped_bsr_name);
+   }
+
+   if (escaped_where_name != NULL) {
+      bfree(escaped_where_name);
+   }
+   
+   if (regexp) {
+      bfree(regexp);
    }
+
    if (find_arg(ua, NT_("yes")) > 0) {
       pm_strcat(ua->cmd, " yes");    /* pass it on to the run command */
    }
@@ -197,6 +285,18 @@ int restore_cmd(UAContext *ua, const char *cmd)
    return 1;
 
 bail_out:
+   if (escaped_bsr_name != NULL) {
+      bfree(escaped_bsr_name);
+   }
+
+   if (escaped_where_name != NULL) {
+      bfree(escaped_where_name);
+   }
+
+   if (regexp) {
+      bfree(regexp);
+   }
+
    free_rx(&rx);
    return 0;
 
@@ -228,12 +328,15 @@ static void free_rx(RESTORE_CTX *rx)
 static bool has_value(UAContext *ua, int i)
 {
    if (!ua->argv[i]) {
-      bsendmsg(ua, _("Missing value for keyword: %s\n"), ua->argk[i]);
+      ua->error_msg(_("Missing value for keyword: %s\n"), ua->argk[i]);
       return false;
    }
    return true;
 }
 
+/*
+ * This gets the client name from which the backup was made
+ */
 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
 {
    /* If no client name specified yet, get it now */
@@ -241,6 +344,9 @@ static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
       CLIENT_DBR cr;
       /* try command line argument */
       int i = find_arg_with_value(ua, NT_("client"));
+      if (i < 0) {
+         i = find_arg_with_value(ua, NT_("backupclient"));
+      }
       if (i >= 0) {
          if (!has_value(ua, i)) {
             return 0;
@@ -257,6 +363,27 @@ static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
    return 1;
 }
 
+/*
+ * This is where we pick up a client name to restore to.
+ */
+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));    
+
+   /* try command line argument */
+   int i = find_arg_with_value(ua, NT_("restoreclient"));
+   if (i >= 0) {
+      if (!has_value(ua, i)) {
+         return 0;
+      }
+      bstrncpy(rx.RestoreClientName, ua->argv[i], sizeof(rx.RestoreClientName));
+      return 1;
+   }
+   return 1;
+}
+
+
 
 /*
  * The first step in the restore process is for the user to
@@ -272,6 +399,8 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
    char *p;
    char date[MAX_TIME_LENGTH];
    bool have_date = false;
+   /* Include current second if using current time */
+   utime_t now = time(NULL) + 1;
    JobId_t JobId;
    JOB_DBR jr = { (JobId_t)-1 };
    bool done = false;
@@ -293,23 +422,29 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
 
    const char *kw[] = {
        /* These keywords are handled in a for loop */
-      "jobid",     /* 0 */
-      "current",   /* 1 */
-      "before",    /* 2 */
-      "file",      /* 3 */
-      "directory", /* 4 */
-      "select",    /* 5 */
-      "pool",      /* 6 */
-      "all",       /* 7 */
+      "jobid",       /* 0 */
+      "current",     /* 1 */
+      "before",      /* 2 */
+      "file",        /* 3 */
+      "directory",   /* 4 */
+      "select",      /* 5 */
+      "pool",        /* 6 */
+      "all",         /* 7 */
 
       /* The keyword below are handled by individual arg lookups */
-      "client",    /* 8 */
-      "storage",   /* 9 */
-      "fileset",   /* 10 */
-      "where",     /* 11 */
-      "yes",       /* 12 */
-      "bootstrap", /* 13 */
-      "done",      /* 14 */
+      "client",       /* 8 */
+      "storage",      /* 9 */
+      "fileset",      /* 10 */
+      "where",        /* 11 */
+      "yes",          /* 12 */
+      "bootstrap",    /* 13 */
+      "done",         /* 14 */
+      "strip_prefix", /* 15 */
+      "add_prefix",   /* 16 */
+      "add_suffix",   /* 17 */
+      "regexwhere",   /* 18 */
+      "restoreclient", /* 19 */
+      "copies",        /* 20 */
       NULL
    };
 
@@ -324,7 +459,7 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
          }
       }
       if (!found_kw) {
-         bsendmsg(ua, _("Unknown keyword: %s\n"), ua->argk[i]);
+         ua->error_msg(_("Unknown keyword: %s\n"), ua->argk[i]);
          return 0;
       }
       /* Found keyword in kw[] list, process it */
@@ -340,15 +475,20 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
          done = true;
          break;
       case 1:                            /* current */
-         bstrutime(date, sizeof(date), time(NULL));
+         /*
+          * Note, we add one second here just to include any job
+          *  that may have finished within the current second,
+          *  which happens a lot in scripting small jobs.
+          */
+         bstrutime(date, sizeof(date), now);
          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) {
-            bsendmsg(ua, _("Improper date format: %s\n"), ua->argv[i]);
+            ua->error_msg(_("Improper date format: %s\n"), ua->argv[i]);
             return 0;
          }
          bstrncpy(date, ua->argv[i], sizeof(date));
@@ -360,7 +500,7 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
             return 0;
          }
          if (!have_date) {
-            bstrutime(date, sizeof(date), time(NULL));
+            bstrutime(date, sizeof(date), now);
          }
          if (!get_client_name(ua, rx)) {
             return 0;
@@ -370,7 +510,7 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
          return 2;
       case 5:                            /* select */
          if (!have_date) {
-            bstrutime(date, sizeof(date), time(NULL));
+            bstrutime(date, sizeof(date), now);
          }
          if (!select_backups_before_date(ua, rx, date)) {
             return 0;
@@ -383,12 +523,12 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
          }
          rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
          if (!rx->pool) {
-            bsendmsg(ua, _("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
+            ua->error_msg(_("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
             return 0;
          }
          if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
             rx->pool = NULL;
-            bsendmsg(ua, _("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
+            ua->error_msg(_("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
             return 0;
          }
          break;
@@ -404,7 +544,7 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
    }
 
    if (!done) {
-      bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
+      ua->send_msg(_("\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"));
@@ -425,6 +565,10 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
       case -1:                        /* error or cancel */
          return 0;
       case 0:                         /* list last 20 Jobs run */
+         if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
+            ua->error_msg(_("SQL query not authorized.\n"));
+            return 0;
+         }
          gui_save = ua->jcr->gui;
          ua->jcr->gui = true;
          db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
@@ -440,8 +584,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_type], rx->ClientName, fname);
          free(fname);
          gui_save = ua->jcr->gui;
          ua->jcr->gui = true;
@@ -456,6 +600,10 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
          pm_strcpy(rx->JobIds, ua->cmd);
          break;
       case 3:                         /* Enter an SQL list command */
+         if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
+            ua->error_msg(_("SQL query not authorized.\n"));
+            return 0;
+         }
          if (!get_cmd(ua, _("Enter SQL list command: "))) {
             return 0;
          }
@@ -466,25 +614,31 @@ 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), time(NULL));
+         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), time(NULL));
+         if (!have_date) {
+            bstrutime(date, sizeof(date), now);
+         }
          if (!get_client_name(ua, rx)) {
             return 0;
          }
-         bsendmsg(ua, _("Enter file names with paths, or < to enter a filename\n"
+         ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
                         "containing a list of file names with paths, and terminate\n"
                         "them with a blank line.\n"));
          for ( ;; ) {
@@ -499,13 +653,15 @@ 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;
          }
-         bsendmsg(ua, _("Enter file names with paths, or < to enter a filename\n"
+         ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
                         "containing a list of file names with paths, and terminate\n"
                         "them with a blank line.\n"));
          for ( ;; ) {
@@ -521,7 +677,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), time(NULL));
+         if (!have_date) {
+            bstrutime(date, sizeof(date), now);
+         }
          if (!select_backups_before_date(ua, rx, date)) {
             return 0;
          }
@@ -529,8 +687,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;
@@ -540,7 +700,7 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
 
       case 10:                        /* Enter directories */
          if (*rx->JobIds != 0) {
-            bsendmsg(ua, _("You have already seleted the following JobIds: %s\n"),
+            ua->send_msg(_("You have already selected the following JobIds: %s\n"),
                rx->JobIds);
          } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
             if (*rx->JobIds != 0 && *ua->cmd) {
@@ -551,11 +711,13 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
          if (*rx->JobIds == 0 || *rx->JobIds == '.') {
             return 0;                 /* nothing entered, return */
          }
-         bstrutime(date, sizeof(date), time(NULL));
+         if (!have_date) {
+            bstrutime(date, sizeof(date), now);
+         }
          if (!get_client_name(ua, rx)) {
             return 0;
          }
-         bsendmsg(ua, _("Enter full directory names or start the name\n"
+         ua->send_msg(_("Enter full directory names or start the name\n"
                         "with a < to indicate it is a filename containing a list\n"
                         "of directories and terminate them with a blank line.\n"));
          for ( ;; ) {
@@ -567,7 +729,7 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
                break;
             }
             /* Add trailing slash to end of directory names */
-            if (ua->cmd[0] != '<' && ua->cmd[len-1] != '/') {
+            if (ua->cmd[0] != '<' && !IsPathSeparator(ua->cmd[len-1])) {
                strcat(ua->cmd, "/");
             }
             insert_one_file_or_dir(ua, rx, date, true);
@@ -579,23 +741,19 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
       }
    }
 
-   if (*rx->JobIds == 0) {
-      bsendmsg(ua, _("No Jobs selected.\n"));
-      return 0;
-   }
-   if (strchr(rx->JobIds,',')) {
-      bsendmsg(ua, _("You have selected the following JobIds: %s\n"), rx->JobIds);
-   }
-   else {
-      bsendmsg(ua, _("You have selected the following JobId: %s\n"), rx->JobIds);
-   }
-
-
+   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.
+    */
    for (p=rx->JobIds; ; ) {
+      char ed1[50];
       int stat = get_next_jobid_from_list(&p, &JobId);
       if (stat < 0) {
-         bsendmsg(ua, _("Invalid JobId in list.\n"));
+         ua->error_msg(_("Invalid JobId in list.\n"));
+         free_pool_memory(JobIds);
          return 0;
       }
       if (stat == 0) {
@@ -607,18 +765,33 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
       memset(&jr, 0, sizeof(JOB_DBR));
       jr.JobId = JobId;
       if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
-         char ed1[50];
-         bsendmsg(ua, _("Unable to get Job record for JobId=%s: ERR=%s\n"),
+         ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
             edit_int64(JobId, ed1), db_strerror(ua->db));
+         free_pool_memory(JobIds);
          return 0;
       }
       if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
-         bsendmsg(ua, _("No authorization. Job \"%s\" not selected.\n"),
-            jr.Name);
+         ua->error_msg(_("Access to JobId=%s (Job \"%s\") not authorized. Not selected.\n"),
+            edit_int64(JobId, ed1), jr.Name);
          continue;
       }
+      if (*JobIds != 0) {
+         pm_strcat(JobIds, ",");
+      }
+      pm_strcat(JobIds, edit_int64(JobId, ed1));
       rx->TotalFiles += jr.JobFiles;
    }
+   free_pool_memory(rx->JobIds);
+   rx->JobIds = JobIds;               /* Set ACL filtered list */
+   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 {
+      ua->info_msg(_("You have selected the following JobId: %s\n"), rx->JobIds);
+   }
    return 1;
 }
 
@@ -627,7 +800,7 @@ static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
  */
 static int get_date(UAContext *ua, char *date, int date_len)
 {
-   bsendmsg(ua, _("The restored files will the most current backup\n"
+   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 :"))) {
@@ -636,7 +809,7 @@ static int get_date(UAContext *ua, char *date, int date_len)
       if (str_to_utime(ua->cmd) != 0) {
          break;
       }
-      bsendmsg(ua, _("Improper date format.\n"));
+      ua->error_msg(_("Improper date format.\n"));
    }
    bstrncpy(date, ua->cmd, date_len);
    return 1;
@@ -657,19 +830,19 @@ static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, b
       p++;
       if ((ffd = fopen(p, "rb")) == NULL) {
          berrno be;
-         bsendmsg(ua, _("Cannot open file %s: ERR=%s\n"),
-            p, be.strerror());
+         ua->error_msg(_("Cannot open file %s: ERR=%s\n"),
+            p, be.bstrerror());
          break;
       }
       while (fgets(file, sizeof(file), ffd)) {
          line++;
          if (dir) {
             if (!insert_dir_into_findex_list(ua, rx, file, date)) {
-               bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
+               ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
             }
          } else {
             if (!insert_file_into_findex_list(ua, rx, file, date)) {
-               bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
+               ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
             }
          }
       }
@@ -698,7 +871,7 @@ 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, 
            rx->ClientName);
@@ -709,11 +882,12 @@ static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *f
    rx->found = false;
    /* Find and insert jobid and File Index */
    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
-      bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
+      ua->error_msg(_("Query failed: %s. ERR=%s\n"),
          rx->query, db_strerror(ua->db));
    }
    if (!rx->found) {
-      bsendmsg(ua, _("No database record found for: %s\n"), file);
+      ua->error_msg(_("No database record found for: %s\n"), file);
+//    ua->error_msg("Query=%s\n", rx->query);
       return true;
    }
    return true;
@@ -725,23 +899,22 @@ 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) {
-      bsendmsg(ua, _("No JobId specified cannot continue.\n"));
+      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_type], rx->JobIds, dir, rx->ClientName);
    }
    rx->found = false;
    /* Find and insert jobid and File Index */
    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
-      bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
+      ua->error_msg(_("Query failed: %s. ERR=%s\n"),
          rx->query, db_strerror(ua->db));
    }
    if (!rx->found) {
-      bsendmsg(ua, _("No database record found for: %s\n"), dir);
+      ua->error_msg(_("No database record found for: %s\n"), dir);
       return true;
    }
    return true;
@@ -758,17 +931,17 @@ static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *
    rx->found = false;
    /* Find and insert jobid and File Index */
    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
-      bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
+      ua->error_msg(_("Query failed: %s. ERR=%s\n"),
          rx->query, db_strerror(ua->db));
    }
    if (!rx->found) {
-      bsendmsg(ua, _("No table found: %s\n"), table);
+      ua->error_msg(_("No table found: %s\n"), table);
       return true;
    }
    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;
 
@@ -779,11 +952,11 @@ static void split_path_and_filename(RESTORE_CTX *rx, char *name)
     * must be a path name (e.g. c:).
     */
    for (p=f=name; *p; p++) {
-      if (*p == '/') {
+      if (IsPathSeparator(*p)) {
          f = p;                       /* set pos of last slash */
       }
    }
-   if (*f == '/') {                   /* did we find a slash? */
+   if (IsPathSeparator(*f)) {         /* did we find a slash? */
       f++;                            /* yes, point to filename */
    } else {                           /* no, whole thing must be path name */
       f = p;
@@ -796,9 +969,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;
@@ -806,15 +978,47 @@ 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;
    }
 
-   Dmsg2(100, "sllit path=%s file=%s\n", rx->path, rx->fname);
+   Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
+}
+
+static bool ask_for_fileregex(UAContext *ua, RESTORE_CTX *rx)
+{
+   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): "))) {
+      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->bsr->fileregex = bstrdup(ua->cmd);
+               return true;
+            }
+         }
+      }
+   }
+   return false;
 }
 
 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
@@ -837,14 +1041,13 @@ static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
     * 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) {
       /* Use first JobId as estimate of the number of files to restore */
       Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
-      if (!db_sql_query(ua->db, rx->query, count_handler, (void *)rx)) {
-         bsendmsg(ua, "%s\n", db_strerror(ua->db));
+      if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
+         ua->error_msg("%s\n", db_strerror(ua->db));
       }
       if (rx->found) {
          /* Add about 25% more than this job for over estimate */
@@ -852,6 +1055,16 @@ 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, insert_tree_handler, (void *)&tree)) {
+      ua->error_msg("%s", db_strerror(ua->db));
+   }
+#else
    for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
       char ed1[50];
 
@@ -859,53 +1072,34 @@ static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
          continue;                    /* eliminate duplicate JobIds */
       }
       last_JobId = JobId;
-      bsendmsg(ua, _("\nBuilding directory tree for JobId %s ...  "), 
-         edit_int64(JobId, ed1));
-      items++;
       /*
        * Find files for this JobId and insert them in the tree
        */
       Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
       if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
-         bsendmsg(ua, "%s", db_strerror(ua->db));
+         ua->error_msg("%s", db_strerror(ua->db));
       }
    }
+#endif
    if (tree.FileCount == 0) {
-      bsendmsg(ua, _("\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;
+         }
       }
    } else {
       char ec1[50];
-      if (items==1) {
-         if (tree.all) {
-            bsendmsg(ua, _("\n1 Job, %s files inserted into the tree and marked for extraction.\n"),
-              edit_uint64_with_commas(tree.FileCount, ec1));
-         }
-         else {
-            bsendmsg(ua, _("\n1 Job, %s files inserted into the tree.\n"),
-              edit_uint64_with_commas(tree.FileCount, ec1));
-         }
-      }
-      else {
-         if (tree.all) {
-            bsendmsg(ua, _("\n%d Jobs, %s files inserted into the tree and marked for extraction.\n"),
-              items, edit_uint64_with_commas(tree.FileCount, ec1));
-         }
-         else {
-            bsendmsg(ua, _("\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) {
@@ -945,20 +1139,20 @@ static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *dat
    bool ok = false;
    FILESET_DBR fsr;
    CLIENT_DBR cr;
+   POOL_MEM other_filter(PM_MESSAGE);
+   POOL_MEM temp_filter(PM_MESSAGE);
    char fileset_name[MAX_NAME_LENGTH];
    char ed1[50], ed2[50];
-   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)) {
-      bsendmsg(ua, "%s\n", db_strerror(ua->db));
+   if (!db_sql_query(ua->db, uar_create_temp[db_type], NULL, NULL)) {
+      ua->error_msg("%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 (!db_sql_query(ua->db, uar_create_temp1[db_type], NULL, NULL)) {
+      ua->error_msg("%s\n", db_strerror(ua->db));
    }
    /*
     * Select Client from the Catalog
@@ -977,7 +1171,7 @@ static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *dat
    if (i >= 0) {
       bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
-         bsendmsg(ua, _("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
+         ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
             db_strerror(ua->db));
          i = -1;
       }
@@ -987,97 +1181,112 @@ static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *dat
       Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
       start_prompt(ua, _("The defined FileSet resources are:\n"));
       if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
-         bsendmsg(ua, "%s\n", db_strerror(ua->db));
+         ua->error_msg("%s\n", db_strerror(ua->db));
       }
       if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
                  fileset_name, sizeof(fileset_name)) < 0) {
+         ua->error_msg(_("No FileSet found for client \"%s\".\n"), cr.Name);
          goto bail_out;
       }
 
       bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
       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"
+         ua->warning_msg(_("Error getting FileSet record: %s\n"), db_strerror(ua->db));
+         ua->send_msg(_("This probably means you modified the FileSet.\n"
                      "Continuing anyway.\n"));
       }
    }
 
    /* If Pool specified, add PoolId specification */
-   pool_select[0] = 0;
    if (rx->pool) {
       POOL_DBR pr;
       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 ", 
-            edit_int64(pr.PoolId, ed1));
+         Mmsg(other_filter, " AND Media.PoolId=%s ", 
+              edit_int64(pr.PoolId, ed1));
       } else {
-         bsendmsg(ua, _("Pool \"%s\" not found, using any pool.\n"), pr.Name);
+         ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
       }
    }
+   /* include copies or not in job selection */
+   if (find_arg(ua, NT_("copies")) > 0) {
+      Mmsg(temp_filter, "%s AND Job.Type IN ('%c', '%c') ", 
+           other_filter.c_str(), (char)JT_BACKUP, (char)JT_JOB_COPY);
+   } else {
+      Mmsg(temp_filter, "%s AND Job.Type = '%c' ", other_filter.c_str(),
+           (char)JT_BACKUP);
+   }
+   pm_strcpy(other_filter, temp_filter.c_str());
 
    /* Find JobId of last Full backup for this client, fileset */
    edit_int64(cr.ClientId, ed1);
    Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
-         pool_select);
+        other_filter.c_str());
+   Dmsg1(0, "sql=%s\n", rx->query);
    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
-      bsendmsg(ua, "%s\n", db_strerror(ua->db));
+      ua->error_msg("%s\n", db_strerror(ua->db));
       goto bail_out;
    }
 
    /* 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));
+      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.
     */
    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));
+      ua->warning_msg("%s\n", db_strerror(ua->db));
    }
    if (rx->JobTDate == 0) {
-      bsendmsg(ua, _("No Full backup before %s found.\n"), date);
+      ua->error_msg(_("No Full backup before %s found.\n"), date);
       goto bail_out;
    }
 
    /* Now find most recent Differental Job after Full save, if any */
    Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
-        edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
+        edit_int64(cr.ClientId, ed2), fsr.FileSet, other_filter.c_str());
    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
-      bsendmsg(ua, "%s\n", db_strerror(ua->db));
+      ua->warning_msg("%s\n", db_strerror(ua->db));
    }
    /* Now update JobTDate to lock onto Differental, if any */
    rx->JobTDate = 0;
+   Dmsg1(0, "sql=%s\n", rx->query);
    if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
-      bsendmsg(ua, "%s\n", db_strerror(ua->db));
+      ua->warning_msg("%s\n", db_strerror(ua->db));
    }
    if (rx->JobTDate == 0) {
-      bsendmsg(ua, _("No Full backup before %s found.\n"), date);
+      ua->error_msg(_("No Full backup before %s found.\n"), date);
       goto bail_out;
    }
 
    /* Now find all Incremental Jobs after Full/dif save */
    Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
-        edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
+        edit_int64(cr.ClientId, ed2), fsr.FileSet, other_filter.c_str());
+   Dmsg1(0, "sql=%s\n", rx->query);
    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
-      bsendmsg(ua, "%s\n", db_strerror(ua->db));
+      ua->warning_msg("%s\n", db_strerror(ua->db));
    }
 
    /* 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));
+      ua->warning_msg("%s\n", db_strerror(ua->db));
    }
 
    if (rx->JobIds[0] != 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);
       ok = true;
    } else {
-      bsendmsg(ua, _("No jobs found.\n"));
+      ua->warning_msg(_("No jobs found.\n"));
    }
 
 bail_out:
@@ -1097,11 +1306,12 @@ bail_out:
  */
 int get_next_jobid_from_list(char **p, JobId_t *JobId)
 {
-   char jobid[30];
+   const int maxlen = 30;
+   char jobid[maxlen+1];
    char *q = *p;
 
    jobid[0] = 0;
-   for (int i=0; i<(int)sizeof(jobid); i++) {
+   for (int i=0; i<maxlen; i++) {
       if (*q == 0) {
          break;
       } else if (*q == ',') {
@@ -1121,7 +1331,7 @@ int get_next_jobid_from_list(char **p, JobId_t *JobId)
    return 1;
 }
 
-static int count_handler(void *ctx, int num_fields, char **row)
+static int restore_count_handler(void *ctx, int num_fields, char **row)
 {
    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
    rx->JobId = str_to_int64(row[0]);
@@ -1136,6 +1346,8 @@ static int 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;
+
+   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]));
    rx->found = true;
@@ -1234,7 +1446,7 @@ void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *
          }
       }
       if (store && (store != rx.store)) {
-         bsendmsg(ua, _("Warning default storage overridden by \"%s\" on command line.\n"),
+         ua->info_msg(_("Warning default storage overridden by \"%s\" on command line.\n"),
             store->name());
          rx.store = store;
          Dmsg1(200, "Set store=%s\n", rx.store->name());
@@ -1250,7 +1462,7 @@ 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());
-               bsendmsg(ua, _("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
+               ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
                   Storage, store->name(), MediaType);
             }
             UnlockRes();
@@ -1258,12 +1470,14 @@ void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *
          }
       }
       UnlockRes();
-      bsendmsg(ua, _("\nUnable to find Storage resource for\n"
+      ua->warning_msg(_("\nUnable to find Storage resource for\n"
          "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
    }
 
    /* 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());
+   }
 
 }