use std::io::{self, Read, Write};
use std::path::Path;
use crate::common::io::{read_file_direct, read_stdin};
#[cfg(target_os = "linux")]
pub enum CatPlainError {
IsDirectory,
InputIsOutput,
Io(io::Error),
}
#[cfg(target_os = "linux")]
impl From<io::Error> for CatPlainError {
fn from(e: io::Error) -> Self {
CatPlainError::Io(e)
}
}
#[derive(Clone, Debug, Default)]
pub struct CatConfig {
pub number: bool,
pub number_nonblank: bool,
pub show_ends: bool,
pub show_tabs: bool,
pub show_nonprinting: bool,
pub squeeze_blank: bool,
}
impl CatConfig {
pub fn is_plain(&self) -> bool {
!self.number
&& !self.number_nonblank
&& !self.show_ends
&& !self.show_tabs
&& !self.show_nonprinting
&& !self.squeeze_blank
}
}
#[cfg(target_os = "linux")]
pub fn cat_plain_file_linux(path: &Path) -> Result<bool, CatPlainError> {
use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::io::AsRawFd;
let file = std::fs::OpenOptions::new()
.read(true)
.custom_flags(libc::O_NOATIME)
.open(path)
.or_else(|_| std::fs::File::open(path))?;
let in_fd = file.as_raw_fd();
let mut in_stat: libc::stat = unsafe { std::mem::zeroed() };
if unsafe { libc::fstat(in_fd, &mut in_stat) } != 0 {
return Err(io::Error::last_os_error().into());
}
let in_mode = in_stat.st_mode & libc::S_IFMT;
if in_mode == libc::S_IFDIR {
return Err(CatPlainError::IsDirectory);
}
let stdout = io::stdout();
let out_fd = stdout.as_raw_fd();
let mut out_stat: libc::stat = unsafe { std::mem::zeroed() };
if unsafe { libc::fstat(out_fd, &mut out_stat) } != 0 {
return Err(io::Error::last_os_error().into());
}
if in_stat.st_dev == out_stat.st_dev && in_stat.st_ino == out_stat.st_ino {
return Err(CatPlainError::InputIsOutput);
}
let file_size = in_stat.st_size as usize;
if file_size == 0 {
if in_mode != libc::S_IFREG {
return Ok(false); }
let mut buf = [0u8; 65536];
let mut out = stdout.lock();
loop {
let n = match nix_read(in_fd, &mut buf) {
Ok(0) => break,
Ok(n) => n,
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(_) => break,
};
out.write_all(&buf[..n])?;
}
return Ok(true);
}
unsafe {
libc::posix_fadvise(in_fd, 0, 0, libc::POSIX_FADV_SEQUENTIAL);
}
let stdout_mode = out_stat.st_mode & libc::S_IFMT;
if stdout_mode == libc::S_IFIFO {
let mut remaining = file_size;
while remaining > 0 {
let chunk = remaining.min(1024 * 1024 * 1024);
let ret = unsafe {
libc::splice(
in_fd,
std::ptr::null_mut(),
out_fd,
std::ptr::null_mut(),
chunk,
libc::SPLICE_F_MOVE,
)
};
if ret > 0 {
remaining -= ret as usize;
} else if ret == 0 {
break;
} else {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::Interrupted {
continue;
}
return Ok(cat_readwrite(in_fd, stdout.lock())?);
}
}
return Ok(true);
}
if stdout_mode == libc::S_IFREG {
let mut remaining = file_size;
while remaining > 0 {
let chunk = remaining.min(0x7ffff000);
let ret = unsafe {
libc::copy_file_range(
in_fd,
std::ptr::null_mut(),
out_fd,
std::ptr::null_mut(),
chunk,
0,
)
};
if ret > 0 {
remaining -= ret as usize;
} else if ret == 0 {
break;
} else {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::Interrupted {
continue;
}
return Ok(cat_readwrite(in_fd, stdout.lock())?);
}
}
return Ok(true);
}
Ok(cat_readwrite(in_fd, stdout.lock())?)
}
#[cfg(target_os = "linux")]
fn cat_readwrite(in_fd: i32, mut out: impl Write) -> io::Result<bool> {
let mut buf = vec![0u8; 256 * 1024];
loop {
let n = match nix_read(in_fd, &mut buf) {
Ok(0) => break,
Ok(n) => n,
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
};
out.write_all(&buf[..n])?;
}
Ok(true)
}
#[cfg(target_os = "linux")]
fn nix_read(fd: i32, buf: &mut [u8]) -> io::Result<usize> {
let ret = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) };
if ret >= 0 {
Ok(ret as usize)
} else {
Err(io::Error::last_os_error())
}
}
pub fn cat_plain_file(path: &Path, out: &mut impl Write) -> io::Result<bool> {
#[cfg(target_os = "linux")]
{
match cat_plain_file_linux(path) {
Ok(true) => return Ok(true),
Ok(false) => {}
Err(CatPlainError::Io(e)) if e.kind() == io::ErrorKind::BrokenPipe => {
return Err(e);
}
Err(_) => {} }
}
let data = read_file_direct(path)?;
if !data.is_empty() {
out.write_all(&data)?;
}
Ok(true)
}
pub fn cat_plain_stdin(out: &mut impl Write) -> io::Result<()> {
#[cfg(target_os = "linux")]
{
let stdin_fd = 0i32;
let mut stat: libc::stat = unsafe { std::mem::zeroed() };
if unsafe { libc::fstat(1, &mut stat) } == 0
&& (stat.st_mode & libc::S_IFMT) == libc::S_IFIFO
{
loop {
let ret = unsafe {
libc::splice(
stdin_fd,
std::ptr::null_mut(),
1,
std::ptr::null_mut(),
1024 * 1024 * 1024,
libc::SPLICE_F_MOVE,
)
};
if ret > 0 {
continue;
} else if ret == 0 {
return Ok(());
} else {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::Interrupted {
continue;
}
break;
}
}
}
}
let stdin = io::stdin();
let mut reader = stdin.lock();
let mut buf = [0u8; 262144]; loop {
let n = match reader.read(&mut buf) {
Ok(0) => break,
Ok(n) => n,
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
};
out.write_all(&buf[..n])?;
}
Ok(())
}
fn _build_nonprinting_table(show_tabs: bool) -> ([u8; 256], [bool; 256]) {
let mut table = [0u8; 256];
let mut multi = [false; 256];
for i in 0..256u16 {
let b = i as u8;
match b {
b'\n' => {
table[i as usize] = b'\n';
}
b'\t' => {
if show_tabs {
table[i as usize] = b'I';
multi[i as usize] = true;
} else {
table[i as usize] = b'\t';
}
}
0..=8 | 10..=31 => {
table[i as usize] = b + 64;
multi[i as usize] = true;
}
32..=126 => {
table[i as usize] = b;
}
127 => {
table[i as usize] = b'?';
multi[i as usize] = true;
}
128..=159 => {
table[i as usize] = b - 128 + 64;
multi[i as usize] = true;
}
160..=254 => {
table[i as usize] = b - 128;
multi[i as usize] = true;
}
255 => {
table[i as usize] = b'?';
multi[i as usize] = true;
}
}
}
(table, multi)
}
#[inline]
fn write_nonprinting(b: u8, show_tabs: bool, out: &mut Vec<u8>) {
match b {
b'\t' if !show_tabs => out.push(b'\t'),
b'\n' => out.push(b'\n'),
0..=8 | 10..=31 => {
out.push(b'^');
out.push(b + 64);
}
9 => {
out.push(b'^');
out.push(b'I');
}
32..=126 => out.push(b),
127 => {
out.push(b'^');
out.push(b'?');
}
128..=159 => {
out.push(b'M');
out.push(b'-');
out.push(b'^');
out.push(b - 128 + 64);
}
160..=254 => {
out.push(b'M');
out.push(b'-');
out.push(b - 128);
}
255 => {
out.push(b'M');
out.push(b'-');
out.push(b'^');
out.push(b'?');
}
}
}
fn cat_show_all_fast(
data: &[u8],
show_tabs: bool,
show_ends: bool,
out: &mut impl Write,
) -> io::Result<()> {
const BUF_SIZE: usize = 256 * 1024;
let cap = data.len().min(BUF_SIZE) + data.len().min(BUF_SIZE) / 2;
let mut buf = Vec::with_capacity(cap);
let mut pos = 0;
while pos < data.len() {
let start = pos;
while pos < data.len() && data[pos].wrapping_sub(32) <= 94 {
pos += 1;
}
if pos > start {
buf.extend_from_slice(&data[start..pos]);
}
if pos >= data.len() {
break;
}
let b = data[pos];
pos += 1;
match b {
b'\n' => {
if show_ends {
buf.extend_from_slice(b"$\n");
} else {
buf.push(b'\n');
}
}
b'\t' if show_tabs => buf.extend_from_slice(b"^I"),
b'\t' => buf.push(b'\t'),
0..=8 | 10..=31 => {
buf.push(b'^');
buf.push(b + 64);
}
127 => buf.extend_from_slice(b"^?"),
128..=159 => {
buf.push(b'M');
buf.push(b'-');
buf.push(b'^');
buf.push(b - 128 + 64);
}
160..=254 => {
buf.push(b'M');
buf.push(b'-');
buf.push(b - 128);
}
255 => buf.extend_from_slice(b"M-^?"),
_ => unreachable!(),
}
if buf.len() >= BUF_SIZE {
out.write_all(&buf)?;
buf.clear();
}
}
if !buf.is_empty() {
out.write_all(&buf)?;
}
Ok(())
}
#[inline(always)]
unsafe fn write_line_number_raw(dst: *mut u8, num: u64) -> usize {
if num <= 999999 {
let mut n = num as u32;
let d5 = n / 100000;
n -= d5 * 100000;
let d4 = n / 10000;
n -= d4 * 10000;
let d3 = n / 1000;
n -= d3 * 1000;
let d2 = n / 100;
n -= d2 * 100;
let d1 = n / 10;
let d0 = n - d1 * 10;
let width = if num >= 100000 {
6
} else if num >= 10000 {
5
} else if num >= 1000 {
4
} else if num >= 100 {
3
} else if num >= 10 {
2
} else {
1
};
let pad = 6 - width;
unsafe {
for i in 0..pad {
*dst.add(i) = b' ';
}
}
let digits = [
d5 as u8 + b'0',
d4 as u8 + b'0',
d3 as u8 + b'0',
d2 as u8 + b'0',
d1 as u8 + b'0',
d0 as u8 + b'0',
];
unsafe {
std::ptr::copy_nonoverlapping(digits[6 - width..].as_ptr(), dst.add(pad), width);
*dst.add(6) = b'\t';
}
7
} else {
let mut buf = itoa::Buffer::new();
let s = buf.format(num);
let pad = if s.len() < 6 { 6 - s.len() } else { 0 };
unsafe {
for i in 0..pad {
*dst.add(i) = b' ';
}
std::ptr::copy_nonoverlapping(s.as_ptr(), dst.add(pad), s.len());
*dst.add(pad + s.len()) = b'\t';
}
pad + s.len() + 1
}
}
#[cfg(target_os = "linux")]
fn cat_stream_numbered(
fd: i32,
line_num: &mut u64,
nonblank: bool,
out: &mut impl Write,
) -> io::Result<bool> {
const READ_BUF: usize = 4 * 1024 * 1024;
let mut buf = vec![0u8; READ_BUF];
let mut carry: Vec<u8> = Vec::new();
loop {
let n = loop {
let ret = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) };
if ret >= 0 {
break ret as usize;
}
let err = io::Error::last_os_error();
if err.kind() != io::ErrorKind::Interrupted {
return Err(err);
}
};
if n == 0 {
if !carry.is_empty() {
if nonblank {
cat_number_nonblank_fast(&carry, line_num, out)?;
} else {
cat_number_all_fast(&carry, line_num, out)?;
}
}
return Ok(true);
}
let chunk = &buf[..n];
if carry.is_empty() {
match memchr::memrchr(b'\n', chunk) {
Some(last_nl) => {
let complete = &chunk[..last_nl + 1];
if nonblank {
cat_number_nonblank_fast(complete, line_num, out)?;
} else {
cat_number_all_fast(complete, line_num, out)?;
}
if last_nl + 1 < n {
carry.extend_from_slice(&chunk[last_nl + 1..]);
}
}
None => {
carry.extend_from_slice(chunk);
}
}
} else {
match memchr::memchr(b'\n', chunk) {
Some(first_nl) => {
carry.extend_from_slice(&chunk[..first_nl + 1]);
if nonblank {
cat_number_nonblank_fast(&carry, line_num, out)?;
} else {
cat_number_all_fast(&carry, line_num, out)?;
}
carry.clear();
let rest = &chunk[first_nl + 1..];
if !rest.is_empty() {
match memchr::memrchr(b'\n', rest) {
Some(last_nl) => {
let complete = &rest[..last_nl + 1];
if nonblank {
cat_number_nonblank_fast(complete, line_num, out)?;
} else {
cat_number_all_fast(complete, line_num, out)?;
}
if last_nl + 1 < rest.len() {
carry.extend_from_slice(&rest[last_nl + 1..]);
}
}
None => {
carry.extend_from_slice(rest);
}
}
}
}
None => {
carry.extend_from_slice(chunk);
}
}
}
}
}
fn cat_number_all_fast(data: &[u8], line_num: &mut u64, out: &mut impl Write) -> io::Result<()> {
if data.is_empty() {
return Ok(());
}
let alloc = (data.len() * 2 + 256).min(64 * 1024 * 1024);
let mut output: Vec<u8> = Vec::with_capacity(alloc);
let mut out_ptr = output.as_mut_ptr();
let mut out_pos: usize = 0;
let mut num = *line_num;
let mut pos: usize = 0;
for nl_pos in memchr::memchr_iter(b'\n', data) {
let line_len = nl_pos + 1 - pos;
let needed = out_pos + line_len + 22; if needed > output.capacity() {
unsafe { output.set_len(out_pos) };
output.reserve(needed.saturating_sub(output.len()));
out_ptr = output.as_mut_ptr();
}
unsafe {
out_pos += write_line_number_raw(out_ptr.add(out_pos), num);
}
num += 1;
unsafe {
std::ptr::copy_nonoverlapping(data.as_ptr().add(pos), out_ptr.add(out_pos), line_len);
}
out_pos += line_len;
pos = nl_pos + 1;
if out_pos >= 8 * 1024 * 1024 {
unsafe { output.set_len(out_pos) };
out.write_all(&output)?;
output.clear();
out_pos = 0;
out_ptr = output.as_mut_ptr();
}
}
if pos < data.len() {
let remaining = data.len() - pos;
let needed = out_pos + remaining + 22;
if needed > output.capacity() {
unsafe { output.set_len(out_pos) };
output.reserve(needed.saturating_sub(output.len()));
out_ptr = output.as_mut_ptr();
}
unsafe {
out_pos += write_line_number_raw(out_ptr.add(out_pos), num);
}
num += 1;
unsafe {
std::ptr::copy_nonoverlapping(data.as_ptr().add(pos), out_ptr.add(out_pos), remaining);
}
out_pos += remaining;
}
*line_num = num;
unsafe { output.set_len(out_pos) };
if !output.is_empty() {
out.write_all(&output)?;
}
Ok(())
}
fn cat_number_nonblank_fast(
data: &[u8],
line_num: &mut u64,
out: &mut impl Write,
) -> io::Result<()> {
if data.is_empty() {
return Ok(());
}
let alloc = (data.len() * 2 + 256).min(64 * 1024 * 1024);
let mut output: Vec<u8> = Vec::with_capacity(alloc);
let mut out_ptr = output.as_mut_ptr();
let mut out_pos: usize = 0;
let mut num = *line_num;
let mut pos: usize = 0;
for nl_pos in memchr::memchr_iter(b'\n', data) {
let line_len = nl_pos + 1 - pos;
let needed = out_pos + line_len + 22;
if needed > output.capacity() {
unsafe { output.set_len(out_pos) };
output.reserve(needed.saturating_sub(output.len()));
out_ptr = output.as_mut_ptr();
}
let is_blank = nl_pos == pos;
if !is_blank {
unsafe {
out_pos += write_line_number_raw(out_ptr.add(out_pos), num);
}
num += 1;
}
unsafe {
std::ptr::copy_nonoverlapping(data.as_ptr().add(pos), out_ptr.add(out_pos), line_len);
}
out_pos += line_len;
pos = nl_pos + 1;
if out_pos >= 8 * 1024 * 1024 {
unsafe { output.set_len(out_pos) };
out.write_all(&output)?;
output.clear();
out_pos = 0;
out_ptr = output.as_mut_ptr();
}
}
if pos < data.len() {
let remaining = data.len() - pos;
let needed = out_pos + remaining + 22;
if needed > output.capacity() {
unsafe { output.set_len(out_pos) };
output.reserve(needed.saturating_sub(output.len()));
out_ptr = output.as_mut_ptr();
}
unsafe {
out_pos += write_line_number_raw(out_ptr.add(out_pos), num);
}
num += 1;
unsafe {
std::ptr::copy_nonoverlapping(data.as_ptr().add(pos), out_ptr.add(out_pos), remaining);
}
out_pos += remaining;
}
*line_num = num;
unsafe { output.set_len(out_pos) };
if !output.is_empty() {
out.write_all(&output)?;
}
Ok(())
}
pub fn cat_with_options(
data: &[u8],
config: &CatConfig,
line_num: &mut u64,
pending_cr: &mut bool,
out: &mut impl Write,
) -> io::Result<()> {
if data.is_empty() {
return Ok(());
}
if config.show_nonprinting && !config.number && !config.number_nonblank && !config.squeeze_blank
{
return cat_show_all_fast(data, config.show_tabs, config.show_ends, out);
}
if config.number
&& !config.number_nonblank
&& !config.show_ends
&& !config.show_tabs
&& !config.show_nonprinting
&& !config.squeeze_blank
&& !*pending_cr
{
return cat_number_all_fast(data, line_num, out);
}
if config.number_nonblank
&& !config.number
&& !config.show_ends
&& !config.show_tabs
&& !config.show_nonprinting
&& !config.squeeze_blank
&& !*pending_cr
{
return cat_number_nonblank_fast(data, line_num, out);
}
let estimated = data.len() + data.len() / 10 + 1024;
let mut buf = Vec::with_capacity(estimated.min(16 * 1024 * 1024));
let mut prev_blank = false;
let mut pos = 0;
let mut itoa_buf = itoa::Buffer::new();
if *pending_cr {
*pending_cr = false;
if config.show_ends
&& !(config.show_nonprinting || config.show_tabs)
&& !data.is_empty()
&& data[0] == b'\n'
{
buf.extend_from_slice(b"^M$\n");
pos = 1;
} else {
buf.push(b'\r');
}
}
while pos < data.len() {
let line_end = memchr::memchr(b'\n', &data[pos..])
.map(|p| pos + p + 1)
.unwrap_or(data.len());
let line = &data[pos..line_end];
let is_blank = line == b"\n" || line.is_empty();
if config.squeeze_blank && is_blank && prev_blank {
pos = line_end;
continue;
}
prev_blank = is_blank;
if config.number_nonblank {
if !is_blank {
let s = itoa_buf.format(*line_num);
let pad = if s.len() < 6 { 6 - s.len() } else { 0 };
buf.extend(std::iter::repeat_n(b' ', pad));
buf.extend_from_slice(s.as_bytes());
buf.push(b'\t');
*line_num += 1;
}
} else if config.number {
let s = itoa_buf.format(*line_num);
let pad = if s.len() < 6 { 6 - s.len() } else { 0 };
buf.extend(std::iter::repeat_n(b' ', pad));
buf.extend_from_slice(s.as_bytes());
buf.push(b'\t');
*line_num += 1;
}
if config.show_nonprinting || config.show_tabs {
let content_end = if line.last() == Some(&b'\n') {
line.len() - 1
} else {
line.len()
};
for &b in &line[..content_end] {
if config.show_nonprinting {
write_nonprinting(b, config.show_tabs, &mut buf);
} else if config.show_tabs && b == b'\t' {
buf.extend_from_slice(b"^I");
} else {
buf.push(b);
}
}
if config.show_ends && line.last() == Some(&b'\n') {
buf.push(b'$');
}
if line.last() == Some(&b'\n') {
buf.push(b'\n');
}
} else {
if config.show_ends {
let has_newline = line.last() == Some(&b'\n');
let content_end = if has_newline {
line.len() - 1
} else {
line.len()
};
let content = &line[..content_end];
if has_newline && !content.is_empty() && content[content.len() - 1] == b'\r' {
buf.extend_from_slice(&content[..content.len() - 1]);
buf.extend_from_slice(b"^M");
} else if !has_newline && !content.is_empty() && content[content.len() - 1] == b'\r'
{
buf.extend_from_slice(&content[..content.len() - 1]);
*pending_cr = true;
} else {
buf.extend_from_slice(content);
}
if has_newline {
buf.push(b'$');
buf.push(b'\n');
}
} else {
buf.extend_from_slice(line);
}
}
if buf.len() >= 8 * 1024 * 1024 {
out.write_all(&buf)?;
buf.clear();
}
pos = line_end;
}
if !buf.is_empty() {
out.write_all(&buf)?;
}
Ok(())
}
pub fn cat_file(
filename: &str,
config: &CatConfig,
line_num: &mut u64,
pending_cr: &mut bool,
out: &mut impl Write,
tool_name: &str,
) -> io::Result<bool> {
if filename == "-" {
if config.is_plain() {
match cat_plain_stdin(out) {
Ok(()) => return Ok(true),
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => {
std::process::exit(0);
}
Err(e) => {
eprintln!(
"{}: standard input: {}",
tool_name,
crate::common::io_error_msg(&e)
);
return Ok(false);
}
}
}
match read_stdin() {
Ok(data) => {
cat_with_options(&data, config, line_num, pending_cr, out)?;
Ok(true)
}
Err(e) => {
eprintln!(
"{}: standard input: {}",
tool_name,
crate::common::io_error_msg(&e)
);
Ok(false)
}
}
} else {
let path = Path::new(filename);
if config.is_plain() {
#[cfg(target_os = "linux")]
{
match cat_plain_file_linux(path) {
Ok(true) => return Ok(true),
Ok(false) => {
}
Err(CatPlainError::IsDirectory) => {
eprintln!("{}: {}: Is a directory", tool_name, filename);
return Ok(false);
}
Err(CatPlainError::InputIsOutput) => {
eprintln!("{}: {}: input file is output file", tool_name, filename);
return Ok(false);
}
Err(CatPlainError::Io(e)) if e.kind() == io::ErrorKind::BrokenPipe => {
std::process::exit(0);
}
Err(CatPlainError::Io(e)) => {
eprintln!(
"{}: {}: {}",
tool_name,
filename,
crate::common::io_error_msg(&e)
);
return Ok(false);
}
}
}
if let Ok(file_meta) = std::fs::metadata(path) {
if file_meta.is_dir() {
eprintln!("{}: {}: Is a directory", tool_name, filename);
return Ok(false);
}
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
let mut stdout_stat: libc::stat = unsafe { std::mem::zeroed() };
if unsafe { libc::fstat(1, &mut stdout_stat) } == 0
&& file_meta.dev() == stdout_stat.st_dev as u64
&& file_meta.ino() == stdout_stat.st_ino as u64
{
eprintln!("{}: {}: input file is output file", tool_name, filename);
return Ok(false);
}
}
}
match read_file_direct(path) {
Ok(data) => {
if !data.is_empty() {
out.write_all(&data)?;
}
return Ok(true);
}
Err(e) => {
eprintln!(
"{}: {}: {}",
tool_name,
filename,
crate::common::io_error_msg(&e)
);
return Ok(false);
}
}
}
#[cfg(target_os = "linux")]
{
use std::os::unix::io::AsRawFd;
if let Ok(file) = crate::common::io::open_noatime(path) {
let fd = file.as_raw_fd();
let mut stat: libc::stat = unsafe { std::mem::zeroed() };
if unsafe { libc::fstat(fd, &mut stat) } == 0 {
if (stat.st_mode & libc::S_IFMT) == libc::S_IFDIR {
eprintln!("{}: {}: Is a directory", tool_name, filename);
return Ok(false);
}
let mut stdout_stat: libc::stat = unsafe { std::mem::zeroed() };
if unsafe { libc::fstat(1, &mut stdout_stat) } == 0
&& stat.st_dev == stdout_stat.st_dev
&& stat.st_ino == stdout_stat.st_ino
{
eprintln!("{}: {}: input file is output file", tool_name, filename);
return Ok(false);
}
}
if config.number
&& !config.number_nonblank
&& !config.show_ends
&& !config.show_tabs
&& !config.show_nonprinting
&& !config.squeeze_blank
&& !*pending_cr
{
unsafe {
libc::posix_fadvise(fd, 0, 0, libc::POSIX_FADV_SEQUENTIAL);
}
return cat_stream_numbered(fd, line_num, false, out);
}
if config.number_nonblank
&& !config.number
&& !config.show_ends
&& !config.show_tabs
&& !config.show_nonprinting
&& !config.squeeze_blank
&& !*pending_cr
{
unsafe {
libc::posix_fadvise(fd, 0, 0, libc::POSIX_FADV_SEQUENTIAL);
}
return cat_stream_numbered(fd, line_num, true, out);
}
let size = if stat.st_size > 0 {
stat.st_size as usize
} else {
0
};
let mut data = Vec::with_capacity(size);
use std::io::Read;
if (&file).read_to_end(&mut data).is_ok() {
cat_with_options(&data, config, line_num, pending_cr, out)?;
return Ok(true);
}
}
}
match std::fs::metadata(path) {
Ok(meta) if meta.is_dir() => {
eprintln!("{}: {}: Is a directory", tool_name, filename);
return Ok(false);
}
_ => {}
}
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
if let Ok(file_meta) = std::fs::metadata(path) {
let mut stdout_stat: libc::stat = unsafe { std::mem::zeroed() };
if unsafe { libc::fstat(1, &mut stdout_stat) } == 0
&& file_meta.dev() == stdout_stat.st_dev as u64
&& file_meta.ino() == stdout_stat.st_ino as u64
{
eprintln!("{}: {}: input file is output file", tool_name, filename);
return Ok(false);
}
}
}
match read_file_direct(path) {
Ok(data) => {
cat_with_options(&data, config, line_num, pending_cr, out)?;
Ok(true)
}
Err(e) => {
eprintln!(
"{}: {}: {}",
tool_name,
filename,
crate::common::io_error_msg(&e)
);
Ok(false)
}
}
}
}