Skip to content

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

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

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

Conversation

@abendrothj

@abendrothj abendrothj commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

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. An entry that shows up at a name is replaced through replace_entry_at. 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. 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 final directory xattr reopen. Run on macOS and Linux. On Debian GNU/Hurd the mv tests fail only where main does (the new ones are Linux-only), and a cross-device move of a 0640 FIFO, alone or in a directory, keeps its mode as with GNU 9.10. AIX is only cross-checked with clippy.

Based on #15123; until the stack below it merges, the diff here shows those commits too.

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 degrade performance by 10.68%

⚡ 7 improved benchmarks
❌ 13 regressed benchmarks
✅ 379 untouched benchmarks
⏩ 54 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
❌ Simulation three_39_bit_primes 371.3 ms 613.8 ms -39.5%
❌ Memory dd_copy_default 19.7 KB 28.6 KB -31.08%
❌ Memory dd_copy_4k_blocks 23.1 KB 31.9 KB -27.83%
❌ Memory dd_copy_partial 23.1 KB 32 KB -27.82%
❌ Memory dd_copy_with_seek 23.4 KB 32.3 KB -27.56%
❌ Memory dd_copy_with_skip 23.4 KB 32 KB -26.89%
❌ Memory dd_copy_8k_blocks 27.1 KB 35.6 KB -24.1%
❌ Simulation five_38_bit_primes 1.7 s 2.2 s -21.62%
❌ Simulation true_consecutive_calls 294.7 ns 350.2 ns -15.86%
❌ Memory dd_copy_64k_blocks 83.1 KB 91.9 KB -9.67%
❌ Simulation dd_copy_partial 717.6 µs 755.5 µs -5.01%
❌ Simulation hostname_ip_lookup[100000] 169.1 µs 178 µs -4.99%
❌ Memory dd_copy_separate_blocks 185.4 KB 194.8 KB -4.83%
⚡ Simulation split_lines 11.1 ms 8.5 ms +29.92%
⚡ Simulation split_numeric_suffix 11.3 ms 8.7 ms +29.23%
⚡ Simulation tsort_complex_dag[50000] 97.2 ms 87.6 ms +10.86%
⚡ Simulation tsort_tree_dag[(10, 3)] 39.4 ms 36.5 ms +7.93%
⚡ Simulation tsort_wide_dag[100000] 167 ms 157.6 ms +5.98%
⚡ Simulation tsort_linear_chain[1000000] 2 s 1.9 s +4.99%
⚡ Simulation thirteen_39_bit_primes 9 s 8.6 s +4.52%

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 (b3b9dde) with main (8cf2e4f)

Open in CodSpeed

Footnotes

  1. 54 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.

@codspeed

codspeed Bot commented Oct 7, 2026

Copy link
Copy Markdown

Unable to generate the flame graphs

The performance report has correctly been generated, but there was an internal error while generating the flame graphs for this run. We're working on fixing the issue. Feel free to contact us on Discord or at support@codspeed.io if the issue persists.

Open a directory readable, and on EACCES retry with O_PATH or O_SEARCH,
which anchor *at calls without read access. The targets with either flag
are named by the has_o_path and has_o_search cfg aliases. If the retry
fails, its own error is returned.
Move the temporary name and renameat logic of replace_link into
replace_entry_at, which creates the entry through a callback relative to
an open directory, so other callers can replace entries the same way.

The parent is now opened with DirFd::open_anchor, so ln -sf replacing a
link in a directory with write and search but no read permission works,
as it does with GNU, instead of failing with EACCES.
Create fifos, sockets and device nodes relative to an open directory.
nix and rustix do not provide mknodat on Apple targets, so this calls
libc directly.
A cross-device move of a socket or device node went through the
regular file copy, which removed the destination and then failed to
open the source. A fifo also removed the destination before creating
the new one, and lost its mode.

Fifos, sockets and device nodes are now created with their mode and
ownership in a private directory next to the destination and renamed
over it, so the destination is kept if the node cannot be created, and
the ownership and mode cannot land on another file linked over the
destination name meanwhile. The private directory is made 0700 by name
right after it is created, before it is opened, without changing the
umask; the check after the open rejects a directory moved there in
between. As for regular files, setuid and setgid are dropped when the
ownership cannot be kept.

Fixes uutils#13145
Sockets and device nodes inside a directory moved across devices went
through the regular file copy, which cannot open them, so the move
failed. Fifos were recreated with mode 0666 minus the umask. Recreate
all of them with copy_special_file, which keeps the mode.
A caller creating a file can then keep writing through the descriptor it
opened. Also make link_at public, for creating links relative to an
open directory, and add DirFd::open_subdir_anchor, which opens a
subdirectory without following a symlink and, where the platform allows,
without needing read permission, for creating entries in it.
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 while the
copy was running was followed, so the content went wherever it pointed.

Hold each destination directory open, search-only where possible, and
create files, subdirectories, symlinks, special files and hard links
relative to it without following symlinks. An entry that appears at a
name is replaced through replace_entry_at. Regular files share
copy_file_data with the single file fallback, which sets ownership and
mode through the descriptor. The names of each source directory are read
before descending, so only one descriptor stays open per level.

Closes uutils#14991
@abendrothj
abendrothj force-pushed the mv-cross-device-dir-dirfd branch from b3b9dde to 52022fc Compare October 7, 2026 08:21

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