]> git.sur5r.net Git - bacula/bacula/commitdiff
Separate tree and bsr code from ua_restore.c
authorKern Sibbald <kern@sibbald.com>
Mon, 2 Jun 2003 14:26:38 +0000 (14:26 +0000)
committerKern Sibbald <kern@sibbald.com>
Mon, 2 Jun 2003 14:26:38 +0000 (14:26 +0000)
git-svn-id: https://bacula.svn.sourceforge.net/svnroot/bacula/trunk@560 91ce42f0-d328-0410-95d8-f526ca767f89

12 files changed:
bacula/kernstodo
bacula/src/cats/sql_find.c
bacula/src/dird/Makefile.in
bacula/src/dird/bsr.c [new file with mode: 0644]
bacula/src/dird/bsr.h [new file with mode: 0644]
bacula/src/dird/dird.h
bacula/src/dird/job.c
bacula/src/dird/protos.h
bacula/src/dird/ua.h
bacula/src/dird/ua_restore.c
bacula/src/dird/ua_tree.c [new file with mode: 0644]
bacula/src/version.h

index 332e04e12a5f52edc2f75c5be0d908fc380aeec7..79bfbfcc7b847ec22ca3b279d06f511baacdf778 100644 (file)
@@ -32,6 +32,10 @@ Testing to do: (painful)
 For 1.31 release:
 - Implement FileSet VolIndex.
 - Sort JobIds entered into recover tree.
+- The bsr for Dan's job has file indexes covering the whole range rather
+  than only the range contained on the volume.
+  Constrain FileIndex to be within range for Volume.
+- Fix Verify VolumeToCatalog to use BSRs -- it is broken.
 - Should Bacula make an Append tape as Purged when purging?
 - Test a second language e.g. french.
 - Start working on Base jobs.
@@ -40,10 +44,6 @@ For 1.31 release:
 - Unsaved Flag in Job record.
 - Base Flag in Job record.
 - Implement UnsavedFiles DB record.
-- The bsr for Dan's job has file indexes covering the whole range rather
-  than only the range contained on the volume.
-  Constrain FileIndex to be within range for Volume.
-- Fix Verify VolumeToCatalog to use BSRs -- it is broken.
 - Use switch() in backup.c and restore.c in FD instead of giant if statement.
 - Implement argc/argv for daemon command line scanning using table driven
   stuff below.
@@ -915,4 +915,3 @@ Done: (see kernsdone for more)
 - Bytes restored is wrong.
 - The "List last 20 Jobs run" doesnt work correctly in restore.
   It doesnt show the last 20 jobs , but some older ones.
-
index 8ed28dd9b6b47aa24855af556a3f4e34571c650a..0dab06bccee983d987b0145316f091c0662ee274 100644 (file)
@@ -152,14 +152,14 @@ db_find_last_jobid(JCR *jcr, B_DB *mdb, JOB_DBR *jr)
    db_lock(mdb);
    if (jr->Level == L_VERIFY_CATALOG) {
       Mmsg(&mdb->cmd, 
-"SELECT JobId FROM Job WHERE Type='%c' AND Level='%c' AND Name='%s' AND "
+"SELECT JobId FROM Job WHERE Type='V' AND Level='%c' AND Name='%s' AND "
 "ClientId=%u ORDER BY StartTime DESC LIMIT 1",
-          JT_VERIFY, L_VERIFY_INIT, jr->Name, jr->ClientId);
+          L_VERIFY_INIT, jr->Name, jr->ClientId);
    } else if (jr->Level == L_VERIFY_VOLUME_TO_CATALOG) {
       Mmsg(&mdb->cmd, 
-"SELECT JobId FROM Job WHERE Type='%c' AND "
-"ClientId=%u ORDER BY StartTime DESC LIMIT 1",
-          JT_BACKUP, jr->ClientId);
+"SELECT JobId FROM Job WHERE Type='B' AND "
+"ClientId=%u ORDER BY StartTime DESC LIMIT 1", 
+          jr->ClientId);
    } else {
       Mmsg1(&mdb->errmsg, _("Unknown Job level=%c\n"), jr->Level);
       db_unlock(mdb);
index bb8a01711a635abfd0e1dc5e7517f4f5c0f138ce..313a192358fe28dae4c26a1306ce635b6203b349 100644 (file)
@@ -23,7 +23,7 @@ dummy:
 
 #
 SVRSRCS = dird.c admin.c authenticate.c \
-         autoprune.c backup.c \
+         autoprune.c backup.c bsr.c \
          catreq.c dird_conf.c \
          fd_cmds.c getmsg.c inc_conf.c job.c \
          mountreq.c msgchan.c newvol.c \
@@ -34,9 +34,9 @@ SVRSRCS = dird.c admin.c authenticate.c \
          ua_input.c ua_label.c ua_output.c ua_prune.c \
          ua_purge.c ua_restore.c ua_run.c \
          ua_select.c ua_server.c \
-         ua_status.c verify.c
+         ua_status.c ua_tree.c verify.c
 SVROBJS = dird.o admin.o authenticate.o \
-         autoprune.o backup.o \
+         autoprune.o backup.o bsr.o \
          catreq.o dird_conf.o \
          fd_cmds.o getmsg.o inc_conf.o job.o \
          mountreq.o msgchan.o newvol.o \
@@ -47,7 +47,7 @@ SVROBJS = dird.o admin.o authenticate.o \
          ua_input.o ua_label.o ua_output.o ua_prune.o \
          ua_purge.o ua_restore.o ua_run.o \
          ua_select.o ua_server.o \
-         ua_status.o verify.o
+         ua_status.o ua_tree.o verify.o
 
 # these are the objects that are changed by the .configure process
 EXTRAOBJS = @OBJLIST@
diff --git a/bacula/src/dird/bsr.c b/bacula/src/dird/bsr.c
new file mode 100644 (file)
index 0000000..38aa67d
--- /dev/null
@@ -0,0 +1,321 @@
+/*
+ *
+ *   Bacula Director -- Bootstrap Record routines.
+ *
+ *     BSR (bootstrap record) handling routines split from 
+ *       ua_restore.c July MMIII
+ *
+ *     Kern Sibbald, July MMII
+ *
+ *   Version $Id$
+ */
+
+/*
+   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
+   published by the Free Software Foundation; either version 2 of
+   the License, or (at your option) any later version.
+
+   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., 59 Temple Place - Suite 330, Boston,
+   MA 02111-1307, USA.
+
+ */
+
+#include "bacula.h"
+#include "dird.h"
+
+/* Forward referenced functions */
+static RBSR *sort_bsr(RBSR *bsr);
+static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd);
+
+
+/*
+ * Create new FileIndex entry for BSR 
+ */
+static RBSR_FINDEX *new_findex() 
+{
+   RBSR_FINDEX *fi = (RBSR_FINDEX *)bmalloc(sizeof(RBSR_FINDEX));
+   memset(fi, 0, sizeof(RBSR_FINDEX));
+   return fi;
+}
+
+/* Free all BSR FileIndex entries */
+static void free_findex(RBSR_FINDEX *fi)
+{
+   if (fi) {
+      free_findex(fi->next);
+      free(fi);
+   }
+}
+
+static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd) 
+{
+   if (fi) {
+      if (fi->findex == fi->findex2) {
+         fprintf(fd, "FileIndex=%d\n", fi->findex);
+      } else {
+         fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
+      }
+      write_findex(ua, fi->next, fd);
+   }
+}
+
+
+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);
+      }
+      print_findex(ua, fi->next);
+   }
+}
+
+/* Create a new bootstrap record */
+RBSR *new_bsr()
+{
+   RBSR *bsr = (RBSR *)bmalloc(sizeof(RBSR));
+   memset(bsr, 0, sizeof(RBSR));
+   return bsr;
+}
+
+/* Free the entire BSR */
+void free_bsr(RBSR *bsr)
+{
+   if (bsr) {
+      free_findex(bsr->fi);
+      free_bsr(bsr->next);
+      if (bsr->VolParams) {
+        free(bsr->VolParams);
+      }
+      free(bsr);
+   }
+}
+
+/*
+ * Complete the BSR by filling in the VolumeName and
+ *  VolSessionId and VolSessionTime using the JobId
+ */
+int complete_bsr(UAContext *ua, RBSR *bsr)
+{
+   JOB_DBR jr;
+
+   if (bsr) {
+      memset(&jr, 0, sizeof(jr));
+      jr.JobId = bsr->JobId;
+      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;
+      }
+      bsr->VolSessionId = jr.VolSessionId;
+      bsr->VolSessionTime = jr.VolSessionTime;
+      if ((bsr->VolCount=db_get_job_volume_parameters(ua->jcr, ua->db, bsr->JobId, 
+          &(bsr->VolParams))) == 0) {
+         bsendmsg(ua, _("Unable to get Job Volume Parameters. ERR=%s\n"), db_strerror(ua->db));
+        if (bsr->VolParams) {
+           free(bsr->VolParams);
+           bsr->VolParams = NULL;
+        }
+        return 0;
+      }
+      return complete_bsr(ua, bsr->next);
+   }
+   return 1;
+}
+
+/*
+ * Write the bootstrap record to file
+ */
+int write_bsr_file(UAContext *ua, RBSR *bsr)
+{
+   FILE *fd;
+   POOLMEM *fname = get_pool_memory(PM_MESSAGE);
+   int stat;
+
+   Mmsg(&fname, "%s/restore.bsr", working_directory);
+   fd = fopen(fname, "w+");
+   if (!fd) {
+      bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"), 
+        fname, strerror(errno));
+      free_pool_memory(fname);
+      return 0;
+   }
+   /* Sort the bsr chain */
+   bsr = sort_bsr(bsr);
+   /* Write them to file */
+   write_bsr(ua, bsr, fd);
+   stat = !ferror(fd);
+   fclose(fd);
+   bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
+
+   /* Tell the user what he will need to mount */
+   bsendmsg(ua, _("\nThe restore job will require the following Volumes:\n"));
+   /* Create Unique list of Volumes using prompt list */
+   start_prompt(ua, "");
+   for (RBSR *nbsr=bsr; nbsr; nbsr=nbsr->next) {
+      for (int i=0; i < nbsr->VolCount; i++) {
+        add_prompt(ua, nbsr->VolParams[i].VolumeName);
+      }
+   }
+   for (int i=0; i < ua->num_prompts; i++) {
+      bsendmsg(ua, "   %s\n", ua->prompt[i]);
+      free(ua->prompt[i]);
+   }
+   ua->num_prompts = 0;
+   bsendmsg(ua, "\n");
+   free_pool_memory(fname);
+   return stat;
+}
+
+/*
+ * First sort the bsr chain, then sort the VolParams   
+ */
+static RBSR *sort_bsr(RBSR *bsr)
+{
+   if (!bsr) {
+      return bsr;
+   }
+   /* ****FIXME**** sort the bsr chain */
+   for (RBSR *nbsr=bsr; nbsr; nbsr=nbsr->next) {
+   }
+   return bsr;
+}
+
+static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
+{
+   if (bsr) {
+      for (int i=0; i < bsr->VolCount; i++) {
+         fprintf(fd, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
+         fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
+         fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
+         fprintf(fd, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile, 
+                bsr->VolParams[i].EndFile);
+         fprintf(fd, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
+                bsr->VolParams[i].EndBlock);
+        write_findex(ua, bsr->fi, fd);
+      }
+      write_bsr(ua, bsr->next, fd);
+   }
+}
+
+static void print_bsr(UAContext *ua, RBSR *bsr)
+{
+   if (bsr) {
+      for (int i=0; i < bsr->VolCount; i++) {
+         bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
+         bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
+         bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
+         bsendmsg(ua, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile, 
+                 bsr->VolParams[i].EndFile);
+         bsendmsg(ua, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
+                 bsr->VolParams[i].EndBlock);
+        print_findex(ua, bsr->fi);
+      }
+      print_bsr(ua, bsr->next);
+   }
+}
+
+
+/*
+ * Add a FileIndex to the list of BootStrap records.
+ *  Here we are only dealing with JobId's and the FileIndexes
+ *  associated with those JobIds.
+ */
+void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
+{
+   RBSR *nbsr;
+   RBSR_FINDEX *fi, *lfi;
+
+   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;
+   }
+   /* 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;
+      }
+   }
+
+   /* 
+    * At this point, bsr points to bsr containing JobId,
+    *  and we are sure that there is at least one fi record.
+    */
+   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;
+   }
+   /* 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)) { 
+           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;
+}
diff --git a/bacula/src/dird/bsr.h b/bacula/src/dird/bsr.h
new file mode 100644 (file)
index 0000000..f133d3e
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ *
+ *   Bootstrap Record header file
+ *
+ *     BSR (bootstrap record) handling routines split from 
+ *       ua_restore.c July MMIII
+ *
+ *     Kern Sibbald, July MMII
+ *
+ *   Version $Id$
+ */
+
+/*
+   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
+   published by the Free Software Foundation; either version 2 of
+   the License, or (at your option) any later version.
+
+   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., 59 Temple Place - Suite 330, Boston,
+   MA 02111-1307, USA.
+
+ */
+
+
+/* FileIndex entry in restore bootstrap record */
+struct RBSR_FINDEX {
+   RBSR_FINDEX *next;
+   int32_t findex;
+   int32_t findex2;
+};
+
+/* 
+ * Restore bootstrap record -- not the real one, but useful here   
+ *  The restore bsr is a chain of BSR records (linked by next).
+ *  Each BSR represents a single JobId, and within it, it
+ *    contains a linked list of file indexes for that JobId.
+ *    The complete_bsr() routine, will then add all the volumes
+ *    on which the Job is stored to the BSR.
+ */
+struct RBSR {
+   RBSR *next;                       /* next JobId */
+   uint32_t JobId;                   /* JobId this bsr */
+   uint32_t VolSessionId;                  
+   uint32_t VolSessionTime;
+   int     VolCount;                 /* Volume parameter count */
+   VOL_PARAMS *VolParams;            /* Volume, start/end file/blocks */
+   RBSR_FINDEX *fi;                  /* File indexes this JobId */
+};
index efc225b689fb83d10d4730ed91e31f1bc4a9ee65..ec6f1ebb683aa19ee98ddc64551a04a7a96e55e2 100644 (file)
@@ -39,6 +39,7 @@
 
 #include "jcr.h"
 
+#include "bsr.h"
 #include "ua.h"
 #include "protos.h"
 
index c6172103caf0948fd83307c90abcdf3e97f48014..23f2b98a4c5d157495114743f1ae7bc84dd8750f 100644 (file)
@@ -462,7 +462,7 @@ int get_or_create_client_record(JCR *jcr)
         free_pool_memory(jcr->client_uname);
       }
       jcr->client_uname = get_memory(strlen(cr.Uname) + 1);
-      strcpy(jcr->client_uname, cr.Uname);
+      pm_strcpy(&jcr->client_uname, cr.Uname);
    }
    Dmsg2(100, "Created Client %s record %d\n", jcr->client->hdr.name, 
       jcr->jr.ClientId);
index 0bc68b99397a848ec8cc32262ba26b214287b912..f65d8c8671adcc0178a825323c9ef0fa8ce170b1 100644 (file)
@@ -41,6 +41,14 @@ extern int find_recycled_volume(JCR *jcr, MEDIA_DBR *mr);
 /* backup.c */
 extern int wait_for_job_termination(JCR *jcr);
 
+/* bsr.c */
+RBSR *new_bsr();
+void free_bsr(RBSR *bsr);
+int complete_bsr(UAContext *ua, RBSR *bsr);
+int write_bsr_file(UAContext *ua, RBSR *bsr);
+void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex);
+
+
 /* catreq.c */
 extern void catalog_request(JCR *jcr, BSOCK *bs, char *buf);
 extern void catalog_update(JCR *jcr, BSOCK *bs, char *buf);
@@ -143,6 +151,10 @@ int find_arg_with_value(UAContext *ua, char *keyword);
 int do_keyword_prompt(UAContext *ua, char *msg, char **list);
 int confirm_retention(UAContext *ua, utime_t *ret, char *msg);
 
+/* ua_tree.c */
+void user_select_files_from_tree(TREE_CTX *tree);
+int insert_tree_handler(void *ctx, int num_fields, char **row);
+
 /* ua_prune.c */
 int prune_files(UAContext *ua, CLIENT *client);
 int prune_jobs(UAContext *ua, CLIENT *client, int JobType);
index f46992802be9aeb8a90879bebb9ec230d59050cc..d63e949f90ade1c64a1ef9ecd917755ff853b71c 100644 (file)
@@ -35,21 +35,31 @@ struct UAContext {
    JCR *jcr;
    B_DB *db;
    CAT *catalog;
-   POOLMEM *cmd;                      /* return command/name buffer */
-   POOLMEM *args;                     /* command line arguments */
-   char *argk[MAX_CMD_ARGS];          /* argument keywords */
-   char *argv[MAX_CMD_ARGS];          /* argument values */
-   int argc;                          /* number of arguments */
-   char **prompt;                     /* list of prompts */
-   int max_prompts;                   /* max size of list */
-   int num_prompts;                   /* current number in list */
-   int auto_display_messages;         /* if set, display messages */
+   POOLMEM *cmd;                     /* return command/name buffer */
+   POOLMEM *args;                    /* command line arguments */
+   char *argk[MAX_CMD_ARGS];         /* argument keywords */
+   char *argv[MAX_CMD_ARGS];         /* argument values */
+   int argc;                         /* number of arguments */
+   char **prompt;                    /* list of prompts */
+   int max_prompts;                  /* max size of list */
+   int num_prompts;                  /* current number in list */
+   int auto_display_messages;        /* if set, display messages */
    int user_notified_msg_pending;     /* set when user notified */
-   int automount;                     /* if set, mount after label */
-   int quit;                          /* if set, quit */
-   int verbose;                       /* set for normal UA verbosity */
-   uint32_t pint32_val;               /* positive integer */
-   int32_t  int32_val;                /* positive/negative */
-};          
+   int automount;                    /* if set, mount after label */
+   int quit;                         /* if set, quit */
+   int verbose;                      /* set for normal UA verbosity */
+   uint32_t pint32_val;              /* positive integer */
+   int32_t  int32_val;               /* positive/negative */
+};         
+
+/* Context for insert_tree_handler() */
+struct 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;
+};
+
 
 #endif
index 40428f5512e403a7cc500cae4f528f7f3c6d97a7..7fae1160ab95da16c44a46ffe3fb679ce498a469 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
  *
@@ -30,9 +35,6 @@
 
 #include "bacula.h"
 #include "dird.h"
-#include <fnmatch.h>
-#include "findlib/find.h"
-
 
 
 /* Imported functions */
@@ -46,15 +48,6 @@ extern char *uar_inc,                *uar_list_temp,   *uar_sel_jobid_temp;
 extern char *uar_sel_all_temp1, *uar_sel_fileset, *uar_mediatype;
 
 
-/* Context for insert_tree_handler() */
-struct 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;
-};
-
 /* Main structure for obtaining JobIds */
 struct JOBIDS {
    utime_t JobTDate;
@@ -64,32 +57,6 @@ struct JOBIDS {
    STORE  *store;
 };
 
-
-/* FileIndex entry in restore bootstrap record */
-struct RBSR_FINDEX {
-   RBSR_FINDEX *next;
-   int32_t findex;
-   int32_t findex2;
-};
-
-/* 
- * Restore bootstrap record -- not the real one, but useful here   
- *  The restore bsr is a chain of BSR records (linked by next).
- *  Each BSR represents a single JobId, and within it, it
- *    contains a linked list of file indexes for that JobId.
- *    The complete_bsr() routine, will then add all the volumes
- *    on which the Job is stored to the BSR.
- */
-struct RBSR {
-   RBSR *next;                       /* next JobId */
-   uint32_t JobId;                   /* JobId this bsr */
-   uint32_t VolSessionId;                  
-   uint32_t VolSessionTime;
-   int     VolCount;                 /* Volume parameter count */
-   VOL_PARAMS *VolParams;            /* Volume, start/end file/blocks */
-   RBSR_FINDEX *fi;                  /* File indexes this JobId */
-};
-
 struct NAME_LIST {
    char **name;                      /* list of names */
    int num_ids;                      /* ids stored */
@@ -102,25 +69,15 @@ struct NAME_LIST {
 
 
 /* Forward referenced functions */
-static RBSR *new_bsr();
-static void free_bsr(RBSR *bsr);
-static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd);
-static int  write_bsr_file(UAContext *ua, 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, JOBIDS *ji);
-static void user_select_files(TREE_CTX *tree);
 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, JOBIDS *ji);
-static RBSR *sort_bsr(RBSR *bsr);
 
 
 /*
@@ -220,7 +177,8 @@ int restorecmd(UAContext *ua, char *cmd)
       }
 
    }
-   bsendmsg(ua, "%d items inserted into the tree and marked for extraction.\n", items);
+   bsendmsg(ua, "%d item%s inserted into the tree and marked for extraction.\n", 
+      items, items==1?"":"s");
    free_pool_memory(query);
 
    /* Check MediaType and select storage that corresponds */
@@ -228,7 +186,7 @@ int restorecmd(UAContext *ua, char *cmd)
    free_name_list(&name_list);
 
    /* Let the user select which files to restore */
-   user_select_files(&tree);
+   user_select_files_from_tree(&tree);
 
    /*
     * Walk down through the tree finding all files marked to be 
@@ -544,684 +502,11 @@ static int fileset_handler(void *ctx, int num_fields, char **row)
    return 0;
 }
 
-/* Forward referenced commands */
-
-static int markcmd(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 dircmd(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 unmarkcmd(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_("mark"),       markcmd,      _("mark file for restoration")},
- { N_("unmark"),     unmarkcmd,    _("unmark file for restoration")},
- { N_("cd"),         cdcmd,        _("change current directory")},
- { N_("pwd"),        pwdcmd,       _("print current working directory")},
- { N_("ls"),         lscmd,        _("list current directory")},    
- { N_("dir"),        dircmd,       _("list current directory")},    
- { N_("count"),      countcmd,     _("count marked files")},
- { N_("find"),       findcmd,      _("find files")},
- { N_("done"),       quitcmd,      _("leave file selection mode")},
- { N_("exit"),       quitcmd,      _("exit = done")},
- { N_("help"),       helpcmd,      _("print help")},
- { 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", ...
- */
-static void user_select_files(TREE_CTX *tree)
-{
-   char cwd[2000];
-
-   bsendmsg(tree->ua, _( 
-      "\nYou are now entering file selection mode where you add and\n"
-      "remove files to be restored. All files are initially added.\n"
-      "Enter \"done\" to leave this mode.\n\n"));
-   /*
-    * Enter interactive command handler allowing selection
-    *  of individual files.
-    */
-   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_ua_args(tree->ua);
-      if (tree->ua->argc == 0) {
-        return;
-      }
-
-      len = strlen(tree->ua->argk[0]);
-      found = 0;
-      stat = 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 exit.\n"));
-        continue;
-      }
-      if (!stat) {
-        break;
-      }
-   }
-}
-
-/*
- * Create new FileIndex entry for BSR 
- */
-static RBSR_FINDEX *new_findex() 
-{
-   RBSR_FINDEX *fi = (RBSR_FINDEX *)bmalloc(sizeof(RBSR_FINDEX));
-   memset(fi, 0, sizeof(RBSR_FINDEX));
-   return fi;
-}
-
-/* Free all BSR FileIndex entries */
-static void free_findex(RBSR_FINDEX *fi)
-{
-   if (fi) {
-      free_findex(fi->next);
-      free(fi);
-   }
-}
-
-static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd) 
-{
-   if (fi) {
-      if (fi->findex == fi->findex2) {
-         fprintf(fd, "FileIndex=%d\n", fi->findex);
-      } else {
-         fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
-      }
-      write_findex(ua, fi->next, fd);
-   }
-}
-
-
-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);
-      }
-      print_findex(ua, fi->next);
-   }
-}
-
-/* Create a new bootstrap record */
-static RBSR *new_bsr()
-{
-   RBSR *bsr = (RBSR *)bmalloc(sizeof(RBSR));
-   memset(bsr, 0, sizeof(RBSR));
-   return bsr;
-}
-
-/* Free the entire BSR */
-static void free_bsr(RBSR *bsr)
-{
-   if (bsr) {
-      free_findex(bsr->fi);
-      free_bsr(bsr->next);
-      if (bsr->VolParams) {
-        free(bsr->VolParams);
-      }
-      free(bsr);
-   }
-}
-
-/*
- * Complete the BSR by filling in the VolumeName and
- *  VolSessionId and VolSessionTime using the JobId
- */
-static int complete_bsr(UAContext *ua, RBSR *bsr)
-{
-   JOB_DBR jr;
-
-   if (bsr) {
-      memset(&jr, 0, sizeof(jr));
-      jr.JobId = bsr->JobId;
-      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;
-      }
-      bsr->VolSessionId = jr.VolSessionId;
-      bsr->VolSessionTime = jr.VolSessionTime;
-      if ((bsr->VolCount=db_get_job_volume_parameters(ua->jcr, ua->db, bsr->JobId, 
-          &(bsr->VolParams))) == 0) {
-         bsendmsg(ua, _("Unable to get Job Volume Parameters. ERR=%s\n"), db_strerror(ua->db));
-        if (bsr->VolParams) {
-           free(bsr->VolParams);
-           bsr->VolParams = NULL;
-        }
-        return 0;
-      }
-      return complete_bsr(ua, bsr->next);
-   }
-   return 1;
-}
-
-/*
- * Write the bootstrap record to file
- */
-static int write_bsr_file(UAContext *ua, RBSR *bsr)
-{
-   FILE *fd;
-   POOLMEM *fname = get_pool_memory(PM_MESSAGE);
-   int stat;
-
-   Mmsg(&fname, "%s/restore.bsr", working_directory);
-   fd = fopen(fname, "w+");
-   if (!fd) {
-      bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"), 
-        fname, strerror(errno));
-      free_pool_memory(fname);
-      return 0;
-   }
-   /* Sort the bsr chain */
-   bsr = sort_bsr(bsr);
-   /* Write them to file */
-   write_bsr(ua, bsr, fd);
-   stat = !ferror(fd);
-   fclose(fd);
-   bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
-
-   /* Tell the user what he will need to mount */
-   bsendmsg(ua, _("\nThe restore job will require the following Volumes:\n"));
-   /* Create Unique list of Volumes using prompt list */
-   start_prompt(ua, "");
-   for (RBSR *nbsr=bsr; nbsr; nbsr=nbsr->next) {
-      for (int i=0; i < nbsr->VolCount; i++) {
-        add_prompt(ua, nbsr->VolParams[i].VolumeName);
-      }
-   }
-   for (int i=0; i < ua->num_prompts; i++) {
-      bsendmsg(ua, "   %s\n", ua->prompt[i]);
-      free(ua->prompt[i]);
-   }
-   ua->num_prompts = 0;
-   bsendmsg(ua, "\n");
-   free_pool_memory(fname);
-   return stat;
-}
-
-/*
- * First sort the bsr chain, then sort the VolParams   
- */
-static RBSR *sort_bsr(RBSR *bsr)
-{
-   if (!bsr) {
-      return bsr;
-   }
-   /* ****FIXME**** sort the bsr chain */
-   for (RBSR *nbsr=bsr; nbsr; nbsr=nbsr->next) {
-   }
-   return bsr;
-}
-
-static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
-{
-   if (bsr) {
-      for (int i=0; i < bsr->VolCount; i++) {
-         fprintf(fd, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
-         fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
-         fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
-         fprintf(fd, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile, 
-                bsr->VolParams[i].EndFile);
-         fprintf(fd, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
-                bsr->VolParams[i].EndBlock);
-        write_findex(ua, bsr->fi, fd);
-      }
-      write_bsr(ua, bsr->next, fd);
-   }
-}
-
-static void print_bsr(UAContext *ua, RBSR *bsr)
-{
-   if (bsr) {
-      for (int i=0; i < bsr->VolCount; i++) {
-         bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
-         bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
-         bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
-         bsendmsg(ua, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile, 
-                 bsr->VolParams[i].EndFile);
-         bsendmsg(ua, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
-                 bsr->VolParams[i].EndBlock);
-        print_findex(ua, bsr->fi);
-      }
-      print_bsr(ua, bsr->next);
-   }
-}
-
-
-/*
- * Add a FileIndex to the list of BootStrap records.
- *  Here we are only dealing with JobId's and the FileIndexes
- *  associated with those JobIds.
- */
-static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
-{
-   RBSR *nbsr;
-   RBSR_FINDEX *fi, *lfi;
-
-   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;
-   }
-   /* 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;
-      }
-   }
-
-   /* 
-    * At this point, bsr points to bsr containing JobId,
-    *  and we are sure that there is at least one fi record.
-    */
-   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;
-   }
-   /* 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)) { 
-           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;
-}
-
-/*
- * This callback routine is responsible for inserting the
- *  items it gets into the directory tree. For each JobId selected
- *  this routine is called once for each file. We do not allow
- *  duplicate filenames, but instead keep the info from the most
- *  recent file entered (i.e. the JobIds are assumed to be sorted)
- */
-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) {
-      if (*row[0] != '/') {           /* Must be Win32 directory */
-        type = TN_DIR_NLS;
-      } else {
-        type = TN_DIR;
-      }
-   } else {
-      type = TN_FILE;
-   }
-   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;
-   }
-   Dmsg3(200, "FI=%d type=%d fname=%s\n", node->FileIndex, type, 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;
-   }
-   new_node->FileIndex = atoi(row[2]);
-   new_node->JobId = atoi(row[3]);
-   new_node->type = type;
-   new_node->extract = 1;            /* extract all by default */
-   tree->cnt++;
-   return 0;
-}
-
-
-/*
- * Set extract to value passed. We recursively walk
- *  down the tree setting all children if the 
- *  node is a directory.
- */
-static void set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, int value)
-{
-   TREE_NODE *n;
-   FILE_DBR fdbr;
-   struct stat statp;
-
-   node->extract = value;
-   /* For a non-file (i.e. directory), we see all the children */
-   if (node->type != TN_FILE) {
-      for (n=node->child; n; n=n->sibling) {
-        set_extract(ua, n, tree, value);
-      }
-   } else if (value) {
-      char cwd[2000];
-      /* Ordinary file, we get the full path, look up the
-       * attributes, decode them, and if we are hard linked to
-       * a file that was saved, we must load that file too.
-       */
-      tree_getpath(node, cwd, sizeof(cwd));
-      fdbr.FileId = 0;
-      fdbr.JobId = node->JobId;
-      if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
-        uint32_t LinkFI;
-        decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
-        /*
-         * If we point to a hard linked file, traverse the tree to
-         * find that file, and mark it for restoration as well. It
-         * must have the Link we just obtained and the same JobId.
-         */
-        if (LinkFI) {
-           for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
-              if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
-                 n->extract = 1;
-                 break;
-              }
-           }
-        }
-      }
-   }
-}
-
-static int markcmd(UAContext *ua, TREE_CTX *tree)
-{
-   TREE_NODE *node;
-
-   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(ua, node, tree, 1);
-      }
-   }
-   return 1;
-}
-
-static int countcmd(UAContext *ua, TREE_CTX *tree)
-{
-   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 restoration.\n", total, extract);
-   return 1;
-}
-
-static int findcmd(UAContext *ua, TREE_CTX *tree)
-{
-   char cwd[2000];
-
-   if (ua->argc == 1) {
-      bsendmsg(ua, _("No file specification given.\n"));
-      return 0;
-   }
-   
-   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%s\n", node->extract?"*":"", cwd);
-        }
-      }
-   }
-   return 1;
-}
-
-
-
-static int lscmd(UAContext *ua, TREE_CTX *tree)
-{
-   TREE_NODE *node;
-
-   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;
-}
-
-extern char *getuser(uid_t uid);
-extern char *getgroup(gid_t gid);
-
-/*
- * This is actually the long form used for "dir"
- */
-static void ls_output(char *buf, char *fname, int extract, struct stat *statp)
-{
-   char *p, *f;
-   char ec1[30];
-   int n;
-
-   p = encode_mode(statp->st_mode, buf);
-   n = sprintf(p, "  %2d ", (uint32_t)statp->st_nlink);
-   p += n;
-   n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
-   p += n;
-   n = sprintf(p, "%8.8s  ", edit_uint64(statp->st_size, ec1));
-   p += n;
-   p = encode_time(statp->st_ctime, p);
-   *p++ = ' ';
-   if (extract) {
-      *p++ = '*';
-   } else {
-      *p++ = ' ';
-   }
-   for (f=fname; *f; )
-      *p++ = *f++;
-   *p = 0;
-}
-
-
-/*
- * Like ls command, but give more detail on each file
- */
-static int dircmd(UAContext *ua, TREE_CTX *tree)
-{
-   TREE_NODE *node;
-   FILE_DBR fdbr;
-   struct stat statp;
-   char buf[1000];
-   char cwd[1100];
-
-   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) {
-        tree_getpath(node, cwd, sizeof(cwd));
-        fdbr.FileId = 0;
-        fdbr.JobId = node->JobId;
-        if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
-           uint32_t LinkFI;
-           decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
-           ls_output(buf, cwd, node->extract, &statp);
-            bsendmsg(ua, "%s\n", buf);
-        } else {
-           /* Something went wrong getting attributes -- print name */
-            bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
-               (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
-        }
-      }
-   }
-   return 1;
-}
-
-
-static int helpcmd(UAContext *ua, TREE_CTX *tree) 
-{
-   unsigned int i;
-
-/* 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;
-}
-
-/*
- * Change directories. Note, if the user specifies x: and it fails,
- *   we assume it is a Win32 absolute cd rather than relative and
- *   try a second time with /x: ...  Win32 kludge.
- */
-static int cdcmd(UAContext *ua, TREE_CTX *tree) 
-{
-   TREE_NODE *node;
-   char cwd[2000];
-
-   if (ua->argc != 2) {
-      return 1;
-   }
-   node = tree_cwd(ua->argk[1], tree->root, tree->node);
-   if (!node) {
-      /* Try once more if Win32 drive -- make absolute */
-      if (ua->argk[1][1] == ':') {  /* win32 drive */
-         strcpy(cwd, "/");
-        strcat(cwd, ua->argk[1]);
-        node = tree_cwd(cwd, tree->root, tree->node);
-      }
-      if (!node) {
-         bsendmsg(ua, _("Invalid path given.\n"));
-      } else {
-        tree->node = node;
-      }
-   } else {
-      tree->node = node;
-   }
-   tree_getpath(tree->node, cwd, sizeof(cwd));
-   bsendmsg(ua, _("cwd is: %s\n"), cwd);
-   return 1;
-}
-
-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;
-}
-
-
-static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
-{
-   TREE_NODE *node;
-
-   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(ua, node, tree, 0);
-      }
-   }
-   return 1;
-}
-
-static int quitcmd(UAContext *ua, TREE_CTX *tree) 
-{
-   return 0;
-}
-
-
 /*
  * 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)
 {
diff --git a/bacula/src/dird/ua_tree.c b/bacula/src/dird/ua_tree.c
new file mode 100644 (file)
index 0000000..a3ae586
--- /dev/null
@@ -0,0 +1,428 @@
+/*
+ *
+ *   Bacula Director -- User Agent Database File tree for Restore
+ *     command.
+ *
+ *     Kern Sibbald, July MMII
+ *
+ *   Version $Id$
+ */
+
+/*
+   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
+   published by the Free Software Foundation; either version 2 of
+   the License, or (at your option) any later version.
+
+   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., 59 Temple Place - Suite 330, Boston,
+   MA 02111-1307, USA.
+
+ */
+
+#include "bacula.h"
+#include "dird.h"
+#include <fnmatch.h>
+#include "findlib/find.h"
+
+
+/* Forward referenced commands */
+
+static int markcmd(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 dircmd(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 unmarkcmd(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_("mark"),       markcmd,      _("mark file for restoration")},
+ { N_("unmark"),     unmarkcmd,    _("unmark file for restoration")},
+ { N_("cd"),         cdcmd,        _("change current directory")},
+ { N_("pwd"),        pwdcmd,       _("print current working directory")},
+ { N_("ls"),         lscmd,        _("list current directory")},    
+ { N_("dir"),        dircmd,       _("list current directory")},    
+ { N_("count"),      countcmd,     _("count marked files")},
+ { N_("find"),       findcmd,      _("find files")},
+ { N_("done"),       quitcmd,      _("leave file selection mode")},
+ { N_("exit"),       quitcmd,      _("exit = done")},
+ { N_("help"),       helpcmd,      _("print help")},
+ { 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", ...
+ */
+void user_select_files_from_tree(TREE_CTX *tree)
+{
+   char cwd[2000];
+
+   bsendmsg(tree->ua, _( 
+      "\nYou are now entering file selection mode where you add and\n"
+      "remove files to be restored. All files are initially added.\n"
+      "Enter \"done\" to leave this mode.\n\n"));
+   /*
+    * Enter interactive command handler allowing selection
+    *  of individual files.
+    */
+   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_ua_args(tree->ua);
+      if (tree->ua->argc == 0) {
+        return;
+      }
+
+      len = strlen(tree->ua->argk[0]);
+      found = 0;
+      stat = 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 exit.\n"));
+        continue;
+      }
+      if (!stat) {
+        break;
+      }
+   }
+}
+
+
+/*
+ * This callback routine is responsible for inserting the
+ *  items it gets into the directory tree. For each JobId selected
+ *  this routine is called once for each file. We do not allow
+ *  duplicate filenames, but instead keep the info from the most
+ *  recent file entered (i.e. the JobIds are assumed to be sorted)
+ */
+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) {
+      if (*row[0] != '/') {           /* Must be Win32 directory */
+        type = TN_DIR_NLS;
+      } else {
+        type = TN_DIR;
+      }
+   } else {
+      type = TN_FILE;
+   }
+   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;
+   }
+   Dmsg3(200, "FI=%d type=%d fname=%s\n", node->FileIndex, type, 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;
+   }
+   new_node->FileIndex = atoi(row[2]);
+   new_node->JobId = atoi(row[3]);
+   new_node->type = type;
+   new_node->extract = 1;            /* extract all by default */
+   tree->cnt++;
+   return 0;
+}
+
+
+/*
+ * Set extract to value passed. We recursively walk
+ *  down the tree setting all children if the 
+ *  node is a directory.
+ */
+static void set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, int value)
+{
+   TREE_NODE *n;
+   FILE_DBR fdbr;
+   struct stat statp;
+
+   node->extract = value;
+   /* For a non-file (i.e. directory), we see all the children */
+   if (node->type != TN_FILE) {
+      for (n=node->child; n; n=n->sibling) {
+        set_extract(ua, n, tree, value);
+      }
+   } else if (value) {
+      char cwd[2000];
+      /* Ordinary file, we get the full path, look up the
+       * attributes, decode them, and if we are hard linked to
+       * a file that was saved, we must load that file too.
+       */
+      tree_getpath(node, cwd, sizeof(cwd));
+      fdbr.FileId = 0;
+      fdbr.JobId = node->JobId;
+      if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
+        uint32_t LinkFI;
+        decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
+        /*
+         * If we point to a hard linked file, traverse the tree to
+         * find that file, and mark it for restoration as well. It
+         * must have the Link we just obtained and the same JobId.
+         */
+        if (LinkFI) {
+           for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
+              if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
+                 n->extract = 1;
+                 break;
+              }
+           }
+        }
+      }
+   }
+}
+
+static int markcmd(UAContext *ua, TREE_CTX *tree)
+{
+   TREE_NODE *node;
+
+   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(ua, node, tree, 1);
+      }
+   }
+   return 1;
+}
+
+static int countcmd(UAContext *ua, TREE_CTX *tree)
+{
+   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 restoration.\n", total, extract);
+   return 1;
+}
+
+static int findcmd(UAContext *ua, TREE_CTX *tree)
+{
+   char cwd[2000];
+
+   if (ua->argc == 1) {
+      bsendmsg(ua, _("No file specification given.\n"));
+      return 0;
+   }
+   
+   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%s\n", node->extract?"*":"", cwd);
+        }
+      }
+   }
+   return 1;
+}
+
+
+
+static int lscmd(UAContext *ua, TREE_CTX *tree)
+{
+   TREE_NODE *node;
+
+   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;
+}
+
+extern char *getuser(uid_t uid);
+extern char *getgroup(gid_t gid);
+
+/*
+ * This is actually the long form used for "dir"
+ */
+static void ls_output(char *buf, char *fname, int extract, struct stat *statp)
+{
+   char *p, *f;
+   char ec1[30];
+   int n;
+
+   p = encode_mode(statp->st_mode, buf);
+   n = sprintf(p, "  %2d ", (uint32_t)statp->st_nlink);
+   p += n;
+   n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
+   p += n;
+   n = sprintf(p, "%8.8s  ", edit_uint64(statp->st_size, ec1));
+   p += n;
+   p = encode_time(statp->st_ctime, p);
+   *p++ = ' ';
+   if (extract) {
+      *p++ = '*';
+   } else {
+      *p++ = ' ';
+   }
+   for (f=fname; *f; )
+      *p++ = *f++;
+   *p = 0;
+}
+
+
+/*
+ * Like ls command, but give more detail on each file
+ */
+static int dircmd(UAContext *ua, TREE_CTX *tree)
+{
+   TREE_NODE *node;
+   FILE_DBR fdbr;
+   struct stat statp;
+   char buf[1000];
+   char cwd[1100];
+
+   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) {
+        tree_getpath(node, cwd, sizeof(cwd));
+        fdbr.FileId = 0;
+        fdbr.JobId = node->JobId;
+        if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
+           uint32_t LinkFI;
+           decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
+           ls_output(buf, cwd, node->extract, &statp);
+            bsendmsg(ua, "%s\n", buf);
+        } else {
+           /* Something went wrong getting attributes -- print name */
+            bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
+               (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
+        }
+      }
+   }
+   return 1;
+}
+
+
+static int helpcmd(UAContext *ua, TREE_CTX *tree) 
+{
+   unsigned int i;
+
+/* 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;
+}
+
+/*
+ * Change directories. Note, if the user specifies x: and it fails,
+ *   we assume it is a Win32 absolute cd rather than relative and
+ *   try a second time with /x: ...  Win32 kludge.
+ */
+static int cdcmd(UAContext *ua, TREE_CTX *tree) 
+{
+   TREE_NODE *node;
+   char cwd[2000];
+
+   if (ua->argc != 2) {
+      return 1;
+   }
+   node = tree_cwd(ua->argk[1], tree->root, tree->node);
+   if (!node) {
+      /* Try once more if Win32 drive -- make absolute */
+      if (ua->argk[1][1] == ':') {  /* win32 drive */
+         strcpy(cwd, "/");
+        strcat(cwd, ua->argk[1]);
+        node = tree_cwd(cwd, tree->root, tree->node);
+      }
+      if (!node) {
+         bsendmsg(ua, _("Invalid path given.\n"));
+      } else {
+        tree->node = node;
+      }
+   } else {
+      tree->node = node;
+   }
+   tree_getpath(tree->node, cwd, sizeof(cwd));
+   bsendmsg(ua, _("cwd is: %s\n"), cwd);
+   return 1;
+}
+
+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;
+}
+
+
+static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
+{
+   TREE_NODE *node;
+
+   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(ua, node, tree, 0);
+      }
+   }
+   return 1;
+}
+
+static int quitcmd(UAContext *ua, TREE_CTX *tree) 
+{
+   return 0;
+}
index d0a233c6131375c46cc9327f84f07648c74ffee8..113c8b32f9f48abc86d9b57c90f189a89e03f5e1 100644 (file)
@@ -1,8 +1,8 @@
 /* */
 #define VERSION "1.31"
 #define VSTRING "1"
-#define BDATE   "01 Jun 2003"
-#define LSMDATE "01Jun03"
+#define BDATE   "02 Jun 2003"
+#define LSMDATE "02Jun03"
 
 /* Debug flags */
 #define DEBUG 1