@@ -130,6 +130,32 @@ impl FileOperationExecutor {
130130 . with_context ( || format ! ( "Failed to create directory: {}" , parent. display( ) ) ) ?;
131131 }
132132
133+ // If destination exists and is read-only, make it writable first
134+ if dest. exists ( ) {
135+ let metadata = fs:: metadata ( dest)
136+ . with_context ( || format ! ( "Failed to read metadata: {}" , dest. display( ) ) ) ?;
137+ let perms = metadata. permissions ( ) ;
138+
139+ if perms. readonly ( ) {
140+ #[ cfg( unix) ]
141+ {
142+ use std:: os:: unix:: fs:: PermissionsExt ;
143+ // On Unix, add write permission for owner only
144+ let mode = perms. mode ( ) ;
145+ let new_perms = fs:: Permissions :: from_mode ( mode | 0o200 ) ; // Add owner write
146+ fs:: set_permissions ( dest, new_perms)
147+ . with_context ( || format ! ( "Failed to make file writable: {}" , dest. display( ) ) ) ?;
148+ }
149+ #[ cfg( not( unix) ) ]
150+ {
151+ let mut new_perms = perms. clone ( ) ;
152+ new_perms. set_readonly ( false ) ;
153+ fs:: set_permissions ( dest, new_perms)
154+ . with_context ( || format ! ( "Failed to make file writable: {}" , dest. display( ) ) ) ?;
155+ }
156+ }
157+ }
158+
133159 // Copy file
134160 fs:: copy ( source, dest) . with_context ( || {
135161 format ! ( "Failed to copy {} to {}" , source. display( ) , dest. display( ) )
@@ -296,4 +322,28 @@ mod tests {
296322 assert ! ( dst. exists( ) ) ;
297323 assert ! ( dst. is_dir( ) ) ;
298324 }
325+
326+ #[ test]
327+ fn test_copy_file_overwrites_readonly_destination ( ) {
328+ use std:: os:: unix:: fs:: PermissionsExt ;
329+
330+ let tmp = TempDir :: new ( ) . unwrap ( ) ;
331+ let src = tmp. path ( ) . join ( "source.txt" ) ;
332+ let dst = tmp. path ( ) . join ( "dest.txt" ) ;
333+
334+ // Create source file with new content
335+ fs:: write ( & src, "new content" ) . unwrap ( ) ;
336+
337+ // Create destination file with old content and make it read-only
338+ fs:: write ( & dst, "old content" ) . unwrap ( ) ;
339+ let mut perms = fs:: metadata ( & dst) . unwrap ( ) . permissions ( ) ;
340+ perms. set_mode ( 0o444 ) ; // read-only for all
341+ fs:: set_permissions ( & dst, perms) . unwrap ( ) ;
342+
343+ // This should succeed even though destination is read-only
344+ FileOperationExecutor :: copy_file ( & src, & dst) . unwrap ( ) ;
345+
346+ // Verify the file was overwritten with new content
347+ assert_eq ! ( fs:: read_to_string( & dst) . unwrap( ) , "new content" ) ;
348+ }
299349}
0 commit comments