diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index ca6e8a7c6..b1443dbb9 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -5,7 +5,7 @@ use clap::{builder::PossibleValue, crate_version, Arg, ArgAction, Command}; use std::fs::OpenOptions; -use std::io::{copy, sink, stdin, stdout, Error, ErrorKind, Read, Result, Write}; +use std::io::{copy, stdin, stdout, Error, ErrorKind, Read, Result, Write}; use std::path::PathBuf; use uucore::display::Quotable; use uucore::error::UResult; @@ -148,15 +148,10 @@ fn tee(options: &Options) -> Result<()> { } let mut writers: Vec = options .files - .clone() - .into_iter() - .map(|file| { - Ok(NamedWriter { - name: file.clone(), - inner: open(file, options.append, options.output_error.as_ref())?, - }) - }) + .iter() + .filter_map(|file| open(file, options.append, options.output_error.as_ref())) .collect::>>()?; + let had_open_errors = writers.len() != options.files.len(); writers.insert( 0, @@ -181,38 +176,41 @@ fn tee(options: &Options) -> Result<()> { _ => Ok(()), }; - if res.is_err() || output.flush().is_err() || output.error_occurred() { + if had_open_errors || res.is_err() || output.flush().is_err() || output.error_occurred() { Err(Error::from(ErrorKind::Other)) } else { Ok(()) } } +/// Tries to open the indicated file and return it. Reports an error if that's not possible. +/// If that error should lead to program termination, this function returns Some(Err()), +/// otherwise it returns None. fn open( - name: String, + name: &str, append: bool, output_error: Option<&OutputErrorMode>, -) -> Result> { - let path = PathBuf::from(name.clone()); - let inner: Box = { - let mut options = OpenOptions::new(); - let mode = if append { - options.append(true) - } else { - options.truncate(true) - }; - match mode.write(true).create(true).open(path.as_path()) { - Ok(file) => Box::new(file), - Err(f) => { - show_error!("{}: {}", name.maybe_quote(), f); - match output_error { - Some(OutputErrorMode::Exit | OutputErrorMode::ExitNoPipe) => return Err(f), - _ => Box::new(sink()), - } +) -> Option> { + let path = PathBuf::from(name); + let mut options = OpenOptions::new(); + let mode = if append { + options.append(true) + } else { + options.truncate(true) + }; + match mode.write(true).create(true).open(path.as_path()) { + Ok(file) => Some(Ok(NamedWriter { + inner: Box::new(file), + name: name.to_owned(), + })), + Err(f) => { + show_error!("{}: {}", name.maybe_quote(), f); + match output_error { + Some(OutputErrorMode::Exit | OutputErrorMode::ExitNoPipe) => Some(Err(f)), + _ => None, } } - }; - Ok(Box::new(NamedWriter { inner, name }) as Box) + } } struct MultiWriter { diff --git a/tests/by-util/test_tee.rs b/tests/by-util/test_tee.rs index c18668278..9ff4ea7dc 100644 --- a/tests/by-util/test_tee.rs +++ b/tests/by-util/test_tee.rs @@ -3,6 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. use crate::common::util::TestScenario; +use regex::Regex; #[cfg(target_os = "linux")] use std::fmt::Write; @@ -92,6 +93,27 @@ fn test_tee_no_more_writeable_1() { assert_eq!(at.read(file_out), content); } +#[test] +fn test_readonly() { + let (at, mut ucmd) = at_and_ucmd!(); + let content_tee = "hello"; + let content_file = "world"; + let file_out = "tee_file_out"; + let writable_file = "tee_file_out2"; + at.write(file_out, content_file); + at.set_readonly(file_out); + ucmd.arg(file_out) + .arg(writable_file) + .pipe_in(content_tee) + .ignore_stdin_write_error() + .fails() + .stdout_is(content_tee) + // Windows says "Access is denied" for some reason. + .stderr_matches(&Regex::new("(Permission|Access is) denied").unwrap()); + assert_eq!(at.read(file_out), content_file); + assert_eq!(at.read(writable_file), content_tee); +} + #[test] #[cfg(target_os = "linux")] fn test_tee_no_more_writeable_2() {