jdk/src/windows/classes/sun/nio/fs/WindowsFileCopy.java
changeset 2057 3acf8e5e2ca0
child 5506 202f599c92aa
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/windows/classes/sun/nio/fs/WindowsFileCopy.java	Sun Feb 15 12:25:54 2009 +0000
@@ -0,0 +1,519 @@
+/*
+ * Copyright 2008-2009 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.nio.fs;
+
+import java.nio.file.*;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import com.sun.nio.file.ExtendedCopyOption;
+
+import static sun.nio.fs.WindowsNativeDispatcher.*;
+import static sun.nio.fs.WindowsConstants.*;
+
+/**
+ * Utility methods for copying and moving files.
+ */
+
+class WindowsFileCopy {
+    private WindowsFileCopy() {
+    }
+
+    /**
+     * Copy file from source to target
+     */
+    static void copy(final WindowsPath source,
+                     final WindowsPath target,
+                     CopyOption... options)
+        throws IOException
+    {
+        // map options
+        boolean replaceExisting = false;
+        boolean copyAttributes = false;
+        boolean followLinks = true;
+        boolean interruptible = false;
+        for (CopyOption option: options) {
+            if (option == StandardCopyOption.REPLACE_EXISTING) {
+                replaceExisting = true;
+                continue;
+            }
+            if (option == LinkOption.NOFOLLOW_LINKS) {
+                followLinks = false;
+                continue;
+            }
+            if (option == StandardCopyOption.COPY_ATTRIBUTES) {
+                copyAttributes = true;
+                continue;
+            }
+            if (option == ExtendedCopyOption.INTERRUPTIBLE) {
+                interruptible = true;
+                continue;
+            }
+            if (option == null)
+                throw new NullPointerException();
+            throw new UnsupportedOperationException("Unsupported copy option");
+        }
+
+        // check permissions. If the source file is a symbolic link then
+        // later we must also check LinkPermission
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            source.checkRead();
+            target.checkWrite();
+        }
+
+        // get attributes of source file
+        // attempt to get attributes of target file
+        // if both files are the same there is nothing to do
+        // if target exists and !replace then throw exception
+
+        WindowsFileAttributes sourceAttrs = null;
+        WindowsFileAttributes targetAttrs = null;
+
+        long sourceHandle = 0L;
+        try {
+            sourceHandle = source.openForReadAttributeAccess(followLinks);
+        } catch (WindowsException x) {
+            x.rethrowAsIOException(source);
+        }
+        try {
+            // source attributes
+            try {
+                sourceAttrs = WindowsFileAttributes.readAttributes(sourceHandle);
+            } catch (WindowsException x) {
+                x.rethrowAsIOException(source);
+            }
+
+            // open target (don't follow links)
+            long targetHandle = 0L;
+            try {
+                targetHandle = target.openForReadAttributeAccess(false);
+                try {
+                    targetAttrs = WindowsFileAttributes.readAttributes(targetHandle);
+
+                    // if both files are the same then nothing to do
+                    if (WindowsFileAttributes.isSameFile(sourceAttrs, targetAttrs)) {
+                        return;
+                    }
+
+                    // can't replace file
+                    if (!replaceExisting) {
+                        throw new FileAlreadyExistsException(
+                            target.getPathForExceptionMessage());
+                    }
+
+                } finally {
+                    CloseHandle(targetHandle);
+                }
+            } catch (WindowsException x) {
+                // ignore
+            }
+
+        } finally {
+            CloseHandle(sourceHandle);
+        }
+
+        // if source file is a symbolic link then we must check for LinkPermission
+        if (sm != null && sourceAttrs.isSymbolicLink()) {
+            sm.checkPermission(new LinkPermission("symbolic"));
+        }
+
+        final String sourcePath = asWin32Path(source);
+        final String targetPath = asWin32Path(target);
+
+        // if target exists then delete it.
+        if (targetAttrs != null) {
+            try {
+                if (targetAttrs.isDirectory() || targetAttrs.isDirectoryLink()) {
+                    RemoveDirectory(targetPath);
+                } else {
+                    DeleteFile(targetPath);
+                }
+            } catch (WindowsException x) {
+                if (targetAttrs.isDirectory()) {
+                    // ERROR_ALREADY_EXISTS is returned when attempting to delete
+                    // non-empty directory on SAMBA servers.
+                    if (x.lastError() == ERROR_DIR_NOT_EMPTY ||
+                        x.lastError() == ERROR_ALREADY_EXISTS)
+                    {
+                        throw new FileAlreadyExistsException(
+                            target.getPathForExceptionMessage());
+                    }
+                }
+                x.rethrowAsIOException(target);
+            }
+        }
+
+        // Use CopyFileEx if the file is not a directory or junction
+        if (!sourceAttrs.isDirectory() && !sourceAttrs.isDirectoryLink()) {
+            final int flags =
+                (source.getFileSystem().supportsLinks() && !followLinks) ?
+                COPY_FILE_COPY_SYMLINK : 0;
+
+            if (interruptible) {
+                // interruptible copy
+                Cancellable copyTask = new Cancellable() {
+                    @Override
+                    public int cancelValue() {
+                        return 1;  // TRUE
+                    }
+                    @Override
+                    public void implRun() throws IOException {
+                        try {
+                            CopyFileEx(sourcePath, targetPath, flags,
+                                       addressToPollForCancel());
+                        } catch (WindowsException x) {
+                            x.rethrowAsIOException(source, target);
+                        }
+                    }
+                };
+                try {
+                    Cancellable.runInterruptibly(copyTask);
+                } catch (ExecutionException e) {
+                    Throwable t = e.getCause();
+                    if (t instanceof IOException)
+                        throw (IOException)t;
+                    throw new IOException(t);
+                }
+            } else {
+                // non-interruptible copy
+                try {
+                    CopyFileEx(sourcePath, targetPath, flags, 0L);
+                } catch (WindowsException x) {
+                    x.rethrowAsIOException(source, target);
+                }
+            }
+            if (copyAttributes) {
+                // CopyFileEx does not copy security attributes
+                try {
+                    copySecurityAttributes(source, target, followLinks);
+                } catch (IOException x) {
+                    // ignore
+                }
+            }
+            return;
+        }
+
+        // copy directory or directory junction
+        try {
+            if (sourceAttrs.isDirectory()) {
+                CreateDirectory(targetPath, 0L);
+            } else {
+                String linkTarget = WindowsLinkSupport.readLink(source);
+                int flags = SYMBOLIC_LINK_FLAG_DIRECTORY;
+                CreateSymbolicLink(targetPath,
+                                   addPrefixIfNeeded(linkTarget),
+                                   flags);
+            }
+        } catch (WindowsException x) {
+            x.rethrowAsIOException(target);
+        }
+        if (copyAttributes) {
+            // copy DOS/timestamps attributes
+            WindowsFileAttributeViews.Dos view =
+                WindowsFileAttributeViews.createDosView(target, false);
+            try {
+                view.setAttributes(sourceAttrs);
+            } catch (IOException x) {
+                if (sourceAttrs.isDirectory()) {
+                    try {
+                        RemoveDirectory(targetPath);
+                    } catch (WindowsException ignore) { }
+                }
+            }
+
+            // copy security attributes. If this fail it doesn't cause the move
+            // to fail.
+            try {
+                copySecurityAttributes(source, target, followLinks);
+            } catch (IOException ignore) { }
+        }
+    }
+
+    /**
+     * Move file from source to target
+     */
+    static void move(WindowsPath source, WindowsPath target, CopyOption... options)
+        throws IOException
+    {
+        // map options
+        boolean atomicMove = false;
+        boolean replaceExisting = false;
+        for (CopyOption option: options) {
+            if (option == StandardCopyOption.ATOMIC_MOVE) {
+                atomicMove = true;
+                continue;
+            }
+            if (option == StandardCopyOption.REPLACE_EXISTING) {
+                replaceExisting = true;
+                continue;
+            }
+            if (option == LinkOption.NOFOLLOW_LINKS) {
+                // ignore
+                continue;
+            }
+            if (option == null) throw new NullPointerException();
+            throw new UnsupportedOperationException("Unsupported copy option");
+        }
+
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            source.checkWrite();
+            target.checkWrite();
+        }
+
+        final String sourcePath = asWin32Path(source);
+        final String targetPath = asWin32Path(target);
+
+        // atomic case
+        if (atomicMove) {
+            try {
+                MoveFileEx(sourcePath, targetPath, MOVEFILE_REPLACE_EXISTING);
+            } catch (WindowsException x) {
+                if (x.lastError() == ERROR_NOT_SAME_DEVICE) {
+                    throw new AtomicMoveNotSupportedException(
+                        source.getPathForExceptionMessage(),
+                        target.getPathForExceptionMessage(),
+                        x.errorString());
+                }
+                x.rethrowAsIOException(source, target);
+            }
+            return;
+        }
+
+        // get attributes of source file
+        // attempt to get attributes of target file
+        // if both files are the same there is nothing to do
+        // if target exists and !replace then throw exception
+
+        WindowsFileAttributes sourceAttrs = null;
+        WindowsFileAttributes targetAttrs = null;
+
+        long sourceHandle = 0L;
+        try {
+            sourceHandle = source.openForReadAttributeAccess(false);
+        } catch (WindowsException x) {
+            x.rethrowAsIOException(source);
+        }
+        try {
+            // source attributes
+            try {
+                sourceAttrs = WindowsFileAttributes.readAttributes(sourceHandle);
+            } catch (WindowsException x) {
+                x.rethrowAsIOException(source);
+            }
+
+            // open target (don't follow links)
+            long targetHandle = 0L;
+            try {
+                targetHandle = target.openForReadAttributeAccess(false);
+                try {
+                    targetAttrs = WindowsFileAttributes.readAttributes(targetHandle);
+
+                    // if both files are the same then nothing to do
+                    if (WindowsFileAttributes.isSameFile(sourceAttrs, targetAttrs)) {
+                        return;
+                    }
+
+                    // can't replace file
+                    if (!replaceExisting) {
+                        throw new FileAlreadyExistsException(
+                            target.getPathForExceptionMessage());
+                    }
+
+                } finally {
+                    CloseHandle(targetHandle);
+                }
+            } catch (WindowsException x) {
+                // ignore
+            }
+
+        } finally {
+            CloseHandle(sourceHandle);
+        }
+
+        // if target exists then delete it.
+        if (targetAttrs != null) {
+            try {
+                if (targetAttrs.isDirectory() || targetAttrs.isDirectoryLink()) {
+                    RemoveDirectory(targetPath);
+                } else {
+                    DeleteFile(targetPath);
+                }
+            } catch (WindowsException x) {
+                if (targetAttrs.isDirectory()) {
+                    // ERROR_ALREADY_EXISTS is returned when attempting to delete
+                    // non-empty directory on SAMBA servers.
+                    if (x.lastError() == ERROR_DIR_NOT_EMPTY ||
+                        x.lastError() == ERROR_ALREADY_EXISTS)
+                    {
+                        throw new FileAlreadyExistsException(
+                            target.getPathForExceptionMessage());
+                    }
+                }
+                x.rethrowAsIOException(target);
+            }
+        }
+
+        // first try MoveFileEx (no options). If target is on same volume then
+        // all attributes (including security attributes) are preserved.
+        try {
+            MoveFileEx(sourcePath, targetPath, 0);
+            return;
+        } catch (WindowsException x) {
+            if (x.lastError() != ERROR_NOT_SAME_DEVICE)
+                x.rethrowAsIOException(source, target);
+        }
+
+        // target is on different volume so use MoveFileEx with copy option
+        if (!sourceAttrs.isDirectory() && !sourceAttrs.isDirectoryLink()) {
+            try {
+                MoveFileEx(sourcePath, targetPath, MOVEFILE_COPY_ALLOWED);
+            } catch (WindowsException x) {
+                x.rethrowAsIOException(source, target);
+            }
+            // MoveFileEx does not copy security attributes when moving
+            // across volumes.
+            try {
+                copySecurityAttributes(source, target, false);
+            } catch (IOException x) {
+                // ignore
+            }
+            return;
+        }
+
+        // moving directory or directory-link to another file system
+        assert sourceAttrs.isDirectory() || sourceAttrs.isDirectoryLink();
+
+        // create new directory or directory junction
+        try {
+            if (sourceAttrs.isDirectory()) {
+                CreateDirectory(targetPath, 0L);
+            } else {
+                String linkTarget = WindowsLinkSupport.readLink(source);
+                CreateSymbolicLink(targetPath,
+                                   addPrefixIfNeeded(linkTarget),
+                                   SYMBOLIC_LINK_FLAG_DIRECTORY);
+            }
+        } catch (WindowsException x) {
+            x.rethrowAsIOException(target);
+        }
+
+        // copy timestamps/DOS attributes
+        WindowsFileAttributeViews.Dos view =
+                WindowsFileAttributeViews.createDosView(target, false);
+        try {
+            view.setAttributes(sourceAttrs);
+        } catch (IOException x) {
+            // rollback
+            try {
+                RemoveDirectory(targetPath);
+            } catch (WindowsException ignore) { }
+            throw x;
+        }
+
+        // copy security attributes. If this fails it doesn't cause the move
+        // to fail.
+        try {
+            copySecurityAttributes(source, target, false);
+        } catch (IOException ignore) { }
+
+        // delete source
+        try {
+            RemoveDirectory(sourcePath);
+        } catch (WindowsException x) {
+            // rollback
+            try {
+                RemoveDirectory(targetPath);
+            } catch (WindowsException ignore) { }
+            // ERROR_ALREADY_EXISTS is returned when attempting to delete
+            // non-empty directory on SAMBA servers.
+            if (x.lastError() == ERROR_DIR_NOT_EMPTY ||
+                x.lastError() == ERROR_ALREADY_EXISTS)
+            {
+                throw new DirectoryNotEmptyException(
+                    target.getPathForExceptionMessage());
+            }
+            x.rethrowAsIOException(source);
+        }
+    }
+
+
+    private static String asWin32Path(WindowsPath path) throws IOException {
+        try {
+            return path.getPathForWin32Calls();
+        } catch (WindowsException x) {
+            x.rethrowAsIOException(path);
+            return null;
+        }
+    }
+
+    /**
+     * Copy DACL/owner/group from source to target
+     */
+    private static void copySecurityAttributes(WindowsPath source,
+                                               WindowsPath target,
+                                               boolean followLinks)
+        throws IOException
+    {
+        String path = WindowsLinkSupport.getFinalPath(source, followLinks);
+
+        // may need SeRestorePrivilege to set file owner
+        WindowsSecurity.Privilege priv =
+            WindowsSecurity.enablePrivilege("SeRestorePrivilege");
+        try {
+            int request = (DACL_SECURITY_INFORMATION |
+                OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION);
+            NativeBuffer buffer =
+                WindowsAclFileAttributeView.getFileSecurity(path, request);
+            try {
+                try {
+                    SetFileSecurity(target.getPathForWin32Calls(), request,
+                        buffer.address());
+                } catch (WindowsException x) {
+                    x.rethrowAsIOException(target);
+                }
+            } finally {
+                buffer.release();
+            }
+        } finally {
+            priv.drop();
+        }
+    }
+
+    /**
+     * Add long path prefix to path if required
+     */
+    private static String addPrefixIfNeeded(String path) {
+        if (path.length() > 248) {
+            if (path.startsWith("\\\\")) {
+                path = "\\\\?\\UNC" + path.substring(1, path.length());
+            } else {
+                path = "\\\\?\\" + path;
+            }
+        }
+        return path;
+    }
+}