diff options
author | Martin Baulig <mabaul@microsoft.com> | 2019-12-10 21:53:37 +0300 |
---|---|---|
committer | Alexander Köplinger <alex.koeplinger@outlook.com> | 2019-12-10 21:53:37 +0300 |
commit | c388c710a7b3d9cfe0ea5d8fe2c1c1ddbeb46ee2 (patch) | |
tree | 52bfecbe6b60ba391ec3390b63fc7b30979cd9f1 /src | |
parent | e610bc82cd25f141b1981b1f2e1bd1e7ce873087 (diff) |
Cherry-pick dotnet/corefx#37583. (#375)
Use clonefile for CopyFile, if available (#37583)
* Rework SystemNative_CopyFile to use paths instead of file descriptors
Use clonefile for CopyFile, if available
* Bail out in SystemNative_CopyFile if access fails. We don't want to proceed if the destination file is read-only.
* Handle a race condition where clonefile can result in EEXIST error
* Update comment about fcopyfile
* Update PathInternal.TrimEndingDirectorySeparator to Path.TrimEndingDirectorySeparator
* Add missing close(outFd) call
* Remove unnecessary ! operator
* Save errno around close(outFd) calls
* access already sets EACCESS errno, just keep it
* Remove fcopyfile support
* Open destination file with O_CLOEXEC
* Return correct error code for source == destination
* Rearrange error handling a bit
* Remove forgotten HAVE_FCOPYFILE
(cherry picked from commit 5f45a8d5aa116d688f92952a1de72abd8b30f1a7)
Diffstat (limited to 'src')
-rw-r--r-- | src/Common/src/Interop/Unix/System.Native/Interop.CopyFile.cs | 2 | ||||
-rw-r--r-- | src/Native/Unix/Common/pal_config.h.in | 2 | ||||
-rw-r--r-- | src/Native/Unix/System.Native/pal_io.c | 112 | ||||
-rw-r--r-- | src/Native/Unix/System.Native/pal_io.h | 4 | ||||
-rw-r--r-- | src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs | 24 |
5 files changed, 110 insertions, 34 deletions
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 f7035fcead..fb97106635 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, SafeFileHandle destination); + internal static extern int CopyFile(SafeFileHandle source, string srcPath, string destPath, int overwrite); } } diff --git a/src/Native/Unix/Common/pal_config.h.in b/src/Native/Unix/Common/pal_config.h.in index 033e390489..3665b6a6ee 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_FCOPYFILE +#cmakedefine01 HAVE_CLONEFILE #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 ddd56b91c9..1406ee72c1 100644 --- a/src/Native/Unix/System.Native/pal_io.c +++ b/src/Native/Unix/System.Native/pal_io.c @@ -28,9 +28,11 @@ #include <termios.h> #include <unistd.h> #include <limits.h> -#if HAVE_FCOPYFILE -#include <copyfile.h> -#elif HAVE_SENDFILE_4 +#if HAVE_CLONEFILE +#include <sys/attr.h> +#include <sys/clonefile.h> +#endif +#if HAVE_SENDFILE_4 #include <sys/sendfile.h> #endif #if HAVE_INOTIFY @@ -1193,7 +1195,6 @@ 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) { @@ -1246,25 +1247,15 @@ static int32_t CopyFile_ReadWrite(int inFd, int outFd) free(buffer); return 0; } -#endif // !HAVE_FCOPYFILE -int32_t SystemNative_CopyFile(intptr_t sourceFd, intptr_t destinationFd) +int32_t SystemNative_CopyFile(intptr_t sourceFd, const char* srcPath, const char* destPath, int32_t overwrite) { int inFd = ToFileDescriptor(sourceFd); - 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 outFd; 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) { @@ -1284,9 +1275,74 @@ int32_t SystemNative_CopyFile(intptr_t sourceFd, intptr_t destinationFd) } #endif -#if HAVE_SENDFILE_4 + 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 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. @@ -1302,6 +1358,9 @@ int32_t SystemNative_CopyFile(intptr_t sourceFd, intptr_t destinationFd) { if (errno != EINVAL && errno != ENOSYS) { + tmpErrno = errno; + close(outFd); + errno = tmpErrno; return -1; } else @@ -1327,6 +1386,9 @@ int32_t SystemNative_CopyFile(intptr_t sourceFd, intptr_t destinationFd) // 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; } @@ -1351,16 +1413,10 @@ int32_t SystemNative_CopyFile(intptr_t sourceFd, intptr_t destinationFd) while ((ret = futimes(outFd, origTimes)) < 0 && errno == EINTR); #endif -#if !TARGET_ANDROID - // On Android, the copy should still succeed even if copying the file times didn't. - if (ret != 0) - { - return -1; - } -#endif - + tmpErrno = errno; + close(outFd); + errno = tmpErrno; 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 f13cdc6606..125b006d44 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 to the destination file descriptor. + * Copies all data from the source file descriptor/path to the destination file path. * * Returns 0 on success; otherwise, returns -1 and sets errno. */ -DLLEXPORT int32_t SystemNative_CopyFile(intptr_t sourceFd, intptr_t destinationFd); +DLLEXPORT int32_t SystemNative_CopyFile(intptr_t sourceFd, const char* srcPath, const char* destPath, int32_t overwrite); /** * 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 b2007ca32d..81ea613d6b 100644 --- a/src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs +++ b/src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs @@ -52,9 +52,29 @@ namespace System.IO // 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)) { - Interop.CheckIo(Interop.Sys.CopyFile(src.SafeFileHandle, dst.SafeFileHandle)); + 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); + } } } |