Skip to content

std: add fs::rename_noreplace - #162027

Open
valentynkit wants to merge 5 commits into
rust-lang:mainfrom
valentynkit:fs-rename-noreplace
Open

valentynkit wants to merge 5 commits into
rust-lang:mainfrom
valentynkit:fs-rename-noreplace

Conversation

@valentynkit

@valentynkit valentynkit commented Aug 30, 2026 •

Copy link
Copy Markdown
Contributor

View all comments

Tracking issue: #161427
Accepted ACP: rust-lang/libs-team#131

Summary

Add fs::rename_noreplace. Unlike fs::rename, the destination is never overwritten, instead returns ErrorKind::AlreadyExists if to already exists.

Platform-specific APIs used:

  • renameat2 with RENAME_NOREPLACE flag on Linux and Android
  • renamex_np with RENAME_EXCL on Apple
  • MoveFileExW without the MOVEFILE_REPLACE_EXISTING flag on Windows. Extracted functionality into rename_inner to avoid duplication for rename and rename_noreplace
  • On other Unix platforms and on WASI, and on Linux, Android and Apple when the kernel or the filesystem doesn't support it, link followed by unlink is used instead.
  • Platforms with neither return Unsupported.

Linux and Android case, has different error handling than Apple because the platforms report these conditions differently. On Linux the syscall may not exist at all (ENOSYS), and EINVAL covers "filesystem doesn't support the flag" and flag misuse (incorrect combinations of flags, as example). On Apple renamex_np is called directly, and Darwin use ENOTSUP for not supported case, and keeps EINVAL only for misuse combinations.

EINVAL An invalid flag was specified in flags.
EINVAL Both RENAME_NOREPLACE and RENAME_EXCHANGE were specified in flags.
EINVAL The filesystem does not support one of the flags in flags.
renameat2

Open questions

  1. ENOSYS on Linux and Android could be cached, unlike EINVAL case which on the same machine, when using different filesystem may return different result. In this PR I deliberately avoided adding caching to avoid premature optimization, but could add it if you think it worth it.
  2. The link + unlink fallback can't move directories: link returns EPERM. Should it report Unsupported there instead?

r? libs

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Aug 30, 2026
@valentynkit
valentynkit marked this pull request as ready for review August 30, 2026 19:10
@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Aug 30, 2026
Comment thread library/std/src/sys/fs/unix.rs
Comment thread library/std/src/sys/fs/unix.rs
Comment thread library/std/src/fs.rs Outdated
@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 13, 2026
@rustbot

rustbot commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

Reminder, once the PR becomes ready for a review, use @rustbot ready.

Comment thread library/std/src/fs.rs Outdated
@joshtriplett

Copy link
Copy Markdown
Member

Added one suggestion to improve the documentation comment.

With that suggestion applied, r=me.

@joshtriplett

Copy link
Copy Markdown
Member

@bors delegate+

@rust-bors

rust-bors Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

✌️ @valentynkit, you can now approve this pull request!

If @joshtriplett told you to "r=me" after making some further change, then please make that change and post @bors r=joshtriplett.

View changes since this delegation.

@rustbot

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

valentynkit and others added 4 commits September 27, 2026 22:43
Unlike `fs::rename`, the destination is never overwritten, returning
`ErrorKind::AlreadyExists` if `to` already exists.

Platform-specific APIs used: `renameat2` with `RENAME_NOREPLACE` flag on `Linux` and `Android`, `renamex_np` with `RENAME_EXCL` on `Apple`,
and `MoveFileExW` without the `MOVEFILE_REPLACE_EXISTING` flag on `Windows`.
On other Unix platforms, and on Linux, Android and Apple when the kernel
or the filesystem doesn't support it, `link` followed by `unlink` is
used instead. Platforms with neither return `Unsupported`.
Co-authored-by: Josh Triplett <josh@joshtriplett.org>
windows-bindgen no longer generates the `MOVE_FILE_FLAGS` alias, so `u32` is
used instead.
@rustbot

rustbot commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@valentynkit

Copy link
Copy Markdown
Contributor Author

@rustbot ready
CI failed because #162270 removed the MOVE_FILE_FLAGS alias. Rebased and switched the Windows code to use plain u32.

Since this change wasn't part of the requested suggestion, I'd rather not approve it myself using the delegation, even so it's straightforward change. Would you mind taking a quick look?

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Sep 27, 2026
@Mark-Simulacrum

Copy link
Copy Markdown
Member

@bors r=joshtriplett,Mark-Simulacrum

@rust-bors

rust-bors Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

📌 Commit 7b7ed95 has been approved by joshtriplett,Mark-Simulacrum

It is now in the queue for this repository.

@rust-bors rust-bors Bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Oct 3, 2026
jhpratt added a commit to jhpratt/rust that referenced this pull request Oct 3, 2026
…=joshtriplett,Mark-Simulacrum

std: add `fs::rename_noreplace`

Tracking issue: rust-lang#161427
Accepted ACP: rust-lang/libs-team#131

## Summary

Add `fs::rename_noreplace`. Unlike `fs::rename`, the destination is never overwritten, instead returns `ErrorKind::AlreadyExists` if `to` already exists.

Platform-specific APIs used:
- [`renameat2`](https://man7.org/linux/man-pages/man2/renameat2.2.html) with `RENAME_NOREPLACE` flag on Linux and Android
- `renamex_np` with `RENAME_EXCL` on Apple
- [`MoveFileExW`](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefileexw) without the `MOVEFILE_REPLACE_EXISTING` flag on Windows. Extracted functionality into `rename_inner` to avoid duplication for `rename` and `rename_noreplace`
- On other Unix platforms and on WASI, and on Linux, Android and Apple when the kernel or the filesystem doesn't support it, `link` followed by `unlink` is used instead.
- Platforms with neither return `Unsupported`.

Linux and Android case, has different error handling than Apple because the platforms report these conditions differently. On Linux the syscall may not exist at all (`ENOSYS`), and `EINVAL` covers  "filesystem doesn't support the flag" and flag misuse (incorrect combinations of flags, as example). On Apple `renamex_np` is called directly, and Darwin use `ENOTSUP` for not supported case, and keeps `EINVAL` only for misuse combinations.

  > EINVAL An invalid flag was specified in flags.
  > EINVAL Both RENAME_NOREPLACE and RENAME_EXCHANGE were specified in flags.
  > EINVAL The filesystem does not support one of the flags in flags.
[`renameat2`](https://man7.org/linux/man-pages/man2/renameat2.2.html)

## Open questions

1. `ENOSYS` on Linux and Android could be cached, unlike `EINVAL` case which on the same machine, when using different filesystem may return different result. In this PR I deliberately avoided adding caching to avoid premature optimization, but could add it if you think it worth it.
2. The `link` + `unlink` fallback can't move directories: `link` returns `EPERM`. Should it report `Unsupported` there instead?

r? libs
rust-bors Bot pushed a commit that referenced this pull request Oct 4, 2026
Rollup of 18 pull requests

Successful merges:

 - #158102 (When compiling without a specified `--edition`, emit a message)
 - #162027 (std: add `fs::rename_noreplace`)
 - #162761 (Lower attributes for functions without bodies)
 - #163161 (implement FCW for `rustc_allowed_through_unstable_modules` items)
 - #163613 (Tweak the rendering of "not general enough" errors on the old trait solver)
 - #162062 (core: fix the docs of PanicInfo::location)
 - #163140 (document safety requirements for atomic intrinsics)
 - #163342 (Don't imply incorrect things about `Global` in the docs of `System`)
 - #163445 (Add safety comments for alloc::str)
 - #163503 (Mark Rc strong/weak count methods must_use)
 - #163548 (fs::set_permissions_nofollow: Android support, test cleanup)
 - #163585 ([triagebot] Create `debugger_visualizer` assign group)
 - #163597 (Add `SplitPathsRef` implementation for motor to make std build)
 - #163602 (Move media & home dirs tests to fs tests.)
 - #163667 (Finalize changes on expect messages for library/core/src/fmt/mod.rs)
 - #163682 ([rustdoc] Correctly link to (imported) enum variants with "jump to def")
 - #163683 (Fix GCC codegen backend comment in bootstrap)
 - #163703 (Move more `rustdoc-html tests` in the right location)

Failed merges:

 - #161491 (Rip out old solver coherence)
@jhpratt

jhpratt commented Oct 4, 2026

Copy link
Copy Markdown
Member

@bors r- #163729 (comment)

failures:

---- fs::tests::test_rename_noreplace_directory stdout ----

thread 'fs::tests::test_rename_noreplace_directory' (10893) panicked at library/std/src/fs/tests.rs:2739:54:
called `Result::unwrap()` on an `Err` value: Os { code: 1, kind: PermissionDenied, message: "Operation not permitted" }
---- fs::tests::test_rename_noreplace_directory stdout end ----

failures:
    fs::tests::test_rename_noreplace_directory

test result: FAILED. 429 passed; 1 failed; 28 ignored; 0 measured; 0 filtered out; finished in 302.65s

@rust-bors rust-bors Bot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Oct 4, 2026
@rust-bors

rust-bors Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

This pull request was unapproved.

This PR was contained in a rollup (#163729), which was unapproved.

View changes since this unapproval

`test_rename_noreplace_directory()` fails on kernels before Linux 3.15,
because they lack `renameat2`, so std implementation falls back to `link`/`unlink`,
which couldn't move directories, so expectations of the test were wrong.
Test now accepts `PermissionDenied` on Unix, in fallback case.
@valentynkit

Copy link
Copy Markdown
Contributor Author
failures:

---- fs::tests::test_rename_noreplace_directory stdout ----
thread 'fs::tests::test_rename_noreplace_directory' (10893) panicked at library/std/src/fs/tests.rs:2739:54:
called ``Result::unwrap()`` on an ``Err`` value: Os { code: 1, kind: PermissionDenied, message: "Operation not permitted" }

Sorry, my bad for not catching this during implementation.
After going through it, it looks like the problem in the test itself test_rename_noreplace_directory assumed that gated platforms will always use the native rename no replace, but older version like Linux before 3.15 fall back to implementation that use link/unlink, which doesn't support directory. This limitation is documented in implementation and this PR body, but I didn't handle the fallback path in the test.

Test was updated to support fallback approach.

@valentynkit

Copy link
Copy Markdown
Contributor Author

@bors try jobs=test-arm-android
Running the failed job, looks like it's only included during the rollup.

@rust-bors

This comment has been minimized.

rust-bors Bot pushed a commit that referenced this pull request Oct 4, 2026
std: add `fs::rename_noreplace`


try-job: test-arm-android
@rust-bors

rust-bors Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

☀️ Try build successful (CI)
Build commit: 94dce72 (94dce72406d558b25c6b2761ebac5fc850af9e18)
Base parent: a639ea0 (a639ea0890c0977b1c45596fd6b5f9a70a1d67da)

@valentynkit

Copy link
Copy Markdown
Contributor Author

@rustbot ready

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Oct 4, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants