-
Notifications
You must be signed in to change notification settings - Fork 1k
fix!: stop deleting $CARGO_HOME during uninstall #4861
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
base: main
Are you sure you want to change the base?
Changes from all commits
acc938f
14fb229
fa2f7ed
d756bea
5e3c9de
cdf7906
b80beaf
b7663c0
1c41abe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,15 +24,14 @@ | |
| //! During uninstall (`rustup self uninstall`): | ||
| //! | ||
| //! * Delete `$RUSTUP_HOME`. | ||
| //! * Delete everything in `$CARGO_HOME`, including | ||
| //! the rustup binary and its hardlinks | ||
| //! * Delete rustup tool proxy binaries from `$CARGO_HOME`/bin. | ||
| //! * Delete `$CARGO_HOME`/bin if it is empty after uninstall. | ||
| //! | ||
| //! Deleting the running binary during uninstall is tricky | ||
| //! and racy on Windows. | ||
|
|
||
| use std::borrow::Cow; | ||
| use std::env::{self, consts::EXE_SUFFIX}; | ||
| #[cfg(not(windows))] | ||
| use std::io; | ||
| use std::io::Write; | ||
| use std::path::{Component, MAIN_SEPARATOR, Path, PathBuf}; | ||
|
|
@@ -47,7 +46,7 @@ use clap::ValueEnum; | |
| use clap::builder::PossibleValue; | ||
| use clap_cargo::style::{GOOD, WARN}; | ||
| use itertools::Itertools; | ||
| use same_file::Handle; | ||
| use same_file::{Handle, is_same_file}; | ||
| use serde::{Deserialize, Serialize}; | ||
| use tracing::{error, info, trace, warn}; | ||
|
|
||
|
|
@@ -80,7 +79,7 @@ mod shell; | |
| #[cfg(unix)] | ||
| mod unix; | ||
| #[cfg(unix)] | ||
| use unix::{delete_rustup_and_cargo_home, do_add_to_path, do_remove_from_path}; | ||
| use unix::{do_add_to_path, do_remove_from_path}; | ||
| #[cfg(unix)] | ||
| pub(crate) use unix::{run_update, self_replace}; | ||
|
|
||
|
|
@@ -91,7 +90,7 @@ pub use windows::complete_windows_uninstall; | |
| #[cfg(all(windows, feature = "test"))] | ||
| pub use windows::{RegistryGuard, RegistryValueId, USER_PATH, get_path}; | ||
| #[cfg(windows)] | ||
| use windows::{delete_rustup_and_cargo_home, do_add_to_path, do_remove_from_path}; | ||
| use windows::{do_add_to_path, do_remove_from_path}; | ||
| #[cfg(windows)] | ||
| pub(crate) use windows::{run_update, self_replace}; | ||
|
|
||
|
|
@@ -1048,6 +1047,12 @@ async fn maybe_install_rust(opts: InstallOpts<'_>, cfg: &mut Cfg<'_>) -> Result< | |
| Ok(()) | ||
| } | ||
|
|
||
| /// Uninstall process: | ||
| /// 1. Remove rustup home. | ||
| /// 2. Clean up rustup tool proxies. | ||
| /// 3. Remove rustup binary file. | ||
| /// 4. Try to clean up $CARGO_HOME/bin if it's empty. | ||
| /// 5. Upon successfully removing $CARGO_HOME/bin, clean up $PATH. | ||
| pub(crate) fn uninstall( | ||
| no_prompt: bool, | ||
| no_modify_path: bool, | ||
|
|
@@ -1061,7 +1066,8 @@ pub(crate) fn uninstall( | |
|
|
||
| let cargo_home = process.cargo_home()?; | ||
|
|
||
| if !cargo_home.join(format!("bin/rustup{EXE_SUFFIX}")).exists() { | ||
| let rustup_path = cargo_home.join(format!("bin/rustup{EXE_SUFFIX}")); | ||
| if !rustup_path.exists() { | ||
| return Err(CliError::NotSelfInstalled { p: cargo_home }.into()); | ||
| } | ||
|
|
||
|
|
@@ -1090,72 +1096,64 @@ pub(crate) fn uninstall( | |
| utils::remove_dir("rustup_home", &rustup_dir)?; | ||
| } | ||
|
|
||
| info!("removing cargo home"); | ||
| // Clean up rustup tool links | ||
| let cargo_bin_path = cargo_home.join("bin"); | ||
| let proxy_paths = TOOLS | ||
| .iter() | ||
| .chain(DUP_TOOLS.iter()) | ||
| .map(|tool| cargo_bin_path.join(format!("{tool}{EXE_SUFFIX}"))); | ||
|
Cloud0310 marked this conversation as resolved.
|
||
|
|
||
| // Remove CARGO_HOME/bin from PATH | ||
| if !no_modify_path { | ||
| do_remove_from_path(process)?; | ||
| for proxy_path in proxy_paths { | ||
| if is_same_file(&proxy_path, &rustup_path).unwrap_or(false) { | ||
| utils::remove_file("rustup tool proxy", &proxy_path)?; | ||
| } | ||
| } | ||
|
|
||
| // Delete everything in CARGO_HOME *except* the rustup bin | ||
| // Remove rustup executable and then clean up `$CARGO_HOME/bin` if empty. | ||
| // Optionally remove it from $PATH if successfully removed. | ||
| #[cfg(unix)] | ||
| clean_cargo_bin(no_modify_path, process)?; | ||
| // NOTE: On windows, this is *tricky*, | ||
| // the running executable and on Windows can't be unlinked until the process exits. | ||
| // see: windows::{complete_windows_uninstall,clean_cargo_bin} | ||
| #[cfg(windows)] | ||
| windows::clean_cargo_bin(no_modify_path, process)?; | ||
|
|
||
| // First everything except the bin directory | ||
| let diriter = fs::read_dir(&cargo_home).map_err(|e| CliError::ReadDirError { | ||
| p: cargo_home.clone(), | ||
| source: e, | ||
| })?; | ||
| for dirent in diriter { | ||
| let dirent = dirent.map_err(|e| CliError::ReadDirError { | ||
| p: cargo_home.clone(), | ||
| source: e, | ||
| })?; | ||
| if dirent.file_name().to_str() != Some("bin") { | ||
| if dirent.path().is_dir() { | ||
| utils::remove_dir("cargo_home", &dirent.path())?; | ||
| } else { | ||
| utils::remove_file("cargo_home", &dirent.path())?; | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
-1103
to
-1119
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is not clear from reviewing this (presumably Unix-only) commit why this (presumably platform-independent) logic could be removed. |
||
| info!("rustup is uninstalled"); | ||
| Ok(ExitCode::SUCCESS) | ||
| } | ||
|
|
||
| // Then everything in bin except rustup and tools. These can't be unlinked | ||
| // until this process exits (on windows). | ||
| let tools = TOOLS | ||
| .iter() | ||
| .chain(DUP_TOOLS.iter()) | ||
| .map(|t| format!("{t}{EXE_SUFFIX}")); | ||
| let tools: Vec<_> = tools.chain(vec![format!("rustup{EXE_SUFFIX}")]).collect(); | ||
| let bin_dir = cargo_home.join("bin"); | ||
| let diriter = fs::read_dir(&bin_dir).map_err(|e| CliError::ReadDirError { | ||
| p: bin_dir.clone(), | ||
| source: e, | ||
| })?; | ||
| for dirent in diriter { | ||
| let dirent = dirent.map_err(|e| CliError::ReadDirError { | ||
| p: bin_dir.clone(), | ||
| source: e, | ||
| })?; | ||
| let name = dirent.file_name(); | ||
| let file_is_tool = name.to_str().map(|n| tools.iter().any(|t| *t == n)); | ||
| if file_is_tool == Some(false) { | ||
| if dirent.path().is_dir() { | ||
| utils::remove_dir("cargo_home", &dirent.path())?; | ||
| } else { | ||
| utils::remove_file("cargo_home", &dirent.path())?; | ||
| } | ||
| /// Remove the `$CARGO_HOME/bin` directory if it's empty. | ||
| /// On success, remove it from `$PATH` unless `no_modify_path` is set. | ||
| /// If the directory is not empty, emit a warning and return success. | ||
| fn clean_cargo_bin(no_modify_path: bool, process: &Process) -> Result<()> { | ||
| // Remove rustup binary | ||
| let cargo_bin_path = process.cargo_home()?.join("bin"); | ||
| let rustup_path = cargo_bin_path.join(format!("rustup{EXE_SUFFIX}")); | ||
|
|
||
| utils::remove_file("rustup_bin", &rustup_path)?; | ||
|
|
||
| // Remove $CARGO_HOME/bin | ||
| let cargo_bin_path_display = cargo_bin_path.display(); | ||
| info!("removing empty cargo bin directory `{cargo_bin_path_display}`"); | ||
|
|
||
| let Err(e) = fs::remove_dir(&cargo_bin_path) else { | ||
| if !no_modify_path { | ||
| info!("removing cargo bin directory `{cargo_bin_path_display}` from $PATH"); | ||
| do_remove_from_path(process)?; | ||
| } | ||
| } | ||
|
|
||
| info!("removing rustup binaries"); | ||
| return Ok(()); | ||
| }; | ||
|
|
||
| // Delete rustup. This is tricky because this is *probably* | ||
| // the running executable and on Windows can't be unlinked until | ||
| // the process exits. | ||
| delete_rustup_and_cargo_home(process)?; | ||
| if e.kind() == io::ErrorKind::DirectoryNotEmpty { | ||
| warn!("keeping non-empty cargo bin directory `{cargo_bin_path_display}`"); | ||
|
|
||
| info!("rustup is uninstalled"); | ||
| return Ok(()); | ||
| } | ||
|
|
||
| Ok(ExitCode::SUCCESS) | ||
| Err(e) | ||
| .with_context(|| format!("failed to remove cargo bin directory `{cargo_bin_path_display}`")) | ||
| } | ||
|
|
||
| #[derive(Clone, Copy, Debug)] | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -348,16 +348,18 @@ fn has_windows_sdk_libs(process: &Process) -> bool { | |
| false | ||
| } | ||
|
|
||
| /// Run by rustup-gc-$num.exe to delete CARGO_HOME | ||
| /// Run by rustup-gc-$num.exe to delete rustup binary | ||
|
Cloud0310 marked this conversation as resolved.
|
||
| #[tracing::instrument(level = "trace")] | ||
| pub fn complete_windows_uninstall(process: &Process) -> Result<utils::ExitCode> { | ||
| use std::process::Stdio; | ||
|
|
||
| wait_for_parent()?; | ||
|
|
||
| // Now that the parent has exited there are hopefully no more files open in CARGO_HOME | ||
| let cargo_home = process.cargo_home()?; | ||
| utils::remove_dir("cargo_home", &cargo_home)?; | ||
| let no_modify_path = process.var_os(GC_MODIFY_PATH).as_deref() != Some(OsStr::new("1")); | ||
|
|
||
| // Clean up CARGO_HOME/bin if it's empty now | ||
| // On success, also remove it from $PATH. | ||
| super::clean_cargo_bin(no_modify_path, process)?; | ||
|
|
||
| // Now, run a *system* binary to inherit the DELETE_ON_CLOSE | ||
| // handle to *this* process, then exit. The OS will delete the gc | ||
|
|
@@ -374,6 +376,10 @@ pub fn complete_windows_uninstall(process: &Process) -> Result<utils::ExitCode> | |
| Ok(utils::ExitCode(0)) | ||
| } | ||
|
|
||
| // The rustup-gc executable cannot accept normal function call here, | ||
| // so we use env var here, notifying it if we need to remove $CARGO_HOME/bin from $PATH | ||
| const GC_MODIFY_PATH: &str = "RUSTUP_GC_MODIFY_PATH"; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps this should be introduced independently to make it more clear why it is introduced/what it's purpose is? (It should be defined below all callers.) |
||
|
|
||
| pub(crate) fn wait_for_parent() -> Result<()> { | ||
| use std::io; | ||
| use std::mem; | ||
|
|
@@ -644,9 +650,9 @@ pub(crate) fn self_replace(process: &Process) -> Result<utils::ExitCode> { | |
| Ok(utils::ExitCode(0)) | ||
| } | ||
|
|
||
| // The last step of uninstallation is to delete *this binary*, | ||
| // rustup.exe and the CARGO_HOME that contains it. On Unix, this | ||
| // works fine. On Windows you can't delete files while they are open, | ||
| // The last step of uninstallation is to delete *this binary*, rustup.exe. | ||
| // On Unix, this works fine. | ||
| // On Windows you can't delete files while they are open, | ||
| // like when they are running. | ||
| // | ||
| // Here's what we're going to do: | ||
|
|
@@ -659,7 +665,7 @@ pub(crate) fn self_replace(process: &Process) -> Result<utils::ExitCode> { | |
| // processes created with the option to inherit handles | ||
| // will also keep them open. | ||
| // - Run the gc exe, which waits for the original rustup.exe | ||
| // process to close, then deletes CARGO_HOME. This process | ||
| // process to close, then deletes rustup.exe. This process | ||
| // has inherited a FILE_FLAG_DELETE_ON_CLOSE handle to itself. | ||
| // - Finally, spawn yet another system binary with the inherit handles | ||
| // flag, so *it* inherits the FILE_FLAG_DELETE_ON_CLOSE handle to | ||
|
|
@@ -674,7 +680,7 @@ pub(crate) fn self_replace(process: &Process) -> Result<utils::ExitCode> { | |
| // | ||
| // .. augmented with this SO answer | ||
| // https://stackoverflow.com/questions/10319526/understanding-a-self-deleting-program-in-c | ||
| pub(crate) fn delete_rustup_and_cargo_home(process: &Process) -> Result<()> { | ||
| pub(crate) fn clean_cargo_bin(no_modify_path: bool, process: &Process) -> Result<()> { | ||
| use std::io; | ||
| use std::ptr; | ||
| use std::thread; | ||
|
|
@@ -734,6 +740,7 @@ pub(crate) fn delete_rustup_and_cargo_home(process: &Process) -> Result<()> { | |
| }; | ||
|
|
||
| Command::new(gc_exe) | ||
| .env(GC_MODIFY_PATH, if no_modify_path { "0" } else { "1" }) | ||
| .spawn() | ||
| .context(CliError::WindowsUninstallMadness)?; | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest introducing this in a separate first commit, and then updating it in each commit (where necessary) as a way of documenting what is changed.
View changes since the review