@@ -34,6 +34,7 @@ use common::{self, Confirm};
34
34
use errors:: * ;
35
35
use rustup_dist:: dist;
36
36
use rustup_utils:: utils;
37
+ use same_file:: Handle ;
37
38
use std:: env;
38
39
use std:: env:: consts:: EXE_SUFFIX ;
39
40
use std:: path:: { Path , PathBuf , Component } ;
@@ -657,29 +658,76 @@ pub fn install_proxies() -> Result<()> {
657
658
let ref bin_path = try!( utils:: cargo_home ( ) ) . join ( "bin" ) ;
658
659
let ref rustup_path = bin_path. join ( & format ! ( "rustup{}" , EXE_SUFFIX ) ) ;
659
660
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
+
663
+ let mut tool_handles = Vec :: new ( ) ;
664
+ let mut link_afterwards = Vec :: new ( ) ;
663
665
664
666
// Try to hardlink all the Rust exes to the rustup exe. Some systems,
665
667
// like Android, does not support hardlinks, so we fallback to symlinks.
668
+ //
669
+ // Note that this function may not be running in the context of a fresh
670
+ // self update but rather as part of a normal update to fill in missing
671
+ // proxies. In that case our process may actually have the `rustup.exe`
672
+ // file open, and on systems like Windows that means that you can't
673
+ // even remove other hard links to the same file. Basically if we have
674
+ // `rustup.exe` open and running and `cargo.exe` is a hard link to that
675
+ // file, we can't remove `cargo.exe`.
676
+ //
677
+ // To avoid unnecessary errors from being returned here we use the
678
+ // `same-file` crate and its `Handle` type to avoid clobbering hard links
679
+ // that are already valid. If a hard link already points to the
680
+ // `rustup.exe` file then we leave it alone and move to the next one.
681
+ //
682
+ // As yet one final caveat, when we're looking at handles for files we can't
683
+ // actually delete files (they'll say they're deleted but they won't
684
+ // actually be on Windows). As a result we manually drop all the
685
+ // `tool_handles` later on. This'll allow us, afterwards, to actually
686
+ // overwrite all the previous hard links with new ones.
666
687
for tool in TOOLS {
667
- let ref tool_path = bin_path. join ( & format ! ( "{}{}" , tool, EXE_SUFFIX ) ) ;
668
- if tool_path. exists ( ) {
669
- file_size = utils:: file_size ( tool_path) ?;
688
+ let tool_path = bin_path. join ( & format ! ( "{}{}" , tool, EXE_SUFFIX ) ) ;
689
+ if let Ok ( handle) = Handle :: from_path ( & tool_path) {
690
+ tool_handles. push ( handle) ;
691
+ if rustup == * tool_handles. last ( ) . unwrap ( ) {
692
+ continue
693
+ }
670
694
}
671
- try! ( utils :: hard_or_symlink_file ( rustup_path , tool_path) ) ;
695
+ link_afterwards . push ( tool_path) ;
672
696
}
673
697
674
698
for tool in DUP_TOOLS {
675
699
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) ) ;
700
+ if let Ok ( handle) = Handle :: from_path ( tool_path) {
701
+ // Like above, don't clobber anything that's already hardlinked to
702
+ // avoid extraneous errors from being returned.
703
+ if rustup == handle {
704
+ continue
705
+ }
706
+
707
+ // If this file exists and is *not* equivalent to all other
708
+ // preexisting tools we found, then we're going to assume that it
709
+ // was preinstalled and actually pointing to a totally different
710
+ // binary. This is intended for cases where historically users
711
+ // rand `cargo install rustfmt` and so they had custom `rustfmt`
712
+ // and `cargo-fmt` executables lying around, but we as rustup have
713
+ // since started managing these tools.
714
+ //
715
+ // If the file is managed by rustup it should be equivalent to some
716
+ // previous file, and if it's not equivalent to anything then it's
717
+ // pretty likely that it needs to be dealt with manually.
718
+ if tool_handles. iter ( ) . all ( |h| * h != handle) {
719
+ warn ! ( "tool `{}` is already installed, remove it from `{}`, then run `rustup update` \
720
+ to have rustup manage this tool.",
721
+ tool, bin_path. to_string_lossy( ) ) ;
722
+ continue
723
+ }
682
724
}
725
+ try!( utils:: hard_or_symlink_file ( rustup_path, tool_path) ) ;
726
+ }
727
+
728
+ drop ( tool_handles) ;
729
+ for path in link_afterwards {
730
+ try!( utils:: hard_or_symlink_file ( rustup_path, & path) ) ;
683
731
}
684
732
685
733
Ok ( ( ) )
0 commit comments