Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use files instead of junctions on Windows #11269

Open
wants to merge 4 commits into
base: charlie/version
Choose a base branch
from

Conversation

charliermarsh
Copy link
Member

@charliermarsh charliermarsh commented Feb 6, 2025

Summary

Instead of using junctions, we can just write files that contain (as the file contents) the target path. This requires a little more finesse in that, as readers, we need to know where to expect these. But it also means we get to avoid junctions, which have led to a variety of confusing behaviors. Further, replace_symlink should now be on atomic on Windows.

Closes #11263.

@charliermarsh charliermarsh added the no-build Disable building binaries in CI label Feb 6, 2025
@charliermarsh charliermarsh force-pushed the charlie/sym branch 7 times, most recently from a4bc912 to 4e21181 Compare February 6, 2025 01:54
@charliermarsh charliermarsh added the windows Specific to the Windows platform label Feb 6, 2025
@charliermarsh charliermarsh marked this pull request as ready for review February 6, 2025 02:02
@charliermarsh charliermarsh force-pushed the charlie/sym branch 2 times, most recently from 7a01a26 to 4a3c71c Compare February 6, 2025 02:08
@charliermarsh charliermarsh requested a review from zanieb February 6, 2025 02:18
@charliermarsh
Copy link
Member Author

We might be able to remove some of the extra flock usages we have on Windows after this, but I want to test and evaluate that separately. The proximate motivation here is that Ofek / Datadog are seeing a really strange behavior in their Windows container whereby we had a junction that... didn't point to anything? Like, it's not that the target was gone, the target was empty.

),
));
}
// First, attempt to create a file at the location, but fail if it doesn't exist.
Copy link
Member

@konstin konstin Feb 6, 2025

Choose a reason for hiding this comment

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

Does this intend to say "fail if it does exist"?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes!

Comment on lines 66 to 67
let temp_dir = tempfile::tempdir_in(dst.as_ref().parent().unwrap())?;
let temp_file = temp_dir.path().join("link");
Copy link
Member

Choose a reason for hiding this comment

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

Do we need a tempdir or can we directly create a tempfile with a random name in the directory?

Copy link
Member

@BurntSushi BurntSushi left a comment

Choose a reason for hiding this comment

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

Rolling your own symlinks because Windows-native symlinks are so bad... It's just crazy enough to work! Haha.

I made a suggestion about how to avoid the lossy call here. Sorry about being unclear last night.

I also wonder if perhaps we can emit some more logs in places, but I defer to you on that. I don't have a ton of context on this area of the code.

.path()
.file_name()
.and_then(|file_name| file_name.to_str())
else {
Copy link
Member

Choose a reason for hiding this comment

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

Should this be an error? Or perhaps a WARN log or something?

else {
continue;
};
if WheelFilename::from_stem(filename).is_err() {
Copy link
Member

Choose a reason for hiding this comment

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

Same here?

if WheelFilename::from_stem(filename).is_err() {
continue;
}
if let Ok(target) = uv_fs::resolve_symlink(entry.path()) {
Copy link
Member

Choose a reason for hiding this comment

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

And here.

Although I guess we weren't emitting logs before. I wonder if it might help debugging if something goes wrong here.

Ok(mut file) => {
// Write the target path to the file.
use std::io::Write;
file.write_all(src.as_ref().to_string_lossy().as_bytes())?;
Copy link
Member

Choose a reason for hiding this comment

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

Oh, okay, now that I know what you are doing here, I think I can suggest something more bullet proof that isn't too much work. Basically, on Windows, file paths are just arbitrary sequences of u16 (with some restrictions), just like on Unix, file paths are just arbitrary sequences of u8. So the way to avoid the lossy conversion here is to just store the u16 sequence. You can get the raw u16 sequence from this API in std: https://doc.rust-lang.org/std/os/windows/ffi/trait.OsStrExt.html

Once you have a Vec<u16> build in memory, create a Vec<u8> with twice the length and then use byteorder to (safely) write the &[u16] into a &mut [u8]: https://docs.rs/byteorder/latest/byteorder/trait.ByteOrder.html#tymethod.write_u16_into

Then save that to the file.

To go in the other direction, use ByteOrder::read_u16_into and std::os::windows::ffi::OsStringExt.

When using byteorder, I'd suggest picking a specific byteorder (byteorder::BigEndian) and always using it. Regardless of the endianness of the host platform. That way, the file format is portable.

Copy link
Member Author

Choose a reason for hiding this comment

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

Awesome, thank you!

@charliermarsh charliermarsh requested a review from Gankra February 6, 2025 17:14
@charliermarsh charliermarsh changed the base branch from main to charlie/version February 7, 2025 02:34
@charliermarsh
Copy link
Member Author

Ok, I simplified things a bit (I think). Instead of "generically" using these routines to read and write symlinks, they're now methods on the cache specifically framed at writing links to archive entries and reading links to archive entries.

@charliermarsh
Copy link
Member Author

(I didn't mean to close, accidental click.)

@ofek
Copy link
Contributor

ofek commented Feb 7, 2025

In the future, would it be possible to detect symlink capability on Windows and use this new approach as a fallback?

@charliermarsh charliermarsh added this to the v0.6.0 milestone Feb 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
no-build Disable building binaries in CI windows Specific to the Windows platform
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Replace junctions with "fake" symlinks on Windows
4 participants