/* * Copyright (c) 2008, 2009, Oracle and/or its affiliates. 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.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 DirectoryNotEmptyException( 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 DirectoryNotEmptyException( 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; } }