6808647: (file) Paths.get("C:").newDirectoryStream() iterates over Path elements with additional slash [win]
authoralanb
Tue, 24 Feb 2009 09:11:42 +0000
changeset 2071 5e6af6d106cb
parent 2069 2cd4a0aa917f
child 2072 80dfe4469bbd
6808647: (file) Paths.get("C:").newDirectoryStream() iterates over Path elements with additional slash [win] 6808648: (file) Files.walkFileTree should obtain file attributes during iteration [win] Reviewed-by: sherman
jdk/make/java/nio/FILES_java.gmk
jdk/src/share/classes/java/nio/file/FileTreeWalker.java
jdk/src/share/classes/sun/nio/fs/BasicFileAttributesHolder.java
jdk/src/windows/classes/sun/nio/fs/WindowsDirectoryStream.java
jdk/src/windows/classes/sun/nio/fs/WindowsFileAttributes.java
jdk/src/windows/classes/sun/nio/fs/WindowsFileSystem.java
jdk/src/windows/classes/sun/nio/fs/WindowsNativeDispatcher.java
jdk/src/windows/classes/sun/nio/fs/WindowsPath.java
jdk/src/windows/native/sun/nio/fs/WindowsNativeDispatcher.c
jdk/test/java/nio/file/DirectoryStream/DriveLetter.java
--- a/jdk/make/java/nio/FILES_java.gmk	Mon Feb 23 10:36:19 2009 +0000
+++ b/jdk/make/java/nio/FILES_java.gmk	Tue Feb 24 09:11:42 2009 +0000
@@ -252,6 +252,7 @@
 	sun/nio/fs/AbstractUserDefinedFileAttributeView.java \
 	sun/nio/fs/AbstractWatchKey.java \
 	sun/nio/fs/AbstractWatchService.java \
+	sun/nio/fs/BasicFileAttributesHolder.java \
 	sun/nio/fs/Cancellable.java \
 	sun/nio/fs/DefaultFileSystemProvider.java \
 	sun/nio/fs/DefaultFileTypeDetector.java \
--- a/jdk/src/share/classes/java/nio/file/FileTreeWalker.java	Mon Feb 23 10:36:19 2009 +0000
+++ b/jdk/src/share/classes/java/nio/file/FileTreeWalker.java	Tue Feb 24 09:11:42 2009 +0000
@@ -28,6 +28,7 @@
 import java.nio.file.attribute.*;
 import java.io.IOException;
 import java.util.*;
+import sun.nio.fs.BasicFileAttributesHolder;
 
 /**
  * Simple file tree walker that works in a similar manner to nftw(3C).
@@ -65,6 +66,10 @@
      * Walk file tree starting at the given file
      */
     void walk(Path start, int maxDepth) {
+        // don't use attributes of starting file as they may be stale
+        if (start instanceof BasicFileAttributesHolder) {
+            ((BasicFileAttributesHolder)start).invalidate();
+        }
         FileVisitResult result = walk(start,
                                       maxDepth,
                                       new ArrayList<AncestorDirectory>());
@@ -75,11 +80,9 @@
 
     /**
      * @param   file
-     *          The directory to visit
-     * @param   path
-     *          list of directories that is relative path from starting file
+     *          the directory to visit
      * @param   depth
-     *          Depth remaining
+     *          depth remaining
      * @param   ancestors
      *          use when cycle detection is enabled
      */
@@ -91,28 +94,36 @@
         if (depth-- < 0)
             return FileVisitResult.CONTINUE;
 
+        // if attributes are cached then use them if possible
         BasicFileAttributes attrs = null;
+        if (file instanceof BasicFileAttributesHolder) {
+            BasicFileAttributes cached = ((BasicFileAttributesHolder)file).get();
+            if (!followLinks || !cached.isSymbolicLink())
+                attrs = cached;
+        }
         IOException exc = null;
 
         // attempt to get attributes of file. If fails and we are following
         // links then a link target might not exist so get attributes of link
-        try {
+        if (attrs == null) {
             try {
-                attrs = Attributes.readBasicFileAttributes(file, linkOptions);
-            } catch (IOException x1) {
-                if (followLinks) {
-                    try {
-                        attrs = Attributes
-                            .readBasicFileAttributes(file, LinkOption.NOFOLLOW_LINKS);
-                    } catch (IOException x2) {
-                        exc = x2;
+                try {
+                    attrs = Attributes.readBasicFileAttributes(file, linkOptions);
+                } catch (IOException x1) {
+                    if (followLinks) {
+                        try {
+                            attrs = Attributes
+                                .readBasicFileAttributes(file, LinkOption.NOFOLLOW_LINKS);
+                        } catch (IOException x2) {
+                            exc = x2;
+                        }
+                    } else {
+                        exc = x1;
                     }
-                } else {
-                    exc = x1;
                 }
+            } catch (SecurityException x) {
+                return FileVisitResult.CONTINUE;
             }
-        } catch (SecurityException x) {
-            return FileVisitResult.CONTINUE;
         }
 
         // unable to get attributes of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/nio/fs/BasicFileAttributesHolder.java	Tue Feb 24 09:11:42 2009 +0000
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2008-2009 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.nio.fs;
+
+import java.nio.file.attribute.BasicFileAttributes;
+
+/**
+ * Implemented by objects that may hold or cache the attributes of a file.
+ */
+
+public interface BasicFileAttributesHolder {
+    /**
+     * Returns cached attributes (may be null). If file is a symbolic link then
+     * the attributes are the link attributes and not the final target of the
+     * file.
+     */
+    BasicFileAttributes get();
+
+    /**
+     * Invalidates cached attributes
+     */
+    void invalidate();
+}
--- a/jdk/src/windows/classes/sun/nio/fs/WindowsDirectoryStream.java	Mon Feb 23 10:36:19 2009 +0000
+++ b/jdk/src/windows/classes/sun/nio/fs/WindowsDirectoryStream.java	Tue Feb 24 09:11:42 2009 +0000
@@ -26,6 +26,7 @@
 package sun.nio.fs;
 
 import java.nio.file.*;
+import java.nio.file.attribute.BasicFileAttributes;
 import java.util.Iterator;
 import java.util.ConcurrentModificationException;
 import java.util.NoSuchElementException;
@@ -49,6 +50,9 @@
     // first entry in the directory
     private final String firstName;
 
+    // buffer for WIN32_FIND_DATA structure that receives information about file
+    private final NativeBuffer findDataBuffer;
+
     private final Object closeLock = new Object();
 
     // need closeLock to access these
@@ -75,6 +79,7 @@
             FirstFile first = FindFirstFile(search);
             this.handle = first.handle();
             this.firstName = first.name();
+            this.findDataBuffer = WindowsFileAttributes.getBufferForFindData();
         } catch (WindowsException x) {
             if (x.lastError() == ERROR_DIRECTORY) {
                 throw new NotDirectoryException(dir.getPathForExceptionMessage());
@@ -95,6 +100,7 @@
                 return;
             isOpen = false;
         }
+        findDataBuffer.release();
         try {
             FindClose(handle);
         } catch (WindowsException x) {
@@ -133,11 +139,19 @@
         }
 
         // applies filter and also ignores "." and ".."
-        private Path acceptEntry(String s) {
+        private Path acceptEntry(String s, BasicFileAttributes attrs) {
             if (s.equals(".") || s.equals(".."))
                 return null;
+            if (dir.needsSlashWhenResolving()) {
+                StringBuilder sb = new StringBuilder(dir.toString());
+                sb.append('\\');
+                sb.append(s);
+                s = sb.toString();
+            } else {
+                s = dir + s;
+            }
             Path entry = WindowsPath
-                .createFromNormalizedPath(dir.getFileSystem(), dir + "\\" + s);
+                .createFromNormalizedPath(dir.getFileSystem(), s, attrs);
             if (filter.accept(entry)) {
                 return entry;
             } else {
@@ -149,21 +163,27 @@
         private Path readNextEntry() {
             // handle first element returned by search
             if (first != null) {
-                nextEntry = acceptEntry(first);
+                nextEntry = acceptEntry(first, null);
                 first = null;
                 if (nextEntry != null)
                     return nextEntry;
             }
 
-            String name = null;
             for (;;) {
+                String name = null;
+                WindowsFileAttributes attrs;
+
                 // synchronize on closeLock to prevent close while reading
                 synchronized (closeLock) {
                     if (!isOpen)
                         throwAsConcurrentModificationException(new
                             IllegalStateException("Directory stream is closed"));
                     try {
-                        name = FindNextFile(handle);
+                        name = FindNextFile(handle, findDataBuffer.address());
+                        if (name == null) {
+                            // NO_MORE_FILES
+                            return null;
+                        }
                     } catch (WindowsException x) {
                         try {
                             x.rethrowAsIOException(dir);
@@ -171,13 +191,16 @@
                             throwAsConcurrentModificationException(ioe);
                         }
                     }
+
+                    // grab the attributes from the WIN32_FIND_DATA structure
+                    // (needs to be done while holding closeLock because close
+                    // will release the buffer)
+                    attrs = WindowsFileAttributes
+                        .fromFindData(findDataBuffer.address());
                 }
 
-                // EOF
-                if (name == null)
-                    return null;
-
-                Path entry = acceptEntry(name);
+                // return entry if accepted by filter
+                Path entry = acceptEntry(name, attrs);
                 if (entry != null)
                     return entry;
             }
--- a/jdk/src/windows/classes/sun/nio/fs/WindowsFileAttributes.java	Mon Feb 23 10:36:19 2009 +0000
+++ b/jdk/src/windows/classes/sun/nio/fs/WindowsFileAttributes.java	Tue Feb 24 09:11:42 2009 +0000
@@ -87,6 +87,29 @@
     private static final short OFFSETOF_FILE_ATTRIBUTE_DATA_SIZEHIGH        = 28;
     private static final short OFFSETOF_FILE_ATTRIBUTE_DATA_SIZELOW         = 32;
 
+    /**
+     * typedef struct _WIN32_FIND_DATA {
+     *   DWORD dwFileAttributes;
+     *   FILETIME ftCreationTime;
+     *   FILETIME ftLastAccessTime;
+     *   FILETIME ftLastWriteTime;
+     *   DWORD nFileSizeHigh;
+     *   DWORD nFileSizeLow;
+     *   DWORD dwReserved0;
+     *   DWORD dwReserved1;
+     *   TCHAR cFileName[MAX_PATH];
+     *   TCHAR cAlternateFileName[14];
+     * } WIN32_FIND_DATA;
+     */
+    private static final short SIZEOF_FIND_DATA = 592;
+    private static final short OFFSETOF_FIND_DATA_ATTRIBUTES = 0;
+    private static final short OFFSETOF_FIND_DATA_CREATETIME = 4;
+    private static final short OFFSETOF_FIND_DATA_LASTACCESSTIME = 12;
+    private static final short OFFSETOF_FIND_DATA_LASTWRITETIME = 20;
+    private static final short OFFSETOF_FIND_DATA_SIZEHIGH = 28;
+    private static final short OFFSETOF_FIND_DATA_SIZELOW = 32;
+    private static final short OFFSETOF_FIND_DATA_RESERVED0 = 36;
+
     // indicates if accurate metadata is required (interesting on NTFS only)
     private static final boolean ensureAccurateMetadata;
     static {
@@ -210,6 +233,41 @@
                                          0); // fileIndexLow
     }
 
+
+    /**
+     * Allocates a native buffer for a WIN32_FIND_DATA structure
+     */
+    static NativeBuffer getBufferForFindData() {
+        return NativeBuffers.getNativeBuffer(SIZEOF_FIND_DATA);
+    }
+
+    /**
+     * Create a WindowsFileAttributes from a WIN32_FIND_DATA structure
+     */
+    static WindowsFileAttributes fromFindData(long address) {
+        int fileAttrs = unsafe.getInt(address + OFFSETOF_FIND_DATA_ATTRIBUTES);
+        long creationTime =
+            toJavaTime(unsafe.getLong(address + OFFSETOF_FIND_DATA_CREATETIME));
+        long lastAccessTime =
+            toJavaTime(unsafe.getLong(address + OFFSETOF_FIND_DATA_LASTACCESSTIME));
+        long lastWriteTime =
+            toJavaTime(unsafe.getLong(address + OFFSETOF_FIND_DATA_LASTWRITETIME));
+        long size = ((long)(unsafe.getInt(address + OFFSETOF_FIND_DATA_SIZEHIGH)) << 32)
+            + (unsafe.getInt(address + OFFSETOF_FIND_DATA_SIZELOW) & 0xFFFFFFFFL);
+        int reparseTag = ((fileAttrs & FILE_ATTRIBUTE_REPARSE_POINT) != 0) ?
+            + unsafe.getInt(address + OFFSETOF_FIND_DATA_RESERVED0) : 0;
+        return new WindowsFileAttributes(fileAttrs,
+                                         creationTime,
+                                         lastAccessTime,
+                                         lastWriteTime,
+                                         size,
+                                         reparseTag,
+                                         1,  // linkCount
+                                         0,  // volSerialNumber
+                                         0,  // fileIndexHigh
+                                         0); // fileIndexLow
+    }
+
     /**
      * Reads the attributes of an open file
      */
--- a/jdk/src/windows/classes/sun/nio/fs/WindowsFileSystem.java	Mon Feb 23 10:36:19 2009 +0000
+++ b/jdk/src/windows/classes/sun/nio/fs/WindowsFileSystem.java	Tue Feb 24 09:11:42 2009 +0000
@@ -236,11 +236,9 @@
 
     @Override
     public Path getPath(String path) {
-        WindowsPathParser.Result result = WindowsPathParser.parse(path);
-        return new WindowsPath(this, result.type(), result.root(), result.path());
+        return WindowsPath.parse(this, path);
     }
 
-
     @Override
     public UserPrincipalLookupService getUserPrincipalLookupService() {
         return theLookupService;
--- a/jdk/src/windows/classes/sun/nio/fs/WindowsNativeDispatcher.java	Mon Feb 23 10:36:19 2009 +0000
+++ b/jdk/src/windows/classes/sun/nio/fs/WindowsNativeDispatcher.java	Tue Feb 24 09:11:42 2009 +0000
@@ -211,9 +211,10 @@
      *   LPWIN32_FIND_DATA lpFindFileData
      * )
      *
-     * @return  lpFindFileData->cFileName
+     * @return  lpFindFileData->cFileName or null
      */
-    static native String FindNextFile(long handle) throws WindowsException;
+    static native String FindNextFile(long handle, long address)
+        throws WindowsException;
 
     /**
      * HANDLE FindFirstStreamW(
--- a/jdk/src/windows/classes/sun/nio/fs/WindowsPath.java	Mon Feb 23 10:36:19 2009 +0000
+++ b/jdk/src/windows/classes/sun/nio/fs/WindowsPath.java	Tue Feb 24 09:11:42 2009 +0000
@@ -83,10 +83,10 @@
     /**
      * Initializes a new instance of this class.
      */
-    WindowsPath(WindowsFileSystem fs,
-                WindowsPathType type,
-                String root,
-                String path)
+    private WindowsPath(WindowsFileSystem fs,
+                        WindowsPathType type,
+                        String root,
+                        String path)
     {
         this.fs = fs;
         this.type = type;
@@ -95,7 +95,7 @@
     }
 
     /**
-     * Creates a WindowsPath by parsing the given path.
+     * Creates a Path by parsing the given path.
      */
     static WindowsPath parse(WindowsFileSystem fs, String path) {
         WindowsPathParser.Result result = WindowsPathParser.parse(path);
@@ -103,18 +103,71 @@
     }
 
     /**
-     * Creates a WindowsPath from a given path that is known to be normalized.
+     * Creates a Path from a given path that is known to be normalized.
      */
-    static WindowsPath createFromNormalizedPath(WindowsFileSystem fs, String path) {
+    static WindowsPath createFromNormalizedPath(WindowsFileSystem fs,
+                                                String path,
+                                                BasicFileAttributes attrs)
+    {
         try {
             WindowsPathParser.Result result =
                 WindowsPathParser.parseNormalizedPath(path);
-            return new WindowsPath(fs, result.type(), result.root(), result.path());
+            if (attrs == null) {
+                return new WindowsPath(fs,
+                                       result.type(),
+                                       result.root(),
+                                       result.path());
+            } else {
+                return new WindowsPathWithAttributes(fs,
+                                                     result.type(),
+                                                     result.root(),
+                                                     result.path(),
+                                                     attrs);
+            }
         } catch (InvalidPathException x) {
             throw new AssertionError(x.getMessage());
         }
     }
 
+    /**
+     * Creates a WindowsPath from a given path that is known to be normalized.
+     */
+    static WindowsPath createFromNormalizedPath(WindowsFileSystem fs,
+                                                String path)
+    {
+        return createFromNormalizedPath(fs, path, null);
+    }
+
+    /**
+     * Special implementation with attached/cached attributes (used to quicken
+     * file tree traveral)
+     */
+    private static class WindowsPathWithAttributes
+        extends WindowsPath implements BasicFileAttributesHolder
+    {
+        final WeakReference<BasicFileAttributes> ref;
+
+        WindowsPathWithAttributes(WindowsFileSystem fs,
+                                  WindowsPathType type,
+                                  String root,
+                                  String path,
+                                  BasicFileAttributes attrs)
+        {
+            super(fs, type, root, path);
+            ref = new WeakReference<BasicFileAttributes>(attrs);
+        }
+
+        @Override
+        public BasicFileAttributes get() {
+            return ref.get();
+        }
+
+        @Override
+        public void invalidate() {
+            ref.clear();
+        }
+    }
+
     // use this message when throwing exceptions
     String getPathForExceptionMessage() {
         return path;
@@ -290,6 +343,12 @@
         return type == WindowsPathType.UNC;
     }
 
+    boolean needsSlashWhenResolving() {
+        if (path.endsWith("\\"))
+            return false;
+        return path.length() > root.length();
+    }
+
     @Override
     public boolean isAbsolute() {
         return type == WindowsPathType.ABSOLUTE || type == WindowsPathType.UNC;
--- a/jdk/src/windows/native/sun/nio/fs/WindowsNativeDispatcher.c	Mon Feb 23 10:36:19 2009 +0000
+++ b/jdk/src/windows/native/sun/nio/fs/WindowsNativeDispatcher.c	Tue Feb 24 09:11:42 2009 +0000
@@ -392,16 +392,16 @@
 
 JNIEXPORT jstring JNICALL
 Java_sun_nio_fs_WindowsNativeDispatcher_FindNextFile(JNIEnv* env, jclass this,
-    jlong handle)
+    jlong handle, jlong dataAddress)
 {
-    WIN32_FIND_DATAW data;
     HANDLE h = (HANDLE)jlong_to_ptr(handle);
+    WIN32_FIND_DATAW* data = (WIN32_FIND_DATAW*)jlong_to_ptr(dataAddress);
 
-    if (FindNextFileW(h, &data) != 0) {
-        return (*env)->NewString(env, data.cFileName, wcslen(data.cFileName));
+    if (FindNextFileW(h, data) != 0) {
+        return (*env)->NewString(env, data->cFileName, wcslen(data->cFileName));
     } else {
-        if (GetLastError() != ERROR_NO_MORE_FILES)
-            throwWindowsException(env, GetLastError());
+    if (GetLastError() != ERROR_NO_MORE_FILES)
+        throwWindowsException(env, GetLastError());
         return NULL;
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/nio/file/DirectoryStream/DriveLetter.java	Tue Feb 24 09:11:42 2009 +0000
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2008-2009 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/* @test
+ * @bug 6808647
+ * @summary Checks that a DirectoryStream's iterator returns the expected
+ *    path when opening a directory by specifying only the drive letter.
+ * @library ..
+ */
+
+import java.nio.file.*;
+import java.io.File;
+import java.io.IOException;
+
+public class DriveLetter {
+
+    public static void main(String[] args) throws IOException {
+        String os = System.getProperty("os.name");
+        if (!os.startsWith("Windows")) {
+            System.out.println("This is Windows specific test");
+            return;
+        }
+        String here = System.getProperty("user.dir");
+        if (here.length() < 2 || here.charAt(1) != ':')
+            throw new RuntimeException("Unable to determine drive letter");
+
+        // create temporary file in current directory
+        File tempFile = File.createTempFile("foo", "tmp", new File(here));
+        try {
+            // we should expect C:foo.tmp to be returned by iterator
+            String drive = here.substring(0, 2);
+            Path expected = Paths.get(drive).resolve(tempFile.getName());
+
+            boolean found = false;
+            DirectoryStream<Path> stream = Paths.get(drive).newDirectoryStream();
+            try {
+                for (Path file : stream) {
+                    if (file.equals(expected)) {
+                        found = true;
+                        break;
+                    }
+                }
+            } finally {
+                stream.close();
+            }
+            if (!found)
+                throw new RuntimeException("Temporary file not found???");
+
+        } finally {
+            tempFile.delete();
+        }
+    }
+}