diff --git a/src/cli/self_update/unix.rs b/src/cli/self_update/unix.rs index 7edcd68b78..e594336675 100644 --- a/src/cli/self_update/unix.rs +++ b/src/cli/self_update/unix.rs @@ -56,10 +56,7 @@ pub(crate) fn do_remove_from_path(process: &Process) -> Result<()> { let file = utils::read_file("rcfile", rc)?; let file_bytes = file.into_bytes(); // FIXME: This is whitespace sensitive where it should not be. - if let Some(idx) = file_bytes - .windows(source_bytes.len()) - .position(|w| w == source_bytes.as_slice()) - { + if let Some(idx) = find_exact_line(&file_bytes, &source_bytes) { // Here we rewrite the file without the offending line. let mut new_bytes = file_bytes[..idx].to_vec(); new_bytes.extend(&file_bytes[idx + source_bytes.len()..]); @@ -148,10 +145,7 @@ fn remove_legacy_source_command(source_cmd: String, process: &Process) -> Result let file = utils::read_file("rcfile", &rc)?; let file_bytes = file.into_bytes(); // FIXME: This is whitespace sensitive where it should not be. - if let Some(idx) = file_bytes - .windows(cmd_bytes.len()) - .position(|w| w == cmd_bytes.as_slice()) - { + if let Some(idx) = find_exact_line(&file_bytes, &cmd_bytes) { // Here we rewrite the file without the offending line. let mut new_bytes = file_bytes[..idx].to_vec(); new_bytes.extend(&file_bytes[idx + cmd_bytes.len()..]); @@ -162,6 +156,16 @@ fn remove_legacy_source_command(source_cmd: String, process: &Process) -> Result Ok(()) } +fn find_exact_line(file: &[u8], line: &[u8]) -> Option { + // The trailing newline enforces the end boundary; check the start boundary here. + assert!(line.ends_with(b"\n")); + file.windows(line.len()) + .enumerate() + .find_map(|(idx, candidate)| { + (candidate == line && (idx == 0 || file[idx - 1] == b'\n')).then_some(idx) + }) +} + fn remove_legacy_paths(process: &Process) -> Result<()> { // Before the work to support more kinds of shells, which was released in // version 1.23.0 of Rustup, we always inserted this line instead, which is diff --git a/tests/suite/cli_paths.rs b/tests/suite/cli_paths.rs index 65b6170aab..b8a5e70dde 100644 --- a/tests/suite/cli_paths.rs +++ b/tests/suite/cli_paths.rs @@ -383,6 +383,41 @@ error: could not amend shell profile[..] } } + #[tokio::test] + async fn install_preserves_guarded_legacy_source() { + let cx = CliTestContext::new(Scenario::Empty).await; + let profile = cx.config.homedir.join(".bash_profile"); + let guarded_source = "[ -f \"$HOME/.cargo/env\" ] && source \"$HOME/.cargo/env\"\n"; + raw::write_file(&profile, guarded_source).unwrap(); + + let mut cmd = cx.config.cmd("rustup-init", &INIT_NONE[1..]); + cmd.env_remove("CARGO_HOME"); + assert!(cmd.output().unwrap().status.success()); + + let new_profile = fs::read_to_string(&profile).unwrap(); + let expected = guarded_source.to_owned() + &source("$HOME/.cargo", POSIX_SH); + assert_eq!(new_profile, expected); + } + + #[tokio::test] + async fn uninstall_preserves_guarded_source() { + let cx = CliTestContext::new(Scenario::Empty).await; + let profile = cx.config.homedir.join(".bash_profile"); + let guarded_source = "[ -f \"$HOME/.cargo/env\" ] && . \"$HOME/.cargo/env\"\n"; + raw::write_file(&profile, guarded_source).unwrap(); + + let mut cmd = cx.config.cmd("rustup-init", &INIT_NONE[1..]); + cmd.env_remove("CARGO_HOME"); + assert!(cmd.output().unwrap().status.success()); + + let mut cmd = cx.config.cmd("rustup", ["self", "uninstall", "-y"]); + cmd.env_remove("CARGO_HOME"); + assert!(cmd.output().unwrap().status.success()); + + let new_profile = fs::read_to_string(&profile).unwrap(); + assert_eq!(new_profile, guarded_source); + } + #[tokio::test] async fn uninstall_cleans_up_legacy_paths() { let cx = CliTestContext::new(Scenario::Empty).await;