Skip to content

Commit 48170cb

Browse files
committed
SR-4993: Read zero length files on Linux
- Some files (eg in /proc) report a filesize of zero and need to be read into a buffer instead of using mmap().
1 parent 5fd6130 commit 48170cb

File tree

2 files changed

+82
-1
lines changed

2 files changed

+82
-1
lines changed

Foundation/NSData.swift

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
146146
public convenience init(bytesNoCopy bytes: UnsafeMutableRawPointer, length: Int, deallocator: ((UnsafeMutableRawPointer, Int) -> Void)? = nil) {
147147
self.init(bytes: bytes, length: length, copy: false, deallocator: deallocator)
148148
}
149+
149150
public convenience init(contentsOfFile path: String, options readOptionsMask: ReadingOptions = []) throws {
150151
let readResult = try NSData.readBytesFromFileWithExtendedAttributes(path, options: readOptionsMask)
151152
self.init(bytes: readResult.bytes, length: readResult.length, copy: false, deallocator: readResult.deallocator)
@@ -380,7 +381,13 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
380381
}
381382

382383
let length = Int(info.st_size)
383-
384+
385+
#if os(Linux)
386+
if length == 0 && (info.st_mode & S_IFMT == S_IFREG) {
387+
return try readZeroSizeFile(fd)
388+
}
389+
#endif
390+
384391
if options.contains(.alwaysMapped) {
385392
let data = mmap(nil, length, PROT_READ, MAP_PRIVATE, fd, 0)
386393

@@ -414,6 +421,39 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
414421
free(buffer)
415422
}
416423
}
424+
425+
#if os(Linux)
426+
internal static func readZeroSizeFile(_ fd: Int32) throws -> NSDataReadResult {
427+
let blockSize = 1024 * 1024 // 1MB
428+
var data: UnsafeMutableRawPointer? = nil
429+
var bytesRead = 0
430+
var amt = 0
431+
432+
repeat {
433+
data = realloc(data, bytesRead + blockSize)
434+
amt = read(fd, data!.advanced(by: bytesRead), blockSize)
435+
436+
// Dont continue on EINTR or EAGAIN as the file position may not
437+
// have changed, see read(2).
438+
if amt < 0 {
439+
free(data!)
440+
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)
441+
}
442+
bytesRead += amt
443+
} while amt > 0
444+
445+
if bytesRead == 0 {
446+
free(data!)
447+
data = malloc(0)
448+
} else {
449+
data = realloc(data, bytesRead) // shrink down the allocated block.
450+
}
451+
452+
return NSDataReadResult(bytes: data!, length: bytesRead) { buffer, length in
453+
free(buffer)
454+
}
455+
}
456+
#endif
417457

418458
internal func makeTemporaryFile(inDirectory dirPath: String) throws -> (Int32, String) {
419459
let template = dirPath._nsObject.appendingPathComponent("tmp.XXXXXX")

TestFoundation/TestNSData.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class TestNSData: XCTestCase {
3737
("test_base64Data_medium", test_base64Data_medium),
3838
("test_base64Data_small", test_base64Data_small),
3939
("test_openingNonExistentFile", test_openingNonExistentFile),
40+
("test_contentsOfFile", test_contentsOfFile),
41+
("test_contentsOfZeroFile", test_contentsOfZeroFile),
4042
("test_basicReadWrite", test_basicReadWrite),
4143
("test_bufferSizeCalculation", test_bufferSizeCalculation),
4244
// ("test_dataHash", test_dataHash), Disabled due to lack of brdiging in swift runtime -- infinite loops
@@ -908,6 +910,45 @@ extension TestNSData {
908910
XCTAssertTrue(didCatchError)
909911
}
910912

913+
func test_contentsOfFile() {
914+
let testDir = testBundle().resourcePath
915+
let filename = testDir!.appending("/NSStringTestData.txt")
916+
917+
let contents = NSData(contentsOfFile: filename)
918+
XCTAssertNotNil(contents)
919+
if let contents = contents {
920+
let ptr = UnsafeMutableRawPointer(mutating: contents.bytes)
921+
let str = String(bytesNoCopy: ptr, length: contents.length,
922+
encoding: .ascii, freeWhenDone: false)
923+
XCTAssertEqual(str, "swift-corelibs-foundation")
924+
}
925+
}
926+
927+
func test_contentsOfZeroFile() {
928+
#if os(Linux)
929+
let contents = NSData(contentsOfFile: "/proc/self/cmdline")
930+
XCTAssertNotNil(contents)
931+
if let contents = contents {
932+
XCTAssertTrue(contents.length > 0)
933+
let ptr = UnsafeMutableRawPointer(mutating: contents.bytes)
934+
let str = String(bytesNoCopy: ptr, length: contents.length,
935+
encoding: .ascii, freeWhenDone: false)
936+
XCTAssertNotNil(str)
937+
if let str = str {
938+
XCTAssertTrue(str.hasSuffix("TestFoundation"))
939+
}
940+
}
941+
942+
do {
943+
let maps = try String(contentsOfFile: "/proc/self/maps", encoding: .utf8)
944+
XCTAssertTrue(maps.characters.count > 0)
945+
print(maps)
946+
} catch {
947+
XCTFail()
948+
}
949+
#endif
950+
}
951+
911952
func test_basicReadWrite() {
912953
let url = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent("testfile")
913954
let count = 1 << 24

0 commit comments

Comments
 (0)