Skip to content

Commit 320198c

Browse files
committed
mv: don't prompt to overwrite read-only file when stdin is not a terminal
1 parent bc58d3d commit 320198c

2 files changed

Lines changed: 54 additions & 8 deletions

File tree

src/uu/mv/src/mv.rs

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use rustc_hash::FxHashSet;
2020
use std::env;
2121
use std::ffi::OsString;
2222
use std::fs;
23-
use std::io::{self, IsTerminal};
23+
use std::io::{self, IsTerminal, stdin};
2424
#[cfg(unix)]
2525
use std::os::unix;
2626
#[cfg(unix)]
@@ -105,6 +105,11 @@ pub struct Options {
105105
/// `--debug`
106106
pub debug: bool,
107107

108+
#[doc(hidden)]
109+
/// `---presume-input-tty`
110+
/// Always use `None`; GNU flag for testing use only
111+
pub __presume_input_tty: Option<bool>,
112+
108113
/// `-Z, --context`
109114
pub context: Option<String>,
110115
}
@@ -123,6 +128,7 @@ impl Default for Options {
123128
progress_bar: false,
124129
debug: false,
125130
context: None,
131+
__presume_input_tty: None,
126132
}
127133
}
128134
}
@@ -153,6 +159,8 @@ static ARG_FILES: &str = "files";
153159
static OPT_DEBUG: &str = "debug";
154160
static OPT_CONTEXT: &str = "context";
155161
static OPT_SELINUX: &str = "selinux";
162+
static PRESUME_INPUT_TTY: &str = "-presume-input-tty";
163+
156164

157165
#[uucore::main]
158166
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
@@ -220,6 +228,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
220228
progress_bar: matches.get_flag(OPT_PROGRESS),
221229
debug: matches.get_flag(OPT_DEBUG),
222230
context,
231+
__presume_input_tty: if matches.get_flag(PRESUME_INPUT_TTY) {
232+
Some(true)
233+
} else {
234+
None
235+
}
223236
};
224237

225238
mv(&files[..], &opts)
@@ -333,6 +346,13 @@ pub fn uu_app() -> Command {
333346
.help(translate!("mv-help-debug"))
334347
.action(ArgAction::SetTrue),
335348
)
349+
.arg(
350+
Arg::new(PRESUME_INPUT_TTY)
351+
.long("presume-input-tty")
352+
.alias(PRESUME_INPUT_TTY)
353+
.hide(true)
354+
.action(ArgAction::SetTrue),
355+
)
336356
}
337357

338358
fn determine_overwrite_mode(matches: &ArgMatches) -> OverwriteMode {
@@ -427,12 +447,12 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()>
427447
} else if target.exists() && source_is_dir {
428448
match opts.overwrite {
429449
OverwriteMode::NoClobber => return Ok(()),
430-
OverwriteMode::Interactive => prompt_overwrite(target, None)?,
450+
OverwriteMode::Interactive => prompt_overwrite(target, None,opts)?,
431451
OverwriteMode::Force => {}
432452
OverwriteMode::Default => {
433453
let (writable, mode) = is_writable(target);
434-
if !writable && io::stdin().is_terminal() {
435-
prompt_overwrite(target, mode)?;
454+
if !writable && stdin().is_terminal() {
455+
prompt_overwrite(target, mode,opts)?;
436456
}
437457
}
438458
}
@@ -725,13 +745,13 @@ fn rename(
725745
}
726746
return Ok(());
727747
}
728-
OverwriteMode::Interactive => prompt_overwrite(to, None)?,
748+
OverwriteMode::Interactive => prompt_overwrite(to, None,opts)?,
729749
OverwriteMode::Force => {}
730750
OverwriteMode::Default => {
731751
// GNU mv prompts when stdin is a TTY and target is not writable
732752
let (writable, mode) = is_writable(to);
733-
if !writable && io::stdin().is_terminal() {
734-
prompt_overwrite(to, mode)?;
753+
if !writable && stdin().is_terminal() {
754+
prompt_overwrite(to, mode,opts)?;
735755
}
736756
}
737757
}
@@ -1276,7 +1296,14 @@ fn get_interactive_prompt(to: &Path, _cached_mode: Option<u32>) -> String {
12761296
}
12771297

12781298
/// Prompts the user for confirmation and returns an error if declined.
1279-
fn prompt_overwrite(to: &Path, cached_mode: Option<u32>) -> io::Result<()> {
1299+
fn prompt_overwrite(to: &Path, cached_mode: Option<u32>, options: &Options) -> io::Result<()> {
1300+
let stdin_ok = options.__presume_input_tty.unwrap_or(false) || stdin().is_terminal();
1301+
match (stdin_ok, &options.overwrite) {
1302+
// stdin is not a terminal and this is just the default protection prompt
1303+
// skip silently like GNU mv does
1304+
(false, OverwriteMode::Default) => return Ok(()),
1305+
_ => {}
1306+
}
12801307
if !prompt_yes!("{}", get_interactive_prompt(to, cached_mode)) {
12811308
return Err(io::Error::other(""));
12821309
}

tests/by-util/test_mv.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,25 @@ use uutests::util::TerminalSimulation;
1818
use uutests::util::TestScenario;
1919
use uutests::{at_and_ucmd, util_name};
2020

21+
22+
23+
#[cfg(unix)]
24+
#[test]
25+
fn test_mv_no_prompt_when_stdin_not_terminal() {
26+
let (at, mut ucmd) = at_and_ucmd!();
27+
at.write("target", "original");
28+
at.set_mode("target", 0o555);
29+
at.write("source", "replacement");
30+
31+
ucmd.arg("source")
32+
.arg("target")
33+
.pipe_in("")
34+
.succeeds();
35+
36+
assert_eq!(at.read("target"), "replacement");
37+
}
38+
39+
2140
#[test]
2241
fn test_mv_invalid_arg() {
2342
new_ucmd!().arg("--definitely-invalid").fails_with_code(1);

0 commit comments

Comments
 (0)