Skip to content

Data.write: adds support for the combination of .atomic & .withoutOverwriting, instead of crashing (#1098)#2011

Open
wadetregaskis wants to merge 1 commit into
swiftlang:mainfrom
wadetregaskis:atomicWithoutOverwriting
Open

Data.write: adds support for the combination of .atomic & .withoutOverwriting, instead of crashing (#1098)#2011
wadetregaskis wants to merge 1 commit into
swiftlang:mainfrom
wadetregaskis:atomicWithoutOverwriting

Conversation

@wadetregaskis
Copy link
Copy Markdown
Contributor

Motivation:

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 not just innately bad but also a source-breaking regression.

Modifications:

  • 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.

Result:

Writing atomically without overwriting now works [again].

Testing:

New unit test added.

…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`.
@wadetregaskis wadetregaskis requested a review from a team as a code owner May 31, 2026 16:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant