mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-29 12:07:46 +00:00
commit
05823dd619
7 changed files with 619 additions and 25 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3096,6 +3096,7 @@ name = "uu_yes"
|
||||||
version = "0.0.14"
|
version = "0.0.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap 3.1.18",
|
"clap 3.1.18",
|
||||||
|
"libc",
|
||||||
"nix",
|
"nix",
|
||||||
"uucore",
|
"uucore",
|
||||||
]
|
]
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate uucore;
|
extern crate uucore;
|
||||||
|
|
||||||
use clap::{crate_version, Arg, Command};
|
use clap::{crate_version, Arg, Command, PossibleValue};
|
||||||
use retain_mut::RetainMut;
|
use retain_mut::RetainMut;
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::{copy, sink, stdin, stdout, Error, ErrorKind, Read, Result, Write};
|
use std::io::{copy, sink, stdin, stdout, Error, ErrorKind, Read, Result, Write};
|
||||||
|
@ -17,6 +17,8 @@ use uucore::display::Quotable;
|
||||||
use uucore::error::UResult;
|
use uucore::error::UResult;
|
||||||
use uucore::format_usage;
|
use uucore::format_usage;
|
||||||
|
|
||||||
|
// spell-checker:ignore nopipe
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use uucore::libc;
|
use uucore::libc;
|
||||||
|
|
||||||
|
@ -27,6 +29,8 @@ mod options {
|
||||||
pub const APPEND: &str = "append";
|
pub const APPEND: &str = "append";
|
||||||
pub const IGNORE_INTERRUPTS: &str = "ignore-interrupts";
|
pub const IGNORE_INTERRUPTS: &str = "ignore-interrupts";
|
||||||
pub const FILE: &str = "file";
|
pub const FILE: &str = "file";
|
||||||
|
pub const IGNORE_PIPE_ERRORS: &str = "ignore-pipe-errors";
|
||||||
|
pub const OUTPUT_ERROR: &str = "output-error";
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -34,6 +38,15 @@ struct Options {
|
||||||
append: bool,
|
append: bool,
|
||||||
ignore_interrupts: bool,
|
ignore_interrupts: bool,
|
||||||
files: Vec<String>,
|
files: Vec<String>,
|
||||||
|
output_error: Option<OutputErrorMode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum OutputErrorMode {
|
||||||
|
Warn,
|
||||||
|
WarnNoPipe,
|
||||||
|
Exit,
|
||||||
|
ExitNoPipe,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[uucore::main]
|
#[uucore::main]
|
||||||
|
@ -47,6 +60,25 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
.values_of(options::FILE)
|
.values_of(options::FILE)
|
||||||
.map(|v| v.map(ToString::to_string).collect())
|
.map(|v| v.map(ToString::to_string).collect())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
|
output_error: {
|
||||||
|
if matches.is_present(options::IGNORE_PIPE_ERRORS) {
|
||||||
|
Some(OutputErrorMode::WarnNoPipe)
|
||||||
|
} else if matches.is_present(options::OUTPUT_ERROR) {
|
||||||
|
if let Some(v) = matches.value_of(options::OUTPUT_ERROR) {
|
||||||
|
match v {
|
||||||
|
"warn" => Some(OutputErrorMode::Warn),
|
||||||
|
"warn-nopipe" => Some(OutputErrorMode::WarnNoPipe),
|
||||||
|
"exit" => Some(OutputErrorMode::Exit),
|
||||||
|
"exit-nopipe" => Some(OutputErrorMode::ExitNoPipe),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Some(OutputErrorMode::WarnNoPipe)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
match tee(&options) {
|
match tee(&options) {
|
||||||
|
@ -79,6 +111,29 @@ pub fn uu_app<'a>() -> Command<'a> {
|
||||||
.multiple_occurrences(true)
|
.multiple_occurrences(true)
|
||||||
.value_hint(clap::ValueHint::FilePath),
|
.value_hint(clap::ValueHint::FilePath),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new(options::IGNORE_PIPE_ERRORS)
|
||||||
|
.short('p')
|
||||||
|
.help("set write error behavior (ignored on non-Unix platforms)"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new(options::OUTPUT_ERROR)
|
||||||
|
.long(options::OUTPUT_ERROR)
|
||||||
|
.require_equals(true)
|
||||||
|
.min_values(0)
|
||||||
|
.max_values(1)
|
||||||
|
.possible_values([
|
||||||
|
PossibleValue::new("warn")
|
||||||
|
.help("produce warnings for errors writing to any output"),
|
||||||
|
PossibleValue::new("warn-nopipe")
|
||||||
|
.help("produce warnings for errors that are not pipe errors (ignored on non-unix platforms)"),
|
||||||
|
PossibleValue::new("exit").help("exit on write errors to any output"),
|
||||||
|
PossibleValue::new("exit-nopipe")
|
||||||
|
.help("exit on write errors to any output that are not pipe errors (equivalent to exit on non-unix platforms)"),
|
||||||
|
])
|
||||||
|
.help("set write error behavior")
|
||||||
|
.conflicts_with(options::IGNORE_PIPE_ERRORS),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
@ -96,19 +151,40 @@ fn ignore_interrupts() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn enable_pipe_errors() -> Result<()> {
|
||||||
|
let ret = unsafe { libc::signal(libc::SIGPIPE, libc::SIG_DFL) };
|
||||||
|
if ret == libc::SIG_ERR {
|
||||||
|
return Err(Error::new(ErrorKind::Other, ""));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
fn enable_pipe_errors() -> Result<()> {
|
||||||
|
// Do nothing.
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn tee(options: &Options) -> Result<()> {
|
fn tee(options: &Options) -> Result<()> {
|
||||||
if options.ignore_interrupts {
|
if options.ignore_interrupts {
|
||||||
ignore_interrupts()?;
|
ignore_interrupts()?;
|
||||||
}
|
}
|
||||||
|
if options.output_error.is_none() {
|
||||||
|
enable_pipe_errors()?;
|
||||||
|
}
|
||||||
|
|
||||||
let mut writers: Vec<NamedWriter> = options
|
let mut writers: Vec<NamedWriter> = options
|
||||||
.files
|
.files
|
||||||
.clone()
|
.clone()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|file| NamedWriter {
|
.map(|file| {
|
||||||
|
Ok(NamedWriter {
|
||||||
name: file.clone(),
|
name: file.clone(),
|
||||||
inner: open(file, options.append),
|
inner: open(file, options.append, options.output_error.as_ref())?,
|
||||||
})
|
})
|
||||||
.collect();
|
})
|
||||||
|
.collect::<Result<Vec<NamedWriter>>>()?;
|
||||||
|
|
||||||
writers.insert(
|
writers.insert(
|
||||||
0,
|
0,
|
||||||
|
@ -118,21 +194,33 @@ fn tee(options: &Options) -> Result<()> {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut output = MultiWriter::new(writers);
|
let mut output = MultiWriter::new(writers, options.output_error.clone());
|
||||||
let input = &mut NamedReader {
|
let input = &mut NamedReader {
|
||||||
inner: Box::new(stdin()) as Box<dyn Read>,
|
inner: Box::new(stdin()) as Box<dyn Read>,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: replaced generic 'copy' call to be able to stop copying
|
let res = match copy(input, &mut output) {
|
||||||
// if all outputs are closed (due to errors)
|
// ErrorKind::Other is raised by MultiWriter when all writers
|
||||||
if copy(input, &mut output).is_err() || output.flush().is_err() || output.error_occurred() {
|
// have exited, so that copy will abort. It's equivalent to
|
||||||
Err(Error::new(ErrorKind::Other, ""))
|
// success of this part (if there was an error that should
|
||||||
|
// cause a failure from any writer, that error would have been
|
||||||
|
// returned instead).
|
||||||
|
Err(e) if e.kind() != ErrorKind::Other => Err(e),
|
||||||
|
_ => Ok(()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if res.is_err() || output.flush().is_err() || output.error_occurred() {
|
||||||
|
Err(Error::from(ErrorKind::Other))
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open(name: String, append: bool) -> Box<dyn Write> {
|
fn open(
|
||||||
|
name: String,
|
||||||
|
append: bool,
|
||||||
|
output_error: Option<&OutputErrorMode>,
|
||||||
|
) -> Result<Box<dyn Write>> {
|
||||||
let path = PathBuf::from(name.clone());
|
let path = PathBuf::from(name.clone());
|
||||||
let inner: Box<dyn Write> = {
|
let inner: Box<dyn Write> = {
|
||||||
let mut options = OpenOptions::new();
|
let mut options = OpenOptions::new();
|
||||||
|
@ -143,58 +231,132 @@ fn open(name: String, append: bool) -> Box<dyn Write> {
|
||||||
};
|
};
|
||||||
match mode.write(true).create(true).open(path.as_path()) {
|
match mode.write(true).create(true).open(path.as_path()) {
|
||||||
Ok(file) => Box::new(file),
|
Ok(file) => Box::new(file),
|
||||||
Err(_) => Box::new(sink()),
|
Err(f) => {
|
||||||
|
show_error!("{}: {}", name.maybe_quote(), f);
|
||||||
|
match output_error {
|
||||||
|
Some(OutputErrorMode::Exit) | Some(OutputErrorMode::ExitNoPipe) => {
|
||||||
|
return Err(f)
|
||||||
|
}
|
||||||
|
_ => Box::new(sink()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Box::new(NamedWriter { inner, name }) as Box<dyn Write>
|
Ok(Box::new(NamedWriter { inner, name }) as Box<dyn Write>)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MultiWriter {
|
struct MultiWriter {
|
||||||
writers: Vec<NamedWriter>,
|
writers: Vec<NamedWriter>,
|
||||||
initial_len: usize,
|
output_error_mode: Option<OutputErrorMode>,
|
||||||
|
ignored_errors: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MultiWriter {
|
impl MultiWriter {
|
||||||
fn new(writers: Vec<NamedWriter>) -> Self {
|
fn new(writers: Vec<NamedWriter>, output_error_mode: Option<OutputErrorMode>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
initial_len: writers.len(),
|
|
||||||
writers,
|
writers,
|
||||||
|
output_error_mode,
|
||||||
|
ignored_errors: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn error_occurred(&self) -> bool {
|
fn error_occurred(&self) -> bool {
|
||||||
self.writers.len() != self.initial_len
|
self.ignored_errors != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_error(
|
||||||
|
mode: Option<&OutputErrorMode>,
|
||||||
|
f: Error,
|
||||||
|
writer: &NamedWriter,
|
||||||
|
ignored_errors: &mut usize,
|
||||||
|
) -> Result<()> {
|
||||||
|
match mode {
|
||||||
|
Some(OutputErrorMode::Warn) => {
|
||||||
|
show_error!("{}: {}", writer.name.maybe_quote(), f);
|
||||||
|
*ignored_errors += 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Some(OutputErrorMode::WarnNoPipe) | None => {
|
||||||
|
if f.kind() != ErrorKind::BrokenPipe {
|
||||||
|
show_error!("{}: {}", writer.name.maybe_quote(), f);
|
||||||
|
*ignored_errors += 1;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Some(OutputErrorMode::Exit) => {
|
||||||
|
show_error!("{}: {}", writer.name.maybe_quote(), f);
|
||||||
|
Err(f)
|
||||||
|
}
|
||||||
|
Some(OutputErrorMode::ExitNoPipe) => {
|
||||||
|
if f.kind() != ErrorKind::BrokenPipe {
|
||||||
|
show_error!("{}: {}", writer.name.maybe_quote(), f);
|
||||||
|
Err(f)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Write for MultiWriter {
|
impl Write for MultiWriter {
|
||||||
fn write(&mut self, buf: &[u8]) -> Result<usize> {
|
fn write(&mut self, buf: &[u8]) -> Result<usize> {
|
||||||
|
let mut aborted = None;
|
||||||
|
let mode = self.output_error_mode.clone();
|
||||||
|
let mut errors = 0;
|
||||||
RetainMut::retain_mut(&mut self.writers, |writer| {
|
RetainMut::retain_mut(&mut self.writers, |writer| {
|
||||||
let result = writer.write_all(buf);
|
let result = writer.write_all(buf);
|
||||||
match result {
|
match result {
|
||||||
Err(f) => {
|
Err(f) => {
|
||||||
show_error!("{}: {}", writer.name.maybe_quote(), f);
|
if let Err(e) = process_error(mode.as_ref(), f, writer, &mut errors) {
|
||||||
|
if aborted.is_none() {
|
||||||
|
aborted = Some(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
_ => true,
|
_ => true,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
self.ignored_errors += errors;
|
||||||
|
if let Some(e) = aborted {
|
||||||
|
Err(e)
|
||||||
|
} else if self.writers.is_empty() {
|
||||||
|
// This error kind will never be raised by the standard
|
||||||
|
// library, so we can use it for early termination of
|
||||||
|
// `copy`
|
||||||
|
Err(Error::from(ErrorKind::Other))
|
||||||
|
} else {
|
||||||
Ok(buf.len())
|
Ok(buf.len())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn flush(&mut self) -> Result<()> {
|
fn flush(&mut self) -> Result<()> {
|
||||||
|
let mut aborted = None;
|
||||||
|
let mode = self.output_error_mode.clone();
|
||||||
|
let mut errors = 0;
|
||||||
RetainMut::retain_mut(&mut self.writers, |writer| {
|
RetainMut::retain_mut(&mut self.writers, |writer| {
|
||||||
let result = writer.flush();
|
let result = writer.flush();
|
||||||
match result {
|
match result {
|
||||||
Err(f) => {
|
Err(f) => {
|
||||||
show_error!("{}: {}", writer.name.maybe_quote(), f);
|
if let Err(e) = process_error(mode.as_ref(), f, writer, &mut errors) {
|
||||||
|
if aborted.is_none() {
|
||||||
|
aborted = Some(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
_ => true,
|
_ => true,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
self.ignored_errors += errors;
|
||||||
|
if let Some(e) = aborted {
|
||||||
|
Err(e)
|
||||||
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct NamedWriter {
|
struct NamedWriter {
|
||||||
inner: Box<dyn Write>,
|
inner: Box<dyn Write>,
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
// * For the full copyright and license information, please view the LICENSE
|
// * For the full copyright and license information, please view the LICENSE
|
||||||
// * file that was distributed with this source code.
|
// * file that was distributed with this source code.
|
||||||
|
|
||||||
// spell-checker:ignore (ToDO) tstr sigstr cmdname setpgid sigchld
|
// spell-checker:ignore (ToDO) tstr sigstr cmdname setpgid sigchld getpid
|
||||||
mod status;
|
mod status;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -241,6 +241,47 @@ fn wait_or_kill_process(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn preserve_signal_info(signal: libc::c_int) -> libc::c_int {
|
||||||
|
// This is needed because timeout is expected to preserve the exit
|
||||||
|
// status of its child. It is not the case that utilities have a
|
||||||
|
// single simple exit code, that's an illusion some shells
|
||||||
|
// provide. Instead exit status is really two numbers:
|
||||||
|
//
|
||||||
|
// - An exit code if the program ran to completion
|
||||||
|
//
|
||||||
|
// - A signal number if the program was terminated by a signal
|
||||||
|
//
|
||||||
|
// The easiest way to preserve the latter seems to be to kill
|
||||||
|
// ourselves with whatever signal our child exited with, which is
|
||||||
|
// what the following is intended to accomplish.
|
||||||
|
unsafe {
|
||||||
|
libc::kill(libc::getpid(), signal);
|
||||||
|
}
|
||||||
|
signal
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
fn preserve_signal_info(signal: libc::c_int) -> libc::c_int {
|
||||||
|
// Do nothing
|
||||||
|
signal
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn enable_pipe_errors() -> std::io::Result<()> {
|
||||||
|
let ret = unsafe { libc::signal(libc::SIGPIPE, libc::SIG_DFL) };
|
||||||
|
if ret == libc::SIG_ERR {
|
||||||
|
return Err(std::io::Error::new(std::io::ErrorKind::Other, ""));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
fn enable_pipe_errors() -> std::io::Result<()> {
|
||||||
|
// Do nothing.
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// TODO: Improve exit codes, and make them consistent with the GNU Coreutils exit codes.
|
/// TODO: Improve exit codes, and make them consistent with the GNU Coreutils exit codes.
|
||||||
|
|
||||||
fn timeout(
|
fn timeout(
|
||||||
|
@ -255,6 +296,9 @@ fn timeout(
|
||||||
if !foreground {
|
if !foreground {
|
||||||
unsafe { libc::setpgid(0, 0) };
|
unsafe { libc::setpgid(0, 0) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enable_pipe_errors()?;
|
||||||
|
|
||||||
let mut process = process::Command::new(&cmd[0])
|
let mut process = process::Command::new(&cmd[0])
|
||||||
.args(&cmd[1..])
|
.args(&cmd[1..])
|
||||||
.stdin(Stdio::inherit())
|
.stdin(Stdio::inherit())
|
||||||
|
@ -286,7 +330,7 @@ fn timeout(
|
||||||
match process.wait_or_timeout(duration) {
|
match process.wait_or_timeout(duration) {
|
||||||
Ok(Some(status)) => Err(status
|
Ok(Some(status)) => Err(status
|
||||||
.code()
|
.code()
|
||||||
.unwrap_or_else(|| status.signal().unwrap())
|
.unwrap_or_else(|| preserve_signal_info(status.signal().unwrap()))
|
||||||
.into()),
|
.into()),
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
report_if_verbose(signal, &cmd[0], verbose);
|
report_if_verbose(signal, &cmd[0], verbose);
|
||||||
|
|
|
@ -16,6 +16,7 @@ path = "src/yes.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
|
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
|
||||||
|
libc = "0.2.126"
|
||||||
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["pipes"] }
|
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["pipes"] }
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
|
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
/* last synced with: yes (GNU coreutils) 8.13 */
|
/* last synced with: yes (GNU coreutils) 8.13 */
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Result, Write};
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate clap;
|
extern crate clap;
|
||||||
|
@ -70,10 +70,27 @@ fn prepare_buffer<'a>(input: &'a str, buffer: &'a mut [u8; BUF_SIZE]) -> &'a [u8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn enable_pipe_errors() -> Result<()> {
|
||||||
|
let ret = unsafe { libc::signal(libc::SIGPIPE, libc::SIG_DFL) };
|
||||||
|
if ret == libc::SIG_ERR {
|
||||||
|
return Err(io::Error::new(io::ErrorKind::Other, ""));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
fn enable_pipe_errors() -> Result<()> {
|
||||||
|
// Do nothing.
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn exec(bytes: &[u8]) -> io::Result<()> {
|
pub fn exec(bytes: &[u8]) -> io::Result<()> {
|
||||||
let stdout = io::stdout();
|
let stdout = io::stdout();
|
||||||
let mut stdout = stdout.lock();
|
let mut stdout = stdout.lock();
|
||||||
|
|
||||||
|
enable_pipe_errors()?;
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
{
|
{
|
||||||
match splice::splice_data(bytes, &stdout) {
|
match splice::splice_data(bytes, &stdout) {
|
||||||
|
|
|
@ -4,6 +4,8 @@ use crate::common::util::*;
|
||||||
// inspired by:
|
// inspired by:
|
||||||
// https://github.com/coreutils/coreutils/tests/misc/tee.sh
|
// https://github.com/coreutils/coreutils/tests/misc/tee.sh
|
||||||
|
|
||||||
|
// spell-checker:ignore nopipe
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tee_processing_multiple_operands() {
|
fn test_tee_processing_multiple_operands() {
|
||||||
// POSIX says: "Processing of at least 13 file operands shall be supported."
|
// POSIX says: "Processing of at least 13 file operands shall be supported."
|
||||||
|
@ -98,3 +100,356 @@ fn test_tee_no_more_writeable_2() {
|
||||||
// assert_eq!(at.read(file_out_b), content);
|
// assert_eq!(at.read(file_out_b), content);
|
||||||
// assert!(result.stderr.contains("No space left on device"));
|
// assert!(result.stderr.contains("No space left on device"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
mod linux_only {
|
||||||
|
use crate::common::util::*;
|
||||||
|
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::process::Output;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
fn make_broken_pipe() -> File {
|
||||||
|
use libc::c_int;
|
||||||
|
use std::os::unix::io::FromRawFd;
|
||||||
|
|
||||||
|
let mut fds: [c_int; 2] = [0, 0];
|
||||||
|
if unsafe { libc::pipe(&mut fds as *mut c_int) } != 0 {
|
||||||
|
panic!("Failed to create pipe");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop the read end of the pipe
|
||||||
|
let _ = unsafe { File::from_raw_fd(fds[0]) };
|
||||||
|
|
||||||
|
// Make the write end of the pipe into a Rust File
|
||||||
|
unsafe { File::from_raw_fd(fds[1]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_tee(proc: &mut UCommand) -> (String, Output) {
|
||||||
|
let content = (1..=100000).map(|x| format!("{}\n", x)).collect::<String>();
|
||||||
|
|
||||||
|
let mut prog = proc.run_no_wait();
|
||||||
|
|
||||||
|
let mut stdin = prog
|
||||||
|
.stdin
|
||||||
|
.take()
|
||||||
|
.unwrap_or_else(|| panic!("Could not take child process stdin"));
|
||||||
|
|
||||||
|
let c = content.clone();
|
||||||
|
let thread = thread::spawn(move || {
|
||||||
|
let _ = stdin.write_all(c.as_bytes());
|
||||||
|
});
|
||||||
|
|
||||||
|
let output = prog.wait_with_output().unwrap();
|
||||||
|
|
||||||
|
thread.join().unwrap();
|
||||||
|
|
||||||
|
(content, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_success(output: &Output) {
|
||||||
|
assert!(
|
||||||
|
output.status.success(),
|
||||||
|
"Command was expected to succeed.\nstdout = {}\n stderr = {}",
|
||||||
|
std::str::from_utf8(&output.stdout).unwrap(),
|
||||||
|
std::str::from_utf8(&output.stderr).unwrap(),
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
output.stderr.is_empty(),
|
||||||
|
"Unexpected data on stderr.\n stderr = {}",
|
||||||
|
std::str::from_utf8(&output.stderr).unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_failure(output: &Output, message: &str) {
|
||||||
|
assert!(
|
||||||
|
!output.status.success(),
|
||||||
|
"Command was expected to fail.\nstdout = {}\n stderr = {}",
|
||||||
|
std::str::from_utf8(&output.stdout).unwrap(),
|
||||||
|
std::str::from_utf8(&output.stderr).unwrap(),
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
std::str::from_utf8(&output.stderr)
|
||||||
|
.unwrap()
|
||||||
|
.contains(message),
|
||||||
|
"Expected to see error message fragment {} in stderr, but did not.\n stderr = {}",
|
||||||
|
message,
|
||||||
|
std::str::from_utf8(&output.stderr).unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_silent_failure(output: &Output) {
|
||||||
|
assert!(
|
||||||
|
!output.status.success(),
|
||||||
|
"Command was expected to fail.\nstdout = {}\n stderr = {}",
|
||||||
|
std::str::from_utf8(&output.stdout).unwrap(),
|
||||||
|
std::str::from_utf8(&output.stderr).unwrap(),
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
output.stderr.is_empty(),
|
||||||
|
"Unexpected data on stderr.\n stderr = {}",
|
||||||
|
std::str::from_utf8(&output.stderr).unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_correct(name: &str, at: &AtPath, contents: &str) {
|
||||||
|
assert!(at.file_exists(name));
|
||||||
|
let compare = at.read(name);
|
||||||
|
assert_eq!(compare, contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_short(name: &str, at: &AtPath, contents: &str) {
|
||||||
|
assert!(at.file_exists(name));
|
||||||
|
let compare = at.read(name);
|
||||||
|
assert!(
|
||||||
|
compare.len() < contents.len(),
|
||||||
|
"Too many bytes ({}) written to {} (should be a short count from {})",
|
||||||
|
compare.len(),
|
||||||
|
name,
|
||||||
|
contents.len()
|
||||||
|
);
|
||||||
|
assert!(contents.starts_with(&compare),
|
||||||
|
"Expected truncated output to be a prefix of the correct output, but it isn't.\n Correct: {}\n Compare: {}",
|
||||||
|
contents,
|
||||||
|
compare);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pipe_error_default() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
let file_out_a = "tee_file_out_a";
|
||||||
|
|
||||||
|
let proc = ucmd.arg(file_out_a).set_stdout(make_broken_pipe());
|
||||||
|
|
||||||
|
let (content, output) = run_tee(proc);
|
||||||
|
|
||||||
|
expect_silent_failure(&output);
|
||||||
|
expect_short(file_out_a, &at, content.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pipe_error_warn_nopipe_1() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
let file_out_a = "tee_file_out_a";
|
||||||
|
|
||||||
|
let proc = ucmd
|
||||||
|
.arg("-p")
|
||||||
|
.arg(file_out_a)
|
||||||
|
.set_stdout(make_broken_pipe());
|
||||||
|
|
||||||
|
let (content, output) = run_tee(proc);
|
||||||
|
|
||||||
|
expect_success(&output);
|
||||||
|
expect_correct(file_out_a, &at, content.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pipe_error_warn_nopipe_2() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
let file_out_a = "tee_file_out_a";
|
||||||
|
|
||||||
|
let proc = ucmd
|
||||||
|
.arg("--output-error")
|
||||||
|
.arg(file_out_a)
|
||||||
|
.set_stdout(make_broken_pipe());
|
||||||
|
|
||||||
|
let (content, output) = run_tee(proc);
|
||||||
|
|
||||||
|
expect_success(&output);
|
||||||
|
expect_correct(file_out_a, &at, content.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pipe_error_warn_nopipe_3() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
let file_out_a = "tee_file_out_a";
|
||||||
|
|
||||||
|
let proc = ucmd
|
||||||
|
.arg("--output-error=warn-nopipe")
|
||||||
|
.arg(file_out_a)
|
||||||
|
.set_stdout(make_broken_pipe());
|
||||||
|
|
||||||
|
let (content, output) = run_tee(proc);
|
||||||
|
|
||||||
|
expect_success(&output);
|
||||||
|
expect_correct(file_out_a, &at, content.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pipe_error_warn() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
let file_out_a = "tee_file_out_a";
|
||||||
|
|
||||||
|
let proc = ucmd
|
||||||
|
.arg("--output-error=warn")
|
||||||
|
.arg(file_out_a)
|
||||||
|
.set_stdout(make_broken_pipe());
|
||||||
|
|
||||||
|
let (content, output) = run_tee(proc);
|
||||||
|
|
||||||
|
expect_failure(&output, "Broken pipe");
|
||||||
|
expect_correct(file_out_a, &at, content.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pipe_error_exit() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
let file_out_a = "tee_file_out_a";
|
||||||
|
|
||||||
|
let proc = ucmd
|
||||||
|
.arg("--output-error=exit")
|
||||||
|
.arg(file_out_a)
|
||||||
|
.set_stdout(make_broken_pipe());
|
||||||
|
|
||||||
|
let (content, output) = run_tee(proc);
|
||||||
|
|
||||||
|
expect_failure(&output, "Broken pipe");
|
||||||
|
expect_short(file_out_a, &at, content.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pipe_error_exit_nopipe() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
let file_out_a = "tee_file_out_a";
|
||||||
|
|
||||||
|
let proc = ucmd
|
||||||
|
.arg("--output-error=exit-nopipe")
|
||||||
|
.arg(file_out_a)
|
||||||
|
.set_stdout(make_broken_pipe());
|
||||||
|
|
||||||
|
let (content, output) = run_tee(proc);
|
||||||
|
|
||||||
|
expect_success(&output);
|
||||||
|
expect_correct(file_out_a, &at, content.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_space_error_default() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
let file_out_a = "tee_file_out_a";
|
||||||
|
|
||||||
|
let proc = ucmd.arg(file_out_a).arg("/dev/full");
|
||||||
|
|
||||||
|
let (content, output) = run_tee(proc);
|
||||||
|
|
||||||
|
expect_failure(&output, "No space left");
|
||||||
|
expect_correct(file_out_a, &at, content.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_space_error_warn_nopipe_1() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
let file_out_a = "tee_file_out_a";
|
||||||
|
|
||||||
|
let proc = ucmd
|
||||||
|
.arg("-p")
|
||||||
|
.arg(file_out_a)
|
||||||
|
.arg("/dev/full")
|
||||||
|
.set_stdout(make_broken_pipe());
|
||||||
|
|
||||||
|
let (content, output) = run_tee(proc);
|
||||||
|
|
||||||
|
expect_failure(&output, "No space left");
|
||||||
|
expect_correct(file_out_a, &at, content.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_space_error_warn_nopipe_2() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
let file_out_a = "tee_file_out_a";
|
||||||
|
|
||||||
|
let proc = ucmd
|
||||||
|
.arg("--output-error")
|
||||||
|
.arg(file_out_a)
|
||||||
|
.arg("/dev/full")
|
||||||
|
.set_stdout(make_broken_pipe());
|
||||||
|
|
||||||
|
let (content, output) = run_tee(proc);
|
||||||
|
|
||||||
|
expect_failure(&output, "No space left");
|
||||||
|
expect_correct(file_out_a, &at, content.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_space_error_warn_nopipe_3() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
let file_out_a = "tee_file_out_a";
|
||||||
|
|
||||||
|
let proc = ucmd
|
||||||
|
.arg("--output-error=warn-nopipe")
|
||||||
|
.arg(file_out_a)
|
||||||
|
.arg("/dev/full")
|
||||||
|
.set_stdout(make_broken_pipe());
|
||||||
|
|
||||||
|
let (content, output) = run_tee(proc);
|
||||||
|
|
||||||
|
expect_failure(&output, "No space left");
|
||||||
|
expect_correct(file_out_a, &at, content.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_space_error_warn() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
let file_out_a = "tee_file_out_a";
|
||||||
|
|
||||||
|
let proc = ucmd
|
||||||
|
.arg("--output-error=warn")
|
||||||
|
.arg(file_out_a)
|
||||||
|
.arg("/dev/full")
|
||||||
|
.set_stdout(make_broken_pipe());
|
||||||
|
|
||||||
|
let (content, output) = run_tee(proc);
|
||||||
|
|
||||||
|
expect_failure(&output, "No space left");
|
||||||
|
expect_correct(file_out_a, &at, content.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_space_error_exit() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
let file_out_a = "tee_file_out_a";
|
||||||
|
|
||||||
|
let proc = ucmd
|
||||||
|
.arg("--output-error=exit")
|
||||||
|
.arg(file_out_a)
|
||||||
|
.arg("/dev/full")
|
||||||
|
.set_stdout(make_broken_pipe());
|
||||||
|
|
||||||
|
let (content, output) = run_tee(proc);
|
||||||
|
|
||||||
|
expect_failure(&output, "No space left");
|
||||||
|
expect_short(file_out_a, &at, content.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_space_error_exit_nopipe() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
let file_out_a = "tee_file_out_a";
|
||||||
|
|
||||||
|
let proc = ucmd
|
||||||
|
.arg("--output-error=exit-nopipe")
|
||||||
|
.arg(file_out_a)
|
||||||
|
.arg("/dev/full")
|
||||||
|
.set_stdout(make_broken_pipe());
|
||||||
|
|
||||||
|
let (content, output) = run_tee(proc);
|
||||||
|
|
||||||
|
expect_failure(&output, "No space left");
|
||||||
|
expect_short(file_out_a, &at, content.as_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,21 @@
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
use std::process::ExitStatus;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::unix::process::ExitStatusExt;
|
||||||
|
|
||||||
use crate::common::util::*;
|
use crate::common::util::*;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn check_termination(result: &ExitStatus) {
|
||||||
|
assert_eq!(result.signal(), Some(libc::SIGPIPE as i32));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
fn check_termination(result: &ExitStatus) {
|
||||||
|
assert!(result.success(), "yes did not exit successfully");
|
||||||
|
}
|
||||||
|
|
||||||
/// Run `yes`, capture some of the output, close the pipe, and verify it.
|
/// Run `yes`, capture some of the output, close the pipe, and verify it.
|
||||||
fn run(args: &[&str], expected: &[u8]) {
|
fn run(args: &[&str], expected: &[u8]) {
|
||||||
let mut cmd = new_ucmd!();
|
let mut cmd = new_ucmd!();
|
||||||
|
@ -10,7 +24,7 @@ fn run(args: &[&str], expected: &[u8]) {
|
||||||
let mut buf = vec![0; expected.len()];
|
let mut buf = vec![0; expected.len()];
|
||||||
stdout.read_exact(&mut buf).unwrap();
|
stdout.read_exact(&mut buf).unwrap();
|
||||||
drop(stdout);
|
drop(stdout);
|
||||||
assert!(child.wait().unwrap().success());
|
check_termination(&child.wait().unwrap());
|
||||||
assert_eq!(buf.as_slice(), expected);
|
assert_eq!(buf.as_slice(), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue