Skip to content

Commit fd4301d

Browse files
committed
tee: correctly handle writing to read-only files
1 parent 5c36ed9 commit fd4301d

File tree

2 files changed

+36
-11
lines changed

2 files changed

+36
-11
lines changed

src/uu/tee/src/tee.rs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
use clap::{builder::PossibleValue, crate_version, Arg, ArgAction, Command};
77
use std::fs::OpenOptions;
8-
use std::io::{copy, sink, stdin, stdout, Error, ErrorKind, Read, Result, Write};
8+
use std::io::{copy, stdin, stdout, Error, ErrorKind, Read, Result, Write};
99
use std::path::PathBuf;
1010
use uucore::display::Quotable;
1111
use uucore::error::UResult;
@@ -150,8 +150,9 @@ fn tee(options: &Options) -> Result<()> {
150150
.files
151151
.clone()
152152
.into_iter()
153-
.map(|file| open(file, options.append, options.output_error.as_ref()))
153+
.filter_map(|file| open(file, options.append, options.output_error.as_ref()))
154154
.collect::<Result<Vec<NamedWriter>>>()?;
155+
let had_open_errors = writers.len() != options.files.len();
155156

156157
writers.insert(
157158
0,
@@ -176,14 +177,21 @@ fn tee(options: &Options) -> Result<()> {
176177
_ => Ok(()),
177178
};
178179

179-
if res.is_err() || output.flush().is_err() || output.error_occurred() {
180+
if had_open_errors || res.is_err() || output.flush().is_err() || output.error_occurred() {
180181
Err(Error::from(ErrorKind::Other))
181182
} else {
182183
Ok(())
183184
}
184185
}
185186

186-
fn open(name: String, append: bool, output_error: Option<&OutputErrorMode>) -> Result<NamedWriter> {
187+
/// Tries to open the indicated file and return it. Reports an error if that's not possible.
188+
/// If that error should lead to program termination, this function returns Some(Err()),
189+
/// otherwise it returns None.
190+
fn open(
191+
name: String,
192+
append: bool,
193+
output_error: Option<&OutputErrorMode>,
194+
) -> Option<Result<NamedWriter>> {
187195
let path = PathBuf::from(name.clone());
188196
let mut options = OpenOptions::new();
189197
let mode = if append {
@@ -192,18 +200,15 @@ fn open(name: String, append: bool, output_error: Option<&OutputErrorMode>) -> R
192200
options.truncate(true)
193201
};
194202
match mode.write(true).create(true).open(path.as_path()) {
195-
Ok(file) => Ok(NamedWriter {
203+
Ok(file) => Some(Ok(NamedWriter {
196204
inner: Box::new(file),
197205
name,
198-
}),
206+
})),
199207
Err(f) => {
200208
show_error!("{}: {}", name.maybe_quote(), f);
201209
match output_error {
202-
Some(OutputErrorMode::Exit | OutputErrorMode::ExitNoPipe) => Err(f),
203-
_ => Ok(NamedWriter {
204-
inner: Box::new(sink()),
205-
name,
206-
}),
210+
Some(OutputErrorMode::Exit | OutputErrorMode::ExitNoPipe) => Some(Err(f)),
211+
_ => None,
207212
}
208213
}
209214
}

tests/by-util/test_tee.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,26 @@ fn test_tee_no_more_writeable_1() {
9292
assert_eq!(at.read(file_out), content);
9393
}
9494

95+
#[test]
96+
fn test_readonly() {
97+
let (at, mut ucmd) = at_and_ucmd!();
98+
let content_tee = "hello";
99+
let content_file = "world";
100+
let file_out = "tee_file_out";
101+
let writable_file = "tee_file_out2";
102+
at.write(file_out, content_file);
103+
at.set_readonly(file_out);
104+
ucmd.arg(file_out)
105+
.arg(writable_file)
106+
.pipe_in(content_tee)
107+
.ignore_stdin_write_error()
108+
.fails()
109+
.stdout_is(content_tee)
110+
.stderr_contains("Permission denied");
111+
assert_eq!(at.read(file_out), content_file);
112+
assert_eq!(at.read(writable_file), content_tee);
113+
}
114+
95115
#[test]
96116
#[cfg(target_os = "linux")]
97117
fn test_tee_no_more_writeable_2() {

0 commit comments

Comments
 (0)