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
26package sun.nio.fs;
27
28import java.nio.file.*;
29import java.io.IOException;
30import java.io.IOError;
31import java.security.AccessController;
32import java.security.PrivilegedAction;
33import jdk.internal.misc.Unsafe;
34
35import static sun.nio.fs.WindowsNativeDispatcher.*;
36import static sun.nio.fs.WindowsConstants.*;
37
38/**
39 * Utility methods for symbolic link support on Windows Vista and newer.
40 */
41
42class 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}
432