Skip to content

Commit d34a1d8

Browse files
authored
Merge pull request #1895 from spevans/pr_sr_8999_42
[4.2] SR-8999: NSData contentsOf expects st_size to be the size of the file.
2 parents e691868 + defbe66 commit d34a1d8

File tree

3 files changed

+92
-156
lines changed

3 files changed

+92
-156
lines changed

Foundation/FileHandle.swift

Lines changed: 64 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -24,102 +24,95 @@ open class FileHandle : NSObject, NSSecureCoding {
2424
}
2525

2626
open var availableData: Data {
27-
return _readDataOfLength(Int.max, untilEOF: false)
27+
do {
28+
let readResult = try _readDataOfLength(Int.max, untilEOF: false)
29+
return readResult.toData()
30+
} catch {
31+
fatalError("\(error)")
32+
}
2833
}
2934

3035
open func readDataToEndOfFile() -> Data {
3136
return readData(ofLength: Int.max)
3237
}
3338

3439
open func readData(ofLength length: Int) -> Data {
35-
return _readDataOfLength(length, untilEOF: true)
40+
do {
41+
let readResult = try _readDataOfLength(length, untilEOF: true)
42+
return readResult.toData()
43+
} catch {
44+
fatalError("\(error)")
45+
}
3646
}
3747

38-
internal func _readDataOfLength(_ length: Int, untilEOF: Bool) -> Data {
48+
internal func _readDataOfLength(_ length: Int, untilEOF: Bool, options: NSData.ReadingOptions = []) throws -> NSData.NSDataReadResult {
3949
precondition(_fd >= 0, "Bad file descriptor")
50+
if length == 0 && !untilEOF {
51+
// Nothing requested, return empty response
52+
return NSData.NSDataReadResult(bytes: nil, length: 0, deallocator: nil)
53+
}
54+
4055
var statbuf = stat()
41-
var dynamicBuffer: UnsafeMutableRawPointer? = nil
42-
var total = 0
4356
if fstat(_fd, &statbuf) < 0 {
44-
fatalError("Unable to read file")
57+
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)
4558
}
46-
if statbuf.st_mode & S_IFMT != S_IFREG {
47-
/* We get here on sockets, character special files, FIFOs ... */
48-
var currentAllocationSize: size_t = 1024 * 8
49-
dynamicBuffer = malloc(currentAllocationSize)
50-
var remaining = length
51-
while remaining > 0 {
52-
let amountToRead = min(1024 * 8, remaining)
53-
// Make sure there is always at least amountToRead bytes available in the buffer.
54-
if (currentAllocationSize - total) < amountToRead {
55-
currentAllocationSize *= 2
56-
dynamicBuffer = _CFReallocf(dynamicBuffer!, currentAllocationSize)
57-
if dynamicBuffer == nil {
58-
fatalError("unable to allocate backing buffer")
59+
60+
let readBlockSize: Int
61+
if statbuf.st_mode & S_IFMT == S_IFREG {
62+
// TODO: Should files over a certain size always be mmap()'d?
63+
if options.contains(.alwaysMapped) {
64+
// Filesizes are often 64bit even on 32bit systems
65+
let mapSize = min(length, Int(clamping: statbuf.st_size))
66+
let data = mmap(nil, mapSize, PROT_READ, MAP_PRIVATE, _fd, 0)
67+
// Swift does not currently expose MAP_FAILURE
68+
if data != UnsafeMutableRawPointer(bitPattern: -1) {
69+
return NSData.NSDataReadResult(bytes: data!, length: mapSize) { buffer, length in
70+
munmap(buffer, length)
5971
}
6072
}
61-
let amtRead = read(_fd, dynamicBuffer!.advanced(by: total), amountToRead)
62-
if 0 > amtRead {
63-
free(dynamicBuffer)
64-
fatalError("read failure")
65-
}
66-
if 0 == amtRead {
67-
break // EOF
68-
}
69-
70-
total += amtRead
71-
remaining -= amtRead
72-
73-
if total == length || !untilEOF {
74-
break // We read everything the client asked for.
75-
}
73+
}
74+
75+
if statbuf.st_blksize > 0 {
76+
readBlockSize = Int(clamping: statbuf.st_blksize)
77+
} else {
78+
readBlockSize = 1024 * 8
7679
}
7780
} else {
78-
let offset = lseek(_fd, 0, SEEK_CUR)
79-
if offset < 0 {
80-
fatalError("Unable to fetch current file offset")
81+
/* We get here on sockets, character special files, FIFOs ... */
82+
readBlockSize = 1024 * 8
83+
}
84+
var currentAllocationSize = readBlockSize
85+
var dynamicBuffer = malloc(currentAllocationSize)!
86+
var total = 0
87+
88+
while total < length {
89+
let remaining = length - total
90+
let amountToRead = min(readBlockSize, remaining)
91+
// Make sure there is always at least amountToRead bytes available in the buffer.
92+
if (currentAllocationSize - total) < amountToRead {
93+
currentAllocationSize *= 2
94+
dynamicBuffer = _CFReallocf(dynamicBuffer, currentAllocationSize)
8195
}
82-
if off_t(statbuf.st_size) > offset {
83-
var remaining = size_t(off_t(statbuf.st_size) - offset)
84-
remaining = min(remaining, size_t(length))
85-
86-
dynamicBuffer = malloc(remaining)
87-
if dynamicBuffer == nil {
88-
fatalError("Malloc failure")
89-
}
90-
91-
while remaining > 0 {
92-
let count = read(_fd, dynamicBuffer!.advanced(by: total), remaining)
93-
if count < 0 {
94-
free(dynamicBuffer)
95-
fatalError("Unable to read from fd")
96-
}
97-
if count == 0 {
98-
break
99-
}
100-
total += count
101-
remaining -= count
102-
}
96+
let amtRead = read(_fd, dynamicBuffer.advanced(by: total), amountToRead)
97+
if amtRead < 0 {
98+
free(dynamicBuffer)
99+
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)
100+
}
101+
total += amtRead
102+
if amtRead == 0 || !untilEOF { // If there is nothing more to read or we shouldnt keep reading then exit
103+
break
103104
}
104105
}
105106

106-
if length == Int.max && total > 0 {
107-
dynamicBuffer = _CFReallocf(dynamicBuffer!, total)
108-
}
109-
110107
if total == 0 {
111108
free(dynamicBuffer)
109+
return NSData.NSDataReadResult(bytes: nil, length: 0, deallocator: nil)
112110
}
113-
else if total > 0 {
114-
let bytePtr = dynamicBuffer!.bindMemory(to: UInt8.self, capacity: total)
115-
return Data(bytesNoCopy: bytePtr, count: total, deallocator: .free)
116-
}
117-
else {
118-
assertionFailure("The total number of read bytes must not be negative")
119-
free(dynamicBuffer)
111+
dynamicBuffer = _CFReallocf(dynamicBuffer, total)
112+
let bytePtr = dynamicBuffer.bindMemory(to: UInt8.self, capacity: total)
113+
return NSData.NSDataReadResult(bytes: bytePtr, length: total) { buffer, length in
114+
free(buffer)
120115
}
121-
122-
return Data()
123116
}
124117

125118
open func write(_ data: Data) {

Foundation/NSData.swift

Lines changed: 16 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
206206

207207
if url.isFileURL {
208208
let data = try NSData.readBytesFromFileWithExtendedAttributes(url.path, options: readOptionsMask)
209-
readResult = NSData(bytesNoCopy: data.bytes, length: data.length, deallocator: data.deallocator)
209+
readResult = data.toNSData()
210210
} else {
211211
let session = URLSession(configuration: URLSessionConfiguration.default)
212212
let cond = NSCondition()
@@ -410,102 +410,33 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
410410

411411
// MARK: - IO
412412
internal struct NSDataReadResult {
413-
var bytes: UnsafeMutableRawPointer
413+
var bytes: UnsafeMutableRawPointer?
414414
var length: Int
415-
var deallocator: ((_ buffer: UnsafeMutableRawPointer, _ length: Int) -> Void)?
416-
}
417-
418-
internal static func readBytesFromFileWithExtendedAttributes(_ path: String, options: ReadingOptions) throws -> NSDataReadResult {
419-
let fd = _CFOpenFile(path, O_RDONLY)
420-
if fd < 0 {
421-
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)
422-
}
423-
defer {
424-
close(fd)
425-
}
415+
var deallocator: ((_ buffer: UnsafeMutableRawPointer, _ length: Int) -> Void)!
426416

427-
var info = stat()
428-
let ret = withUnsafeMutablePointer(to: &info) { infoPointer -> Bool in
429-
if fstat(fd, infoPointer) < 0 {
430-
return false
417+
func toNSData() -> NSData {
418+
if bytes == nil {
419+
return NSData()
431420
}
432-
return true
433-
}
434-
435-
if !ret {
436-
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)
437-
}
438-
439-
let length = Int(info.st_size)
440-
if length == 0 && (info.st_mode & S_IFMT == S_IFREG) {
441-
return try readZeroSizeFile(fd)
421+
return NSData(bytesNoCopy: bytes!, length: length, deallocator: deallocator)
442422
}
443423

444-
if options.contains(.alwaysMapped) {
445-
let data = mmap(nil, length, PROT_READ, MAP_PRIVATE, fd, 0)
446-
447-
// Swift does not currently expose MAP_FAILURE
448-
if data != UnsafeMutableRawPointer(bitPattern: -1) {
449-
return NSDataReadResult(bytes: data!, length: length) { buffer, length in
450-
munmap(buffer, length)
451-
}
424+
func toData() -> Data {
425+
guard let bytes = bytes else {
426+
return Data()
452427
}
453-
454-
}
455-
456-
let data = malloc(length)!
457-
var remaining = Int(info.st_size)
458-
var total = 0
459-
while remaining > 0 {
460-
let amt = read(fd, data.advanced(by: total), remaining)
461-
if amt < 0 {
462-
break
463-
}
464-
remaining -= amt
465-
total += amt
428+
return Data(bytesNoCopy: bytes, count: length, deallocator: Data.Deallocator.custom(deallocator))
466429
}
430+
}
467431

468-
if remaining != 0 {
469-
free(data)
432+
internal static func readBytesFromFileWithExtendedAttributes(_ path: String, options: ReadingOptions) throws -> NSDataReadResult {
433+
guard let handle = FileHandle(path: path, flags: O_RDONLY, createMode: 0) else {
470434
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)
471435
}
472-
473-
return NSDataReadResult(bytes: data, length: length) { buffer, length in
474-
free(buffer)
475-
}
436+
let result = try handle._readDataOfLength(Int.max, untilEOF: true)
437+
return result
476438
}
477439

478-
internal static func readZeroSizeFile(_ fd: Int32) throws -> NSDataReadResult {
479-
let blockSize = 1024 * 1024 // 1MB
480-
var data: UnsafeMutableRawPointer? = nil
481-
var bytesRead = 0
482-
var amt = 0
483-
484-
repeat {
485-
data = realloc(data, bytesRead + blockSize)
486-
amt = read(fd, data!.advanced(by: bytesRead), blockSize)
487-
488-
// Dont continue on EINTR or EAGAIN as the file position may not
489-
// have changed, see read(2).
490-
if amt < 0 {
491-
free(data!)
492-
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)
493-
}
494-
bytesRead += amt
495-
} while amt > 0
496-
497-
if bytesRead == 0 {
498-
free(data!)
499-
data = malloc(0)
500-
} else {
501-
data = realloc(data, bytesRead) // shrink down the allocated block.
502-
}
503-
504-
return NSDataReadResult(bytes: data!, length: bytesRead) { buffer, length in
505-
free(buffer)
506-
}
507-
}
508-
509440
internal func makeTemporaryFile(inDirectory dirPath: String) throws -> (Int32, String) {
510441
let template = dirPath._nsObject.appendingPathComponent("tmp.XXXXXX")
511442
let maxLength = Int(PATH_MAX) + 1

TestFoundation/TestNSData.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ class TestNSData: LoopbackServerTest {
185185
("test_openingNonExistentFile", test_openingNonExistentFile),
186186
("test_contentsOfFile", test_contentsOfFile),
187187
("test_contentsOfZeroFile", test_contentsOfZeroFile),
188+
("test_wrongSizedFile", test_wrongSizedFile),
188189
("test_contentsOfURL", test_contentsOfURL),
189190
("test_basicReadWrite", test_basicReadWrite),
190191
("test_bufferSizeCalculation", test_bufferSizeCalculation),
@@ -1484,6 +1485,17 @@ extension TestNSData {
14841485
#endif
14851486
}
14861487

1488+
func test_wrongSizedFile() {
1489+
#if os(Linux)
1490+
// Some files in /sys report a non-zero st_size often bigger than the contents
1491+
guard let data = NSData.init(contentsOfFile: "/sys/kernel/profiling") else {
1492+
XCTFail("Cant read /sys/kernel/profiling")
1493+
return
1494+
}
1495+
XCTAssert(data.length > 0)
1496+
#endif
1497+
}
1498+
14871499
func test_contentsOfURL() {
14881500
let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/country.txt"
14891501
let url = URL(string: urlString)!

0 commit comments

Comments
 (0)