Conversation
| // File owns the delete-on-close handle until it is passed to Command below. | ||
| let gc_handle = OpenOptions::new() | ||
| .read(true) | ||
| .share_mode(FILE_SHARE_READ | FILE_SHARE_DELETE) | ||
| .custom_flags(FILE_FLAG_DELETE_ON_CLOSE) | ||
| .open(&gc_exe) | ||
| .context(CliError::WindowsUninstallMadness)?; | ||
|
|
||
| Command::new(gc_exe) | ||
| // Keep Command alive through the sleep so it retains our deletion handle. | ||
| let mut command = Command::new(gc_exe); | ||
| command | ||
| .stdin(gc_handle) |
There was a problem hiding this comment.
Question: Would you mind explaining to me why this refactoring action produces equivalent code? It's not immediately clear to me at least when looking at this...
There was a problem hiding this comment.
The key is preserving the delete-on-close handle through this chain:
File
-> Command::stdin(file)
-> DuplicateHandle(..., inherit=true, DUPLICATE_SAME_ACCESS)
-> GC’s stdin
-> inherited by net
Rust’s Windows implementation explicitly uses:
Stdio::Handle(ref handle) => handle.duplicate(0, true, c::DUPLICATE_SAME_ACCESS),
ref
Then, internally it calls:
sys::c::DuplicateHandle(
cur_proc,
handle,
cur_proc,
&mut ret,
access,
inherit as sys::c::BOOL,
options,)Keeping the handle and options. ref
The duplicate references the same file object, and FILE_FLAG_DELETE_ON_CLOSE explicitly includes duplicated handles in its cleanup lifetime.
FILE_FLAG_DELETE_ON_CLOSE: The file is to be deleted immediately after all of its handles are closed, which includes the specified handle and any other open or duplicated handles.
If there are existing open handles to a file, the call fails unless they were all opened with the FILE_SHARE_DELETE share mode. Windows documentation
The named Command retains the parent’s handle through the existing sleep, replacing the scope guard. Removing net’s Stdio::null() lets it inherit the handle from GC.
So this preserves the intended cleanup-handle lifetime, while changing stdin into the handle-passing mechanism; GC does not read it.
There was a problem hiding this comment.
Also added comment for this in code.
There was a problem hiding this comment.
To be honest this does feel like a surprising hack. Would it not be clearer to call DuplicateHandle ourselves? It's a bit of unsafe but not too much.
There was a problem hiding this comment.
To be honest this does feel like a surprising hack. Would it not be clearer to call
DuplicateHandleourselves? It's a bit of unsafe but not too much.
Maybe I was trying too hard for explain this by telling the whole underlaying implementation details here, and this might be the reason why you think using DuplicateHandle with unsafe is more intuitive here.
Basically,we're doing two things with old code:
- Creating a handle with
FILE_FLAG_DELETE_ON_CLOSE. - Letting the sub command inherit this handle.
In the new code, the first thing is done with:
let gc_handle = OpenOptions::new()
.read(true)
.share_mode(FILE_SHARE_READ | FILE_SHARE_DELETE)
.custom_flags(FILE_FLAG_DELETE_ON_CLOSE)
.open(&gc_exe)
.context(CliError::WindowsUninstallMadness)?;The second thing (THE ODD "HACK") is done with:
let mut command = Command::new(gc_exe);
command
.stdin(gc_handle)By passing the stdin a handle is just what Command::stdin docs does:
Configuration for the child process’s standard input (stdin) handle.
Defaults to inherit when used with spawn or status, and defaults to piped when used with output.
Then it's completely ok for passing it a handle with configured options, then it inherits.
So, naturally, we do the same as old code.
However, I do think I should refine the comments more on this for justifying why passing Stdio a file handle is the doing the inherition here. Thanks for letting me knowing this :) @ChrisDenton
There was a problem hiding this comment.
BTW, when thinking of this old code piece, I nocited there might be a further improvement: replace old scopeguard::guard with std::os::windows::io::OwnedHandle.
Use OpenOptions and inherited stdin to manage the GC handle with standard file APIs. Keep Command alive through the existing sleep to retain the handle.
Inline the single-use ensure_empty helper and replace GcErr with an inline error. Preserve the existing directory and GC filename filter.
aa884c5 to
45817dc
Compare
|
This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
Attempt the existing GC self-cleanup even if waiting for the parent or removing cargo-home state fails. Preserve the original uninstall error when starting cleanup also fails; report the cleanup error when uninstalling succeeded.
|
☔ The latest upstream changes (possibly #5091) made this pull request unmergeable. Please resolve the merge conflicts. |
|
New findings on windows uninstallation:
Note
So maybe we can safely remove all the madness about Windows unintallation and self replacement. |
|
Rustup already does not support FAT32 so I'm not worried on that front. However, I don't think posix semantics completely removes the need for some degree of extra ceremony. It does not bypass locks and anti-malware software can still cause havoc. Though I think most of this can be mitigate using hard links to rustup's |
On the cleanup side, I don't think moving this to However, there's a mechanism of Windows NTFS we can make use of: ADS (Alternate Data Streams), which is used for creating alternative backups of a data, and it's removed once the main file is gone. This also allows for self delete, and seld replacement. Still, the idea is shared by @baka-gourd and it has a C demo here: https://gist.github.com/codehz/fa3340806d67eca90aec89a33852d707 |
|
I wouldn't call something "bloat" if it's a necessary part of supporting users, however unfortunate it may be.
I'm not sure that solves a problem we have. Currently the hack we use is to ultimately piggyback on a system executable. Not great but it seems to work out ok. We could replace that with the directory trick but, aside from being more complex, it has most of the same issues. We still need to try to delete many files that may be in use or have locks or have A/V inspecting them. Plus hiding executables is a very malware thing to do so I worry that could trigger heuristics. If we wanted to simplify things a bit we could make use of the |
|
I guess by just copying Another point is that, we don't need to open another program Basically the old way is:
The new way in this would be:
But I do agree with your opinion upon:
And I have also investigated the I've already tested on Windows Server 2025 for POC of ADS problem, and it's working just right. |
|
As I discussed with @rami3l, he thought this ADS trick also helps with his goal of concurrent safe rustup. |
There was a problem hiding this comment.
Looking at all but the first commit, I think the changes here are quite reasonable.
As for the ADS experiment, I think it is more or less independent from the changes proposed here and there is no blocking relationship from one to another, so maybe it can be in a separate draft, given that it doesn't influence our prioritization on #5056.
@ChrisDenton as for the first commit I think I think I can entrust you with your judgement on whether "a certain amount of unsafe" is better here in terms of readability? Does that sound okay to you?
part of #5056, first of Windows GC code fixes.
Including:
GCErrthat only used once