Skip to content

Commit 43bf7dc

Browse files
committed
rm: skip protected prompt when stdin is not interactive; Fix #7326
1 parent cce6e49 commit 43bf7dc

File tree

2 files changed

+105
-15
lines changed

2 files changed

+105
-15
lines changed

src/uu/rm/src/rm.rs

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use clap::{builder::ValueParser, parser::ValueSource, Arg, ArgAction, Command};
99
use std::ffi::{OsStr, OsString};
1010
use std::fs::{self, Metadata};
11+
use std::io::{stdin, IsTerminal};
1112
use std::ops::BitOr;
1213
#[cfg(not(windows))]
1314
use std::os::unix::ffi::OsStrExt;
@@ -68,6 +69,25 @@ pub struct Options {
6869
pub dir: bool,
6970
/// `-v`, `--verbose`
7071
pub verbose: bool,
72+
#[doc(hidden)]
73+
/// `---presume-input-tty`
74+
/// Always use `None`; GNU flag for testing use only
75+
pub __presume_input_tty: Option<bool>,
76+
}
77+
78+
impl Default for Options {
79+
fn default() -> Self {
80+
Self {
81+
force: false,
82+
interactive: InteractiveMode::PromptProtected,
83+
one_fs: false,
84+
preserve_root: true,
85+
recursive: false,
86+
dir: false,
87+
verbose: false,
88+
__presume_input_tty: None,
89+
}
90+
}
7191
}
7292

7393
const ABOUT: &str = help_about!("rm.md");
@@ -145,6 +165,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
145165
recursive: matches.get_flag(OPT_RECURSIVE),
146166
dir: matches.get_flag(OPT_DIR),
147167
verbose: matches.get_flag(OPT_VERBOSE),
168+
__presume_input_tty: if matches.get_flag(PRESUME_INPUT_TTY) {
169+
Some(true)
170+
} else {
171+
None
172+
},
148173
};
149174
if options.interactive == InteractiveMode::Once && (options.recursive || files.len() > 3) {
150175
let msg: String = format!(
@@ -611,7 +636,13 @@ fn prompt_file(path: &Path, options: &Options) -> bool {
611636
prompt_yes!("remove file {}?", path.quote())
612637
};
613638
}
614-
prompt_file_permission_readonly(path)
639+
let stdin_ok = options.__presume_input_tty.unwrap_or(false) || stdin().is_terminal();
640+
let prompt_protected = stdin_ok && options.interactive == InteractiveMode::PromptProtected;
641+
if prompt_protected || options.interactive == InteractiveMode::Always {
642+
prompt_file_permission_readonly(path)
643+
} else {
644+
true
645+
}
615646
}
616647

617648
fn prompt_file_permission_readonly(path: &Path) -> bool {
@@ -625,26 +656,29 @@ fn prompt_file_permission_readonly(path: &Path) -> bool {
625656
}
626657
}
627658

628-
// For directories finding if they are writable or not is a hassle. In Unix we can use the built-in rust crate to to check mode bits. But other os don't have something similar afaik
659+
// For directories finding if they are writable or not is a hassle. In Unix we can use the built-in rust crate to check mode bits. But other os don't have something similar afaik
629660
// Most cases are covered by keep eye out for edge cases
630661
#[cfg(unix)]
631662
fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata) -> bool {
663+
let stdin_ok = options.__presume_input_tty.unwrap_or(false) || stdin().is_terminal();
632664
match (
665+
stdin_ok,
633666
is_readable_metadata(metadata),
634667
is_writable_metadata(metadata),
635668
options.interactive,
636669
) {
637-
(false, false, _) => prompt_yes!(
670+
(false, _, _, InteractiveMode::PromptProtected) => true,
671+
(_, false, false, _) => prompt_yes!(
638672
"attempt removal of inaccessible directory {}?",
639673
path.quote()
640674
),
641-
(false, true, InteractiveMode::Always) => prompt_yes!(
675+
(_, false, true, InteractiveMode::Always) => prompt_yes!(
642676
"attempt removal of inaccessible directory {}?",
643677
path.quote()
644678
),
645-
(true, false, _) => prompt_yes!("remove write-protected directory {}?", path.quote()),
646-
(_, _, InteractiveMode::Always) => prompt_yes!("remove directory {}?", path.quote()),
647-
(_, _, _) => true,
679+
(_, true, false, _) => prompt_yes!("remove write-protected directory {}?", path.quote()),
680+
(_, _, _, InteractiveMode::Always) => prompt_yes!("remove directory {}?", path.quote()),
681+
(_, _, _, _) => true,
648682
}
649683
}
650684

@@ -669,12 +703,12 @@ fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata
669703
use std::os::windows::prelude::MetadataExt;
670704
use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_READONLY;
671705
let not_user_writable = (metadata.file_attributes() & FILE_ATTRIBUTE_READONLY) != 0;
672-
if not_user_writable {
673-
prompt_yes!("remove write-protected directory {}?", path.quote())
674-
} else if options.interactive == InteractiveMode::Always {
675-
prompt_yes!("remove directory {}?", path.quote())
676-
} else {
677-
true
706+
let stdin_ok = options.__presume_input_tty.unwrap_or(false) || stdin().is_terminal();
707+
match (stdin_ok, not_user_writable, options.interactive) {
708+
(false, _, InteractiveMode::PromptProtected) => true,
709+
(_, true, _) => prompt_yes!("remove write-protected directory {}?", path.quote()),
710+
(_, _, InteractiveMode::Always) => prompt_yes!("remove directory {}?", path.quote()),
711+
(_, _, _) => true,
678712
}
679713
}
680714

tests/by-util/test_rm.rs

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,50 @@ fn test_rm_prompts() {
577577
assert!(!at.dir_exists("a"));
578578
}
579579

580+
#[cfg(feature = "chmod")]
581+
#[test]
582+
fn test_rm_prompts_no_tty() {
583+
// This test ensures InteractiveMode.PromptProtected proceeds silently with non-interactive stdin
584+
585+
use std::io::Write;
586+
587+
let scene = TestScenario::new(util_name!());
588+
let at = &scene.fixtures;
589+
590+
at.mkdir("a/");
591+
592+
let file_1 = "a/empty";
593+
let file_2 = "a/empty-no-write";
594+
let file_3 = "a/f-no-write";
595+
596+
at.touch(file_1);
597+
at.touch(file_2);
598+
at.make_file(file_3)
599+
.write_all(b"not-empty")
600+
.expect("Couldn't write to a/f-no-write");
601+
602+
at.symlink_dir("a/empty-f", "a/slink");
603+
at.symlink_dir(".", "a/slink-dot");
604+
605+
let dir_1 = "a/b/";
606+
let dir_2 = "a/b-no-write/";
607+
608+
at.mkdir(dir_1);
609+
at.mkdir(dir_2);
610+
611+
scene
612+
.ccmd("chmod")
613+
.arg("u-w")
614+
.arg(file_3)
615+
.arg(dir_2)
616+
.arg(file_2)
617+
.succeeds();
618+
619+
scene.ucmd().arg("-r").arg("a").succeeds().no_output();
620+
621+
assert!(!at.dir_exists("a"));
622+
}
623+
580624
#[test]
581625
fn test_rm_force_prompts_order() {
582626
// Needed for talking with stdin on platforms where CRLF or LF matters
@@ -644,7 +688,13 @@ fn test_prompt_write_protected_yes() {
644688

645689
scene.ccmd("chmod").arg("0").arg(file_1).succeeds();
646690

647-
scene.ucmd().arg(file_1).pipe_in("y").succeeds();
691+
scene
692+
.ucmd()
693+
.arg("---presume-input-tty")
694+
.arg(file_1)
695+
.pipe_in("y")
696+
.succeeds()
697+
.stderr_contains("rm: remove write-protected regular empty file");
648698
assert!(!at.file_exists(file_1));
649699
}
650700

@@ -659,7 +709,13 @@ fn test_prompt_write_protected_no() {
659709

660710
scene.ccmd("chmod").arg("0").arg(file_2).succeeds();
661711

662-
scene.ucmd().arg(file_2).pipe_in("n").succeeds();
712+
scene
713+
.ucmd()
714+
.arg("---presume-input-tty")
715+
.arg(file_2)
716+
.pipe_in("n")
717+
.succeeds()
718+
.stderr_contains("rm: remove write-protected regular empty file");
663719
assert!(at.file_exists(file_2));
664720
}
665721

0 commit comments

Comments
 (0)