]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/dird/ua_restore.c
Cleanup help usage
[bacula/bacula] / bacula / src / dird / ua_restore.c
index f5c4595adf83f6742e5198ce0ec3cfcce2ecf340..e7b18b15b7f79622df38616b2ae5df6a56c98cb2 100644 (file)
@@ -1,14 +1,14 @@
 /*
    Bacula® - The Network Backup Solution
 
-   Copyright (C) 2002-2007 Free Software Foundation Europe e.V.
+   Copyright (C) 2002-2009 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.
+   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
@@ -20,7 +20,7 @@
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301, USA.
 
-   Bacula® is a registered trademark of John Walker.
+   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.
@@ -44,7 +44,6 @@
 #include "bacula.h"
 #include "dird.h"
 
-
 /* Imported functions */
 extern void print_bsr(UAContext *ua, RBSR *bsr);
 
@@ -59,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);
@@ -67,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);
 
 /*
@@ -83,6 +83,8 @@ int restore_cmd(UAContext *ua, const char *cmd)
    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);
@@ -94,6 +96,46 @@ int restore_cmd(UAContext *ua, const char *cmd)
    i = find_arg_with_value(ua, "where");
    if (i >= 0) {
       rx.where = ua->argv[i];
+   }
+
+   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;
@@ -152,6 +194,8 @@ int restore_cmd(UAContext *ua, const char *cmd)
          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;
@@ -182,29 +226,39 @@ int restore_cmd(UAContext *ua, const char *cmd)
       ua->error_msg(_("No Client resource found!\n"));
       goto bail_out;
    }
+   get_restore_client_name(ua, rx);
 
    escaped_bsr_name = escape_filename(jcr->RestoreBootstrap);
-   escaped_where_name = escape_filename(rx.where);
 
    /* Build run command */
-   if (rx.where) {
-      if (!acl_access_ok(ua, Where_ACL, rx.where)) {
-         ua->error_msg(_("\"where\" specification not authorized.\n"));
-         goto bail_out;
-      }
+   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():"",
+          "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():"",
+          "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());
    }
@@ -216,6 +270,10 @@ int restore_cmd(UAContext *ua, const char *cmd)
    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 */
@@ -235,6 +293,10 @@ bail_out:
       bfree(escaped_where_name);
    }
 
+   if (regexp) {
+      bfree(regexp);
+   }
+
    free_rx(&rx);
    return 0;
 
@@ -272,6 +334,9 @@ static bool has_value(UAContext *ua, int i)
    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 */
@@ -279,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;
@@ -295,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
@@ -310,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;
@@ -326,28 +417,35 @@ 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 };
 
    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
    };
 
@@ -378,11 +476,16 @@ 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) {
@@ -398,7 +501,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;
@@ -408,7 +511,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;
@@ -453,6 +556,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++) {
@@ -482,8 +586,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;
@@ -512,21 +616,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), 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;
          }
@@ -545,8 +655,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;
@@ -567,7 +679,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;
          }
@@ -575,8 +689,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;
@@ -597,7 +713,9 @@ 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;
          }
@@ -620,11 +738,35 @@ 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_accurate_get_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;
@@ -715,7 +857,7 @@ static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, b
       if ((ffd = fopen(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)) {
@@ -755,7 +897,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);
@@ -771,6 +913,7 @@ static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *f
    }
    if (!rx->found) {
       ua->error_msg(_("No database record found for: %s\n"), file);
+//    ua->error_msg("Query=%s\n", rx->query);
       return true;
    }
    return true;
@@ -782,14 +925,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, 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 */
@@ -825,7 +967,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;
 
@@ -853,9 +995,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;
@@ -863,9 +1004,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;
@@ -874,6 +1014,44 @@ 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 ask_for_fileregex(UAContext *ua, RESTORE_CTX *rx)
+{
+   if (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 (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)
 {
    TREE_CTX tree;
@@ -894,13 +1072,12 @@ 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)) {
+      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) {
@@ -909,6 +1086,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];
 
@@ -916,9 +1103,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
        */
@@ -927,42 +1111,42 @@ static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
          ua->error_msg("%s", db_strerror(ua->db));
       }
    }
+#endif
+   /*
+    * 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;
+         }
       }
    } 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) {
@@ -1007,14 +1191,13 @@ 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)) {
+   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)) {
+   if (!db_sql_query(ua->db, uar_create_temp1[db_type], NULL, NULL)) {
       ua->error_msg("%s\n", db_strerror(ua->db));
    }
    /*
@@ -1088,6 +1271,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.
     */
@@ -1106,7 +1290,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));
@@ -1131,6 +1315,11 @@ 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, 
+                                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;
@@ -1144,42 +1333,7 @@ bail_out:
    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 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]);
@@ -1194,6 +1348,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;
@@ -1322,6 +1478,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());
+   }
 
 }