Skip to content

Commit ee5db1a

Browse files
committed
Auto merge of #1326 - alexcrichton:fix-errors, r=alexcrichton
Fix self update errors filling in missing proxies The previous logic had some subtle bugs for a number of reasons, and hopefully this iteration irons them out. Closes #1316
2 parents 17aa5b5 + e842e17 commit ee5db1a

File tree

7 files changed

+110
-25
lines changed

7 files changed

+110
-25
lines changed

Cargo.lock

+32
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ markdown = "0.2"
4242
rand = "0.3.11"
4343
regex = "0.2"
4444
remove_dir_all = "0.2.0"
45+
same-file = "1.0"
4546
scopeguard = "0.3"
4647
serde = "1.0"
4748
serde_derive = "1.0"

src/rustup-cli/errors.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#![allow(dead_code)]
22

3+
use std::io;
34
use std::path::PathBuf;
45

56
use rustup;
@@ -15,6 +16,7 @@ error_chain! {
1516

1617
foreign_links {
1718
Temp(temp::Error);
19+
Io(io::Error);
1820
}
1921

2022
errors {

src/rustup-cli/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ extern crate term;
2626
extern crate itertools;
2727
extern crate time;
2828
extern crate rand;
29+
extern crate same_file;
2930
extern crate scopeguard;
3031
extern crate tempdir;
3132
extern crate sha2;

src/rustup-cli/self_update.rs

+46-11
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use common::{self, Confirm};
3434
use errors::*;
3535
use rustup_dist::dist;
3636
use rustup_utils::utils;
37+
use same_file::Handle;
3738
use std::env;
3839
use std::env::consts::EXE_SUFFIX;
3940
use std::path::{Path, PathBuf, Component};
@@ -657,29 +658,63 @@ pub fn install_proxies() -> Result<()> {
657658
let ref bin_path = try!(utils::cargo_home()).join("bin");
658659
let ref rustup_path = bin_path.join(&format!("rustup{}", EXE_SUFFIX));
659660

660-
// Record the size of the known links, then when we get files which may or
661-
// may not be links, we compare their size. Same size means probably a link.
662-
let mut file_size = 0;
661+
let rustup = Handle::from_path(rustup_path)?;
662+
let mut prev_handles = Vec::new();
663663

664664
// Try to hardlink all the Rust exes to the rustup exe. Some systems,
665665
// like Android, does not support hardlinks, so we fallback to symlinks.
666+
//
667+
// Note that this function may not be running in the context of a fresh
668+
// self update but rather as part of a normal update to fill in missing
669+
// proxies. In that case our process may actually have the `rustup.exe`
670+
// file open, and on systems like Windows that means that you can't
671+
// even remove other hard links to the same file. Basically if we have
672+
// `rustup.exe` open and running and `cargo.exe` is a hard link to that
673+
// file, we can't remove `cargo.exe`.
674+
//
675+
// To avoid unnecessary errors from being returned here we use the
676+
// `same-file` crate and its `Handle` type to avoid clobbering hard links
677+
// that are already valid. If a hard link already points to the
678+
// `rustup.exe` file then we leave it alone and move to the next one.
666679
for tool in TOOLS {
667680
let ref tool_path = bin_path.join(&format!("{}{}", tool, EXE_SUFFIX));
668-
if tool_path.exists() {
669-
file_size = utils::file_size(tool_path)?;
681+
if let Ok(handle) = Handle::from_path(tool_path) {
682+
prev_handles.push(handle);
683+
if rustup == *prev_handles.last().unwrap() {
684+
continue
685+
}
670686
}
671687
try!(utils::hard_or_symlink_file(rustup_path, tool_path));
672688
}
673689

674690
for tool in DUP_TOOLS {
675691
let ref tool_path = bin_path.join(&format!("{}{}", tool, EXE_SUFFIX));
676-
if tool_path.exists() && (file_size == 0 || utils::file_size(tool_path)? != file_size) {
677-
warn!("tool `{}` is already installed, remove it from `{}`, then run `rustup update` \
678-
to have rustup manage this tool.",
679-
tool, bin_path.to_string_lossy());
680-
} else {
681-
try!(utils::hard_or_symlink_file(rustup_path, tool_path));
692+
if let Ok(handle) = Handle::from_path(tool_path) {
693+
// Like above, don't clobber anything that's already hardlinked to
694+
// avoid extraneous errors from being returned.
695+
if rustup == handle {
696+
continue
697+
}
698+
699+
// If this file exists and is *not* equivalent to all other
700+
// preexisting tools we found, then we're going to assume that it
701+
// was preinstalled and actually pointing to a totally different
702+
// binary. This is intended for cases where historically users
703+
// rand `cargo install rustfmt` and so they had custom `rustfmt`
704+
// and `cargo-fmt` executables lying around, but we as rustup have
705+
// since started managing these tools.
706+
//
707+
// If the file is managed by rustup it should be equivalent to some
708+
// previous file, and if it's not equivalent to anything then it's
709+
// pretty likely that it needs to be dealt with manually.
710+
if prev_handles.iter().all(|h| *h != handle) {
711+
warn!("tool `{}` is already installed, remove it from `{}`, then run `rustup update` \
712+
to have rustup manage this tool.",
713+
tool, bin_path.to_string_lossy());
714+
continue
715+
}
682716
}
717+
try!(utils::hard_or_symlink_file(rustup_path, tool_path));
683718
}
684719

685720
Ok(())

src/rustup-mock/src/clitools.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -322,13 +322,19 @@ pub fn run(config: &Config, name: &str, args: &[&str], env: &[(&str, &str)]) ->
322322
for env in env {
323323
cmd.env(env.0, env.1);
324324
}
325+
326+
println!("running {:?}", cmd);
325327
let out = cmd.output().expect("failed to run test command");
326328

327-
SanitizedOutput {
329+
let output = SanitizedOutput {
328330
ok: out.status.success(),
329331
stdout: String::from_utf8(out.stdout).unwrap(),
330332
stderr: String::from_utf8(out.stderr).unwrap(),
331-
}
333+
};
334+
println!("status: {}", out.status);
335+
println!("----- stdout\n{}", output.stdout);
336+
println!("----- stderr\n{}", output.stderr);
337+
return output
332338
}
333339

334340
// Creates a mock dist server populated with some test data

tests/cli-self-upd.rs

+20-12
Original file line numberDiff line numberDiff line change
@@ -1250,28 +1250,36 @@ fn rls_proxy_set_up_after_update() {
12501250

12511251
#[test]
12521252
fn update_does_not_overwrite_rustfmt() {
1253-
update_setup(&|config, _| {
1253+
update_setup(&|config, self_dist| {
12541254
expect_ok(config, &["rustup-init", "-y"]);
1255+
let version = env!("CARGO_PKG_VERSION");
1256+
output_release_file(self_dist, "1", version);
1257+
1258+
// Since we just did a fresh install rustfmt will exist. Let's emulate
1259+
// it not existing in this test though by removing it just after our
1260+
// installation.
12551261
let ref rustfmt_path = config.cargodir.join(format!("bin/rustfmt{}", EXE_SUFFIX));
1262+
assert!(rustfmt_path.exists());
1263+
fs::remove_file(rustfmt_path).unwrap();
12561264
raw::write_file(rustfmt_path, "").unwrap();
12571265
assert_eq!(utils::file_size(rustfmt_path).unwrap(), 0);
12581266

1267+
// Ok, now a self-update should complain about `rustfmt` not looking
1268+
// like rustup and the user should take some action.
12591269
expect_stderr_ok(config, &["rustup", "self", "update"],
12601270
"`rustfmt` is already installed");
1261-
expect_ok(config, &["rustup", "self", "update"]);
12621271
assert!(rustfmt_path.exists());
12631272
assert_eq!(utils::file_size(rustfmt_path).unwrap(), 0);
12641273

1265-
1266-
// We run the test twice because the first time around none of the shims
1267-
// exist, and we want to check that out check for rustfmt works if there
1268-
// are shims or not.
1269-
let ref rustdoc_path = config.cargodir.join(format!("bin/rustdoc{}", EXE_SUFFIX));
1270-
assert!(rustdoc_path.exists());
1271-
1272-
expect_stderr_ok(config, &["rustup", "self", "update"],
1273-
"`rustfmt` is already installed");
1274+
// Now simluate us removing the rustfmt executable and rerunning a self
1275+
// update, this should install the rustup shim. Note that we don't run
1276+
// `rustup` here but rather the rustup we've actually installed, this'll
1277+
// help reproduce bugs related to having that file being opened by the
1278+
// current process.
1279+
fs::remove_file(rustfmt_path).unwrap();
1280+
let installed_rustup = config.cargodir.join("bin/rustup");
1281+
expect_ok(config, &[installed_rustup.to_str().unwrap(), "self", "update"]);
12741282
assert!(rustfmt_path.exists());
1275-
assert_eq!(utils::file_size(rustfmt_path).unwrap(), 0);
1283+
assert!(utils::file_size(rustfmt_path).unwrap() > 0);
12761284
});
12771285
}

0 commit comments

Comments
 (0)