Skip to content

Commit 12d6069

Browse files
authored
(123104044) Consolidate/standardize file-based error reporting
* Consolidate/standardize file-based error reporting * Fix build failures * Fix test failures
1 parent 0a6059e commit 12d6069

File tree

9 files changed

+166
-194
lines changed

9 files changed

+166
-194
lines changed

Sources/FoundationEssentials/Data/Data+Error.swift

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -15,77 +15,8 @@
1515
@_implementationOnly import os
1616
@_implementationOnly import _ForSwiftFoundation
1717
@_implementationOnly import _CShims
18-
#else
19-
package import _CShims
2018
#endif
2119

22-
#if canImport(Darwin)
23-
import Darwin
24-
#elseif canImport(Glibc)
25-
import Glibc
26-
#endif
27-
28-
internal func fileReadingOrWritingError(posixErrno: Int32, path: PathOrURL?, reading: Bool, variant: String? = nil, extraUserInfo: [String: AnyHashable] = [:]) -> Error {
29-
let code: CocoaError.Code
30-
if reading {
31-
switch posixErrno {
32-
case EFBIG:
33-
code = .fileReadTooLarge
34-
case ENOENT:
35-
code = .fileReadNoSuchFile
36-
case EPERM, EACCES:
37-
code = .fileReadNoPermission
38-
case ENAMETOOLONG:
39-
code = .fileReadInvalidFileName
40-
default:
41-
code = .fileReadUnknown
42-
}
43-
} else {
44-
switch posixErrno {
45-
case ENOENT:
46-
code = .fileNoSuchFile
47-
case EPERM, EACCES:
48-
code = .fileWriteNoPermission
49-
case ENAMETOOLONG:
50-
code = .fileWriteInvalidFileName
51-
case EDQUOT, ENOSPC:
52-
code = .fileWriteOutOfSpace
53-
case EROFS:
54-
code = .fileWriteVolumeReadOnly
55-
case EEXIST:
56-
code = .fileWriteFileExists
57-
default:
58-
code = .fileWriteUnknown
59-
}
60-
}
61-
62-
var userInfo : [String : AnyHashable] = [:]
63-
if let posixError = POSIXErrorCode(rawValue: posixErrno) {
64-
userInfo[NSUnderlyingErrorKey] = POSIXError(posixError)
65-
}
66-
67-
if let variant {
68-
userInfo[NSUserStringVariantErrorKey] = [variant]
69-
}
70-
71-
if let path {
72-
switch path {
73-
case .path(let path):
74-
userInfo[NSFilePathErrorKey] = path
75-
case .url(let url):
76-
userInfo[NSURLErrorKey] = url
77-
}
78-
}
79-
80-
if !extraUserInfo.isEmpty {
81-
for (k, v) in extraUserInfo {
82-
userInfo[k] = v
83-
}
84-
}
85-
86-
return CocoaError(code, userInfo: userInfo)
87-
}
88-
8920
internal func logFileIOErrno(_ err: Int32, at place: String) {
9021
#if FOUNDATION_FRAMEWORK && !os(bridgeOS)
9122
let errnoDesc = String(cString: strerror(err))

Sources/FoundationEssentials/Data/Data+Reading.swift

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ internal func readBytesFromFile(path inPath: PathOrURL, reportProgress: Bool, ma
180180
}
181181

182182
guard fd >= 0 else {
183-
throw fileReadingOrWritingError(posixErrno: errno, path: inPath, reading: true)
183+
throw CocoaError.errorWithFilePath(inPath, errno: errno, reading: true)
184184
}
185185

186186
defer {
@@ -198,13 +198,13 @@ internal func readBytesFromFile(path inPath: PathOrURL, reportProgress: Bool, ma
198198
let err = fstat(fd, &filestat)
199199

200200
guard err == 0 else {
201-
throw fileReadingOrWritingError(posixErrno: errno, path: inPath, reading: true)
201+
throw CocoaError.errorWithFilePath(inPath, errno: errno, reading: true)
202202
}
203203

204204
// The following check is valid for 64-bit platforms.
205205
if filestat.st_size > Int.max {
206206
// We cannot hold this in `Data`, which uses Int as its count.
207-
throw fileReadingOrWritingError(posixErrno: EFBIG, path: inPath, reading: true)
207+
throw CocoaError.errorWithFilePath(inPath, errno: EFBIG, reading: true)
208208
}
209209

210210
let fileSize = min(Int(clamping: filestat.st_size), maxLength ?? Int.max)
@@ -219,17 +219,17 @@ internal func readBytesFromFile(path inPath: PathOrURL, reportProgress: Bool, ma
219219
if fileType != S_IFREG {
220220
// EACCES is still an odd choice, but at least we have a better error for directories.
221221
let code = (fileType == S_IFDIR) ? EISDIR : EACCES
222-
throw fileReadingOrWritingError(posixErrno: code, path: inPath, reading: true)
222+
throw CocoaError.errorWithFilePath(inPath, errno: code, reading: true)
223223
}
224224

225225
if fileSize < 0 {
226-
throw fileReadingOrWritingError(posixErrno: ENOMEM, path: inPath, reading: true)
226+
throw CocoaError.errorWithFilePath(inPath, errno: ENOMEM, reading: true)
227227
}
228228

229229
#if _pointerBitWidth(_32)
230230
// Refuse to do more than 2 GB on 32-bit platforms
231231
if fileSize > SSIZE_MAX {
232-
throw fileReadingOrWritingError(posixErrno: EFBIG, path: inPath, reading: true)
232+
throw CocoaError.errorWithFilePath(inPath, errno: EFBIG, reading: true)
233233
}
234234
#endif
235235

@@ -243,11 +243,11 @@ internal func readBytesFromFile(path inPath: PathOrURL, reportProgress: Bool, ma
243243
} else if shouldMap {
244244
#if !NO_FILESYSTEM
245245
guard let bytes = mmap(nil, Int(fileSize), PROT_READ, MAP_PRIVATE, fd, 0) else {
246-
throw fileReadingOrWritingError(posixErrno: errno, path: inPath, reading: true)
246+
throw CocoaError.errorWithFilePath(inPath, errno: errno, reading: true)
247247
}
248248

249249
guard bytes != MAP_FAILED else {
250-
throw fileReadingOrWritingError(posixErrno: errno, path: inPath, reading: true)
250+
throw CocoaError.errorWithFilePath(inPath, errno: errno, reading: true)
251251
}
252252

253253
// Using bytes as the unit in this case doesn't really make any sense, since the amount of work required for mmap isn't meanginfully proportional to the size being mapped.
@@ -262,7 +262,7 @@ internal func readBytesFromFile(path inPath: PathOrURL, reportProgress: Bool, ma
262262
} else {
263263
// We've verified above that fileSize will fit in `Int`
264264
guard let bytes = malloc(Int(fileSize)) else {
265-
throw fileReadingOrWritingError(posixErrno: ENOMEM, path: inPath, reading: true)
265+
throw CocoaError.errorWithFilePath(inPath, errno: ENOMEM, reading: true)
266266
}
267267

268268
localProgress?.becomeCurrent(withPendingUnitCount: Int64(fileSize))
@@ -323,9 +323,10 @@ private func readBytesFromFileDescriptor(_ fd: Int32, path: PathOrURL, buffer in
323323
} while numBytesRead < 0 && errno == EINTR
324324

325325
if numBytesRead < 0 {
326-
logFileIOErrno(errno, at: "read")
326+
let errNum = errno
327+
logFileIOErrno(errNum, at: "read")
327328
// The read failed
328-
throw fileReadingOrWritingError(posixErrno: errno, path: path, reading: true)
329+
throw CocoaError.errorWithFilePath(path, errno: errNum, reading: true)
329330
} else if numBytesRead == 0 {
330331
// Getting zero here is weird, since it may imply unexpected end of file... If we do, return the number of bytes read so far (which is compatible with the way read() would work with just one call).
331332
break

Sources/FoundationEssentials/Data/Data+Writing.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ private func createTemporaryFile(at destinationPath: String, inPath: PathOrURL,
161161
// The warning diligently tells us we shouldn't be using mktemp() because blindly opening the returned path opens us up to a TOCTOU race. However, in this case, we're being careful by doing O_CREAT|O_EXCL and repeating, just like the implementation of mkstemp.
162162
// Furthermore, we can't compatibly switch to mkstemp() until we have the ability to set fchmod correctly, which requires the ability to query the current umask, which we don't have. (22033100)
163163
guard mktemp(templateFileSystemRep) != nil else {
164-
throw fileReadingOrWritingError(posixErrno: errno, path: inPath, reading: false)
164+
throw CocoaError.errorWithFilePath(inPath, errno: errno, reading: false)
165165
}
166166

167167
let flags: Int32 = O_CREAT | O_EXCL | O_RDWR
@@ -173,7 +173,7 @@ private func createTemporaryFile(at destinationPath: String, inPath: PathOrURL,
173173

174174
// If the file exists, we repeat. Otherwise throw the error.
175175
if errno != EEXIST {
176-
throw fileReadingOrWritingError(posixErrno: errno, path: inPath, reading: false)
176+
throw CocoaError.errorWithFilePath(inPath, errno: errno, reading: false)
177177
}
178178

179179
// Try again
@@ -211,7 +211,7 @@ private func createProtectedTemporaryFile(at destinationPath: String, inPath: Pa
211211
return (fd, auxFile, temporaryDirectoryPath)
212212
} else {
213213
cleanupTemporaryDirectory(at: temporaryDirectoryPath)
214-
throw fileReadingOrWritingError(posixErrno: errno, path: inPath, reading: false)
214+
throw CocoaError.errorWithFilePath(inPath, errno: errno, reading: false)
215215
}
216216
}
217217
}
@@ -233,14 +233,14 @@ private func write(data: Data, toFileDescriptor fd: Int32, path: PathOrURL, pare
233233
if count > 0 {
234234
let result = try writeToFileDescriptorWithProgress(fd, data: region, reportProgress: parentProgress != nil)
235235
if result != count {
236-
throw fileReadingOrWritingError(posixErrno: errno, path: path, reading: false)
236+
throw CocoaError.errorWithFilePath(path, errno: errno, reading: false)
237237
}
238238
}
239239
}
240240

241241
if !data.isEmpty {
242242
if fsync(fd) < 0 {
243-
throw fileReadingOrWritingError(posixErrno: errno, path: path, reading: false)
243+
throw CocoaError.errorWithFilePath(path, errno: errno, reading: false)
244244
}
245245
}
246246
}
@@ -306,7 +306,7 @@ private func writeDataToFileAux(path inPath: PathOrURL, data: Data, options: Dat
306306
}
307307
}
308308
} else if (errno != ENOENT) && (errno != ENAMETOOLONG) {
309-
throw fileReadingOrWritingError(posixErrno: errno, path: inPath, reading: false)
309+
throw CocoaError.errorWithFilePath(inPath, errno: errno, reading: false)
310310
}
311311
#else
312312
let newPath = inPath.path
@@ -332,7 +332,7 @@ private func writeDataToFileAux(path inPath: PathOrURL, data: Data, options: Dat
332332
let savedErrno = errno
333333
// Cleanup temporary directory
334334
cleanupTemporaryDirectory(at: temporaryDirectoryPath)
335-
throw fileReadingOrWritingError(posixErrno: savedErrno, path: inPath, reading: false)
335+
throw CocoaError.errorWithFilePath(inPath, errno: savedErrno, reading: false)
336336
}
337337

338338
defer { close(fd) }
@@ -353,7 +353,7 @@ private func writeDataToFileAux(path inPath: PathOrURL, data: Data, options: Dat
353353
if parentProgress?.isCancelled ?? false {
354354
throw CocoaError(.userCancelled)
355355
} else {
356-
throw fileReadingOrWritingError(posixErrno: 0, path: inPath, reading: false)
356+
throw CocoaError.errorWithFilePath(inPath, errno: 0, reading: false)
357357
}
358358
}
359359

@@ -398,7 +398,7 @@ private func writeDataToFileAux(path inPath: PathOrURL, data: Data, options: Dat
398398
unlink(auxPathFileSystemRep)
399399
cleanupTemporaryDirectory(at: temporaryDirectoryPath)
400400
cleanupTemporaryDirectory(at: temporaryDirectoryPath2)
401-
throw fileReadingOrWritingError(posixErrno: errno, path: inPath, reading: false)
401+
throw CocoaError.errorWithFilePath(inPath, errno: errno, reading: false)
402402
}
403403

404404
unlink(auxPath2FileSystemRep)
@@ -415,7 +415,7 @@ private func writeDataToFileAux(path inPath: PathOrURL, data: Data, options: Dat
415415
} else {
416416
unlink(auxPathFileSystemRep)
417417
cleanupTemporaryDirectory(at: temporaryDirectoryPath)
418-
throw fileReadingOrWritingError(posixErrno: errno, path: inPath, reading: false)
418+
throw CocoaError.errorWithFilePath(inPath, errno: errno, reading: false)
419419
}
420420
}
421421

@@ -467,7 +467,7 @@ private func writeDataToFileNoAux(path inPath: PathOrURL, data: Data, options: D
467467

468468
guard fd >= 0 else {
469469
let savedErrno = errno
470-
throw fileReadingOrWritingError(posixErrno: savedErrno, path: inPath, reading: false)
470+
throw CocoaError.errorWithFilePath(inPath, errno: savedErrno, reading: false)
471471
}
472472

473473
defer { close(fd) }
@@ -483,7 +483,7 @@ private func writeDataToFileNoAux(path inPath: PathOrURL, data: Data, options: D
483483
unlink(pathFileSystemRep)
484484
throw CocoaError(.userCancelled)
485485
} else {
486-
throw fileReadingOrWritingError(posixErrno: 0, path: inPath, reading: false)
486+
throw CocoaError.errorWithFilePath(inPath, errno: 0, reading: false)
487487
}
488488
}
489489

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
#if FOUNDATION_FRAMEWORK
13+
@_implementationOnly import _ForSwiftFoundation
14+
#endif
15+
16+
#if canImport(Darwin)
17+
import Darwin
18+
#elseif canImport(Glibc)
19+
import Glibc
20+
#endif
21+
22+
extension CocoaError.Code {
23+
fileprivate init(fileErrno: Int32, reading: Bool) {
24+
self = if reading {
25+
switch fileErrno {
26+
case EFBIG: .fileReadTooLarge
27+
case ENOENT: .fileReadNoSuchFile
28+
case EPERM, EACCES: .fileReadNoPermission
29+
case ENAMETOOLONG: .fileReadInvalidFileName
30+
default: .fileReadUnknown
31+
}
32+
} else {
33+
switch fileErrno {
34+
case ENOENT: .fileNoSuchFile
35+
case EPERM, EACCES: .fileWriteNoPermission
36+
case ENAMETOOLONG: .fileWriteInvalidFileName
37+
case EDQUOT, ENOSPC: .fileWriteOutOfSpace
38+
case EROFS: .fileWriteVolumeReadOnly
39+
case EEXIST: .fileWriteFileExists
40+
default: .fileWriteUnknown
41+
}
42+
}
43+
}
44+
}
45+
46+
extension Dictionary {
47+
func setting(_ key: Key, to value: Value) -> Self {
48+
var copy = self
49+
copy[key] = value
50+
return copy
51+
}
52+
}
53+
54+
extension CocoaError {
55+
// MARK: Error Creation with CocoaError.Code
56+
57+
static func errorWithFilePath(_ code: CocoaError.Code, _ path: String) -> CocoaError {
58+
CocoaError(code, userInfo: [NSFilePathErrorKey : path])
59+
}
60+
61+
static func errorWithFilePath(_ code: CocoaError.Code, _ url: URL) -> CocoaError {
62+
CocoaError(code, userInfo: [NSURLErrorKey : url])
63+
}
64+
65+
// MARK: Error Creation with errno
66+
67+
private static func _errorWithErrno(_ errno: Int32, reading: Bool, variant: String?, userInfo: [String : AnyHashable]) -> CocoaError {
68+
guard let code = POSIXError.Code(rawValue: errno) else {
69+
fatalError("Invalid posix errno \(errno)")
70+
}
71+
72+
var userInfo = userInfo.setting(NSUnderlyingErrorKey, to: POSIXError(code))
73+
if let variant {
74+
userInfo[NSUserStringVariantErrorKey] = [variant]
75+
}
76+
77+
return CocoaError(Code(fileErrno: errno, reading: reading), userInfo: userInfo)
78+
}
79+
80+
static func errorWithFilePath(_ pathOrURL: PathOrURL, errno: Int32, reading: Bool, variant: String? = nil, additionalUserInfo: [String : AnyHashable] = [:]) -> CocoaError {
81+
switch pathOrURL {
82+
case .path(let path):
83+
return Self.errorWithFilePath(path, errno: errno, reading: reading, variant: variant, additionalUserInfo: additionalUserInfo)
84+
case .url(let url):
85+
return Self.errorWithFilePath(url, errno: errno, reading: reading, variant: variant, additionalUserInfo: additionalUserInfo)
86+
}
87+
}
88+
89+
static func errorWithFilePath(_ path: String, errno: Int32, reading: Bool, variant: String? = nil, additionalUserInfo: [String : AnyHashable] = [:]) -> CocoaError {
90+
Self._errorWithErrno(
91+
errno,
92+
reading: reading,
93+
variant: variant,
94+
userInfo: additionalUserInfo.setting(NSFilePathErrorKey, to: path)
95+
)
96+
}
97+
98+
static func errorWithFilePath(_ url: URL, errno: Int32, reading: Bool, variant: String? = nil, additionalUserInfo: [String : AnyHashable] = [:]) -> CocoaError {
99+
Self._errorWithErrno(
100+
errno,
101+
reading: reading,
102+
variant: variant,
103+
userInfo: additionalUserInfo.setting(NSURLErrorKey, to: url)
104+
)
105+
}
106+
107+
static func errorWithFilePath(_ path: String? = nil, osStatus: Int, reading: Bool, variant: String? = nil) -> CocoaError {
108+
// Do more or less what _NSErrorWithFilePathAndErrno() does, except for OSStatus values
109+
let errorCode: CocoaError.Code = switch (reading, osStatus) {
110+
case (true, -43 /*fnfErr*/), (true, -120 /*dirNFErr*/): .fileReadNoSuchFile
111+
case (true, -5000 /*afpAccessDenied*/): .fileReadNoPermission
112+
case (true, _): .fileReadUnknown
113+
case (false, -34 /*dskFulErr*/), (false, -1425 /*errFSQuotaExceeded*/): .fileWriteOutOfSpace
114+
case (false, -45 /*fLckdErr*/), (false, -5000 /*afpAccessDenied*/): .fileWriteNoPermission
115+
case (false, _): .fileWriteUnknown
116+
}
117+
#if FOUNDATION_FRAMEWORK
118+
var userInfo: [String : AnyHashable] = [
119+
NSUnderlyingErrorKey : NSError(domain: NSOSStatusErrorDomain, code: osStatus)
120+
]
121+
#else
122+
var userInfo: [String : AnyHashable] = [:]
123+
#endif
124+
if let path {
125+
userInfo[NSFilePathErrorKey] = path
126+
}
127+
if let variant {
128+
userInfo[NSUserStringVariantErrorKey] = [variant]
129+
}
130+
return CocoaError(errorCode, userInfo: userInfo)
131+
}
132+
}

0 commit comments

Comments
 (0)