When sync_tree is used to copy a directory containing Windows directory symlinks, the resulting copy unexpectedly contains file symlinks instead.
To reproduce the problem, set up a test directory in a Windows shell:
> mkdir test
> mkdir test/real
> cd test
> mklink /D link real
> dir
[...]
Directory of C:\Users\itmgr\test
07/02/2024 04:07 PM <DIR> .
07/03/2024 08:42 AM <DIR> ..
07/02/2024 04:06 PM <SYMLINKD> link [real]
07/02/2024 04:06 PM <DIR> real
[...]
Note that the type of the link is SYMLINKD, indicating a directory link. (This corresponds to the flag SYMBOLIC_LINK_FLAG_DIRECTORY in the Windows API function CreateSymbolicLink.)
Now use sync_tree to copy the directory test somewhere else; the link type changes to SYMLINK:
[...]
Directory of C:\Users\itmgr\synced
07/02/2024 04:07 PM <DIR> .
07/03/2024 08:42 AM <DIR> ..
07/02/2024 04:13 PM <SYMLINK> link [real]
07/02/2024 04:06 PM <DIR> real
[...]
The changed symlink causes the Windows function GetFinalPathNameByHandle to raise API error 123: "The filename, directory name, or volume label syntax is incorrect." The consequence is that the symlink breaks tools using Rust's fs::canonicalize or Python's os.path.realpath(..., strict=True). (The non-strict version of the Python function catches OS error 123 and uses a workaround to still read the link.)
When
sync_tree
is used to copy a directory containing Windows directory symlinks, the resulting copy unexpectedly contains file symlinks instead.To reproduce the problem, set up a test directory in a Windows shell:
Note that the type of the link is
SYMLINKD
, indicating a directory link. (This corresponds to the flagSYMBOLIC_LINK_FLAG_DIRECTORY
in the Windows API functionCreateSymbolicLink
.)Now use
sync_tree
to copy the directorytest
somewhere else; the link type changes toSYMLINK
:The changed symlink causes the Windows function
GetFinalPathNameByHandle
to raise API error 123: "The filename, directory name, or volume label syntax is incorrect." The consequence is that the symlink breaks tools using Rust'sfs::canonicalize
or Python'sos.path.realpath(..., strict=True)
. (The non-strict version of the Python function catches OS error 123 and uses a workaround to still read the link.)