Skip to content

Commit 68dd6d8

Browse files
authored
[wasm] Make FileManager.createFile() work on WASI (#992)
* [wasm] Make `FileManager.createFile()` work on WASI fixes swiftwasm/swift#5593 `FileManager.createFile()` currently doesn't work on WASI because it requires `.atomic`, it requires creating a temporary file, and it isn't suppported on WASI. So I have fixed that by removing the `.atomic` requirement only on WASI. * [wasm] Make `Data.WritingOptions.atomic` unavailable on WASI `writeToFileAux`, `createTemporaryFile`, and `createProtectedTemporaryFile` also become unavailable on WASI.
1 parent 95a222f commit 68dd6d8

File tree

6 files changed

+51
-2
lines changed

6 files changed

+51
-2
lines changed

Benchmarks/Benchmarks/DataIO/BenchmarkDataIO.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,11 @@ let benchmarks = {
8888
try data.write(to: testPath)
8989
}
9090

91+
#if !os(WASI) // atomic writing is unavailable on WASI
9192
Benchmark("write-regularFile-atomic", configuration: .cleanupTestPathConfig) { benchmark in
9293
try data.write(to: testPath, options: .atomic)
9394
}
95+
#endif
9496

9597
Benchmark("write-regularFile-alreadyExists",
9698
configuration: .init(
@@ -103,6 +105,7 @@ let benchmarks = {
103105
try? data.write(to: testPath)
104106
}
105107

108+
#if !os(WASI) // atomic writing is unavailable on WASI
106109
Benchmark("write-regularFile-alreadyExists-atomic",
107110
configuration: .init(
108111
setup: {
@@ -113,6 +116,7 @@ let benchmarks = {
113116
) { benchmark in
114117
try? data.write(to: testPath, options: .atomic)
115118
}
119+
#endif
116120

117121
Benchmark("read-regularFile",
118122
configuration: .init(

Sources/FoundationEssentials/Data/Data+Writing.swift

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ private func cleanupTemporaryDirectory(at inPath: String?) {
142142
}
143143

144144
/// Caller is responsible for calling `close` on the `Int32` file descriptor.
145+
#if os(WASI)
146+
@available(*, unavailable, message: "WASI does not have temporary directories")
147+
#endif
145148
private func createTemporaryFile(at destinationPath: String, inPath: PathOrURL, prefix: String, options: Data.WritingOptions, variant: String? = nil) throws -> (Int32, String) {
146149
#if os(WASI)
147150
// WASI does not have temp directories
@@ -206,7 +209,14 @@ private func createTemporaryFile(at destinationPath: String, inPath: PathOrURL,
206209

207210
/// Returns `(file descriptor, temporary file path, temporary directory path)`
208211
/// Caller is responsible for calling `close` on the `Int32` file descriptor and calling `cleanupTemporaryDirectory` on the temporary directory path. The temporary directory path may be nil, if it does not need to be cleaned up.
212+
#if os(WASI)
213+
@available(*, unavailable, message: "WASI does not have temporary directories")
214+
#endif
209215
private func createProtectedTemporaryFile(at destinationPath: String, inPath: PathOrURL, options: Data.WritingOptions, variant: String? = nil) throws -> (Int32, String, String?) {
216+
#if os(WASI)
217+
// WASI does not have temp directories
218+
throw CocoaError(.featureUnsupported)
219+
#else
210220
#if FOUNDATION_FRAMEWORK
211221
if _foundation_sandbox_check(getpid(), nil) != 0 {
212222
// Convert the path back into a string
@@ -248,6 +258,7 @@ private func createProtectedTemporaryFile(at destinationPath: String, inPath: Pa
248258
let temporaryDirectoryPath = destinationPath.deletingLastPathComponent()
249259
let (fd, auxFile) = try createTemporaryFile(at: temporaryDirectoryPath, inPath: inPath, prefix: ".dat.nosync", options: options, variant: variant)
250260
return (fd, auxFile, nil)
261+
#endif // os(WASI)
251262
}
252263

253264
private func write(buffer: UnsafeRawBufferPointer, toFileDescriptor fd: Int32, path: PathOrURL, parentProgress: Progress?) throws {
@@ -322,15 +333,26 @@ internal func writeToFile(path inPath: PathOrURL, data: Data, options: Data.Writ
322333
}
323334

324335
internal func writeToFile(path inPath: PathOrURL, buffer: UnsafeRawBufferPointer, options: Data.WritingOptions, attributes: [String : Data] = [:], reportProgress: Bool = false) throws {
336+
#if os(WASI) // `.atomic` is unavailable on WASI
337+
try writeToFileNoAux(path: inPath, buffer: buffer, options: options, attributes: attributes, reportProgress: reportProgress)
338+
#else
325339
if options.contains(.atomic) {
326340
try writeToFileAux(path: inPath, buffer: buffer, options: options, attributes: attributes, reportProgress: reportProgress)
327341
} else {
328342
try writeToFileNoAux(path: inPath, buffer: buffer, options: options, attributes: attributes, reportProgress: reportProgress)
329343
}
344+
#endif
330345
}
331346

332347
/// Create a new file out of `Data` at a path, using atomic writing.
348+
#if os(WASI)
349+
@available(*, unavailable, message: "atomic writing is unavailable in WASI because temporary files are not supported")
350+
#endif
333351
private func writeToFileAux(path inPath: PathOrURL, buffer: UnsafeRawBufferPointer, options: Data.WritingOptions, attributes: [String : Data], reportProgress: Bool) throws {
352+
#if os(WASI)
353+
// `.atomic` is unavailable on WASI
354+
throw CocoaError(.featureUnsupported)
355+
#else
334356
assert(options.contains(.atomic))
335357

336358
// TODO: Somehow avoid copying back and forth to a String to hold the path
@@ -503,7 +525,6 @@ private func writeToFileAux(path inPath: PathOrURL, buffer: UnsafeRawBufferPoint
503525

504526
cleanupTemporaryDirectory(at: temporaryDirectoryPath)
505527

506-
#if !os(WASI) // WASI does not support fchmod for now
507528
if let mode {
508529
// Try to change the mode if the path has not changed. Do our best, but don't report an error.
509530
#if FOUNDATION_FRAMEWORK
@@ -527,16 +548,18 @@ private func writeToFileAux(path inPath: PathOrURL, buffer: UnsafeRawBufferPoint
527548
fchmod(fd, mode)
528549
#endif
529550
}
530-
#endif // os(WASI)
531551
}
532552
}
533553
}
534554
#endif
555+
#endif // os(WASI)
535556
}
536557

537558
/// Create a new file out of `Data` at a path, not using atomic writing.
538559
private func writeToFileNoAux(path inPath: PathOrURL, buffer: UnsafeRawBufferPointer, options: Data.WritingOptions, attributes: [String : Data], reportProgress: Bool) throws {
560+
#if !os(WASI) // `.atomic` is unavailable on WASI
539561
assert(!options.contains(.atomic))
562+
#endif
540563

541564
#if os(Windows)
542565
try inPath.path.withNTPathRepresentation { pwszPath in

Sources/FoundationEssentials/Data/Data.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2099,6 +2099,9 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect
20992099
public init(rawValue: UInt) { self.rawValue = rawValue }
21002100

21012101
/// An option to write data to an auxiliary file first and then replace the original file with the auxiliary file when the write completes.
2102+
#if os(WASI)
2103+
@available(*, unavailable, message: "atomic writing is unavailable in WASI because temporary files are not supported")
2104+
#endif
21022105
public static let atomic = WritingOptions(rawValue: 1 << 0)
21032106

21042107
/// An option that attempts to write data to a file and fails with an error if the destination file already exists.
@@ -2474,9 +2477,11 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect
24742477
/// - parameter options: Options for writing the data. Default value is `[]`.
24752478
/// - throws: An error in the Cocoa domain, if there is an error writing to the `URL`.
24762479
public func write(to url: URL, options: Data.WritingOptions = []) throws {
2480+
#if !os(WASI) // `.atomic` is unavailable on WASI
24772481
if options.contains(.withoutOverwriting) && options.contains(.atomic) {
24782482
fatalError("withoutOverwriting is not supported with atomic")
24792483
}
2484+
#endif
24802485

24812486
guard url.isFileURL else {
24822487
throw CocoaError(.fileWriteUnsupportedScheme)

Sources/FoundationEssentials/FileManager/FileManager+Files.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,9 @@ extension _FileManagerImpl {
260260
}
261261
attr?[.protectionKey] = nil
262262
}
263+
#elseif os(WASI)
264+
// `.atomic` is unavailable on WASI
265+
let opts: Data.WritingOptions = []
263266
#else
264267
let opts = Data.WritingOptions.atomic
265268
#endif

Sources/FoundationEssentials/String/String+IO.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,12 @@ extension StringProtocol {
433433
attributes = [:]
434434
}
435435

436+
#if os(WASI)
437+
guard !useAuxiliaryFile else { throw CocoaError(.featureUnsupported) }
438+
let options : Data.WritingOptions = []
439+
#else
436440
let options : Data.WritingOptions = useAuxiliaryFile ? [.atomic] : []
441+
#endif
437442

438443
try writeToFile(path: .path(String(path)), data: data, options: options, attributes: attributes, reportProgress: false)
439444
}
@@ -451,7 +456,12 @@ extension StringProtocol {
451456
attributes = [:]
452457
}
453458

459+
#if os(WASI)
460+
guard !useAuxiliaryFile else { throw CocoaError(.featureUnsupported) }
461+
let options : Data.WritingOptions = []
462+
#else
454463
let options : Data.WritingOptions = useAuxiliaryFile ? [.atomic] : []
464+
#endif
455465

456466
try writeToFile(path: .url(url), data: data, options: options, attributes: attributes, reportProgress: false)
457467
}

Tests/FoundationEssentialsTests/DataIOTests.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ class DataIOTests : XCTestCase {
9393

9494
// Atomic writing is a very different code path
9595
func test_readWriteAtomic() throws {
96+
#if os(WASI)
97+
try XCTSkip("atomic writing is not supported on WASI")
98+
#else
9699
let url = testURL()
97100
// Perform an atomic write to a file that does not exist
98101
try writeAndVerifyTestData(to: url, writeOptions: [.atomic])
@@ -101,6 +104,7 @@ class DataIOTests : XCTestCase {
101104
try writeAndVerifyTestData(to: url, writeOptions: [.atomic])
102105

103106
cleanup(at: url)
107+
#endif
104108
}
105109

106110
func test_readWriteMapped() throws {

0 commit comments

Comments
 (0)