@@ -166,6 +166,11 @@ public protocol FileSystem: class {
166
166
// FIXME: This is obviously not a very efficient or flexible API.
167
167
func writeFileContents( _ path: AbsolutePath , bytes: ByteString ) throws
168
168
169
+ /// Write the contents of a file.
170
+ //
171
+ // FIXME: This is obviously not a very efficient or flexible API.
172
+ func writeFileContents( _ path: AbsolutePath , bytes: ByteString , atomically: Bool ) throws
173
+
169
174
/// Recursively deletes the file system entity at `path`.
170
175
///
171
176
/// If there is no file system entity at `path`, this function does nothing (in particular, this is not considered
@@ -193,6 +198,15 @@ public extension FileSystem {
193
198
func chmod( _ mode: FileMode , path: AbsolutePath ) throws {
194
199
try chmod ( mode, path: path, options: [ ] )
195
200
}
201
+
202
+ // Unless the file system type provides an override for this method, throw
203
+ // if `atomically` is `true`, otherwise fall back to whatever implementation already exists.
204
+ func writeFileContents( _ path: AbsolutePath , bytes: ByteString , atomically: Bool ) throws {
205
+ guard !atomically else {
206
+ throw FileSystemError . unsupported
207
+ }
208
+ try writeFileContents ( path, bytes: bytes)
209
+ }
196
210
197
211
/// Write to a file from a stream producer.
198
212
func writeFileContents( _ path: AbsolutePath , body: ( OutputByteStream ) -> Void ) throws {
@@ -351,6 +365,24 @@ private class LocalFileSystem: FileSystem {
351
365
break
352
366
}
353
367
}
368
+
369
+ func writeFileContents( _ path: AbsolutePath , bytes: ByteString , atomically: Bool ) throws {
370
+ // Perform non-atomic writes using the fast path.
371
+ if !atomically {
372
+ return try writeFileContents ( path, bytes: bytes)
373
+ }
374
+ let temp = try TemporaryFile ( dir: path. parentDirectory, deleteOnClose: false )
375
+ do {
376
+ try writeFileContents ( temp. path, bytes: bytes)
377
+ try rename ( temp. path, to: path)
378
+ } catch {
379
+ // Write or rename failed, delete the temporary file.
380
+ // Rethrow the original error, however, as that's the
381
+ // root cause of the failure.
382
+ _ = try ? self . removeFileTree ( temp. path)
383
+ throw error
384
+ }
385
+ }
354
386
355
387
func removeFileTree( _ path: AbsolutePath ) throws {
356
388
if self . exists ( path, followSymlink: false ) {
@@ -681,6 +713,12 @@ public class InMemoryFileSystem: FileSystem {
681
713
// Write the file.
682
714
contents. entries [ path. basename] = Node ( . file( bytes) )
683
715
}
716
+
717
+ public func writeFileContents( _ path: AbsolutePath , bytes: ByteString , atomically: Bool ) throws {
718
+ // In memory file system's writeFileContents is already atomic, so ignore the parameter here
719
+ // and just call the base implementation.
720
+ try writeFileContents ( path, bytes: bytes)
721
+ }
684
722
685
723
public func removeFileTree( _ path: AbsolutePath ) throws {
686
724
// Ignore root and get the parent node's content if its a directory.
0 commit comments