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); + } } }