Data.write: adds support for the combination of .atomic & .withoutOverwriting, instead of crashing (#1098)#2011
Conversation
…Overwriting`, instead of crashing (swiftlang#1098). Prior to this patch, `Data.write(to:options:)` called `fatalError` when both `.atomic` and `.withoutOverwriting` were passed together. That combination worked in Swift 5.10 and is documented as supported (Apple's Foundation maps it to "write to aux file with O_EXCL, then exchange"), so trapping is a source-breaking regression. The implementation details depend on platform: - POSIX: after writing the auxiliary file, `link` is used to map it under the final name too. `link` fails with `EEXIST` if the destination already exists, which is exactly the contract of `.withoutOverwriting`. And whether successful or not, the original name is unlinked (which leaves only the intended final name referring to the file, or deletes the file if the `link` failed). - Windows: in `SetFileInformationByHandle`, `FILE_RENAME_FLAG_REPLACE_IF_EXISTS` / `MOVEFILE_REPLACE_EXISTING` are dropped if `.withoutOverwriting` is specified, and the `ERROR_FILE_EXISTS` & `ERROR_ALREADY_EXISTS` errors are mapped to Cocoa's `fileWriteFileExists`.
Can you please elaborate on this statement? It seems mostly incorrect to me. This combination of options is explicitly documented as unsupported:
https://developer.apple.com/documentation/foundation/nsdata/writingoptions/withoutoverwriting Additionally, this source code is shared with |
|
The semantics of When originally implemented, we had no silver bullet syscall that could support an atomic rename() that had the same semantics as Furthermore, without any kind of availability declaration, we'd probably have to predicate this behavior on deployment target so that it doesn't surprisingly behave differently on current OS's and then crash immediately when running on older OS's. RE: |
Motivation:
Prior to this patch,
Data.write(to:options:)calledfatalErrorwhen both.atomicand.withoutOverwritingwere passed together. That combination worked in Swift 5.10 and is documented as supported (Apple's Foundation maps it to "write to aux file with O_EXCL, then exchange"), so trapping is not just innately bad but also a source-breaking regression.Modifications:
linkis used to map it under the final name too.linkfails withEEXISTif the destination already exists, which is exactly the contract of.withoutOverwriting. And whether successful or not, the original name is unlinked (which leaves only the intended final name referring to the file, or deletes the file if thelinkfailed).SetFileInformationByHandle,FILE_RENAME_FLAG_REPLACE_IF_EXISTS/MOVEFILE_REPLACE_EXISTINGare dropped if.withoutOverwritingis specified, and theERROR_FILE_EXISTS&ERROR_ALREADY_EXISTSerrors are mapped to Cocoa'sfileWriteFileExists.Result:
Writing atomically without overwriting now works [again].
Testing:
New unit test added.