blob: d35752b8ee12b74e117c922be599980d0d1fe281 [file] [log] [blame]
/*
* 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 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;
}
}