<commit_before>//! Self-installation and updating
//!
//! This is the installer at the heart of Rust. If it breaks
//! everything breaks. It is conceptually very simple, as rustup is
//! distributed as a single binary, and installation mostly requires
//! copying it into place. There are some tricky bits though, mostly
//! because of workarounds to self-delete an exe on Windows.
//!
//! During install (as `rustup-init`):
//!
//! * copy the self exe to $CARGO_HOME/bin
//! * hardlink rustc, etc to *that*
//! * update the PATH in a system-specific way
//! * run the equivalent of `rustup default stable`
//!
//! During upgrade (`rustup self upgrade`):
//!
//! * 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.
//!
//! During uninstall (`rustup self uninstall`):
//!
//! * Delete `$RUSTUP_HOME`.
//! * Delete everything in `$CARGO_HOME`, including
//!   the rustup binary and its hardlinks
//!
//! Deleting the running binary during uninstall is tricky
//! and racy on Windows.

use crate::common::{self, ignorable_error, Confirm};
use crate::errors::*;
use crate::markdown::md;
use crate::term2;
use rustup::dist::dist::{self, Profile, TargetTriple};
use rustup::toolchain::DistributableToolchain;
use rustup::utils::utils;
use rustup::utils::Notification;
use rustup::{Cfg, UpdateStatus};
use rustup::{DUP_TOOLS, TOOLS};
use same_file::Handle;
use std::borrow::Cow;
use std::env;
use std::env::consts::EXE_SUFFIX;
use std::fs;
use std::path::{Component, Path, PathBuf};
use std::process::{self, Command};

pub struct InstallOpts<'a> {
    pub default_host_triple: Option<String>,
    pub default_toolchain: Option<String>,
    pub profile: String,
    pub no_modify_path: bool,
    pub components: &'a [&'a str],
    pub targets: &'a [&'a str],
}

#[cfg(feature = "no-self-update")]
pub const NEVER_SELF_UPDATE: bool = true;
#[cfg(not(feature = "no-self-update"))]
pub const NEVER_SELF_UPDATE: bool = false;

// The big installation messages. These are macros because the first
// argument of format! needs to be a literal.

macro_rules! pre_install_msg_template {
    ($platform_msg:literal) => {
        concat!(
            r"
# Welcome to Rust!

This will download and install the official compiler for the Rust
programming language, and its package manager, Cargo.

It will add the `cargo`, `rustc`, `rustup` and other commands to
Cargo's bin directory, located at:

    {cargo_home_bin}

This can be modified with the CARGO_HOME environment variable.

Rustup metadata and toolchains will be installed into the Rustup
home directory, located at:

    {rustup_home}

This can be modified with the RUSTUP_HOME environment variable.

",
            $platform_msg,
            r#"

You can uninstall at any time with `rustup self uninstall` and
these changes will be reverted.
"#
        )
    };
}

macro_rules! pre_install_msg_unix {
    () => {
        pre_install_msg_template!(
            "This path will then be added to your `PATH` environment variable by
modifying the profile file{plural} located at:

{rcfiles}"
        )
    };
}

macro_rules! pre_install_msg_win {
    () => {
        pre_install_msg_template!(
            "This path will then be added to your `PATH` environment variable by
modifying the `HKEY_CURRENT_USER/Environment/PATH` registry key."
        )
    };
}

macro_rules! pre_install_msg_no_modify_path {
    () => {
        pre_install_msg_template!(
            "This path needs to be in your `PATH` environment variable,
but will not be added automatically."
        )
    };
}

macro_rules! post_install_msg_unix {
    () => {
        r"# Rust is installed now. Great!

To get started you need Cargo's bin directory ({cargo_home}/bin) in your `PATH`
environment variable. Next time you log in this will be done
automatically.

To configure your current shell run `source {cargo_home}/env`
"
    };
}

macro_rules! post_install_msg_win {
    () => {
        r"# Rust is installed now. Great!

To get started you need Cargo's bin directory ({cargo_home}\\bin) in your `PATH`
environment variable. Future applications will automatically have the
correct environment, but you may need to restart your current shell.
"
    };
}

macro_rules! post_install_msg_unix_no_modify_path {
    () => {
        r"# Rust is installed now. Great!

To get started you need Cargo's bin directory ({cargo_home}/bin) in your `PATH`
environment variable.

To configure your current shell run `source {cargo_home}/env`
"
    };
}

macro_rules! post_install_msg_win_no_modify_path {
    () => {
        r"# Rust is installed now. Great!

To get started you need Cargo's bin directory ({cargo_home}\\bin) in your `PATH`
environment variable. This has not been done automatically.
"
    };
}

macro_rules! pre_uninstall_msg {
    () => {
        r"# Thanks for hacking in Rust!

This will uninstall all Rust toolchains and data, and remove
`{cargo_home}/bin` from your `PATH` environment variable.

"
    };
}

static MSVC_MESSAGE: &str = r#"# Rust Visual C++ prerequisites

Rust requires the Microsoft C++ build tools for Visual Studio 2013 or
later, but they don't seem to be installed.

The easiest way to acquire the build tools is by installing Microsoft
Visual C++ Build Tools 2019 which provides just the Visual C++ build
tools:

    https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019

Please ensure the Windows 10 SDK and the English language pack components
are included when installing the Visual C++ Build Tools.

Alternately, you can install Visual Studio 2019, Visual Studio 2017,
Visual Studio 2015, or Visual Studio 2013 and during install select
the "C++ tools":

    https://visualstudio.microsoft.com/downloads/

_Install the C++ build tools before proceeding_.

If you will be targeting the GNU ABI or otherwise know what you are
doing then it is fine to continue installation without the build
tools, but otherwise, install the C++ build tools before proceeding.
"#;

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

/// `CARGO_HOME` suitable for display, possibly with $HOME
/// substituted for the directory prefix
fn canonical_cargo_home() -> Result<Cow<'static, str>> {
    let path = utils::cargo_home()?;

    let default_cargo_home = utils::home_dir()
        .unwrap_or_else(|| PathBuf::from("."))
        .join(".cargo");
    Ok(if default_cargo_home == path {
        if cfg!(unix) {
            "$HOME/.cargo".into()
        } else {
            r"%USERPROFILE%\.cargo".into()
        }
    } else {
        path.to_string_lossy().into_owned().into()
    })
}

/// Installing is a simple matter of copying the running binary to
/// `CARGO_HOME`/bin, hard-linking the various Rust tools to it,
/// and adding `CARGO_HOME`/bin to PATH.
pub fn install(no_prompt: bool, verbose: bool, quiet: bool, mut opts: InstallOpts) -> Result<()> {
    if !env::var_os("RUSTUP_INIT_SKIP_EXISTENCE_CHECKS").map_or(false, |s| s == "yes") {
        do_pre_install_sanity_checks(no_prompt)?;
    }

    do_pre_install_options_sanity_checks(&opts)?;

    if !env::var_os("RUSTUP_INIT_SKIP_EXISTENCE_CHECKS").map_or(false, |s| s == "yes") {
        check_existence_of_rustc_or_cargo_in_path(no_prompt)?;
    }

    #[cfg(unix)]
    do_anti_sudo_check(no_prompt)?;

    let mut term = term2::stdout();
    if !do_msvc_check(&opts)? {
        if no_prompt {
            warn!("installing msvc toolchain without its prerequisites");
        } else {
            md(&mut term, MSVC_MESSAGE);
            if !common::confirm("\nContinue? (Y/n)", true)? {
                info!("aborting installation");
                return Ok(());
            }
        }
    }

    if !no_prompt {
        let msg = pre_install_msg(opts.no_modify_path)?;

        md(&mut term, msg);

        loop {
            md(&mut term, current_install_opts(&opts));
            match common::confirm_advanced()? {
                Confirm::No => {
                    info!("aborting installation");
                    return Ok(());
                }
                Confirm::Yes => {
                    break;
                }
                Confirm::Advanced => {
                    opts = customize_install(opts)?;
                }
            }
        }
    }

    let install_res: Result<()> = (|| {
        install_bins()?;
        if !opts.no_modify_path {
            do_add_to_path(&get_add_path_methods())?;
        }
        utils::create_rustup_home()?;
        maybe_install_rust(
            opts.default_toolchain.as_deref(),
            &opts.profile,
            opts.default_host_triple.as_deref(),
            opts.components,
            opts.targets,
            verbose,
            quiet,
        )?;

        if cfg!(unix) {
            let env_file = utils::cargo_home()?.join("env");
            let env_str = format!("{}\n", shell_export_string()?);
            utils::write_file("env", &env_file, &env_str)?;
        }

        Ok(())
    })();

    if let Err(ref e) = install_res {
        common::report_error(e);

        // On windows, where installation happens in a console
        // that may have opened just for this purpose, give
        // the user an opportunity to see the error before the
        // window closes.
        if cfg!(windows) && !no_prompt {
            println!();
            println!("Press the Enter key to continue.");
            common::read_line()?;
        }

        process::exit(1);
    }

    let cargo_home = canonical_cargo_home()?;
    #[cfg(windows)]
    let cargo_home = cargo_home.replace('\\', r"\\");
    let msg = match (opts.no_modify_path, cfg!(unix)) {
        (false, true) => format!(post_install_msg_unix!(), cargo_home = cargo_home),
        (false, false) => format!(post_install_msg_win!(), cargo_home = cargo_home),
        (true, true) => format!(
            post_install_msg_unix_no_modify_path!(),
            cargo_home = cargo_home
        ),
        (true, false) => format!(
            post_install_msg_win_no_modify_path!(),
            cargo_home = cargo_home
        ),
    };
    md(&mut term, msg);

    if !no_prompt {
        // On windows, where installation happens in a console
        // that may have opened just for this purpose, require
        // the user to press a key to continue.
        if cfg!(windows) {
            println!();
            println!("Press the Enter key to continue.");
            common::read_line()?;
        }
    }

    Ok(())
}

fn rustc_or_cargo_exists_in_path() -> Result<()> {
    // Ignore rustc and cargo if present in $HOME/.cargo/bin or a few other directories
    fn ignore_paths(path: &PathBuf) -> bool {
        !path
            .components()
            .any(|c| c == Component::Normal(".cargo".as_ref()))
    }

    if let Some(paths) = env::var_os("PATH") {
        let paths = env::split_paths(&paths).filter(ignore_paths);

        for path in paths {
            let rustc = path.join(format!("rustc{}", EXE_SUFFIX));
            let cargo = path.join(format!("cargo{}", EXE_SUFFIX));

            if rustc.exists() || cargo.exists() {
                return Err(path.to_str().unwrap().into());
            }
        }
    }
    Ok(())
}

fn check_existence_of_rustc_or_cargo_in_path(no_prompt: bool) -> Result<()> {
    // Only the test runner should set this
    let skip_check = env::var_os("RUSTUP_INIT_SKIP_PATH_CHECK");

    // Skip this if the environment variable is set
    if skip_check == Some("yes".into()) {
        return Ok(());
    }

    if let Err(path) = rustc_or_cargo_exists_in_path() {
        warn!("it looks like you have an existing installation of Rust at:");
        warn!("{}", path);
        warn!("rustup should not be installed alongside Rust. Please uninstall your existing Rust first.");
        warn!("Otherwise you may have confusion unless you are careful with your PATH");
        warn!("If you are sure that you want both rustup and your already installed Rust");
        warn!("then please reply `y' or `yes' or set RUSTUP_INIT_SKIP_PATH_CHECK to yes");
        warn!("or pass `-y' to ignore all ignorable checks.");
        ignorable_error("cannot install while Rust is installed".into(), no_prompt)?;
    }
    Ok(())
}

fn do_pre_install_sanity_checks(no_prompt: bool) -> Result<()> {
    let rustc_manifest_path = PathBuf::from("/usr/local/lib/rustlib/manifest-rustc");
    let uninstaller_path = PathBuf::from("/usr/local/lib/rustlib/uninstall.sh");
    let rustup_sh_path = utils::home_dir().unwrap().join(".rustup");
    let rustup_sh_version_path = rustup_sh_path.join("rustup-version");

    let rustc_exists = rustc_manifest_path.exists() && uninstaller_path.exists();
    let rustup_sh_exists = rustup_sh_version_path.exists();

    if rustc_exists {
        warn!("it looks like you have an existing installation of Rust");
        warn!("rustup cannot be installed alongside Rust. Please uninstall first");
        warn!(
            "run `{}` as root to uninstall Rust",
            uninstaller_path.display()
        );
        ignorable_error("cannot install while Rust is installed".into(), no_prompt)?;
    }

    if rustup_sh_exists {
        warn!("it looks like you have existing rustup.sh metadata");
        warn!("rustup cannot be installed while rustup.sh metadata exists");
        warn!("delete `{}` to remove rustup.sh", rustup_sh_path.display());
        warn!("or, if you already have rustup installed, you can run");
        warn!("`rustup self update` and `rustup toolchain list` to upgrade");
        warn!("your directory structure");
        ignorable_error(
            "cannot install while rustup.sh is installed".into(),
            no_prompt,
        )?;
    }

    Ok(())
}

fn do_pre_install_options_sanity_checks(opts: &InstallOpts) -> Result<()> {
    use std::str::FromStr;
    // Verify that the installation options are vaguely sane
    (|| {
        let host_triple = opts
            .default_host_triple
            .as_ref()
            .map(|s| dist::TargetTriple::new(s))
            .unwrap_or_else(TargetTriple::from_host_or_build);
        let toolchain_to_use = match &opts.default_toolchain {
            None => "stable",
            Some(s) if s == "none" => "stable",
            Some(s) => &s,
        };
        let partial_channel = dist::PartialToolchainDesc::from_str(toolchain_to_use)?;
        let resolved = partial_channel.resolve(&host_triple)?.to_string();
        debug!(
            "Successfully resolved installation toolchain as: {}",
            resolved
        );
        Ok(())
    })()
    .map_err(|e: Box<dyn std::error::Error>| {
        format!(
            "Pre-checks for host and toolchain failed: {}\n\
             If you are unsure of suitable values, the 'stable' toolchain is the default.\n\
             Valid host triples look something like: {}",
            e,
            dist::TargetTriple::from_host_or_build()
        )
    })?;
    Ok(())
}

// If the user is trying to install with sudo, on some systems this will
// result in writing root-owned files to the user's home directory, because
// sudo is configured not to change $HOME. Don't let that bogosity happen.
#[cfg(unix)]
fn do_anti_sudo_check(no_prompt: bool) -> Result<()> {
    pub fn home_mismatch() -> (bool, PathBuf, PathBuf) {
        let fallback = || (false, PathBuf::new(), PathBuf::new());
        // test runner should set this, nothing else
        if env::var_os("RUSTUP_INIT_SKIP_SUDO_CHECK").map_or(false, |s| s == "yes") {
            return fallback();
        }

        match (utils::home_dir_from_passwd(), env::var_os("HOME")) {
            (Some(pw), Some(eh)) if eh != pw => return (true, PathBuf::from(eh), pw),
            (None, _) => warn!("getpwuid_r: couldn't get user data"),
            _ => {}
        }
        fallback()
    }

    match home_mismatch() {
        (false, _, _) => {}
        (true, env_home, euid_home) => {
            err!("$HOME differs from euid-obtained home directory: you may be using sudo");
            err!("$HOME directory: {}", env_home.display());
            err!("euid-obtained home directory: {}", euid_home.display());
            if !no_prompt {
                err!("if this is what you want, restart the installation with `-y'");
                process::exit(1);
            }
        }
    }

    Ok(())
}

// Provide guidance about setting up MSVC if it doesn't appear to be
// installed
#[cfg(windows)]
fn do_msvc_check(opts: &InstallOpts) -> Result<bool> {
    // Test suite skips this since it's env dependent
    if env::var("RUSTUP_INIT_SKIP_MSVC_CHECK").is_ok() {
        return Ok(true);
    }

    use cc::windows_registry;
    let host_triple = if let Some(trip) = opts.default_host_triple.as_ref() {
        trip.to_owned()
    } else {
        TargetTriple::from_host_or_build().to_string()
    };
    let installing_msvc = host_triple.contains("msvc");
    let have_msvc = windows_registry::find_tool(&host_triple, "cl.exe").is_some();
    if installing_msvc && !have_msvc {
        return Ok(false);
    }

    Ok(true)
}

#[cfg(not(windows))]
fn do_msvc_check(_opts: &InstallOpts) -> Result<bool> {
    Ok(true)
}

fn pre_install_msg(no_modify_path: bool) -> Result<String> {
    let cargo_home = utils::cargo_home()?;
    let cargo_home_bin = cargo_home.join("bin");
    let rustup_home = utils::rustup_home()?;

    if !no_modify_path {
        if cfg!(unix) {
            let add_path_methods = get_add_path_methods();
            let rcfiles = add_path_methods
                .into_iter()
                .filter_map(|m| {
                    if let PathUpdateMethod::RcFile(path) = m {
                        Some(format!("{}", path.display()))
                    } else {
                        None
                    }
                })
                .collect::<Vec<_>>();
            let plural = if rcfiles.len() > 1 { "s" } else { "" };
            let rcfiles = rcfiles
                .into_iter()
                .map(|f| format!("    {}", f))
                .collect::<Vec<_>>();
            let rcfiles = rcfiles.join("\n");
            Ok(format!(
                pre_install_msg_unix!(),
                cargo_home_bin = cargo_home_bin.display(),
                plural = plural,
                rcfiles = rcfiles,
                rustup_home = rustup_home.display(),
            ))
        } else {
            Ok(format!(
                pre_install_msg_win!(),
                cargo_home_bin = cargo_home_bin.display(),
                rustup_home = rustup_home.display(),
            ))
        }
    } else {
        Ok(format!(
            pre_install_msg_no_modify_path!(),
            cargo_home_bin = cargo_home_bin.display(),
            rustup_home = rustup_home.display(),
        ))
    }
}

fn current_install_opts(opts: &InstallOpts) -> String {
    format!(
        r"Current installation options:

- ` `default host triple: `{}`
- `   `default toolchain: `{}`
- `             `profile: `{}`
- modify PATH variable: `{}`
",
        opts.default_host_triple
            .as_ref()
            .map(|s| TargetTriple::new(s))
            .unwrap_or_else(TargetTriple::from_host_or_build),
        opts.default_toolchain
            .as_deref()
            .unwrap_or("stable (default)"),<commit_msg>Clarify help message with the Cargo home directory<commit_after>//! Self-installation and updating
//!
//! This is the installer at the heart of Rust. If it breaks
//! everything breaks. It is conceptually very simple, as rustup is
//! distributed as a single binary, and installation mostly requires
//! copying it into place. There are some tricky bits though, mostly
//! because of workarounds to self-delete an exe on Windows.
//!
//! During install (as `rustup-init`):
//!
//! * copy the self exe to $CARGO_HOME/bin
//! * hardlink rustc, etc to *that*
//! * update the PATH in a system-specific way
//! * run the equivalent of `rustup default stable`
//!
//! During upgrade (`rustup self upgrade`):
//!
//! * 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.
//!
//! During uninstall (`rustup self uninstall`):
//!
//! * Delete `$RUSTUP_HOME`.
//! * Delete everything in `$CARGO_HOME`, including
//!   the rustup binary and its hardlinks
//!
//! Deleting the running binary during uninstall is tricky
//! and racy on Windows.

use crate::common::{self, ignorable_error, Confirm};
use crate::errors::*;
use crate::markdown::md;
use crate::term2;
use rustup::dist::dist::{self, Profile, TargetTriple};
use rustup::toolchain::DistributableToolchain;
use rustup::utils::utils;
use rustup::utils::Notification;
use rustup::{Cfg, UpdateStatus};
use rustup::{DUP_TOOLS, TOOLS};
use same_file::Handle;
use std::borrow::Cow;
use std::env;
use std::env::consts::EXE_SUFFIX;
use std::fs;
use std::path::{Component, Path, PathBuf};
use std::process::{self, Command};

pub struct InstallOpts<'a> {
    pub default_host_triple: Option<String>,
    pub default_toolchain: Option<String>,
    pub profile: String,
    pub no_modify_path: bool,
    pub components: &'a [&'a str],
    pub targets: &'a [&'a str],
}

#[cfg(feature = "no-self-update")]
pub const NEVER_SELF_UPDATE: bool = true;
#[cfg(not(feature = "no-self-update"))]
pub const NEVER_SELF_UPDATE: bool = false;

// The big installation messages. These are macros because the first
// argument of format! needs to be a literal.

macro_rules! pre_install_msg_template {
    ($platform_msg:literal) => {
        concat!(
            r"
# Welcome to Rust!

This will download and install the official compiler for the Rust
programming language, and its package manager, Cargo.

Rustup metadata and toolchains will be installed into the Rustup
home directory, located at:

    {rustup_home}

This can be modified with the RUSTUP_HOME environment variable.

The Cargo home directory located at:

    {cargo_home}

This can be modified with the CARGO_HOME environment variable.

The `cargo`, `rustc`, `rustup` and other commands will be added to
Cargo's bin directory, located at:

    {cargo_home_bin}

",
            $platform_msg,
            r#"

You can uninstall at any time with `rustup self uninstall` and
these changes will be reverted.
"#
        )
    };
}

macro_rules! pre_install_msg_unix {
    () => {
        pre_install_msg_template!(
            "This path will then be added to your `PATH` environment variable by
modifying the profile file{plural} located at:

{rcfiles}"
        )
    };
}

macro_rules! pre_install_msg_win {
    () => {
        pre_install_msg_template!(
            "This path will then be added to your `PATH` environment variable by
modifying the `HKEY_CURRENT_USER/Environment/PATH` registry key."
        )
    };
}

macro_rules! pre_install_msg_no_modify_path {
    () => {
        pre_install_msg_template!(
            "This path needs to be in your `PATH` environment variable,
but will not be added automatically."
        )
    };
}

macro_rules! post_install_msg_unix {
    () => {
        r"# Rust is installed now. Great!

To get started you need Cargo's bin directory ({cargo_home}/bin) in your `PATH`
environment variable. Next time you log in this will be done
automatically.

To configure your current shell run `source {cargo_home}/env`
"
    };
}

macro_rules! post_install_msg_win {
    () => {
        r"# Rust is installed now. Great!

To get started you need Cargo's bin directory ({cargo_home}\\bin) in your `PATH`
environment variable. Future applications will automatically have the
correct environment, but you may need to restart your current shell.
"
    };
}

macro_rules! post_install_msg_unix_no_modify_path {
    () => {
        r"# Rust is installed now. Great!

To get started you need Cargo's bin directory ({cargo_home}/bin) in your `PATH`
environment variable.

To configure your current shell run `source {cargo_home}/env`
"
    };
}

macro_rules! post_install_msg_win_no_modify_path {
    () => {
        r"# Rust is installed now. Great!

To get started you need Cargo's bin directory ({cargo_home}\\bin) in your `PATH`
environment variable. This has not been done automatically.
"
    };
}

macro_rules! pre_uninstall_msg {
    () => {
        r"# Thanks for hacking in Rust!

This will uninstall all Rust toolchains and data, and remove
`{cargo_home}/bin` from your `PATH` environment variable.

"
    };
}

static MSVC_MESSAGE: &str = r#"# Rust Visual C++ prerequisites

Rust requires the Microsoft C++ build tools for Visual Studio 2013 or
later, but they don't seem to be installed.

The easiest way to acquire the build tools is by installing Microsoft
Visual C++ Build Tools 2019 which provides just the Visual C++ build
tools:

    https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019

Please ensure the Windows 10 SDK and the English language pack components
are included when installing the Visual C++ Build Tools.

Alternately, you can install Visual Studio 2019, Visual Studio 2017,
Visual Studio 2015, or Visual Studio 2013 and during install select
the "C++ tools":

    https://visualstudio.microsoft.com/downloads/

_Install the C++ build tools before proceeding_.

If you will be targeting the GNU ABI or otherwise know what you are
doing then it is fine to continue installation without the build
tools, but otherwise, install the C++ build tools before proceeding.
"#;

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

/// `CARGO_HOME` suitable for display, possibly with $HOME
/// substituted for the directory prefix
fn canonical_cargo_home() -> Result<Cow<'static, str>> {
    let path = utils::cargo_home()?;

    let default_cargo_home = utils::home_dir()
        .unwrap_or_else(|| PathBuf::from("."))
        .join(".cargo");
    Ok(if default_cargo_home == path {
        if cfg!(unix) {
            "$HOME/.cargo".into()
        } else {
            r"%USERPROFILE%\.cargo".into()
        }
    } else {
        path.to_string_lossy().into_owned().into()
    })
}

/// Installing is a simple matter of copying the running binary to
/// `CARGO_HOME`/bin, hard-linking the various Rust tools to it,
/// and adding `CARGO_HOME`/bin to PATH.
pub fn install(no_prompt: bool, verbose: bool, quiet: bool, mut opts: InstallOpts) -> Result<()> {
    if !env::var_os("RUSTUP_INIT_SKIP_EXISTENCE_CHECKS").map_or(false, |s| s == "yes") {
        do_pre_install_sanity_checks(no_prompt)?;
    }

    do_pre_install_options_sanity_checks(&opts)?;

    if !env::var_os("RUSTUP_INIT_SKIP_EXISTENCE_CHECKS").map_or(false, |s| s == "yes") {
        check_existence_of_rustc_or_cargo_in_path(no_prompt)?;
    }

    #[cfg(unix)]
    do_anti_sudo_check(no_prompt)?;

    let mut term = term2::stdout();
    if !do_msvc_check(&opts)? {
        if no_prompt {
            warn!("installing msvc toolchain without its prerequisites");
        } else {
            md(&mut term, MSVC_MESSAGE);
            if !common::confirm("\nContinue? (Y/n)", true)? {
                info!("aborting installation");
                return Ok(());
            }
        }
    }

    if !no_prompt {
        let msg = pre_install_msg(opts.no_modify_path)?;

        md(&mut term, msg);

        loop {
            md(&mut term, current_install_opts(&opts));
            match common::confirm_advanced()? {
                Confirm::No => {
                    info!("aborting installation");
                    return Ok(());
                }
                Confirm::Yes => {
                    break;
                }
                Confirm::Advanced => {
                    opts = customize_install(opts)?;
                }
            }
        }
    }

    let install_res: Result<()> = (|| {
        install_bins()?;
        if !opts.no_modify_path {
            do_add_to_path(&get_add_path_methods())?;
        }
        utils::create_rustup_home()?;
        maybe_install_rust(
            opts.default_toolchain.as_deref(),
            &opts.profile,
            opts.default_host_triple.as_deref(),
            opts.components,
            opts.targets,
            verbose,
            quiet,
        )?;

        if cfg!(unix) {
            let env_file = utils::cargo_home()?.join("env");
            let env_str = format!("{}\n", shell_export_string()?);
            utils::write_file("env", &env_file, &env_str)?;
        }

        Ok(())
    })();

    if let Err(ref e) = install_res {
        common::report_error(e);

        // On windows, where installation happens in a console
        // that may have opened just for this purpose, give
        // the user an opportunity to see the error before the
        // window closes.
        if cfg!(windows) && !no_prompt {
            println!();
            println!("Press the Enter key to continue.");
            common::read_line()?;
        }

        process::exit(1);
    }

    let cargo_home = canonical_cargo_home()?;
    #[cfg(windows)]
    let cargo_home = cargo_home.replace('\\', r"\\");
    let msg = match (opts.no_modify_path, cfg!(unix)) {
        (false, true) => format!(post_install_msg_unix!(), cargo_home = cargo_home),
        (false, false) => format!(post_install_msg_win!(), cargo_home = cargo_home),
        (true, true) => format!(
            post_install_msg_unix_no_modify_path!(),
            cargo_home = cargo_home
        ),
        (true, false) => format!(
            post_install_msg_win_no_modify_path!(),
            cargo_home = cargo_home
        ),
    };
    md(&mut term, msg);

    if !no_prompt {
        // On windows, where installation happens in a console
        // that may have opened just for this purpose, require
        // the user to press a key to continue.
        if cfg!(windows) {
            println!();
            println!("Press the Enter key to continue.");
            common::read_line()?;
        }
    }

    Ok(())
}

fn rustc_or_cargo_exists_in_path() -> Result<()> {
    // Ignore rustc and cargo if present in $HOME/.cargo/bin or a few other directories
    fn ignore_paths(path: &PathBuf) -> bool {
        !path
            .components()
            .any(|c| c == Component::Normal(".cargo".as_ref()))
    }

    if let Some(paths) = env::var_os("PATH") {
        let paths = env::split_paths(&paths).filter(ignore_paths);

        for path in paths {
            let rustc = path.join(format!("rustc{}", EXE_SUFFIX));
            let cargo = path.join(format!("cargo{}", EXE_SUFFIX));

            if rustc.exists() || cargo.exists() {
                return Err(path.to_str().unwrap().into());
            }
        }
    }
    Ok(())
}

fn check_existence_of_rustc_or_cargo_in_path(no_prompt: bool) -> Result<()> {
    // Only the test runner should set this
    let skip_check = env::var_os("RUSTUP_INIT_SKIP_PATH_CHECK");

    // Skip this if the environment variable is set
    if skip_check == Some("yes".into()) {
        return Ok(());
    }

    if let Err(path) = rustc_or_cargo_exists_in_path() {
        warn!("it looks like you have an existing installation of Rust at:");
        warn!("{}", path);
        warn!("rustup should not be installed alongside Rust. Please uninstall your existing Rust first.");
        warn!("Otherwise you may have confusion unless you are careful with your PATH");
        warn!("If you are sure that you want both rustup and your already installed Rust");
        warn!("then please reply `y' or `yes' or set RUSTUP_INIT_SKIP_PATH_CHECK to yes");
        warn!("or pass `-y' to ignore all ignorable checks.");
        ignorable_error("cannot install while Rust is installed".into(), no_prompt)?;
    }
    Ok(())
}

fn do_pre_install_sanity_checks(no_prompt: bool) -> Result<()> {
    let rustc_manifest_path = PathBuf::from("/usr/local/lib/rustlib/manifest-rustc");
    let uninstaller_path = PathBuf::from("/usr/local/lib/rustlib/uninstall.sh");
    let rustup_sh_path = utils::home_dir().unwrap().join(".rustup");
    let rustup_sh_version_path = rustup_sh_path.join("rustup-version");

    let rustc_exists = rustc_manifest_path.exists() && uninstaller_path.exists();
    let rustup_sh_exists = rustup_sh_version_path.exists();

    if rustc_exists {
        warn!("it looks like you have an existing installation of Rust");
        warn!("rustup cannot be installed alongside Rust. Please uninstall first");
        warn!(
            "run `{}` as root to uninstall Rust",
            uninstaller_path.display()
        );
        ignorable_error("cannot install while Rust is installed".into(), no_prompt)?;
    }

    if rustup_sh_exists {
        warn!("it looks like you have existing rustup.sh metadata");
        warn!("rustup cannot be installed while rustup.sh metadata exists");
        warn!("delete `{}` to remove rustup.sh", rustup_sh_path.display());
        warn!("or, if you already have rustup installed, you can run");
        warn!("`rustup self update` and `rustup toolchain list` to upgrade");
        warn!("your directory structure");
        ignorable_error(
            "cannot install while rustup.sh is installed".into(),
            no_prompt,
        )?;
    }

    Ok(())
}

fn do_pre_install_options_sanity_checks(opts: &InstallOpts) -> Result<()> {
    use std::str::FromStr;
    // Verify that the installation options are vaguely sane
    (|| {
        let host_triple = opts
            .default_host_triple
            .as_ref()
            .map(|s| dist::TargetTriple::new(s))
            .unwrap_or_else(TargetTriple::from_host_or_build);
        let toolchain_to_use = match &opts.default_toolchain {
            None => "stable",
            Some(s) if s == "none" => "stable",
            Some(s) => &s,
        };
        let partial_channel = dist::PartialToolchainDesc::from_str(toolchain_to_use)?;
        let resolved = partial_channel.resolve(&host_triple)?.to_string();
        debug!(
            "Successfully resolved installation toolchain as: {}",
            resolved
        );
        Ok(())
    })()
    .map_err(|e: Box<dyn std::error::Error>| {
        format!(
            "Pre-checks for host and toolchain failed: {}\n\
             If you are unsure of suitable values, the 'stable' toolchain is the default.\n\
             Valid host triples look something like: {}",
            e,
            dist::TargetTriple::from_host_or_build()
        )
    })?;
    Ok(())
}

// If the user is trying to install with sudo, on some systems this will
// result in writing root-owned files to the user's home directory, because
// sudo is configured not to change $HOME. Don't let that bogosity happen.
#[cfg(unix)]
fn do_anti_sudo_check(no_prompt: bool) -> Result<()> {
    pub fn home_mismatch() -> (bool, PathBuf, PathBuf) {
        let fallback = || (false, PathBuf::new(), PathBuf::new());
        // test runner should set this, nothing else
        if env::var_os("RUSTUP_INIT_SKIP_SUDO_CHECK").map_or(false, |s| s == "yes") {
            return fallback();
        }

        match (utils::home_dir_from_passwd(), env::var_os("HOME")) {
            (Some(pw), Some(eh)) if eh != pw => return (true, PathBuf::from(eh), pw),
            (None, _) => warn!("getpwuid_r: couldn't get user data"),
            _ => {}
        }
        fallback()
    }

    match home_mismatch() {
        (false, _, _) => {}
        (true, env_home, euid_home) => {
            err!("$HOME differs from euid-obtained home directory: you may be using sudo");
            err!("$HOME directory: {}", env_home.display());
            err!("euid-obtained home directory: {}", euid_home.display());
            if !no_prompt {
                err!("if this is what you want, restart the installation with `-y'");
                process::exit(1);
            }
        }
    }

    Ok(())
}

// Provide guidance about setting up MSVC if it doesn't appear to be
// installed
#[cfg(windows)]
fn do_msvc_check(opts: &InstallOpts) -> Result<bool> {
    // Test suite skips this since it's env dependent
    if env::var("RUSTUP_INIT_SKIP_MSVC_CHECK").is_ok() {
        return Ok(true);
    }

    use cc::windows_registry;
    let host_triple = if let Some(trip) = opts.default_host_triple.as_ref() {
        trip.to_owned()
    } else {
        TargetTriple::from_host_or_build().to_string()
    };
    let installing_msvc = host_triple.contains("msvc");
    let have_msvc = windows_registry::find_tool(&host_triple, "cl.exe").is_some();
    if installing_msvc && !have_msvc {
        return Ok(false);
    }

    Ok(true)
}

#[cfg(not(windows))]
fn do_msvc_check(_opts: &InstallOpts) -> Result<bool> {
    Ok(true)
}

fn pre_install_msg(no_modify_path: bool) -> Result<String> {
    let cargo_home = utils::cargo_home()?;
    let cargo_home_bin = cargo_home.join("bin");
    let rustup_home = utils::rustup_home()?;

    if !no_modify_path {
        if cfg!(unix) {
            let add_path_methods = get_add_path_methods();
            let rcfiles = add_path_methods
                .into_iter()
                .filter_map(|m| {
                    if let PathUpdateMethod::RcFile(path) = m {
                        Some(format!("{}", path.display()))
                    } else {
                        None
                    }
                })
                .collect::<Vec<_>>();
            let plural = if rcfiles.len() > 1 { "s" } else { "" };
            let rcfiles = rcfiles
                .into_iter()
                .map(|f| format!("    {}", f))
                .collect::<Vec<_>>();
            let rcfiles = rcfiles.join("\n");
            Ok(format!(
                pre_install_msg_unix!(),
                cargo_home = cargo_home.display(),
                cargo_home_bin = cargo_home_bin.display(),
                plural = plural,
                rcfiles = rcfiles,
                rustup_home = rustup_home.display(),
            ))
        } else {
            Ok(format!(
                pre_install_msg_win!(),
                cargo_home = cargo_home.display(),
                cargo_home_bin = cargo_home_bin.display(),
                rustup_home = rustup_home.display(),
            ))
        }
    } else {
        Ok(format!(
            pre_install_msg_no_modify_path!(),
            cargo_home = cargo_home.display(),
            cargo_home_bin = cargo_home_bin.display(),
            rustup_home = rustup_home.display(),
        ))
    }
}

fn current_install_opts(opts: &InstallOpts) -> String {
    format!(
        r"Current installation options:

- ` `default host triple: `{}`
- `   `default toolchain: `{}`
- `             `profile: `{}`
- modify PATH variable: `{}`
",
        opts.default_host_triple
            .as_ref()
            .map(|s| TargetTriple::new(s))
            .unwrap_or_else(TargetTriple::from_host_or_build),
        opts.default_toolchain
            .as_deref()
            .unwrap_or("stable (default)"),
{'fraction_non_alphanumeric': 0.12459786549558291, 'fraction_numerical': 0.0017872644640759842, 'mean_word_length': 2.5206292134831463, 'pattern_counts': {'":': 2, '<': 43, '<?xml version=': 0, '>': 119, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3063208', 'n_tokens_mistral': 12140, 'n_tokens_neox': 11755, 'n_words': 3311}