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