diff --git a/src/etc/testcases/taskdefs/copy.xml b/src/etc/testcases/taskdefs/copy.xml
index bf4441c17..2601556fe 100644
--- a/src/etc/testcases/taskdefs/copy.xml
+++ b/src/etc/testcases/taskdefs/copy.xml
@@ -265,4 +265,31 @@ a=b=
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/org/apache/tools/ant/util/ResourceUtils.java b/src/main/org/apache/tools/ant/util/ResourceUtils.java
index 9b246a0a3..c8989f9d0 100644
--- a/src/main/org/apache/tools/ant/util/ResourceUtils.java
+++ b/src/main/org/apache/tools/ant/util/ResourceUtils.java
@@ -417,7 +417,7 @@ public class ResourceUtils {
final File sourceFile =
source.as(FileProvider.class).getFile();
try {
- copyUsingFileChannels(sourceFile, destFile);
+ copyUsingFileChannels(sourceFile, destFile, project);
copied = true;
} catch (final IOException ex) {
String msg = "Attempt to copy " + sourceFile
@@ -641,11 +641,17 @@ public class ResourceUtils {
final Project project)
throws IOException {
+ if (areSame(source, dest)) {
+ // copying the "same" file to itself will corrupt the file, so we skip it
+ log(project, "Skipping (self) copy of " + source + " to " + dest);
+ return;
+ }
+
try (Reader in = filterWith(project, inputEncoding, filterChains,
- source.getInputStream());
- BufferedWriter out = new BufferedWriter(new OutputStreamWriter(
- getOutputStream(dest, append, project),
- charsetFor(outputEncoding)))) {
+ source.getInputStream());
+ BufferedWriter out = new BufferedWriter(new OutputStreamWriter(
+ getOutputStream(dest, append, project),
+ charsetFor(outputEncoding)))) {
final LineTokenizer lineTokenizer = new LineTokenizer();
lineTokenizer.setIncludeDelims(true);
@@ -691,11 +697,18 @@ public class ResourceUtils {
final String outputEncoding,
final Project project)
throws IOException {
+
+ if (areSame(source, dest)) {
+ // copying the "same" file to itself will corrupt the file, so we skip it
+ log(project, "Skipping (self) copy of " + source + " to " + dest);
+ return;
+ }
+
try (Reader in = filterWith(project, inputEncoding, filterChains,
- source.getInputStream());
- BufferedWriter out = new BufferedWriter(new OutputStreamWriter(
- getOutputStream(dest, append, project),
- charsetFor(outputEncoding)))) {
+ source.getInputStream());
+ BufferedWriter out = new BufferedWriter(new OutputStreamWriter(
+ getOutputStream(dest, append, project),
+ charsetFor(outputEncoding)))) {
final char[] buffer = new char[FileUtils.BUF_SIZE];
while (true) {
final int nRead = in.read(buffer, 0, buffer.length);
@@ -705,12 +718,18 @@ public class ResourceUtils {
out.write(buffer, 0, nRead);
}
}
+
}
private static void copyUsingFileChannels(final File sourceFile,
- final File destFile)
+ final File destFile, final Project project)
throws IOException {
+ if (FileUtils.getFileUtils().areSame(sourceFile, destFile)) {
+ // copying the "same" file to itself will corrupt the file, so we skip it
+ log(project, "Skipping (self) copy of " + sourceFile + " to " + destFile);
+ return;
+ }
final File parent = destFile.getParentFile();
if (parent != null && !parent.isDirectory()
&& !(parent.mkdirs() || parent.isDirectory())) {
@@ -738,8 +757,14 @@ public class ResourceUtils {
private static void copyUsingStreams(final Resource source, final Resource dest,
final boolean append, final Project project)
throws IOException {
+
+ if (areSame(source, dest)) {
+ // copying the "same" file to itself will corrupt the file, so we skip it
+ log(project, "Skipping (self) copy of " + source + " to " + dest);
+ return;
+ }
try (InputStream in = source.getInputStream();
- OutputStream out = getOutputStream(dest, append, project)) {
+ OutputStream out = getOutputStream(dest, append, project)) {
final byte[] buffer = new byte[FileUtils.BUF_SIZE];
int count = 0;
@@ -768,6 +793,33 @@ public class ResourceUtils {
return resource.getOutputStream();
}
+ private static boolean areSame(final Resource resource1, final Resource resource2) throws IOException {
+ if (resource1 == null || resource2 == null) {
+ return false;
+ }
+ final FileProvider fileResource1 = resource1.as(FileProvider.class);
+ if (fileResource1 == null) {
+ return false;
+ }
+ final FileProvider fileResource2 = resource2.as(FileProvider.class);
+ if (fileResource2 == null) {
+ return false;
+ }
+ return FileUtils.getFileUtils().areSame(fileResource1.getFile(), fileResource2.getFile());
+ }
+
+ private static void log(final Project project, final String message) {
+ log(project, message, Project.MSG_VERBOSE);
+ }
+
+ private static void log(final Project project, final String message, final int level) {
+ if (project == null) {
+ System.out.println(message);
+ } else {
+ project.log(message, level);
+ }
+ }
+
public interface ResourceSelectorProvider {
ResourceSelector getTargetSelectorForSource(Resource source);
}
diff --git a/src/tests/junit/org/apache/tools/ant/taskdefs/CopyTest.java b/src/tests/junit/org/apache/tools/ant/taskdefs/CopyTest.java
index a7a32a9a5..719bc4456 100644
--- a/src/tests/junit/org/apache/tools/ant/taskdefs/CopyTest.java
+++ b/src/tests/junit/org/apache/tools/ant/taskdefs/CopyTest.java
@@ -21,12 +21,16 @@ package org.apache.tools.ant.taskdefs;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.BuildFileRule;
import org.apache.tools.ant.FileUtilities;
+import org.apache.tools.ant.taskdefs.condition.Os;
import org.apache.tools.ant.util.FileUtils;
+import org.junit.Assert;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
+import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
@@ -189,7 +193,7 @@ public class CopyTest {
assertTrue(ex.getMessage().endsWith(" does not exist."));
}
}
-
+
@Test
public void testFileResourcePlain() {
buildRule.executeTarget("testFileResourcePlain");
@@ -212,7 +216,7 @@ public class CopyTest {
assertTrue(file2.exists());
assertTrue(file3.exists());
}
-
+
@Test
public void testFileResourceWithFilter() {
buildRule.executeTarget("testFileResourceWithFilter");
@@ -225,7 +229,7 @@ public class CopyTest {
// no-op: not a real business error
}
}
-
+
@Test
public void testPathAsResource() {
buildRule.executeTarget("testPathAsResource");
@@ -236,7 +240,7 @@ public class CopyTest {
assertTrue(file2.exists());
assertTrue(file3.exists());
}
-
+
@Test
public void testZipfileset() {
buildRule.executeTarget("testZipfileset");
@@ -252,7 +256,7 @@ public class CopyTest {
public void testDirset() {
buildRule.executeTarget("testDirset");
}
-
+
@Ignore("Previously ignored due to naming convention")
@Test
public void testResourcePlain() {
@@ -276,5 +280,60 @@ public class CopyTest {
public void testOnlineResources() {
buildRule.executeTarget("testOnlineResources");
}
-
+
+ /**
+ * Tests that the {@code copy} task doesn't corrupt the source file, if the target of the copy operation is a symlink
+ * to the source file being copied
+ *
+ * @throws Exception
+ * @see issue 60644
+ */
+ @Test
+ public void testCopyToSymlinkedSelf() throws Exception {
+ // we are only going to test against systems that support symlinks
+ Assume.assumeTrue("Symlinks not supported on this operating system", Os.isFamily(Os.FAMILY_UNIX));
+
+ // setup the source files to run copying against
+ buildRule.executeTarget("setupSelfCopyTesting");
+ final File testDir = new File(buildRule.getProject().getProperty("self.copy.test.root.dir"));
+ Assert.assertTrue(testDir + " was expected to be a directory", testDir.isDirectory());
+ final File srcFile = new File(testDir, "file.txt");
+ Assert.assertTrue("Source file " + srcFile + " was expected to be a file", srcFile.isFile());
+ final long originalFileSize = srcFile.length();
+ final String originalContent;
+ final BufferedReader reader = new BufferedReader(new FileReader(srcFile));
+ try {
+ originalContent = FileUtils.readFully(reader);
+ } finally {
+ reader.close();
+ }
+ Assert.assertTrue("Content missing in file " + srcFile, originalContent != null && originalContent.length() > 0);
+
+ // run the copy tests
+ buildRule.executeTarget("testSelfCopy");
+ // make sure the source file hasn't been impacted by the copy
+ assertSizeAndContent(srcFile, originalFileSize, originalContent);
+ final File symlinkedFile = new File(testDir, "sylmink-file.txt");
+ Assert.assertTrue(symlinkedFile + " was expected to be a file", symlinkedFile.isFile());
+ assertSizeAndContent(symlinkedFile, originalFileSize, originalContent);
+
+ final File symlinkedTestDir = new File(buildRule.getProject().getProperty("self.copy.test.symlinked.dir"));
+ Assert.assertTrue(symlinkedTestDir + " was expected to be a directory", symlinkedTestDir.isDirectory());
+ assertSizeAndContent(new File(symlinkedTestDir, "file.txt"), originalFileSize, originalContent);
+ assertSizeAndContent(new File(symlinkedTestDir, "sylmink-file.txt"), originalFileSize, originalContent);
+
+ }
+
+ private void assertSizeAndContent(final File file, final long expectedSize, final String expectedContent) throws IOException {
+ Assert.assertTrue(file + " was expected to be a file", file.isFile());
+ Assert.assertEquals("Unexpected size of file " + file, expectedSize, file.length());
+ final BufferedReader reader = new BufferedReader(new FileReader(file));
+ final String content;
+ try {
+ content = FileUtils.readFully(reader);
+ } finally {
+ reader.close();
+ }
+ Assert.assertEquals("Unexpected content in file " + file, expectedContent, content);
+ }
}