@@ -14,8 +14,8 @@ use filetime::{FileTime, set_file_times};
1414use selinux:: SecurityContext ;
1515use std:: ffi:: OsString ;
1616use std:: fmt:: Debug ;
17- use std:: fs:: File ;
1817use std:: fs:: { self , metadata} ;
18+ use std:: fs:: { File , OpenOptions } ;
1919use std:: path:: { MAIN_SEPARATOR , Path , PathBuf } ;
2020use std:: process;
2121use thiserror:: Error ;
@@ -36,7 +36,7 @@ use uucore::translate;
3636use uucore:: { format_usage, show, show_error, show_if_err} ;
3737
3838#[ cfg( unix) ]
39- use std:: os:: unix:: fs:: { FileTypeExt , MetadataExt } ;
39+ use std:: os:: unix:: fs:: MetadataExt ;
4040#[ cfg( unix) ]
4141use std:: os:: unix:: prelude:: OsStrExt ;
4242
@@ -88,8 +88,8 @@ enum InstallError {
8888 #[ error( "{}" , translate!( "install-error-backup-failed" , "from" => . 0 . quote( ) , "to" => . 1 . quote( ) ) ) ]
8989 BackupFailed ( PathBuf , PathBuf , #[ source] std:: io:: Error ) ,
9090
91- #[ error( "{}" , translate!( "install-error-install-failed" , "from" => . 0 . quote( ) , "to" => . 1 . quote( ) ) ) ]
92- InstallFailed ( PathBuf , PathBuf , # [ source ] std :: io :: Error ) ,
91+ #[ error( "{}" , translate!( "install-error-install-failed" , "from" => . 0 . quote( ) , "to" => . 1 . quote( ) , "error" => . 2 . clone ( ) ) ) ]
92+ InstallFailed ( PathBuf , PathBuf , String ) ,
9393
9494 #[ error( "{}" , translate!( "install-error-strip-failed" , "error" => . 0 . clone( ) ) ) ]
9595 StripProgramFailed ( String ) ,
@@ -796,22 +796,6 @@ fn perform_backup(to: &Path, b: &Behavior) -> UResult<Option<PathBuf>> {
796796 }
797797}
798798
799- /// Copy a non-special file using [`fs::copy`].
800- ///
801- /// # Parameters
802- /// * `from` - The source file path.
803- /// * `to` - The destination file path.
804- ///
805- /// # Returns
806- ///
807- /// Returns an empty Result or an error in case of failure.
808- fn copy_normal_file ( from : & Path , to : & Path ) -> UResult < ( ) > {
809- if let Err ( err) = fs:: copy ( from, to) {
810- return Err ( InstallError :: InstallFailed ( from. to_path_buf ( ) , to. to_path_buf ( ) , err) . into ( ) ) ;
811- }
812- Ok ( ( ) )
813- }
814-
815799/// Copy a file from one path to another. Handles the certain cases of special
816800/// files (e.g character specials).
817801///
@@ -838,8 +822,10 @@ fn copy_file(from: &Path, to: &Path) -> UResult<()> {
838822 )
839823 . into ( ) ) ;
840824 }
841- // fs::copy fails if destination is a invalid symlink.
842- // so lets just remove all existing files at destination before copy.
825+
826+ // Remove existing file at destination to allow overwriting
827+ // Note: create_new() below provides TOCTOU protection; if something
828+ // appears at this path between the remove and create, it will fail safely
843829 if let Err ( e) = fs:: remove_file ( to) {
844830 if e. kind ( ) != std:: io:: ErrorKind :: NotFound {
845831 show_error ! (
@@ -849,25 +835,13 @@ fn copy_file(from: &Path, to: &Path) -> UResult<()> {
849835 }
850836 }
851837
852- let ft = match metadata ( from) {
853- Ok ( ft) => ft. file_type ( ) ,
854- Err ( err) => {
855- return Err (
856- InstallError :: InstallFailed ( from. to_path_buf ( ) , to. to_path_buf ( ) , err) . into ( ) ,
857- ) ;
858- }
859- } ;
860-
861- // Stream-based copying to get around the limitations of std::fs::copy
862- #[ cfg( unix) ]
863- if ft. is_char_device ( ) || ft. is_block_device ( ) || ft. is_fifo ( ) {
864- let mut handle = File :: open ( from) ?;
865- let mut dest = File :: create ( to) ?;
866- copy_stream ( & mut handle, & mut dest) ?;
867- return Ok ( ( ) ) ;
868- }
838+ let mut handle = File :: open ( from) ?;
839+ // create_new provides TOCTOU protection
840+ let mut dest = OpenOptions :: new ( ) . write ( true ) . create_new ( true ) . open ( to) ?;
869841
870- copy_normal_file ( from, to) ?;
842+ copy_stream ( & mut handle, & mut dest) . map_err ( |err| {
843+ InstallError :: InstallFailed ( from. to_path_buf ( ) , to. to_path_buf ( ) , err. to_string ( ) )
844+ } ) ?;
871845
872846 Ok ( ( ) )
873847}
0 commit comments