mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 03:27:44 +00:00
yes: Cleanup
Report errors properly instead of panicking. Replace zero_copy by a simpler specialized private module. Do not assume splices move all data at once. Use the modern uutils machinery. Remove the "latency" feature. The time it takes to prepare the buffer is drowned out by the startup time anyway. yes: Add tests yes: Fix long input test on Windows
This commit is contained in:
parent
ea16cc72c7
commit
e5d6c6970b
15 changed files with 217 additions and 398 deletions
|
@ -106,12 +106,14 @@ whoami
|
|||
|
||||
# * vars/errno
|
||||
errno
|
||||
EBADF
|
||||
EEXIST
|
||||
EINVAL
|
||||
ENODATA
|
||||
ENOENT
|
||||
ENOSYS
|
||||
EPERM
|
||||
EOPNOTSUPP
|
||||
EPERM
|
||||
|
||||
# * vars/fcntl
|
||||
F_GETFL
|
||||
|
|
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -3141,6 +3141,7 @@ name = "uu_yes"
|
|||
version = "0.0.7"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"nix 0.20.0",
|
||||
"uucore",
|
||||
"uucore_procs",
|
||||
]
|
||||
|
@ -3157,8 +3158,6 @@ dependencies = [
|
|||
"getopts",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"nix 0.19.1",
|
||||
"platform-info",
|
||||
"termion",
|
||||
"thiserror",
|
||||
"time",
|
||||
|
|
|
@ -16,13 +16,11 @@ path = "src/yes.rs"
|
|||
|
||||
[dependencies]
|
||||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["zero-copy"] }
|
||||
uucore = { version=">=0.0.9", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
#
|
||||
latency = []
|
||||
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
|
||||
nix = "0.20.0"
|
||||
|
||||
[[bin]]
|
||||
name = "yes"
|
||||
|
|
110
src/uu/yes/src/splice.rs
Normal file
110
src/uu/yes/src/splice.rs
Normal file
|
@ -0,0 +1,110 @@
|
|||
//! On Linux we can use vmsplice() to write data more efficiently.
|
||||
//!
|
||||
//! This does not always work. We're not allowed to splice to some targets,
|
||||
//! and on some systems (notably WSL 1) it isn't supported at all.
|
||||
//!
|
||||
//! If we get an error code that suggests splicing isn't supported then we
|
||||
//! tell that to the caller so it can fall back to a robust naïve method. If
|
||||
//! we get another kind of error we bubble it up as normal.
|
||||
//!
|
||||
//! vmsplice() can only splice into a pipe, so if the output is not a pipe
|
||||
//! we make our own and use splice() to bridge the gap from the pipe to the
|
||||
//! output.
|
||||
//!
|
||||
//! We assume that an "unsupported" error will only ever happen before any
|
||||
//! data was successfully written to the output. That way we don't have to
|
||||
//! make any effort to rescue data from the pipe if splice() fails, we can
|
||||
//! just fall back and start over from the beginning.
|
||||
|
||||
use std::{
|
||||
fs::File,
|
||||
io,
|
||||
os::unix::io::{AsRawFd, FromRawFd},
|
||||
};
|
||||
|
||||
use nix::{
|
||||
errno::Errno,
|
||||
fcntl::SpliceFFlags,
|
||||
libc::S_IFIFO,
|
||||
sys::{stat::fstat, uio::IoVec},
|
||||
};
|
||||
|
||||
pub(crate) fn splice_data(bytes: &[u8], out: &impl AsRawFd) -> Result<()> {
|
||||
let is_pipe = fstat(out.as_raw_fd())?.st_mode & S_IFIFO != 0;
|
||||
|
||||
if is_pipe {
|
||||
loop {
|
||||
let mut bytes = bytes;
|
||||
while !bytes.is_empty() {
|
||||
let len = vmsplice(out, bytes)?;
|
||||
bytes = &bytes[len..];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let (read, write) = pipe()?;
|
||||
loop {
|
||||
let mut bytes = bytes;
|
||||
while !bytes.is_empty() {
|
||||
let len = vmsplice(&write, bytes)?;
|
||||
let mut remaining = len;
|
||||
while remaining > 0 {
|
||||
match splice(&read, out, remaining)? {
|
||||
0 => panic!("Unexpected end of pipe"),
|
||||
n => remaining -= n,
|
||||
};
|
||||
}
|
||||
bytes = &bytes[len..];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum Error {
|
||||
Unsupported,
|
||||
Io(io::Error),
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
impl From<nix::Error> for Error {
|
||||
fn from(error: nix::Error) -> Self {
|
||||
match error {
|
||||
nix::Error::Sys(errno) => Error::Io(io::Error::from_raw_os_error(errno as i32)),
|
||||
_ => Error::Io(io::Error::last_os_error()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_unsupported(error: nix::Error) -> Error {
|
||||
match error.as_errno() {
|
||||
Some(Errno::EINVAL) | Some(Errno::ENOSYS) | Some(Errno::EBADF) => Error::Unsupported,
|
||||
_ => error.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn splice(source: &impl AsRawFd, target: &impl AsRawFd, len: usize) -> Result<usize> {
|
||||
nix::fcntl::splice(
|
||||
source.as_raw_fd(),
|
||||
None,
|
||||
target.as_raw_fd(),
|
||||
None,
|
||||
len,
|
||||
SpliceFFlags::empty(),
|
||||
)
|
||||
.map_err(maybe_unsupported)
|
||||
}
|
||||
|
||||
fn vmsplice(target: &impl AsRawFd, bytes: &[u8]) -> Result<usize> {
|
||||
nix::fcntl::vmsplice(
|
||||
target.as_raw_fd(),
|
||||
&[IoVec::from_slice(bytes)],
|
||||
SpliceFFlags::empty(),
|
||||
)
|
||||
.map_err(maybe_unsupported)
|
||||
}
|
||||
|
||||
fn pipe() -> nix::Result<(File, File)> {
|
||||
let (read, write) = nix::unistd::pipe()?;
|
||||
// SAFETY: The file descriptors do not have other owners.
|
||||
unsafe { Ok((File::from_raw_fd(read), File::from_raw_fd(write))) }
|
||||
}
|
|
@ -7,37 +7,27 @@
|
|||
|
||||
/* last synced with: yes (GNU coreutils) 8.13 */
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::io::{self, Write};
|
||||
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use std::borrow::Cow;
|
||||
use std::io::{self, Write};
|
||||
use uucore::zero_copy::ZeroCopyWriter;
|
||||
use uucore::error::{UResult, USimpleError};
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
mod splice;
|
||||
|
||||
// it's possible that using a smaller or larger buffer might provide better performance on some
|
||||
// systems, but honestly this is good enough
|
||||
const BUF_SIZE: usize = 16 * 1024;
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let app = uu_app();
|
||||
|
||||
let matches = match app.get_matches_from_safe(args) {
|
||||
Ok(m) => m,
|
||||
Err(ref e)
|
||||
if e.kind == clap::ErrorKind::HelpDisplayed
|
||||
|| e.kind == clap::ErrorKind::VersionDisplayed =>
|
||||
{
|
||||
println!("{}", e);
|
||||
return 0;
|
||||
}
|
||||
Err(f) => {
|
||||
show_error!("{}", f);
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
#[uucore_procs::gen_uumain]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app().get_matches_from(args);
|
||||
|
||||
let string = if let Some(values) = matches.values_of("STRING") {
|
||||
let mut result = values.fold(String::new(), |res, s| res + s + " ");
|
||||
|
@ -51,16 +41,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
let mut buffer = [0; BUF_SIZE];
|
||||
let bytes = prepare_buffer(&string, &mut buffer);
|
||||
|
||||
exec(bytes);
|
||||
|
||||
0
|
||||
match exec(bytes) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(err) if err.kind() == io::ErrorKind::BrokenPipe => Ok(()),
|
||||
Err(err) => Err(USimpleError::new(1, format!("standard output: {}", err))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
app_from_crate!().arg(Arg::with_name("STRING").index(1).multiple(true))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "latency"))]
|
||||
fn prepare_buffer<'a>(input: &'a str, buffer: &'a mut [u8; BUF_SIZE]) -> &'a [u8] {
|
||||
if input.len() < BUF_SIZE / 2 {
|
||||
let mut size = 0;
|
||||
|
@ -75,16 +66,20 @@ fn prepare_buffer<'a>(input: &'a str, buffer: &'a mut [u8; BUF_SIZE]) -> &'a [u8
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "latency")]
|
||||
fn prepare_buffer<'a>(input: &'a str, _buffer: &'a mut [u8; BUF_SIZE]) -> &'a [u8] {
|
||||
input.as_bytes()
|
||||
}
|
||||
pub fn exec(bytes: &[u8]) -> io::Result<()> {
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
{
|
||||
match splice::splice_data(bytes, &stdout) {
|
||||
Ok(_) => return Ok(()),
|
||||
Err(splice::Error::Io(err)) => return Err(err),
|
||||
Err(splice::Error::Unsupported) => (),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exec(bytes: &[u8]) {
|
||||
let mut stdout_raw = io::stdout();
|
||||
let mut writer = ZeroCopyWriter::with_default(&mut stdout_raw, |stdout| stdout.lock());
|
||||
loop {
|
||||
// TODO: needs to check if pipe fails
|
||||
writer.write_all(bytes).unwrap();
|
||||
stdout.write_all(bytes)?;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,9 +22,6 @@ getopts = "<= 0.2.21"
|
|||
wild = "2.0"
|
||||
# * optional
|
||||
thiserror = { version="1.0", optional=true }
|
||||
lazy_static = { version="1.3", optional=true }
|
||||
nix = { version="<= 0.19", optional=true }
|
||||
platform-info = { version="<= 0.1", optional=true }
|
||||
time = { version="<= 0.1.43", optional=true }
|
||||
walkdir = { version="2.3.2", optional=true }
|
||||
# * "problem" dependencies (pinned)
|
||||
|
@ -58,4 +55,3 @@ signals = []
|
|||
utf8 = []
|
||||
utmpx = ["time", "libc", "dns-lookup"]
|
||||
wide = []
|
||||
zero-copy = ["nix", "libc", "lazy_static", "platform-info"]
|
||||
|
|
|
@ -8,8 +8,6 @@ pub mod fs;
|
|||
pub mod fsext;
|
||||
#[cfg(feature = "ringbuffer")]
|
||||
pub mod ringbuffer;
|
||||
#[cfg(feature = "zero-copy")]
|
||||
pub mod zero_copy;
|
||||
|
||||
// * (platform-specific) feature-gated modules
|
||||
// ** non-windows
|
||||
|
|
|
@ -1,148 +0,0 @@
|
|||
use self::platform::*;
|
||||
|
||||
use std::io::{self, Write};
|
||||
|
||||
mod platform;
|
||||
|
||||
pub trait AsRawObject {
|
||||
fn as_raw_object(&self) -> RawObject;
|
||||
}
|
||||
|
||||
pub trait FromRawObject: Sized {
|
||||
/// # Safety
|
||||
/// ToDO ...
|
||||
unsafe fn from_raw_object(obj: RawObject) -> Option<Self>;
|
||||
}
|
||||
|
||||
// TODO: also make a SpliceWriter that takes an input fd and and output fd and uses splice() to
|
||||
// transfer data
|
||||
// TODO: make a TeeWriter or something that takes an input fd and two output fds and uses tee() to
|
||||
// transfer to both output fds
|
||||
|
||||
enum InnerZeroCopyWriter<T: Write + Sized> {
|
||||
Platform(PlatformZeroCopyWriter),
|
||||
Standard(T),
|
||||
}
|
||||
|
||||
impl<T: Write + Sized> Write for InnerZeroCopyWriter<T> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
match self {
|
||||
InnerZeroCopyWriter::Platform(ref mut writer) => writer.write(buf),
|
||||
InnerZeroCopyWriter::Standard(ref mut writer) => writer.write(buf),
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
match self {
|
||||
InnerZeroCopyWriter::Platform(ref mut writer) => writer.flush(),
|
||||
InnerZeroCopyWriter::Standard(ref mut writer) => writer.flush(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ZeroCopyWriter<T: Write + AsRawObject + Sized> {
|
||||
/// This field is never used, but we need it to drop file descriptors
|
||||
#[allow(dead_code)]
|
||||
raw_obj_owner: Option<T>,
|
||||
|
||||
inner: InnerZeroCopyWriter<T>,
|
||||
}
|
||||
|
||||
struct TransformContainer<'a, A: Write + AsRawObject + Sized, B: Write + Sized> {
|
||||
/// This field is never used and probably could be converted into PhantomData, but might be
|
||||
/// useful for restructuring later (at the moment it's basically left over from an earlier
|
||||
/// design)
|
||||
#[allow(dead_code)]
|
||||
original: Option<&'a mut A>,
|
||||
|
||||
transformed: Option<B>,
|
||||
}
|
||||
|
||||
impl<'a, A: Write + AsRawObject + Sized, B: Write + Sized> Write for TransformContainer<'a, A, B> {
|
||||
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
|
||||
self.transformed.as_mut().unwrap().write(bytes)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.transformed.as_mut().unwrap().flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A: Write + AsRawObject + Sized, B: Write + Sized> AsRawObject
|
||||
for TransformContainer<'a, A, B>
|
||||
{
|
||||
fn as_raw_object(&self) -> RawObject {
|
||||
panic!("Test should never be used")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Write + AsRawObject + Sized> ZeroCopyWriter<T> {
|
||||
pub fn new(writer: T) -> Self {
|
||||
let raw_obj = writer.as_raw_object();
|
||||
match unsafe { PlatformZeroCopyWriter::new(raw_obj) } {
|
||||
Ok(inner) => ZeroCopyWriter {
|
||||
raw_obj_owner: Some(writer),
|
||||
inner: InnerZeroCopyWriter::Platform(inner),
|
||||
},
|
||||
_ => {
|
||||
// creating the splice writer failed for whatever reason, so just make a default
|
||||
// writer
|
||||
ZeroCopyWriter {
|
||||
raw_obj_owner: None,
|
||||
inner: InnerZeroCopyWriter::Standard(writer),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_default<'a: 'b, 'b, F, W>(
|
||||
writer: &'a mut T,
|
||||
func: F,
|
||||
) -> ZeroCopyWriter<impl Write + AsRawObject + Sized + 'b>
|
||||
where
|
||||
F: Fn(&'a mut T) -> W,
|
||||
W: Write + Sized + 'b,
|
||||
{
|
||||
let raw_obj = writer.as_raw_object();
|
||||
match unsafe { PlatformZeroCopyWriter::new(raw_obj) } {
|
||||
Ok(inner) => ZeroCopyWriter {
|
||||
raw_obj_owner: Some(TransformContainer {
|
||||
original: Some(writer),
|
||||
transformed: None,
|
||||
}),
|
||||
inner: InnerZeroCopyWriter::Platform(inner),
|
||||
},
|
||||
_ => {
|
||||
// XXX: should func actually consume writer and leave it up to the user to save the value?
|
||||
// maybe provide a default stdin method then? in some cases it would make more sense for the
|
||||
// value to be consumed
|
||||
let real_writer = func(writer);
|
||||
ZeroCopyWriter {
|
||||
raw_obj_owner: None,
|
||||
inner: InnerZeroCopyWriter::Standard(TransformContainer {
|
||||
original: None,
|
||||
transformed: Some(real_writer),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: unsure how to get something like this working without allocating, so not providing it
|
||||
/*pub fn stdout() -> ZeroCopyWriter<impl Write + AsRawObject + Sized> {
|
||||
let mut stdout = io::stdout();
|
||||
ZeroCopyWriter::with_default(&mut stdout, |stdout| {
|
||||
stdout.lock()
|
||||
})
|
||||
}*/
|
||||
}
|
||||
|
||||
impl<T: Write + AsRawObject + Sized> Write for ZeroCopyWriter<T> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.inner.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.inner.flush()
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub use self::linux::*;
|
||||
#[cfg(unix)]
|
||||
pub use self::unix::*;
|
||||
#[cfg(windows)]
|
||||
pub use self::windows::*;
|
||||
|
||||
// Add any operating systems we support here
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
pub use self::default::*;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
mod linux;
|
||||
#[cfg(unix)]
|
||||
mod unix;
|
||||
#[cfg(windows)]
|
||||
mod windows;
|
||||
|
||||
// Add any operating systems we support here
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
mod default;
|
|
@ -1,21 +0,0 @@
|
|||
use crate::features::zero_copy::RawObject;
|
||||
|
||||
use std::io::{self, Write};
|
||||
|
||||
pub struct PlatformZeroCopyWriter;
|
||||
|
||||
impl PlatformZeroCopyWriter {
|
||||
pub unsafe fn new(_obj: RawObject) -> Result<Self, ()> {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for PlatformZeroCopyWriter {
|
||||
fn write(&mut self, _bytes: &[u8]) -> io::Result<usize> {
|
||||
panic!("should never occur")
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
panic!("should never occur")
|
||||
}
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
use std::io::{self, Write};
|
||||
use std::os::unix::io::RawFd;
|
||||
|
||||
use libc::{O_APPEND, S_IFIFO, S_IFREG};
|
||||
use nix::errno::Errno;
|
||||
use nix::fcntl::{fcntl, splice, vmsplice, FcntlArg, SpliceFFlags};
|
||||
use nix::sys::stat::{fstat, FileStat};
|
||||
use nix::sys::uio::IoVec;
|
||||
use nix::unistd::pipe;
|
||||
use platform_info::{PlatformInfo, Uname};
|
||||
|
||||
use crate::features::zero_copy::{FromRawObject, RawObject};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref IN_WSL: bool = {
|
||||
let info = PlatformInfo::new().unwrap();
|
||||
info.release().contains("Microsoft")
|
||||
};
|
||||
}
|
||||
|
||||
pub struct PlatformZeroCopyWriter {
|
||||
raw_obj: RawObject,
|
||||
read_pipe: RawFd,
|
||||
write_pipe: RawFd,
|
||||
#[allow(clippy::type_complexity)]
|
||||
write_fn: fn(&mut PlatformZeroCopyWriter, &[IoVec<&[u8]>], usize) -> io::Result<usize>,
|
||||
}
|
||||
|
||||
impl PlatformZeroCopyWriter {
|
||||
pub unsafe fn new(raw_obj: RawObject) -> nix::Result<Self> {
|
||||
if *IN_WSL {
|
||||
// apparently WSL hasn't implemented vmsplice(), causing writes to fail
|
||||
// thus, we will just say zero-copy doesn't work there rather than working
|
||||
// around it
|
||||
return Err(nix::Error::from(Errno::EOPNOTSUPP));
|
||||
}
|
||||
|
||||
let stat_info: FileStat = fstat(raw_obj)?;
|
||||
let access_mode: libc::c_int = fcntl(raw_obj, FcntlArg::F_GETFL)?;
|
||||
|
||||
let is_regular = (stat_info.st_mode & S_IFREG) != 0;
|
||||
let is_append = (access_mode & O_APPEND) != 0;
|
||||
let is_fifo = (stat_info.st_mode & S_IFIFO) != 0;
|
||||
|
||||
if is_regular && !is_append {
|
||||
let (read_pipe, write_pipe) = pipe()?;
|
||||
|
||||
Ok(PlatformZeroCopyWriter {
|
||||
raw_obj,
|
||||
read_pipe,
|
||||
write_pipe,
|
||||
write_fn: write_regular,
|
||||
})
|
||||
} else if is_fifo {
|
||||
Ok(PlatformZeroCopyWriter {
|
||||
raw_obj,
|
||||
read_pipe: Default::default(),
|
||||
write_pipe: Default::default(),
|
||||
write_fn: write_fifo,
|
||||
})
|
||||
} else {
|
||||
// FIXME: how to error?
|
||||
Err(nix::Error::from(Errno::UnknownErrno))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRawObject for PlatformZeroCopyWriter {
|
||||
unsafe fn from_raw_object(obj: RawObject) -> Option<Self> {
|
||||
PlatformZeroCopyWriter::new(obj).ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for PlatformZeroCopyWriter {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
let iovec = &[IoVec::from_slice(buf)];
|
||||
|
||||
let func = self.write_fn;
|
||||
func(self, iovec, buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
// XXX: not sure if we need anything else
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn write_regular(
|
||||
writer: &mut PlatformZeroCopyWriter,
|
||||
iovec: &[IoVec<&[u8]>],
|
||||
len: usize,
|
||||
) -> io::Result<usize> {
|
||||
vmsplice(writer.write_pipe, iovec, SpliceFFlags::empty())
|
||||
.and_then(|_| {
|
||||
splice(
|
||||
writer.read_pipe,
|
||||
None,
|
||||
writer.raw_obj,
|
||||
None,
|
||||
len,
|
||||
SpliceFFlags::empty(),
|
||||
)
|
||||
})
|
||||
.map_err(|_| io::Error::last_os_error())
|
||||
}
|
||||
|
||||
fn write_fifo(
|
||||
writer: &mut PlatformZeroCopyWriter,
|
||||
iovec: &[IoVec<&[u8]>],
|
||||
_len: usize,
|
||||
) -> io::Result<usize> {
|
||||
vmsplice(writer.raw_obj, iovec, SpliceFFlags::empty()).map_err(|_| io::Error::last_os_error())
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
|
||||
use crate::features::zero_copy::{AsRawObject, FromRawObject};
|
||||
|
||||
pub type RawObject = RawFd;
|
||||
|
||||
impl<T: AsRawFd> AsRawObject for T {
|
||||
fn as_raw_object(&self) -> RawObject {
|
||||
self.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: check if this works right
|
||||
impl<T: FromRawFd> FromRawObject for T {
|
||||
unsafe fn from_raw_object(obj: RawObject) -> Option<Self> {
|
||||
Some(T::from_raw_fd(obj))
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
use std::os::windows::io::{AsRawHandle, FromRawHandle, RawHandle};
|
||||
|
||||
use crate::features::zero_copy::{AsRawObject, FromRawObject};
|
||||
|
||||
pub type RawObject = RawHandle;
|
||||
|
||||
impl<T: AsRawHandle> AsRawObject for T {
|
||||
fn as_raw_object(&self) -> RawObject {
|
||||
self.as_raw_handle()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromRawHandle> FromRawObject for T {
|
||||
unsafe fn from_raw_object(obj: RawObject) -> Option<Self> {
|
||||
Some(T::from_raw_handle(obj))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: see if there's some zero-copy stuff in Windows
|
|
@ -3,14 +3,6 @@
|
|||
// Copyright (C) ~ Alex Lyon <arcterus@mail.com>
|
||||
// Copyright (C) ~ Roy Ivy III <rivy.dev@gmail.com>; MIT license
|
||||
|
||||
// * feature-gated external crates
|
||||
#[cfg(all(feature = "lazy_static", target_os = "linux"))]
|
||||
extern crate lazy_static;
|
||||
#[cfg(feature = "nix")]
|
||||
extern crate nix;
|
||||
#[cfg(feature = "platform-info")]
|
||||
extern crate platform_info;
|
||||
|
||||
// * feature-gated external crates (re-shared as public internal modules)
|
||||
#[cfg(feature = "libc")]
|
||||
pub extern crate libc;
|
||||
|
@ -46,8 +38,6 @@ pub use crate::features::fs;
|
|||
pub use crate::features::fsext;
|
||||
#[cfg(feature = "ringbuffer")]
|
||||
pub use crate::features::ringbuffer;
|
||||
#[cfg(feature = "zero-copy")]
|
||||
pub use crate::features::zero_copy;
|
||||
|
||||
// * (platform-specific) feature-gated modules
|
||||
// ** non-windows
|
||||
|
|
|
@ -1 +1,72 @@
|
|||
// ToDO: add tests
|
||||
use std::io::Read;
|
||||
|
||||
use crate::common::util::*;
|
||||
|
||||
/// Run `yes`, capture some of the output, close the pipe, and verify it.
|
||||
fn run(args: &[&str], expected: &[u8]) {
|
||||
let mut cmd = new_ucmd!();
|
||||
let mut child = cmd.args(args).run_no_wait();
|
||||
let mut stdout = child.stdout.take().unwrap();
|
||||
let mut buf = vec![0; expected.len()];
|
||||
stdout.read_exact(&mut buf).unwrap();
|
||||
drop(stdout);
|
||||
assert!(child.wait().unwrap().success());
|
||||
assert_eq!(buf.as_slice(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple() {
|
||||
run(&[], b"y\ny\ny\ny\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_args() {
|
||||
run(&["a", "bar", "c"], b"a bar c\na bar c\na ba");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_long_output() {
|
||||
run(&[], "y\n".repeat(512 * 1024).as_bytes());
|
||||
}
|
||||
|
||||
/// Test with an output that seems likely to get mangled in case of incomplete writes.
|
||||
#[test]
|
||||
fn test_long_odd_output() {
|
||||
run(&["abcdef"], "abcdef\n".repeat(1024 * 1024).as_bytes());
|
||||
}
|
||||
|
||||
/// Test with an input that doesn't fit in the standard buffer.
|
||||
#[test]
|
||||
fn test_long_input() {
|
||||
#[cfg(not(windows))]
|
||||
const TIMES: usize = 14000;
|
||||
// On Windows the command line is limited to 8191 bytes.
|
||||
// This is not actually enough to fill the buffer, but it's still nice to
|
||||
// try something long.
|
||||
#[cfg(windows)]
|
||||
const TIMES: usize = 500;
|
||||
let arg = "abcdefg".repeat(TIMES) + "\n";
|
||||
let expected_out = arg.repeat(30);
|
||||
run(&[&arg[..arg.len() - 1]], expected_out.as_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
|
||||
fn test_piped_to_dev_full() {
|
||||
use std::fs::OpenOptions;
|
||||
|
||||
for &append in &[true, false] {
|
||||
{
|
||||
let dev_full = OpenOptions::new()
|
||||
.write(true)
|
||||
.append(append)
|
||||
.open("/dev/full")
|
||||
.unwrap();
|
||||
|
||||
new_ucmd!()
|
||||
.set_stdout(dev_full)
|
||||
.fails()
|
||||
.stderr_contains("No space left on device");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue