Skip to content

Commit d1ed951

Browse files
committed
SR-14953: Data.write(to:) fails when writing to /dev/stdout
- FileHandle.synchronize() would throw when calling fsync() on special files. Ignore EINVAL and EROFS if fsync() fails, so that the behaviour matches Darwin Foundation.
1 parent b630302 commit d1ed951

File tree

3 files changed

+21
-1
lines changed

3 files changed

+21
-1
lines changed

Sources/Foundation/FileHandle.swift

+8-1
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,14 @@ open class FileHandle : NSObject {
595595
throw _NSErrorWithWindowsError(GetLastError(), reading: false)
596596
}
597597
#else
598-
guard fsync(_fd) >= 0 else { throw _NSErrorWithErrno(errno, reading: false) }
598+
// Linux, macOS, OpenBSD return -1 and errno == EINVAL if trying to sync a special file,
599+
// eg a fifo, character device etc which can be ignored.
600+
// Additionally, Linux may return EROFS if tying to sync on a readonly filesystem, which also can be ignored.
601+
// macOS can also return ENOTSUP for pipes but dont ignore it, so that the behaviour matches Darwin's Foundation.
602+
if fsync(_fd) < 0 {
603+
if errno == EINVAL || errno == EROFS { return }
604+
throw _NSErrorWithErrno(errno, reading: false)
605+
}
599606
#endif
600607
}
601608

Tests/Foundation/Tests/TestFileHandle.swift

+7
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,12 @@ class TestFileHandle : XCTestCase {
613613
}
614614
#endif
615615

616+
func testSynchronizeOnSpecialFile() throws {
617+
// .synchronize() on a special file shouldnt fail
618+
let fh = try XCTUnwrap(FileHandle(forWritingAtPath: "/dev/stdout"))
619+
XCTAssertNoThrow(try fh.synchronize())
620+
}
621+
616622
static var allTests : [(String, (TestFileHandle) -> () throws -> ())] {
617623
var tests: [(String, (TestFileHandle) -> () throws -> ())] = [
618624
("testReadUpToCount", testReadUpToCount),
@@ -632,6 +638,7 @@ class TestFileHandle : XCTestCase {
632638
("test_waitForDataInBackgroundAndNotify", test_waitForDataInBackgroundAndNotify),
633639
/* ⚠️ */ ("test_readWriteHandlers", testExpectedToFail(test_readWriteHandlers,
634640
/* ⚠️ */ "<rdar://problem/50860781> sporadically times out")),
641+
("testSynchronizeOnSpecialFile", testSynchronizeOnSpecialFile),
635642
]
636643

637644
#if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT

Tests/Foundation/Tests/TestNSData.swift

+6
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ class TestNSData: LoopbackServerTest {
237237
("test_writeToURLOptions", test_writeToURLOptions),
238238
("test_writeToURLPermissions", test_writeToURLPermissions),
239239
("test_writeToURLPermissionsWithAtomic", test_writeToURLPermissionsWithAtomic),
240+
("test_writeToURLSpecialFile", test_writeToURLSpecialFile),
240241
("test_edgeNoCopyDescription", test_edgeNoCopyDescription),
241242
("test_initializeWithBase64EncodedDataGetsDecodedData", test_initializeWithBase64EncodedDataGetsDecodedData),
242243
("test_initializeWithBase64EncodedDataWithNonBase64CharacterIsNil", test_initializeWithBase64EncodedDataWithNonBase64CharacterIsNil),
@@ -623,6 +624,11 @@ class TestNSData: LoopbackServerTest {
623624
#endif
624625
}
625626

627+
func test_writeToURLSpecialFile() {
628+
let url = URL(fileURLWithPath: "/dev/stdout")
629+
XCTAssertNoThrow(try Data("Output to STDOUT\n".utf8).write(to: url))
630+
}
631+
626632
func test_emptyDescription() {
627633
let expected = "<>"
628634

0 commit comments

Comments
 (0)