From 73088187bc9fcfb94038e5f3b398d85ddb0abf5a Mon Sep 17 00:00:00 2001 From: gabrielhnf Date: Fri, 8 May 2026 21:58:13 -0300 Subject: [PATCH 1/5] chmod: fix EROFS showing as Permission denied --- src/uu/chmod/locales/en-US.ftl | 1 + src/uu/chmod/locales/fr-FR.ftl | 1 + src/uu/chmod/src/chmod.rs | 12 ++++++++++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/uu/chmod/locales/en-US.ftl b/src/uu/chmod/locales/en-US.ftl index 12df1e2b7a5..4294ab31fce 100644 --- a/src/uu/chmod/locales/en-US.ftl +++ b/src/uu/chmod/locales/en-US.ftl @@ -10,6 +10,7 @@ chmod-error-no-such-file = cannot access {$file}: No such file or directory chmod-error-preserve-root = it is dangerous to operate recursively on {$file} chmod: use --no-preserve-root to override this failsafe chmod-error-permission-denied = cannot access {$file}: Permission denied +chmod-error-read-only-file-system = changing permissions of {$file}: Read-only file system chmod-error-new-permissions = {$file}: new permissions are {$actual}, not {$expected} chmod-error-missing-operand = missing operand diff --git a/src/uu/chmod/locales/fr-FR.ftl b/src/uu/chmod/locales/fr-FR.ftl index f4e21b1b725..27ec0061356 100644 --- a/src/uu/chmod/locales/fr-FR.ftl +++ b/src/uu/chmod/locales/fr-FR.ftl @@ -22,6 +22,7 @@ chmod-error-no-such-file = impossible d'accéder à {$file} : Aucun fichier ou r chmod-error-preserve-root = il est dangereux d'opérer récursivement sur {$file} chmod: utiliser --no-preserve-root pour outrepasser cette protection chmod-error-permission-denied = impossible d'accéder à {$file} : Permission refusée +chmod-error-read-only-file-system = modification des permissions de {$file} : Système de fichiers en lecture seule chmod-error-new-permissions = {$file} : les nouvelles permissions sont {$actual}, pas {$expected} chmod-error-missing-operand = opérande manquant diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 6eb9add77ed..2aa0a85c956 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -35,6 +35,8 @@ enum ChmodError { PreserveRoot(PathBuf), #[error("{}", translate!("chmod-error-permission-denied", "file" => _0.quote()))] PermissionDenied(PathBuf), + #[error("{}", translate!("chmod-error-read-only-file-system", "file" => _0.quote()))] + ReadOnlyFileSystem(PathBuf), #[error("{}", translate!("chmod-error-new-permissions", "file" => _0.maybe_quote(), "actual" => _1.clone(), "expected" => _2.clone()))] NewPermissions(PathBuf, String, String), } @@ -609,14 +611,20 @@ impl Chmoder { let (new_mode, _) = self.calculate_new_mode(current_mode, file_path.is_dir())?; // Use safe traversal to change the mode - if let Err(_e) = dir_fd.chmod_at(entry_name, new_mode, symlink_behavior) { + if let Err(e) = dir_fd.chmod_at(entry_name, new_mode, symlink_behavior) { if self.verbose { println!( "failed to change mode of {} to {new_mode:o}", file_path.quote(), ); } - return Err(ChmodError::PermissionDenied(file_path.into()).into()); + let err = if e.kind() == std::io::ErrorKind::ReadOnlyFilesystem { + ChmodError::ReadOnlyFileSystem(file_path.into()) + } else { + ChmodError::PermissionDenied(file_path.into()) + }; + + return Err(err.into()); } // Report the change using the helper method From 1451e75197a41a6eb028c7994318b0621f90b342 Mon Sep 17 00:00:00 2001 From: gabrielhnf Date: Sat, 9 May 2026 09:06:27 -0300 Subject: [PATCH 2/5] fix formatting --- src/uu/chmod/src/chmod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 2aa0a85c956..9274b2c6333 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -619,10 +619,10 @@ impl Chmoder { ); } let err = if e.kind() == std::io::ErrorKind::ReadOnlyFilesystem { - ChmodError::ReadOnlyFileSystem(file_path.into()) - } else { - ChmodError::PermissionDenied(file_path.into()) - }; + ChmodError::ReadOnlyFileSystem(file_path.into()) + } else { + ChmodError::PermissionDenied(file_path.into()) + }; return Err(err.into()); } From b067af88708b3ccb538d0d7c2813cdea5a4cdc93 Mon Sep 17 00:00:00 2001 From: gabrielhnf Date: Sun, 10 May 2026 11:25:06 -0300 Subject: [PATCH 3/5] chmod: add test for EROFS error message --- tests/by-util/test_chmod.rs | 50 +++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index a75324b7c67..7558079eed9 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -403,6 +403,56 @@ fn test_permission_denied() { .stderr_is("chmod: cannot access 'd/no-x/y': Permission denied\n"); } +#[test] +#[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))] +fn test_chmod_readonly_filesystem() { + let mut scene = TestScenario::new(util_name!()); + + // Test must be run as root (or with `sudo -E`) + if scene.cmd("whoami").run().stdout_str() != "root\n" { + return; + } + + // Prepare the mount + let mountpoint = "readonly_mount"; + scene.fixtures.mkdir(mountpoint); + let mountpoint_path = scene.fixtures.plus_as_string(mountpoint); + + scene + .mount_temp_fs(&mountpoint_path) + .expect("mounting tmpfs failed"); + + // Create a file and set permissions so chmod will attempt to change them + scene.fixtures.touch(&format!("{mountpoint}/file.txt")); + scene.cmd("chmod").arg("400").arg(format!("{mountpoint_path}/file.txt")).run(); + + // Remount as read-only + scene + .cmd("mount") + .arg("-o") + .arg("remount,ro") + .arg(&mountpoint_path) + .run(); + + // Should say "Read-only file system" not "Permission denied" + scene + .ucmd() + .arg("ugo+w") + .arg(format!("{mountpoint_path}/file.txt")) + .fails() + .stderr_contains("Read-only file system"); + + // Remount as read-write so umount can clean up + scene + .cmd("mount") + .arg("-o") + .arg("remount,rw") + .arg(&mountpoint_path) + .run(); + + scene.umount_temp_fs(); +} + #[test] #[allow(clippy::unreadable_literal)] fn test_chmod_recursive_correct_exit_code() { From 652ba5084a7a329f246d193e884d71bc8cdfb71b Mon Sep 17 00:00:00 2001 From: gabrielhnf Date: Sun, 10 May 2026 11:30:26 -0300 Subject: [PATCH 4/5] fix formatting --- tests/by-util/test_chmod.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index 7558079eed9..f765ad2443b 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -424,7 +424,11 @@ fn test_chmod_readonly_filesystem() { // Create a file and set permissions so chmod will attempt to change them scene.fixtures.touch(&format!("{mountpoint}/file.txt")); - scene.cmd("chmod").arg("400").arg(format!("{mountpoint_path}/file.txt")).run(); + scene + .cmd("chmod") + .arg("400") + .arg(format!("{mountpoint_path}/file.txt")) + .run(); // Remount as read-only scene From 68eb7ed3fcb6b83601c4679ec23323d886ed3d63 Mon Sep 17 00:00:00 2001 From: gabrielhnf Date: Sun, 10 May 2026 11:42:02 -0300 Subject: [PATCH 5/5] fix clippy warning --- tests/by-util/test_chmod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index f765ad2443b..bc9c95f3bf4 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -423,7 +423,7 @@ fn test_chmod_readonly_filesystem() { .expect("mounting tmpfs failed"); // Create a file and set permissions so chmod will attempt to change them - scene.fixtures.touch(&format!("{mountpoint}/file.txt")); + scene.fixtures.touch(format!("{mountpoint}/file.txt")); scene .cmd("chmod") .arg("400")