Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/corefx.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Baulig <mabaul@microsoft.com>2019-12-10 21:53:37 +0300
committerAlexander Köplinger <alex.koeplinger@outlook.com>2019-12-10 21:53:37 +0300
commitc388c710a7b3d9cfe0ea5d8fe2c1c1ddbeb46ee2 (patch)
tree52bfecbe6b60ba391ec3390b63fc7b30979cd9f1
parente610bc82cd25f141b1981b1f2e1bd1e7ce873087 (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)
-rw-r--r--src/Common/src/Interop/Unix/System.Native/Interop.CopyFile.cs2
-rw-r--r--src/Native/Unix/Common/pal_config.h.in2
-rw-r--r--src/Native/Unix/System.Native/pal_io.c112
-rw-r--r--src/Native/Unix/System.Native/pal_io.h4
-rw-r--r--src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs24
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);
+ }
}
}