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 }