]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/dird/ua_tree.c
Jobq.c race bug fixed + minor updates
[bacula/bacula] / bacula / src / dird / ua_tree.c
index 29db4a832dd91ebddf45b2ea66909b2f23dbd3d5..d383ff3bffef32a7af9372df4d9a299caa682b99 100644 (file)
@@ -1,7 +1,8 @@
 /*
  *
  *   Bacula Director -- User Agent Database File tree for Restore
- *     command.
+ *     command. This file interacts with the user implementing the
+ *     UA tree commands.
  *
  *     Kern Sibbald, July MMII
  *
@@ -9,7 +10,7 @@
  */
 
 /*
-   Copyright (C) 2002-2003 Kern Sibbald and John Walker
+   Copyright (C) 2002-2004 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
 /* Forward referenced commands */
 
 static int markcmd(UAContext *ua, TREE_CTX *tree);
+static int markdircmd(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 lsmarkcmd(UAContext *ua, TREE_CTX *tree);
 static int dircmd(UAContext *ua, TREE_CTX *tree);
+static int estimatecmd(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 unmarkdircmd(UAContext *ua, TREE_CTX *tree);
 static int quitcmd(UAContext *ua, TREE_CTX *tree);
+static int donecmd(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_("count"),      countcmd,     _("count marked files in and below the cd")},
  { 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_("done"),       donecmd,      _("leave file selection mode")},
+ { N_("estimate"),   estimatecmd,  _("estimate restore size")},
+ { N_("exit"),       donecmd,      _("exit = done")},
+ { N_("find"),       findcmd,      _("find files -- wildcards allowed")},
  { N_("help"),       helpcmd,      _("print help")},
+ { N_("ls"),         lscmd,        _("list current directory -- wildcards allowed")},    
+ { N_("lsmark"),     lsmarkcmd,    _("list the marked files in and below the cd")},    
+ { N_("mark"),       markcmd,      _("mark file to be restored")},
+ { N_("markdir"),    markdircmd,   _("mark directory entry to be restored -- nonrecursive")},
+ { N_("pwd"),        pwdcmd,       _("print current working directory")},
+ { N_("unmark"),     unmarkcmd,    _("unmark file to be restored")},
+ { N_("unmarkdir"),  unmarkdircmd, _("unmark directory -- no recursion")},
+ { N_("quit"),       quitcmd,      _("quit")},
  { N_("?"),          helpcmd,      _("print help")},    
             };
 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
@@ -71,9 +82,10 @@ static struct cmdstruct commands[] = {
  *  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)
+bool user_select_files_from_tree(TREE_CTX *tree)
 {
    char cwd[2000];
+   bool stat;
    /* Get a new context so we don't destroy restore command args */
    UAContext *ua = new_ua_context(tree->ua->jcr);
    ua->UA_sock = tree->ua->UA_sock;   /* patch in UA socket */
@@ -90,7 +102,7 @@ void user_select_files_from_tree(TREE_CTX *tree)
    tree_getpath(tree->node, cwd, sizeof(cwd));
    bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
    for ( ;; ) {       
-      int found, len, stat, i;
+      int found, len, i;
       if (!get_cmd(ua, "$ ")) {
         break;
       }
@@ -101,7 +113,7 @@ void user_select_files_from_tree(TREE_CTX *tree)
 
       len = strlen(ua->argk[0]);
       found = 0;
-      stat = 0;
+      stat = false;
       for (i=0; i<(int)comsize; i++)      /* search for command */
         if (strncasecmp(ua->argk[0],  _(commands[i].key), len) == 0) {
            stat = (*commands[i].func)(ua, tree);   /* go execute command */
@@ -117,7 +129,10 @@ void user_select_files_from_tree(TREE_CTX *tree)
       }
    }
    ua->UA_sock = NULL;                /* don't release restore socket */
+   stat = !ua->quit;
+   ua->quit = false;
    free_ua_context(ua);              /* get rid of temp UA context */
+   return stat;
 }
 
 
@@ -127,13 +142,21 @@ void user_select_files_from_tree(TREE_CTX *tree)
  *  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)
+ *
+ *   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
  */
 int insert_tree_handler(void *ctx, int num_fields, char **row)
 {
+   struct stat statp;
    TREE_CTX *tree = (TREE_CTX *)ctx;
    char fname[5000];
    TREE_NODE *node, *new_node;
    int type;
+   bool hard_link, first_time, ok;
+   int FileIndex;
+   JobId_t JobId;
 
    strip_trailing_junk(row[1]);
    if (*row[1] == 0) {               /* no filename => directory */
@@ -145,25 +168,56 @@ int insert_tree_handler(void *ctx, int num_fields, char **row)
    } else {
       type = TN_FILE;
    }
-   bsnprintf(fname, sizeof(fname), "%s%s", row[0], row[1]);
    if (tree->avail_node) {
-      node = tree->avail_node;
+      node = tree->avail_node;       /* if prev node avail use it */
    } else {
-      node = new_tree_node(tree->root, type);
+      node = new_tree_node(tree->root, type);  /* get new node */
       tree->avail_node = node;
    }
+   hard_link = (decode_LinkFI(row[4], &statp) != 0);
+   bsnprintf(fname, sizeof(fname), "%s%s%s", row[0], row[1], "");
    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;
+      first_time = false;            /* we saw this file before */
+      tree->avail_node = node;       /* node already exists */
    } else {
-      tree->avail_node = NULL;
+      first_time = true;             /* first time we saw this file */
+      tree->avail_node = NULL;       /* added node to tree */
+   }
+   JobId = (JobId_t)str_to_int64(row[3]);
+   FileIndex = atoi(row[2]);
+   /*
+    * - The first time we see a file, we accept it.
+    * - In the same JobId, we accept only the first copy of a
+    *  hard linked file (the others are simply pointers).
+    * - In the same JobId, we accept the last copy of any other
+    *  file -- in particular directories.
+    *
+    * All the code to set ok could be condensed to a single
+    *  line, but it would be even harder to read.
+    */
+   ok = true;
+   if (!first_time && JobId == new_node->JobId) {
+      if ((hard_link && FileIndex > new_node->FileIndex) ||
+         (!hard_link && FileIndex < new_node->FileIndex)) {
+        ok = false;
+      }
+   }
+   if (ok) {
+      new_node->hard_link = hard_link;
+      new_node->FileIndex = FileIndex;
+      new_node->JobId = JobId;
+      new_node->type = type;
+      new_node->soft_link = S_ISLNK(statp.st_mode) != 0;
+      if (tree->all) {
+        new_node->extract = true;          /* extract all by default */
+        if (type == TN_DIR || type == TN_DIR_NLS) {
+           new_node->extract_dir = true;   /* if dir, extract it */
+        }
+      }
    }
-   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;
 }
@@ -174,78 +228,140 @@ int insert_tree_handler(void *ctx, int num_fields, char **row)
  *  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)
+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 = value;
+   node->extract = extract;
+   if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
+      node->extract_dir = extract;    /* set/clear dir too */
+   }
+   if (node->type != TN_NEWDIR) {
+      count++;
+   }
    /* For a non-file (i.e. directory), we see all the children */
-   if (node->type != TN_FILE) {
+   if (node->type != TN_FILE || (node->soft_link && node->child)) {
+      /* Recursive set children within directory */
       for (n=node->child; n; n=n->sibling) {
-        set_extract(ua, n, tree, value);
+        count += set_extract(ua, n, tree, extract);
+      }
+      /*
+       * Walk up tree marking any unextracted parent to be
+       * extracted.
+       */
+      if (extract) {
+        while (node->parent && !node->parent->extract_dir) {
+           node = node->parent;
+           node->extract_dir = true;
+        }
       }
-   } else if (value) {
+   } else if (extract) {
       char cwd[2000];
-      /* Ordinary file, we get the full path, look up the
+      /*
+       * 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)) {
+      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 */
         /*
          * If we point to a hard linked file, traverse the tree to
-         * find that file, and mark it for restoration as well. It
+         * find that file, and mark it to be restored 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;
+                 n->extract = true;
+                 if (n->type == TN_DIR || n->type == TN_DIR_NLS) {
+                    n->extract_dir = true;
+                 }
                  break;
               }
            }
         }
       }
    }
+   return count;
 }
 
+/*
+ * Recursively mark the current directory to be restored as 
+ *  well as all directories and files below it.
+ */
 static int markcmd(UAContext *ua, TREE_CTX *tree)
 {
    TREE_NODE *node;
+   int count = 0;
 
-   if (ua->argc < 2)
+   if (ua->argc < 2 || !tree->node->child) {
+      bsendmsg(ua, _("No files marked.\n"));
       return 1;
-   if (!tree->node->child) {    
+   }
+   for (int i=1; i < ua->argc; i++) {
+      for (node = tree->node->child; node; node=node->sibling) {
+        if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
+           count += set_extract(ua, node, tree, true);
+        }
+      }
+   }
+   if (count == 0) {
+      bsendmsg(ua, _("No files marked.\n"));
+   } else {
+      bsendmsg(ua, _("%d file%s marked.\n"), count, count==0?"":"s");
+   }
+   return 1;
+}
+
+static int markdircmd(UAContext *ua, TREE_CTX *tree)
+{
+   TREE_NODE *node;
+   int count = 0;
+
+   if (ua->argc < 2 || !tree->node->child) {
+      bsendmsg(ua, _("No files marked.\n"));
       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);
+   for (int i=1; i < ua->argc; i++) {
+      for (node = tree->node->child; node; node=node->sibling) {
+        if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
+           if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
+              node->extract_dir = true;
+              count++;
+           }
+        }
       }
    }
+   if (count == 0) {
+      bsendmsg(ua, _("No directories marked.\n"));
+   } else {
+      bsendmsg(ua, _("%d director%s marked.\n"), count, count==1?"y":"ies");
+   }
    return 1;
 }
 
+
 static int countcmd(UAContext *ua, TREE_CTX *tree)
 {
-   int total, extract;
+   int total, num_extract;
 
-   total = extract = 0;
+   total = num_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++;
+        if (node->extract || node->extract_dir) {
+           num_extract++;
         }
       }
    }
-   bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
+   bsendmsg(ua, "%d total files/dirs. %d marked to be restored.\n", total, num_extract);
    return 1;
 }
 
@@ -261,8 +377,16 @@ static int findcmd(UAContext *ua, TREE_CTX *tree)
    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) {
+           char *tag;
            tree_getpath(node, cwd, sizeof(cwd));
-            bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
+           if (node->extract) {
+               tag = "*";
+           } else if (node->extract_dir) {
+               tag = "+";
+           } else {
+               tag = "";
+           }
+            bsendmsg(ua, "%s%s\n", tag, cwd);
         }
       }
    }
@@ -280,20 +404,62 @@ static int lscmd(UAContext *ua, TREE_CTX *tree)
    }
    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)?"/":"");
+        char *tag;
+        if (node->extract) {
+            tag = "*";
+        } else if (node->extract_dir) {
+            tag = "+";
+        } else {
+            tag = "";
+        }
+         bsendmsg(ua, "%s%s%s\n", tag, node->fname, node->child?"/":"");
       }
    }
    return 1;
 }
 
+/*
+ * Ls command that lists only the marked files
+ */
+static void rlsmark(UAContext *ua, TREE_NODE *node) 
+{
+   if (!node->child) {    
+      return;
+   }
+   for (node = node->child; node; node=node->sibling) {
+      if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
+         (node->extract || node->extract_dir)) {
+        char *tag;
+        if (node->extract) {
+            tag = "*";
+        } else if (node->extract_dir) {
+            tag = "+";
+        } else {
+            tag = "";
+        }
+         bsendmsg(ua, "%s%s%s\n", tag, node->fname, node->child?"/":"");
+        if (node->child) {
+           rlsmark(ua, node);
+        }
+      }
+   }
+}
+
+static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
+{
+   rlsmark(ua, tree->node);
+   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)
+static void ls_output(char *buf, char *fname, char *tag, struct stat *statp)
 {
    char *p, *f;
    char ec1[30];
@@ -308,13 +474,10 @@ static void ls_output(char *buf, char *fname, int extract, struct stat *statp)
    p += n;
    p = encode_time(statp->st_ctime, p);
    *p++ = ' ';
-   if (extract) {
-      *p++ = '*';
-   } else {
-      *p++ = ' ';
-   }
-   for (f=fname; *f; )
+   *p++ = *tag;
+   for (f=fname; *f; ) {
       *p++ = *f++;
+   }
    *p = 0;
 }
 
@@ -327,33 +490,96 @@ static int dircmd(UAContext *ua, TREE_CTX *tree)
    TREE_NODE *node;
    FILE_DBR fdbr;
    struct stat statp;
-   char buf[1000];
-   char cwd[1100];
+   char buf[1100];
+   char cwd[1100], *pcwd;
 
    if (!tree->node->child) {    
       return 1;
    }
    for (node = tree->node->child; node; node=node->sibling) {
+      char *tag;
       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
+        if (node->extract) {
+            tag = "*";
+        } else if (node->extract_dir) {
+            tag = "+";
+        } else {
+            tag = " ";
+        }
         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)) {
+        /*
+         * Strip / from soft links to directories.
+         *   This is because soft links to files have a trailing slash
+         *   when returned from tree_getpath, but db_get_file_attr...
+         *   treats soft links as files, so they do not have a trailing
+         *   slash like directory names.
+         */
+        if (node->type == TN_FILE && node->child) {
+           bstrncpy(buf, cwd, sizeof(buf));
+           pcwd = buf;
+           int len = strlen(buf);
+           if (len > 1) {
+              buf[len-1] = 0;        /* strip trailing / */
+           }
+        } else {
+           pcwd = cwd;
+        }
+        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 */
-           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)?"/":"");
+           memset(&statp, 0, sizeof(statp));
+        }
+        ls_output(buf, cwd, tag, &statp);
+         bsendmsg(ua, "%s\n", buf);
+      }
+   }
+   return 1;
+}
+
+
+static int estimatecmd(UAContext *ua, TREE_CTX *tree)
+{
+   int total, num_extract;
+   uint64_t total_bytes = 0;
+   FILE_DBR fdbr;
+   struct stat statp;
+   char cwd[1100];
+   char ec1[50];
+
+   total = num_extract = 0;
+   for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
+      if (node->type != TN_NEWDIR) {
+        total++;
+        /* If regular file, get size */
+        if (node->extract && node->type == TN_FILE) {
+           num_extract++;
+           tree_getpath(node, cwd, sizeof(cwd));
+           fdbr.FileId = 0;
+           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 */
+              if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
+                 total_bytes += statp.st_size;
+              }
+           }
+        /* Directory, count only */
+        } else if (node->extract || node->extract_dir) {
+           num_extract++;
         }
       }
    }
+   bsendmsg(ua, "%d total files; %d marked to be restored; %s bytes.\n", 
+           total, num_extract, edit_uint64_with_commas(total_bytes, ec1));
    return 1;
 }
 
 
+
 static int helpcmd(UAContext *ua, TREE_CTX *tree) 
 {
    unsigned int i;
@@ -413,21 +639,64 @@ static int pwdcmd(UAContext *ua, TREE_CTX *tree)
 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
 {
    TREE_NODE *node;
+   int count = 0;
 
-   if (ua->argc < 2)
+   if (ua->argc < 2 || !tree->node->child) {    
+      bsendmsg(ua, _("No files unmarked.\n"));
       return 1;
-   if (!tree->node->child) {    
+   }
+   for (int i=1; i < ua->argc; i++) {
+      for (node = tree->node->child; node; node=node->sibling) {
+        if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
+           count += set_extract(ua, node, tree, false);
+        }
+      }
+   }
+   if (count == 0) {
+      bsendmsg(ua, _("No files unmarked.\n"));
+   } else {
+      bsendmsg(ua, _("%d file%s unmarked.\n"), count, count==0?"":"s");
+   }
+   return 1;
+}
+
+static int unmarkdircmd(UAContext *ua, TREE_CTX *tree)
+{
+   TREE_NODE *node;
+   int count = 0;
+
+   if (ua->argc < 2 || !tree->node->child) {
+      bsendmsg(ua, _("No directories unmarked.\n"));
       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);
+
+   for (int i=1; i < ua->argc; i++) {
+      for (node = tree->node->child; node; node=node->sibling) {
+        if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
+           if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
+              node->extract_dir = false;
+              count++;
+           }
+        }
       }
    }
+
+   if (count == 0) {
+      bsendmsg(ua, _("No directories unmarked.\n"));
+   } else {
+      bsendmsg(ua, _("%d director%s unmarked.\n"), count, count==1?"y":"ies");
+   }
    return 1;
 }
 
+
+static int donecmd(UAContext *ua, TREE_CTX *tree) 
+{
+   return 0;
+}
+
 static int quitcmd(UAContext *ua, TREE_CTX *tree) 
 {
+   ua->quit = true;
    return 0;
 }