From 15a00898af8639ddf738e13aa147d5c8c1962eac Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Thu, 10 Jul 2025 18:28:01 -0700 Subject: [PATCH] Add the ability to copy pipes using File.Copy Bug:https://github.com/dart-lang/sdk/issues/60999 Change-Id: I2e20ad5c051b43582483d5275d6d2b65b6f3395a Tested: unit Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/439823 Commit-Queue: Brian Quinlan Reviewed-by: Alexander Aprelev --- runtime/bin/file_linux.cc | 5 ++++- runtime/bin/file_macos.cc | 2 ++ tests/standalone/io/file_copy_test.dart | 27 +++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/runtime/bin/file_linux.cc b/runtime/bin/file_linux.cc index 471ed13bafc..9f7cbaf96df 100644 --- a/runtime/bin/file_linux.cc +++ b/runtime/bin/file_linux.cc @@ -494,7 +494,10 @@ bool File::Copy(Namespace* namespc, // From sendfile man pages: // Applications may wish to fall back to read(2)/write(2) in the case // where sendfile() fails with EINVAL or ENOSYS. - if ((result < 0) && ((errno == EINVAL) || (errno == ENOSYS))) { + // + // Also, fallback on ESPIPE (returned when the input file is not seekable). + if ((result < 0) && + ((errno == EINVAL) || (errno == ENOSYS) || (errno == ESPIPE))) { const intptr_t kBufferSize = 8 * KB; uint8_t* buffer = reinterpret_cast(malloc(kBufferSize)); while ((result = TEMP_FAILURE_RETRY(read(old_fd, buffer, kBufferSize))) > diff --git a/runtime/bin/file_macos.cc b/runtime/bin/file_macos.cc index 00849f2452a..456bf24deab 100644 --- a/runtime/bin/file_macos.cc +++ b/runtime/bin/file_macos.cc @@ -489,6 +489,8 @@ bool File::Copy(Namespace* namespc, const char* new_path) { File::Type type = File::GetType(namespc, old_path, true); if (type == kIsFile || type == kIsSock || type == kIsPipe) { + // `copyfile` will complete with [ENOTSUP] if the source file is: + // "...not a directory, symbolic link, or regular file." return (copyfile(old_path, new_path, nullptr, COPYFILE_ALL) == 0); } SetErrno(type); diff --git a/tests/standalone/io/file_copy_test.dart b/tests/standalone/io/file_copy_test.dart index 605a6182022..fe8b595acce 100644 --- a/tests/standalone/io/file_copy_test.dart +++ b/tests/standalone/io/file_copy_test.dart @@ -5,13 +5,39 @@ // Dart test program for testing File.copy* import 'dart:io'; +import 'dart:ffi' as ffi; import "package:expect/async_helper.dart"; import "package:expect/expect.dart"; +import 'package:ffi/ffi.dart'; const FILE_CONTENT1 = 'some string'; const FILE_CONTENT2 = 'some other string'; +@ffi.Native, ffi.Int)>() +external int mkfifo(ffi.Pointer pathname, int mode); + +void testFifo() async { + var tmp = Directory.systemTemp.createTempSync('copy-fifo'); + if (!Platform.isLinux) { + return; + } + + using((arena) { + // 432 => rw-rw---- + if (mkfifo("${tmp.path}/fifo".toNativeUtf8(allocator: arena).cast(), 432) == + -1) { + throw FileSystemException('error creating FIFO'); + } + }); + + final write = File("${tmp.path}/fifo").writeAsString("Hello World!"); + final copy = File("${tmp.path}/fifo").copy("${tmp.path}/copy"); + await Future.wait([write, copy]); + Expect.equals("Hello World!", File("${tmp.path}/copy").readAsStringSync()); + tmp.deleteSync(recursive: true); +} + void testCopySync() { var tmp = Directory.systemTemp.createTempSync('dart-file-copy'); @@ -115,6 +141,7 @@ void testCopy() { main() { testCopySync(); testCopy(); + testFifo(); // This is Windows only test. testWithForwardSlashes(); }