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 }