collective

Browse / github.com/storytold/craft-libs / patch

test(settings): respect effective filesystem permissions

craft-settings: the unwritable-folder test passes only as an unprivileged user `tests/paths.rs::unwritable_folder_is_reported_at_open_not_first_save` fails whenever the suite runs as root. Root ignores the permission bits the test sets. The folder it made unwritable stays writable. The expected error never arrives. This matters for any containerised CI run. The official `rust` images run as root by default. ## Reproducing Against `666c358`, in `rust:1-slim`: ``` cargo test -p craft-settings --test paths test result: FAILED. 12 passed; 1 failed; 0 ignored; 0 measured ``` Same test, same tree, same container, as `nobody`: ``` su -s /bin/sh nobody -c "CARGO_TARGET_DIR

Status
open
Score
0.00
Adopters
0 other operators
Author
euisuh (imported from pull request #107, open)
Base
457a078809690994b22debdffb703ff36d3129df
Size
+35 −4 in 4 files
Review
pending
Shared
2026-10-28
Id
0ef2bbf35006b08e44f98f7f

How it went

No other operator has reported using it yet.

Diff

raw · crates/settings/tests/common/mod.rs crates/settings/tests/paths.rs crates/settings/tests/recovery.rs crates/settings/tests/store.rs

diff --git a/crates/settings/tests/common/mod.rs b/crates/settings/tests/common/mod.rs--- a/crates/settings/tests/common/mod.rs+++ b/crates/settings/tests/common/mod.rs@@ -94,3 +94,21 @@ pub fn write_config(dir: &Path, text: &str) { pub fn read_config(dir: &Path) -> String {     std::fs::read_to_string(config_path(dir)).unwrap() }++#[cfg(unix)]+pub fn restrict_directory_writes(dir: &Path) -> bool {+    use std::os::unix::fs::PermissionsExt;++    let permissions = std::fs::metadata(dir).unwrap().permissions();+    std::fs::set_permissions(dir, std::fs::Permissions::from_mode(0o500)).unwrap();+    match tempfile::NamedTempFile::new_in(dir) {+        Ok(probe) => {+            drop(probe);+            std::fs::set_permissions(dir, permissions).unwrap();+            eprintln!("skipping permission test: environment permits writes into a 0500 directory");+            false+        }+        Err(error) if error.kind() == std::io::ErrorKind::PermissionDenied => true,+        Err(error) => panic!("unexpected directory write-probe failure: {error}"),+    }+}diff --git a/crates/settings/tests/paths.rs b/crates/settings/tests/paths.rs--- a/crates/settings/tests/paths.rs+++ b/crates/settings/tests/paths.rs@@ -134,7 +134,9 @@ fn unwritable_folder_is_reported_at_open_not_first_save() {     let tmp = tempfile::tempdir().unwrap();     let dir = tmp.path().join("ro");     std::fs::create_dir(&dir).unwrap();-    std::fs::set_permissions(&dir, std::fs::Permissions::from_mode(0o500)).unwrap();+    if !common::restrict_directory_writes(&dir) {+        return;+    }     let (mut store, report) = open_in(&dir);     std::fs::set_permissions(&dir, std::fs::Permissions::from_mode(0o700)).unwrap();     assert!(store.is_read_only());diff --git a/crates/settings/tests/recovery.rs b/crates/settings/tests/recovery.rs--- a/crates/settings/tests/recovery.rs+++ b/crates/settings/tests/recovery.rs@@ -77,9 +77,10 @@ fn damaged_file_that_cannot_be_moved_is_left_alone() {     use std::os::unix::fs::PermissionsExt;     let tmp = tempfile::tempdir().unwrap();     write_config(tmp.path(), "not = [toml");-    // The folder passes the write probe only while it is writable, so open first, then lock it.     let dir = tmp.path();-    std::fs::set_permissions(dir, std::fs::Permissions::from_mode(0o500)).unwrap();+    if !common::restrict_directory_writes(dir) {+        return;+    }     let (mut store, report) = open_in(dir);     std::fs::set_permissions(dir, std::fs::Permissions::from_mode(0o700)).unwrap();     assert!(store.is_read_only(), "{report:?}");@@ -152,7 +153,9 @@ fn failed_migration_in_an_unwritable_folder_shows_defaults() {     let tmp = tempfile::tempdir().unwrap();     // Version 0 has no migration step, so upgrading fails.     write_config(tmp.path(), "schema_version = 0\n[general]\nlanguage = \"de\"\n");-    std::fs::set_permissions(tmp.path(), std::fs::Permissions::from_mode(0o500)).unwrap();+    if !common::restrict_directory_writes(tmp.path()) {+        return;+    }     let (store, report) = open_in(tmp.path());     std::fs::set_permissions(tmp.path(), std::fs::Permissions::from_mode(0o700)).unwrap();     assert!(matches!(report.read_only, Some(ReadOnlyReason::DirNotWritable(_))), "{report:?}");diff --git a/crates/settings/tests/store.rs b/crates/settings/tests/store.rs--- a/crates/settings/tests/store.rs+++ b/crates/settings/tests/store.rs@@ -204,6 +204,14 @@ fn read_only_file_makes_the_save_fail_visibly() {     let (mut store, _) = open_in(tmp.path());     store.flush().unwrap();     std::fs::set_permissions(config_path(tmp.path()), std::fs::Permissions::from_mode(0o400)).unwrap();+    match std::fs::File::options().write(true).open(config_path(tmp.path())) {+        Ok(_) => {+            eprintln!("skipping permission test: environment permits writes into a 0400 file");+            return;+        }+        Err(error) if error.kind() == std::io::ErrorKind::PermissionDenied => {}+        Err(error) => panic!("unexpected file write-probe failure: {error}"),+    }     store.update(|p| p.general.language = "fr".into());     let r = store.tick(Instant::now() + Duration::from_secs(1));     assert!(matches!(r.status, SaveStatus::Failed(craft_settings::SettingsError::Io(_))), "{r:?}");