Skip to content

Preserve Windows directory symlinks in cp#12741

Open
caomengxuan666 wants to merge 2 commits into
uutils:mainfrom
caomengxuan666:fix-windows-cp-directory-symlink-upstream
Open

Preserve Windows directory symlinks in cp#12741
caomengxuan666 wants to merge 2 commits into
uutils:mainfrom
caomengxuan666:fix-windows-cp-directory-symlink-upstream

Conversation

@caomengxuan666

@caomengxuan666 caomengxuan666 commented Jun 9, 2026

Copy link
Copy Markdown

Summary

  • preserve Windows directory symlinks when cp recreates symlinks
  • choose symlink_dir on Windows when the source symlink metadata has the directory attribute
  • strengthen the existing directory symlink copy test to verify the copy is usable as a directory

This also addresses microsoft/coreutils#106 downstream.

Verification

  • cargo fmt -p uu_cp -- --check
  • cargo check -p uu_cp
  • manual elevated Windows runtime repro:
    • created hello_world_symlink as <SYMLINKD> [hello_world]
    • ran target\debug\cp.exe -a hello_world_symlink test
    • verified copied test is <SYMLINKD> [hello_world]
    • verified dir test works and test\file.txt is readable

@caomengxuan666 caomengxuan666 force-pushed the fix-windows-cp-directory-symlink-upstream branch from 4f7d22e to e184213 Compare June 9, 2026 11:20
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

GNU testsuite comparison:

Skip an intermittent issue tests/date/resolution (fails in this run but passes in the 'main' branch)
Skipping an intermittent issue tests/misc/tty-eof (passes in this run but fails in the 'main' branch)
Skipping an intermittent issue tests/tail/pid-pipe (passes in this run but fails in the 'main' branch)

Comment thread src/uu/cp/src/cp.rs Outdated
Comment thread src/uu/cp/src/cp.rs Outdated
Comment thread tests/by-util/test_cp.rs
@sylvestre

Copy link
Copy Markdown
Contributor

@lhecker wdyt ?

@caomengxuan666

caomengxuan666 commented Jun 9, 2026

Copy link
Copy Markdown
Author

Thanks, updated.

Comment thread src/uu/cp/src/cp.rs Outdated
#[cfg(target_os = "wasi")] _symlinked_files: &mut HashSet<FileInformation>,
) -> CopyResult<()> {
#[cfg(not(windows))]
let _ = source_type_hint;

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 isn't enough...

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok,I would refactor it.I previously had a misunderstanding about this.

@caomengxuan666 caomengxuan666 force-pushed the fix-windows-cp-directory-symlink-upstream branch from 02c2cb1 to 0b7f1cf Compare June 9, 2026 19:12
Comment thread src/uu/cp/src/cp.rs Outdated
Ok(())
}

#[cfg(windows)]

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.

it is not duplicating a lot of code ...

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. I kept the platform-specific signatures, but moved the shared creation/error handling/recording path into helper functions to avoid duplicating that logic.

@lhecker lhecker Jun 9, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, this PR would be a lot easier to review if it was closer to the original structure.
Edit: Ah, GitHub hadn't updated to show me your comment.

@caomengxuan666 caomengxuan666 Jun 9, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated again. I kept the original symlink_file cfg block layout and only added a Windows hint argument plus the symlink_dir / symlink_file selection. Non-Windows no longer needs a dummy parameter.

@caomengxuan666 caomengxuan666 force-pushed the fix-windows-cp-directory-symlink-upstream branch 3 times, most recently from 825e3fe to 347ba06 Compare June 9, 2026 19:39

@lhecker lhecker left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The diff is a lot tighter now which is nice.

Comment thread src/uu/cp/src/cp.rs Outdated
fn symlink_file(
source: &Path,
dest: &Path,
#[cfg(windows)] source_type_hint: &Path,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sylvestre Do you know how cp's dereference mode interacts with copying symlinks? Because if it doesn't, then we can just pass source_metadata here. That already comes from walkdir's metadata and when options.dereference == false, then it refers to the symlink's metadata.

Comment thread src/uu/cp/src/cp.rs Outdated
Comment on lines +2922 to +2923
#[cfg(windows)]
source,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this correct? My reading of the function was that it intentionally dereferences the symlink and then copies the symlink target (aka link), so whether source was a symlink or not shouldn't be relevant right?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are right here.

link is only the target path from read_link, and what we need on Windows is the symlink type from the source symlink itself.

So passing source_metadata may be a better way than passing source and stat it again. I can update it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. copy_link now uses the existing source_metadata as the Windows symlink type hint.

@caomengxuan666 caomengxuan666 force-pushed the fix-windows-cp-directory-symlink-upstream branch 2 times, most recently from 23e721b to 1beed0e Compare June 10, 2026 07:36
@codspeed-hq

codspeed-hq Bot commented Jun 10, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

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

⚡ 3 improved benchmarks
❌ 1 regressed benchmark
✅ 319 untouched benchmarks
⏩ 46 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory cp_recursive_deep_tree[(120, 4)] 562.5 KB 615.6 KB -8.62%
Simulation ls_recursive_balanced_tree[(6, 4, 15)] 52.3 ms 49.6 ms +5.5%
Simulation ls_recursive_wide_tree[(10000, 1000)] 34.3 ms 33 ms +3.99%
Simulation single_date_now 85.6 µs 82.8 µs +3.35%

Tip

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


Comparing caomengxuan666:fix-windows-cp-directory-symlink-upstream (d33d0cd) with main (f406ea0)

Open in CodSpeed

Footnotes

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

@lhecker lhecker left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR appears correct to me now, with just one nit left.

Comment thread src/uu/cp/src/cp.rs Outdated
Comment on lines +1992 to +1999
const FILE_ATTRIBUTE_DIRECTORY: u32 = 0x10;

let file_type = source_metadata.file_type();
if file_type.is_symlink() {
source_metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0
} else {
file_type.is_dir()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have read the implementation of is_dir() and found that it basically checks !is_symlink && is_directory where the latter checks for FILE_ATTRIBUTE_DIRECTORY. So, this can be simplified to:

Suggested change
const FILE_ATTRIBUTE_DIRECTORY: u32 = 0x10;
let file_type = source_metadata.file_type();
if file_type.is_symlink() {
source_metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0
} else {
file_type.is_dir()
}
const FILE_ATTRIBUTE_DIRECTORY: u32 = 0x10;
source_metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated, simplified as suggested.

@caomengxuan666 caomengxuan666 force-pushed the fix-windows-cp-directory-symlink-upstream branch from 1beed0e to d33d0cd Compare June 10, 2026 11:38
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.

3 participants