|
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.util.concurrent.ExecutionException; |
|
31 import com.sun.nio.file.ExtendedCopyOption; |
|
32 |
|
33 import static sun.nio.fs.WindowsNativeDispatcher.*; |
|
34 import static sun.nio.fs.WindowsConstants.*; |
|
35 |
|
36 /** |
|
37 * Utility methods for copying and moving files. |
|
38 */ |
|
39 |
|
40 class WindowsFileCopy { |
|
41 private WindowsFileCopy() { |
|
42 } |
|
43 |
|
44 /** |
|
45 * Copy file from source to target |
|
46 */ |
|
47 static void copy(final WindowsPath source, |
|
48 final WindowsPath target, |
|
49 CopyOption... options) |
|
50 throws IOException |
|
51 { |
|
52 // map options |
|
53 boolean replaceExisting = false; |
|
54 boolean copyAttributes = false; |
|
55 boolean followLinks = true; |
|
56 boolean interruptible = false; |
|
57 for (CopyOption option: options) { |
|
58 if (option == StandardCopyOption.REPLACE_EXISTING) { |
|
59 replaceExisting = true; |
|
60 continue; |
|
61 } |
|
62 if (option == LinkOption.NOFOLLOW_LINKS) { |
|
63 followLinks = false; |
|
64 continue; |
|
65 } |
|
66 if (option == StandardCopyOption.COPY_ATTRIBUTES) { |
|
67 copyAttributes = true; |
|
68 continue; |
|
69 } |
|
70 if (option == ExtendedCopyOption.INTERRUPTIBLE) { |
|
71 interruptible = true; |
|
72 continue; |
|
73 } |
|
74 if (option == null) |
|
75 throw new NullPointerException(); |
|
76 throw new UnsupportedOperationException("Unsupported copy option"); |
|
77 } |
|
78 |
|
79 // check permissions. If the source file is a symbolic link then |
|
80 // later we must also check LinkPermission |
|
81 SecurityManager sm = System.getSecurityManager(); |
|
82 if (sm != null) { |
|
83 source.checkRead(); |
|
84 target.checkWrite(); |
|
85 } |
|
86 |
|
87 // get attributes of source file |
|
88 // attempt to get attributes of target file |
|
89 // if both files are the same there is nothing to do |
|
90 // if target exists and !replace then throw exception |
|
91 |
|
92 WindowsFileAttributes sourceAttrs = null; |
|
93 WindowsFileAttributes targetAttrs = null; |
|
94 |
|
95 long sourceHandle = 0L; |
|
96 try { |
|
97 sourceHandle = source.openForReadAttributeAccess(followLinks); |
|
98 } catch (WindowsException x) { |
|
99 x.rethrowAsIOException(source); |
|
100 } |
|
101 try { |
|
102 // source attributes |
|
103 try { |
|
104 sourceAttrs = WindowsFileAttributes.readAttributes(sourceHandle); |
|
105 } catch (WindowsException x) { |
|
106 x.rethrowAsIOException(source); |
|
107 } |
|
108 |
|
109 // open target (don't follow links) |
|
110 long targetHandle = 0L; |
|
111 try { |
|
112 targetHandle = target.openForReadAttributeAccess(false); |
|
113 try { |
|
114 targetAttrs = WindowsFileAttributes.readAttributes(targetHandle); |
|
115 |
|
116 // if both files are the same then nothing to do |
|
117 if (WindowsFileAttributes.isSameFile(sourceAttrs, targetAttrs)) { |
|
118 return; |
|
119 } |
|
120 |
|
121 // can't replace file |
|
122 if (!replaceExisting) { |
|
123 throw new FileAlreadyExistsException( |
|
124 target.getPathForExceptionMessage()); |
|
125 } |
|
126 |
|
127 } finally { |
|
128 CloseHandle(targetHandle); |
|
129 } |
|
130 } catch (WindowsException x) { |
|
131 // ignore |
|
132 } |
|
133 |
|
134 } finally { |
|
135 CloseHandle(sourceHandle); |
|
136 } |
|
137 |
|
138 // if source file is a symbolic link then we must check for LinkPermission |
|
139 if (sm != null && sourceAttrs.isSymbolicLink()) { |
|
140 sm.checkPermission(new LinkPermission("symbolic")); |
|
141 } |
|
142 |
|
143 final String sourcePath = asWin32Path(source); |
|
144 final String targetPath = asWin32Path(target); |
|
145 |
|
146 // if target exists then delete it. |
|
147 if (targetAttrs != null) { |
|
148 try { |
|
149 if (targetAttrs.isDirectory() || targetAttrs.isDirectoryLink()) { |
|
150 RemoveDirectory(targetPath); |
|
151 } else { |
|
152 DeleteFile(targetPath); |
|
153 } |
|
154 } catch (WindowsException x) { |
|
155 if (targetAttrs.isDirectory()) { |
|
156 // ERROR_ALREADY_EXISTS is returned when attempting to delete |
|
157 // non-empty directory on SAMBA servers. |
|
158 if (x.lastError() == ERROR_DIR_NOT_EMPTY || |
|
159 x.lastError() == ERROR_ALREADY_EXISTS) |
|
160 { |
|
161 throw new FileAlreadyExistsException( |
|
162 target.getPathForExceptionMessage()); |
|
163 } |
|
164 } |
|
165 x.rethrowAsIOException(target); |
|
166 } |
|
167 } |
|
168 |
|
169 // Use CopyFileEx if the file is not a directory or junction |
|
170 if (!sourceAttrs.isDirectory() && !sourceAttrs.isDirectoryLink()) { |
|
171 final int flags = |
|
172 (source.getFileSystem().supportsLinks() && !followLinks) ? |
|
173 COPY_FILE_COPY_SYMLINK : 0; |
|
174 |
|
175 if (interruptible) { |
|
176 // interruptible copy |
|
177 Cancellable copyTask = new Cancellable() { |
|
178 @Override |
|
179 public int cancelValue() { |
|
180 return 1; // TRUE |
|
181 } |
|
182 @Override |
|
183 public void implRun() throws IOException { |
|
184 try { |
|
185 CopyFileEx(sourcePath, targetPath, flags, |
|
186 addressToPollForCancel()); |
|
187 } catch (WindowsException x) { |
|
188 x.rethrowAsIOException(source, target); |
|
189 } |
|
190 } |
|
191 }; |
|
192 try { |
|
193 Cancellable.runInterruptibly(copyTask); |
|
194 } catch (ExecutionException e) { |
|
195 Throwable t = e.getCause(); |
|
196 if (t instanceof IOException) |
|
197 throw (IOException)t; |
|
198 throw new IOException(t); |
|
199 } |
|
200 } else { |
|
201 // non-interruptible copy |
|
202 try { |
|
203 CopyFileEx(sourcePath, targetPath, flags, 0L); |
|
204 } catch (WindowsException x) { |
|
205 x.rethrowAsIOException(source, target); |
|
206 } |
|
207 } |
|
208 if (copyAttributes) { |
|
209 // CopyFileEx does not copy security attributes |
|
210 try { |
|
211 copySecurityAttributes(source, target, followLinks); |
|
212 } catch (IOException x) { |
|
213 // ignore |
|
214 } |
|
215 } |
|
216 return; |
|
217 } |
|
218 |
|
219 // copy directory or directory junction |
|
220 try { |
|
221 if (sourceAttrs.isDirectory()) { |
|
222 CreateDirectory(targetPath, 0L); |
|
223 } else { |
|
224 String linkTarget = WindowsLinkSupport.readLink(source); |
|
225 int flags = SYMBOLIC_LINK_FLAG_DIRECTORY; |
|
226 CreateSymbolicLink(targetPath, |
|
227 addPrefixIfNeeded(linkTarget), |
|
228 flags); |
|
229 } |
|
230 } catch (WindowsException x) { |
|
231 x.rethrowAsIOException(target); |
|
232 } |
|
233 if (copyAttributes) { |
|
234 // copy DOS/timestamps attributes |
|
235 WindowsFileAttributeViews.Dos view = |
|
236 WindowsFileAttributeViews.createDosView(target, false); |
|
237 try { |
|
238 view.setAttributes(sourceAttrs); |
|
239 } catch (IOException x) { |
|
240 if (sourceAttrs.isDirectory()) { |
|
241 try { |
|
242 RemoveDirectory(targetPath); |
|
243 } catch (WindowsException ignore) { } |
|
244 } |
|
245 } |
|
246 |
|
247 // copy security attributes. If this fail it doesn't cause the move |
|
248 // to fail. |
|
249 try { |
|
250 copySecurityAttributes(source, target, followLinks); |
|
251 } catch (IOException ignore) { } |
|
252 } |
|
253 } |
|
254 |
|
255 /** |
|
256 * Move file from source to target |
|
257 */ |
|
258 static void move(WindowsPath source, WindowsPath target, CopyOption... options) |
|
259 throws IOException |
|
260 { |
|
261 // map options |
|
262 boolean atomicMove = false; |
|
263 boolean replaceExisting = false; |
|
264 for (CopyOption option: options) { |
|
265 if (option == StandardCopyOption.ATOMIC_MOVE) { |
|
266 atomicMove = true; |
|
267 continue; |
|
268 } |
|
269 if (option == StandardCopyOption.REPLACE_EXISTING) { |
|
270 replaceExisting = true; |
|
271 continue; |
|
272 } |
|
273 if (option == LinkOption.NOFOLLOW_LINKS) { |
|
274 // ignore |
|
275 continue; |
|
276 } |
|
277 if (option == null) throw new NullPointerException(); |
|
278 throw new UnsupportedOperationException("Unsupported copy option"); |
|
279 } |
|
280 |
|
281 SecurityManager sm = System.getSecurityManager(); |
|
282 if (sm != null) { |
|
283 source.checkWrite(); |
|
284 target.checkWrite(); |
|
285 } |
|
286 |
|
287 final String sourcePath = asWin32Path(source); |
|
288 final String targetPath = asWin32Path(target); |
|
289 |
|
290 // atomic case |
|
291 if (atomicMove) { |
|
292 try { |
|
293 MoveFileEx(sourcePath, targetPath, MOVEFILE_REPLACE_EXISTING); |
|
294 } catch (WindowsException x) { |
|
295 if (x.lastError() == ERROR_NOT_SAME_DEVICE) { |
|
296 throw new AtomicMoveNotSupportedException( |
|
297 source.getPathForExceptionMessage(), |
|
298 target.getPathForExceptionMessage(), |
|
299 x.errorString()); |
|
300 } |
|
301 x.rethrowAsIOException(source, target); |
|
302 } |
|
303 return; |
|
304 } |
|
305 |
|
306 // get attributes of source file |
|
307 // attempt to get attributes of target file |
|
308 // if both files are the same there is nothing to do |
|
309 // if target exists and !replace then throw exception |
|
310 |
|
311 WindowsFileAttributes sourceAttrs = null; |
|
312 WindowsFileAttributes targetAttrs = null; |
|
313 |
|
314 long sourceHandle = 0L; |
|
315 try { |
|
316 sourceHandle = source.openForReadAttributeAccess(false); |
|
317 } catch (WindowsException x) { |
|
318 x.rethrowAsIOException(source); |
|
319 } |
|
320 try { |
|
321 // source attributes |
|
322 try { |
|
323 sourceAttrs = WindowsFileAttributes.readAttributes(sourceHandle); |
|
324 } catch (WindowsException x) { |
|
325 x.rethrowAsIOException(source); |
|
326 } |
|
327 |
|
328 // open target (don't follow links) |
|
329 long targetHandle = 0L; |
|
330 try { |
|
331 targetHandle = target.openForReadAttributeAccess(false); |
|
332 try { |
|
333 targetAttrs = WindowsFileAttributes.readAttributes(targetHandle); |
|
334 |
|
335 // if both files are the same then nothing to do |
|
336 if (WindowsFileAttributes.isSameFile(sourceAttrs, targetAttrs)) { |
|
337 return; |
|
338 } |
|
339 |
|
340 // can't replace file |
|
341 if (!replaceExisting) { |
|
342 throw new FileAlreadyExistsException( |
|
343 target.getPathForExceptionMessage()); |
|
344 } |
|
345 |
|
346 } finally { |
|
347 CloseHandle(targetHandle); |
|
348 } |
|
349 } catch (WindowsException x) { |
|
350 // ignore |
|
351 } |
|
352 |
|
353 } finally { |
|
354 CloseHandle(sourceHandle); |
|
355 } |
|
356 |
|
357 // if target exists then delete it. |
|
358 if (targetAttrs != null) { |
|
359 try { |
|
360 if (targetAttrs.isDirectory() || targetAttrs.isDirectoryLink()) { |
|
361 RemoveDirectory(targetPath); |
|
362 } else { |
|
363 DeleteFile(targetPath); |
|
364 } |
|
365 } catch (WindowsException x) { |
|
366 if (targetAttrs.isDirectory()) { |
|
367 // ERROR_ALREADY_EXISTS is returned when attempting to delete |
|
368 // non-empty directory on SAMBA servers. |
|
369 if (x.lastError() == ERROR_DIR_NOT_EMPTY || |
|
370 x.lastError() == ERROR_ALREADY_EXISTS) |
|
371 { |
|
372 throw new FileAlreadyExistsException( |
|
373 target.getPathForExceptionMessage()); |
|
374 } |
|
375 } |
|
376 x.rethrowAsIOException(target); |
|
377 } |
|
378 } |
|
379 |
|
380 // first try MoveFileEx (no options). If target is on same volume then |
|
381 // all attributes (including security attributes) are preserved. |
|
382 try { |
|
383 MoveFileEx(sourcePath, targetPath, 0); |
|
384 return; |
|
385 } catch (WindowsException x) { |
|
386 if (x.lastError() != ERROR_NOT_SAME_DEVICE) |
|
387 x.rethrowAsIOException(source, target); |
|
388 } |
|
389 |
|
390 // target is on different volume so use MoveFileEx with copy option |
|
391 if (!sourceAttrs.isDirectory() && !sourceAttrs.isDirectoryLink()) { |
|
392 try { |
|
393 MoveFileEx(sourcePath, targetPath, MOVEFILE_COPY_ALLOWED); |
|
394 } catch (WindowsException x) { |
|
395 x.rethrowAsIOException(source, target); |
|
396 } |
|
397 // MoveFileEx does not copy security attributes when moving |
|
398 // across volumes. |
|
399 try { |
|
400 copySecurityAttributes(source, target, false); |
|
401 } catch (IOException x) { |
|
402 // ignore |
|
403 } |
|
404 return; |
|
405 } |
|
406 |
|
407 // moving directory or directory-link to another file system |
|
408 assert sourceAttrs.isDirectory() || sourceAttrs.isDirectoryLink(); |
|
409 |
|
410 // create new directory or directory junction |
|
411 try { |
|
412 if (sourceAttrs.isDirectory()) { |
|
413 CreateDirectory(targetPath, 0L); |
|
414 } else { |
|
415 String linkTarget = WindowsLinkSupport.readLink(source); |
|
416 CreateSymbolicLink(targetPath, |
|
417 addPrefixIfNeeded(linkTarget), |
|
418 SYMBOLIC_LINK_FLAG_DIRECTORY); |
|
419 } |
|
420 } catch (WindowsException x) { |
|
421 x.rethrowAsIOException(target); |
|
422 } |
|
423 |
|
424 // copy timestamps/DOS attributes |
|
425 WindowsFileAttributeViews.Dos view = |
|
426 WindowsFileAttributeViews.createDosView(target, false); |
|
427 try { |
|
428 view.setAttributes(sourceAttrs); |
|
429 } catch (IOException x) { |
|
430 // rollback |
|
431 try { |
|
432 RemoveDirectory(targetPath); |
|
433 } catch (WindowsException ignore) { } |
|
434 throw x; |
|
435 } |
|
436 |
|
437 // copy security attributes. If this fails it doesn't cause the move |
|
438 // to fail. |
|
439 try { |
|
440 copySecurityAttributes(source, target, false); |
|
441 } catch (IOException ignore) { } |
|
442 |
|
443 // delete source |
|
444 try { |
|
445 RemoveDirectory(sourcePath); |
|
446 } catch (WindowsException x) { |
|
447 // rollback |
|
448 try { |
|
449 RemoveDirectory(targetPath); |
|
450 } catch (WindowsException ignore) { } |
|
451 // ERROR_ALREADY_EXISTS is returned when attempting to delete |
|
452 // non-empty directory on SAMBA servers. |
|
453 if (x.lastError() == ERROR_DIR_NOT_EMPTY || |
|
454 x.lastError() == ERROR_ALREADY_EXISTS) |
|
455 { |
|
456 throw new DirectoryNotEmptyException( |
|
457 target.getPathForExceptionMessage()); |
|
458 } |
|
459 x.rethrowAsIOException(source); |
|
460 } |
|
461 } |
|
462 |
|
463 |
|
464 private static String asWin32Path(WindowsPath path) throws IOException { |
|
465 try { |
|
466 return path.getPathForWin32Calls(); |
|
467 } catch (WindowsException x) { |
|
468 x.rethrowAsIOException(path); |
|
469 return null; |
|
470 } |
|
471 } |
|
472 |
|
473 /** |
|
474 * Copy DACL/owner/group from source to target |
|
475 */ |
|
476 private static void copySecurityAttributes(WindowsPath source, |
|
477 WindowsPath target, |
|
478 boolean followLinks) |
|
479 throws IOException |
|
480 { |
|
481 String path = WindowsLinkSupport.getFinalPath(source, followLinks); |
|
482 |
|
483 // may need SeRestorePrivilege to set file owner |
|
484 WindowsSecurity.Privilege priv = |
|
485 WindowsSecurity.enablePrivilege("SeRestorePrivilege"); |
|
486 try { |
|
487 int request = (DACL_SECURITY_INFORMATION | |
|
488 OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION); |
|
489 NativeBuffer buffer = |
|
490 WindowsAclFileAttributeView.getFileSecurity(path, request); |
|
491 try { |
|
492 try { |
|
493 SetFileSecurity(target.getPathForWin32Calls(), request, |
|
494 buffer.address()); |
|
495 } catch (WindowsException x) { |
|
496 x.rethrowAsIOException(target); |
|
497 } |
|
498 } finally { |
|
499 buffer.release(); |
|
500 } |
|
501 } finally { |
|
502 priv.drop(); |
|
503 } |
|
504 } |
|
505 |
|
506 /** |
|
507 * Add long path prefix to path if required |
|
508 */ |
|
509 private static String addPrefixIfNeeded(String path) { |
|
510 if (path.length() > 248) { |
|
511 if (path.startsWith("\\\\")) { |
|
512 path = "\\\\?\\UNC" + path.substring(1, path.length()); |
|
513 } else { |
|
514 path = "\\\\?\\" + path; |
|
515 } |
|
516 } |
|
517 return path; |
|
518 } |
|
519 } |