diff --git a/src/Common/src/Interop/Unix/System.Native/Interop.CopyFile.cs b/src/Common/src/Interop/Unix/System.Native/Interop.CopyFile.cs index fb97106635b4..f7035fceadd3 100644 --- a/src/Common/src/Interop/Unix/System.Native/Interop.CopyFile.cs +++ b/src/Common/src/Interop/Unix/System.Native/Interop.CopyFile.cs @@ -11,6 +11,6 @@ internal static partial class Interop internal static partial class Sys { [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_CopyFile", SetLastError = true)] - internal static extern int CopyFile(SafeFileHandle source, string srcPath, string destPath, int overwrite); + internal static extern int CopyFile(SafeFileHandle source, SafeFileHandle destination); } } diff --git a/src/Native/Unix/Common/pal_config.h.in b/src/Native/Unix/Common/pal_config.h.in index 3665b6a6eedc..033e39048947 100644 --- a/src/Native/Unix/Common/pal_config.h.in +++ b/src/Native/Unix/Common/pal_config.h.in @@ -46,7 +46,7 @@ #cmakedefine01 HAVE_KQUEUE #cmakedefine01 HAVE_SENDFILE_4 #cmakedefine01 HAVE_SENDFILE_6 -#cmakedefine01 HAVE_CLONEFILE +#cmakedefine01 HAVE_FCOPYFILE #cmakedefine01 HAVE_GETNAMEINFO_SIGNED_FLAGS #cmakedefine01 HAVE_GETPEEREID #cmakedefine01 HAVE_SUPPORT_FOR_DUAL_MODE_IPV4_PACKET_INFO diff --git a/src/Native/Unix/System.Native/pal_io.c b/src/Native/Unix/System.Native/pal_io.c index 1406ee72c1c2..ddd56b91c93d 100644 --- a/src/Native/Unix/System.Native/pal_io.c +++ b/src/Native/Unix/System.Native/pal_io.c @@ -28,11 +28,9 @@ #include #include #include -#if HAVE_CLONEFILE -#include -#include -#endif -#if HAVE_SENDFILE_4 +#if HAVE_FCOPYFILE +#include +#elif HAVE_SENDFILE_4 #include #endif #if HAVE_INOTIFY @@ -1195,6 +1193,7 @@ int32_t SystemNative_Write(intptr_t fd, const void* buffer, int32_t bufferSize) return (int32_t)count; } +#if !HAVE_FCOPYFILE // Read all data from inFd and write it to outFd static int32_t CopyFile_ReadWrite(int inFd, int outFd) { @@ -1247,15 +1246,25 @@ static int32_t CopyFile_ReadWrite(int inFd, int outFd) free(buffer); return 0; } +#endif // !HAVE_FCOPYFILE -int32_t SystemNative_CopyFile(intptr_t sourceFd, const char* srcPath, const char* destPath, int32_t overwrite) +int32_t SystemNative_CopyFile(intptr_t sourceFd, intptr_t destinationFd) { int inFd = ToFileDescriptor(sourceFd); - int outFd; + int outFd = ToFileDescriptor(destinationFd); + +#if HAVE_FCOPYFILE + // If fcopyfile is available (OS X), try to use it, as the whole copy + // can be performed in the kernel, without lots of unnecessary copying. + // Copy data and metadata. + return fcopyfile(inFd, outFd, NULL, COPYFILE_ALL) == 0 ? 0 : -1; +#else + // Get the stats on the source file. int ret; - int tmpErrno; - int openFlags; struct stat_ sourceStat; + bool copied = false; + + // First, stat the source file. while ((ret = fstat_(inFd, &sourceStat)) < 0 && errno == EINTR); if (ret != 0) { @@ -1275,74 +1284,9 @@ int32_t SystemNative_CopyFile(intptr_t sourceFd, const char* srcPath, const char } #endif - struct stat_ destStat; - while ((ret = stat_(destPath, &destStat)) < 0 && errno == EINTR); - if (ret == 0) - { - if (!overwrite) - { - errno = EEXIST; - return -1; - } - - if (sourceStat.st_dev == destStat.st_dev && sourceStat.st_ino == destStat.st_ino) - { - // Attempt to copy file over itself. Fail with the same error code as - // open would. - errno = EBUSY; - return -1; - } - -#if HAVE_CLONEFILE - // For clonefile we need to unlink the destination file first but we need to - // check permission first to ensure we don't try to unlink read-only file. - if (access(destPath, W_OK) != 0) - { - return -1; - } - - ret = unlink(destPath); - if (ret != 0) - { - return ret; - } -#endif - } - -#if HAVE_CLONEFILE - while ((ret = clonefile(srcPath, destPath, 0)) < 0 && errno == EINTR); - // EEXIST can happen due to race condition between the stat/unlink above - // and the clonefile here. The file could be (re-)created from another - // thread or process before we have a chance to call clonefile. Handle - // it by falling back to the slow path. - if (ret == 0 || (errno != ENOTSUP && errno != EXDEV && errno != EEXIST)) - { - return ret; - } -#else - // Unused variable - (void)srcPath; -#endif - - openFlags = O_WRONLY | O_TRUNC | O_CREAT | (overwrite ? 0 : O_EXCL); -#if HAVE_O_CLOEXEC - openFlags |= O_CLOEXEC; -#endif - while ((outFd = open(destPath, openFlags, sourceStat.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) < 0 && errno == EINTR); - if (outFd < 0) - { - return -1; - } -#if !HAVE_O_CLOEXEC - fcntl(outFd, F_SETFD, FD_CLOEXEC); -#endif - - // Get the stats on the source file. - bool copied = false; - +#if HAVE_SENDFILE_4 // If sendfile is available (Linux), try to use it, as the whole copy // can be performed in the kernel, without lots of unnecessary copying. -#if HAVE_SENDFILE_4 // On 32-bit, if you use 64-bit offsets, the last argument of `sendfile' will be a // `size_t' a 32-bit integer while the `st_size' field of the stat structure will be off64_t. @@ -1358,9 +1302,6 @@ int32_t SystemNative_CopyFile(intptr_t sourceFd, const char* srcPath, const char { if (errno != EINVAL && errno != ENOSYS) { - tmpErrno = errno; - close(outFd); - errno = tmpErrno; return -1; } else @@ -1386,9 +1327,6 @@ int32_t SystemNative_CopyFile(intptr_t sourceFd, const char* srcPath, const char // Manually read all data from the source and write it to the destination. if (!copied && CopyFile_ReadWrite(inFd, outFd) != 0) { - tmpErrno = errno; - close(outFd); - errno = tmpErrno; return -1; } @@ -1413,10 +1351,16 @@ int32_t SystemNative_CopyFile(intptr_t sourceFd, const char* srcPath, const char while ((ret = futimes(outFd, origTimes)) < 0 && errno == EINTR); #endif - tmpErrno = errno; - close(outFd); - errno = tmpErrno; +#if !TARGET_ANDROID + // On Android, the copy should still succeed even if copying the file times didn't. + if (ret != 0) + { + return -1; + } +#endif + return 0; +#endif // HAVE_FCOPYFILE } intptr_t SystemNative_INotifyInit(void) diff --git a/src/Native/Unix/System.Native/pal_io.h b/src/Native/Unix/System.Native/pal_io.h index 125b006d440b..f13cdc660655 100644 --- a/src/Native/Unix/System.Native/pal_io.h +++ b/src/Native/Unix/System.Native/pal_io.h @@ -705,11 +705,11 @@ DLLEXPORT void SystemNative_Sync(void); DLLEXPORT int32_t SystemNative_Write(intptr_t fd, const void* buffer, int32_t bufferSize); /** - * Copies all data from the source file descriptor/path to the destination file path. + * Copies all data from the source file descriptor to the destination file descriptor. * * Returns 0 on success; otherwise, returns -1 and sets errno. */ -DLLEXPORT int32_t SystemNative_CopyFile(intptr_t sourceFd, const char* srcPath, const char* destPath, int32_t overwrite); +DLLEXPORT int32_t SystemNative_CopyFile(intptr_t sourceFd, intptr_t destinationFd); /** * Initializes a new inotify instance and returns a file diff --git a/src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs b/src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs index 6a4b97ba6747..b2007ca32d8a 100644 --- a/src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs +++ b/src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs @@ -52,29 +52,9 @@ public static void CopyFile(string sourceFullPath, string destFullPath, bool ove // Copy the contents of the file from the source to the destination, creating the destination in the process using (var src = new FileStream(sourceFullPath, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultBufferSize, FileOptions.None)) + using (var dst = new FileStream(destFullPath, overwrite ? FileMode.Create : FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, DefaultBufferSize, FileOptions.None)) { - int result = Interop.Sys.CopyFile(src.SafeFileHandle, sourceFullPath, destFullPath, overwrite ? 1 : 0); - - if (result < 0) - { - Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); - - // If we fail to open the file due to a path not existing, we need to know whether to blame - // the file itself or its directory. If we're creating the file, then we blame the directory, - // otherwise we blame the file. - // - // When opening, we need to align with Windows, which considers a missing path to be - // FileNotFound only if the containing directory exists. - - bool isDirectory = (error.Error == Interop.Error.ENOENT) && - (overwrite || !DirectoryExists(Path.GetDirectoryName(PathInternal.TrimEndingDirectorySeparator(destFullPath)))); - - Interop.CheckIo( - error.Error, - destFullPath, - isDirectory, - errorRewriter: e => (e.Error == Interop.Error.EISDIR) ? Interop.Error.EACCES.Info() : e); - } + Interop.CheckIo(Interop.Sys.CopyFile(src.SafeFileHandle, dst.SafeFileHandle)); } }