1 /* 2 * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.nio.fs; 27 28 import java.nio.file.*; 29 import java.io.IOException; 30 import java.io.IOError; 31 import java.security.AccessController; 32 import java.security.PrivilegedAction; 33 import jdk.internal.misc.Unsafe; 34 35 import static sun.nio.fs.WindowsNativeDispatcher.*; 36 import static sun.nio.fs.WindowsConstants.*; 37 38 /** 39 * Utility methods for symbolic link support on Windows Vista and newer. 40 */ 41 42 class WindowsLinkSupport { 43 private static final Unsafe unsafe = Unsafe.getUnsafe(); 44 45 private WindowsLinkSupport() { 46 } 47 48 /** 49 * Returns the target of a symbolic link 50 */ 51 static String readLink(WindowsPath path) throws IOException { 52 long handle = 0L; 53 try { 54 handle = path.openForReadAttributeAccess(false); // don't follow links 55 } catch (WindowsException x) { 56 x.rethrowAsIOException(path); 57 } 58 try { 59 return readLinkImpl(handle); 60 } finally { 61 CloseHandle(handle); 62 } 63 } 64 65 /** 66 * Returns the final path (all symbolic links resolved) or null if this 67 * operation is not supported. 68 */ 69 static String getFinalPath(WindowsPath input) throws IOException { 70 long h = 0; 71 try { 72 h = input.openForReadAttributeAccess(true); 73 } catch (WindowsException x) { 74 x.rethrowAsIOException(input); 75 } 76 try { 77 return stripPrefix(GetFinalPathNameByHandle(h)); 78 } catch (WindowsException x) { 79 // ERROR_INVALID_LEVEL is the error returned when not supported 80 // (a sym link to file on FAT32 or Samba server for example) 81 if (x.lastError() != ERROR_INVALID_LEVEL) 82 x.rethrowAsIOException(input); 83 } finally { 84 CloseHandle(h); 85 } 86 return null; 87 } 88 89 /** 90 * Returns the final path of a given path as a String. This should be used 91 * prior to calling Win32 system calls that do not follow links. 92 */ 93 static String getFinalPath(WindowsPath input, boolean followLinks) 94 throws IOException 95 { 96 WindowsFileSystem fs = input.getFileSystem(); 97 try { 98 // if not following links then don't need final path 99 if (!followLinks) 100 return input.getPathForWin32Calls(); 101 102 // if file is not a sym link then don't need final path 103 if (!WindowsFileAttributes.get(input, false).isSymbolicLink()) { 104 return input.getPathForWin32Calls(); 105 } 106 } catch (WindowsException x) { 107 x.rethrowAsIOException(input); 108 } 109 110 // The file is a symbolic link so attempt to get the final path 111 String result = getFinalPath(input); 112 if (result != null) 113 return result; 114 115 // Fallback: read target of link, resolve against parent, and repeat 116 // until file is not a link. 117 WindowsPath target = input; 118 int linkCount = 0; 119 do { 120 try { 121 WindowsFileAttributes attrs = 122 WindowsFileAttributes.get(target, false); 123 // non a link so we are done 124 if (!attrs.isSymbolicLink()) { 125 return target.getPathForWin32Calls(); 126 } 127 } catch (WindowsException x) { 128 x.rethrowAsIOException(target); 129 } 130 WindowsPath link = WindowsPath 131 .createFromNormalizedPath(fs, readLink(target)); 132 WindowsPath parent = target.getParent(); 133 if (parent == null) { 134 // no parent so use parent of absolute path 135 final WindowsPath t = target; 136 target = AccessController 137 .doPrivileged(new PrivilegedAction<WindowsPath>() { 138 @Override 139 public WindowsPath run() { 140 return t.toAbsolutePath(); 141 }}); 142 parent = target.getParent(); 143 } 144 target = parent.resolve(link); 145 146 } while (++linkCount < 32); 147 148 throw new FileSystemException(input.getPathForExceptionMessage(), null, 149 "Too many links"); 150 } 151 152 /** 153 * Returns the actual path of a file, optionally resolving all symbolic 154 * links. 155 */ 156 static String getRealPath(WindowsPath input, boolean resolveLinks) 157 throws IOException 158 { 159 WindowsFileSystem fs = input.getFileSystem(); 160 161 // Start with absolute path 162 String path = null; 163 try { 164 path = input.toAbsolutePath().toString(); 165 } catch (IOError x) { 166 throw (IOException)(x.getCause()); 167 } 168 169 // Collapse "." and ".." 170 if (path.indexOf('.') >= 0) { 171 try { 172 path = GetFullPathName(path); 173 } catch (WindowsException x) { 174 x.rethrowAsIOException(input); 175 } 176 } 177 178 // string builder to build up components of path 179 StringBuilder sb = new StringBuilder(path.length()); 180 181 // Copy root component 182 int start; 183 char c0 = path.charAt(0); 184 char c1 = path.charAt(1); 185 if ((c0 <= 'z' && c0 >= 'a' || c0 <= 'Z' && c0 >= 'A') && 186 c1 == ':' && path.charAt(2) == '\\') { 187 // Driver specifier 188 sb.append(Character.toUpperCase(c0)); 189 sb.append(":\\"); 190 start = 3; 191 } else if (c0 == '\\' && c1 == '\\') { 192 // UNC pathname, begins with "\\\\host\\share" 193 int last = path.length() - 1; 194 int pos = path.indexOf('\\', 2); 195 // skip both server and share names 196 if (pos == -1 || (pos == last)) { 197 // The UNC does not have a share name (collapsed by GetFullPathName) 198 throw new FileSystemException(input.getPathForExceptionMessage(), 199 null, "UNC has invalid share"); 200 } 201 pos = path.indexOf('\\', pos+1); 202 if (pos < 0) { 203 pos = last; 204 sb.append(path).append("\\"); 205 } else { 206 sb.append(path, 0, pos+1); 207 } 208 start = pos + 1; 209 } else { 210 throw new AssertionError("path type not recognized"); 211 } 212 213 // if the result is only a root component then we simply check it exists 214 if (start >= path.length()) { 215 String result = sb.toString(); 216 try { 217 GetFileAttributes(result); 218 } catch (WindowsException x) { 219 x.rethrowAsIOException(path); 220 } 221 return result; 222 } 223 224 // iterate through each component to get its actual name in the 225 // directory 226 int curr = start; 227 while (curr < path.length()) { 228 int next = path.indexOf('\\', curr); 229 int end = (next == -1) ? path.length() : next; 230 String search = sb.toString() + path.substring(curr, end); 231 try { 232 FirstFile fileData = FindFirstFile(WindowsPath.addPrefixIfNeeded(search)); 233 FindClose(fileData.handle()); 234 235 // if a reparse point is encountered then we must return the 236 // final path. 237 if (resolveLinks && 238 WindowsFileAttributes.isReparsePoint(fileData.attributes())) 239 { 240 String result = getFinalPath(input); 241 if (result == null) { 242 // Fallback to slow path, usually because there is a sym 243 // link to a file system that doesn't support sym links. 244 WindowsPath resolved = resolveAllLinks( 245 WindowsPath.createFromNormalizedPath(fs, path)); 246 result = getRealPath(resolved, false); 247 } 248 return result; 249 } 250 251 // add the name to the result 252 sb.append(fileData.name()); 253 if (next != -1) { 254 sb.append('\\'); 255 } 256 } catch (WindowsException e) { 257 e.rethrowAsIOException(path); 258 } 259 curr = end + 1; 260 } 261 262 return sb.toString(); 263 } 264 265 /** 266 * Returns target of a symbolic link given the handle of an open file 267 * (that should be a link). 268 */ 269 private static String readLinkImpl(long handle) throws IOException { 270 int size = MAXIMUM_REPARSE_DATA_BUFFER_SIZE; 271 NativeBuffer buffer = NativeBuffers.getNativeBuffer(size); 272 try { 273 try { 274 DeviceIoControlGetReparsePoint(handle, buffer.address(), size); 275 } catch (WindowsException x) { 276 // FIXME: exception doesn't have file name 277 if (x.lastError() == ERROR_NOT_A_REPARSE_POINT) 278 throw new NotLinkException(null, null, x.errorString()); 279 x.rethrowAsIOException((String)null); 280 } 281 282 /* 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_BUFFER 307 */ 308 final short OFFSETOF_REPARSETAG = 0; 309 final short OFFSETOF_PATHOFFSET = 8; 310 final short OFFSETOF_PATHLENGTH = 10; 311 final short OFFSETOF_PATHBUFFER = 16 + 4; // check this 312 313 int tag = (int)unsafe.getLong(buffer.address() + OFFSETOF_REPARSETAG); 314 if (tag != IO_REPARSE_TAG_SYMLINK) { 315 // FIXME: exception doesn't have file name 316 throw new NotLinkException(null, null, "Reparse point is not a symbolic link"); 317 } 318 319 // get offset and length of target 320 short nameOffset = unsafe.getShort(buffer.address() + OFFSETOF_PATHOFFSET); 321 short nameLengthInBytes = unsafe.getShort(buffer.address() + OFFSETOF_PATHLENGTH); 322 if ((nameLengthInBytes % 2) != 0) 323 throw new FileSystemException(null, null, "Symbolic link corrupted"); 324 325 // copy into char array 326 char[] name = new char[nameLengthInBytes/2]; 327 unsafe.copyMemory(null, buffer.address() + OFFSETOF_PATHBUFFER + nameOffset, 328 name, Unsafe.ARRAY_CHAR_BASE_OFFSET, nameLengthInBytes); 329 330 // remove special prefix 331 String target = stripPrefix(new String(name)); 332 if (target.length() == 0) { 333 throw new IOException("Symbolic link target is invalid"); 334 } 335 return target; 336 } finally { 337 buffer.release(); 338 } 339 } 340 341 /** 342 * Resolve all symbolic-links in a given absolute and normalized path 343 */ 344 private static WindowsPath resolveAllLinks(WindowsPath path) 345 throws IOException 346 { 347 assert path.isAbsolute(); 348 WindowsFileSystem fs = path.getFileSystem(); 349 350 // iterate through each name element of the path, resolving links as 351 // we go. 352 int linkCount = 0; 353 int elem = 0; 354 while (elem < path.getNameCount()) { 355 WindowsPath current = path.getRoot().resolve(path.subpath(0, elem+1)); 356 357 WindowsFileAttributes attrs = null; 358 try { 359 attrs = WindowsFileAttributes.get(current, false); 360 } catch (WindowsException x) { 361 x.rethrowAsIOException(current); 362 } 363 364 /** 365 * If a symbolic link then we resolve it against the parent 366 * of the current name element. We then resolve any remaining 367 * part of the path against the result. The target of the link 368 * may have "." and ".." components so re-normalize and restart 369 * the process from the first element. 370 */ 371 if (attrs.isSymbolicLink()) { 372 linkCount++; 373 if (linkCount > 32) 374 throw new IOException("Too many links"); 375 WindowsPath target = WindowsPath 376 .createFromNormalizedPath(fs, readLink(current)); 377 WindowsPath remainder = null; 378 int count = path.getNameCount(); 379 if ((elem+1) < count) { 380 remainder = path.subpath(elem+1, count); 381 } 382 path = current.getParent().resolve(target); 383 try { 384 String full = GetFullPathName(path.toString()); 385 if (!full.equals(path.toString())) { 386 path = WindowsPath.createFromNormalizedPath(fs, full); 387 } 388 } catch (WindowsException x) { 389 x.rethrowAsIOException(path); 390 } 391 if (remainder != null) { 392 path = path.resolve(remainder); 393 } 394 395 // reset 396 elem = 0; 397 } else { 398 // not a link 399 elem++; 400 } 401 } 402 403 return path; 404 } 405 406 /** 407 * Strip long path or symbolic link prefix from path 408 */ 409 private static String stripPrefix(String path) { 410 // prefix for resolved/long path 411 if (path.startsWith("\\\\?\\")) { 412 if (path.startsWith("\\\\?\\UNC\\")) { 413 path = "\\" + path.substring(7); 414 } else { 415 path = path.substring(4); 416 } 417 return path; 418 } 419 420 // prefix for target of symbolic link 421 if (path.startsWith("\\??\\")) { 422 if (path.startsWith("\\??\\UNC\\")) { 423 path = "\\" + path.substring(7); 424 } else { 425 path = path.substring(4); 426 } 427 return path; 428 } 429 return path; 430 } 431 }