Skip to content

Commit 1734c7b

Browse files
authored
Improve temporary file location for atomic writes (#1477)
* (156721664) Temporary files for atomic writes should use _amkrtemp instead of mktemp when applicable * Fix build failure
1 parent 4cdbaf6 commit 1734c7b

File tree

2 files changed

+69
-32
lines changed

2 files changed

+69
-32
lines changed

Sources/FoundationEssentials/Data/Data+Writing.swift

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,37 @@ private func createTemporaryFile(at destinationPath: String, inPath: PathOrURL,
157157

158158
let pidString = String(ProcessInfo.processInfo.processIdentifier, radix: 16, uppercase: true)
159159
let template = directoryPath + prefix + pidString + ".XXXXXX"
160-
var count = 0
161160
let maxCount = 7
162-
repeat {
161+
for _ in 0 ..< maxCount {
162+
#if FOUNDATION_FRAMEWORK
163+
let (sandboxResult, amkrErrno) = inPath.withFileSystemRepresentation { inPathFileSystemRep -> ((Int32, String)?, Int32?) in
164+
guard let inPathFileSystemRep else {
165+
return (nil, nil)
166+
}
167+
// First, try _amkrtemp to carry over any sandbox extensions for inPath to the temporary file (even if the application isn't sandboxed)
168+
guard let uniqueTempFile = _amkrtemp(inPathFileSystemRep) else {
169+
return (nil, errno)
170+
}
171+
defer { free(uniqueTempFile) }
172+
let fd = openFileDescriptorProtected(path: uniqueTempFile, flags: O_CREAT | O_EXCL | O_RDWR, options: options)
173+
if fd >= 0 {
174+
// Got a good fd
175+
return ((fd, String(cString: uniqueTempFile)), nil)
176+
}
177+
return (nil, errno)
178+
}
179+
180+
// If _amkrtemp succeeded, return its result
181+
if let sandboxResult {
182+
return sandboxResult
183+
}
184+
// If _amkrtemp failed with EEXIST, just retry
185+
if amkrErrno == EEXIST {
186+
continue
187+
}
188+
// Otherwise, fall through to mktemp below
189+
#endif
190+
163191
let result = try template.withMutableFileSystemRepresentation { templateFileSystemRep -> (Int32, String)? in
164192
guard let templateFileSystemRep else {
165193
throw CocoaError(.fileWriteInvalidFileName)
@@ -187,7 +215,12 @@ private func createTemporaryFile(at destinationPath: String, inPath: PathOrURL,
187215

188216
// If the file exists, we repeat. Otherwise throw the error.
189217
if errno != EEXIST {
190-
throw CocoaError.errorWithFilePath(inPath, errno: errno, reading: false, variant: variant)
218+
#if FOUNDATION_FRAMEWORK
219+
let debugDescription = "Creating a temporary file via mktemp failed. Creating the temporary file via _amkrtemp previously also failed with errno \(amkrErrno)"
220+
#else
221+
let debugDescription: String? = nil
222+
#endif
223+
throw CocoaError.errorWithFilePath(inPath, errno: errno, reading: false, variant: variant, debugDescription: debugDescription)
191224
}
192225

193226
// Try again
@@ -196,14 +229,10 @@ private func createTemporaryFile(at destinationPath: String, inPath: PathOrURL,
196229

197230
if let result {
198231
return result
199-
} else {
200-
count += 1
201-
if count > maxCount {
202-
// Prevent an infinite loop; even if the error is obscure
203-
throw CocoaError(.fileWriteUnknown)
204-
}
205232
}
206-
} while true
233+
}
234+
// We hit max count, prevent an infinite loop; even if the error is obscure
235+
throw CocoaError(.fileWriteUnknown)
207236
#endif // os(WASI)
208237
}
209238

Sources/FoundationEssentials/Error/CocoaError+FilePath.swift

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ import WinSDK
3131
// MARK: - Error Creation with CocoaError.Code
3232

3333
extension CocoaError {
34-
static func errorWithFilePath(_ code: CocoaError.Code, _ path: String, variant: String? = nil, source: String? = nil, destination: String? = nil) -> CocoaError {
35-
CocoaError(code, path: path, variant: variant, source: source, destination: destination)
34+
static func errorWithFilePath(_ code: CocoaError.Code, _ path: String, variant: String? = nil, source: String? = nil, destination: String? = nil, debugDescription: String? = nil) -> CocoaError {
35+
CocoaError(code, path: path, variant: variant, source: source, destination: destination, debugDescription: debugDescription)
3636
}
3737

38-
static func errorWithFilePath(_ code: CocoaError.Code, _ url: URL, variant: String? = nil, source: String? = nil, destination: String? = nil) -> CocoaError {
39-
CocoaError(code, url: url, variant: variant, source: source, destination: destination)
38+
static func errorWithFilePath(_ code: CocoaError.Code, _ url: URL, variant: String? = nil, source: String? = nil, destination: String? = nil, debugDescription: String? = nil) -> CocoaError {
39+
CocoaError(code, url: url, variant: variant, source: source, destination: destination, debugDescription: debugDescription)
4040
}
4141
}
4242

@@ -81,21 +81,21 @@ extension POSIXError {
8181
}
8282

8383
extension CocoaError {
84-
static func errorWithFilePath(_ pathOrURL: PathOrURL, errno: Int32, reading: Bool, variant: String? = nil, source: String? = nil, destination: String? = nil) -> CocoaError {
84+
static func errorWithFilePath(_ pathOrURL: PathOrURL, errno: Int32, reading: Bool, variant: String? = nil, source: String? = nil, destination: String? = nil, debugDescription: String? = nil) -> CocoaError {
8585
switch pathOrURL {
8686
case .path(let path):
87-
return Self.errorWithFilePath(path, errno: errno, reading: reading, variant: variant, source: source, destination: destination)
87+
return Self.errorWithFilePath(path, errno: errno, reading: reading, variant: variant, source: source, destination: destination, debugDescription: debugDescription)
8888
case .url(let url):
89-
return Self.errorWithFilePath(url, errno: errno, reading: reading, variant: variant, source: source, destination: destination)
89+
return Self.errorWithFilePath(url, errno: errno, reading: reading, variant: variant, source: source, destination: destination, debugDescription: debugDescription)
9090
}
9191
}
9292

93-
static func errorWithFilePath(_ path: String, errno: Int32, reading: Bool, variant: String? = nil, source: String? = nil, destination: String? = nil) -> CocoaError {
94-
CocoaError(Code(fileErrno: errno, reading: reading), path: path, underlying: POSIXError(errno: errno), variant: variant, source: source, destination: destination)
93+
static func errorWithFilePath(_ path: String, errno: Int32, reading: Bool, variant: String? = nil, source: String? = nil, destination: String? = nil, debugDescription: String? = nil) -> CocoaError {
94+
CocoaError(Code(fileErrno: errno, reading: reading), path: path, underlying: POSIXError(errno: errno), variant: variant, source: source, destination: destination, debugDescription: debugDescription)
9595
}
9696

97-
static func errorWithFilePath(_ url: URL, errno: Int32, reading: Bool, variant: String? = nil, source: String? = nil, destination: String? = nil) -> CocoaError {
98-
CocoaError(Code(fileErrno: errno, reading: reading), url: url, underlying: POSIXError(errno: errno), variant: variant, source: source, destination: destination)
97+
static func errorWithFilePath(_ url: URL, errno: Int32, reading: Bool, variant: String? = nil, source: String? = nil, destination: String? = nil, debugDescription: String? = nil) -> CocoaError {
98+
CocoaError(Code(fileErrno: errno, reading: reading), url: url, underlying: POSIXError(errno: errno), variant: variant, source: source, destination: destination, debugDescription: debugDescription)
9999
}
100100
}
101101

@@ -144,18 +144,18 @@ extension CocoaError.Code {
144144
}
145145

146146
extension CocoaError {
147-
static func errorWithFilePath(_ path: PathOrURL, win32 dwError: DWORD, reading: Bool) -> CocoaError {
147+
static func errorWithFilePath(_ path: PathOrURL, win32 dwError: DWORD, reading: Bool, debugDescription: String? = nil) -> CocoaError {
148148
switch path {
149149
case let .path(path):
150-
return CocoaError(.init(win32: dwError, reading: reading, emptyPath: path.isEmpty), path: path, underlying: Win32Error(dwError))
150+
return CocoaError(.init(win32: dwError, reading: reading, emptyPath: path.isEmpty), path: path, underlying: Win32Error(dwError), debugDescription: debugDescription)
151151
case let .url(url):
152152
let pathStr = url.withUnsafeFileSystemRepresentation { String(cString: $0!) }
153-
return CocoaError(.init(win32: dwError, reading: reading, emptyPath: pathStr.isEmpty), path: pathStr, url: url, underlying: Win32Error(dwError))
153+
return CocoaError(.init(win32: dwError, reading: reading, emptyPath: pathStr.isEmpty), path: pathStr, url: url, underlying: Win32Error(dwError), debugDescription: debugDescription)
154154
}
155155
}
156156

157-
static func errorWithFilePath(_ path: String? = nil, win32 dwError: DWORD, reading: Bool, variant: String? = nil, source: String? = nil, destination: String? = nil) -> CocoaError {
158-
return CocoaError(.init(win32: dwError, reading: reading, emptyPath: path?.isEmpty), path: path, underlying: Win32Error(dwError), variant: variant, source: source, destination: destination)
157+
static func errorWithFilePath(_ path: String? = nil, win32 dwError: DWORD, reading: Bool, variant: String? = nil, source: String? = nil, destination: String? = nil, debugDescription: String? = nil) -> CocoaError {
158+
return CocoaError(.init(win32: dwError, reading: reading, emptyPath: path?.isEmpty), path: path, underlying: Win32Error(dwError), variant: variant, source: source, destination: destination, debugDescription: debugDescription)
159159
}
160160
}
161161
#endif
@@ -190,7 +190,8 @@ extension CocoaError {
190190
underlying: (some Error)? = Optional<CocoaError>.none,
191191
variant: String? = nil,
192192
source: String? = nil,
193-
destination: String? = nil
193+
destination: String? = nil,
194+
debugDescription: String? = nil
194195
) {
195196
self.init(
196197
code,
@@ -199,7 +200,8 @@ extension CocoaError {
199200
underlying: underlying,
200201
variant: variant,
201202
source: source,
202-
destination: destination
203+
destination: destination,
204+
debugDescription: debugDescription
203205
)
204206
}
205207

@@ -209,7 +211,8 @@ extension CocoaError {
209211
underlying: (some Error)? = Optional<CocoaError>.none,
210212
variant: String? = nil,
211213
source: String? = nil,
212-
destination: String? = nil
214+
destination: String? = nil,
215+
debugDescription: String? = nil
213216
) {
214217
self.init(
215218
code,
@@ -218,7 +221,8 @@ extension CocoaError {
218221
underlying: underlying,
219222
variant: variant,
220223
source: source,
221-
destination: destination
224+
destination: destination,
225+
debugDescription: debugDescription
222226
)
223227
}
224228

@@ -229,10 +233,11 @@ extension CocoaError {
229233
underlying: (some Error)? = Optional<CocoaError>.none,
230234
variant: String? = nil,
231235
source: String? = nil,
232-
destination: String? = nil
236+
destination: String? = nil,
237+
debugDescription: String? = nil
233238
) {
234239
#if FOUNDATION_FRAMEWORK
235-
self.init(_uncheckedNSError: NSError._cocoaError(withCode: code.rawValue, path: path, url: url, underlying: underlying, variant: variant, source: source, destination: destination) as NSError)
240+
self.init(_uncheckedNSError: NSError._cocoaError(withCode: code.rawValue, path: path, url: url, underlying: underlying, variant: variant, source: source, destination: destination, debugDescription: debugDescription) as NSError)
236241
#else
237242
var userInfo: [String : Any] = [:]
238243
if let path {
@@ -253,6 +258,9 @@ extension CocoaError {
253258
if let variant {
254259
userInfo[NSUserStringVariantErrorKey] = [variant]
255260
}
261+
if let debugDescription {
262+
userInfo[NSDebugDescriptionErrorKey] = debugDescription
263+
}
256264

257265
self.init(code, userInfo: userInfo)
258266
#endif

0 commit comments

Comments
 (0)