Author: niallp
Date: Sat Jan 5 07:05:55 2008
New Revision: 609147
URL: http://svn.apache.org/viewvc?rev=609147&view=rev
Log:
IO-141 - Infinite loop on FileUtils.copyDirectory when the destination
directory is within the source directory - reported by Mark Bryan Yu
Modified:
commons/proper/io/trunk/RELEASE-NOTES.txt
commons/proper/io/trunk/src/java/org/apache/commons/io/FileUtils.java
commons/proper/io/trunk/src/test/org/apache/commons/io/FileUtilsTestCase.java
Modified: commons/proper/io/trunk/RELEASE-NOTES.txt
URL:
http://svn.apache.org/viewvc/commons/proper/io/trunk/RELEASE-NOTES.txt?rev=609147&r1=609146&r2=609147&view=diff
==============================================================================
--- commons/proper/io/trunk/RELEASE-NOTES.txt (original)
+++ commons/proper/io/trunk/RELEASE-NOTES.txt Sat Jan 5 07:05:55 2008
@@ -28,6 +28,8 @@
--------------------
- FileUtils
- forceDelete of orphaned Softlinks does not work [IO-147]
+ - Infinite loop on FileUtils.copyDirectory when the destination directory is
within
+ the source directory [IO-141]
Enhancements from 1.3.2
-----------------------
Modified: commons/proper/io/trunk/src/java/org/apache/commons/io/FileUtils.java
URL:
http://svn.apache.org/viewvc/commons/proper/io/trunk/src/java/org/apache/commons/io/FileUtils.java?rev=609147&r1=609146&r2=609147&view=diff
==============================================================================
--- commons/proper/io/trunk/src/java/org/apache/commons/io/FileUtils.java
(original)
+++ commons/proper/io/trunk/src/java/org/apache/commons/io/FileUtils.java Sat
Jan 5 07:05:55 2008
@@ -25,6 +25,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
@@ -781,7 +782,20 @@
if (srcDir.getCanonicalPath().equals(destDir.getCanonicalPath())) {
throw new IOException("Source '" + srcDir + "' and destination '"
+ destDir + "' are the same");
}
- doCopyDirectory(srcDir, destDir, preserveFileDate);
+
+ // Cater for destination being directory within the source directory
(see IO-141)
+ List exclusionList = null;
+ if (destDir.getCanonicalPath().startsWith(srcDir.getCanonicalPath())) {
+ File[] srcFiles = srcDir.listFiles();
+ if (srcFiles != null && srcFiles.length > 0) {
+ exclusionList = new ArrayList(srcFiles.length);
+ for (int i = 0; i < srcFiles.length; i++) {
+ File copiedFile = new File(destDir, srcFiles[i].getName());
+ exclusionList.add(copiedFile.getCanonicalPath());
+ }
+ }
+ }
+ doCopyDirectory(srcDir, destDir, preserveFileDate, exclusionList);
}
/**
@@ -790,10 +804,12 @@
* @param srcDir the validated source directory, must not be
<code>null</code>
* @param destDir the validated destination directory, must not be
<code>null</code>
* @param preserveFileDate whether to preserve the file date
+ * @param exclusionList List of files and directories to exclude from the
copy, may be null
* @throws IOException if an error occurs
* @since Commons IO 1.1
*/
- private static void doCopyDirectory(File srcDir, File destDir, boolean
preserveFileDate) throws IOException {
+ private static void doCopyDirectory(File srcDir, File destDir, boolean
preserveFileDate,
+ List exclusionList) throws IOException {
if (destDir.exists()) {
if (destDir.isDirectory() == false) {
throw new IOException("Destination '" + destDir + "' exists
but is not a directory");
@@ -816,10 +832,12 @@
}
for (int i = 0; i < files.length; i++) {
File copiedFile = new File(destDir, files[i].getName());
- if (files[i].isDirectory()) {
- doCopyDirectory(files[i], copiedFile, preserveFileDate);
- } else {
- doCopyFile(files[i], copiedFile, preserveFileDate);
+ if (exclusionList == null ||
!exclusionList.contains(files[i].getCanonicalPath())) {
+ if (files[i].isDirectory()) {
+ doCopyDirectory(files[i], copiedFile, preserveFileDate,
exclusionList);
+ } else {
+ doCopyFile(files[i], copiedFile, preserveFileDate);
+ }
}
}
}
Modified:
commons/proper/io/trunk/src/test/org/apache/commons/io/FileUtilsTestCase.java
URL:
http://svn.apache.org/viewvc/commons/proper/io/trunk/src/test/org/apache/commons/io/FileUtilsTestCase.java?rev=609147&r1=609146&r2=609147&view=diff
==============================================================================
---
commons/proper/io/trunk/src/test/org/apache/commons/io/FileUtilsTestCase.java
(original)
+++
commons/proper/io/trunk/src/test/org/apache/commons/io/FileUtilsTestCase.java
Sat Jan 5 07:05:55 2008
@@ -23,6 +23,7 @@
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
@@ -60,6 +61,11 @@
*/
private static final int TEST_DIRECTORY_SIZE = 0;
+ /**
+ * List files recursively
+ */
+ private static final ListDirectoryWalker LIST_WALKER = new
ListDirectoryWalker();
+
/** Delay in milliseconds to make sure test for "last modified date" are
accurate */
//private static final int LAST_MODIFIED_DELAY = 600;
@@ -732,6 +738,57 @@
assertEquals(true, new File(destDir, "sub/A.txt").exists());
}
+ /** Test for IO-141 */
+ public void testCopyDirectoryToChild() throws Exception {
+ File grandParentDir = new File(getTestDirectory(), "grandparent");
+ File parentDir = new File(grandParentDir, "parent");
+ File childDir = new File(parentDir, "child");
+ createFilesForTestCopyDirectory(grandParentDir, parentDir, childDir);
+
+ long expectedCount = LIST_WALKER.list(grandParentDir).size() +
+ LIST_WALKER.list(parentDir).size();
+ long expectedSize = FileUtils.sizeOfDirectory(grandParentDir) +
+ FileUtils.sizeOfDirectory(parentDir);
+ FileUtils.copyDirectory(parentDir, childDir);
+ assertEquals(expectedCount, LIST_WALKER.list(grandParentDir).size());
+ assertEquals(expectedSize, FileUtils.sizeOfDirectory(grandParentDir));
+ }
+
+ /** Test for IO-141 */
+ public void testCopyDirectoryToGrandChild() throws Exception {
+ File grandParentDir = new File(getTestDirectory(), "grandparent");
+ File parentDir = new File(grandParentDir, "parent");
+ File childDir = new File(parentDir, "child");
+ createFilesForTestCopyDirectory(grandParentDir, parentDir, childDir);
+
+ long expectedCount = (LIST_WALKER.list(grandParentDir).size() * 2);
+ long expectedSize = (FileUtils.sizeOfDirectory(grandParentDir) * 2);
+ FileUtils.copyDirectory(grandParentDir, childDir);
+ assertEquals(expectedCount, LIST_WALKER.list(grandParentDir).size());
+ assertEquals(expectedSize, FileUtils.sizeOfDirectory(grandParentDir));
+ }
+
+ private void createFilesForTestCopyDirectory(File grandParentDir, File
parentDir, File childDir) throws Exception {
+ File childDir2 = new File(parentDir, "child2");
+ File grandChildDir = new File(childDir, "grandChild");
+ File grandChild2Dir = new File(childDir2, "grandChild2");
+ File file1 = new File(grandParentDir, "file1.txt");
+ File file2 = new File(parentDir, "file2.txt");
+ File file3 = new File(childDir, "file3.txt");
+ File file4 = new File(childDir2, "file4.txt");
+ File file5 = new File(grandChildDir, "file5.txt");
+ File file6 = new File(grandChild2Dir, "file6.txt");
+ FileUtils.deleteDirectory(grandParentDir);
+ grandChildDir.mkdirs();
+ grandChild2Dir.mkdirs();
+ FileUtils.writeStringToFile(file1, "File 1 in grandparent", "UTF8");
+ FileUtils.writeStringToFile(file2, "File 2 in parent", "UTF8");
+ FileUtils.writeStringToFile(file3, "File 3 in child", "UTF8");
+ FileUtils.writeStringToFile(file4, "File 4 in child2", "UTF8");
+ FileUtils.writeStringToFile(file5, "File 5 in grandChild", "UTF8");
+ FileUtils.writeStringToFile(file6, "File 6 in grandChild2", "UTF8");
+ }
+
public void testCopyDirectoryErrors() throws Exception {
try {
FileUtils.copyDirectory(null, null);
@@ -1183,6 +1240,31 @@
long resultValue = testChecksum.getValue();
assertEquals(expectedValue, resultValue);
+ }
+
+ /**
+ * DirectoryWalker implementation that recursively lists all files and
directories.
+ */
+ static class ListDirectoryWalker extends DirectoryWalker {
+ ListDirectoryWalker() {
+ super();
+ }
+ List list(File startDirectory) throws IOException {
+ ArrayList files = new ArrayList();
+ walk(startDirectory, files);
+ return files;
+ }
+
+ protected void handleDirectoryStart(File directory, int depth,
Collection results) throws IOException {
+ // Add all directories except the starting directory
+ if (depth > 0) {
+ results.add(directory);
+ }
+ }
+
+ protected void handleFile(File file, int depth, Collection results)
throws IOException {
+ results.add(file);
+ }
}
}