Path: blob/master/src/java.base/windows/classes/sun/nio/fs/WindowsLinkSupport.java
41139 views
/*1* Copyright (c) 2008, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.nio.fs;2627import java.nio.file.*;28import java.io.IOException;29import java.io.IOError;30import java.security.AccessController;31import java.security.PrivilegedAction;32import jdk.internal.misc.Unsafe;3334import static sun.nio.fs.WindowsNativeDispatcher.*;35import static sun.nio.fs.WindowsConstants.*;3637/**38* Utility methods for symbolic link support on Windows Vista and newer.39*/4041class WindowsLinkSupport {42private static final Unsafe unsafe = Unsafe.getUnsafe();4344private WindowsLinkSupport() {45}4647/**48* Returns the target of a symbolic link49*/50static String readLink(WindowsPath path) throws IOException {51long handle = 0L;52try {53handle = path.openForReadAttributeAccess(false); // don't follow links54} catch (WindowsException x) {55x.rethrowAsIOException(path);56}57try {58return readLinkImpl(handle);59} finally {60CloseHandle(handle);61}62}6364/**65* Returns the final path (all symbolic links resolved) or null if this66* operation is not supported.67*/68static String getFinalPath(WindowsPath input) throws IOException {69long h = 0;70try {71h = input.openForReadAttributeAccess(true);72} catch (WindowsException x) {73x.rethrowAsIOException(input);74}75try {76return stripPrefix(GetFinalPathNameByHandle(h));77} catch (WindowsException x) {78// ERROR_INVALID_LEVEL is the error returned when not supported79// (a sym link to file on FAT32 or Samba server for example)80if (x.lastError() != ERROR_INVALID_LEVEL)81x.rethrowAsIOException(input);82} finally {83CloseHandle(h);84}85return null;86}8788/**89* Returns the final path of a given path as a String. This should be used90* prior to calling Win32 system calls that do not follow links.91*/92@SuppressWarnings("removal")93static String getFinalPath(WindowsPath input, boolean followLinks)94throws IOException95{96WindowsFileSystem fs = input.getFileSystem();97try {98// if not following links then don't need final path99if (!followLinks)100return input.getPathForWin32Calls();101102// if file is not a sym link then don't need final path103if (!WindowsFileAttributes.get(input, false).isSymbolicLink()) {104return input.getPathForWin32Calls();105}106} catch (WindowsException x) {107x.rethrowAsIOException(input);108}109110// The file is a symbolic link so attempt to get the final path111String result = getFinalPath(input);112if (result != null)113return result;114115// Fallback: read target of link, resolve against parent, and repeat116// until file is not a link.117WindowsPath target = input;118int linkCount = 0;119do {120try {121WindowsFileAttributes attrs =122WindowsFileAttributes.get(target, false);123// non a link so we are done124if (!attrs.isSymbolicLink()) {125return target.getPathForWin32Calls();126}127} catch (WindowsException x) {128x.rethrowAsIOException(target);129}130WindowsPath link = WindowsPath131.createFromNormalizedPath(fs, readLink(target));132WindowsPath parent = target.getParent();133if (parent == null) {134// no parent so use parent of absolute path135final WindowsPath t = target;136target = AccessController137.doPrivileged(new PrivilegedAction<WindowsPath>() {138@Override139public WindowsPath run() {140return t.toAbsolutePath();141}});142parent = target.getParent();143}144target = parent.resolve(link);145146} while (++linkCount < 32);147148throw new FileSystemException(input.getPathForExceptionMessage(), null,149"Too many links");150}151152/**153* Returns the actual path of a file, optionally resolving all symbolic154* links.155*/156static String getRealPath(WindowsPath input, boolean resolveLinks)157throws IOException158{159WindowsFileSystem fs = input.getFileSystem();160161// Start with absolute path162String path = null;163try {164path = input.toAbsolutePath().toString();165} catch (IOError x) {166throw (IOException)(x.getCause());167}168169// Collapse "." and ".."170if (path.indexOf('.') >= 0) {171try {172path = GetFullPathName(path);173} catch (WindowsException x) {174x.rethrowAsIOException(input);175}176}177178// string builder to build up components of path179StringBuilder sb = new StringBuilder(path.length());180181// Copy root component182int start;183char c0 = path.charAt(0);184char c1 = path.charAt(1);185if ((c0 <= 'z' && c0 >= 'a' || c0 <= 'Z' && c0 >= 'A') &&186c1 == ':' && path.charAt(2) == '\\') {187// Driver specifier188sb.append(Character.toUpperCase(c0));189sb.append(":\\");190start = 3;191} else if (c0 == '\\' && c1 == '\\') {192// UNC pathname, begins with "\\\\host\\share"193int last = path.length() - 1;194int pos = path.indexOf('\\', 2);195// skip both server and share names196if (pos == -1 || (pos == last)) {197// The UNC does not have a share name (collapsed by GetFullPathName)198throw new FileSystemException(input.getPathForExceptionMessage(),199null, "UNC has invalid share");200}201pos = path.indexOf('\\', pos+1);202if (pos < 0) {203pos = last;204sb.append(path).append("\\");205} else {206sb.append(path, 0, pos+1);207}208start = pos + 1;209} else {210throw new AssertionError("path type not recognized");211}212213// if the result is only a root component then we simply check it exists214if (start >= path.length()) {215String result = sb.toString();216try {217GetFileAttributes(result);218} catch (WindowsException x) {219x.rethrowAsIOException(path);220}221return result;222}223224// iterate through each component to get its actual name in the225// directory226int curr = start;227while (curr < path.length()) {228int next = path.indexOf('\\', curr);229int end = (next == -1) ? path.length() : next;230String search = sb.toString() + path.substring(curr, end);231try {232FirstFile fileData = FindFirstFile(WindowsPath.addPrefixIfNeeded(search));233FindClose(fileData.handle());234235// if a reparse point is encountered then we must return the236// final path.237if (resolveLinks &&238WindowsFileAttributes.isReparsePoint(fileData.attributes()))239{240String result = getFinalPath(input);241if (result == null) {242// Fallback to slow path, usually because there is a sym243// link to a file system that doesn't support sym links.244WindowsPath resolved = resolveAllLinks(245WindowsPath.createFromNormalizedPath(fs, path));246result = getRealPath(resolved, false);247}248return result;249}250251// add the name to the result252sb.append(fileData.name());253if (next != -1) {254sb.append('\\');255}256} catch (WindowsException e) {257e.rethrowAsIOException(path);258}259curr = end + 1;260}261262return sb.toString();263}264265/**266* Returns target of a symbolic link given the handle of an open file267* (that should be a link).268*/269private static String readLinkImpl(long handle) throws IOException {270int size = MAXIMUM_REPARSE_DATA_BUFFER_SIZE;271NativeBuffer buffer = NativeBuffers.getNativeBuffer(size);272try {273try {274DeviceIoControlGetReparsePoint(handle, buffer.address(), size);275} catch (WindowsException x) {276// FIXME: exception doesn't have file name277if (x.lastError() == ERROR_NOT_A_REPARSE_POINT)278throw new NotLinkException(null, null, x.errorString());279x.rethrowAsIOException((String)null);280}281282/*283* typedef struct _REPARSE_DATA_BUFFER {284* ULONG ReparseTag;285* USHORT ReparseDataLength;286* USHORT Reserved;287* union {288* struct {289* USHORT SubstituteNameOffset;290* USHORT SubstituteNameLength;291* USHORT PrintNameOffset;292* USHORT PrintNameLength;293* WCHAR PathBuffer[1];294* } SymbolicLinkReparseBuffer;295* struct {296* USHORT SubstituteNameOffset;297* USHORT SubstituteNameLength;298* USHORT PrintNameOffset;299* USHORT PrintNameLength;300* WCHAR PathBuffer[1];301* } MountPointReparseBuffer;302* struct {303* UCHAR DataBuffer[1];304* } GenericReparseBuffer;305* };306* } REPARSE_DATA_BUFFER307*/308final short OFFSETOF_REPARSETAG = 0;309final short OFFSETOF_PATHOFFSET = 8;310final short OFFSETOF_PATHLENGTH = 10;311final short OFFSETOF_PATHBUFFER = 16 + 4; // check this312313int tag = (int)unsafe.getLong(buffer.address() + OFFSETOF_REPARSETAG);314if (tag != IO_REPARSE_TAG_SYMLINK) {315// FIXME: exception doesn't have file name316throw new NotLinkException(null, null, "Reparse point is not a symbolic link");317}318319// get offset and length of target320short nameOffset = unsafe.getShort(buffer.address() + OFFSETOF_PATHOFFSET);321short nameLengthInBytes = unsafe.getShort(buffer.address() + OFFSETOF_PATHLENGTH);322if ((nameLengthInBytes % 2) != 0)323throw new FileSystemException(null, null, "Symbolic link corrupted");324325// copy into char array326char[] name = new char[nameLengthInBytes/2];327unsafe.copyMemory(null, buffer.address() + OFFSETOF_PATHBUFFER + nameOffset,328name, Unsafe.ARRAY_CHAR_BASE_OFFSET, nameLengthInBytes);329330// remove special prefix331String target = stripPrefix(new String(name));332if (target.isEmpty()) {333throw new IOException("Symbolic link target is invalid");334}335return target;336} finally {337buffer.release();338}339}340341/**342* Resolve all symbolic-links in a given absolute and normalized path343*/344private static WindowsPath resolveAllLinks(WindowsPath path)345throws IOException346{347assert path.isAbsolute();348WindowsFileSystem fs = path.getFileSystem();349350// iterate through each name element of the path, resolving links as351// we go.352int linkCount = 0;353int elem = 0;354while (elem < path.getNameCount()) {355WindowsPath current = path.getRoot().resolve(path.subpath(0, elem+1));356357WindowsFileAttributes attrs = null;358try {359attrs = WindowsFileAttributes.get(current, false);360} catch (WindowsException x) {361x.rethrowAsIOException(current);362}363364/**365* If a symbolic link then we resolve it against the parent366* of the current name element. We then resolve any remaining367* part of the path against the result. The target of the link368* may have "." and ".." components so re-normalize and restart369* the process from the first element.370*/371if (attrs.isSymbolicLink()) {372linkCount++;373if (linkCount > 32)374throw new IOException("Too many links");375WindowsPath target = WindowsPath376.createFromNormalizedPath(fs, readLink(current));377WindowsPath remainder = null;378int count = path.getNameCount();379if ((elem+1) < count) {380remainder = path.subpath(elem+1, count);381}382path = current.getParent().resolve(target);383try {384String full = GetFullPathName(path.toString());385if (!full.equals(path.toString())) {386path = WindowsPath.createFromNormalizedPath(fs, full);387}388} catch (WindowsException x) {389x.rethrowAsIOException(path);390}391if (remainder != null) {392path = path.resolve(remainder);393}394395// reset396elem = 0;397} else {398// not a link399elem++;400}401}402403return path;404}405406/**407* Strip long path or symbolic link prefix from path408*/409private static String stripPrefix(String path) {410// prefix for resolved/long path411if (path.startsWith("\\\\?\\")) {412if (path.startsWith("\\\\?\\UNC\\")) {413path = "\\" + path.substring(7);414} else {415path = path.substring(4);416}417return path;418}419420// prefix for target of symbolic link421if (path.startsWith("\\??\\")) {422if (path.startsWith("\\??\\UNC\\")) {423path = "\\" + path.substring(7);424} else {425path = path.substring(4);426}427return path;428}429return path;430}431}432433434