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
@@ -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}"),+ }+}@@ -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());@@ -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:?}");@@ -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:?}");