diff --git a/src/libraries/Native/Unix/System.Native/pal_io.c b/src/libraries/Native/Unix/System.Native/pal_io.c index 59b8f2770c103..6534265085639 100644 --- a/src/libraries/Native/Unix/System.Native/pal_io.c +++ b/src/libraries/Native/Unix/System.Native/pal_io.c @@ -1208,41 +1208,46 @@ int32_t SystemNative_CopyFile(intptr_t sourceFd, intptr_t destinationFd) return -1; } - // 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. // So `size' will have to be `uint64_t'. In all other cases, it will be `size_t'. uint64_t size = (uint64_t)sourceStat.st_size; - - // Note that per man page for large files, you have to iterate until the - // whole file is copied (Linux has a limit of 0x7ffff000 bytes copied). - while (size > 0) + if (size != 0) { - ssize_t sent = sendfile(outFd, inFd, NULL, (size >= SSIZE_MAX ? SSIZE_MAX : (size_t)size)); - if (sent < 0) + // Note that per man page for large files, you have to iterate until the + // whole file is copied (Linux has a limit of 0x7ffff000 bytes copied). + while (size > 0) { - if (errno != EINVAL && errno != ENOSYS) + ssize_t sent = sendfile(outFd, inFd, NULL, (size >= SSIZE_MAX ? SSIZE_MAX : (size_t)size)); + if (sent < 0) { - return -1; + if (errno != EINVAL && errno != ENOSYS) + { + return -1; + } + else + { + break; + } } else { - break; + assert((size_t)sent <= size); + size -= (size_t)sent; } } - else + + if (size == 0) { - assert((size_t)sent <= size); - size -= (size_t)sent; + copied = true; } } - if (size == 0) - { - copied = true; - } + // sendfile couldn't be used; fall back to a manual copy below. This could happen // if we're on an old kernel, for example, where sendfile could only be used - // with sockets and not regular files. + // with sockets and not regular files. Additionally, certain files (e.g. procfs) + // may return a size of 0 even though reading from then will produce data. As such, + // we avoid using sendfile with the queried size if the size is reported as 0. #endif // HAVE_SENDFILE_4 // Manually read all data from the source and write it to the destination. diff --git a/src/libraries/System.IO.FileSystem/tests/File/Copy.cs b/src/libraries/System.IO.FileSystem/tests/File/Copy.cs index 0654f99dc9ce3..eb2686807bc1c 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/Copy.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/Copy.cs @@ -239,6 +239,18 @@ public void WindowsAlternateDataStream(string defaultStream, string alternateStr Assert.Throws(() => Copy(testFileAlternateStream, testFile2)); Assert.Throws(() => Copy(testFileAlternateStream, testFile2 + alternateStream)); } + + [Theory] + [PlatformSpecific(TestPlatforms.Linux)] + [InlineData("/proc/cmdline")] + [InlineData("/proc/version")] + [InlineData("/proc/filesystems")] + public void Linux_CopyFromProcfsToFile(string path) + { + string testFile = GetTestFilePath(); + File.Copy(path, testFile); + Assert.Equal(File.ReadAllText(path), File.ReadAllText(testFile)); // assumes chosen files won't change between reads + } #endregion }