Skip to content

mv: create cross-device directory entries relative to the destination descriptor - #14971

Draft
abendrothj wants to merge 9 commits into
uutils:mainfrom
abendrothj:mv-cross-device-dir-dirfd
Draft

abendrothj wants to merge 9 commits into
uutils:mainfrom
abendrothj:mv-cross-device-dir-dirfd

Conversation

@abendrothj

@abendrothj abendrothj commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Based on #15123; only the last three commits are new here.

When a directory move falls back to copying across filesystems, each destination entry was created by joining a path, and regular files went through fs::copy. A symlink that appeared in the destination during the copy was followed, so content went wherever it pointed.

Each destination directory is now held open (search-only where possible), and files, subdirectories, symlinks, special files and hard links are created relative to it without following symlinks. A file, link or special file that shows up at a name is replaced; a directory there fails ("dest appeared"). Regular files share copy_file_data with the single-file fallback, which sets ownership and mode on the open descriptor, using libc fchown so fakeroot still works; util/check-libc-interposition.sh now checks this for mv. On Linux it copies with io::copy, so a move between btrfs subvolumes still reflinks, as fs::copy did, and single-file moves now do too. On macOS, fcopyfile copies the data and then the ACLs, extended attributes and times, which fs::copy kept for files in a directory and single-file moves didn't; if the second part fails, mv warns ("setting attributes for ...") and goes on, as for xattrs elsewhere. Source directory names are read before descending, so one descriptor stays open per level. Still by path: directory ownership, symlink xattrs, the hard link source, and the reopen that sets each directory's xattrs and mode.

On Linux, a 512 MiB file moved between two btrfs subvolumes takes 1 ms against 238 ms on main, as it now reflinks; a directory already did (0.9 ms on both). Across filesystems it is about the same: 4 x 128 MiB took 238 ms against 240 ms (btrfs to tmpfs) and 97.5 against 96.8 ms on macOS (APFS to a RAM disk); 5000 small files took 236 against 248 ms on Linux and 1580 against 1530 ms on macOS, where the separate metadata call costs about 3%.

test_mv_cross_device_dir_replaces_symlink_entry_created_mid_copy plants symlinks from a thread while 200 files are copied. It can't force the timing, so a run where none lands in time passes without proving anything; on Linux it failed 10 of 10 runs without the change and passed 30 of 30 with it, in about 0.02 s. A unit test checks the same replacement deterministically, with the symlink planted beforehand. The other new tests cover a deep tree under a low descriptor limit, an owner-read-masked directory and a write/search-only destination. Run on macOS and Linux.

Closes #14991

Copilot AI balanced review requested due to automatic review settings September 29, 2026 23:50

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.

@abendrothj
abendrothj force-pushed the mv-cross-device-dir-dirfd branch from d0cf9fe to f3fc847 Compare September 29, 2026 23:54
Copilot AI balanced review requested due to automatic review settings September 29, 2026 23:54

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.

@abendrothj
abendrothj force-pushed the mv-cross-device-dir-dirfd branch from f3fc847 to e3fda2a Compare September 29, 2026 23:55
@codspeed

codspeed Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Merging this PR will regress 1 benchmark

1 warning, 1 note

⚠️ 122 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

ℹ️ 1 benchmark has no flamegraph

The flamegraph wasn't generated: most of the time was spent in runtime internals, which would leave nothing useful to analyze.

See affected benchmarks

⚡ 4 improved benchmarks
❌ 1 regressed benchmark
✅ 400 untouched benchmarks
⏩ 70 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
❌ CPU Simulation ls_recursive_wide_tree[(10000, 1000)] 68.1 ms 75 ms -9.21%
⚡ CPU Simulation uniq_case_insensitive[10000] 2.9 ms 2.8 ms +5.11%
⚡ CPU Simulation uniq_with_count[10000] 3.6 ms 3.5 ms +4.13%
⚡ CPU Simulation uniq_heavy_duplicates[10000] 4.2 ms 4 ms +3.49%
⚡ CPU Simulation uniq_check_chars[(10000, "1")] 4.2 ms 4.1 ms +3.47%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing abendrothj:mv-cross-device-dir-dirfd (7f40614) with main (156d715)

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

@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

GNU testsuite comparison:

Congrats! The gnu test tests/mv/mv-special-1 is no longer failing!

@abendrothj
abendrothj force-pushed the mv-cross-device-dir-dirfd branch from e3fda2a to 8d392d1 Compare September 30, 2026 02:35
Copilot AI balanced review requested due to automatic review settings September 30, 2026 02:35

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.

@abendrothj

Copy link
Copy Markdown
Contributor Author

Opened #14991.

Copilot AI balanced review requested due to automatic review settings October 1, 2026 01:14
@abendrothj
abendrothj force-pushed the mv-cross-device-dir-dirfd branch from 8d392d1 to 5e3cb57 Compare October 1, 2026 01:14

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.

@abendrothj
abendrothj force-pushed the mv-cross-device-dir-dirfd branch from 5e3cb57 to 42731bd Compare October 5, 2026 06:13
Copilot AI balanced review requested due to automatic review settings October 5, 2026 06:13

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

Copy link
Copy Markdown
Contributor

this is quite a large patch, can we make it smaller?

Copilot AI balanced review requested due to automatic review settings October 6, 2026 07:15
@abendrothj
abendrothj force-pushed the mv-cross-device-dir-dirfd branch from 42731bd to 7e5ee68 Compare October 6, 2026 07:15

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.

@abendrothj

Copy link
Copy Markdown
Contributor Author

Smaller now: it reuses DirFd, replace_entry_at, link_at and copy_special_file from the PRs below it instead of its own per-entry helpers. Its own diff went from +588/-66 to 5 files, +490/-79, about 200 of them tests. Part of that is a late fix in 7e5ee68: ownership is set with libc fchown on the open descriptor, so fakeroot can intercept it, with a regression test whose prerequisites are in DEVELOPMENT.md. It sits on #15123, so the diff shown here includes the stack below it until that lands.

Copilot AI balanced review requested due to automatic review settings October 6, 2026 07:58

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.

@abendrothj
abendrothj force-pushed the mv-cross-device-dir-dirfd branch from b125862 to b3b9dde Compare October 7, 2026 00:54
Copilot AI balanced review requested due to automatic review settings October 7, 2026 00:54

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.

@abendrothj
abendrothj marked this pull request as draft October 7, 2026 00:55
@abendrothj

Copy link
Copy Markdown
Contributor Author

drafting this until #15120, #15122 and #13334 land; on its own it's +490/-79, about 200 of them tests.

@abendrothj
abendrothj force-pushed the mv-cross-device-dir-dirfd branch 2 times, most recently from 52022fc to 6903ee6 Compare October 12, 2026 10:05
@abendrothj
abendrothj force-pushed the mv-cross-device-dir-dirfd branch 3 times, most recently from d767c93 to 4d87582 Compare October 22, 2026 08:33
@abendrothj

Copy link
Copy Markdown
Contributor Author

Trimmed a bit more: the fakeroot test moved into util/check-libc-interposition.sh, and the clippy allow is gone. Its own diff is now +409/-96, 133 of it tests.

@abendrothj
abendrothj force-pushed the mv-cross-device-dir-dirfd branch from 4d87582 to e4aae21 Compare October 22, 2026 08:50
@sylvestre

Copy link
Copy Markdown
Contributor

sorry but it needs to be rebased

Comment thread src/uu/mv/src/mv.rs Resolved
Comment thread src/uu/mv/src/mv.rs
fn dest_appeared(path: &Path, e: io::Error) -> io::Error {
if e.kind() == io::ErrorKind::AlreadyExists {
io::Error::new(
io::ErrorKind::AlreadyExists,

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.

two full impl DestDir blocks per platform is a lot of duplication, could the path fallback live in uucore instead?

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.

There's one DestDir struct now, with two small create_dest_dir versions. The path versions are only for platforms where safe_traversal isn't built at all (Redox, Windows, WASI), so uucore has nowhere to put them; elsewhere DirAnchor already falls back to the path where it can't hold the directory open. Building a path-only DirAnchor on Redox would let mv drop them; I can do that in a follow-up if you'd like.

Comment thread src/uu/mv/src/mv.rs Resolved
Comment thread tests/by-util/test_mv.rs Resolved
@abendrothj
abendrothj force-pushed the mv-cross-device-dir-dirfd branch from e4aae21 to 0b6fdcb Compare November 7, 2026 06:33
For ln -f/-sf, cp -lf/-sf and mv's cross-device symlink. Without O_PATH
or O_SEARCH (OpenBSD, Hurd) it falls back to the directory's path.
ln and mv's cross-device move use it next. The no-unique-temp-name
error is now translated.
On Apple targets mknodat is looked up at run time, as it exists only
from macOS 13; before that the node is created by the directory's path.
@abendrothj
abendrothj force-pushed the mv-cross-device-dir-dirfd branch from 0b6fdcb to f0b4cef Compare November 11, 2026 04:22
Sockets and device nodes are recreated like fifos, with their mode, and
the destination is replaced only once the new node exists.

Fixes uutils#13145
Sockets and device nodes in the directory no longer fail the move,
and fifos keep their mode.
Entries are created through the open destination directory, so a symlink
that appears in it during the copy is replaced, not written through.

Closes uutils#14991
@abendrothj
abendrothj force-pushed the mv-cross-device-dir-dirfd branch from f0b4cef to 7f40614 Compare November 11, 2026 04:54

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.

mv: cross-device directory move writes through a symlink that appears in the destination

3 participants