@@ -135,7 +135,7 @@ where
135135 } ;
136136
137137 // For possibly existing, overwritten files, we must change the file mode explicitly.
138- finalize_entry ( entry, file, set_executable_after_creation. then_some ( dest ) ) ?;
138+ finalize_entry ( entry, file, set_executable_after_creation) ?;
139139 num_bytes
140140 }
141141 gix_index:: entry:: Mode :: SYMLINK => {
@@ -275,20 +275,16 @@ pub(crate) fn open_file(
275275 try_op_or_unlink ( path, overwrite_existing, |p| options. open ( p) ) . map ( |f| ( f, set_executable_after_creation) )
276276}
277277
278- /// Close `file` and store its stats in `entry`, possibly setting `file` executable depending on `set_executable_after_creation`.
279- #[ cfg_attr( windows, allow( unused_variables) ) ]
278+ /// Close `file` and store its stats in `entry`, possibly setting `file` executable.
280279pub ( crate ) fn finalize_entry (
281280 entry : & mut gix_index:: Entry ,
282281 file : std:: fs:: File ,
283- set_executable_after_creation : Option < & Path > ,
282+ # [ cfg_attr ( windows , allow ( unused_variables ) ) ] set_executable_after_creation : bool ,
284283) -> Result < ( ) , crate :: checkout:: Error > {
285284 // For possibly existing, overwritten files, we must change the file mode explicitly.
286285 #[ cfg( unix) ]
287- if let Some ( path) = set_executable_after_creation {
288- let old_perm = std:: fs:: symlink_metadata ( path) ?. permissions ( ) ;
289- if let Some ( new_perm) = set_mode_executable ( old_perm) {
290- std:: fs:: set_permissions ( path, new_perm) ?;
291- }
286+ if set_executable_after_creation {
287+ set_executable ( & file) ?;
292288 }
293289 // NOTE: we don't call `file.sync_all()` here knowing that some filesystems don't handle this well.
294290 // revisit this once there is a bug to fix.
@@ -297,102 +293,104 @@ pub(crate) fn finalize_entry(
297293 Ok ( ( ) )
298294}
299295
296+ /// Use `fstat` and `fchmod` on a file descriptor to make a regular file executable.
297+ ///
298+ /// See `let_readers_execute` for the exact details of how the mode is transformed.
300299#[ cfg( unix) ]
301- fn set_mode_executable ( mut perm : std:: fs:: Permissions ) -> Option < std:: fs:: Permissions > {
302- use std:: os:: unix:: fs:: PermissionsExt ;
303- let mut mode = perm. mode ( ) ;
304- if mode & 0o170000 != 0o100000 {
305- return None ; // Stop if we don't have a regular file anymore.
306- }
307- mode &= 0o777 ; // Clear non-rwx bits (setuid, setgid, sticky).
308- mode |= ( mode & 0o444 ) >> 2 ; // Let readers also execute.
309- perm. set_mode ( mode) ;
310- Some ( perm)
300+ fn set_executable ( file : & std:: fs:: File ) -> Result < ( ) , std:: io:: Error > {
301+ let old_raw_mode = rustix:: fs:: fstat ( file) ?. st_mode ;
302+ let new_mode = let_readers_execute ( old_raw_mode) ;
303+ rustix:: fs:: fchmod ( file, new_mode) ?;
304+ Ok ( ( ) )
305+ }
306+
307+ /// Given the st_mode of a regular file, compute the mode with executable bits safely added.
308+ ///
309+ /// Currently this adds executable bits for whoever has read bits already. It doesn't use the umask.
310+ /// Set-user-ID and set-group-ID bits are unset for safety. The sticky bit is also unset.
311+ ///
312+ /// This returns only mode bits, not file type. The return value can be passed to chmod or fchmod.
313+ #[ cfg( unix) ]
314+ fn let_readers_execute ( mut raw_mode : rustix:: fs:: RawMode ) -> rustix:: fs:: Mode {
315+ assert_eq ! (
316+ raw_mode & 0o170000 ,
317+ 0o100000 ,
318+ "bug in caller if not from a regular file"
319+ ) ;
320+ raw_mode &= 0o777 ; // Clear type, non-rwx mode bits (setuid, setgid, sticky).
321+ raw_mode |= ( raw_mode & 0o444 ) >> 2 ; // Let readers also execute.
322+ rustix:: fs:: Mode :: from_bits ( raw_mode) . expect ( "all bits recognized" )
311323}
312324
313325#[ cfg( all( test, unix) ) ]
314326mod tests {
315- fn pretty ( maybe_mode : Option < u32 > ) -> String {
316- match maybe_mode {
317- Some ( mode) => format ! ( "Some({mode:04o})" ) ,
318- None => "None" . into ( ) ,
319- }
320- }
321-
322327 #[ test]
323- fn set_mode_executable ( ) {
328+ fn let_readers_execute ( ) {
324329 let cases = [
325330 // Common cases:
326- ( 0o100755 , Some ( 0o755 ) ) ,
327- ( 0o100644 , Some ( 0o755 ) ) ,
328- ( 0o100750 , Some ( 0o750 ) ) ,
329- ( 0o100640 , Some ( 0o750 ) ) ,
330- ( 0o100700 , Some ( 0o700 ) ) ,
331- ( 0o100600 , Some ( 0o700 ) ) ,
332- ( 0o100775 , Some ( 0o775 ) ) ,
333- ( 0o100664 , Some ( 0o775 ) ) ,
334- ( 0o100770 , Some ( 0o770 ) ) ,
335- ( 0o100660 , Some ( 0o770 ) ) ,
336- ( 0o100764 , Some ( 0o775 ) ) ,
337- ( 0o100760 , Some ( 0o770 ) ) ,
331+ ( 0o100755 , 0o755 ) ,
332+ ( 0o100644 , 0o755 ) ,
333+ ( 0o100750 , 0o750 ) ,
334+ ( 0o100640 , 0o750 ) ,
335+ ( 0o100700 , 0o700 ) ,
336+ ( 0o100600 , 0o700 ) ,
337+ ( 0o100775 , 0o775 ) ,
338+ ( 0o100664 , 0o775 ) ,
339+ ( 0o100770 , 0o770 ) ,
340+ ( 0o100660 , 0o770 ) ,
341+ ( 0o100764 , 0o775 ) ,
342+ ( 0o100760 , 0o770 ) ,
338343 // Less common:
339- ( 0o100674 , Some ( 0o775 ) ) ,
340- ( 0o100670 , Some ( 0o770 ) ) ,
341- ( 0o100000 , Some ( 0o000 ) ) ,
342- ( 0o100400 , Some ( 0o500 ) ) ,
343- ( 0o100440 , Some ( 0o550 ) ) ,
344- ( 0o100444 , Some ( 0o555 ) ) ,
345- ( 0o100462 , Some ( 0o572 ) ) ,
346- ( 0o100242 , Some ( 0o252 ) ) ,
347- ( 0o100167 , Some ( 0o177 ) ) ,
344+ ( 0o100674 , 0o775 ) ,
345+ ( 0o100670 , 0o770 ) ,
346+ ( 0o100000 , 0o000 ) ,
347+ ( 0o100400 , 0o500 ) ,
348+ ( 0o100440 , 0o550 ) ,
349+ ( 0o100444 , 0o555 ) ,
350+ ( 0o100462 , 0o572 ) ,
351+ ( 0o100242 , 0o252 ) ,
352+ ( 0o100167 , 0o177 ) ,
348353 // With set-user-ID, set-group-ID, and sticky bits:
349- ( 0o104755 , Some ( 0o755 ) ) ,
350- ( 0o104644 , Some ( 0o755 ) ) ,
351- ( 0o102755 , Some ( 0o755 ) ) ,
352- ( 0o102644 , Some ( 0o755 ) ) ,
353- ( 0o101755 , Some ( 0o755 ) ) ,
354- ( 0o101644 , Some ( 0o755 ) ) ,
355- ( 0o106755 , Some ( 0o755 ) ) ,
356- ( 0o106644 , Some ( 0o755 ) ) ,
357- ( 0o104750 , Some ( 0o750 ) ) ,
358- ( 0o104640 , Some ( 0o750 ) ) ,
359- ( 0o102750 , Some ( 0o750 ) ) ,
360- ( 0o102640 , Some ( 0o750 ) ) ,
361- ( 0o101750 , Some ( 0o750 ) ) ,
362- ( 0o101640 , Some ( 0o750 ) ) ,
363- ( 0o106750 , Some ( 0o750 ) ) ,
364- ( 0o106640 , Some ( 0o750 ) ) ,
365- ( 0o107644 , Some ( 0o755 ) ) ,
366- ( 0o107000 , Some ( 0o000 ) ) ,
367- ( 0o106400 , Some ( 0o500 ) ) ,
368- ( 0o102462 , Some ( 0o572 ) ) ,
369- // Where it was replaced with a directory due to a race:
370- ( 0o040755 , None ) ,
371- ( 0o040644 , None ) ,
372- ( 0o040600 , None ) ,
373- ( 0o041755 , None ) ,
374- ( 0o041644 , None ) ,
375- ( 0o046644 , None ) ,
376- // Where it was replaced with a symlink due to a race:
377- ( 0o120777 , None ) ,
378- ( 0o120644 , None ) ,
379- // Where it was replaced with some other non-regular file due to a race:
380- ( 0o140644 , None ) ,
381- ( 0o060644 , None ) ,
382- ( 0o020644 , None ) ,
383- ( 0o010644 , None ) ,
354+ ( 0o104755 , 0o755 ) ,
355+ ( 0o104644 , 0o755 ) ,
356+ ( 0o102755 , 0o755 ) ,
357+ ( 0o102644 , 0o755 ) ,
358+ ( 0o101755 , 0o755 ) ,
359+ ( 0o101644 , 0o755 ) ,
360+ ( 0o106755 , 0o755 ) ,
361+ ( 0o106644 , 0o755 ) ,
362+ ( 0o104750 , 0o750 ) ,
363+ ( 0o104640 , 0o750 ) ,
364+ ( 0o102750 , 0o750 ) ,
365+ ( 0o102640 , 0o750 ) ,
366+ ( 0o101750 , 0o750 ) ,
367+ ( 0o101640 , 0o750 ) ,
368+ ( 0o106750 , 0o750 ) ,
369+ ( 0o106640 , 0o750 ) ,
370+ ( 0o107644 , 0o755 ) ,
371+ ( 0o107000 , 0o000 ) ,
372+ ( 0o106400 , 0o500 ) ,
373+ ( 0o102462 , 0o572 ) ,
384374 ] ;
385- for ( old_mode, expected) in cases {
386- use std:: os:: unix:: fs:: PermissionsExt ;
387- let old_perm = std:: fs:: Permissions :: from_mode ( old_mode) ;
388- let actual = super :: set_mode_executable ( old_perm) . map ( |perm| perm. mode ( ) ) ;
375+ for ( st_mode, raw_expected) in cases {
376+ let expected = rustix:: fs:: Mode :: from_bits ( raw_expected) . expect ( "expected mode is a mode" ) ;
377+ let actual = super :: let_readers_execute ( st_mode) ;
389378 assert_eq ! (
390- actual,
391- expected,
392- "{old_mode:06o} should become {}, became {}" ,
393- pretty( expected) ,
394- pretty( actual)
379+ actual, expected,
380+ "{st_mode:06o} should become {expected:04o}, became {actual:04o}"
395381 ) ;
396382 }
397383 }
384+
385+ #[ test]
386+ #[ should_panic]
387+ fn let_readers_execute_panics_on_directory ( ) {
388+ super :: let_readers_execute ( 0o040644 ) ;
389+ }
390+
391+ #[ test]
392+ #[ should_panic]
393+ fn let_readers_execute_should_panic_on_symlink ( ) {
394+ super :: let_readers_execute ( 0o120644 ) ;
395+ }
398396}
0 commit comments