Skip to content

Commit d957e64

Browse files
authored
Merge pull request #7492 from bitspill/rm
rm: skip prompt when stdin is not interactive; Fix #7326
1 parent 07501be commit d957e64

File tree

2 files changed

+105
-19
lines changed

2 files changed

+105
-19
lines changed

src/uu/rm/src/rm.rs

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use clap::{Arg, ArgAction, Command, builder::ValueParser, parser::ValueSource};
99
use std::ffi::{OsStr, OsString};
1010
use std::fs::{self, Metadata};
11+
use std::io::{IsTerminal, stdin};
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!(
@@ -608,40 +633,45 @@ fn prompt_file(path: &Path, options: &Options) -> bool {
608633
prompt_yes!("remove file {}?", path.quote())
609634
};
610635
}
611-
prompt_file_permission_readonly(path)
636+
prompt_file_permission_readonly(path, options)
612637
}
613638

614-
fn prompt_file_permission_readonly(path: &Path) -> bool {
615-
match fs::metadata(path) {
616-
Ok(_) if is_writable(path) => true,
617-
Ok(metadata) if metadata.len() == 0 => prompt_yes!(
639+
fn prompt_file_permission_readonly(path: &Path, options: &Options) -> bool {
640+
let stdin_ok = options.__presume_input_tty.unwrap_or(false) || stdin().is_terminal();
641+
match (stdin_ok, fs::metadata(path), options.interactive) {
642+
(false, _, InteractiveMode::PromptProtected) => true,
643+
(_, Ok(_), _) if is_writable(path) => true,
644+
(_, Ok(metadata), _) if metadata.len() == 0 => prompt_yes!(
618645
"remove write-protected regular empty file {}?",
619646
path.quote()
620647
),
621648
_ => prompt_yes!("remove write-protected regular file {}?", path.quote()),
622649
}
623650
}
624651

625-
// 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
652+
// 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
626653
// Most cases are covered by keep eye out for edge cases
627654
#[cfg(unix)]
628655
fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata) -> bool {
656+
let stdin_ok = options.__presume_input_tty.unwrap_or(false) || stdin().is_terminal();
629657
match (
658+
stdin_ok,
630659
is_readable_metadata(metadata),
631660
is_writable_metadata(metadata),
632661
options.interactive,
633662
) {
634-
(false, false, _) => prompt_yes!(
663+
(false, _, _, InteractiveMode::PromptProtected) => true,
664+
(_, false, false, _) => prompt_yes!(
635665
"attempt removal of inaccessible directory {}?",
636666
path.quote()
637667
),
638-
(false, true, InteractiveMode::Always) => prompt_yes!(
668+
(_, false, true, InteractiveMode::Always) => prompt_yes!(
639669
"attempt removal of inaccessible directory {}?",
640670
path.quote()
641671
),
642-
(true, false, _) => prompt_yes!("remove write-protected directory {}?", path.quote()),
643-
(_, _, InteractiveMode::Always) => prompt_yes!("remove directory {}?", path.quote()),
644-
(_, _, _) => true,
672+
(_, true, false, _) => prompt_yes!("remove write-protected directory {}?", path.quote()),
673+
(_, _, _, InteractiveMode::Always) => prompt_yes!("remove directory {}?", path.quote()),
674+
(_, _, _, _) => true,
645675
}
646676
}
647677

@@ -666,12 +696,12 @@ fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata
666696
use std::os::windows::prelude::MetadataExt;
667697
use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_READONLY;
668698
let not_user_writable = (metadata.file_attributes() & FILE_ATTRIBUTE_READONLY) != 0;
669-
if not_user_writable {
670-
prompt_yes!("remove write-protected directory {}?", path.quote())
671-
} else if options.interactive == InteractiveMode::Always {
672-
prompt_yes!("remove directory {}?", path.quote())
673-
} else {
674-
true
699+
let stdin_ok = options.__presume_input_tty.unwrap_or(false) || stdin().is_terminal();
700+
match (stdin_ok, not_user_writable, options.interactive) {
701+
(false, _, InteractiveMode::PromptProtected) => true,
702+
(_, true, _) => prompt_yes!("remove write-protected directory {}?", path.quote()),
703+
(_, _, InteractiveMode::Always) => prompt_yes!("remove directory {}?", path.quote()),
704+
(_, _, _) => true,
675705
}
676706
}
677707

tests/by-util/test_rm.rs

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

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

647691
scene.ccmd("chmod").arg("0").arg(file_1).succeeds();
648692

649-
scene.ucmd().arg(file_1).pipe_in("y").succeeds();
693+
scene
694+
.ucmd()
695+
.arg("---presume-input-tty")
696+
.arg(file_1)
697+
.pipe_in("y")
698+
.succeeds()
699+
.stderr_contains("rm: remove write-protected regular empty file");
650700
assert!(!at.file_exists(file_1));
651701
}
652702

@@ -661,7 +711,13 @@ fn test_prompt_write_protected_no() {
661711

662712
scene.ccmd("chmod").arg("0").arg(file_2).succeeds();
663713

664-
scene.ucmd().arg(file_2).pipe_in("n").succeeds();
714+
scene
715+
.ucmd()
716+
.arg("---presume-input-tty")
717+
.arg(file_2)
718+
.pipe_in("n")
719+
.succeeds()
720+
.stderr_contains("rm: remove write-protected regular empty file");
665721
assert!(at.file_exists(file_2));
666722
}
667723

0 commit comments

Comments
 (0)