Skip to content

generalize some mv code for ln, cp - #14623

Merged
sylvestre merged 2 commits into
uutils:mainfrom
sylvestre:fix-3834-atomic-link-replace
Sep 16, 2026
Merged

sylvestre merged 2 commits into
uutils:mainfrom
sylvestre:fix-3834-atomic-link-replace

Conversation

@sylvestre

Copy link
Copy Markdown
Contributor

No description provided.

Copilot AI lite review requested due to automatic review settings September 16, 2026 21:06

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.

replace links atomically instead of unlinking first

Forced replacement was unlink(dst) then create. The gap lets another user
claim dst in a directory they can write to -- including a name the sticky
bit forbids them to remove -- so a privileged ln hands them a trusted name.
Renaming the temp onto a destination that is already a link to the same
inode does nothing and returns success, so the temp name survived:
`ln -f a b` with b already hard-linked to a left a stray Cu* file.

Unlink the temp after the rename either way; on the normal path it is
already gone.
Copilot AI review requested due to automatic review settings September 16, 2026 21:51
@sylvestre
sylvestre force-pushed the fix-3834-atomic-link-replace branch from f70c1c4 to 8fcb17a Compare September 16, 2026 21:51

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.

@sylvestre
sylvestre merged commit 4bf2727 into uutils:main Sep 16, 2026
112 of 113 checks passed
@sylvestre
sylvestre deleted the fix-3834-atomic-link-replace branch September 16, 2026 22:12
@github-actions

Copy link
Copy Markdown

GNU testsuite comparison:

Skip an intermittent issue tests/tail/retry (fails in this run but passes in the 'main' branch)
Skip an intermittent issue tests/tail/symlink (fails in this run but passes in the 'main' branch)
Skipping an intermittent issue tests/date/date-locale-hour (passes in this run but fails in the 'main' branch)
Note: The gnu test tests/tail/tail-n0f is now being skipped but was previously passing.
Congrats! The gnu test tests/cut/bounded-memory is now passing!
Skip an intermittent issue tests/pr/bounded-memory (was skipped on 'main', now failing)

@codspeed

codspeed Bot commented Sep 16, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 3.24%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
✅ 366 untouched benchmarks
⏩ 50 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
⚡ Simulation du_wide_tree[(5000, 500)] 18.9 ms 18.3 ms +3.24%

Tip

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


Comparing sylvestre:fix-3834-atomic-link-replace (8fcb17a) with main (ebcdac1)

Open in CodSpeed

Footnotes

  1. 50 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. ↩

@xtqqczze

Copy link
Copy Markdown
Contributor

test_force_replace_never_leaves_the_destination_name_free is failing on wasm32-wasip1 target since this was merged, tracked by #14632.

hanthor pushed a commit to tuna-os/tunaOS that referenced this pull request Sep 17, 2026
A THIRD independent root cause, and this one is repo-wide: it blocks the
Manifest job on EVERY variant, and nothing in this repository changed to
cause it.

  ln: failed to create symbolic link '/bin/sh': Not a directory

The `Install dependencies` step installs uutils, which replaces coreutils in
PATH, so the very next line runs uutils' ln. uutils 0.12.0 rewrote the -f
path to "replace links atomically instead of unlinking first"
(uutils/coreutils#14623; a platform-specific follow-up is already filed as
#14632). `ln -sf` takes exactly that rewritten path.

The container is pinned by digest but `apk add` is not, so the bump landed
mid-run and split one run cleanly by the clock. Build Bonito 35202293222,
one run, same image, same command:

  base 09:23, base-hwe 09:45, base-nvidia 09:49,
  cosmic 09:52, niri 09:52, gnome 09:54     ALL PASS   (uutils 0.11.0-r1)
  xfce 09:58:55, kde 10:02:03               BOTH FAIL  (uutils 0.12.0-r0)

Build Gurnard 35205505527 hit the identical error at 10:11 in
pantheon/Manifest, which is what rules out a bonito-specific cause.

`rm` followed by a plain `ln -s` never enters the rewritten -f path and
behaves the same under GNU, busybox and uutils.

NOT pinned to uutils 0.11.0-r1 on purpose. A pin here works, but it needs
someone to notice and lift it once upstream fixes the regression, and this
step already showed what an unpinned moving dependency costs.

Removing /bin/sh mid-script is safe even though the step itself runs under
`sh -e`: a running shell survives its own binary being unlinked, and its
subshells fork the loaded image rather than re-exec the path. Verified both
that and the symlinked-parent case (/bin -> usr/bin, matching wolfi-base)
locally.

Validated: yamllint under ./.yamllint.yml exits 0 with the same 7 pre-existing
line-length warnings as origin/main; the workflow still parses; the 235
workflow tests pass. This merges origin/main first so the change does not
revert #2559's Renovate bump of setup-runner to a23fee8.

CANNOT be proven by this PR's CI: the job runs on workflow_dispatch/schedule
against main, never on pull requests. The real test is the first build after
merge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L59jmWZu8kiH2G9Jx7tcws
@xtqqczze

Copy link
Copy Markdown
Contributor

By the way, rustix::fs::symlinkat doesn't exist on redox, so the fs feature now fails to build:

$ cargo clippy -p uucore --features fs --all-targets --target x86_64-unknown-redox
error[E0425]: cannot find function `symlinkat` in module `rustix::fs`
    --> src/uucore/src/lib/features/fs.rs:1250:21
     |
1250 |         rustix::fs::symlinkat(target, rustix::fs::CWD, dest).map_err(Into::into)

@sylvestre

Copy link
Copy Markdown
Contributor Author

this is why we need CI :)

@oech3

oech3 commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

I think we should abandon redox as no one can maintain.

@xtqqczze

Copy link
Copy Markdown
Contributor

@sylvestre Sorry, I think we should revert this; it is the only thing blocking a clean build on Redox.

Then we can add check-only Redox job to CI:

cargo check -all-targets --features feat_os_unix_redox --target x86_64-unknown-redox

@sylvestre

Copy link
Copy Markdown
Contributor Author

i am sorry but i am not going to revert such change for an OS for which we don't have CI

@xtqqczze

xtqqczze commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

@sylvestre I have uutils building without errors locally now, so I think we’re very close to being able to add CI. From my side, the remaining pieces are:

I’d really like to avoid letting the code rot in the meantime.

@sylvestre

Copy link
Copy Markdown
Contributor Author

this PR fixes security issues
so, i am sorry but reverting isn't an option

xtqqczze added a commit to xtqqczze/uutils-coreutils that referenced this pull request Sep 20, 2026
hideyosh1 pushed a commit to hideyosh1/coreutils that referenced this pull request Sep 26, 2026
* generalize the mv code for ln, cp
replace links atomically instead of unlinking first

Forced replacement was unlink(dst) then create. The gap lets another user
claim dst in a directory they can write to -- including a name the sticky
bit forbids them to remove -- so a privileged ln hands them a trusted name.

* ln: don't leave the temp behind when the rename is a no-op

Renaming the temp onto a destination that is already a link to the same
inode does nothing and returns success, so the temp name survived:
`ln -f a b` with b already hard-linked to a left a stray Cu* file.

Unlink the temp after the rename either way; on the normal path it is
already gone.
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.

4 participants