blob: 00bce624849d15ead99c2727143339e1c5c41e1f [file] [log] [blame]
/*
* 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;
}
}