rev 12972 : 8140606: Update library code to use internal Unsafe
Reviewed-by: duke
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 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 (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 }
--- EOF ---