Skip to content

Commit 69d9141

Browse files
committed
Auto merge of #1201 - RalfJung:symlinks, r=alexcrichton
correctly uninstall toolchains that are stale symlinks Fixes #1169 I'd like to add a test for this -- what is the best way to do that?
2 parents 5f3a291 + 984c997 commit 69d9141

File tree

2 files changed

+44
-4
lines changed

2 files changed

+44
-4
lines changed

src/rustup/toolchain.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,16 @@ impl<'a> Toolchain<'a> {
6868
pub fn path(&self) -> &Path {
6969
&self.path
7070
}
71+
fn is_symlink(&self) -> bool {
72+
use std::fs;
73+
fs::symlink_metadata(&self.path).map(|m| m.file_type().is_symlink()).unwrap_or(false)
74+
}
7175
pub fn exists(&self) -> bool {
7276
// HACK: linked toolchains are symlinks, and, contrary to what std docs
7377
// lead me to believe `fs::metadata`, used by `is_directory` does not
7478
// seem to follow symlinks on windows.
7579
let is_symlink = if cfg!(windows) {
76-
use std::fs;
77-
fs::symlink_metadata(&self.path).map(|m| m.file_type().is_symlink()).unwrap_or(false)
80+
self.is_symlink()
7881
} else {
7982
false
8083
};
@@ -84,7 +87,7 @@ impl<'a> Toolchain<'a> {
8487
Ok(try!(utils::assert_is_directory(&self.path)))
8588
}
8689
pub fn remove(&self) -> Result<()> {
87-
if self.exists() {
90+
if self.exists() || self.is_symlink() {
8891
(self.cfg.notify_handler)(Notification::UninstallingToolchain(&self.name));
8992
} else {
9093
(self.cfg.notify_handler)(Notification::ToolchainNotInstalled(&self.name));

tests/cli-misc.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ extern crate time;
88
extern crate tempdir;
99

1010
use rustup_mock::clitools::{self, Config, Scenario,
11-
expect_stdout_ok, expect_stderr_ok,
11+
expect_stdout_ok, expect_stderr_ok, expect_ok_ex,
1212
expect_ok, expect_err, expect_timeout_ok,
1313
run, this_host_triple};
1414
use rustup_utils::{raw, utils};
@@ -481,3 +481,40 @@ fn with_no_prompt_install_succeeds_if_rustc_exists() {
481481
assert!(out.ok);
482482
});
483483
}
484+
485+
// issue #1169
486+
#[test]
487+
#[cfg(any(unix, windows))]
488+
fn toolchain_broken_symlink() {
489+
use std::path::Path;
490+
use std::fs;
491+
492+
#[cfg(unix)]
493+
fn create_symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) {
494+
use std::os::unix::fs;
495+
fs::symlink(src, dst).unwrap();
496+
}
497+
498+
#[cfg(windows)]
499+
fn create_symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) {
500+
use std::os::windows::fs;
501+
fs::symlink_dir(src, dst).unwrap();
502+
}
503+
504+
setup(&|config| {
505+
// We artifically create a broken symlink toolchain -- but this can also happen "legitimately"
506+
// by having a proper toolchain there, using "toolchain link", and later removing the directory.
507+
fs::create_dir(config.rustupdir.join("toolchains")).unwrap();
508+
create_symlink_dir(config.rustupdir.join("this-directory-does-not-exist"), config.rustupdir.join("toolchains").join("test"));
509+
// Make sure this "fake install" actually worked
510+
expect_ok_ex(config, &["rustup", "toolchain", "list"], "test\n", "");
511+
// Now try to uninstall it. That should work only once.
512+
expect_ok_ex(config, &["rustup", "toolchain", "uninstall", "test"], "",
513+
r"info: uninstalling toolchain 'test'
514+
info: toolchain 'test' uninstalled
515+
");
516+
expect_ok_ex(config, &["rustup", "toolchain", "uninstall", "test"], "",
517+
r"info: no toolchain installed for 'test'
518+
");
519+
});
520+
}

0 commit comments

Comments
 (0)