1/*
2 * Copyright (c) 2008, 2016, 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.util.concurrent.ExecutionException;
31
32import static sun.nio.fs.WindowsNativeDispatcher.*;
33import static sun.nio.fs.WindowsConstants.*;
34
35/**
36 * Utility methods for copying and moving files.
37 */
38
39class WindowsFileCopy {
40    private WindowsFileCopy() {
41    }
42
43    /**
44     * Copy file from source to target
45     */
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    /**
253     * Move file from source to target
254     */
255    static void move(WindowsPath source, WindowsPath target, CopyOption... options)
256        throws IOException
257    {
258        // map options
259        boolean atomicMove = false;
260        boolean replaceExisting = false;
261        for (CopyOption option: options) {
262            if (option == StandardCopyOption.ATOMIC_MOVE) {
263                atomicMove = true;
264                continue;
265            }
266            if (option == StandardCopyOption.REPLACE_EXISTING) {
267                replaceExisting = true;
268                continue;
269            }
270            if (option == LinkOption.NOFOLLOW_LINKS) {
271                // ignore
272                continue;
273            }
274            if (option == null) throw new NullPointerException();
275            throw new UnsupportedOperationException("Unsupported copy option");
276        }
277
278        SecurityManager sm = System.getSecurityManager();
279        if (sm != null) {
280            source.checkWrite();
281            target.checkWrite();
282        }
283
284        final String sourcePath = asWin32Path(source);
285        final String targetPath = asWin32Path(target);
286
287        // atomic case
288        if (atomicMove) {
289            try {
290                MoveFileEx(sourcePath, targetPath, MOVEFILE_REPLACE_EXISTING);
291            } catch (WindowsException x) {
292                if (x.lastError() == ERROR_NOT_SAME_DEVICE) {
293                    throw new AtomicMoveNotSupportedException(
294                        source.getPathForExceptionMessage(),
295                        target.getPathForExceptionMessage(),
296                        x.errorString());
297                }
298                x.rethrowAsIOException(source, target);
299            }
300            return;
301        }
302
303        // get attributes of source file
304        // attempt to get attributes of target file
305        // if both files are the same there is nothing to do
306        // if target exists and !replace then throw exception
307
308        WindowsFileAttributes sourceAttrs = null;
309        WindowsFileAttributes targetAttrs = null;
310
311        long sourceHandle = 0L;
312        try {
313            sourceHandle = source.openForReadAttributeAccess(false);
314        } catch (WindowsException x) {
315            x.rethrowAsIOException(source);
316        }
317        try {
318            // source attributes
319            try {
320                sourceAttrs = WindowsFileAttributes.readAttributes(sourceHandle);
321            } catch (WindowsException x) {
322                x.rethrowAsIOException(source);
323            }
324
325            // open target (don't follow links)
326            long targetHandle = 0L;
327            try {
328                targetHandle = target.openForReadAttributeAccess(false);
329                try {
330                    targetAttrs = WindowsFileAttributes.readAttributes(targetHandle);
331
332                    // if both files are the same then nothing to do
333                    if (WindowsFileAttributes.isSameFile(sourceAttrs, targetAttrs)) {
334                        return;
335                    }
336
337                    // can't replace file
338                    if (!replaceExisting) {
339                        throw new FileAlreadyExistsException(
340                            target.getPathForExceptionMessage());
341                    }
342
343                } finally {
344                    CloseHandle(targetHandle);
345                }
346            } catch (WindowsException x) {
347                // ignore
348            }
349
350        } finally {
351            CloseHandle(sourceHandle);
352        }
353
354        // if target exists then delete it.
355        if (targetAttrs != null) {
356            try {
357                if (targetAttrs.isDirectory() || targetAttrs.isDirectoryLink()) {
358                    RemoveDirectory(targetPath);
359                } else {
360                    DeleteFile(targetPath);
361                }
362            } catch (WindowsException x) {
363                if (targetAttrs.isDirectory()) {
364                    // ERROR_ALREADY_EXISTS is returned when attempting to delete
365                    // non-empty directory on SAMBA servers.
366                    if (x.lastError() == ERROR_DIR_NOT_EMPTY ||
367                        x.lastError() == ERROR_ALREADY_EXISTS)
368                    {
369                        throw new DirectoryNotEmptyException(
370                            target.getPathForExceptionMessage());
371                    }
372                }
373                x.rethrowAsIOException(target);
374            }
375        }
376
377        // first try MoveFileEx (no options). If target is on same volume then
378        // all attributes (including security attributes) are preserved.
379        try {
380            MoveFileEx(sourcePath, targetPath, 0);
381            return;
382        } catch (WindowsException x) {
383            if (x.lastError() != ERROR_NOT_SAME_DEVICE)
384                x.rethrowAsIOException(source, target);
385        }
386
387        // target is on different volume so use MoveFileEx with copy option
388        if (!sourceAttrs.isDirectory() && !sourceAttrs.isDirectoryLink()) {
389            try {
390                MoveFileEx(sourcePath, targetPath, MOVEFILE_COPY_ALLOWED);
391            } catch (WindowsException x) {
392                x.rethrowAsIOException(source, target);
393            }
394            // MoveFileEx does not copy security attributes when moving
395            // across volumes.
396            try {
397                copySecurityAttributes(source, target, false);
398            } catch (IOException x) {
399                // ignore
400            }
401            return;
402        }
403
404        // moving directory or directory-link to another file system
405        assert sourceAttrs.isDirectory() || sourceAttrs.isDirectoryLink();
406
407        // create new directory or directory junction
408        try {
409            if (sourceAttrs.isDirectory()) {
410                CreateDirectory(targetPath, 0L);
411            } else {
412                String linkTarget = WindowsLinkSupport.readLink(source);
413                CreateSymbolicLink(targetPath,
414                                   WindowsPath.addPrefixIfNeeded(linkTarget),
415                                   SYMBOLIC_LINK_FLAG_DIRECTORY);
416            }
417        } catch (WindowsException x) {
418            x.rethrowAsIOException(target);
419        }
420
421        // copy timestamps/DOS attributes
422        WindowsFileAttributeViews.Dos view =
423                WindowsFileAttributeViews.createDosView(target, false);
424        try {
425            view.setAttributes(sourceAttrs);
426        } catch (IOException x) {
427            // rollback
428            try {
429                RemoveDirectory(targetPath);
430            } catch (WindowsException ignore) { }
431            throw x;
432        }
433
434        // copy security attributes. If this fails it doesn't cause the move
435        // to fail.
436        try {
437            copySecurityAttributes(source, target, false);
438        } catch (IOException ignore) { }
439
440        // delete source
441        try {
442            RemoveDirectory(sourcePath);
443        } catch (WindowsException x) {
444            // rollback
445            try {
446                RemoveDirectory(targetPath);
447            } catch (WindowsException ignore) { }
448            // ERROR_ALREADY_EXISTS is returned when attempting to delete
449            // non-empty directory on SAMBA servers.
450            if (x.lastError() == ERROR_DIR_NOT_EMPTY ||
451                x.lastError() == ERROR_ALREADY_EXISTS)
452            {
453                throw new DirectoryNotEmptyException(
454                    target.getPathForExceptionMessage());
455            }
456            x.rethrowAsIOException(source);
457        }
458    }
459
460
461    private static String asWin32Path(WindowsPath path) throws IOException {
462        try {
463            return path.getPathForWin32Calls();
464        } catch (WindowsException x) {
465            x.rethrowAsIOException(path);
466            return null;
467        }
468    }
469
470    /**
471     * Copy DACL/owner/group from source to target
472     */
473    private static void copySecurityAttributes(WindowsPath source,
474                                               WindowsPath target,
475                                               boolean followLinks)
476        throws IOException
477    {
478        String path = WindowsLinkSupport.getFinalPath(source, followLinks);
479
480        // may need SeRestorePrivilege to set file owner
481        WindowsSecurity.Privilege priv =
482            WindowsSecurity.enablePrivilege("SeRestorePrivilege");
483        try {
484            int request = (DACL_SECURITY_INFORMATION |
485                OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION);
486            NativeBuffer buffer =
487                WindowsAclFileAttributeView.getFileSecurity(path, request);
488            try {
489                try {
490                    SetFileSecurity(target.getPathForWin32Calls(), request,
491                        buffer.address());
492                } catch (WindowsException x) {
493                    x.rethrowAsIOException(target);
494                }
495            } finally {
496                buffer.release();
497            }
498        } finally {
499            priv.drop();
500        }
501    }
502}
503