IVY-1602 Prevent cache corruption when attempting to copy a file to a destination...
authorJaikiran Pai <jaikiran@apache.org>
Fri, 1 Feb 2019 06:10:35 +0000 (11:40 +0530)
committerJaikiran Pai <jaikiran@apache.org>
Fri, 1 Feb 2019 07:25:04 +0000 (12:55 +0530)
src/java/org/apache/ivy/util/FileUtil.java
test/java/org/apache/ivy/ant/FileUtilTest.java

index 9c780f2..c845364 100644 (file)
@@ -34,6 +34,7 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.URL;
 import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -104,6 +105,16 @@ public final class FileUtil {
         return true;
     }
 
+    /**
+     * This is the same as calling {@link #copy(File, File, CopyProgressListener, boolean)} with
+     * {@code overwrite} param as {@code true}
+     *
+     * @param src  The source to copy
+     * @param dest The destination
+     * @param l    A {@link CopyProgressListener}. Can be null
+     * @return Returns true if the file was copied. Else returns false
+     * @throws IOException If any exception occurs during the copy operation
+     */
     public static boolean copy(File src, File dest, CopyProgressListener l) throws IOException {
         return copy(src, dest, l, false);
     }
@@ -163,6 +174,19 @@ public final class FileUtil {
             return deepCopy(src, dest, l, overwrite);
         }
         // else it is a file copy
+        // check if it's the same file (the src and the dest). if they are the same, skip the copy
+        try {
+            if (Files.isSameFile(src.toPath(), dest.toPath())) {
+                Message.verbose("Skipping copy of file " + src + " to " + dest + " since they are the same file");
+                // we consider the file as copied if overwrite is true
+                return overwrite;
+            }
+        } catch (NoSuchFileException nsfe) {
+            // ignore and move on and attempt the copy
+        } catch (IOException ioe) {
+            // log and move on and attempt the copy
+            Message.verbose("Could not determine if " + src + " and dest " + dest + " are the same file", ioe);
+        }
         copy(new FileInputStream(src), dest, l);
         long srcLen = src.length();
         long destLen = dest.length();
index 7eb28d6..a3ca469 100644 (file)
  */
 package org.apache.ivy.ant;
 
+import org.apache.ivy.util.CopyProgressListener;
 import org.apache.ivy.util.FileUtil;
+import org.apache.ivy.util.Message;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.BeforeClass;
 import org.junit.Test;
 
 import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -33,6 +44,24 @@ import static org.junit.Assert.assertTrue;
  */
 public class FileUtilTest {
 
+    private static boolean symlinkCapable = false;
+
+    @BeforeClass
+    public static void beforeClass() {
+        try {
+            final Path tmpFile = Files.createTempFile(null, null);
+            tmpFile.toFile().deleteOnExit();
+            final Path symlink = Files.createSymbolicLink(Paths.get(Files.createTempDirectory(null).toString(),
+                    "symlink-test-file"), tmpFile);
+            symlinkCapable = true;
+            symlink.toFile().deleteOnExit();
+        } catch (IOException ioe) {
+            // ignore and move on
+            symlinkCapable = false;
+            Message.info("Current system isn't considered to have symlink capability due to ", ioe);
+        }
+    }
+
     /**
      * Tests that {@link FileUtil#normalize(String)} works as expected for some basic file paths
      */
@@ -83,4 +112,43 @@ public class FileUtilTest {
         }
     }
 
+    /**
+     * Tests that the call to {@link FileUtil#copy(File, File, CopyProgressListener)} doesn't corrupt
+     * the source file if the destination file resolves back to the source file being copied
+     *
+     * @throws Exception
+     * @see <a href="https://issues.apache.org/jira/browse/IVY-1602">IVY-1602</a> for more details
+     */
+    @Test
+    public void testCopyOfSameFile() throws Exception {
+        Assume.assumeTrue("Skipping test due to system not having symlink capability", symlinkCapable);
+        final Path srcDir = Files.createTempDirectory(null);
+        srcDir.toFile().deleteOnExit();
+        // create a src file
+        final Path srcFile = Paths.get(srcDir.toString(), "helloworld.txt");
+        srcFile.toFile().deleteOnExit();
+        final byte[] fileContent = "Hello world!!!".getBytes(StandardCharsets.UTF_8);
+        Files.write(srcFile, fileContent);
+
+        final Path destDir = Paths.get(Files.createTempDirectory(null).toString(), "symlink-dest");
+        destDir.toFile().deleteOnExit();
+        // now create a symlink to the dir containing the src file we intend to copy later
+        Files.createSymbolicLink(destDir, srcDir);
+        // at this point destDir is a symlink to the srcDir and the srcDir contains the srcFile.
+        // we now attempt to copy the srcFile to a destination which resolves back the same srcFile
+        final Path destFile = Paths.get(destDir.toString(), srcFile.getFileName().toString());
+        FileUtil.copy(srcFile.toFile(), destFile.toFile(), null, false);
+        // make sure the copy didn't corrupt the source file
+        Assert.assertTrue("Unexpected content in source file " + srcFile, Arrays.equals(fileContent, Files.readAllBytes(srcFile)));
+        // also check the dest file has the same content as source file after the copy operation
+        Assert.assertTrue("Unexpected content in dest file " + destFile, Arrays.equals(fileContent, Files.readAllBytes(destFile)));
+
+        // do the same tests now with overwrite = true
+        FileUtil.copy(srcFile.toFile(), destFile.toFile(), null, true);
+        // make sure the copy didn't corrupt the source file
+        Assert.assertTrue("Unexpected content in source file " + srcFile, Arrays.equals(fileContent, Files.readAllBytes(srcFile)));
+        // also check the dest file has the same content as source file after the copy operation
+        Assert.assertTrue("Unexpected content in dest file " + destFile, Arrays.equals(fileContent, Files.readAllBytes(destFile)));
+    }
+
 }