|
1 |
| -use std::borrow::Cow; |
2 | 1 | use std::{
|
3 |
| - fs::OpenOptions, |
| 2 | + borrow::Cow, |
| 3 | + fs::{OpenOptions, Permissions}, |
4 | 4 | io::Write,
|
5 | 5 | path::{Path, PathBuf},
|
6 | 6 | };
|
@@ -286,14 +286,114 @@ pub(crate) fn finalize_entry(
|
286 | 286 | // For possibly existing, overwritten files, we must change the file mode explicitly.
|
287 | 287 | #[cfg(unix)]
|
288 | 288 | if let Some(path) = set_executable_after_creation {
|
289 |
| - use std::os::unix::fs::PermissionsExt; |
290 |
| - let mut perm = std::fs::symlink_metadata(path)?.permissions(); |
291 |
| - perm.set_mode(0o777); |
292 |
| - std::fs::set_permissions(path, perm)?; |
| 289 | + let old_perm = std::fs::symlink_metadata(path)?.permissions(); |
| 290 | + if let Some(new_perm) = set_mode_executable(old_perm) { |
| 291 | + std::fs::set_permissions(path, new_perm)?; |
| 292 | + } |
293 | 293 | }
|
294 | 294 | // NOTE: we don't call `file.sync_all()` here knowing that some filesystems don't handle this well.
|
295 | 295 | // revisit this once there is a bug to fix.
|
296 | 296 | entry.stat = Stat::from_fs(&gix_index::fs::Metadata::from_file(&file)?)?;
|
297 | 297 | file.close()?;
|
298 | 298 | Ok(())
|
299 | 299 | }
|
| 300 | + |
| 301 | +#[cfg(unix)] |
| 302 | +fn set_mode_executable(mut perm: Permissions) -> Option<Permissions> { |
| 303 | + use std::os::unix::fs::PermissionsExt; |
| 304 | + let mut mode = perm.mode(); |
| 305 | + if mode & 0o170000 != 0o100000 { |
| 306 | + return None; // Stop if we don't have a regular file anymore. |
| 307 | + } |
| 308 | + mode &= 0o777; // Clear non-rwx bits (setuid, setgid, sticky). |
| 309 | + mode |= (mode & 0o444) >> 2; // Let readers also execute. |
| 310 | + perm.set_mode(mode); |
| 311 | + Some(perm) |
| 312 | +} |
| 313 | + |
| 314 | +#[cfg(all(test, unix))] |
| 315 | +mod tests { |
| 316 | + fn pretty(maybe_mode: Option<u32>) -> String { |
| 317 | + match maybe_mode { |
| 318 | + Some(mode) => format!("Some({mode:04o})"), |
| 319 | + None => "None".into(), |
| 320 | + } |
| 321 | + } |
| 322 | + |
| 323 | + #[test] |
| 324 | + fn set_mode_executable() { |
| 325 | + let cases = [ |
| 326 | + // Common cases: |
| 327 | + (0o100755, Some(0o755)), |
| 328 | + (0o100644, Some(0o755)), |
| 329 | + (0o100750, Some(0o750)), |
| 330 | + (0o100640, Some(0o750)), |
| 331 | + (0o100700, Some(0o700)), |
| 332 | + (0o100600, Some(0o700)), |
| 333 | + (0o100775, Some(0o775)), |
| 334 | + (0o100664, Some(0o775)), |
| 335 | + (0o100770, Some(0o770)), |
| 336 | + (0o100660, Some(0o770)), |
| 337 | + (0o100764, Some(0o775)), |
| 338 | + (0o100760, Some(0o770)), |
| 339 | + // Less common: |
| 340 | + (0o100674, Some(0o775)), |
| 341 | + (0o100670, Some(0o770)), |
| 342 | + (0o100000, Some(0o000)), |
| 343 | + (0o100400, Some(0o500)), |
| 344 | + (0o100440, Some(0o550)), |
| 345 | + (0o100444, Some(0o555)), |
| 346 | + (0o100462, Some(0o572)), |
| 347 | + (0o100242, Some(0o252)), |
| 348 | + (0o100167, Some(0o177)), |
| 349 | + // With set-user-ID, set-group-ID, and sticky bits: |
| 350 | + (0o104755, Some(0o755)), |
| 351 | + (0o104644, Some(0o755)), |
| 352 | + (0o102755, Some(0o755)), |
| 353 | + (0o102644, Some(0o755)), |
| 354 | + (0o101755, Some(0o755)), |
| 355 | + (0o101644, Some(0o755)), |
| 356 | + (0o106755, Some(0o755)), |
| 357 | + (0o106644, Some(0o755)), |
| 358 | + (0o104750, Some(0o750)), |
| 359 | + (0o104640, Some(0o750)), |
| 360 | + (0o102750, Some(0o750)), |
| 361 | + (0o102640, Some(0o750)), |
| 362 | + (0o101750, Some(0o750)), |
| 363 | + (0o101640, Some(0o750)), |
| 364 | + (0o106750, Some(0o750)), |
| 365 | + (0o106640, Some(0o750)), |
| 366 | + (0o107644, Some(0o755)), |
| 367 | + (0o107000, Some(0o000)), |
| 368 | + (0o106400, Some(0o500)), |
| 369 | + (0o102462, Some(0o572)), |
| 370 | + // Where it was replaced with a directory due to a race: |
| 371 | + (0o040755, None), |
| 372 | + (0o040644, None), |
| 373 | + (0o040600, None), |
| 374 | + (0o041755, None), |
| 375 | + (0o041644, None), |
| 376 | + (0o046644, None), |
| 377 | + // Where it was replaced with a symlink due to a race: |
| 378 | + (0o120777, None), |
| 379 | + (0o120644, None), |
| 380 | + // Where it was replaced with some other non-regular file due to a race: |
| 381 | + (0o140644, None), |
| 382 | + (0o060644, None), |
| 383 | + (0o020644, None), |
| 384 | + (0o010644, None), |
| 385 | + ]; |
| 386 | + for (old_mode, expected) in cases { |
| 387 | + use std::os::unix::fs::PermissionsExt; |
| 388 | + let old_perm = std::fs::Permissions::from_mode(old_mode); |
| 389 | + let actual = super::set_mode_executable(old_perm).map(|perm| perm.mode()); |
| 390 | + assert_eq!( |
| 391 | + actual, |
| 392 | + expected, |
| 393 | + "{old_mode:06o} should become {}, became {}", |
| 394 | + pretty(expected), |
| 395 | + pretty(actual) |
| 396 | + ); |
| 397 | + } |
| 398 | + } |
| 399 | +} |
0 commit comments