From 320198ca2343164cbc3ba26421157d0400ad9d61 Mon Sep 17 00:00:00 2001 From: venoosoo <120491991+venoosoo@users.noreply.github.com> Date: Mon, 16 Mar 2026 13:15:05 +0200 Subject: [PATCH 1/4] mv: don't prompt to overwrite read-only file when stdin is not a terminal --- src/uu/mv/src/mv.rs | 43 ++++++++++++++++++++++++++++++++-------- tests/by-util/test_mv.rs | 19 ++++++++++++++++++ 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 517e70a4fc6..1af556e0963 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -20,7 +20,7 @@ use rustc_hash::FxHashSet; use std::env; use std::ffi::OsString; use std::fs; -use std::io::{self, IsTerminal}; +use std::io::{self, IsTerminal, stdin}; #[cfg(unix)] use std::os::unix; #[cfg(unix)] @@ -105,6 +105,11 @@ pub struct Options { /// `--debug` pub debug: bool, + #[doc(hidden)] + /// `---presume-input-tty` + /// Always use `None`; GNU flag for testing use only + pub __presume_input_tty: Option, + /// `-Z, --context` pub context: Option, } @@ -123,6 +128,7 @@ impl Default for Options { progress_bar: false, debug: false, context: None, + __presume_input_tty: None, } } } @@ -153,6 +159,8 @@ static ARG_FILES: &str = "files"; static OPT_DEBUG: &str = "debug"; static OPT_CONTEXT: &str = "context"; static OPT_SELINUX: &str = "selinux"; +static PRESUME_INPUT_TTY: &str = "-presume-input-tty"; + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { @@ -220,6 +228,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { progress_bar: matches.get_flag(OPT_PROGRESS), debug: matches.get_flag(OPT_DEBUG), context, + __presume_input_tty: if matches.get_flag(PRESUME_INPUT_TTY) { + Some(true) + } else { + None + } }; mv(&files[..], &opts) @@ -333,6 +346,13 @@ pub fn uu_app() -> Command { .help(translate!("mv-help-debug")) .action(ArgAction::SetTrue), ) + .arg( + Arg::new(PRESUME_INPUT_TTY) + .long("presume-input-tty") + .alias(PRESUME_INPUT_TTY) + .hide(true) + .action(ArgAction::SetTrue), + ) } fn determine_overwrite_mode(matches: &ArgMatches) -> OverwriteMode { @@ -427,12 +447,12 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()> } else if target.exists() && source_is_dir { match opts.overwrite { OverwriteMode::NoClobber => return Ok(()), - OverwriteMode::Interactive => prompt_overwrite(target, None)?, + OverwriteMode::Interactive => prompt_overwrite(target, None,opts)?, OverwriteMode::Force => {} OverwriteMode::Default => { let (writable, mode) = is_writable(target); - if !writable && io::stdin().is_terminal() { - prompt_overwrite(target, mode)?; + if !writable && stdin().is_terminal() { + prompt_overwrite(target, mode,opts)?; } } } @@ -725,13 +745,13 @@ fn rename( } return Ok(()); } - OverwriteMode::Interactive => prompt_overwrite(to, None)?, + OverwriteMode::Interactive => prompt_overwrite(to, None,opts)?, OverwriteMode::Force => {} OverwriteMode::Default => { // GNU mv prompts when stdin is a TTY and target is not writable let (writable, mode) = is_writable(to); - if !writable && io::stdin().is_terminal() { - prompt_overwrite(to, mode)?; + if !writable && stdin().is_terminal() { + prompt_overwrite(to, mode,opts)?; } } } @@ -1276,7 +1296,14 @@ fn get_interactive_prompt(to: &Path, _cached_mode: Option) -> String { } /// Prompts the user for confirmation and returns an error if declined. -fn prompt_overwrite(to: &Path, cached_mode: Option) -> io::Result<()> { +fn prompt_overwrite(to: &Path, cached_mode: Option, options: &Options) -> io::Result<()> { + let stdin_ok = options.__presume_input_tty.unwrap_or(false) || stdin().is_terminal(); + match (stdin_ok, &options.overwrite) { + // stdin is not a terminal and this is just the default protection prompt + // skip silently like GNU mv does + (false, OverwriteMode::Default) => return Ok(()), + _ => {} + } if !prompt_yes!("{}", get_interactive_prompt(to, cached_mode)) { return Err(io::Error::other("")); } diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 80448ba1b84..55caaf6ce21 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -18,6 +18,25 @@ use uutests::util::TerminalSimulation; use uutests::util::TestScenario; use uutests::{at_and_ucmd, util_name}; + + +#[cfg(unix)] +#[test] +fn test_mv_no_prompt_when_stdin_not_terminal() { + let (at, mut ucmd) = at_and_ucmd!(); + at.write("target", "original"); + at.set_mode("target", 0o555); + at.write("source", "replacement"); + + ucmd.arg("source") + .arg("target") + .pipe_in("") + .succeeds(); + + assert_eq!(at.read("target"), "replacement"); +} + + #[test] fn test_mv_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails_with_code(1); From 684f8fa8b53b28786ee30039b7c9b3dcc743e516 Mon Sep 17 00:00:00 2001 From: venoosoo <120491991+venoosoo@users.noreply.github.com> Date: Mon, 16 Mar 2026 13:18:12 +0200 Subject: [PATCH 2/4] mv: cargo fmt fix --- src/uu/mv/src/mv.rs | 11 +++++------ tests/by-util/test_mv.rs | 12 +++--------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 1af556e0963..427dabbd535 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -161,7 +161,6 @@ static OPT_CONTEXT: &str = "context"; static OPT_SELINUX: &str = "selinux"; static PRESUME_INPUT_TTY: &str = "-presume-input-tty"; - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; @@ -232,7 +231,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { Some(true) } else { None - } + }, }; mv(&files[..], &opts) @@ -447,12 +446,12 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()> } else if target.exists() && source_is_dir { match opts.overwrite { OverwriteMode::NoClobber => return Ok(()), - OverwriteMode::Interactive => prompt_overwrite(target, None,opts)?, + OverwriteMode::Interactive => prompt_overwrite(target, None, opts)?, OverwriteMode::Force => {} OverwriteMode::Default => { let (writable, mode) = is_writable(target); if !writable && stdin().is_terminal() { - prompt_overwrite(target, mode,opts)?; + prompt_overwrite(target, mode, opts)?; } } } @@ -745,13 +744,13 @@ fn rename( } return Ok(()); } - OverwriteMode::Interactive => prompt_overwrite(to, None,opts)?, + OverwriteMode::Interactive => prompt_overwrite(to, None, opts)?, OverwriteMode::Force => {} OverwriteMode::Default => { // GNU mv prompts when stdin is a TTY and target is not writable let (writable, mode) = is_writable(to); if !writable && stdin().is_terminal() { - prompt_overwrite(to, mode,opts)?; + prompt_overwrite(to, mode, opts)?; } } } diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 55caaf6ce21..8dbc71e12be 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -18,8 +18,6 @@ use uutests::util::TerminalSimulation; use uutests::util::TestScenario; use uutests::{at_and_ucmd, util_name}; - - #[cfg(unix)] #[test] fn test_mv_no_prompt_when_stdin_not_terminal() { @@ -27,16 +25,12 @@ fn test_mv_no_prompt_when_stdin_not_terminal() { at.write("target", "original"); at.set_mode("target", 0o555); at.write("source", "replacement"); - - ucmd.arg("source") - .arg("target") - .pipe_in("") - .succeeds(); - + + ucmd.arg("source").arg("target").pipe_in("").succeeds(); + assert_eq!(at.read("target"), "replacement"); } - #[test] fn test_mv_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails_with_code(1); From 659b5d2eb30697d9105e7be99a4b38caa84449d3 Mon Sep 17 00:00:00 2001 From: venoosoo <120491991+venoosoo@users.noreply.github.com> Date: Mon, 16 Mar 2026 14:08:29 +0200 Subject: [PATCH 3/4] mv: remove hidden flag, use stdin().is_terminal() directly --- src/uu/mv/src/mv.rs | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 427dabbd535..5df467e6e7e 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -105,10 +105,6 @@ pub struct Options { /// `--debug` pub debug: bool, - #[doc(hidden)] - /// `---presume-input-tty` - /// Always use `None`; GNU flag for testing use only - pub __presume_input_tty: Option, /// `-Z, --context` pub context: Option, @@ -128,7 +124,6 @@ impl Default for Options { progress_bar: false, debug: false, context: None, - __presume_input_tty: None, } } } @@ -159,7 +154,6 @@ static ARG_FILES: &str = "files"; static OPT_DEBUG: &str = "debug"; static OPT_CONTEXT: &str = "context"; static OPT_SELINUX: &str = "selinux"; -static PRESUME_INPUT_TTY: &str = "-presume-input-tty"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { @@ -227,11 +221,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { progress_bar: matches.get_flag(OPT_PROGRESS), debug: matches.get_flag(OPT_DEBUG), context, - __presume_input_tty: if matches.get_flag(PRESUME_INPUT_TTY) { - Some(true) - } else { - None - }, }; mv(&files[..], &opts) @@ -345,13 +334,6 @@ pub fn uu_app() -> Command { .help(translate!("mv-help-debug")) .action(ArgAction::SetTrue), ) - .arg( - Arg::new(PRESUME_INPUT_TTY) - .long("presume-input-tty") - .alias(PRESUME_INPUT_TTY) - .hide(true) - .action(ArgAction::SetTrue), - ) } fn determine_overwrite_mode(matches: &ArgMatches) -> OverwriteMode { @@ -1296,12 +1278,10 @@ fn get_interactive_prompt(to: &Path, _cached_mode: Option) -> String { /// Prompts the user for confirmation and returns an error if declined. fn prompt_overwrite(to: &Path, cached_mode: Option, options: &Options) -> io::Result<()> { - let stdin_ok = options.__presume_input_tty.unwrap_or(false) || stdin().is_terminal(); - match (stdin_ok, &options.overwrite) { - // stdin is not a terminal and this is just the default protection prompt - // skip silently like GNU mv does - (false, OverwriteMode::Default) => return Ok(()), - _ => {} + if !stdin().is_terminal() { + if options.overwrite == OverwriteMode::Default { + return Ok(()); + } } if !prompt_yes!("{}", get_interactive_prompt(to, cached_mode)) { return Err(io::Error::other("")); From 658f428570ea7721852aed1e6ded5ef50de89ff3 Mon Sep 17 00:00:00 2001 From: venoosoo <120491991+venoosoo@users.noreply.github.com> Date: Mon, 16 Mar 2026 14:09:21 +0200 Subject: [PATCH 4/4] mv: fmt fix --- src/uu/mv/src/mv.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 5df467e6e7e..33a78080b19 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -105,7 +105,6 @@ pub struct Options { /// `--debug` pub debug: bool, - /// `-Z, --context` pub context: Option, }