Skip to content
104 changes: 41 additions & 63 deletions src/cli/self_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@
//! * update the PATH in a system-specific way
//! * run the equivalent of `rustup default stable`
//!
//! During upgrade (`rustup self upgrade`):
//! During upgrade (`rustup self update`):
//!
//! * download rustup-init to $CARGO_HOME/bin/rustup-init
//! * run rustup-init with appropriate flags to indicate
//! this is a self-upgrade
//! * rustup-init copies bins and hardlinks into place. On windows
//! this happens *after* the upgrade command exits successfully.
//! * download rustup-init to a managed path under `$RUSTUP_HOME`
//! * run the downloaded binary in replacement mode
//! * atomically replace rustup and update its proxy links. On Windows
//! this happens after the update command exits.
//!
//! During uninstall (`rustup self uninstall`):
//!
Expand Down Expand Up @@ -77,29 +76,32 @@ use crate::{
#[macro_use]
mod msg;

mod stage;
use stage::{PreparedUpdater, SelfUpdateLock};

#[cfg(unix)]
mod shell;

#[cfg(unix)]
mod unix;
#[cfg(unix)]
use unix::{do_add_to_path, do_remove_from_path};
pub(crate) use unix::self_replace;
#[cfg(unix)]
pub(crate) use unix::{run_update, self_replace};
use unix::{do_add_to_path, do_remove_from_path, run_update};

#[cfg(windows)]
mod windows;
#[cfg(windows)]
pub use windows::complete_windows_uninstall;
#[cfg(windows)]
pub(crate) use windows::self_replace;
#[cfg(all(windows, feature = "test"))]
pub use windows::{RUSTUP_REGISTRY_TEST_ID, RegistryValueId, USER_PATH, get_path};
#[cfg(windows)]
use windows::{
add_uninstall_registry_entry, do_add_to_path, do_remove_from_path,
remove_uninstall_registry_entry,
remove_uninstall_registry_entry, run_update,
};
#[cfg(windows)]
pub(crate) use windows::{run_update, self_replace};

pub(crate) struct InstallOpts<'a> {
pub default_host_tuple: Option<String>,
Expand Down Expand Up @@ -533,10 +535,10 @@ impl SelfUpdateMode {
SelfUpdatePermission::Permit => {}
}

let setup_path = prepare_update(dl_cfg).await?;
let prepared_updater = prepare_update(dl_cfg).await?;

if let Some(setup_path) = &setup_path {
return run_update(setup_path, dl_cfg.process);
if let Some(prepared_updater) = prepared_updater {
return run_update(prepared_updater, dl_cfg.process);
} else {
// Try again in case we emitted "tool `{}` is already installed" last time.
install_proxies(dl_cfg.process)?;
Expand Down Expand Up @@ -583,8 +585,6 @@ impl fmt::Display for SelfUpdateMode {
}
}

static DEFAULT_UPDATE_ROOT: &str = "https://static.rust-lang.org/rustup";

fn update_root(process: &Process) -> String {
process
.var("RUSTUP_UPDATE_ROOT")
Expand Down Expand Up @@ -773,19 +773,7 @@ fn warn_if_default_linker_missing(process: &Process) {
}

fn install_bins(process: &Process) -> anyhow::Result<()> {
let bin_path = process.cargo_home()?.join("bin");
let this_exe_path = utils::current_exe()?;
let rustup_path = bin_path.join(format!("rustup{EXE_SUFFIX}"));

utils::ensure_dir_exists("bin", &bin_path)?;
// NB: Even on Linux we can't just copy the new binary over the (running)
// old binary; we must unlink it first.
if rustup_path.exists() {
utils::remove_file("rustup-bin", &rustup_path)?;
}
utils::copy_file_symlink_to_source(&this_exe_path, &rustup_path)?;
utils::make_executable(&rustup_path)?;
install_proxies(process)
SelfUpdateLock::acquire(process)?.install_bins(process)
}

pub(crate) fn install_proxies(process: &Process) -> anyhow::Result<()> {
Expand Down Expand Up @@ -1125,21 +1113,10 @@ pub(crate) fn self_update_permitted(explicit: bool) -> anyhow::Result<SelfUpdate
Ok(SelfUpdatePermission::Permit)
}

/// Self update downloads rustup-init to `$CARGO_HOME/bin/rustup-init`
/// and runs it.
///
/// It does a few things to accommodate self-delete problems on windows:
///
/// rustup-init is run in two stages, first with `--self-upgrade`,
/// which displays update messages and asks for confirmations, etc;
/// then with `--self-replace`, which replaces the rustup binary and
/// hardlinks. The last step is done without waiting for confirmation
/// on windows so that the running exe can be deleted.
/// Downloads the managed updater and runs it in replacement mode.
///
/// Because it's again difficult for rustup-init to delete itself
/// (and on windows this process will not be running to do it),
/// rustup-init is stored in `$CARGO_HOME/bin`, and then deleted next
/// time rustup runs.
/// The updater is removed by a later rustup invocation because Windows
/// cannot delete the updater while its process is still running.
pub(crate) async fn update(cfg: &Cfg<'_>) -> anyhow::Result<ExitCode> {
common::warn_if_host_is_emulated(cfg.process);

Expand All @@ -1165,8 +1142,8 @@ pub(crate) async fn update(cfg: &Cfg<'_>) -> anyhow::Result<ExitCode> {
}

match prepare_update(&DownloadCfg::new(cfg)).await? {
Some(setup_path) => {
let Some(version) = get_and_parse_new_rustup_version(&setup_path) else {
Some(prepared_updater) => {
let Some(version) = get_and_parse_new_rustup_version(prepared_updater.path()) else {
error!("failed to get rustup version");
return Ok(ExitCode::FAILURE);
};
Expand All @@ -1176,7 +1153,7 @@ pub(crate) async fn update(cfg: &Cfg<'_>) -> anyhow::Result<ExitCode> {
PackageUpdate::Rustup,
Ok(UpdateStatus::Updated(version)),
);
return run_update(&setup_path, cfg.process);
return run_update(prepared_updater, cfg.process);
}
None => {
let _ = common::show_channel_update(
Expand Down Expand Up @@ -1217,18 +1194,14 @@ fn parse_new_rustup_version(version: String) -> String {
String::from(matched_version)
}

pub(crate) async fn prepare_update(dl_cfg: &DownloadCfg<'_>) -> anyhow::Result<Option<PathBuf>> {
async fn prepare_update(dl_cfg: &DownloadCfg<'_>) -> anyhow::Result<Option<PreparedUpdater>> {
let cargo_home = dl_cfg.process.cargo_home()?;
let rustup_path = cargo_home.join(format!("bin{MAIN_SEPARATOR}rustup{EXE_SUFFIX}"));
let setup_path = cargo_home.join(format!("bin{MAIN_SEPARATOR}rustup-init{EXE_SUFFIX}"));

if !rustup_path.exists() {
return Err(CliError::NotSelfInstalled { p: cargo_home }.into());
}

if setup_path.exists() {
utils::remove_file("setup", &setup_path)?;
}
let self_update_lock = SelfUpdateLock::acquire(dl_cfg.process)?;

// Get build tuple
let tuple = TargetTuple::from_build();
Expand Down Expand Up @@ -1268,18 +1241,22 @@ pub(crate) async fn prepare_update(dl_cfg: &DownloadCfg<'_>) -> anyhow::Result<O

// Get download path
let download_url = utils::parse_url(&url)?;
let prepared_updater = self_update_lock.prepare_updater()?;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: Adding let setup_path = prepared_updater.path(); here could save some space in the diff.

// Download new version
info!("downloading self-update (new version: {available_version})");
DownloadOptions::try_from(dl_cfg.process)?
.start(&download_url, &setup_path)
.start(&download_url, prepared_updater.path())
.download()
.await?;

// Mark as executable
utils::make_executable(&setup_path)?;
utils::make_executable(prepared_updater.path())?;

#[cfg(feature = "test")]
dl_cfg.process.checkpoint(CHECKPOINT_SELF_UPDATE_PREPARED);

Ok(Some(setup_path))
Ok(Some(prepared_updater))
}

async fn get_available_rustup_version(dl_cfg: &DownloadCfg<'_>) -> anyhow::Result<String> {
Expand Down Expand Up @@ -1374,16 +1351,15 @@ pub(crate) async fn check_rustup_update(dl_cfg: &DownloadCfg<'_>) -> anyhow::Res

#[tracing::instrument(level = "trace")]
pub(crate) fn cleanup_self_updater(process: &Process) -> anyhow::Result<()> {
let cargo_home = process.cargo_home()?;
let setup = cargo_home.join(format!("bin/rustup-init{EXE_SUFFIX}"));

if setup.exists() {
utils::remove_file("setup", &setup)?;
}

Ok(())
stage::cleanup(process)
}

static DEFAULT_UPDATE_ROOT: &str = "https://static.rust-lang.org/rustup";
#[cfg(feature = "test")]
pub const CHECKPOINT_SELF_UPDATE_PREPARED: &str = "self-update-prepared";
#[cfg(feature = "test")]
pub const CHECKPOINT_SELF_REPLACE_READY: &str = "self-replace-ready";

#[cfg(test)]
mod tests {
use std::collections::HashMap;
Expand Down Expand Up @@ -1439,8 +1415,10 @@ info: default host tuple is {0}
fn install_bins_creates_cargo_home() {
let root_dir = test_dir().unwrap();
let cargo_home = root_dir.path().join("cargo");
let rustup_home = root_dir.path().join("rustup");
let mut vars = HashMap::new();
vars.env("CARGO_HOME", cargo_home.to_string_lossy().to_string());
vars.env("RUSTUP_HOME", rustup_home);
let tp = TestProcess::with_vars(vars);
super::install_bins(&tp.process).unwrap();
assert!(cargo_home.exists());
Expand Down
Loading
Loading