jdk/src/windows/classes/sun/nio/fs/WindowsLinkSupport.java
changeset 2057 3acf8e5e2ca0
child 3065 452aaa2899fc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/windows/classes/sun/nio/fs/WindowsLinkSupport.java	Sun Feb 15 12:25:54 2009 +0000
@@ -0,0 +1,446 @@
+/*
+ * 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.*;
+import java.io.IOException;
+import java.io.IOError;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import sun.misc.Unsafe;
+
+import static sun.nio.fs.WindowsNativeDispatcher.*;
+import static sun.nio.fs.WindowsConstants.*;
+
+/**
+ * Utility methods for symbolic link support on Windows Vista and newer.
+ */
+
+class WindowsLinkSupport {
+    private static final Unsafe unsafe = Unsafe.getUnsafe();
+
+    private WindowsLinkSupport() {
+    }
+
+    /**
+     * Returns the target of a symbolic link
+     */
+    static String readLink(WindowsPath path) throws IOException {
+        long handle = 0L;
+        try {
+            handle = path.openForReadAttributeAccess(false); // don't follow links
+        } catch (WindowsException x) {
+            x.rethrowAsIOException(path);
+        }
+        try {
+            return readLinkImpl(handle);
+        } finally {
+            CloseHandle(handle);
+        }
+    }
+
+    /**
+     * Returns the final path of a given path as a String. This should be used
+     * prior to calling Win32 system calls that do not follow links.
+     */
+    static String getFinalPath(WindowsPath input, boolean followLinks)
+        throws IOException
+    {
+        WindowsFileSystem fs = input.getFileSystem();
+
+        try {
+            // if not following links then don't need final path
+            if (!followLinks || !fs.supportsLinks())
+                return input.getPathForWin32Calls();
+
+            // if file is a sym link then don't need final path
+            if (!WindowsFileAttributes.get(input, false).isSymbolicLink()) {
+                return input.getPathForWin32Calls();
+            }
+        } catch (WindowsException x) {
+            x.rethrowAsIOException(input);
+        }
+
+        // The file is a symbolic link so we open it and try to get the
+        // normalized path. This should succeed on NTFS but may fail if there
+        // is a link to a non-NFTS file system.
+        long h = 0;
+        try {
+            h = input.openForReadAttributeAccess(true);
+        } catch (WindowsException x) {
+            x.rethrowAsIOException(input);
+        }
+        try {
+            return stripPrefix(GetFinalPathNameByHandle(h));
+        } catch (WindowsException x) {
+            // ERROR_INVALID_LEVEL is the error returned when not supported by
+            // the file system
+            if (x.lastError() != ERROR_INVALID_LEVEL)
+                x.rethrowAsIOException(input);
+        } finally {
+            CloseHandle(h);
+        }
+
+        // Fallback: read target of link, resolve against parent, and repeat
+        // until file is not a link.
+        WindowsPath target = input;
+        int linkCount = 0;
+        do {
+            try {
+                WindowsFileAttributes attrs =
+                    WindowsFileAttributes.get(target, false);
+                // non a link so we are done
+                if (!attrs.isSymbolicLink()) {
+                    return target.getPathForWin32Calls();
+                }
+            } catch (WindowsException x) {
+                x.rethrowAsIOException(target);
+            }
+            WindowsPath link = WindowsPath
+                .createFromNormalizedPath(fs, readLink(target));
+            WindowsPath parent = target.getParent();
+            if (parent == null) {
+                // no parent so use parent of absolute path
+                final WindowsPath t = target;
+                target = AccessController
+                    .doPrivileged(new PrivilegedAction<WindowsPath>() {
+                        @Override
+                        public WindowsPath run() {
+                            return t.toAbsolutePath();
+                        }});
+                parent = target.getParent();
+            }
+            target = parent.resolve(link);
+
+        } while (++linkCount < 32);
+
+        throw new FileSystemException(input.getPathForExceptionMessage(), null,
+            "Too many links");
+    }
+
+    /**
+     * Returns the actual path of a file, optionally resolving all symbolic
+     * links.
+     */
+    static String getRealPath(WindowsPath input, boolean resolveLinks)
+        throws IOException
+    {
+        WindowsFileSystem fs = input.getFileSystem();
+        if (!fs.supportsLinks())
+            resolveLinks = false;
+
+        // On Vista use GetFinalPathNameByHandle. This should succeed on NTFS
+        // but may fail if there is a link to a non-NFTS file system.
+        if (resolveLinks) {
+            long h = 0;
+            try {
+                h = input.openForReadAttributeAccess(true);
+            } catch (WindowsException x) {
+                x.rethrowAsIOException(input);
+            }
+            try {
+                return stripPrefix(GetFinalPathNameByHandle(h));
+            } catch (WindowsException x) {
+                if (x.lastError() != ERROR_INVALID_LEVEL)
+                    x.rethrowAsIOException(input);
+            } finally {
+                CloseHandle(h);
+            }
+        }
+
+        // Not resolving links or we are on Windows Vista (or newer) with a
+        // link to non-NFTS file system.
+
+        // Start with absolute path
+        String path = null;
+        try {
+            path = input.toAbsolutePath().toString();
+        } catch (IOError x) {
+            throw (IOException)(x.getCause());
+        }
+
+        // Collapse "." and ".."
+        try {
+            path = GetFullPathName(path);
+        } catch (WindowsException x) {
+            x.rethrowAsIOException(input);
+        }
+
+        // eliminate all symbolic links
+        if (resolveLinks) {
+            path = resolveAllLinks(WindowsPath.createFromNormalizedPath(fs, path));
+        }
+
+        // string builder to build up components of path
+        StringBuilder sb = new StringBuilder(path.length());
+
+        // Copy root component
+        int start;
+        char c0 = path.charAt(0);
+        char c1 = path.charAt(1);
+        if ((c0 <= 'z' && c0 >= 'a' || c0 <= 'Z' && c0 >= 'A') &&
+            c1 == ':' && path.charAt(2) == '\\') {
+            // Driver specifier
+            sb.append(Character.toUpperCase(c0));
+            sb.append(":\\");
+            start = 3;
+        } else if (c0 == '\\' && c1 == '\\') {
+            // UNC pathname, begins with "\\\\host\\share"
+            int last = path.length() - 1;
+            int pos = path.indexOf('\\', 2);
+            // skip both server and share names
+            if (pos == -1 || (pos == last)) {
+                // The UNC does not have a share name (collapsed by GetFullPathName)
+                throw new FileSystemException(input.getPathForExceptionMessage(),
+                    null, "UNC has invalid share");
+            }
+            pos = path.indexOf('\\', pos+1);
+            if (pos < 0) {
+                pos = last;
+                sb.append(path).append("\\");
+            } else {
+                sb.append(path, 0, pos+1);
+            }
+            start = pos + 1;
+        } else {
+            throw new AssertionError("path type not recognized");
+        }
+
+        // check root directory exists
+        try {
+            FirstFile fileData = FindFirstFile(sb.toString() + "*");
+            FindClose(fileData.handle());
+        } catch (WindowsException x) {
+            x.rethrowAsIOException(path);
+        }
+
+        // iterate through each component to get its actual name in the
+        // directory
+        int curr = start;
+        while (curr < path.length()) {
+            int next = path.indexOf('\\', curr);
+            int end = (next == -1) ? path.length() : next;
+            String search = sb.toString() + path.substring(curr, end);
+            try {
+                FirstFile fileData = FindFirstFile(addLongPathPrefixIfNeeded(search));
+                try {
+                    sb.append(fileData.name());
+                    if (next != -1) {
+                        sb.append('\\');
+                    }
+                } finally {
+                    FindClose(fileData.handle());
+                }
+            } catch (WindowsException e) {
+                e.rethrowAsIOException(path);
+            }
+            curr = end + 1;
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Returns target of a symbolic link given the handle of an open file
+     * (that should be a link).
+     */
+    private static String readLinkImpl(long handle) throws IOException {
+        int size = MAXIMUM_REPARSE_DATA_BUFFER_SIZE;
+        NativeBuffer buffer = NativeBuffers.getNativeBuffer(size);
+        try {
+            try {
+                DeviceIoControlGetReparsePoint(handle, buffer.address(), size);
+            } catch (WindowsException x) {
+                // FIXME: exception doesn't have file name
+                if (x.lastError() == ERROR_NOT_A_REPARSE_POINT)
+                    throw new NotLinkException(null, null, x.errorString());
+                x.rethrowAsIOException((String)null);
+            }
+
+            /*
+             * typedef struct _REPARSE_DATA_BUFFER {
+             *     ULONG  ReparseTag;
+             *     USHORT  ReparseDataLength;
+             *     USHORT  Reserved;
+             *     union {
+             *         struct {
+             *             USHORT  SubstituteNameOffset;
+             *             USHORT  SubstituteNameLength;
+             *             USHORT  PrintNameOffset;
+             *             USHORT  PrintNameLength;
+             *             WCHAR  PathBuffer[1];
+             *         } SymbolicLinkReparseBuffer;
+             *         struct {
+             *             USHORT  SubstituteNameOffset;
+             *             USHORT  SubstituteNameLength;
+             *             USHORT  PrintNameOffset;
+             *             USHORT  PrintNameLength;
+             *             WCHAR  PathBuffer[1];
+             *         } MountPointReparseBuffer;
+             *         struct {
+             *             UCHAR  DataBuffer[1];
+             *         } GenericReparseBuffer;
+             *     };
+             * } REPARSE_DATA_BUFFER
+             */
+            final short OFFSETOF_REPARSETAG = 0;
+            final short OFFSETOF_PATHOFFSET = 8;
+            final short OFFSETOF_PATHLENGTH = 10;
+            final short OFFSETOF_PATHBUFFER = 16 + 4;   // check this
+
+            int tag = (int)unsafe.getLong(buffer.address() + OFFSETOF_REPARSETAG);
+            if (tag != IO_REPARSE_TAG_SYMLINK) {
+                // FIXME: exception doesn't have file name
+                throw new NotLinkException(null, null, "Reparse point is not a symbolic link");
+            }
+
+            // get offset and length of target
+            short nameOffset = unsafe.getShort(buffer.address() + OFFSETOF_PATHOFFSET);
+            short nameLengthInBytes = unsafe.getShort(buffer.address() + OFFSETOF_PATHLENGTH);
+            if ((nameLengthInBytes % 2) != 0)
+                throw new FileSystemException(null, null, "Symbolic link corrupted");
+
+            // copy into char array
+            char[] name = new char[nameLengthInBytes/2];
+            unsafe.copyMemory(null, buffer.address() + OFFSETOF_PATHBUFFER + nameOffset,
+                name, Unsafe.ARRAY_CHAR_BASE_OFFSET, nameLengthInBytes);
+
+            // remove special prefix
+            String target = stripPrefix(new String(name));
+            if (target.length() == 0) {
+                throw new IOException("Symbolic link target is invalid");
+            }
+            return target;
+        } finally {
+            buffer.release();
+        }
+    }
+
+    /**
+     * Resolve all symbolic-links in a given absolute and normalized path
+     */
+    private static String resolveAllLinks(WindowsPath path)
+        throws IOException
+    {
+        assert path.isAbsolute();
+        WindowsFileSystem fs = path.getFileSystem();
+
+        // iterate through each name element of the path, resolving links as
+        // we go.
+        int linkCount = 0;
+        int elem = 0;
+        while (elem < path.getNameCount()) {
+            WindowsPath current = path.getRoot().resolve(path.subpath(0, elem+1));
+
+            WindowsFileAttributes attrs = null;
+            try {
+                attrs = WindowsFileAttributes.get(current, false);
+            } catch (WindowsException x) {
+                x.rethrowAsIOException(current);
+            }
+
+            /**
+             * If a symbolic link then we resolve it against the parent
+             * of the current name element. We then resolve any remaining
+             * part of the path against the result. The target of the link
+             * may have "." and ".." components so re-normalize and restart
+             * the process from the first element.
+             */
+            if (attrs.isSymbolicLink()) {
+                linkCount++;
+                if (linkCount > 32)
+                    throw new IOException("Too many links");
+                WindowsPath target = WindowsPath
+                    .createFromNormalizedPath(fs, readLink(current));
+                WindowsPath remainder = null;
+                int count = path.getNameCount();
+                if ((elem+1) < count) {
+                    remainder = path.subpath(elem+1, count);
+                }
+                path = current.getParent().resolve(target);
+                try {
+                    String full = GetFullPathName(path.toString());
+                    if (!full.equals(path.toString())) {
+                        path = WindowsPath.createFromNormalizedPath(fs, full);
+                    }
+                } catch (WindowsException x) {
+                    x.rethrowAsIOException(path);
+                }
+                if (remainder != null) {
+                    path = path.resolve(remainder);
+                }
+
+                // reset
+                elem = 0;
+            } else {
+                // not a link
+                elem++;
+            }
+        }
+
+        return path.toString();
+    }
+
+    /**
+     * Add long path prefix to path if required.
+     */
+    private static String addLongPathPrefixIfNeeded(String path) {
+        if (path.length() > 248) {
+            if (path.startsWith("\\\\")) {
+                path = "\\\\?\\UNC" + path.substring(1, path.length());
+            } else {
+                path = "\\\\?\\" + path;
+            }
+        }
+        return path;
+    }
+
+    /**
+     * Strip long path or symbolic link prefix from path
+     */
+    private static String stripPrefix(String path) {
+        // prefix for resolved/long path
+        if (path.startsWith("\\\\?\\")) {
+            if (path.startsWith("\\\\?\\UNC\\")) {
+                path = "\\" + path.substring(7);
+            } else {
+                path = path.substring(4);
+            }
+            return path;
+        }
+
+        // prefix for target of symbolic link
+        if (path.startsWith("\\??\\")) {
+            if (path.startsWith("\\??\\UNC\\")) {
+                path = "\\" + path.substring(7);
+            } else {
+                path = path.substring(4);
+            }
+            return path;
+        }
+        return path;
+    }
+}