Skip to content

mv: keep copying xattrs after one fails in a cross-device move - #15049

Merged
sylvestre merged 3 commits into
uutils:mainfrom
Cassian433:mv-keep-copying-xattrs-after-failure
Oct 4, 2026
Merged

sylvestre merged 3 commits into
uutils:mainfrom
Cassian433:mv-keep-copying-xattrs-after-failure

Conversation

@Cassian433

Copy link
Copy Markdown
Contributor

Closes #14598

On a cross-device move, copy_xattrs, copy_xattrs_fd and copy_xattrs_skip_selinux returned at the first attribute they could not set, and mv dropped that error with let _ =. Every attribute listed after the failing one was lost, with no message and exit status 0. Moving a file from tmpfs to ext4 with one 8000-byte user.* value is enough to hit it.

The helpers now try every attribute and return the ones that failed along with their errors. mv prints setting attribute 'NAME': ERROR for each one, except ENOTSUP/EOPNOTSUPP, and still finishes the move with exit 0. That is what the GNU manual describes ("Upon failure all but 'Operation not supported' warnings are output"), and GNU 9.4 does the same here, apart from printing the name twice. cp uses the same helpers, so cp --preserve=xattr now copies the remaining attributes too and still reports the first failure as before.

The new test moves a file, and a directory containing a file, from /dev/shm into the test directory. Each file has five attributes, two of them too large for ext4. The names are interleaved so that a failing attribute is listed before one that must be kept, whichever way tmpfs sorts them. The test checks that both failures are reported, that the move succeeds and that the three small attributes arrive. It skips when the destination accepts large values. It fails on main.

The directory's own xattrs go through apply_xattrs_fd, which #14971 is reworking, so I left that path alone.

Comment thread src/uu/cp/src/cp.rs
copy_xattrs(source, dest)
};
// Every attribute has been tried; report the first one that failed.
let copy_xattrs_result = copy_xattrs_result

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 changes cp behavior too, please add a test in tests/by-util/test_cp.rs as well

Comment thread src/uucore/src/lib/features/fsxattr.rs Outdated
/// callers where xattr preservation is best-effort.
fn without_unsupported(
result: std::io::Result<Vec<(OsString, std::io::Error)>>,
) -> std::io::Result<Vec<(OsString, std::io::Error)>> {

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.

std::io::Result<Vec<(OsString, std::io::Error)>> is repeated 6 times, could you please add a type alias?

Comment thread src/uucore/src/lib/features/fsxattr.rs Outdated
if attr_name.as_bytes() == b"security.selinux" {
continue;
}
let result = xattr::get(&source, &attr_name).and_then(|value| match value {

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 is almost the same loop as in copy_xattrs, could be dedup, no? (e.g. a shared helper taking a filter)

@Cassian433

Copy link
Copy Markdown
Contributor Author

Thanks for the review, all three are done in the new commit.

  • cp: added test_cp_preserve_xattr_failure_keeps_the_rest, which copies a file and a directory out of /dev/shm with --preserve=xattr and checks that the attributes after a refused one are still there. It fails if the loop goes back to stopping at the first error.
  • Added XattrCopyResult in fsxattr and used it everywhere that type was spelled out.
  • The three loops now share one private helper, copy_each_xattr, which takes the attribute names, a filter and get/set closures. That covers both the path and fd versions, and the SELinux skip is just the filter.

@codspeed

codspeed Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Merging this PR will improve performance by 88.26%

⚠️ 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

⚡ 10 improved benchmarks
✅ 324 untouched benchmarks
⏩ 117 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
⚡ Memory ls_recursive_balanced_tree[(6, 4, 15)] 1,850 KB 111.1 KB ×17
⚡ Memory ls_recursive_long_all_balanced_tree[(6, 4, 15)] 2,059.1 KB 320.2 KB ×6.4
⚡ Simulation three_39_bit_primes 860 ms 481.9 ms +78.45%
⚡ Memory ls_recursive_deep_tree[(200, 2)] 159.5 KB 125 KB +27.66%
⚡ Simulation five_38_bit_primes 1.9 s 1.5 s +27.6%
⚡ Memory ls_recursive_long_all_mixed_tree 142.3 KB 118.4 KB +20.16%
⚡ Memory ls_recursive_mixed_tree 141.6 KB 117.9 KB +20.08%
⚡ Memory ls_recursive_long_all_deep_tree[(100, 4)] 133.7 KB 117 KB +14.34%
⚡ Simulation ls_recursive_mixed_tree 2.8 ms 2.7 ms +4.98%
⚡ Simulation ls_recursive_balanced_tree[(6, 4, 15)] 118.9 ms 114.6 ms +3.72%

Tip

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


Comparing Cassian433:mv-keep-copying-xattrs-after-failure (a68a26d) with main (e7c9f31)2

Open in CodSpeed

Footnotes

  1. 117 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 (f56fe41) during the generation of this report, so e7c9f31 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

@sylvestre
sylvestre enabled auto-merge (rebase) October 4, 2026 16:39
The xattr copy loops in uucore returned at the first attribute they could
not copy, and mv threw that error away. A cross-device move then dropped
every attribute after the failing one without a word and exited 0.

The copy helpers now try every attribute and return the ones that failed
with their errors. mv reports each of them, except ENOTSUP/EOPNOTSUPP, and
still completes the move with exit status 0, as GNU does. cp goes through
the same helpers, so it now copies the remaining attributes too and still
reports the first failure as before.

Closes uutils#14598
@sylvestre
sylvestre force-pushed the mv-keep-copying-xattrs-after-failure branch from cd0e604 to a68a26d Compare October 4, 2026 16:52
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown

GNU testsuite comparison:

Skipping an intermittent issue tests/date/resolution (passes in this run but fails in the 'main' branch)

@sylvestre
sylvestre merged commit 154be8e into uutils:main Oct 4, 2026
177 of 188 checks passed
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 move drops the rest of the xattrs after one failure, and reports success

2 participants