diff --git a/Cargo.lock b/Cargo.lock index 6ac2fcaea8e..6d95aac9924 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,3 +1,5 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. [[package]] name = "advapi32-sys" version = "0.2.0" @@ -147,6 +149,7 @@ dependencies = [ name = "cat" version = "0.0.1" dependencies = [ + "nix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.1", diff --git a/src/cat/Cargo.toml b/src/cat/Cargo.toml index 584fbdbedf6..ebdce97e629 100644 --- a/src/cat/Cargo.toml +++ b/src/cat/Cargo.toml @@ -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" diff --git a/src/cat/cat.rs b/src/cat/cat.rs index 5885a7c0f87..5aa07e201b1 100644 --- a/src/cat/cat.rs +++ b/src/cat/cat.rs @@ -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; @@ -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."; @@ -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, is_interactive: bool, } @@ -168,7 +180,10 @@ pub fn uumain(args: Vec) -> 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 { @@ -190,7 +205,11 @@ pub fn uumain(args: Vec) -> 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 @@ -205,25 +224,13 @@ fn get_input_type(path: &str) -> CatResult { 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), @@ -241,6 +248,8 @@ fn open(path: &str) -> CatResult { 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, is_interactive: is_stdin_interactive(), }); @@ -253,6 +262,8 @@ fn open(path: &str) -> CatResult { 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, is_interactive: false, }) @@ -260,6 +271,8 @@ fn open(path: &str) -> CatResult { _ => { 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, is_interactive: false, }) @@ -276,20 +289,41 @@ fn open(path: &str) -> CatResult { /// * `files` - There is no short circuit when encountiner an error /// reading a file in this vector fn write_fast(files: Vec) -> 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; } } @@ -301,6 +335,37 @@ fn write_fast(files: Vec) -> 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 @@ -420,10 +485,7 @@ fn write_to_end(in_buf: &[u8], writer: &mut W) -> usize { fn write_tab_to_end(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' { @@ -456,7 +518,8 @@ fn write_nonprint_to_end(in_buf: &[u8], writer: &mut W, tab: &[u8]) -> 128...159 => writer.write_all(&[b'M', b'-', b'^', byte - 64]), 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() {