Skip to content

Commit d4012b2

Browse files
committed
Remove folders with bad permissions
1 parent 6316b55 commit d4012b2

9 files changed

Lines changed: 117 additions & 9 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

nativelink-error/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ rust_library(
2222
"@crates//:serde_json5",
2323
"@crates//:tokio",
2424
"@crates//:tonic",
25+
"@crates//:walkdir",
2526
],
2627
)
2728

nativelink-error/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,4 @@ tonic = { version = "0.13.0", features = [
3030
"tls-ring",
3131
"transport",
3232
], default-features = false }
33+
walkdir = { version = "2.5.0", default-features = false }

nativelink-error/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,12 @@ impl From<Error> for tonic::Status {
277277
}
278278
}
279279

280+
impl From<walkdir::Error> for Error {
281+
fn from(value: walkdir::Error) -> Self {
282+
Self::new(Code::Internal, value.to_string())
283+
}
284+
}
285+
280286
pub trait ResultExt<T> {
281287
/// # Errors
282288
///

nativelink-util/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ rust_library(
8181
"@crates//:tracing-opentelemetry",
8282
"@crates//:tracing-subscriber",
8383
"@crates//:uuid",
84+
"@crates//:walkdir",
8485
],
8586
)
8687

@@ -94,6 +95,7 @@ rust_test_suite(
9495
"tests/common_test.rs",
9596
"tests/evicting_map_test.rs",
9697
"tests/fastcdc_test.rs",
98+
"tests/fs_test.rs",
9799
"tests/health_utils_test.rs",
98100
"tests/operation_id_tests.rs",
99101
"tests/origin_event_test.rs",

nativelink-util/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ uuid = { version = "1.16.0", default-features = false, features = [
8484
"v4",
8585
"v6",
8686
] }
87+
walkdir = { version = "2.5.0", default-features = false }
8788

8889
[dev-dependencies]
8990
nativelink-macro = { path = "../nativelink-macro" }

nativelink-util/src/fs.rs

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
use core::pin::Pin;
1616
use core::sync::atomic::{AtomicUsize, Ordering};
1717
use core::task::{Context, Poll};
18-
use std::fs::Metadata;
18+
use std::fs::{Metadata, Permissions};
1919
use std::io::{IoSlice, Seek};
2020
use std::path::{Path, PathBuf};
2121

@@ -255,10 +255,7 @@ pub async fn hard_link(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<(
255255
call_with_permit(move |_| std::fs::hard_link(src, dst).map_err(Into::<Error>::into)).await
256256
}
257257

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> {
262259
let src = src.as_ref().to_owned();
263260
call_with_permit(move |_| std::fs::set_permissions(src, perm).map_err(Into::<Error>::into))
264261
.await
@@ -361,7 +358,63 @@ pub async fn symlink_metadata(path: impl AsRef<Path>) -> Result<Metadata, Error>
361358
call_with_permit(move |_| std::fs::symlink_metadata(path).map_err(Into::<Error>::into)).await
362359
}
363360

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+
364417
pub async fn remove_dir_all(path: impl AsRef<Path>) -> Result<(), Error> {
365418
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
367420
}

nativelink-util/tests/fs_test.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#![cfg(not(target_family = "windows"))]
2+
// Because windows does permissions differently
3+
4+
use std::env;
5+
use std::fs::{self, Permissions};
6+
use std::os::unix::fs::PermissionsExt;
7+
8+
use nativelink_error::ResultExt;
9+
use nativelink_macro::nativelink_test;
10+
use nativelink_util::fs::remove_dir_all;
11+
12+
#[nativelink_test]
13+
async fn remove_files_with_bad_permissions() -> Result<(), Box<dyn core::error::Error>> {
14+
let temp_dir = env::temp_dir();
15+
let bad_perms_directory = temp_dir.join("bad_perms_directory");
16+
if fs::exists(&bad_perms_directory)? {
17+
remove_dir_all(&bad_perms_directory)
18+
.await
19+
.err_tip(|| format!("first remove_dir_all for {bad_perms_directory:?}"))?;
20+
}
21+
fs::create_dir(&bad_perms_directory)?;
22+
let bad_perms_file = bad_perms_directory.join("bad_perms_file");
23+
if !fs::exists(&bad_perms_file)? {
24+
fs::write(&bad_perms_file, "").err_tip(|| "Can't create file")?;
25+
}
26+
27+
fs::set_permissions(&bad_perms_directory, Permissions::from_mode(0o100)) // execute owner only
28+
.err_tip(|| "Can't set perms on directory")?;
29+
30+
fs::set_permissions(&bad_perms_file, Permissions::from_mode(0o400)) // read owner only
31+
.err_tip(|| "Can't set perms on file")?;
32+
33+
remove_dir_all(&bad_perms_directory)
34+
.await
35+
.err_tip(|| format!("second remove_dir_all for {bad_perms_directory:?}"))?;
36+
37+
assert!(!fs::exists(&bad_perms_directory)?);
38+
Ok(())
39+
}

nativelink-worker/src/local_worker.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -465,9 +465,12 @@ pub async fn new_local_worker(
465465
);
466466

467467
if let Ok(path) = fs::canonicalize(&config.work_directory).await {
468-
fs::remove_dir_all(path)
469-
.await
470-
.err_tip(|| "Could not remove work_directory in LocalWorker")?;
468+
fs::remove_dir_all(&path).await.err_tip(|| {
469+
format!(
470+
"Could not remove work_directory '{}' in LocalWorker",
471+
&path.as_path().to_str().unwrap_or("bad path")
472+
)
473+
})?;
471474
}
472475

473476
fs::create_dir_all(&config.work_directory)

0 commit comments

Comments
 (0)