|
15 | 15 | use core::pin::Pin; |
16 | 16 | use core::sync::atomic::{AtomicUsize, Ordering}; |
17 | 17 | use core::task::{Context, Poll}; |
18 | | -use std::fs::Metadata; |
| 18 | +use std::fs::{Metadata, Permissions}; |
19 | 19 | use std::io::{IoSlice, Seek}; |
20 | 20 | use std::path::{Path, PathBuf}; |
21 | 21 |
|
@@ -255,10 +255,7 @@ pub async fn hard_link(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<( |
255 | 255 | call_with_permit(move |_| std::fs::hard_link(src, dst).map_err(Into::<Error>::into)).await |
256 | 256 | } |
257 | 257 |
|
258 | | -pub async fn set_permissions( |
259 | | - src: impl AsRef<Path>, |
260 | | - perm: std::fs::Permissions, |
261 | | -) -> Result<(), Error> { |
| 258 | +pub async fn set_permissions(src: impl AsRef<Path>, perm: Permissions) -> Result<(), Error> { |
262 | 259 | let src = src.as_ref().to_owned(); |
263 | 260 | call_with_permit(move |_| std::fs::set_permissions(src, perm).map_err(Into::<Error>::into)) |
264 | 261 | .await |
@@ -361,7 +358,63 @@ pub async fn symlink_metadata(path: impl AsRef<Path>) -> Result<Metadata, Error> |
361 | 358 | call_with_permit(move |_| std::fs::symlink_metadata(path).map_err(Into::<Error>::into)).await |
362 | 359 | } |
363 | 360 |
|
| 361 | +// We can't just use the stock remove_dir_all as it falls over if someone's set readonly |
| 362 | +// permissions. This version walks the directories and fixes the permissions where needed |
| 363 | +// before deleting everything. |
| 364 | +#[cfg(not(target_family = "windows"))] |
| 365 | +fn internal_remove_dir_all(path: impl AsRef<Path>) -> Result<(), Error> { |
| 366 | + // Because otherwise Windows builds complain about these things not being used |
| 367 | + use std::io::ErrorKind; |
| 368 | + use std::os::unix::fs::PermissionsExt; |
| 369 | + |
| 370 | + use tracing::debug; |
| 371 | + use walkdir::WalkDir; |
| 372 | + |
| 373 | + for entry in WalkDir::new(&path) { |
| 374 | + let Ok(entry) = &entry else { |
| 375 | + debug!("Can't get into {entry:?}, assuming already deleted"); |
| 376 | + continue; |
| 377 | + }; |
| 378 | + let metadata = entry.metadata()?; |
| 379 | + if metadata.is_dir() { |
| 380 | + match std::fs::remove_dir_all(entry.path()) { |
| 381 | + Ok(()) => {} |
| 382 | + Err(e) if e.kind() == ErrorKind::PermissionDenied => { |
| 383 | + std::fs::set_permissions(entry.path(), Permissions::from_mode(0o700)).err_tip( |
| 384 | + || format!("Setting permissions for {}", entry.path().display()), |
| 385 | + )?; |
| 386 | + } |
| 387 | + e @ Err(_) => e.err_tip(|| format!("Removing {}", entry.path().display()))?, |
| 388 | + } |
| 389 | + } else if metadata.is_file() { |
| 390 | + std::fs::set_permissions(entry.path(), Permissions::from_mode(0o600)) |
| 391 | + .err_tip(|| format!("Setting permissions for {}", entry.path().display()))?; |
| 392 | + } |
| 393 | + } |
| 394 | + |
| 395 | + // should now be safe to delete after we fixed all the permissions in the walk loop |
| 396 | + match std::fs::remove_dir_all(&path) { |
| 397 | + Ok(()) => {} |
| 398 | + Err(e) if e.kind() == ErrorKind::NotFound => {} |
| 399 | + e @ Err(_) => e.err_tip(|| { |
| 400 | + format!( |
| 401 | + "Removing {} after permissions fixes", |
| 402 | + path.as_ref().display() |
| 403 | + ) |
| 404 | + })?, |
| 405 | + } |
| 406 | + Ok(()) |
| 407 | +} |
| 408 | + |
| 409 | +// We can't set the permissions easily in Windows, so just fallback to |
| 410 | +// the stock Rust remove_dir_all |
| 411 | +#[cfg(target_family = "windows")] |
| 412 | +fn internal_remove_dir_all(path: impl AsRef<Path>) -> Result<(), Error> { |
| 413 | + std::fs::remove_dir_all(&path)?; |
| 414 | + Ok(()) |
| 415 | +} |
| 416 | + |
364 | 417 | pub async fn remove_dir_all(path: impl AsRef<Path>) -> Result<(), Error> { |
365 | 418 | let path = path.as_ref().to_owned(); |
366 | | - call_with_permit(move |_| std::fs::remove_dir_all(path).map_err(Into::<Error>::into)).await |
| 419 | + call_with_permit(move |_| internal_remove_dir_all(path)).await |
367 | 420 | } |
0 commit comments