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


Reply via email to