diff --git a/src/java.base/linux/classes/sun/nio/fs/LinuxFileSystem.java b/src/java.base/linux/classes/sun/nio/fs/LinuxFileSystem.java index a3153b31329f3..526e84b5de4f8 100644 --- a/src/java.base/linux/classes/sun/nio/fs/LinuxFileSystem.java +++ b/src/java.base/linux/classes/sun/nio/fs/LinuxFileSystem.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2022, 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 @@ -25,10 +25,16 @@ package sun.nio.fs; -import java.nio.file.*; +import java.nio.file.FileStore; +import java.nio.file.WatchService; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import static sun.nio.fs.LinuxNativeDispatcher.*; +import static sun.nio.fs.UnixConstants.*; /** * Linux implementation of FileSystem @@ -121,10 +127,56 @@ List getMountEntries() { return getMountEntries("/etc/mtab"); } - - @Override FileStore getFileStore(UnixMountEntry entry) throws IOException { return new LinuxFileStore(this, entry); } + + // --- file copying --- + + @Override + void bufferedCopy(int dst, int src, long address, + int size, long addressToPollForCancel) + throws UnixException + { + int advice = POSIX_FADV_SEQUENTIAL | // sequential data access + POSIX_FADV_NOREUSE | // will access only once + POSIX_FADV_WILLNEED; // will access in near future + posix_fadvise(src, 0, 0, advice); + + super.bufferedCopy(dst, src, address, size, addressToPollForCancel); + } + + @Override + int directCopy(int dst, int src, long addressToPollForCancel) + throws UnixException + { + int advice = POSIX_FADV_SEQUENTIAL | // sequential data access + POSIX_FADV_NOREUSE | // will access only once + POSIX_FADV_WILLNEED; // will access in near future + posix_fadvise(src, 0, 0, advice); + + return directCopy0(dst, src, addressToPollForCancel); + } + + // -- native methods -- + + /** + * Copies data between file descriptors {@code src} and {@code dst} using + * a platform-specific function or system call possibly having kernel + * support. + * + * @param dst destination file descriptor + * @param src source file descriptor + * @param addressToPollForCancel address to check for cancellation + * (a non-zero value written to this address indicates cancel) + * + * @return 0 on success, UNAVAILABLE if the platform function would block, + * UNSUPPORTED_CASE if the call does not work with the given + * parameters, or UNSUPPORTED if direct copying is not supported + * on this platform + */ + private static native int directCopy0(int dst, int src, + long addressToPollForCancel) + throws UnixException; } diff --git a/src/java.base/linux/classes/sun/nio/fs/LinuxNativeDispatcher.java b/src/java.base/linux/classes/sun/nio/fs/LinuxNativeDispatcher.java index e09dfecd353df..34917b80a48d9 100644 --- a/src/java.base/linux/classes/sun/nio/fs/LinuxNativeDispatcher.java +++ b/src/java.base/linux/classes/sun/nio/fs/LinuxNativeDispatcher.java @@ -62,6 +62,12 @@ static native int getmntent0(long fp, UnixMountEntry entry, long buffer, int buf */ static native void endmntent(long stream) throws UnixException; + /** + * int posix_fadvise(int fd, off_t offset, off_t len, int advice); + */ + static native int posix_fadvise(int fd, long offset, long len, int advice) + throws UnixException; + // initialize private static native void init(); diff --git a/src/java.base/linux/native/libnio/fs/LinuxFileSystem.c b/src/java.base/linux/native/libnio/fs/LinuxFileSystem.c new file mode 100644 index 0000000000000..8771b8814318f --- /dev/null +++ b/src/java.base/linux/native/libnio/fs/LinuxFileSystem.c @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2022, 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. + */ + +#include "jni.h" +#include "jni_util.h" +#include "jlong.h" + +#include "nio.h" + +#include +#include +#include + +#include +#include + +#include "sun_nio_fs_LinuxFileSystem.h" + +#define RESTARTABLE(_cmd, _result) do { \ + do { \ + _result = _cmd; \ + } while((_result == -1) && (errno == EINTR)); \ +} while(0) + +static void throwUnixException(JNIEnv* env, int errnum) { + jobject x = JNU_NewObjectByName(env, "sun/nio/fs/UnixException", + "(I)V", errnum); + if (x != NULL) { + (*env)->Throw(env, x); + } +} + +// Copy all bytes from src to dst, within the kernel if possible, +// and return zero, otherwise return the appropriate status code. +// +// Return value +// 0 on success +// IOS_UNAVAILABLE if the platform function would block +// IOS_UNSUPPORTED_CASE if the call does not work with the given parameters +// IOS_UNSUPPORTED if direct copying is not supported on this platform +// IOS_THROWN if a Java exception is thrown +// +JNIEXPORT jint JNICALL +Java_sun_nio_fs_LinuxFileSystem_directCopy0 + (JNIEnv* env, jclass this, jint dst, jint src, jlong cancelAddress) +{ + volatile jint* cancel = (jint*)jlong_to_ptr(cancelAddress); + + // Transfer within the kernel + const size_t count = cancel != NULL ? + 1048576 : // 1 MB to give cancellation a chance + 0x7ffff000; // maximum number of bytes that sendfile() can transfer + ssize_t bytes_sent; + + do { + RESTARTABLE(sendfile64(dst, src, NULL, count), bytes_sent); + if (bytes_sent < 0) { + if (errno == EAGAIN) + return IOS_UNAVAILABLE; + if (errno == EINVAL || errno == ENOSYS) + return IOS_UNSUPPORTED_CASE; + throwUnixException(env, errno); + return IOS_THROWN; + } + if (cancel != NULL && *cancel != 0) { + throwUnixException(env, ECANCELED); + return IOS_THROWN; + } + } while (bytes_sent > 0); + + return 0; +} diff --git a/src/java.base/linux/native/libnio/fs/LinuxNativeDispatcher.c b/src/java.base/linux/native/libnio/fs/LinuxNativeDispatcher.c index 20014564172a8..cfdd75192408a 100644 --- a/src/java.base/linux/native/libnio/fs/LinuxNativeDispatcher.c +++ b/src/java.base/linux/native/libnio/fs/LinuxNativeDispatcher.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2022, 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 @@ -33,6 +33,7 @@ #include #include #include +#include #include "sun_nio_fs_LinuxNativeDispatcher.h" @@ -139,6 +140,13 @@ JNIEXPORT void JNICALL Java_sun_nio_fs_LinuxNativeDispatcher_endmntent(JNIEnv* env, jclass this, jlong stream) { FILE* fp = jlong_to_ptr(stream); - /* FIXME - man page doesn't explain how errors are returned */ + // The endmntent() function always returns 1. endmntent(fp); } + +JNIEXPORT jint JNICALL +Java_sun_nio_fs_LinuxNativeDispatcher_posix_1fadvise(JNIEnv* env, jclass this, + jint fd, jlong offset, jlong len, jint advice) +{ + return posix_fadvise64((int)fd, (off64_t)offset, (off64_t)len, (int)advice); +} diff --git a/src/java.base/macosx/classes/sun/nio/fs/BsdFileSystem.java b/src/java.base/macosx/classes/sun/nio/fs/BsdFileSystem.java index 359da72f7f883..3249fe2f70abc 100644 --- a/src/java.base/macosx/classes/sun/nio/fs/BsdFileSystem.java +++ b/src/java.base/macosx/classes/sun/nio/fs/BsdFileSystem.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2022, 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 @@ -25,10 +25,14 @@ package sun.nio.fs; -import java.nio.file.*; import java.io.IOException; -import java.util.*; +import java.nio.file.FileStore; +import java.nio.file.WatchService; import java.security.AccessController; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; import sun.security.action.GetPropertyAction; /** @@ -104,4 +108,19 @@ Iterable getMountEntries() { FileStore getFileStore(UnixMountEntry entry) throws IOException { return new BsdFileStore(this, entry); } + + // --- file copying --- + + @Override + int directCopy(int dst, int src, long addressToPollForCancel) + throws UnixException + { + return directCopy0(dst, src, addressToPollForCancel); + } + + // -- native methods -- + + private static native int directCopy0(int dst, int src, + long addressToPollForCancel) + throws UnixException; } diff --git a/src/java.base/unix/native/libnio/fs/UnixCopyFile.c b/src/java.base/macosx/native/libnio/fs/BsdFileSystem.c similarity index 57% rename from src/java.base/unix/native/libnio/fs/UnixCopyFile.c rename to src/java.base/macosx/native/libnio/fs/BsdFileSystem.c index dca333cc00622..b3ae3f6a72bff 100644 --- a/src/java.base/unix/native/libnio/fs/UnixCopyFile.c +++ b/src/java.base/macosx/native/libnio/fs/BsdFileSystem.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -33,13 +33,8 @@ #include #include -#if defined(__linux__) -#include -#include -#elif defined(_ALLBSD_SOURCE) #include -#endif -#include "sun_nio_fs_UnixCopyFile.h" +#include "sun_nio_fs_BsdFileSystem.h" #define RESTARTABLE(_cmd, _result) do { \ do { \ @@ -55,7 +50,6 @@ static void throwUnixException(JNIEnv* env, int errnum) { } } -#if defined(_ALLBSD_SOURCE) int fcopyfile_callback(int what, int stage, copyfile_state_t state, const char* src, const char* dst, void* cancel) { @@ -70,54 +64,6 @@ int fcopyfile_callback(int what, int stage, copyfile_state_t state, } return COPYFILE_CONTINUE; } -#endif - -// Copy via an intermediate temporary direct buffer -JNIEXPORT void JNICALL -Java_sun_nio_fs_UnixCopyFile_bufferedCopy0 - (JNIEnv* env, jclass this, jint dst, jint src, jlong address, - jint transferSize, jlong cancelAddress) -{ - volatile jint* cancel = (jint*)jlong_to_ptr(cancelAddress); - - char* buf = (char*)jlong_to_ptr(address); - -#if defined(__linux__) - int advice = POSIX_FADV_SEQUENTIAL | // sequential data access - POSIX_FADV_NOREUSE | // will access only once - POSIX_FADV_WILLNEED; // will access in near future - - // ignore the return value hence any failure - posix_fadvise(src, 0, 0, advice); -#endif - - for (;;) { - ssize_t n, pos, len; - RESTARTABLE(read((int)src, buf, transferSize), n); - if (n <= 0) { - if (n < 0) - throwUnixException(env, errno); - return; - } - if (cancel != NULL && *cancel != 0) { - throwUnixException(env, ECANCELED); - return; - } - pos = 0; - len = n; - do { - char* bufp = buf; - bufp += pos; - RESTARTABLE(write((int)dst, bufp, len), n); - if (n == -1) { - throwUnixException(env, errno); - return; - } - pos += n; - len -= n; - } while (len > 0); - } -} // Copy all bytes from src to dst, within the kernel if possible (Linux), // and return zero, otherwise return the appropriate status code. @@ -130,35 +76,11 @@ Java_sun_nio_fs_UnixCopyFile_bufferedCopy0 // IOS_THROWN if a Java exception is thrown // JNIEXPORT jint JNICALL -Java_sun_nio_fs_UnixCopyFile_directCopy0 +Java_sun_nio_fs_BsdFileSystem_directCopy0 (JNIEnv* env, jclass this, jint dst, jint src, jlong cancelAddress) { volatile jint* cancel = (jint*)jlong_to_ptr(cancelAddress); -#if defined(__linux__) - // Transfer within the kernel - const size_t count = cancel != NULL ? - 1048576 : // 1 MB to give cancellation a chance - 0x7ffff000; // maximum number of bytes that sendfile() can transfer - ssize_t bytes_sent; - do { - RESTARTABLE(sendfile64(dst, src, NULL, count), bytes_sent); - if (bytes_sent < 0) { - if (errno == EAGAIN) - return IOS_UNAVAILABLE; - if (errno == EINVAL || errno == ENOSYS) - return IOS_UNSUPPORTED_CASE; - throwUnixException(env, errno); - return IOS_THROWN; - } - if (cancel != NULL && *cancel != 0) { - throwUnixException(env, ECANCELED); - return IOS_THROWN; - } - } while (bytes_sent > 0); - - return 0; -#elif defined(_ALLBSD_SOURCE) copyfile_state_t state; if (cancel != NULL) { state = copyfile_state_alloc(); @@ -178,7 +100,4 @@ Java_sun_nio_fs_UnixCopyFile_directCopy0 copyfile_state_free(state); return 0; -#else - return IOS_UNSUPPORTED; -#endif } diff --git a/src/java.base/unix/classes/sun/nio/fs/UnixConstants.java.template b/src/java.base/unix/classes/sun/nio/fs/UnixConstants.java.template index 7a80e88584b91..822393a077de4 100644 --- a/src/java.base/unix/classes/sun/nio/fs/UnixConstants.java.template +++ b/src/java.base/unix/classes/sun/nio/fs/UnixConstants.java.template @@ -143,8 +143,8 @@ class UnixConstants { static final int PREFIX_AT_REMOVEDIR = 00; #endif - // flags used with setattrlist #ifdef _ALLBSD_SOURCE + // flags used with setattrlist static final int PREFIX_ATTR_CMN_CRTIME = ATTR_CMN_CRTIME; static final int PREFIX_ATTR_CMN_MODTIME = ATTR_CMN_MODTIME; static final int PREFIX_ATTR_CMN_ACCTIME = ATTR_CMN_ACCTIME; @@ -156,4 +156,11 @@ class UnixConstants { static final int PREFIX_ATTR_CMN_ACCTIME = 00; static final int PREFIX_FSOPT_NOFOLLOW = 00; #endif + +#ifdef __linux__ + // advice flags used with posix_fadvise(2) + static final int PREFIX_POSIX_FADV_SEQUENTIAL = POSIX_FADV_SEQUENTIAL; + static final int PREFIX_POSIX_FADV_NOREUSE = POSIX_FADV_NOREUSE; + static final int PREFIX_POSIX_FADV_WILLNEED = POSIX_FADV_WILLNEED; +#endif } diff --git a/src/java.base/unix/classes/sun/nio/fs/UnixCopyFile.java b/src/java.base/unix/classes/sun/nio/fs/UnixCopyFile.java deleted file mode 100644 index a23b6ca797089..0000000000000 --- a/src/java.base/unix/classes/sun/nio/fs/UnixCopyFile.java +++ /dev/null @@ -1,743 +0,0 @@ -/* - * Copyright (c) 2008, 2022, 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.io.IOException; -import java.nio.ByteBuffer; -import java.nio.file.AtomicMoveNotSupportedException; -import java.nio.file.CopyOption; -import java.nio.file.DirectoryNotEmptyException; -import java.nio.file.FileAlreadyExistsException; -import java.nio.file.LinkOption; -import java.nio.file.LinkPermission; -import java.nio.file.StandardCopyOption; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import jdk.internal.misc.Blocker; -import sun.nio.ch.DirectBuffer; -import sun.nio.ch.IOStatus; -import sun.nio.ch.Util; -import static sun.nio.fs.UnixNativeDispatcher.*; -import static sun.nio.fs.UnixConstants.*; - -/** - * Unix implementation of Path#copyTo and Path#moveTo methods. - */ - -class UnixCopyFile { - // minimum size of a temporary direct buffer - private static final int MIN_BUFFER_SIZE = 16384; - - private UnixCopyFile() { } - - // The flags that control how a file is copied or moved - private static class Flags { - boolean replaceExisting; - boolean atomicMove; - boolean followLinks; - boolean interruptible; - - // the attributes to copy - boolean copyBasicAttributes; - boolean copyPosixAttributes; - boolean copyNonPosixAttributes; - - // flags that indicate if we should fail if attributes cannot be copied - boolean failIfUnableToCopyBasic; - boolean failIfUnableToCopyPosix; - boolean failIfUnableToCopyNonPosix; - - static Flags fromCopyOptions(CopyOption... options) { - Flags flags = new Flags(); - flags.followLinks = true; - for (CopyOption option: options) { - if (option == StandardCopyOption.REPLACE_EXISTING) { - flags.replaceExisting = true; - continue; - } - if (option == LinkOption.NOFOLLOW_LINKS) { - flags.followLinks = false; - continue; - } - if (option == StandardCopyOption.COPY_ATTRIBUTES) { - // copy all attributes but only fail if basic attributes - // cannot be copied - flags.copyBasicAttributes = true; - flags.copyPosixAttributes = true; - flags.copyNonPosixAttributes = true; - flags.failIfUnableToCopyBasic = true; - continue; - } - if (ExtendedOptions.INTERRUPTIBLE.matches(option)) { - flags.interruptible = true; - continue; - } - if (option == null) - throw new NullPointerException(); - throw new UnsupportedOperationException("Unsupported copy option"); - } - return flags; - } - - static Flags fromMoveOptions(CopyOption... options) { - Flags flags = new Flags(); - for (CopyOption option: options) { - if (option == StandardCopyOption.ATOMIC_MOVE) { - flags.atomicMove = true; - continue; - } - if (option == StandardCopyOption.REPLACE_EXISTING) { - flags.replaceExisting = true; - continue; - } - if (option == LinkOption.NOFOLLOW_LINKS) { - // ignore - continue; - } - if (option == null) - throw new NullPointerException(); - throw new UnsupportedOperationException("Unsupported copy option"); - } - - // a move requires that all attributes be copied but only fail if - // the basic attributes cannot be copied - flags.copyBasicAttributes = true; - flags.copyPosixAttributes = true; - flags.copyNonPosixAttributes = true; - flags.failIfUnableToCopyBasic = true; - return flags; - } - } - - // copy directory from source to target - private static void copyDirectory(UnixPath source, - UnixFileAttributes attrs, - UnixPath target, - Flags flags) - throws IOException - { - try { - mkdir(target, attrs.mode()); - } catch (UnixException x) { - x.rethrowAsIOException(target); - } - - // no attributes to copy - if (!flags.copyBasicAttributes && - !flags.copyPosixAttributes && - !flags.copyNonPosixAttributes) return; - - // open target directory if possible (this can fail when copying a - // directory for which we don't have read access). - int dfd = -1; - try { - dfd = open(target, O_RDONLY, 0); - } catch (UnixException x) { - // access to target directory required to copy named attributes - if (flags.copyNonPosixAttributes && flags.failIfUnableToCopyNonPosix) { - try { rmdir(target); } catch (UnixException ignore) { } - x.rethrowAsIOException(target); - } - } - - boolean done = false; - try { - // copy owner/group/permissions - if (flags.copyPosixAttributes){ - try { - if (dfd >= 0) { - fchown(dfd, attrs.uid(), attrs.gid()); - fchmod(dfd, attrs.mode()); - } else { - chown(target, attrs.uid(), attrs.gid()); - chmod(target, attrs.mode()); - } - } catch (UnixException x) { - // unable to set owner/group - if (flags.failIfUnableToCopyPosix) - x.rethrowAsIOException(target); - } - } - // copy other attributes - if (flags.copyNonPosixAttributes && (dfd >= 0)) { - int sfd = -1; - try { - sfd = open(source, O_RDONLY, 0); - } catch (UnixException x) { - if (flags.failIfUnableToCopyNonPosix) - x.rethrowAsIOException(source); - } - if (sfd >= 0) { - source.getFileSystem().copyNonPosixAttributes(sfd, dfd); - close(sfd, e -> null); - } - } - // copy time stamps last - if (flags.copyBasicAttributes) { - try { - if (dfd >= 0 && futimesSupported()) { - futimes(dfd, - attrs.lastAccessTime().to(TimeUnit.MICROSECONDS), - attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS)); - } else { - utimes(target, - attrs.lastAccessTime().to(TimeUnit.MICROSECONDS), - attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS)); - } - } catch (UnixException x) { - // unable to set times - if (flags.failIfUnableToCopyBasic) - x.rethrowAsIOException(target); - } - } - done = true; - } finally { - if (dfd >= 0) - close(dfd, e -> null); - if (!done) { - // rollback - try { rmdir(target); } catch (UnixException ignore) { } - } - } - } - - // calculate the least common multiple of two values; - // the parameters in general will be powers of two likely in the - // range [4096, 65536] so this algorithm is expected to converge - // when it is rarely called - private static long lcm(long x, long y) { - assert x > 0 && y > 0 : "Non-positive parameter"; - - long u = x; - long v = y; - - while (u != v) { - if (u < v) - u += x; - else // u > v - v += y; - } - - return u; - } - - // calculate temporary direct buffer size - private static int temporaryBufferSize(UnixPath source, UnixPath target) { - int bufferSize = MIN_BUFFER_SIZE; - try { - long bss = UnixFileStoreAttributes.get(source).blockSize(); - long bst = UnixFileStoreAttributes.get(target).blockSize(); - if (bss > 0 && bst > 0) { - bufferSize = (int)(bss == bst ? bss : lcm(bss, bst)); - } - if (bufferSize < MIN_BUFFER_SIZE) { - int factor = (MIN_BUFFER_SIZE + bufferSize - 1)/bufferSize; - bufferSize *= factor; - } - } catch (UnixException ignored) { - } - return bufferSize; - } - - // whether direct copying is supported on this platform - private static volatile boolean directCopyNotSupported; - - // copy regular file from source to target - private static void copyFile(UnixPath source, - UnixFileAttributes attrs, - UnixPath target, - Flags flags, - long addressToPollForCancel) - throws IOException - { - int fi = -1; - try { - fi = open(source, O_RDONLY, 0); - } catch (UnixException x) { - x.rethrowAsIOException(source); - } - - try { - // open new file - int fo = -1; - try { - fo = open(target, - (O_WRONLY | - O_CREAT | - O_EXCL), - attrs.mode()); - } catch (UnixException x) { - x.rethrowAsIOException(target); - } - - // set to true when file and attributes copied - boolean complete = false; - try { - boolean copied = false; - if (!directCopyNotSupported) { - // copy bytes to target using platform function - long comp = Blocker.begin(); - try { - int res = directCopy0(fo, fi, addressToPollForCancel); - if (res == 0) { - copied = true; - } else if (res == IOStatus.UNSUPPORTED) { - directCopyNotSupported = true; - } - } catch (UnixException x) { - x.rethrowAsIOException(source, target); - } finally { - Blocker.end(comp); - } - } - - if (!copied) { - // copy bytes to target via a temporary direct buffer - int bufferSize = temporaryBufferSize(source, target); - ByteBuffer buf = Util.getTemporaryDirectBuffer(bufferSize); - try { - long comp = Blocker.begin(); - try { - bufferedCopy0(fo, fi, ((DirectBuffer)buf).address(), - bufferSize, addressToPollForCancel); - } catch (UnixException x) { - x.rethrowAsIOException(source, target); - } finally { - Blocker.end(comp); - } - } finally { - Util.releaseTemporaryDirectBuffer(buf); - } - } - - // copy owner/permissions - if (flags.copyPosixAttributes) { - try { - fchown(fo, attrs.uid(), attrs.gid()); - fchmod(fo, attrs.mode()); - } catch (UnixException x) { - if (flags.failIfUnableToCopyPosix) - x.rethrowAsIOException(target); - } - } - // copy non POSIX attributes (depends on file system) - if (flags.copyNonPosixAttributes) { - source.getFileSystem().copyNonPosixAttributes(fi, fo); - } - // copy time attributes - if (flags.copyBasicAttributes) { - try { - if (futimesSupported()) { - futimes(fo, - attrs.lastAccessTime().to(TimeUnit.MICROSECONDS), - attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS)); - } else { - utimes(target, - attrs.lastAccessTime().to(TimeUnit.MICROSECONDS), - attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS)); - } - } catch (UnixException x) { - if (flags.failIfUnableToCopyBasic) - x.rethrowAsIOException(target); - } - } - complete = true; - } finally { - close(fo, e -> null); - - // copy of file or attributes failed so rollback - if (!complete) { - try { - unlink(target); - } catch (UnixException ignore) { } - } - } - } finally { - close(fi, e -> null); - } - } - - // copy symbolic link from source to target - private static void copyLink(UnixPath source, - UnixFileAttributes attrs, - UnixPath target, - Flags flags) - throws IOException - { - byte[] linktarget = null; - try { - linktarget = readlink(source); - } catch (UnixException x) { - x.rethrowAsIOException(source); - } - try { - symlink(linktarget, target); - - if (flags.copyPosixAttributes) { - try { - lchown(target, attrs.uid(), attrs.gid()); - } catch (UnixException x) { - // ignore since link attributes not required to be copied - } - } - } catch (UnixException x) { - x.rethrowAsIOException(target); - } - } - - // copy special file from source to target - private static void copySpecial(UnixPath source, - UnixFileAttributes attrs, - UnixPath target, - Flags flags) - throws IOException - { - try { - mknod(target, attrs.mode(), attrs.rdev()); - } catch (UnixException x) { - x.rethrowAsIOException(target); - } - boolean done = false; - try { - if (flags.copyPosixAttributes) { - try { - chown(target, attrs.uid(), attrs.gid()); - chmod(target, attrs.mode()); - } catch (UnixException x) { - if (flags.failIfUnableToCopyPosix) - x.rethrowAsIOException(target); - } - } - if (flags.copyBasicAttributes) { - try { - utimes(target, - attrs.lastAccessTime().to(TimeUnit.MICROSECONDS), - attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS)); - } catch (UnixException x) { - if (flags.failIfUnableToCopyBasic) - x.rethrowAsIOException(target); - } - } - done = true; - } finally { - if (!done) { - try { unlink(target); } catch (UnixException ignore) { } - } - } - } - - // throw a DirectoryNotEmpty exception if appropriate - static void ensureEmptyDir(UnixPath dir) throws IOException { - try { - long ptr = opendir(dir); - try (UnixDirectoryStream stream = - new UnixDirectoryStream(dir, ptr, e -> true)) { - if (stream.iterator().hasNext()) { - throw new DirectoryNotEmptyException( - dir.getPathForExceptionMessage()); - } - } - } catch (UnixException e) { - e.rethrowAsIOException(dir); - } - } - - // move file from source to target - static void move(UnixPath source, UnixPath target, CopyOption... options) - throws IOException - { - // permission check - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - source.checkWrite(); - target.checkWrite(); - } - - // translate options into flags - Flags flags = Flags.fromMoveOptions(options); - - // handle atomic rename case - if (flags.atomicMove) { - try { - rename(source, target); - } catch (UnixException x) { - if (x.errno() == EXDEV) { - throw new AtomicMoveNotSupportedException( - source.getPathForExceptionMessage(), - target.getPathForExceptionMessage(), - x.errorString()); - } - x.rethrowAsIOException(source, target); - } - return; - } - - // move using rename or copy+delete - UnixFileAttributes sourceAttrs = null; - UnixFileAttributes targetAttrs = null; - - // get attributes of source file (don't follow links) - try { - sourceAttrs = UnixFileAttributes.get(source, false); - } catch (UnixException x) { - x.rethrowAsIOException(source); - } - - // get attributes of target file (don't follow links) - try { - targetAttrs = UnixFileAttributes.get(target, false); - } catch (UnixException x) { - // ignore - } - boolean targetExists = (targetAttrs != null); - - // if the target exists: - // 1. check if source and target are the same file - // 2. throw exception if REPLACE_EXISTING option is not set - // 3. delete target if REPLACE_EXISTING option set - if (targetExists) { - if (sourceAttrs.isSameFile(targetAttrs)) - return; // nothing to do as files are identical - if (!flags.replaceExisting) { - throw new FileAlreadyExistsException( - target.getPathForExceptionMessage()); - } - - // attempt to delete target - try { - if (targetAttrs.isDirectory()) { - rmdir(target); - } else { - unlink(target); - } - } catch (UnixException x) { - // target is non-empty directory that can't be replaced. - if (targetAttrs.isDirectory() && - (x.errno() == EEXIST || x.errno() == ENOTEMPTY)) - { - throw new DirectoryNotEmptyException( - target.getPathForExceptionMessage()); - } - x.rethrowAsIOException(target); - } - } - - // first try rename - try { - rename(source, target); - return; - } catch (UnixException x) { - if (x.errno() != EXDEV && x.errno() != EISDIR) { - x.rethrowAsIOException(source, target); - } - } - - // copy source to target - if (sourceAttrs.isDirectory()) { - ensureEmptyDir(source); - copyDirectory(source, sourceAttrs, target, flags); - } else { - if (sourceAttrs.isSymbolicLink()) { - copyLink(source, sourceAttrs, target, flags); - } else { - if (sourceAttrs.isDevice()) { - copySpecial(source, sourceAttrs, target, flags); - } else { - copyFile(source, sourceAttrs, target, flags, 0L); - } - } - } - - // delete source - try { - if (sourceAttrs.isDirectory()) { - rmdir(source); - } else { - unlink(source); - } - } catch (UnixException x) { - // file was copied but unable to unlink the source file so attempt - // to remove the target and throw a reasonable exception - try { - if (sourceAttrs.isDirectory()) { - rmdir(target); - } else { - unlink(target); - } - } catch (UnixException ignore) { } - - if (sourceAttrs.isDirectory() && - (x.errno() == EEXIST || x.errno() == ENOTEMPTY)) - { - throw new DirectoryNotEmptyException( - source.getPathForExceptionMessage()); - } - x.rethrowAsIOException(source); - } - } - - // copy file from source to target - static void copy(final UnixPath source, - final UnixPath target, - CopyOption... options) throws IOException - { - // permission checks - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - source.checkRead(); - target.checkWrite(); - } - - // translate options into flags - final Flags flags = Flags.fromCopyOptions(options); - - UnixFileAttributes sourceAttrs = null; - UnixFileAttributes targetAttrs = null; - - // get attributes of source file - try { - sourceAttrs = UnixFileAttributes.get(source, flags.followLinks); - } catch (UnixException x) { - x.rethrowAsIOException(source); - } - - // if source file is symbolic link then we must check LinkPermission - if (sm != null && sourceAttrs.isSymbolicLink()) { - sm.checkPermission(new LinkPermission("symbolic")); - } - - // get attributes of target file (don't follow links) - try { - targetAttrs = UnixFileAttributes.get(target, false); - } catch (UnixException x) { - // ignore - } - boolean targetExists = (targetAttrs != null); - - // if the target exists: - // 1. check if source and target are the same file - // 2. throw exception if REPLACE_EXISTING option is not set - // 3. try to unlink the target - if (targetExists) { - if (sourceAttrs.isSameFile(targetAttrs)) - return; // nothing to do as files are identical - if (!flags.replaceExisting) - throw new FileAlreadyExistsException( - target.getPathForExceptionMessage()); - try { - if (targetAttrs.isDirectory()) { - rmdir(target); - } else { - unlink(target); - } - } catch (UnixException x) { - // target is non-empty directory that can't be replaced. - if (targetAttrs.isDirectory() && - (x.errno() == EEXIST || x.errno() == ENOTEMPTY)) - { - throw new DirectoryNotEmptyException( - target.getPathForExceptionMessage()); - } - x.rethrowAsIOException(target); - } - } - - // do the copy - if (sourceAttrs.isDirectory()) { - copyDirectory(source, sourceAttrs, target, flags); - return; - } - if (sourceAttrs.isSymbolicLink()) { - copyLink(source, sourceAttrs, target, flags); - return; - } - if (!flags.interruptible) { - // non-interruptible file copy - copyFile(source, sourceAttrs, target, flags, 0L); - return; - } - - // interruptible file copy - final UnixFileAttributes attrsToCopy = sourceAttrs; - Cancellable copyTask = new Cancellable() { - @Override public void implRun() throws IOException { - copyFile(source, attrsToCopy, target, flags, - addressToPollForCancel()); - } - }; - try { - Cancellable.runInterruptibly(copyTask); - } catch (ExecutionException e) { - Throwable t = e.getCause(); - if (t instanceof IOException) - throw (IOException)t; - throw new IOException(t); - } - } - - // -- native methods -- - - /** - * Copies data between file descriptors {@code src} and {@code dst} using - * a platform-specific function or system call possibly having kernel - * support. - * - * @param dst destination file descriptor - * @param src source file descriptor - * @param addressToPollForCancel address to check for cancellation - * (a non-zero value written to this address indicates cancel) - * - * @return 0 on success, UNAVAILABLE if the platform function would block, - * UNSUPPORTED_CASE if the call does not work with the given - * parameters, or UNSUPPORTED if direct copying is not supported - * on this platform - */ - private static native int directCopy0(int dst, int src, - long addressToPollForCancel) - throws UnixException; - - /** - * Copies data between file descriptors {@code src} and {@code dst} using - * an intermediate temporary direct buffer. - * - * @param dst destination file descriptor - * @param src source file descriptor - * @param address the address of the temporary direct buffer's array - * @param size the size of the temporary direct buffer's array - * @param addressToPollForCancel address to check for cancellation - * (a non-zero value written to this address indicates cancel) - */ - private static native void bufferedCopy0(int dst, int src, long address, - int size, long addressToPollForCancel) - throws UnixException; - - static { - jdk.internal.loader.BootLoader.loadLibrary("nio"); - } - -} diff --git a/src/java.base/unix/classes/sun/nio/fs/UnixFileSystem.java b/src/java.base/unix/classes/sun/nio/fs/UnixFileSystem.java index 0f5aec00c625f..6a930d469d9b5 100644 --- a/src/java.base/unix/classes/sun/nio/fs/UnixFileSystem.java +++ b/src/java.base/unix/classes/sun/nio/fs/UnixFileSystem.java @@ -25,13 +25,38 @@ package sun.nio.fs; -import java.nio.file.*; -import java.nio.file.attribute.*; +import java.nio.ByteBuffer; +import java.nio.file.AtomicMoveNotSupportedException; +import java.nio.file.CopyOption; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.FileStore; +import java.nio.file.FileSystem; +import java.nio.file.LinkOption; +import java.nio.file.LinkPermission; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.GroupPrincipal; +import java.nio.file.attribute.UserPrincipal; +import java.nio.file.attribute.UserPrincipalLookupService; import java.nio.file.spi.FileSystemProvider; import java.io.IOException; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; +import jdk.internal.misc.Blocker; +import sun.nio.ch.DirectBuffer; +import sun.nio.ch.IOStatus; import sun.security.action.GetPropertyAction; +import static sun.nio.fs.UnixConstants.*; +import static sun.nio.fs.UnixNativeDispatcher.*; /** * Base implementation of FileSystem for Unix-like implementations. @@ -40,6 +65,12 @@ abstract class UnixFileSystem extends FileSystem { + // minimum size of a temporary direct buffer + private static final int MIN_BUFFER_SIZE = 16384; + + // whether direct copying is supported on this platform + private static volatile boolean directCopyNotSupported; + private final UnixFileSystemProvider provider; private final byte[] defaultDirectory; private final boolean needToResolveAgainstDefaultDirectory; @@ -355,4 +386,697 @@ String normalizeNativePath(String path) { String normalizeJavaPath(String path) { return path; } + + // Unix implementation of Files#copy and Files#move methods. + + // calculate the least common multiple of two values; + // the parameters in general will be powers of two likely in the + // range [4096, 65536] so this algorithm is expected to converge + // when it is rarely called + private static long lcm(long x, long y) { + assert x > 0 && y > 0 : "Non-positive parameter"; + + long u = x; + long v = y; + + while (u != v) { + if (u < v) + u += x; + else // u > v + v += y; + } + + return u; + } + + // calculate temporary direct buffer size + private static int temporaryBufferSize(UnixPath source, UnixPath target) { + int bufferSize = MIN_BUFFER_SIZE; + try { + long bss = UnixFileStoreAttributes.get(source).blockSize(); + long bst = UnixFileStoreAttributes.get(target).blockSize(); + if (bss > 0 && bst > 0) { + bufferSize = (int)(bss == bst ? bss : lcm(bss, bst)); + } + if (bufferSize < MIN_BUFFER_SIZE) { + int factor = (MIN_BUFFER_SIZE + bufferSize - 1)/bufferSize; + bufferSize *= factor; + } + } catch (UnixException ignored) { + } + return bufferSize; + } + + // The flags that control how a file is copied or moved + private static class Flags { + boolean replaceExisting; + boolean atomicMove; + boolean followLinks; + boolean interruptible; + + // the attributes to copy + boolean copyBasicAttributes; + boolean copyPosixAttributes; + boolean copyNonPosixAttributes; + + // flags that indicate if we should fail if attributes cannot be copied + boolean failIfUnableToCopyBasic; + boolean failIfUnableToCopyPosix; + boolean failIfUnableToCopyNonPosix; + + static Flags fromCopyOptions(CopyOption... options) { + Flags flags = new Flags(); + flags.followLinks = true; + for (CopyOption option: options) { + if (option == StandardCopyOption.REPLACE_EXISTING) { + flags.replaceExisting = true; + continue; + } + if (option == LinkOption.NOFOLLOW_LINKS) { + flags.followLinks = false; + continue; + } + if (option == StandardCopyOption.COPY_ATTRIBUTES) { + // copy all attributes but only fail if basic attributes + // cannot be copied + flags.copyBasicAttributes = true; + flags.copyPosixAttributes = true; + flags.copyNonPosixAttributes = true; + flags.failIfUnableToCopyBasic = true; + continue; + } + if (ExtendedOptions.INTERRUPTIBLE.matches(option)) { + flags.interruptible = true; + continue; + } + if (option == null) + throw new NullPointerException(); + throw new UnsupportedOperationException("Unsupported copy option"); + } + return flags; + } + + static Flags fromMoveOptions(CopyOption... options) { + Flags flags = new Flags(); + for (CopyOption option: options) { + if (option == StandardCopyOption.ATOMIC_MOVE) { + flags.atomicMove = true; + continue; + } + if (option == StandardCopyOption.REPLACE_EXISTING) { + flags.replaceExisting = true; + continue; + } + if (option == LinkOption.NOFOLLOW_LINKS) { + // ignore + continue; + } + if (option == null) + throw new NullPointerException(); + throw new UnsupportedOperationException("Unsupported copy option"); + } + + // a move requires that all attributes be copied but only fail if + // the basic attributes cannot be copied + flags.copyBasicAttributes = true; + flags.copyPosixAttributes = true; + flags.copyNonPosixAttributes = true; + flags.failIfUnableToCopyBasic = true; + return flags; + } + } + + // copy directory from source to target + private void copyDirectory(UnixPath source, + UnixFileAttributes attrs, + UnixPath target, + Flags flags) + throws IOException + { + try { + mkdir(target, attrs.mode()); + } catch (UnixException x) { + x.rethrowAsIOException(target); + } + + // no attributes to copy + if (!flags.copyBasicAttributes && + !flags.copyPosixAttributes && + !flags.copyNonPosixAttributes) return; + + // open target directory if possible (this can fail when copying a + // directory for which we don't have read access). + int dfd = -1; + try { + dfd = open(target, O_RDONLY, 0); + } catch (UnixException x) { + // access to target directory required to copy named attributes + if (flags.copyNonPosixAttributes && flags.failIfUnableToCopyNonPosix) { + try { rmdir(target); } catch (UnixException ignore) { } + x.rethrowAsIOException(target); + } + } + + boolean done = false; + try { + // copy owner/group/permissions + if (flags.copyPosixAttributes){ + try { + if (dfd >= 0) { + fchown(dfd, attrs.uid(), attrs.gid()); + fchmod(dfd, attrs.mode()); + } else { + chown(target, attrs.uid(), attrs.gid()); + chmod(target, attrs.mode()); + } + } catch (UnixException x) { + // unable to set owner/group + if (flags.failIfUnableToCopyPosix) + x.rethrowAsIOException(target); + } + } + // copy other attributes + if (flags.copyNonPosixAttributes && (dfd >= 0)) { + int sfd = -1; + try { + sfd = open(source, O_RDONLY, 0); + } catch (UnixException x) { + if (flags.failIfUnableToCopyNonPosix) + x.rethrowAsIOException(source); + } + if (sfd >= 0) { + source.getFileSystem().copyNonPosixAttributes(sfd, dfd); + UnixNativeDispatcher.close(sfd, e -> null); + } + } + // copy time stamps last + if (flags.copyBasicAttributes) { + try { + if (dfd >= 0 && futimesSupported()) { + futimes(dfd, + attrs.lastAccessTime().to(TimeUnit.MICROSECONDS), + attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS)); + } else { + utimes(target, + attrs.lastAccessTime().to(TimeUnit.MICROSECONDS), + attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS)); + } + } catch (UnixException x) { + // unable to set times + if (flags.failIfUnableToCopyBasic) + x.rethrowAsIOException(target); + } + } + done = true; + } finally { + if (dfd >= 0) + UnixNativeDispatcher.close(dfd, e -> null); + if (!done) { + // rollback + try { rmdir(target); } catch (UnixException ignore) { } + } + } + } + + /** + * Copies data between file descriptors {@code src} and {@code dst} using + * a platform-specific function or system call possibly having kernel + * support. + * + * @param dst destination file descriptor + * @param src source file descriptor + * @param addressToPollForCancel address to check for cancellation + * (a non-zero value written to this address indicates cancel) + * + * @return 0 on success, IOStatus.UNAVAILABLE if the platform function + * would block, IOStatus.UNSUPPORTED_CASE if the call does not + * work with the given parameters, or IOStatus.UNSUPPORTED if + * direct copying is not supported on this platform + */ + int directCopy(int dst, int src, long addressToPollForCancel) + throws UnixException + { + return IOStatus.UNSUPPORTED; + } + + /** + * Copies data between file descriptors {@code src} and {@code dst} using + * an intermediate temporary direct buffer. + * + * @param dst destination file descriptor + * @param src source file descriptor + * @param address the address of the temporary direct buffer's array + * @param size the size of the temporary direct buffer's array + * @param addressToPollForCancel address to check for cancellation + * (a non-zero value written to this address indicates cancel) + */ + void bufferedCopy(int dst, int src, long address, + int size, long addressToPollForCancel) + throws UnixException + { + bufferedCopy0(dst, src, address, size, addressToPollForCancel); + } + + // copy regular file from source to target + void copyFile(UnixPath source, + UnixFileAttributes attrs, + UnixPath target, + Flags flags, + long addressToPollForCancel) + throws IOException + { + int fi = -1; + try { + fi = open(source, O_RDONLY, 0); + } catch (UnixException x) { + x.rethrowAsIOException(source); + } + + try { + // open new file + int fo = -1; + try { + fo = open(target, + (O_WRONLY | + O_CREAT | + O_EXCL), + attrs.mode()); + } catch (UnixException x) { + x.rethrowAsIOException(target); + } + + // set to true when file and attributes copied + boolean complete = false; + try { + boolean copied = false; + if (!directCopyNotSupported) { + // copy bytes to target using platform function + long comp = Blocker.begin(); + try { + int res = directCopy(fo, fi, addressToPollForCancel); + if (res == 0) { + copied = true; + } else if (res == IOStatus.UNSUPPORTED) { + directCopyNotSupported = true; + } + } catch (UnixException x) { + x.rethrowAsIOException(source, target); + } finally { + Blocker.end(comp); + } + } + + if (!copied) { + // copy bytes to target via a temporary direct buffer + int bufferSize = temporaryBufferSize(source, target); + ByteBuffer buf = + sun.nio.ch.Util.getTemporaryDirectBuffer(bufferSize); + try { + long comp = Blocker.begin(); + try { + bufferedCopy(fo, fi, ((DirectBuffer)buf).address(), + bufferSize, addressToPollForCancel); + } catch (UnixException x) { + x.rethrowAsIOException(source, target); + } finally { + Blocker.end(comp); + } + } finally { + sun.nio.ch.Util.releaseTemporaryDirectBuffer(buf); + } + } + + // copy owner/permissions + if (flags.copyPosixAttributes) { + try { + fchown(fo, attrs.uid(), attrs.gid()); + fchmod(fo, attrs.mode()); + } catch (UnixException x) { + if (flags.failIfUnableToCopyPosix) + x.rethrowAsIOException(target); + } + } + // copy non POSIX attributes (depends on file system) + if (flags.copyNonPosixAttributes) { + source.getFileSystem().copyNonPosixAttributes(fi, fo); + } + // copy time attributes + if (flags.copyBasicAttributes) { + try { + if (futimesSupported()) { + futimes(fo, + attrs.lastAccessTime().to(TimeUnit.MICROSECONDS), + attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS)); + } else { + utimes(target, + attrs.lastAccessTime().to(TimeUnit.MICROSECONDS), + attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS)); + } + } catch (UnixException x) { + if (flags.failIfUnableToCopyBasic) + x.rethrowAsIOException(target); + } + } + complete = true; + } finally { + UnixNativeDispatcher.close(fo, e -> null); + + // copy of file or attributes failed so rollback + if (!complete) { + try { + unlink(target); + } catch (UnixException ignore) { } + } + } + } finally { + UnixNativeDispatcher.close(fi, e -> null); + } + } + + // copy symbolic link from source to target + private void copyLink(UnixPath source, + UnixFileAttributes attrs, + UnixPath target, + Flags flags) + throws IOException + { + byte[] linktarget = null; + try { + linktarget = readlink(source); + } catch (UnixException x) { + x.rethrowAsIOException(source); + } + try { + symlink(linktarget, target); + + if (flags.copyPosixAttributes) { + try { + lchown(target, attrs.uid(), attrs.gid()); + } catch (UnixException x) { + // ignore since link attributes not required to be copied + } + } + } catch (UnixException x) { + x.rethrowAsIOException(target); + } + } + + // copy special file from source to target + private void copySpecial(UnixPath source, + UnixFileAttributes attrs, + UnixPath target, + Flags flags) + throws IOException + { + try { + mknod(target, attrs.mode(), attrs.rdev()); + } catch (UnixException x) { + x.rethrowAsIOException(target); + } + boolean done = false; + try { + if (flags.copyPosixAttributes) { + try { + chown(target, attrs.uid(), attrs.gid()); + chmod(target, attrs.mode()); + } catch (UnixException x) { + if (flags.failIfUnableToCopyPosix) + x.rethrowAsIOException(target); + } + } + if (flags.copyBasicAttributes) { + try { + utimes(target, + attrs.lastAccessTime().to(TimeUnit.MICROSECONDS), + attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS)); + } catch (UnixException x) { + if (flags.failIfUnableToCopyBasic) + x.rethrowAsIOException(target); + } + } + done = true; + } finally { + if (!done) { + try { unlink(target); } catch (UnixException ignore) { } + } + } + } + + // throw a DirectoryNotEmpty exception if appropriate + static void ensureEmptyDir(UnixPath dir) throws IOException { + try { + long ptr = opendir(dir); + try (UnixDirectoryStream stream = + new UnixDirectoryStream(dir, ptr, e -> true)) { + if (stream.iterator().hasNext()) { + throw new DirectoryNotEmptyException( + dir.getPathForExceptionMessage()); + } + } + } catch (UnixException e) { + e.rethrowAsIOException(dir); + } + } + + // move file from source to target + void move(UnixPath source, UnixPath target, CopyOption... options) + throws IOException + { + // permission check + @SuppressWarnings("removal") + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + source.checkWrite(); + target.checkWrite(); + } + + // translate options into flags + Flags flags = Flags.fromMoveOptions(options); + + // handle atomic rename case + if (flags.atomicMove) { + try { + rename(source, target); + } catch (UnixException x) { + if (x.errno() == EXDEV) { + throw new AtomicMoveNotSupportedException( + source.getPathForExceptionMessage(), + target.getPathForExceptionMessage(), + x.errorString()); + } + x.rethrowAsIOException(source, target); + } + return; + } + + // move using rename or copy+delete + UnixFileAttributes sourceAttrs = null; + UnixFileAttributes targetAttrs = null; + + // get attributes of source file (don't follow links) + try { + sourceAttrs = UnixFileAttributes.get(source, false); + } catch (UnixException x) { + x.rethrowAsIOException(source); + } + + // get attributes of target file (don't follow links) + try { + targetAttrs = UnixFileAttributes.get(target, false); + } catch (UnixException x) { + // ignore + } + boolean targetExists = (targetAttrs != null); + + // if the target exists: + // 1. check if source and target are the same file + // 2. throw exception if REPLACE_EXISTING option is not set + // 3. delete target if REPLACE_EXISTING option set + if (targetExists) { + if (sourceAttrs.isSameFile(targetAttrs)) + return; // nothing to do as files are identical + if (!flags.replaceExisting) { + throw new FileAlreadyExistsException( + target.getPathForExceptionMessage()); + } + + // attempt to delete target + try { + if (targetAttrs.isDirectory()) { + rmdir(target); + } else { + unlink(target); + } + } catch (UnixException x) { + // target is non-empty directory that can't be replaced. + if (targetAttrs.isDirectory() && + (x.errno() == EEXIST || x.errno() == ENOTEMPTY)) + { + throw new DirectoryNotEmptyException( + target.getPathForExceptionMessage()); + } + x.rethrowAsIOException(target); + } + } + + // first try rename + try { + rename(source, target); + return; + } catch (UnixException x) { + if (x.errno() != EXDEV && x.errno() != EISDIR) { + x.rethrowAsIOException(source, target); + } + } + + // copy source to target + if (sourceAttrs.isDirectory()) { + ensureEmptyDir(source); + copyDirectory(source, sourceAttrs, target, flags); + } else { + if (sourceAttrs.isSymbolicLink()) { + copyLink(source, sourceAttrs, target, flags); + } else { + if (sourceAttrs.isDevice()) { + copySpecial(source, sourceAttrs, target, flags); + } else { + copyFile(source, sourceAttrs, target, flags, 0L); + } + } + } + + // delete source + try { + if (sourceAttrs.isDirectory()) { + rmdir(source); + } else { + unlink(source); + } + } catch (UnixException x) { + // file was copied but unable to unlink the source file so attempt + // to remove the target and throw a reasonable exception + try { + if (sourceAttrs.isDirectory()) { + rmdir(target); + } else { + unlink(target); + } + } catch (UnixException ignore) { } + + if (sourceAttrs.isDirectory() && + (x.errno() == EEXIST || x.errno() == ENOTEMPTY)) + { + throw new DirectoryNotEmptyException( + source.getPathForExceptionMessage()); + } + x.rethrowAsIOException(source); + } + } + + // copy file from source to target + void copy(final UnixPath source, + final UnixPath target, + CopyOption... options) throws IOException + { + // permission checks + @SuppressWarnings("removal") + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + source.checkRead(); + target.checkWrite(); + } + + // translate options into flags + final Flags flags = Flags.fromCopyOptions(options); + + UnixFileAttributes sourceAttrs = null; + UnixFileAttributes targetAttrs = null; + + // get attributes of source file + try { + sourceAttrs = UnixFileAttributes.get(source, flags.followLinks); + } catch (UnixException x) { + x.rethrowAsIOException(source); + } + + // if source file is symbolic link then we must check LinkPermission + if (sm != null && sourceAttrs.isSymbolicLink()) { + sm.checkPermission(new LinkPermission("symbolic")); + } + + // get attributes of target file (don't follow links) + try { + targetAttrs = UnixFileAttributes.get(target, false); + } catch (UnixException x) { + // ignore + } + boolean targetExists = (targetAttrs != null); + + // if the target exists: + // 1. check if source and target are the same file + // 2. throw exception if REPLACE_EXISTING option is not set + // 3. try to unlink the target + if (targetExists) { + if (sourceAttrs.isSameFile(targetAttrs)) + return; // nothing to do as files are identical + if (!flags.replaceExisting) + throw new FileAlreadyExistsException( + target.getPathForExceptionMessage()); + try { + if (targetAttrs.isDirectory()) { + rmdir(target); + } else { + unlink(target); + } + } catch (UnixException x) { + // target is non-empty directory that can't be replaced. + if (targetAttrs.isDirectory() && + (x.errno() == EEXIST || x.errno() == ENOTEMPTY)) + { + throw new DirectoryNotEmptyException( + target.getPathForExceptionMessage()); + } + x.rethrowAsIOException(target); + } + } + + // do the copy + if (sourceAttrs.isDirectory()) { + copyDirectory(source, sourceAttrs, target, flags); + return; + } + if (sourceAttrs.isSymbolicLink()) { + copyLink(source, sourceAttrs, target, flags); + return; + } + if (!flags.interruptible) { + // non-interruptible file copy + copyFile(source, sourceAttrs, target, flags, 0L); + return; + } + + // interruptible file copy + final UnixFileAttributes attrsToCopy = sourceAttrs; + Cancellable copyTask = new Cancellable() { + @Override public void implRun() throws IOException { + copyFile(source, attrsToCopy, target, + flags, addressToPollForCancel()); + } + }; + try { + Cancellable.runInterruptibly(copyTask); + } catch (ExecutionException e) { + Throwable t = e.getCause(); + if (t instanceof IOException) + throw (IOException)t; + throw new IOException(t); + } + } + + + // -- native methods -- + + private static native void bufferedCopy0(int dst, int src, long address, + int size, long addressToPollForCancel) + throws UnixException; } diff --git a/src/java.base/unix/classes/sun/nio/fs/UnixFileSystemProvider.java b/src/java.base/unix/classes/sun/nio/fs/UnixFileSystemProvider.java index b8daf551c6916..9f5df28fcba8c 100644 --- a/src/java.base/unix/classes/sun/nio/fs/UnixFileSystemProvider.java +++ b/src/java.base/unix/classes/sun/nio/fs/UnixFileSystemProvider.java @@ -25,15 +25,38 @@ package sun.nio.fs; -import java.nio.file.*; -import java.nio.file.attribute.*; -import java.nio.file.spi.FileTypeDetector; -import java.nio.channels.*; -import java.net.URI; -import java.util.concurrent.ExecutorService; import java.io.IOException; import java.io.FilePermission; -import java.util.*; +import java.net.URI; +import java.nio.channels.AsynchronousFileChannel; +import java.nio.channels.FileChannel; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.AccessMode; +import java.nio.file.CopyOption; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.DirectoryStream; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.FileStore; +import java.nio.file.FileSystem; +import java.nio.file.FileSystemAlreadyExistsException; +import java.nio.file.LinkOption; +import java.nio.file.LinkPermission; +import java.nio.file.NotDirectoryException; +import java.nio.file.NotLinkException; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.ProviderMismatchException; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.FileAttributeView; +import java.nio.file.attribute.FileOwnerAttributeView; +import java.nio.file.attribute.PosixFileAttributes; +import java.nio.file.attribute.PosixFileAttributeView; +import java.nio.file.spi.FileTypeDetector; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutorService; import jdk.internal.util.StaticProperty; import sun.nio.ch.ThreadPool; @@ -274,18 +297,18 @@ boolean implDelete(Path obj, boolean failIfNotExists) throws IOException { public void copy(Path source, Path target, CopyOption... options) throws IOException { - UnixCopyFile.copy(UnixPath.toUnixPath(source), - UnixPath.toUnixPath(target), - options); + theFileSystem.copy(UnixPath.toUnixPath(source), + UnixPath.toUnixPath(target), + options); } @Override public void move(Path source, Path target, CopyOption... options) throws IOException { - UnixCopyFile.move(UnixPath.toUnixPath(source), - UnixPath.toUnixPath(target), - options); + theFileSystem.move(UnixPath.toUnixPath(source), + UnixPath.toUnixPath(target), + options); } @Override diff --git a/src/java.base/unix/native/libnio/fs/UnixFileSystem.c b/src/java.base/unix/native/libnio/fs/UnixFileSystem.c new file mode 100644 index 0000000000000..c362636824670 --- /dev/null +++ b/src/java.base/unix/native/libnio/fs/UnixFileSystem.c @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2008, 2022, 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. + */ + +#include "jni.h" +#include "jni_util.h" +#include "jlong.h" + +#include "nio.h" + +#include +#include +#include + +#include "sun_nio_fs_UnixFileSystem.h" + +#define RESTARTABLE(_cmd, _result) do { \ + do { \ + _result = _cmd; \ + } while((_result == -1) && (errno == EINTR)); \ +} while(0) + +static void throwUnixException(JNIEnv* env, int errnum) { + jobject x = JNU_NewObjectByName(env, "sun/nio/fs/UnixException", + "(I)V", errnum); + if (x != NULL) { + (*env)->Throw(env, x); + } +} + +// Copy via an intermediate temporary direct buffer +JNIEXPORT void JNICALL +Java_sun_nio_fs_UnixFileSystem_bufferedCopy0 + (JNIEnv* env, jclass this, jint dst, jint src, jlong address, + jint transferSize, jlong cancelAddress) +{ + volatile jint* cancel = (jint*)jlong_to_ptr(cancelAddress); + + char* buf = (char*)jlong_to_ptr(address); + + for (;;) { + ssize_t n, pos, len; + RESTARTABLE(read((int)src, buf, transferSize), n); + if (n <= 0) { + if (n < 0) + throwUnixException(env, errno); + return; + } + if (cancel != NULL && *cancel != 0) { + throwUnixException(env, ECANCELED); + return; + } + pos = 0; + len = n; + do { + char* bufp = buf; + bufp += pos; + RESTARTABLE(write((int)dst, bufp, len), n); + if (n == -1) { + throwUnixException(env, errno); + return; + } + pos += n; + len -= n; + } while (len > 0); + } +}