@@ -15,7 +15,7 @@ use helix_core::syntax::config::LanguageServerFeature;
1515use helix_core:: text_annotations:: { InlineAnnotation , Overlay } ;
1616use helix_event:: TaskController ;
1717use helix_lsp:: util:: lsp_pos_to_pos;
18- use helix_stdx:: faccess:: { copy_metadata , readonly} ;
18+ use helix_stdx:: faccess:: readonly;
1919use helix_vcs:: { DiffHandle , DiffProviderRegistry } ;
2020use once_cell:: sync:: OnceCell ;
2121use thiserror;
@@ -212,36 +212,56 @@ impl Backup {
212212
213213 let backup_path = if is_copy {
214214 let from_meta = std:: fs:: metadata ( & path_) . ok ( ) ?;
215- let backup_path = builder
216- . permissions ( perms) // NEED_CHECK: Don't recall why I didn't do this in the original PR...
217- . make_in ( path_. parent ( ) ?, |backup| std:: fs:: copy ( & path_, backup) )
218- . ok ( ) ?
219- . into_temp_path ( ) ;
215+ let ( backup, backup_path) = builder. tempfile ( ) . ok ( ) ?. into_parts ( ) ;
216+ std:: fs:: copy ( & path_, & backup_path) . ok ( ) ?;
220217
221218 #[ cfg( unix) ]
222219 {
223- use std:: os:: unix:: fs:: { MetadataExt , PermissionsExt } ;
224-
225- let mut perms = from_meta. permissions ( ) ;
226-
227- // Strip s-bit
228- perms. set_mode ( perms. mode ( ) & 0o0777 ) ;
220+ use std:: os:: {
221+ fd:: AsFd ,
222+ unix:: fs:: { MetadataExt , PermissionsExt } ,
223+ } ;
229224
230225 let to_meta = std:: fs:: metadata ( & backup_path) . ok ( ) ?;
231226 let from_gid = from_meta. gid ( ) ;
232227 let to_gid = to_meta. gid ( ) ;
233228
229+ let mut perms = from_meta. permissions ( ) ;
230+ perms. set_mode ( perms. mode ( ) & 0o0777 ) ; // Strip s-bit
231+
234232 // If chown fails, se the protection bits for the roup the same as the perm bits for others
235233 if from_gid != to_gid
236- && helix_stdx:: faccess:: chown ( & backup_path, None , Some ( from_gid) ) . is_err ( )
234+ && helix_stdx:: faccess:: fchown ( backup. as_fd ( ) , None , Some ( from_gid) )
235+ . is_err ( )
237236 {
238237 let new_perms = ( perms. mode ( ) & 0o0707 ) | ( ( perms. mode ( ) & 0o07 ) << 3 ) ;
239238 perms. set_mode ( new_perms) ;
240239 }
240+
241+ #[ cfg( target_os = "macos" ) ]
242+ {
243+ use std:: fs:: { File , FileTimes } ;
244+ use std:: os:: macos:: fs:: FileTimesExt ;
245+
246+ let to_file = File :: options ( ) . write ( true ) . open ( & backup_path) ?;
247+ let times = FileTimes :: new ( ) . set_created ( from_meta. created ( ) ?) ;
248+ to_file. set_times ( times) ?;
249+ }
250+
241251 std:: fs:: set_permissions ( & backup_path, perms) . ok ( ) ?;
242252 helix_stdx:: faccess:: copy_xattr ( & path_, & backup_path) . ok ( ) ?;
243253 }
244254
255+ #[ cfg( windows) ]
256+ {
257+ let backup_p_ = backup_path. clone ( ) ;
258+ tokio:: task:: spawn_blocking ( move || -> std:: io:: Result < ( ) > {
259+ copy_metadata ( & p, & backup_p_) ?;
260+ Ok ( ( ) )
261+ } )
262+ . await ??;
263+ }
264+
245265 let atime = FileTime :: from_last_access_time ( & from_meta) ;
246266 let mtime = FileTime :: from_last_modification_time ( & from_meta) ;
247267 filetime:: set_file_times ( & backup_path, atime, mtime) . ok ( ) ?;
@@ -1221,16 +1241,22 @@ impl Document {
12211241 let from = path. clone ( ) ;
12221242 let to = backup. path . clone ( ) ;
12231243 tokio:: task:: spawn_blocking ( move || -> Result < ( ) , Error > {
1224- helix_stdx:: faccess:: copy_ownership ( & from, & to) ?;
1244+ helix_stdx:: faccess:: copy_metadata ( & from, & to) ?;
12251245 Ok ( ( ) )
12261246 } )
12271247 . await ??;
12281248 }
12291249
12301250 file
12311251 } else {
1232- // INTEGRITY: Backup copy already exists
1233- tokio:: fs:: File :: create ( & path) . await ?
1252+ // INTEGRITY: Backup copy already exists. But possible TOCTOU race w/ file being changed underneath
1253+ let mut open_opt = tokio:: fs:: File :: options ( ) ;
1254+ open_opt
1255+ . create ( false )
1256+ . write ( true )
1257+ . truncate ( true )
1258+ . open ( & path)
1259+ . await ?
12341260 } ;
12351261 to_writer ( & mut dst, encoding_with_bom_info, & text) . await ?;
12361262 dst. sync_all ( ) . await ?;
@@ -1281,14 +1307,10 @@ impl Document {
12811307 filetime:: set_file_times ( & path, atime, mtime) ?;
12821308 }
12831309 } else {
1284- // copy metadata and delete backup
1285- let _ = tokio:: task:: spawn_blocking ( move || {
1286- let _ = copy_metadata ( & backup. path , & write_path)
1287- . map_err ( |e| log:: error!( "Failed to copy metadata on write: {e}" ) ) ;
1288- let _ = std:: fs:: remove_file ( backup. path )
1289- . map_err ( |e| log:: error!( "Failed to remove backup file on write: {e}" ) ) ;
1290- } )
1291- . await ;
1310+ // We're done; delete backup
1311+ let _ = tokio:: fs:: remove_file ( backup. path )
1312+ . await
1313+ . map_err ( |e| log:: error!( "Failed to remove backup file on write: {e}" ) ) ;
12921314 }
12931315 }
12941316
0 commit comments