1 /*
2 * Copyright (c) 2008, 2011, 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.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 DirectoryNotEmptyException(
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 DirectoryNotEmptyException(
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 }