Skip to content

Commit eccf74b

Browse files
authored
Merge pull request #1732 from spevans/pr_sr_8999
SR-8999: NSData contentsOf expects st_size to be the size of the file.
2 parents ca8be27 + d583377 commit eccf74b

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
@@ -31,102 +31,95 @@ open class FileHandle : NSObject, NSSecureCoding {
3131
}
3232

3333
open var availableData: Data {
34-
return _readDataOfLength(Int.max, untilEOF: false)
34+
do {
35+
let readResult = try _readDataOfLength(Int.max, untilEOF: false)
36+
return readResult.toData()
37+
} catch {
38+
fatalError("\(error)")
39+
}
3540
}
3641

3742
open func readDataToEndOfFile() -> Data {
3843
return readData(ofLength: Int.max)
3944
}
4045

4146
open func readData(ofLength length: Int) -> Data {
42-
return _readDataOfLength(length, untilEOF: true)
47+
do {
48+
let readResult = try _readDataOfLength(length, untilEOF: true)
49+
return readResult.toData()
50+
} catch {
51+
fatalError("\(error)")
52+
}
4353
}
4454

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

113-
if length == Int.max && total > 0 {
114-
dynamicBuffer = _CFReallocf(dynamicBuffer!, total)
115-
}
116-
117114
if total == 0 {
118115
free(dynamicBuffer)
116+
return NSData.NSDataReadResult(bytes: nil, length: 0, deallocator: nil)
119117
}
120-
else if total > 0 {
121-
let bytePtr = dynamicBuffer!.bindMemory(to: UInt8.self, capacity: total)
122-
return Data(bytesNoCopy: bytePtr, count: total, deallocator: .free)
123-
}
124-
else {
125-
assertionFailure("The total number of read bytes must not be negative")
126-
free(dynamicBuffer)
118+
dynamicBuffer = _CFReallocf(dynamicBuffer, total)
119+
let bytePtr = dynamicBuffer.bindMemory(to: UInt8.self, capacity: total)
120+
return NSData.NSDataReadResult(bytes: bytePtr, length: total) { buffer, length in
121+
free(buffer)
127122
}
128-
129-
return Data()
130123
}
131124

132125
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),
@@ -1494,6 +1495,17 @@ extension TestNSData {
14941495
#endif
14951496
}
14961497

1498+
func test_wrongSizedFile() {
1499+
#if os(Linux)
1500+
// Some files in /sys report a non-zero st_size often bigger than the contents
1501+
guard let data = NSData.init(contentsOfFile: "/sys/kernel/profiling") else {
1502+
XCTFail("Cant read /sys/kernel/profiling")
1503+
return
1504+
}
1505+
XCTAssert(data.length > 0)
1506+
#endif
1507+
}
1508+
14971509
func test_contentsOfURL() {
14981510
let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/country.txt"
14991511
let url = URL(string: urlString)!

0 commit comments

Comments
 (0)