diff -r 115e09b7a004 -r 3acf8e5e2ca0 jdk/src/windows/classes/sun/nio/fs/WindowsFileCopy.java --- /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; + } +}