Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/cat/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ features = ["fs"]
[target.'cfg(unix)'.dependencies]
unix_socket = "0.5.0"

[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
nix = "0.9.0"

[[bin]]
name = "cat"
path = "../../uumain.rs"
125 changes: 94 additions & 31 deletions src/cat/cat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ extern crate quick_error;
extern crate unix_socket;
#[macro_use]
extern crate uucore;
#[cfg(any(target_os = "linux", target_os = "android"))]
extern crate nix;

// last synced with: cat (GNU coreutils) 8.13
use quick_error::ResultExt;
Expand All @@ -31,6 +33,14 @@ use std::os::unix::fs::FileTypeExt;
#[cfg(unix)]
use unix_socket::UnixStream;

/// Linux splice support
#[cfg(any(target_os = "linux", target_os = "android"))]
use nix::fcntl::{splice, SpliceFFlags};
#[cfg(any(target_os = "linux", target_os = "android"))]
use nix::unistd::pipe;
#[cfg(any(target_os = "linux", target_os = "android"))]
use std::os::unix::io::{AsRawFd, RawFd};

static SYNTAX: &str = "[OPTION]... [FILE]...";
static SUMMARY: &str = "Concatenate FILE(s), or standard input, to standard output
With no FILE, or when FILE is -, read standard input.";
Expand Down Expand Up @@ -100,6 +110,8 @@ struct OutputOptions {

/// Represents an open file handle, stream, or other device
struct InputHandle {
#[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: RawFd,
reader: Box<Read>,
is_interactive: bool,
}
Expand Down Expand Up @@ -168,7 +180,10 @@ pub fn uumain(args: Vec<String>) -> i32 {
files.push("-".to_owned());
}

let can_write_fast = !(show_tabs || show_nonprint || show_ends || squeeze_blank
let can_write_fast = !(show_tabs
|| show_nonprint
|| show_ends
|| squeeze_blank
|| number_mode != NumberingMode::NumberNone);

let success = if can_write_fast {
Expand All @@ -190,7 +205,11 @@ pub fn uumain(args: Vec<String>) -> i32 {
write_lines(files, &options).is_ok()
};

if success { 0 } else { 1 }
if success {
0
} else {
1
}
}

/// Classifies the `InputType` of file at `path` if possible
Expand All @@ -205,25 +224,13 @@ fn get_input_type(path: &str) -> CatResult<InputType> {

match metadata(path).context(path)?.file_type() {
#[cfg(unix)]
ft if ft.is_block_device() =>
{
Ok(InputType::BlockDevice)
}
ft if ft.is_block_device() => Ok(InputType::BlockDevice),
#[cfg(unix)]
ft if ft.is_char_device() =>
{
Ok(InputType::CharacterDevice)
}
ft if ft.is_char_device() => Ok(InputType::CharacterDevice),
#[cfg(unix)]
ft if ft.is_fifo() =>
{
Ok(InputType::Fifo)
}
ft if ft.is_fifo() => Ok(InputType::Fifo),
#[cfg(unix)]
ft if ft.is_socket() =>
{
Ok(InputType::Socket)
}
ft if ft.is_socket() => Ok(InputType::Socket),
ft if ft.is_dir() => Ok(InputType::Directory),
ft if ft.is_file() => Ok(InputType::File),
ft if ft.is_symlink() => Ok(InputType::SymLink),
Expand All @@ -241,6 +248,8 @@ fn open(path: &str) -> CatResult<InputHandle> {
if path == "-" {
let stdin = stdin();
return Ok(InputHandle {
#[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: stdin.as_raw_fd(),
reader: Box::new(stdin) as Box<Read>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mentioned this on the other PR as well, but could you change this to avoid Box?

is_interactive: is_stdin_interactive(),
});
Expand All @@ -253,13 +262,17 @@ fn open(path: &str) -> CatResult<InputHandle> {
let socket = UnixStream::connect(path).context(path)?;
socket.shutdown(Shutdown::Write).context(path)?;
Ok(InputHandle {
#[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: socket.as_raw_fd(),
reader: Box::new(socket) as Box<Read>,
is_interactive: false,
})
}
_ => {
let file = File::open(path).context(path)?;
Ok(InputHandle {
#[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: file.as_raw_fd(),
reader: Box::new(file) as Box<Read>,
is_interactive: false,
})
Expand All @@ -276,20 +289,41 @@ fn open(path: &str) -> CatResult<InputHandle> {
/// * `files` - There is no short circuit when encountiner an error
/// reading a file in this vector
fn write_fast(files: Vec<String>) -> CatResult<()> {
let mut writer = stdout();
let mut in_buf = [0; 1024 * 64];
let mut error_count = 0;
let writer = stdout();
let mut writer_handle = writer.lock();

for file in files {
match open(&file[..]) {
Ok(mut handle) => while let Ok(n) = handle.reader.read(&mut in_buf) {
if n == 0 {
break;
Ok(mut handle) => {
// If we're on Linux or Android, try to use the splice()
// system call for faster writing.
#[cfg(any(target_os = "linux", target_os = "android"))]
{
match write_fast_using_splice(&mut handle, writer.as_raw_fd()) {
Ok(_) => {
// Writing fast with splice worked! We don't need
// to fall back on slower writing.
continue;
}
_ => {
// Ignore any error and fall back to slower
// writing below.
}
}
}
writer.write_all(&in_buf[..n]).context(&file[..])?;
},
// If we're not on Linux or Android, or the splice() call failed,
// fall back on slower writing.
match io::copy(&mut handle.reader, &mut writer_handle) {
Err(error) => {
eprintln!("error when copying file: {}", error);
error_count += 1;
}
_ => {}
}
}
Err(error) => {
writeln!(&mut stderr(), "{}", error)?;
eprintln!("error when opening file: {}", error);
error_count += 1;
}
}
Expand All @@ -301,6 +335,37 @@ fn write_fast(files: Vec<String>) -> CatResult<()> {
}
}

/// This function is called from `write_fast()` on Linux and Android. The
/// function `splice()` is used to move data between two file descriptors
/// without copying between kernel- and userspace. This results in a large
/// speedup.
#[cfg(any(target_os = "linux", target_os = "android"))]
#[inline]
fn write_fast_using_splice(handle: &mut InputHandle, writer: RawFd) -> Result<(), nix::Error> {
const BUF_SIZE: usize = 1024 * 16;

let (pipe_rd, pipe_wr) = pipe()?;

loop {
let res = splice(
handle.file_descriptor,
None,
pipe_wr,
None,
BUF_SIZE,
SpliceFFlags::empty(),
)?;
if res == 0 {
// We read 0 bytes from the input,
// which means we're done copying.
break;
}
let _ = splice(pipe_rd, None, writer, None, BUF_SIZE, SpliceFFlags::empty())?;
}

Ok(())
}

/// State that persists between output of each file
struct OutputState {
/// The current line number
Expand Down Expand Up @@ -420,10 +485,7 @@ fn write_to_end<W: Write>(in_buf: &[u8], writer: &mut W) -> usize {

fn write_tab_to_end<W: Write>(mut in_buf: &[u8], writer: &mut W) -> usize {
loop {
match in_buf
.iter()
.position(|c| *c == b'\n' || *c == b'\t')
{
match in_buf.iter().position(|c| *c == b'\n' || *c == b'\t') {
Some(p) => {
writer.write_all(&in_buf[..p]).unwrap();
if in_buf[p] == b'\n' {
Expand Down Expand Up @@ -456,7 +518,8 @@ fn write_nonprint_to_end<W: Write>(in_buf: &[u8], writer: &mut W, tab: &[u8]) ->
128...159 => writer.write_all(&[b'M', b'-', b'^', byte - 64]),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you change this to use byte literals (and maybe fix the other issues mentioned by the person on IRC)?

160...254 => writer.write_all(&[b'M', b'-', byte - 128]),
_ => writer.write_all(&[b'M', b'-', b'^', 63]),
}.unwrap();
}
.unwrap();
count += 1;
}
if count != in_buf.len() {
Expand Down