/* * Copyright (c) 2008, 2009, Oracle and/or its affiliates. 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.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 (all symbolic links resolved) or null if this * operation is not supported. */ private static String getFinalPath(WindowsPath input) throws IOException { 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 // (a sym link to file on FAT32 or Samba server for example) if (x.lastError() != ERROR_INVALID_LEVEL) x.rethrowAsIOException(input); } finally { CloseHandle(h); } return null; } /** * 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 not 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 attempt to get the final path String result = getFinalPath(input); if (result != null) return result; // 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 (resolveLinks && !fs.supportsLinks()) resolveLinks = false; // Start with absolute path String path = null; try { path = input.toAbsolutePath().toString(); } catch (IOError x) { throw (IOException)(x.getCause()); } // Collapse "." and ".." if (path.indexOf('.') >= 0) { try { path = GetFullPathName(path); } catch (WindowsException x) { x.rethrowAsIOException(input); } } // 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"); } // if the result is only a root component then we simply check it exists if (start >= path.length()) { String result = sb.toString(); try { GetFileAttributes(result); } catch (WindowsException x) { x.rethrowAsIOException(path); } return result; } // 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)); FindClose(fileData.handle()); // if a reparse point is encountered then we must return the // final path. if (resolveLinks && WindowsFileAttributes.isReparsePoint(fileData.attributes())) { String result = getFinalPath(input); if (result == null) { // Fallback to slow path, usually because there is a sym // link to a file system that doesn't support sym links. WindowsPath resolved = resolveAllLinks( WindowsPath.createFromNormalizedPath(fs, path)); result = getRealPath(resolved, false); } return result; } // add the name to the result sb.append(fileData.name()); if (next != -1) { sb.append('\\'); } } 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 WindowsPath 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; } /** * 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; } }