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 || !fs.supportsLinks()) 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 if (resolveLinks && !fs.supportsLinks()) 161 resolveLinks = false; 162 163 // Start with absolute path 164 String path = null; 165 try { 166 path = input.toAbsolutePath().toString(); 167 } catch (IOError x) { 168 throw (IOException)(x.getCause()); 169 } 170 171 // Collapse "." and ".." 172 if (path.indexOf('.') >= 0) { 173 try { 174 path = GetFullPathName(path); 175 } catch (WindowsException x) { 176 x.rethrowAsIOException(input); 177 } 178 } 179 180 // string builder to build up components of path 181 StringBuilder sb = new StringBuilder(path.length()); 182 183 // Copy root component 184 int start; 185 char c0 = path.charAt(0); 186 char c1 = path.charAt(1); 187 if ((c0 <= 'z' && c0 >= 'a' || c0 <= 'Z' && c0 >= 'A') && 188 c1 == ':' && path.charAt(2) == '\\') { 189 // Driver specifier 190 sb.append(Character.toUpperCase(c0)); 191 sb.append(":\\"); 192 start = 3; 193 } else if (c0 == '\\' && c1 == '\\') { 194 // UNC pathname, begins with "\\\\host\\share" 195 int last = path.length() - 1; 196 int pos = path.indexOf('\\', 2); 197 // skip both server and share names 198 if (pos == -1 || (pos == last)) { 199 // The UNC does not have a share name (collapsed by GetFullPathName) 200 throw new FileSystemException(input.getPathForExceptionMessage(), 201 null, "UNC has invalid share"); 202 } 203 pos = path.indexOf('\\', pos+1); 204 if (pos < 0) { 205 pos = last; 206 sb.append(path).append("\\"); 207 } else { 208 sb.append(path, 0, pos+1); 209 } 210 start = pos + 1; 211 } else { 212 throw new AssertionError("path type not recognized"); 213 } 214 215 // if the result is only a root component then we simply check it exists 216 if (start >= path.length()) { 217 String result = sb.toString(); 218 try { 219 GetFileAttributes(result); 220 } catch (WindowsException x) { 221 x.rethrowAsIOException(path); 222 } 223 return result; 224 } 225 226 // iterate through each component to get its actual name in the 227 // directory 228 int curr = start; 229 while (curr < path.length()) { 230 int next = path.indexOf('\\', curr); 231 int end = (next == -1) ? path.length() : next; 232 String search = sb.toString() + path.substring(curr, end); 233 try { 234 FirstFile fileData = FindFirstFile(WindowsPath.addPrefixIfNeeded(search)); 235 FindClose(fileData.handle()); 236 237 // if a reparse point is encountered then we must return the 238 // final path. 239 if (resolveLinks && 240 WindowsFileAttributes.isReparsePoint(fileData.attributes())) 241 { 242 String result = getFinalPath(input); 243 if (result == null) { 244 // Fallback to slow path, usually because there is a sym 245 // link to a file system that doesn't support sym links. 246 WindowsPath resolved = resolveAllLinks( 247 WindowsPath.createFromNormalizedPath(fs, path)); 248 result = getRealPath(resolved, false); 249 } 250 return result; 251 } 252 253 // add the name to the result 254 sb.append(fileData.name()); 255 if (next != -1) { 256 sb.append('\\'); 257 } 258 } catch (WindowsException e) { 259 e.rethrowAsIOException(path); 260 } 261 curr = end + 1; 262 } 263 264 return sb.toString(); 265 } 266 267 /** 268 * Returns target of a symbolic link given the handle of an open file 269 * (that should be a link). 270 */ 271 private static String readLinkImpl(long handle) throws IOException { 272 int size = MAXIMUM_REPARSE_DATA_BUFFER_SIZE; 273 NativeBuffer buffer = NativeBuffers.getNativeBuffer(size); 274 try { 275 try { 276 DeviceIoControlGetReparsePoint(handle, buffer.address(), size); 277 } catch (WindowsException x) { 278 // FIXME: exception doesn't have file name 279 if (x.lastError() == ERROR_NOT_A_REPARSE_POINT) 280 throw new NotLinkException(null, null, x.errorString()); 281 x.rethrowAsIOException((String)null); 282 } 283 284 /* 285 * typedef struct _REPARSE_DATA_BUFFER { 286 * ULONG ReparseTag; 287 * USHORT ReparseDataLength; 288 * USHORT Reserved; 289 * union { 290 * struct { 291 * USHORT SubstituteNameOffset; 292 * USHORT SubstituteNameLength; 293 * USHORT PrintNameOffset; 294 * USHORT PrintNameLength; 295 * WCHAR PathBuffer[1]; 296 * } SymbolicLinkReparseBuffer; 297 * struct { 298 * USHORT SubstituteNameOffset; 299 * USHORT SubstituteNameLength; 300 * USHORT PrintNameOffset; 301 * USHORT PrintNameLength; 302 * WCHAR PathBuffer[1]; 303 * } MountPointReparseBuffer; 304 * struct { 305 * UCHAR DataBuffer[1]; 306 * } GenericReparseBuffer; 307 * }; 308 * } REPARSE_DATA_BUFFER 309 */ 310 final short OFFSETOF_REPARSETAG = 0; 311 final short OFFSETOF_PATHOFFSET = 8; 312 final short OFFSETOF_PATHLENGTH = 10; 313 final short OFFSETOF_PATHBUFFER = 16 + 4; // check this 314 315 int tag = (int)unsafe.getLong(buffer.address() + OFFSETOF_REPARSETAG); 316 if (tag != IO_REPARSE_TAG_SYMLINK) { 317 // FIXME: exception doesn't have file name 318 throw new NotLinkException(null, null, "Reparse point is not a symbolic link"); 319 } 320 321 // get offset and length of target 322 short nameOffset = unsafe.getShort(buffer.address() + OFFSETOF_PATHOFFSET); 323 short nameLengthInBytes = unsafe.getShort(buffer.address() + OFFSETOF_PATHLENGTH); 324 if ((nameLengthInBytes % 2) != 0) 325 throw new FileSystemException(null, null, "Symbolic link corrupted"); 326 327 // copy into char array 328 char[] name = new char[nameLengthInBytes/2]; 329 unsafe.copyMemory(null, buffer.address() + OFFSETOF_PATHBUFFER + nameOffset, 330 name, Unsafe.ARRAY_CHAR_BASE_OFFSET, nameLengthInBytes); 331 332 // remove special prefix 333 String target = stripPrefix(new String(name)); 334 if (target.length() == 0) { 335 throw new IOException("Symbolic link target is invalid"); 336 } 337 return target; 338 } finally { 339 buffer.release(); 340 } 341 } 342 343 /** 344 * Resolve all symbolic-links in a given absolute and normalized path 345 */ 346 private static WindowsPath resolveAllLinks(WindowsPath path) 347 throws IOException 348 { 349 assert path.isAbsolute(); 350 WindowsFileSystem fs = path.getFileSystem(); 351 352 // iterate through each name element of the path, resolving links as 353 // we go. 354 int linkCount = 0; 355 int elem = 0; 356 while (elem < path.getNameCount()) { 357 WindowsPath current = path.getRoot().resolve(path.subpath(0, elem+1)); 358 359 WindowsFileAttributes attrs = null; 360 try { 361 attrs = WindowsFileAttributes.get(current, false); 362 } catch (WindowsException x) { 363 x.rethrowAsIOException(current); 364 } 365 366 /** 367 * If a symbolic link then we resolve it against the parent 368 * of the current name element. We then resolve any remaining 369 * part of the path against the result. The target of the link 370 * may have "." and ".." components so re-normalize and restart 371 * the process from the first element. 372 */ 373 if (attrs.isSymbolicLink()) { 374 linkCount++; 375 if (linkCount > 32) 376 throw new IOException("Too many links"); 377 WindowsPath target = WindowsPath 378 .createFromNormalizedPath(fs, readLink(current)); 379 WindowsPath remainder = null; 380 int count = path.getNameCount(); 381 if ((elem+1) < count) { 382 remainder = path.subpath(elem+1, count); 383 } 384 path = current.getParent().resolve(target); 385 try { 386 String full = GetFullPathName(path.toString()); 387 if (!full.equals(path.toString())) { 388 path = WindowsPath.createFromNormalizedPath(fs, full); 389 } 390 } catch (WindowsException x) { 391 x.rethrowAsIOException(path); 392 } 393 if (remainder != null) { 394 path = path.resolve(remainder); 395 } 396 397 // reset 398 elem = 0; 399 } else { 400 // not a link 401 elem++; 402 } 403 } 404 405 return path; 406 } 407 408 /** 409 * Strip long path or symbolic link prefix from path 410 */ 411 private static String stripPrefix(String path) { 412 // prefix for resolved/long path 413 if (path.startsWith("\\\\?\\")) { 414 if (path.startsWith("\\\\?\\UNC\\")) { 415 path = "\\" + path.substring(7); 416 } else { 417 path = path.substring(4); 418 } 419 return path; 420 } 421 422 // prefix for target of symbolic link 423 if (path.startsWith("\\??\\")) { 424 if (path.startsWith("\\??\\UNC\\")) { 425 path = "\\" + path.substring(7); 426 } else { 427 path = path.substring(4); 428 } 429 return path; 430 } 431 return path; 432 } 433 }