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