@@ -13,9 +13,12 @@ use filetime::{set_file_times, FileTime};
13
13
use std:: error:: Error ;
14
14
use std:: fmt:: { Debug , Display } ;
15
15
use std:: fs;
16
+ #[ cfg( not( unix) ) ]
16
17
use std:: fs:: File ;
17
18
use std:: os:: unix:: fs:: MetadataExt ;
18
19
#[ cfg( unix) ]
20
+ use std:: os:: unix:: fs:: OpenOptionsExt ;
21
+ #[ cfg( unix) ]
19
22
use std:: os:: unix:: prelude:: OsStrExt ;
20
23
use std:: path:: { Path , PathBuf , MAIN_SEPARATOR } ;
21
24
use std:: process;
@@ -748,29 +751,78 @@ fn perform_backup(to: &Path, b: &Behavior) -> UResult<Option<PathBuf>> {
748
751
/// Returns an empty Result or an error in case of failure.
749
752
///
750
753
fn copy_file ( from : & Path , to : & Path ) -> UResult < ( ) > {
751
- // fs::copy fails if destination is a invalid symlink.
752
- // so lets just remove all existing files at destination before copy.
753
- if let Err ( e) = fs:: remove_file ( to) {
754
- if e. kind ( ) != std:: io:: ErrorKind :: NotFound {
755
- show_error ! (
756
- "Failed to remove existing file {}. Error: {:?}" ,
757
- to. display( ) ,
758
- e
759
- ) ;
754
+ // Open the source file and get a file descriptor, making sure the source file exists.
755
+ #[ cfg( unix) ]
756
+ let src = fs:: OpenOptions :: new ( ) . read ( true ) . open ( from) ;
757
+ #[ cfg( not( unix) ) ]
758
+ let src = File :: open ( from) ;
759
+ if let Err ( e) = src {
760
+ show_error ! (
761
+ "Failed to open source file {}. Error: {:?}" ,
762
+ from. display( ) ,
763
+ e
764
+ ) ;
765
+ return Err ( InstallError :: InstallFailed ( from. to_path_buf ( ) , to. to_path_buf ( ) , e) . into ( ) ) ;
766
+ }
767
+ let mut src = src. unwrap ( ) ;
768
+
769
+ // If the source file is opened to be read, the copy should fail only when there is a problem
770
+ // with the destination, so lets just remove all existing files at destination before copy.
771
+ let remove_destination = || {
772
+ if let Err ( e) = fs:: remove_file ( to) {
773
+ if e. kind ( ) != std:: io:: ErrorKind :: NotFound {
774
+ show_error ! (
775
+ "Failed to remove existing file {}. Error: {:?}" ,
776
+ to. display( ) ,
777
+ e
778
+ ) ;
779
+ return Err ( e) ;
780
+ }
760
781
}
782
+ Ok ( ( ) )
783
+ } ;
784
+
785
+ // Errors out this case if the destination file cannot be created.
786
+ if let Err ( e) = remove_destination ( ) {
787
+ return Err ( InstallError :: InstallFailed ( from. to_path_buf ( ) , to. to_path_buf ( ) , e) . into ( ) ) ;
761
788
}
762
789
763
- if from. as_os_str ( ) == "/dev/null" {
764
- /* workaround a limitation of fs::copy
765
- * https://github.com/rust-lang/rust/issues/79390
766
- */
767
- if let Err ( err) = File :: create ( to) {
790
+ // Create the destination file first. Using safer mode on unix to avoid
791
+ // potential unsafe mode between time-of-creation and time-of-chmod.
792
+ #[ cfg( unix) ]
793
+ let dst = fs:: OpenOptions :: new ( )
794
+ . write ( true )
795
+ . create_new ( true )
796
+ . mode ( 0o600 )
797
+ . open ( to) ;
798
+ #[ cfg( not( unix) ) ]
799
+ let dst = File :: create ( to) ;
800
+
801
+ if let Err ( e) = dst {
802
+ show_error ! (
803
+ "Failed to create destination file {}. Error: {:?}" ,
804
+ to. display( ) ,
805
+ e
806
+ ) ;
807
+ return Err ( InstallError :: InstallFailed ( from. to_path_buf ( ) , to. to_path_buf ( ) , e) . into ( ) ) ;
808
+ }
809
+ let mut dst = dst. unwrap ( ) ;
810
+
811
+ /* workaround a limitation of fs::copy: skip copy if source is /dev/null
812
+ * https://github.com/rust-lang/rust/issues/79390
813
+ */
814
+ if from. as_os_str ( ) != "/dev/null" {
815
+ // Use `std::io::copy` to avoid unsafe file modes here.
816
+ if let Err ( err) = std:: io:: copy ( & mut src, & mut dst) {
817
+ drop ( src) ;
818
+ drop ( dst) ;
819
+ // This removal should be successful unless changes happened after the
820
+ // creation of the destination, so we just ignore it here.
821
+ let _ = remove_destination ( ) ;
768
822
return Err (
769
823
InstallError :: InstallFailed ( from. to_path_buf ( ) , to. to_path_buf ( ) , err) . into ( ) ,
770
824
) ;
771
825
}
772
- } else if let Err ( err) = fs:: copy ( from, to) {
773
- return Err ( InstallError :: InstallFailed ( from. to_path_buf ( ) , to. to_path_buf ( ) , err) . into ( ) ) ;
774
826
}
775
827
Ok ( ( ) )
776
828
}
0 commit comments