Skip to content

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

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

abendrothj wants to merge 6 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 two commits are new here (+412/−96).

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 DirFd::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; util/check-libc-interposition.sh now checks this for mv. 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.

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 improve performance by 12.06%

1 warning, 1 note

⚠️ 123 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
✅ 397 untouched benchmarks
⏩ 70 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
⚡ CPU Simulation complex_relative_date 386.2 µs 279 µs +38.42%
⚡ CPU Simulation hostname_ip_lookup[100000] 176 µs 165.7 µs +6.17%
⚡ CPU Simulation cksum_raw_output 33.4 ms 32.1 ms +3.97%
⚡ CPU Simulation cksum_default 33.1 ms 32.1 ms +3.2%

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:mv-cross-device-dir-dirfd (e4aae21) with main (679d1f2)2

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

  2. No successful run was found on main (eb7d426) during the generation of this report, so 679d1f2 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

@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!
Congrats! The gnu test tests/dd/nocache_fail is now passing!
Note: The gnu test tests/rm/rm-readdir-fail was skipped on 'main' but is now 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.

Move the temporary name and renameat logic of replace_link into
DirFd::replace_entry_at, which creates the entry through a callback
relative to the directory, so other callers can replace entries the
same way.

The parent is now opened with DirFd::open_anchor, so ln -sf and cp -sf
replacing an entry in a directory with write and search but no read
permission work, as they do 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.
@abendrothj
abendrothj force-pushed the mv-cross-device-dir-dirfd branch 2 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.

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 DirFd::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.

util/check-libc-interposition.sh now also checks a cross-device mv under
fakeroot, so the ownership change stays visible to LD_PRELOAD wrappers.

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

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