@@ -135,7 +135,7 @@ where
135
135
} ;
136
136
137
137
// 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) ?;
139
139
num_bytes
140
140
}
141
141
gix_index:: entry:: Mode :: SYMLINK => {
@@ -275,20 +275,16 @@ pub(crate) fn open_file(
275
275
try_op_or_unlink ( path, overwrite_existing, |p| options. open ( p) ) . map ( |f| ( f, set_executable_after_creation) )
276
276
}
277
277
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.
280
279
pub ( crate ) fn finalize_entry (
281
280
entry : & mut gix_index:: Entry ,
282
281
file : std:: fs:: File ,
283
- set_executable_after_creation : Option < & Path > ,
282
+ # [ cfg_attr ( windows , allow ( unused_variables ) ) ] set_executable_after_creation : bool ,
284
283
) -> Result < ( ) , crate :: checkout:: Error > {
285
284
// For possibly existing, overwritten files, we must change the file mode explicitly.
286
285
#[ 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) ?;
292
288
}
293
289
// NOTE: we don't call `file.sync_all()` here knowing that some filesystems don't handle this well.
294
290
// revisit this once there is a bug to fix.
@@ -297,102 +293,104 @@ pub(crate) fn finalize_entry(
297
293
Ok ( ( ) )
298
294
}
299
295
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.
300
299
#[ 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" )
311
323
}
312
324
313
325
#[ cfg( all( test, unix) ) ]
314
326
mod 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
-
322
327
#[ test]
323
- fn set_mode_executable ( ) {
328
+ fn let_readers_execute ( ) {
324
329
let cases = [
325
330
// 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 ) ,
338
343
// 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 ) ,
348
353
// 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 ) ,
384
374
] ;
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) ;
389
378
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}"
395
381
) ;
396
382
}
397
383
}
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
+ }
398
396
}
0 commit comments