Skip to content

cp: create removed destinations exclusively - #15330

Open
abendrothj wants to merge 1 commit into
uutils:mainfrom
abendrothj:cp-recreate-dest-exclusive
Open

abendrothj wants to merge 1 commit into
uutils:mainfrom
abendrothj:cp-recreate-dest-exclusive

Conversation

@abendrothj

@abendrothj abendrothj commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Closes #15329

When the destination was missing, or cp had removed or renamed it (--remove-destination, -f, --backup), cp created the new file without O_EXCL. Whatever appeared at that name in between was opened, and a symlink was followed.

cp now creates the file exclusively in those cases and fails if anything is there, with GNU's messages. A destination that is overwritten in place is opened as before. The flag lives in uucore's create_dest_restrictive, where exclusive replaces nofollow. Its only nofollow caller, mv's cross-device fallback, now creates exclusively too, as GNU mv does. mv can't be paused between its removal and the create, so that path has no test of its own; the uucore unit tests cover the exclusive create. --attributes-only creates exclusively on Windows too; the Windows data copy still uses fs::copy. util/check-safe-traversal.sh accepts O_EXCL as well as O_NOFOLLOW for mv's create.

Checked against GNU coreutils 9.12 for --remove-destination with a symlink, a dangling symlink and a regular file created after the removal: messages and exit status match. Added test_cp_does_not_open_dest_recreated_after_removal, which fails without the change on macOS and Linux.

Copilot AI balanced review requested due to automatic review settings October 14, 2026 04:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@codspeed

codspeed Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Merging this PR will improve performance by 17.24%

⚠️ 120 benchmarks spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

ℹ️ 1 benchmark has no flame graph

The flamegraph could not be generated because too much time was spent in runtime internals, making the visualization not useful for performance analysis.

⚡ 3 improved benchmarks
✅ 398 untouched benchmarks
⏩ 70 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
⚡ Simulation factor_three_39_bit_primes 567 ms 396.9 ms +42.84%
⚡ Simulation factor_thirteen_39_bit_primes 9.5 s 8.7 s +8.87%
⚡ Simulation factor_five_38_bit_primes 1.6 s 1.6 s +3.62%

Tip

Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.


Comparing abendrothj:cp-recreate-dest-exclusive (be468b3) with main (c76f272)

Open in CodSpeed

Footnotes

  1. 70 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

Comment thread src/uu/cp/src/cp.rs Outdated
}
// Same exclusive create as for a data copy, which only does so on
// Unix (see `create_dest`).
let exclusive = cfg!(unix) && create_exclusive;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why only unix? create_new works on windows too

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done for --attributes-only. On Windows the data copy goes through fs::copy, which can't create exclusively; that needs CopyFileExW with COPY_FILE_FAIL_IF_EXISTS. I can do that in a follow-up.

translate!("cp-error-cannot-create-regular-file", "path" => dest.quote()),
)
})?;
let mut dst_file = create_dest(dest, exclusive)?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this also changes the initial mode of the stream dest on macOS (0o622 -> 0o600), please mention it in the commit message

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment thread tests/by-util/test_cp.rs Outdated
}
// Opening the writer releases cp's source open. Open it non-blocking, so a
// cp that never opens the source fails the test instead of hanging it.
let mut writer = loop {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test is quite complex (polling loops, ENXIO, BrokenPipe), could it be simpler? i am worried it will be flaky on CI

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplified: one write to the FIFO after dest is replaced, so the ENXIO loop and the BrokenPipe check are gone. The wait for cp to remove dest stays, since the FIFO is what holds cp there, now with a 60s deadline.

On macOS, a streamed destination is now created at 0600, as on Linux,
instead of 0622 minus the umask.
@abendrothj
abendrothj force-pushed the cp-recreate-dest-exclusive branch from 8270ad8 to be468b3 Compare October 14, 2026 22:08
Copilot AI balanced review requested due to automatic review settings October 14, 2026 22:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cp --remove-destination opens a file that appears after the removal

3 participants