Skip to content

Commit c00478b

Browse files
authored
Reverts dotnet/corefx#37583 ("Use clonefile for CopyFile, if available #37583") (#40753)
* Revert " Use clonefile for CopyFile, if available (dotnet/corefx#37583)" This reverts commit 02ba75a. * Add unit test * Disable tests failing in browser-wasm
1 parent 321e5eb commit c00478b

File tree

7 files changed

+79
-129
lines changed

7 files changed

+79
-129
lines changed

src/libraries/Common/src/Interop/Unix/System.Native/Interop.CopyFile.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ internal static partial class Interop
1010
internal static partial class Sys
1111
{
1212
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_CopyFile", SetLastError = true)]
13-
internal static extern int CopyFile(SafeFileHandle source, string srcPath, string destPath, int overwrite);
13+
internal static extern int CopyFile(SafeFileHandle source, SafeFileHandle destination);
1414
}
1515
}

src/libraries/Native/Unix/Common/pal_config.h.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
#cmakedefine01 HAVE_SENDFILE_4
5454
#cmakedefine01 HAVE_SENDFILE_6
5555
#cmakedefine01 HAVE_SENDFILE_7
56-
#cmakedefine01 HAVE_CLONEFILE
56+
#cmakedefine01 HAVE_FCOPYFILE
5757
#cmakedefine01 HAVE_GETNAMEINFO_SIGNED_FLAGS
5858
#cmakedefine01 HAVE_GETPEEREID
5959
#cmakedefine01 HAVE_SUPPORT_FOR_DUAL_MODE_IPV4_PACKET_INFO

src/libraries/Native/Unix/System.Native/pal_io.c

Lines changed: 48 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,9 @@
2626
#include <termios.h>
2727
#include <unistd.h>
2828
#include <limits.h>
29-
#if HAVE_CLONEFILE
30-
#include <sys/attr.h>
31-
#include <sys/clonefile.h>
32-
#endif
33-
#if HAVE_SENDFILE_4
29+
#if HAVE_FCOPYFILE
30+
#include <copyfile.h>
31+
#elif HAVE_SENDFILE_4
3432
#include <sys/sendfile.h>
3533
#endif
3634
#if HAVE_INOTIFY
@@ -1020,6 +1018,7 @@ int32_t SystemNative_Write(intptr_t fd, const void* buffer, int32_t bufferSize)
10201018
return Common_Write(fd, buffer, bufferSize);
10211019
}
10221020

1021+
#if !HAVE_FCOPYFILE
10231022
// Read all data from inFd and write it to outFd
10241023
static int32_t CopyFile_ReadWrite(int inFd, int outFd)
10251024
{
@@ -1072,90 +1071,32 @@ static int32_t CopyFile_ReadWrite(int inFd, int outFd)
10721071
free(buffer);
10731072
return 0;
10741073
}
1074+
#endif // !HAVE_FCOPYFILE
10751075

1076-
int32_t SystemNative_CopyFile(intptr_t sourceFd, const char* srcPath, const char* destPath, int32_t overwrite)
1076+
int32_t SystemNative_CopyFile(intptr_t sourceFd, intptr_t destinationFd)
10771077
{
10781078
int inFd = ToFileDescriptor(sourceFd);
1079-
int outFd;
1079+
int outFd = ToFileDescriptor(destinationFd);
1080+
1081+
#if HAVE_FCOPYFILE
1082+
// If fcopyfile is available (OS X), try to use it, as the whole copy
1083+
// can be performed in the kernel, without lots of unnecessary copying.
1084+
// Copy data and metadata.
1085+
return fcopyfile(inFd, outFd, NULL, COPYFILE_ALL) == 0 ? 0 : -1;
1086+
#else
1087+
// Get the stats on the source file.
10801088
int ret;
1081-
int tmpErrno;
1082-
int openFlags;
10831089
struct stat_ sourceStat;
1084-
1090+
bool copied = false;
1091+
#if HAVE_SENDFILE_4
1092+
// If sendfile is available (Linux), try to use it, as the whole copy
1093+
// can be performed in the kernel, without lots of unnecessary copying.
10851094
while ((ret = fstat_(inFd, &sourceStat)) < 0 && errno == EINTR);
10861095
if (ret != 0)
10871096
{
10881097
return -1;
10891098
}
10901099

1091-
struct stat_ destStat;
1092-
while ((ret = stat_(destPath, &destStat)) < 0 && errno == EINTR);
1093-
if (ret == 0)
1094-
{
1095-
if (!overwrite)
1096-
{
1097-
errno = EEXIST;
1098-
return -1;
1099-
}
1100-
1101-
if (sourceStat.st_dev == destStat.st_dev && sourceStat.st_ino == destStat.st_ino)
1102-
{
1103-
// Attempt to copy file over itself. Fail with the same error code as
1104-
// open would.
1105-
errno = EBUSY;
1106-
return -1;
1107-
}
1108-
1109-
#if HAVE_CLONEFILE
1110-
// For clonefile we need to unlink the destination file first but we need to
1111-
// check permission first to ensure we don't try to unlink read-only file.
1112-
if (access(destPath, W_OK) != 0)
1113-
{
1114-
return -1;
1115-
}
1116-
1117-
ret = unlink(destPath);
1118-
if (ret != 0)
1119-
{
1120-
return ret;
1121-
}
1122-
#endif
1123-
}
1124-
1125-
#if HAVE_CLONEFILE
1126-
while ((ret = clonefile(srcPath, destPath, 0)) < 0 && errno == EINTR);
1127-
// EEXIST can happen due to race condition between the stat/unlink above
1128-
// and the clonefile here. The file could be (re-)created from another
1129-
// thread or process before we have a chance to call clonefile. Handle
1130-
// it by falling back to the slow path.
1131-
if (ret == 0 || (errno != ENOTSUP && errno != EXDEV && errno != EEXIST))
1132-
{
1133-
return ret;
1134-
}
1135-
#else
1136-
// Unused variable
1137-
(void)srcPath;
1138-
#endif
1139-
1140-
openFlags = O_WRONLY | O_TRUNC | O_CREAT | (overwrite ? 0 : O_EXCL);
1141-
#if HAVE_O_CLOEXEC
1142-
openFlags |= O_CLOEXEC;
1143-
#endif
1144-
while ((outFd = open(destPath, openFlags, sourceStat.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) < 0 && errno == EINTR);
1145-
if (outFd < 0)
1146-
{
1147-
return -1;
1148-
}
1149-
#if !HAVE_O_CLOEXEC
1150-
fcntl(outFd, F_SETFD, FD_CLOEXEC);
1151-
#endif
1152-
1153-
// Get the stats on the source file.
1154-
bool copied = false;
1155-
1156-
// If sendfile is available (Linux), try to use it, as the whole copy
1157-
// can be performed in the kernel, without lots of unnecessary copying.
1158-
#if HAVE_SENDFILE_4
11591100

11601101
// On 32-bit, if you use 64-bit offsets, the last argument of `sendfile' will be a
11611102
// `size_t' a 32-bit integer while the `st_size' field of the stat structure will be off64_t.
@@ -1171,9 +1112,6 @@ int32_t SystemNative_CopyFile(intptr_t sourceFd, const char* srcPath, const char
11711112
{
11721113
if (errno != EINVAL && errno != ENOSYS)
11731114
{
1174-
tmpErrno = errno;
1175-
close(outFd);
1176-
errno = tmpErrno;
11771115
return -1;
11781116
}
11791117
else
@@ -1199,37 +1137,47 @@ int32_t SystemNative_CopyFile(intptr_t sourceFd, const char* srcPath, const char
11991137
// Manually read all data from the source and write it to the destination.
12001138
if (!copied && CopyFile_ReadWrite(inFd, outFd) != 0)
12011139
{
1202-
tmpErrno = errno;
1203-
close(outFd);
1204-
errno = tmpErrno;
12051140
return -1;
12061141
}
12071142

12081143
// Now that the data from the file has been copied, copy over metadata
12091144
// from the source file. First copy the file times.
12101145
// If futimes nor futimes are available on this platform, file times will
12111146
// not be copied over.
1147+
while ((ret = fstat_(inFd, &sourceStat)) < 0 && errno == EINTR);
1148+
if (ret == 0)
1149+
{
12121150
#if HAVE_FUTIMENS
1213-
// futimens is prefered because it has a higher resolution.
1214-
struct timespec origTimes[2];
1215-
origTimes[0].tv_sec = (time_t)sourceStat.st_atime;
1216-
origTimes[0].tv_nsec = ST_ATIME_NSEC(&sourceStat);
1217-
origTimes[1].tv_sec = (time_t)sourceStat.st_mtime;
1218-
origTimes[1].tv_nsec = ST_MTIME_NSEC(&sourceStat);
1219-
while ((ret = futimens(outFd, origTimes)) < 0 && errno == EINTR);
1151+
// futimens is prefered because it has a higher resolution.
1152+
struct timespec origTimes[2];
1153+
origTimes[0].tv_sec = (time_t)sourceStat.st_atime;
1154+
origTimes[0].tv_nsec = ST_ATIME_NSEC(&sourceStat);
1155+
origTimes[1].tv_sec = (time_t)sourceStat.st_mtime;
1156+
origTimes[1].tv_nsec = ST_MTIME_NSEC(&sourceStat);
1157+
while ((ret = futimens(outFd, origTimes)) < 0 && errno == EINTR);
12201158
#elif HAVE_FUTIMES
1221-
struct timeval origTimes[2];
1222-
origTimes[0].tv_sec = sourceStat.st_atime;
1223-
origTimes[0].tv_usec = (int32_t)(ST_ATIME_NSEC(&sourceStat) / 1000);
1224-
origTimes[1].tv_sec = sourceStat.st_mtime;
1225-
origTimes[1].tv_usec = (int32_t)(ST_MTIME_NSEC(&sourceStat) / 1000);
1226-
while ((ret = futimes(outFd, origTimes)) < 0 && errno == EINTR);
1159+
struct timeval origTimes[2];
1160+
origTimes[0].tv_sec = sourceStat.st_atime;
1161+
origTimes[0].tv_usec = (int32_t)(ST_ATIME_NSEC(&sourceStat) / 1000);
1162+
origTimes[1].tv_sec = sourceStat.st_mtime;
1163+
origTimes[1].tv_usec = (int32_t)(ST_MTIME_NSEC(&sourceStat) / 1000);
1164+
while ((ret = futimes(outFd, origTimes)) < 0 && errno == EINTR);
12271165
#endif
1166+
}
1167+
if (ret != 0)
1168+
{
1169+
return -1;
1170+
}
1171+
1172+
// Then copy permissions.
1173+
while ((ret = fchmod(outFd, sourceStat.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) < 0 && errno == EINTR);
1174+
if (ret != 0)
1175+
{
1176+
return -1;
1177+
}
12281178

1229-
tmpErrno = errno;
1230-
close(outFd);
1231-
errno = tmpErrno;
12321179
return 0;
1180+
#endif // HAVE_FCOPYFILE
12331181
}
12341182

12351183
intptr_t SystemNative_INotifyInit(void)

src/libraries/Native/Unix/System.Native/pal_io.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -650,11 +650,11 @@ PALEXPORT void SystemNative_Sync(void);
650650
PALEXPORT int32_t SystemNative_Write(intptr_t fd, const void* buffer, int32_t bufferSize);
651651

652652
/**
653-
* Copies all data from the source file descriptor/path to the destination file path.
653+
* Copies all data from the source file descriptor to the destination file descriptor.
654654
*
655655
* Returns 0 on success; otherwise, returns -1 and sets errno.
656656
*/
657-
PALEXPORT int32_t SystemNative_CopyFile(intptr_t sourceFd, const char* srcPath, const char* destPath, int32_t overwrite);
657+
PALEXPORT int32_t SystemNative_CopyFile(intptr_t sourceFd, intptr_t destinationFd);
658658

659659
/**
660660
* Initializes a new inotify instance and returns a file

src/libraries/Native/Unix/configure.cmake

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -406,9 +406,9 @@ check_c_source_compiles(
406406
HAVE_SENDFILE_7)
407407

408408
check_symbol_exists(
409-
clonefile
410-
"sys/clonefile.h"
411-
HAVE_CLONEFILE)
409+
fcopyfile
410+
copyfile.h
411+
HAVE_FCOPYFILE)
412412

413413
check_include_files(
414414
"sys/sockio.h"

src/libraries/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,29 +21,9 @@ public static void CopyFile(string sourceFullPath, string destFullPath, bool ove
2121

2222
// Copy the contents of the file from the source to the destination, creating the destination in the process
2323
using (var src = new FileStream(sourceFullPath, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultBufferSize, FileOptions.None))
24+
using (var dst = new FileStream(destFullPath, overwrite ? FileMode.Create : FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, DefaultBufferSize, FileOptions.None))
2425
{
25-
int result = Interop.Sys.CopyFile(src.SafeFileHandle, sourceFullPath, destFullPath, overwrite ? 1 : 0);
26-
27-
if (result < 0)
28-
{
29-
Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo();
30-
31-
// If we fail to open the file due to a path not existing, we need to know whether to blame
32-
// the file itself or its directory. If we're creating the file, then we blame the directory,
33-
// otherwise we blame the file.
34-
//
35-
// When opening, we need to align with Windows, which considers a missing path to be
36-
// FileNotFound only if the containing directory exists.
37-
38-
bool isDirectory = (error.Error == Interop.Error.ENOENT) &&
39-
(overwrite || !DirectoryExists(Path.GetDirectoryName(Path.TrimEndingDirectorySeparator(destFullPath.AsSpan()))!));
40-
41-
Interop.CheckIo(
42-
error.Error,
43-
destFullPath,
44-
isDirectory,
45-
errorRewriter: e => (e.Error == Interop.Error.EISDIR) ? Interop.Error.EACCES.Info() : e);
46-
}
26+
Interop.CheckIo(Interop.Sys.CopyFile(src.SafeFileHandle, dst.SafeFileHandle));
4727
}
4828
}
4929

src/libraries/System.IO.FileSystem/tests/File/Copy.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ public static IEnumerable<object[]> CopyFileWithData_MemberData()
115115

116116
[Theory]
117117
[MemberData(nameof(CopyFileWithData_MemberData))]
118+
[ActiveIssue("https://github.com/dotnet/runtime/issues/40867", TestPlatforms.Browser)]
118119
public void CopyFileWithData(char[] data, bool readOnly)
119120
{
120121
string testFileSource = GetTestFilePath();
@@ -360,4 +361,25 @@ public void WindowsAlternateDataStreamOverwrite(string defaultStream, string alt
360361
Assert.Throws<IOException>(() => Copy(testFileAlternateStream, testFile2 + alternateStream, overwrite: true));
361362
}
362363
}
364+
365+
/// <summary>
366+
/// Single tests that shouldn't be duplicated by inheritance.
367+
/// </summary>
368+
[ActiveIssue("https://github.com/dotnet/runtime/issues/40867", TestPlatforms.Browser)]
369+
public sealed class File_Copy_Single : FileSystemTest
370+
{
371+
[Fact]
372+
public void EnsureThrowWhenCopyToNonSharedFile()
373+
{
374+
DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath());
375+
string file1 = Path.Combine(testDirectory.FullName, GetTestFileName());
376+
string file2 = Path.Combine(testDirectory.FullName, GetTestFileName());
377+
378+
File.WriteAllText(file1, "foo");
379+
File.WriteAllText(file2, "bar");
380+
381+
using var stream = new FileStream(file1, FileMode.Open, FileAccess.Read, FileShare.None);
382+
Assert.Throws<IOException>(() => File.Copy(file2, file1, overwrite: true));
383+
}
384+
}
363385
}

0 commit comments

Comments
 (0)