diff --git a/Benchmarks/Benchmarks/DataIO/BenchmarkDataIO.swift b/Benchmarks/Benchmarks/DataIO/BenchmarkDataIO.swift index a99af1197..a0a24ebce 100644 --- a/Benchmarks/Benchmarks/DataIO/BenchmarkDataIO.swift +++ b/Benchmarks/Benchmarks/DataIO/BenchmarkDataIO.swift @@ -88,9 +88,11 @@ let benchmarks = { try data.write(to: testPath) } +#if !os(WASI) // atomic writing is unavailable on WASI Benchmark("write-regularFile-atomic", configuration: .cleanupTestPathConfig) { benchmark in try data.write(to: testPath, options: .atomic) } +#endif Benchmark("write-regularFile-alreadyExists", configuration: .init( @@ -103,6 +105,7 @@ let benchmarks = { try? data.write(to: testPath) } +#if !os(WASI) // atomic writing is unavailable on WASI Benchmark("write-regularFile-alreadyExists-atomic", configuration: .init( setup: { @@ -113,6 +116,7 @@ let benchmarks = { ) { benchmark in try? data.write(to: testPath, options: .atomic) } +#endif Benchmark("read-regularFile", configuration: .init( diff --git a/Sources/FoundationEssentials/Data/Data+Writing.swift b/Sources/FoundationEssentials/Data/Data+Writing.swift index fa05b2ca0..657f2ae2e 100644 --- a/Sources/FoundationEssentials/Data/Data+Writing.swift +++ b/Sources/FoundationEssentials/Data/Data+Writing.swift @@ -142,6 +142,9 @@ private func cleanupTemporaryDirectory(at inPath: String?) { } /// Caller is responsible for calling `close` on the `Int32` file descriptor. +#if os(WASI) +@available(*, unavailable, message: "WASI does not have temporary directories") +#endif private func createTemporaryFile(at destinationPath: String, inPath: PathOrURL, prefix: String, options: Data.WritingOptions, variant: String? = nil) throws -> (Int32, String) { #if os(WASI) // WASI does not have temp directories @@ -206,7 +209,14 @@ private func createTemporaryFile(at destinationPath: String, inPath: PathOrURL, /// Returns `(file descriptor, temporary file path, temporary directory path)` /// 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. +#if os(WASI) +@available(*, unavailable, message: "WASI does not have temporary directories") +#endif private func createProtectedTemporaryFile(at destinationPath: String, inPath: PathOrURL, options: Data.WritingOptions, variant: String? = nil) throws -> (Int32, String, String?) { +#if os(WASI) + // WASI does not have temp directories + throw CocoaError(.featureUnsupported) +#else #if FOUNDATION_FRAMEWORK if _foundation_sandbox_check(getpid(), nil) != 0 { // Convert the path back into a string @@ -248,6 +258,7 @@ private func createProtectedTemporaryFile(at destinationPath: String, inPath: Pa let temporaryDirectoryPath = destinationPath.deletingLastPathComponent() let (fd, auxFile) = try createTemporaryFile(at: temporaryDirectoryPath, inPath: inPath, prefix: ".dat.nosync", options: options, variant: variant) return (fd, auxFile, nil) +#endif // os(WASI) } 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 } internal func writeToFile(path inPath: PathOrURL, buffer: UnsafeRawBufferPointer, options: Data.WritingOptions, attributes: [String : Data] = [:], reportProgress: Bool = false) throws { +#if os(WASI) // `.atomic` is unavailable on WASI + try writeToFileNoAux(path: inPath, buffer: buffer, options: options, attributes: attributes, reportProgress: reportProgress) +#else if options.contains(.atomic) { try writeToFileAux(path: inPath, buffer: buffer, options: options, attributes: attributes, reportProgress: reportProgress) } else { try writeToFileNoAux(path: inPath, buffer: buffer, options: options, attributes: attributes, reportProgress: reportProgress) } +#endif } /// Create a new file out of `Data` at a path, using atomic writing. +#if os(WASI) +@available(*, unavailable, message: "atomic writing is unavailable in WASI because temporary files are not supported") +#endif private func writeToFileAux(path inPath: PathOrURL, buffer: UnsafeRawBufferPointer, options: Data.WritingOptions, attributes: [String : Data], reportProgress: Bool) throws { +#if os(WASI) + // `.atomic` is unavailable on WASI + throw CocoaError(.featureUnsupported) +#else assert(options.contains(.atomic)) // 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 cleanupTemporaryDirectory(at: temporaryDirectoryPath) -#if !os(WASI) // WASI does not support fchmod for now if let mode { // Try to change the mode if the path has not changed. Do our best, but don't report an error. #if FOUNDATION_FRAMEWORK @@ -527,16 +548,18 @@ private func writeToFileAux(path inPath: PathOrURL, buffer: UnsafeRawBufferPoint fchmod(fd, mode) #endif } -#endif // os(WASI) } } } #endif +#endif // os(WASI) } /// Create a new file out of `Data` at a path, not using atomic writing. private func writeToFileNoAux(path inPath: PathOrURL, buffer: UnsafeRawBufferPointer, options: Data.WritingOptions, attributes: [String : Data], reportProgress: Bool) throws { +#if !os(WASI) // `.atomic` is unavailable on WASI assert(!options.contains(.atomic)) +#endif #if os(Windows) try inPath.path.withNTPathRepresentation { pwszPath in diff --git a/Sources/FoundationEssentials/Data/Data.swift b/Sources/FoundationEssentials/Data/Data.swift index cc3322a47..178c7cfa3 100644 --- a/Sources/FoundationEssentials/Data/Data.swift +++ b/Sources/FoundationEssentials/Data/Data.swift @@ -2099,6 +2099,9 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect public init(rawValue: UInt) { self.rawValue = rawValue } /// An option to write data to an auxiliary file first and then replace the original file with the auxiliary file when the write completes. +#if os(WASI) + @available(*, unavailable, message: "atomic writing is unavailable in WASI because temporary files are not supported") +#endif public static let atomic = WritingOptions(rawValue: 1 << 0) /// 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 /// - parameter options: Options for writing the data. Default value is `[]`. /// - throws: An error in the Cocoa domain, if there is an error writing to the `URL`. public func write(to url: URL, options: Data.WritingOptions = []) throws { +#if !os(WASI) // `.atomic` is unavailable on WASI if options.contains(.withoutOverwriting) && options.contains(.atomic) { fatalError("withoutOverwriting is not supported with atomic") } +#endif guard url.isFileURL else { throw CocoaError(.fileWriteUnsupportedScheme) diff --git a/Sources/FoundationEssentials/FileManager/FileManager+Files.swift b/Sources/FoundationEssentials/FileManager/FileManager+Files.swift index 39fb16395..8f0083f04 100644 --- a/Sources/FoundationEssentials/FileManager/FileManager+Files.swift +++ b/Sources/FoundationEssentials/FileManager/FileManager+Files.swift @@ -260,6 +260,9 @@ extension _FileManagerImpl { } attr?[.protectionKey] = nil } + #elseif os(WASI) + // `.atomic` is unavailable on WASI + let opts: Data.WritingOptions = [] #else let opts = Data.WritingOptions.atomic #endif diff --git a/Sources/FoundationEssentials/String/String+IO.swift b/Sources/FoundationEssentials/String/String+IO.swift index 559e84cd7..55f8e19ae 100644 --- a/Sources/FoundationEssentials/String/String+IO.swift +++ b/Sources/FoundationEssentials/String/String+IO.swift @@ -433,7 +433,12 @@ extension StringProtocol { attributes = [:] } +#if os(WASI) + guard !useAuxiliaryFile else { throw CocoaError(.featureUnsupported) } + let options : Data.WritingOptions = [] +#else let options : Data.WritingOptions = useAuxiliaryFile ? [.atomic] : [] +#endif try writeToFile(path: .path(String(path)), data: data, options: options, attributes: attributes, reportProgress: false) } @@ -451,7 +456,12 @@ extension StringProtocol { attributes = [:] } +#if os(WASI) + guard !useAuxiliaryFile else { throw CocoaError(.featureUnsupported) } + let options : Data.WritingOptions = [] +#else let options : Data.WritingOptions = useAuxiliaryFile ? [.atomic] : [] +#endif try writeToFile(path: .url(url), data: data, options: options, attributes: attributes, reportProgress: false) } diff --git a/Tests/FoundationEssentialsTests/DataIOTests.swift b/Tests/FoundationEssentialsTests/DataIOTests.swift index 76375dcbd..324012c11 100644 --- a/Tests/FoundationEssentialsTests/DataIOTests.swift +++ b/Tests/FoundationEssentialsTests/DataIOTests.swift @@ -93,6 +93,9 @@ class DataIOTests : XCTestCase { // Atomic writing is a very different code path func test_readWriteAtomic() throws { + #if os(WASI) + try XCTSkip("atomic writing is not supported on WASI") + #else let url = testURL() // Perform an atomic write to a file that does not exist try writeAndVerifyTestData(to: url, writeOptions: [.atomic]) @@ -101,6 +104,7 @@ class DataIOTests : XCTestCase { try writeAndVerifyTestData(to: url, writeOptions: [.atomic]) cleanup(at: url) + #endif } func test_readWriteMapped() throws {