]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/dird/ua_tree.c
regress: add new many-reload-test to standard scripts
[bacula/bacula] / bacula / src / dird / ua_tree.c
index d3df13ac16af3b473c37a791da8a43072487e332..a03eb1b15845144e390f2850923317bfe800c894 100644 (file)
@@ -1,39 +1,27 @@
 /*
-   Bacula® - The Network Backup Solution
-
-   Copyright (C) 2002-2009 Free Software Foundation Europe e.V.
-
-   The main author of Bacula is Kern Sibbald, with contributions from
-   many others, a complete list can be found in the file AUTHORS.
-   This program is Free Software; you can redistribute it and/or
-   modify it under the terms of version two of the GNU General Public
-   License as published by the Free Software Foundation and included
-   in the file LICENSE.
-
-   This program is distributed in the hope that it will be useful, but
-   WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-   General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-   02110-1301, USA.
-
-   Bacula® is a registered trademark of Kern Sibbald.
-   The licensor of Bacula is the Free Software Foundation Europe
-   (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
-   Switzerland, email:ftf@fsfeurope.org.
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2017 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
 */
 /*
- *
  *   Bacula Director -- User Agent Database File tree for Restore
  *      command. This file interacts with the user implementing the
  *      UA tree commands.
  *
  *     Kern Sibbald, July MMII
- *
- *   Version $Id$
  */
 
 #include "bacula.h"
@@ -65,7 +53,10 @@ static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree);
 static int quitcmd(UAContext *ua, TREE_CTX *tree);
 static int donecmd(UAContext *ua, TREE_CTX *tree);
-
+static int dot_lsdircmd(UAContext *ua, TREE_CTX *tree);
+static int dot_lscmd(UAContext *ua, TREE_CTX *tree);
+static int dot_helpcmd(UAContext *ua, TREE_CTX *tree);
+static int dot_lsmarkcmd(UAContext *ua, TREE_CTX *tree);
 
 struct cmdstruct { const char *key; int (*func)(UAContext *ua, TREE_CTX *tree); const char *help; };
 static struct cmdstruct commands[] = {
@@ -81,7 +72,10 @@ static struct cmdstruct commands[] = {
  { NT_("find"),       findcmd,      _("find files, wildcards allowed")},
  { NT_("help"),       helpcmd,      _("print help")},
  { NT_("ls"),         lscmd,        _("list current directory, wildcards allowed")},
+ { NT_(".ls"),        dot_lscmd,    _("list current directory, wildcards allowed")},
+ { NT_(".lsdir"),     dot_lsdircmd, _("list subdir in current directory, wildcards allowed")},
  { NT_("lsmark"),     lsmarkcmd,    _("list the marked files in and below the cd")},
+ { NT_(".lsmark"),    dot_lsmarkcmd,_("list the marked files in")},
  { NT_("mark"),       markcmd,      _("mark dir/file to be restored recursively, wildcards allowed")},
  { NT_("markdir"),    markdircmd,   _("mark directory name to be restored (no files)")},
  { NT_("pwd"),        pwdcmd,       _("print current working directory")},
@@ -89,6 +83,7 @@ static struct cmdstruct commands[] = {
  { NT_("unmark"),     unmarkcmd,    _("unmark dir/file to be restored recursively in dir")},
  { NT_("unmarkdir"),  unmarkdircmd, _("unmark directory name only no recursion")},
  { NT_("quit"),       quitcmd,      _("quit and do not do restore")},
+ { NT_(".help"),      dot_helpcmd,  _("print help")},
  { NT_("?"),          helpcmd,      _("print help")},
              };
 #define comsize ((int)(sizeof(commands)/sizeof(struct cmdstruct)))
@@ -123,13 +118,13 @@ bool user_select_files_from_tree(TREE_CTX *tree)
    ua->send_msg(_("cwd is: %s\n"), cwd);
    for ( ;; ) {
       int found, len, i;
-      if (!get_cmd(ua, "$ ")) {
+      if (!get_cmd(ua, "$ ", true)) {
          break;
       }
       if (ua->api) user->signal(BNET_CMD_BEGIN);
       parse_args_only(ua->cmd, &ua->args, &ua->argc, ua->argk, ua->argv, MAX_CMD_ARGS);
       if (ua->argc == 0) {
-         ua->warning_msg(_("Invalid command \"%s\". Enter \"done\" to exit.\n"), ua->cmd);
+         ua->warning_msg(_("Invalid command \"%s\".  Enter \"done\" to exit.\n"), ua->cmd);
          if (ua->api) user->signal(BNET_CMD_FAILED);
          continue;
       }
@@ -138,13 +133,17 @@ bool user_select_files_from_tree(TREE_CTX *tree)
       found = 0;
       stat = false;
       for (i=0; i<comsize; i++)       /* search for command */
-         if (strncasecmp(ua->argk[0],  _(commands[i].key), len) == 0) {
+         if (strncasecmp(ua->argk[0],  commands[i].key, len) == 0) {
             stat = (*commands[i].func)(ua, tree);   /* go execute command */
             found = 1;
             break;
          }
       if (!found) {
-         ua->warning_msg(_("Invalid command \"%s\". Enter \"done\" to exit.\n"), ua->cmd);
+         if (*ua->argk[0] == '.') {
+            /* Some unknow dot command -- probably .messages, ignore it */
+            continue;
+         }
+         ua->warning_msg(_("Invalid command \"%s\".  Enter \"done\" to exit.\n"), ua->cmd);
          if (ua->api) user->signal(BNET_CMD_FAILED);
          continue;
       }
@@ -161,7 +160,6 @@ bool user_select_files_from_tree(TREE_CTX *tree)
    return stat;
 }
 
-
 /*
  * This callback routine is responsible for inserting the
  *  items it gets into the directory tree. For each JobId selected
@@ -171,7 +169,7 @@ bool user_select_files_from_tree(TREE_CTX *tree)
  *
  *   See uar_sel_files in sql_cmds.c for query that calls us.
  *      row[0]=Path, row[1]=Filename, row[2]=FileIndex
- *      row[3]=JobId row[4]=LStat
+ *      row[3]=JobId row[4]=LStat row[5]=DeltaSeq
  */
 int insert_tree_handler(void *ctx, int num_fields, char **row)
 {
@@ -181,10 +179,13 @@ int insert_tree_handler(void *ctx, int num_fields, char **row)
    int type;
    bool hard_link, ok;
    int FileIndex;
+   int32_t delta_seq;
    JobId_t JobId;
+   HL_ENTRY *entry = NULL;
+   int32_t LinkFI;
 
-   Dmsg4(400, "Path=%s%s FI=%s JobId=%s\n", row[0], row[1],
-      row[2], row[3]);
+   Dmsg4(150, "Path=%s%s FI=%s JobId=%s\n", row[0], row[1],
+         row[2], row[3]);
    if (*row[1] == 0) {                 /* no filename => directory */
       if (!IsPathSeparator(*row[0])) { /* Must be Win32 directory */
          type = TN_DIR_NLS;
@@ -194,11 +195,37 @@ int insert_tree_handler(void *ctx, int num_fields, char **row)
    } else {
       type = TN_FILE;
    }
-   hard_link = (decode_LinkFI(row[4], &statp) != 0);
+   decode_stat(row[4], &statp, sizeof(statp), &LinkFI);
+
+   hard_link = (LinkFI != 0);
    node = insert_tree_node(row[0], row[1], type, tree->root, NULL);
    JobId = str_to_int64(row[3]);
    FileIndex = str_to_int64(row[2]);
-   Dmsg2(400, "JobId=%s FileIndex=%s\n", row[3], row[2]);
+   delta_seq = str_to_int64(row[5]);
+   Dmsg6(150, "node=0x%p JobId=%s FileIndex=%s Delta=%s node.delta=%d LinkFI=%d\n",
+         node, row[3], row[2], row[5], node->delta_seq, LinkFI);
+
+   /* TODO: check with hardlinks */
+   if (delta_seq > 0) {
+      if (delta_seq == (node->delta_seq + 1)) {
+         tree_add_delta_part(tree->root, node, node->JobId, node->FileIndex);
+
+      } else {
+         /* File looks to be deleted */
+         if (node->delta_seq == -1) { /* just created */
+            tree_remove_node(tree->root, node);
+
+         } else {
+            tree->ua->warning_msg(_("Something is wrong with the Delta sequence of %s, "
+                                    "skipping new parts. Current sequence is %d\n"),
+                                  row[1], node->delta_seq);
+
+            Dmsg3(0, "Something is wrong with Delta, skip it "
+                  "fname=%s d1=%d d2=%d\n", row[1], node->delta_seq, delta_seq);
+         }
+         return 0;
+      }
+   }
    /*
     * - The first time we see a file (node->inserted==true), we accept it.
     * - In the same JobId, we accept only the first copy of a
@@ -222,12 +249,35 @@ int insert_tree_handler(void *ctx, int num_fields, char **row)
       node->JobId = JobId;
       node->type = type;
       node->soft_link = S_ISLNK(statp.st_mode) != 0;
+      node->delta_seq = delta_seq;
+      node->can_access = true;
       if (tree->all) {
          node->extract = true;          /* extract all by default */
          if (type == TN_DIR || type == TN_DIR_NLS) {
             node->extract_dir = true;   /* if dir, extract it */
          }
       }
+      /* insert file having hardlinks into hardlink hashtable */
+      if (statp.st_nlink > 1 && type != TN_DIR && type != TN_DIR_NLS) {
+         if (!LinkFI) {
+            /* first occurrence - file hardlinked to */
+            entry = (HL_ENTRY *)tree->root->hardlinks.hash_malloc(sizeof(HL_ENTRY));
+            entry->key = (((uint64_t) JobId) << 32) + FileIndex;
+            entry->node = node;
+            tree->root->hardlinks.insert(entry->key, entry);
+         } else if (tree->hardlinks_in_mem) {
+            /* hardlink to known file index: lookup original file */
+            uint64_t file_key = (((uint64_t) JobId) << 32) + LinkFI;
+            HL_ENTRY *first_hl = (HL_ENTRY *) tree->root->hardlinks.lookup(file_key);
+            if (first_hl && first_hl->node) {
+               /* then add hardlink entry to linked node*/
+               entry = (HL_ENTRY *)tree->root->hardlinks.hash_malloc(sizeof(HL_ENTRY));
+               entry->key = (((uint64_t) JobId) << 32) + FileIndex;
+               entry->node = first_hl->node;
+               tree->root->hardlinks.insert(entry->key, entry);
+            }
+         }
+      }
    }
    if (node->inserted) {
       tree->FileCount++;
@@ -240,7 +290,6 @@ int insert_tree_handler(void *ctx, int num_fields, char **row)
    return 0;
 }
 
-
 /*
  * Set extract to value passed. We recursively walk
  *  down the tree setting all children if the
@@ -249,8 +298,6 @@ int insert_tree_handler(void *ctx, int num_fields, char **row)
 static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extract)
 {
    TREE_NODE *n;
-   FILE_DBR fdbr;
-   struct stat statp;
    int count = 0;
 
    node->extract = extract;
@@ -270,40 +317,48 @@ static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extr
        * Walk up tree marking any unextracted parent to be
        * extracted.
        */
-      if (extract) {
+      if (!tree->no_auto_parent && extract) {
          while (node->parent && !node->parent->extract_dir) {
             node = node->parent;
             node->extract_dir = true;
          }
       }
    } else if (extract) {
-      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 (node->hard_link && db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
-         int32_t LinkFI;
-         decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
+      uint64_t key = 0;
+      if (tree->hardlinks_in_mem) {
+         if (node->hard_link) {
+            key = (((uint64_t) node->JobId) << 32) + node->FileIndex;  /* every hardlink is in hashtable, and it points to linked file */
+         }
+      } else {
+         /* Get the hard link if it exists */
+         FILE_DBR fdbr;
+         struct stat statp;
+         char cwd[2000];
          /*
-          * If we point to a hard linked file, traverse the tree to
-          * find that file, and mark it to be restored as well. It
-          * must have the Link we just obtained and the same JobId.
+          * 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.
           */
-         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 = true;
-                  if (n->type == TN_DIR || n->type == TN_DIR_NLS) {
-                     n->extract_dir = true;
-                  }
-                  break;
-               }
-            }
+         tree_getpath(node, cwd, sizeof(cwd));
+         fdbr.FileId = 0;
+         fdbr.JobId = node->JobId;
+         if (node->hard_link && db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
+            int32_t LinkFI;
+            decode_stat(fdbr.LStat, &statp, sizeof(statp), &LinkFI); /* decode stat pkt */
+            key = (((uint64_t) node->JobId) << 32) + LinkFI;  /* lookup by linked file's fileindex */
+         }
+      }
+      /* If file hard linked and we have a key */
+      if (node->hard_link && key != 0) {
+         /*
+          * If we point to a hard linked file, find that file in
+          * hardlinks hashmap, and mark it to be restored as well.
+          */
+         HL_ENTRY *entry = (HL_ENTRY *)tree->root->hardlinks.lookup(key);
+         if (entry && entry->node) {
+            n = entry->node;
+            n->extract = true;
+            n->extract_dir = (n->type == TN_DIR || n->type == TN_DIR_NLS);
          }
       }
    }
@@ -436,7 +491,52 @@ static int findcmd(UAContext *ua, TREE_CTX *tree)
    return 1;
 }
 
+static int dot_lsdircmd(UAContext *ua, TREE_CTX *tree)
+{
+   TREE_NODE *node;
 
+   if (!tree_node_has_child(tree->node)) {
+      return 1;
+   }
+
+   foreach_child(node, tree->node) {
+      if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
+         if (tree_node_has_child(node)) {
+            ua->send_msg("%s/\n", node->fname);
+         }
+      }
+   }
+
+   return 1;
+}
+
+static int dot_helpcmd(UAContext *ua, TREE_CTX *tree)
+{
+   for (int i=0; i<comsize; i++) {
+      /* List only non-dot commands */
+      if (commands[i].key[0] != '.') {
+         ua->send_msg("%s\n", commands[i].key);
+      }
+   }
+   return 1;
+}
+
+static int dot_lscmd(UAContext *ua, TREE_CTX *tree)
+{
+   TREE_NODE *node;
+
+   if (!tree_node_has_child(tree->node)) {
+      return 1;
+   }
+
+   foreach_child(node, tree->node) {
+      if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
+         ua->send_msg("%s%s\n", node->fname, tree_node_has_child(node)?"/":"");
+      }
+   }
+
+   return 1;
+}
 
 static int lscmd(UAContext *ua, TREE_CTX *tree)
 {
@@ -464,12 +564,40 @@ static int lscmd(UAContext *ua, TREE_CTX *tree)
 /*
  * Ls command that lists only the marked files
  */
-static void rlsmark(UAContext *ua, TREE_NODE *tnode)
+static int dot_lsmarkcmd(UAContext *ua, TREE_CTX *tree)
 {
    TREE_NODE *node;
+   if (!tree_node_has_child(tree->node)) {
+      return 1;
+   }
+   foreach_child(node, tree->node) {
+      if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
+          (node->extract || node->extract_dir)) {
+         ua->send_msg("%s%s\n", node->fname, tree_node_has_child(node)?"/":"");
+      }
+   }
+   return 1;
+}
+
+/*
+ * This recursive ls command that lists only the marked files
+ */
+static void rlsmark(UAContext *ua, TREE_NODE *tnode, int level)
+{
+   TREE_NODE *node;
+   const int max_level = 100;
+   char indent[max_level*2+1];
+   int i, j;
    if (!tree_node_has_child(tnode)) {
       return;
    }
+   level = MIN(level, max_level);
+   j = 0;
+   for (i=0; i<level; i++) {
+      indent[j++] = ' ';
+      indent[j++] = ' ';
+   }
+   indent[j] = 0;
    foreach_child(node, tnode) {
       if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
           (node->extract || node->extract_dir)) {
@@ -481,9 +609,9 @@ static void rlsmark(UAContext *ua, TREE_NODE *tnode)
          } else {
             tag = "";
          }
-         ua->send_msg("%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
+         ua->send_msg("%s%s%s%s\n", indent, tag, node->fname, tree_node_has_child(node)?"/":"");
          if (tree_node_has_child(node)) {
-            rlsmark(ua, node);
+            rlsmark(ua, node, level+1);
          }
       }
    }
@@ -491,15 +619,15 @@ static void rlsmark(UAContext *ua, TREE_NODE *tnode)
 
 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
 {
-   rlsmark(ua, tree->node);
+   rlsmark(ua, tree->node, 0);
    return 1;
 }
 
 /*
  * This is actually the long form used for "dir"
  */
-static void ls_output(guid_list *guid, char *buf, const char *fname, const char *tag, 
-                      struct stat *statp, bool dot_cmd) 
+static void ls_output(guid_list *guid, char *buf, const char *fname, const char *tag,
+                      struct stat *statp, bool dot_cmd)
 {
    char *p;
    const char *f;
@@ -513,7 +641,7 @@ static void ls_output(guid_list *guid, char *buf, const char *fname, const char
       *p++ = ',';
       n = sprintf(p, "%d,", (uint32_t)statp->st_nlink);
       p += n;
-      n = sprintf(p, "%s,%s,", 
+      n = sprintf(p, "%s,%s,",
                   guid->uid_to_name(statp->st_uid, en1, sizeof(en1)),
                   guid->gid_to_name(statp->st_gid, en2, sizeof(en2)));
       p += n;
@@ -526,11 +654,11 @@ static void ls_output(guid_list *guid, char *buf, const char *fname, const char
    } else {
       n = sprintf(p, "  %2d ", (uint32_t)statp->st_nlink);
       p += n;
-      n = sprintf(p, "%-8.8s %-8.8s", 
+      n = sprintf(p, "%-8.8s %-8.8s",
                   guid->uid_to_name(statp->st_uid, en1, sizeof(en1)),
                   guid->gid_to_name(statp->st_gid, en2, sizeof(en2)));
       p += n;
-      n = sprintf(p, "%10.10s  ", edit_int64(statp->st_size, ec1));
+      n = sprintf(p, "%12.12s  ", edit_int64(statp->st_size, ec1));
       p += n;
       if (statp->st_ctime > statp->st_mtime) {
          time = statp->st_ctime;
@@ -598,7 +726,7 @@ static int do_dircmd(UAContext *ua, TREE_CTX *tree, bool dot_cmd)
          }
          if (db_get_file_attributes_record(ua->jcr, ua->db, pcwd, NULL, &fdbr)) {
             int32_t LinkFI;
-            decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
+            decode_stat(fdbr.LStat, &statp, sizeof(statp), &LinkFI); /* decode stat pkt */
          } else {
             /* Something went wrong getting attributes -- print name */
             memset(&statp, 0, sizeof(statp));
@@ -643,7 +771,7 @@ static int estimatecmd(UAContext *ua, TREE_CTX *tree)
             fdbr.JobId = node->JobId;
             if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
                int32_t LinkFI;
-               decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
+               decode_stat(fdbr.LStat, &statp, sizeof(statp), &LinkFI); /* decode stat pkt */
                if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
                   total_bytes += statp.st_size;
                }
@@ -691,6 +819,7 @@ static int cdcmd(UAContext *ua, TREE_CTX *tree)
       ua->error_msg(_("Too few or too many arguments. Try using double quotes.\n"));
       return 1;
    }
+
    node = tree_cwd(ua->argk[1], tree->root, tree->node);
    if (!node) {
       /* Try once more if Win32 drive -- make absolute */
@@ -701,11 +830,14 @@ static int cdcmd(UAContext *ua, TREE_CTX *tree)
       }
       if (!node) {
          ua->warning_msg(_("Invalid path given.\n"));
-      } else {
+      }
+   }
+   if (node) {
+      if (node->can_access) {
          tree->node = node;
+      } else {
+         ua->warning_msg(_("Invalid path given. Permission denied.\n"));
       }
-   } else {
-      tree->node = node;
    }
    return pwdcmd(ua, tree);
 }