Added utils.get_relative_path() to compute the path of an absolute path
as relative to a given directory path and used it when creating the
test results sysinfo/current_reboot symlink to create a relative symlink
that would still be valid when relocated.

Added a "preserve_symlinks" default False keyword argument to
AbstractSSHHost.get_file/send_file to tell rsync to try to preserve the
original symlink and not transform them into the referenced
file/directory when copied (only for rsync as scp has no way to do
this). Used the new flag when fetching the test result directory to
preserve symlinks. Along with the previous change this should fix an
issue of >100k sysinfo bloat because of duplicated content instead of
preserving the client symlink.

Signed-off-by: Mihai Rusu <dizzy@google.com>


git-svn-id: http://test.kernel.org/svn/autotest/trunk@3104 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/client/bin/base_sysinfo.py b/client/bin/base_sysinfo.py
index f4c0880..9ff7907 100644
--- a/client/bin/base_sysinfo.py
+++ b/client/bin/base_sysinfo.py
@@ -249,7 +249,10 @@
         reboot_dir = self._get_boot_subdir()
         assert os.path.exists(reboot_dir)
         symlink_dest = os.path.join(test_sysinfodir, "reboot_current")
-        os.symlink(reboot_dir, symlink_dest)
+        os.symlink(
+            # create a relative symlink
+            utils.get_relative_path(reboot_dir, os.path.dirname(symlink_dest)),
+            symlink_dest)
 
         # run all the standard logging commands
         for log in self.test_loggables:
diff --git a/client/common_lib/utils.py b/client/common_lib/utils.py
index 2a13c4e..711ad42 100644
--- a/client/common_lib/utils.py
+++ b/client/common_lib/utils.py
@@ -915,3 +915,39 @@
     if pidf:
       pidf.write("%s\n" % os.getpid())
       pidf.close()
+
+
+def get_relative_path(path, reference):
+    """Given 2 absolute paths "path" and "reference", compute the path of
+    "path" as relative to the directory "reference".
+
+    @param path the absolute path to convert to a relative path
+    @param reference an absolute directory path to which the relative
+        path will be computed
+    """
+    # normalize the paths (remove double slashes, etc)
+    assert(os.path.isabs(path))
+    assert(os.path.isabs(reference))
+
+    path = os.path.normpath(path)
+    reference = os.path.normpath(reference)
+
+    # we could use os.path.split() but it splits from the end
+    path_list = path.split(os.path.sep)[1:]
+    ref_list = reference.split(os.path.sep)[1:]
+
+    # find the longest leading common path
+    for i in xrange(min(len(path_list), len(ref_list))):
+        if path_list[i] != ref_list[i]:
+            # decrement i so when exiting this loop either by no match or by
+            # end of range we are one step behind
+            i -= 1
+            break
+    i += 1
+    # drop the common part of the paths, not interested in that anymore
+    del path_list[:i]
+
+    # for each uncommon component in the reference prepend a ".."
+    path_list[:0] = ['..'] * (len(ref_list) - i)
+
+    return os.path.join(*path_list)
diff --git a/client/common_lib/utils_unittest.py b/client/common_lib/utils_unittest.py
index ae45894..1aca533 100644
--- a/client/common_lib/utils_unittest.py
+++ b/client/common_lib/utils_unittest.py
@@ -571,6 +571,23 @@
                                 "subfile")
 
 
+class test_get_relative_path(unittest.TestCase):
+    def test_not_absolute(self):
+        self.assertRaises(AssertionError, utils.get_relative_path, "a", "b")
+
+    def test_same_dir(self):
+        self.assertEqual(utils.get_relative_path("/a/b/c", "/a/b"), "c")
+
+    def test_forward_dir(self):
+        self.assertEqual(utils.get_relative_path("/a/b/c/d", "/a/b"), "c/d")
+
+    def test_previous_dir(self):
+        self.assertEqual(utils.get_relative_path("/a/b", "/a/b/c/d"), "../..")
+
+    def test_parallel_dir(self):
+        self.assertEqual(utils.get_relative_path("/a/c/d", "/a/b/c/d"),
+                         "../../../c/d")
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/server/autotest.py b/server/autotest.py
index d6b185e..a8d8a96 100644
--- a/server/autotest.py
+++ b/server/autotest.py
@@ -726,7 +726,7 @@
         try:
             keyval_path = self._prepare_for_copying_logs()
             self.host.get_file(self.client_results_dir + '/',
-                               self.server_results_dir)
+                               self.server_results_dir, preserve_symlinks=True)
             self._process_copied_logs(keyval_path)
             self._postprocess_copied_logs()
         except Exception:
diff --git a/server/hosts/abstract_ssh.py b/server/hosts/abstract_ssh.py
index 6181762..9c41c9a 100644
--- a/server/hosts/abstract_ssh.py
+++ b/server/hosts/abstract_ssh.py
@@ -43,7 +43,7 @@
                                " ".join(paths))
 
 
-    def _make_rsync_cmd(self, sources, dest, delete_dest):
+    def _make_rsync_cmd(self, sources, dest, delete_dest, preserve_symlinks):
         """ Given a list of source paths and a destination path, produces the
         appropriate rsync command for copying them. Remote paths must be
         pre-encoded. """
@@ -52,8 +52,13 @@
             delete_flag = "--delete"
         else:
             delete_flag = ""
-        command = "rsync -L %s --timeout=1800 --rsh='%s' -az %s %s"
-        return command % (delete_flag, ssh_cmd, " ".join(sources), dest)
+        if preserve_symlinks:
+            symlink_flag = ""
+        else:
+            symlink_flag = "-L"
+        command = "rsync %s %s --timeout=1800 --rsh='%s' -az %s %s"
+        return command % (symlink_flag, delete_flag, ssh_cmd,
+                          " ".join(sources), dest)
 
 
     def _make_scp_cmd(self, sources, dest):
@@ -149,7 +154,8 @@
             set_file_privs(dest)
 
 
-    def get_file(self, source, dest, delete_dest=False, preserve_perm=True):
+    def get_file(self, source, dest, delete_dest=False, preserve_perm=True,
+                 preserve_symlinks=False):
         """
         Copy files from the remote host to a local path.
 
@@ -172,6 +178,8 @@
                              source
                 preserve_perm: tells get_file() to try to preserve the sources
                                permissions on files and dirs
+                preserve_symlinks: try to preserve symlinks instead of
+                                   transforming them into files/dirs on copy
 
         Raises:
                 AutoservRunError: the scp command failed
@@ -184,7 +192,7 @@
             remote_source = self._encode_remote_paths(source)
             local_dest = utils.sh_escape(dest)
             rsync = self._make_rsync_cmd([remote_source], local_dest,
-                                         delete_dest)
+                                         delete_dest, preserve_symlinks)
             utils.run(rsync)
         except error.CmdError, e:
             logging.warn("warning: rsync failed with: %s", e)
@@ -213,7 +221,8 @@
             self._set_umask_perms(dest)
 
 
-    def send_file(self, source, dest, delete_dest=False):
+    def send_file(self, source, dest, delete_dest=False,
+                  preserve_symlinks=False):
         """
         Copy files from a local path to the remote host.
 
@@ -234,6 +243,9 @@
                 delete_dest: if this is true, the command will also clear
                              out any old files at dest that are not in the
                              source
+                preserve_symlinks: controls if symlinks on the source will be
+                    copied as such on the destination or transformed into the
+                    referenced file/directory
 
         Raises:
                 AutoservRunError: the scp command failed
@@ -245,7 +257,7 @@
         try:
             local_sources = [utils.sh_escape(path) for path in source]
             rsync = self._make_rsync_cmd(local_sources, remote_dest,
-                                         delete_dest)
+                                         delete_dest, preserve_symlinks)
             utils.run(rsync)
         except error.CmdError, e:
             logging.warn("warning: rsync failed with: %s", e)