jdk/src/windows/classes/sun/nio/fs/WindowsLinkSupport.java
changeset 2057 3acf8e5e2ca0
child 3065 452aaa2899fc
equal deleted inserted replaced
2056:115e09b7a004 2057:3acf8e5e2ca0
       
     1 /*
       
     2  * Copyright 2008-2009 Sun Microsystems, Inc.  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.  Sun designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
       
    22  * CA 95054 USA or visit www.sun.com if you need additional information or
       
    23  * have any 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 sun.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 of a given path as a String. This should be used
       
    67      * prior to calling Win32 system calls that do not follow links.
       
    68      */
       
    69     static String getFinalPath(WindowsPath input, boolean followLinks)
       
    70         throws IOException
       
    71     {
       
    72         WindowsFileSystem fs = input.getFileSystem();
       
    73 
       
    74         try {
       
    75             // if not following links then don't need final path
       
    76             if (!followLinks || !fs.supportsLinks())
       
    77                 return input.getPathForWin32Calls();
       
    78 
       
    79             // if file is a sym link then don't need final path
       
    80             if (!WindowsFileAttributes.get(input, false).isSymbolicLink()) {
       
    81                 return input.getPathForWin32Calls();
       
    82             }
       
    83         } catch (WindowsException x) {
       
    84             x.rethrowAsIOException(input);
       
    85         }
       
    86 
       
    87         // The file is a symbolic link so we open it and try to get the
       
    88         // normalized path. This should succeed on NTFS but may fail if there
       
    89         // is a link to a non-NFTS file system.
       
    90         long h = 0;
       
    91         try {
       
    92             h = input.openForReadAttributeAccess(true);
       
    93         } catch (WindowsException x) {
       
    94             x.rethrowAsIOException(input);
       
    95         }
       
    96         try {
       
    97             return stripPrefix(GetFinalPathNameByHandle(h));
       
    98         } catch (WindowsException x) {
       
    99             // ERROR_INVALID_LEVEL is the error returned when not supported by
       
   100             // the file system
       
   101             if (x.lastError() != ERROR_INVALID_LEVEL)
       
   102                 x.rethrowAsIOException(input);
       
   103         } finally {
       
   104             CloseHandle(h);
       
   105         }
       
   106 
       
   107         // Fallback: read target of link, resolve against parent, and repeat
       
   108         // until file is not a link.
       
   109         WindowsPath target = input;
       
   110         int linkCount = 0;
       
   111         do {
       
   112             try {
       
   113                 WindowsFileAttributes attrs =
       
   114                     WindowsFileAttributes.get(target, false);
       
   115                 // non a link so we are done
       
   116                 if (!attrs.isSymbolicLink()) {
       
   117                     return target.getPathForWin32Calls();
       
   118                 }
       
   119             } catch (WindowsException x) {
       
   120                 x.rethrowAsIOException(target);
       
   121             }
       
   122             WindowsPath link = WindowsPath
       
   123                 .createFromNormalizedPath(fs, readLink(target));
       
   124             WindowsPath parent = target.getParent();
       
   125             if (parent == null) {
       
   126                 // no parent so use parent of absolute path
       
   127                 final WindowsPath t = target;
       
   128                 target = AccessController
       
   129                     .doPrivileged(new PrivilegedAction<WindowsPath>() {
       
   130                         @Override
       
   131                         public WindowsPath run() {
       
   132                             return t.toAbsolutePath();
       
   133                         }});
       
   134                 parent = target.getParent();
       
   135             }
       
   136             target = parent.resolve(link);
       
   137 
       
   138         } while (++linkCount < 32);
       
   139 
       
   140         throw new FileSystemException(input.getPathForExceptionMessage(), null,
       
   141             "Too many links");
       
   142     }
       
   143 
       
   144     /**
       
   145      * Returns the actual path of a file, optionally resolving all symbolic
       
   146      * links.
       
   147      */
       
   148     static String getRealPath(WindowsPath input, boolean resolveLinks)
       
   149         throws IOException
       
   150     {
       
   151         WindowsFileSystem fs = input.getFileSystem();
       
   152         if (!fs.supportsLinks())
       
   153             resolveLinks = false;
       
   154 
       
   155         // On Vista use GetFinalPathNameByHandle. This should succeed on NTFS
       
   156         // but may fail if there is a link to a non-NFTS file system.
       
   157         if (resolveLinks) {
       
   158             long h = 0;
       
   159             try {
       
   160                 h = input.openForReadAttributeAccess(true);
       
   161             } catch (WindowsException x) {
       
   162                 x.rethrowAsIOException(input);
       
   163             }
       
   164             try {
       
   165                 return stripPrefix(GetFinalPathNameByHandle(h));
       
   166             } catch (WindowsException x) {
       
   167                 if (x.lastError() != ERROR_INVALID_LEVEL)
       
   168                     x.rethrowAsIOException(input);
       
   169             } finally {
       
   170                 CloseHandle(h);
       
   171             }
       
   172         }
       
   173 
       
   174         // Not resolving links or we are on Windows Vista (or newer) with a
       
   175         // link to non-NFTS file system.
       
   176 
       
   177         // Start with absolute path
       
   178         String path = null;
       
   179         try {
       
   180             path = input.toAbsolutePath().toString();
       
   181         } catch (IOError x) {
       
   182             throw (IOException)(x.getCause());
       
   183         }
       
   184 
       
   185         // Collapse "." and ".."
       
   186         try {
       
   187             path = GetFullPathName(path);
       
   188         } catch (WindowsException x) {
       
   189             x.rethrowAsIOException(input);
       
   190         }
       
   191 
       
   192         // eliminate all symbolic links
       
   193         if (resolveLinks) {
       
   194             path = resolveAllLinks(WindowsPath.createFromNormalizedPath(fs, path));
       
   195         }
       
   196 
       
   197         // string builder to build up components of path
       
   198         StringBuilder sb = new StringBuilder(path.length());
       
   199 
       
   200         // Copy root component
       
   201         int start;
       
   202         char c0 = path.charAt(0);
       
   203         char c1 = path.charAt(1);
       
   204         if ((c0 <= 'z' && c0 >= 'a' || c0 <= 'Z' && c0 >= 'A') &&
       
   205             c1 == ':' && path.charAt(2) == '\\') {
       
   206             // Driver specifier
       
   207             sb.append(Character.toUpperCase(c0));
       
   208             sb.append(":\\");
       
   209             start = 3;
       
   210         } else if (c0 == '\\' && c1 == '\\') {
       
   211             // UNC pathname, begins with "\\\\host\\share"
       
   212             int last = path.length() - 1;
       
   213             int pos = path.indexOf('\\', 2);
       
   214             // skip both server and share names
       
   215             if (pos == -1 || (pos == last)) {
       
   216                 // The UNC does not have a share name (collapsed by GetFullPathName)
       
   217                 throw new FileSystemException(input.getPathForExceptionMessage(),
       
   218                     null, "UNC has invalid share");
       
   219             }
       
   220             pos = path.indexOf('\\', pos+1);
       
   221             if (pos < 0) {
       
   222                 pos = last;
       
   223                 sb.append(path).append("\\");
       
   224             } else {
       
   225                 sb.append(path, 0, pos+1);
       
   226             }
       
   227             start = pos + 1;
       
   228         } else {
       
   229             throw new AssertionError("path type not recognized");
       
   230         }
       
   231 
       
   232         // check root directory exists
       
   233         try {
       
   234             FirstFile fileData = FindFirstFile(sb.toString() + "*");
       
   235             FindClose(fileData.handle());
       
   236         } catch (WindowsException x) {
       
   237             x.rethrowAsIOException(path);
       
   238         }
       
   239 
       
   240         // iterate through each component to get its actual name in the
       
   241         // directory
       
   242         int curr = start;
       
   243         while (curr < path.length()) {
       
   244             int next = path.indexOf('\\', curr);
       
   245             int end = (next == -1) ? path.length() : next;
       
   246             String search = sb.toString() + path.substring(curr, end);
       
   247             try {
       
   248                 FirstFile fileData = FindFirstFile(addLongPathPrefixIfNeeded(search));
       
   249                 try {
       
   250                     sb.append(fileData.name());
       
   251                     if (next != -1) {
       
   252                         sb.append('\\');
       
   253                     }
       
   254                 } finally {
       
   255                     FindClose(fileData.handle());
       
   256                 }
       
   257             } catch (WindowsException e) {
       
   258                 e.rethrowAsIOException(path);
       
   259             }
       
   260             curr = end + 1;
       
   261         }
       
   262 
       
   263         return sb.toString();
       
   264     }
       
   265 
       
   266     /**
       
   267      * Returns target of a symbolic link given the handle of an open file
       
   268      * (that should be a link).
       
   269      */
       
   270     private static String readLinkImpl(long handle) throws IOException {
       
   271         int size = MAXIMUM_REPARSE_DATA_BUFFER_SIZE;
       
   272         NativeBuffer buffer = NativeBuffers.getNativeBuffer(size);
       
   273         try {
       
   274             try {
       
   275                 DeviceIoControlGetReparsePoint(handle, buffer.address(), size);
       
   276             } catch (WindowsException x) {
       
   277                 // FIXME: exception doesn't have file name
       
   278                 if (x.lastError() == ERROR_NOT_A_REPARSE_POINT)
       
   279                     throw new NotLinkException(null, null, x.errorString());
       
   280                 x.rethrowAsIOException((String)null);
       
   281             }
       
   282 
       
   283             /*
       
   284              * typedef struct _REPARSE_DATA_BUFFER {
       
   285              *     ULONG  ReparseTag;
       
   286              *     USHORT  ReparseDataLength;
       
   287              *     USHORT  Reserved;
       
   288              *     union {
       
   289              *         struct {
       
   290              *             USHORT  SubstituteNameOffset;
       
   291              *             USHORT  SubstituteNameLength;
       
   292              *             USHORT  PrintNameOffset;
       
   293              *             USHORT  PrintNameLength;
       
   294              *             WCHAR  PathBuffer[1];
       
   295              *         } SymbolicLinkReparseBuffer;
       
   296              *         struct {
       
   297              *             USHORT  SubstituteNameOffset;
       
   298              *             USHORT  SubstituteNameLength;
       
   299              *             USHORT  PrintNameOffset;
       
   300              *             USHORT  PrintNameLength;
       
   301              *             WCHAR  PathBuffer[1];
       
   302              *         } MountPointReparseBuffer;
       
   303              *         struct {
       
   304              *             UCHAR  DataBuffer[1];
       
   305              *         } GenericReparseBuffer;
       
   306              *     };
       
   307              * } REPARSE_DATA_BUFFER
       
   308              */
       
   309             final short OFFSETOF_REPARSETAG = 0;
       
   310             final short OFFSETOF_PATHOFFSET = 8;
       
   311             final short OFFSETOF_PATHLENGTH = 10;
       
   312             final short OFFSETOF_PATHBUFFER = 16 + 4;   // check this
       
   313 
       
   314             int tag = (int)unsafe.getLong(buffer.address() + OFFSETOF_REPARSETAG);
       
   315             if (tag != IO_REPARSE_TAG_SYMLINK) {
       
   316                 // FIXME: exception doesn't have file name
       
   317                 throw new NotLinkException(null, null, "Reparse point is not a symbolic link");
       
   318             }
       
   319 
       
   320             // get offset and length of target
       
   321             short nameOffset = unsafe.getShort(buffer.address() + OFFSETOF_PATHOFFSET);
       
   322             short nameLengthInBytes = unsafe.getShort(buffer.address() + OFFSETOF_PATHLENGTH);
       
   323             if ((nameLengthInBytes % 2) != 0)
       
   324                 throw new FileSystemException(null, null, "Symbolic link corrupted");
       
   325 
       
   326             // copy into char array
       
   327             char[] name = new char[nameLengthInBytes/2];
       
   328             unsafe.copyMemory(null, buffer.address() + OFFSETOF_PATHBUFFER + nameOffset,
       
   329                 name, Unsafe.ARRAY_CHAR_BASE_OFFSET, nameLengthInBytes);
       
   330 
       
   331             // remove special prefix
       
   332             String target = stripPrefix(new String(name));
       
   333             if (target.length() == 0) {
       
   334                 throw new IOException("Symbolic link target is invalid");
       
   335             }
       
   336             return target;
       
   337         } finally {
       
   338             buffer.release();
       
   339         }
       
   340     }
       
   341 
       
   342     /**
       
   343      * Resolve all symbolic-links in a given absolute and normalized path
       
   344      */
       
   345     private static String resolveAllLinks(WindowsPath path)
       
   346         throws IOException
       
   347     {
       
   348         assert path.isAbsolute();
       
   349         WindowsFileSystem fs = path.getFileSystem();
       
   350 
       
   351         // iterate through each name element of the path, resolving links as
       
   352         // we go.
       
   353         int linkCount = 0;
       
   354         int elem = 0;
       
   355         while (elem < path.getNameCount()) {
       
   356             WindowsPath current = path.getRoot().resolve(path.subpath(0, elem+1));
       
   357 
       
   358             WindowsFileAttributes attrs = null;
       
   359             try {
       
   360                 attrs = WindowsFileAttributes.get(current, false);
       
   361             } catch (WindowsException x) {
       
   362                 x.rethrowAsIOException(current);
       
   363             }
       
   364 
       
   365             /**
       
   366              * If a symbolic link then we resolve it against the parent
       
   367              * of the current name element. We then resolve any remaining
       
   368              * part of the path against the result. The target of the link
       
   369              * may have "." and ".." components so re-normalize and restart
       
   370              * the process from the first element.
       
   371              */
       
   372             if (attrs.isSymbolicLink()) {
       
   373                 linkCount++;
       
   374                 if (linkCount > 32)
       
   375                     throw new IOException("Too many links");
       
   376                 WindowsPath target = WindowsPath
       
   377                     .createFromNormalizedPath(fs, readLink(current));
       
   378                 WindowsPath remainder = null;
       
   379                 int count = path.getNameCount();
       
   380                 if ((elem+1) < count) {
       
   381                     remainder = path.subpath(elem+1, count);
       
   382                 }
       
   383                 path = current.getParent().resolve(target);
       
   384                 try {
       
   385                     String full = GetFullPathName(path.toString());
       
   386                     if (!full.equals(path.toString())) {
       
   387                         path = WindowsPath.createFromNormalizedPath(fs, full);
       
   388                     }
       
   389                 } catch (WindowsException x) {
       
   390                     x.rethrowAsIOException(path);
       
   391                 }
       
   392                 if (remainder != null) {
       
   393                     path = path.resolve(remainder);
       
   394                 }
       
   395 
       
   396                 // reset
       
   397                 elem = 0;
       
   398             } else {
       
   399                 // not a link
       
   400                 elem++;
       
   401             }
       
   402         }
       
   403 
       
   404         return path.toString();
       
   405     }
       
   406 
       
   407     /**
       
   408      * Add long path prefix to path if required.
       
   409      */
       
   410     private static String addLongPathPrefixIfNeeded(String path) {
       
   411         if (path.length() > 248) {
       
   412             if (path.startsWith("\\\\")) {
       
   413                 path = "\\\\?\\UNC" + path.substring(1, path.length());
       
   414             } else {
       
   415                 path = "\\\\?\\" + path;
       
   416             }
       
   417         }
       
   418         return path;
       
   419     }
       
   420 
       
   421     /**
       
   422      * Strip long path or symbolic link prefix from path
       
   423      */
       
   424     private static String stripPrefix(String path) {
       
   425         // prefix for resolved/long path
       
   426         if (path.startsWith("\\\\?\\")) {
       
   427             if (path.startsWith("\\\\?\\UNC\\")) {
       
   428                 path = "\\" + path.substring(7);
       
   429             } else {
       
   430                 path = path.substring(4);
       
   431             }
       
   432             return path;
       
   433         }
       
   434 
       
   435         // prefix for target of symbolic link
       
   436         if (path.startsWith("\\??\\")) {
       
   437             if (path.startsWith("\\??\\UNC\\")) {
       
   438                 path = "\\" + path.substring(7);
       
   439             } else {
       
   440                 path = path.substring(4);
       
   441             }
       
   442             return path;
       
   443         }
       
   444         return path;
       
   445     }
       
   446 }