1use std::io::{self, Read, Write};
2use std::path::Path;
3
4use crate::common::io::{read_file_direct, read_stdin};
5
6#[cfg(target_os = "linux")]
10pub enum CatPlainError {
11 IsDirectory,
13 InputIsOutput,
15 Io(io::Error),
17}
18
19#[cfg(target_os = "linux")]
20impl From<io::Error> for CatPlainError {
21 fn from(e: io::Error) -> Self {
22 CatPlainError::Io(e)
23 }
24}
25
26#[derive(Clone, Debug, Default)]
28pub struct CatConfig {
29 pub number: bool,
30 pub number_nonblank: bool,
31 pub show_ends: bool,
32 pub show_tabs: bool,
33 pub show_nonprinting: bool,
34 pub squeeze_blank: bool,
35}
36
37impl CatConfig {
38 pub fn is_plain(&self) -> bool {
40 !self.number
41 && !self.number_nonblank
42 && !self.show_ends
43 && !self.show_tabs
44 && !self.show_nonprinting
45 && !self.squeeze_blank
46 }
47}
48
49#[cfg(target_os = "linux")]
60pub fn cat_plain_file_linux(path: &Path) -> Result<bool, CatPlainError> {
61 use std::os::unix::fs::OpenOptionsExt;
62 use std::os::unix::io::AsRawFd;
63
64 let file = std::fs::OpenOptions::new()
65 .read(true)
66 .custom_flags(libc::O_NOATIME)
67 .open(path)
68 .or_else(|_| std::fs::File::open(path))?;
69
70 let in_fd = file.as_raw_fd();
71
72 let mut in_stat: libc::stat = unsafe { std::mem::zeroed() };
74 if unsafe { libc::fstat(in_fd, &mut in_stat) } != 0 {
75 return Err(io::Error::last_os_error().into());
76 }
77
78 let in_mode = in_stat.st_mode & libc::S_IFMT;
79
80 if in_mode == libc::S_IFDIR {
82 return Err(CatPlainError::IsDirectory);
83 }
84
85 let stdout = io::stdout();
87 let out_fd = stdout.as_raw_fd();
88 let mut out_stat: libc::stat = unsafe { std::mem::zeroed() };
89 if unsafe { libc::fstat(out_fd, &mut out_stat) } != 0 {
90 return Err(io::Error::last_os_error().into());
91 }
92
93 if in_stat.st_dev == out_stat.st_dev && in_stat.st_ino == out_stat.st_ino {
95 return Err(CatPlainError::InputIsOutput);
96 }
97
98 let file_size = in_stat.st_size as usize;
99
100 if file_size == 0 {
101 if in_mode != libc::S_IFREG {
103 return Ok(false); }
105 let mut buf = [0u8; 65536];
107 let mut out = stdout.lock();
108 loop {
109 let n = match nix_read(in_fd, &mut buf) {
110 Ok(0) => break,
111 Ok(n) => n,
112 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
113 Err(_) => break,
114 };
115 out.write_all(&buf[..n])?;
116 }
117 return Ok(true);
118 }
119
120 unsafe {
122 libc::posix_fadvise(in_fd, 0, 0, libc::POSIX_FADV_SEQUENTIAL);
123 }
124
125 let stdout_mode = out_stat.st_mode & libc::S_IFMT;
126
127 if stdout_mode == libc::S_IFIFO {
128 let mut remaining = file_size;
130 while remaining > 0 {
131 let chunk = remaining.min(1024 * 1024 * 1024);
132 let ret = unsafe {
133 libc::splice(
134 in_fd,
135 std::ptr::null_mut(),
136 out_fd,
137 std::ptr::null_mut(),
138 chunk,
139 libc::SPLICE_F_MOVE,
140 )
141 };
142 if ret > 0 {
143 remaining -= ret as usize;
144 } else if ret == 0 {
145 break;
146 } else {
147 let err = io::Error::last_os_error();
148 if err.kind() == io::ErrorKind::Interrupted {
149 continue;
150 }
151 return Ok(cat_readwrite(in_fd, stdout.lock())?);
153 }
154 }
155 return Ok(true);
156 }
157
158 if stdout_mode == libc::S_IFREG {
159 let mut remaining = file_size;
164 while remaining > 0 {
165 let chunk = remaining.min(0x7ffff000);
166 let ret = unsafe {
167 libc::copy_file_range(
168 in_fd,
169 std::ptr::null_mut(),
170 out_fd,
171 std::ptr::null_mut(),
172 chunk,
173 0,
174 )
175 };
176 if ret > 0 {
177 remaining -= ret as usize;
178 } else if ret == 0 {
179 break;
180 } else {
181 let err = io::Error::last_os_error();
182 if err.kind() == io::ErrorKind::Interrupted {
183 continue;
184 }
185 return Ok(cat_readwrite(in_fd, stdout.lock())?);
187 }
188 }
189 return Ok(true);
190 }
191
192 Ok(cat_readwrite(in_fd, stdout.lock())?)
194}
195
196#[cfg(target_os = "linux")]
198fn cat_readwrite(in_fd: i32, mut out: impl Write) -> io::Result<bool> {
199 let mut buf = vec![0u8; 256 * 1024];
201 loop {
202 let n = match nix_read(in_fd, &mut buf) {
203 Ok(0) => break,
204 Ok(n) => n,
205 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
206 Err(e) => return Err(e),
207 };
208 out.write_all(&buf[..n])?;
209 }
210 Ok(true)
211}
212
213#[cfg(target_os = "linux")]
215fn nix_read(fd: i32, buf: &mut [u8]) -> io::Result<usize> {
216 let ret = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) };
217 if ret >= 0 {
218 Ok(ret as usize)
219 } else {
220 Err(io::Error::last_os_error())
221 }
222}
223
224pub fn cat_plain_file(path: &Path, out: &mut impl Write) -> io::Result<bool> {
228 #[cfg(target_os = "linux")]
229 {
230 match cat_plain_file_linux(path) {
231 Ok(true) => return Ok(true),
232 Ok(false) => {}
233 Err(CatPlainError::Io(e)) if e.kind() == io::ErrorKind::BrokenPipe => {
234 return Err(e);
235 }
236 Err(_) => {} }
238 }
239
240 let data = read_file_direct(path)?;
242 if !data.is_empty() {
243 out.write_all(&data)?;
244 }
245 Ok(true)
246}
247
248pub fn cat_plain_stdin(out: &mut impl Write) -> io::Result<()> {
250 #[cfg(target_os = "linux")]
251 {
252 let stdin_fd = 0i32;
254 let mut stat: libc::stat = unsafe { std::mem::zeroed() };
255 if unsafe { libc::fstat(1, &mut stat) } == 0
256 && (stat.st_mode & libc::S_IFMT) == libc::S_IFIFO
257 {
258 loop {
260 let ret = unsafe {
261 libc::splice(
262 stdin_fd,
263 std::ptr::null_mut(),
264 1,
265 std::ptr::null_mut(),
266 1024 * 1024 * 1024,
267 libc::SPLICE_F_MOVE,
268 )
269 };
270 if ret > 0 {
271 continue;
272 } else if ret == 0 {
273 return Ok(());
274 } else {
275 let err = io::Error::last_os_error();
276 if err.kind() == io::ErrorKind::Interrupted {
277 continue;
278 }
279 break;
281 }
282 }
283 }
284 }
285
286 let stdin = io::stdin();
288 let mut reader = stdin.lock();
289 let mut buf = [0u8; 262144]; loop {
291 let n = match reader.read(&mut buf) {
292 Ok(0) => break,
293 Ok(n) => n,
294 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
295 Err(e) => return Err(e),
296 };
297 out.write_all(&buf[..n])?;
298 }
299 Ok(())
300}
301
302fn _build_nonprinting_table(show_tabs: bool) -> ([u8; 256], [bool; 256]) {
306 let mut table = [0u8; 256];
307 let mut multi = [false; 256];
308
309 for i in 0..256u16 {
310 let b = i as u8;
311 match b {
312 b'\n' => {
313 table[i as usize] = b'\n';
314 }
315 b'\t' => {
316 if show_tabs {
317 table[i as usize] = b'I';
318 multi[i as usize] = true;
319 } else {
320 table[i as usize] = b'\t';
321 }
322 }
323 0..=8 | 10..=31 => {
324 table[i as usize] = b + 64;
326 multi[i as usize] = true;
327 }
328 32..=126 => {
329 table[i as usize] = b;
330 }
331 127 => {
332 table[i as usize] = b'?';
334 multi[i as usize] = true;
335 }
336 128..=159 => {
337 table[i as usize] = b - 128 + 64;
339 multi[i as usize] = true;
340 }
341 160..=254 => {
342 table[i as usize] = b - 128;
344 multi[i as usize] = true;
345 }
346 255 => {
347 table[i as usize] = b'?';
349 multi[i as usize] = true;
350 }
351 }
352 }
353
354 (table, multi)
355}
356
357#[inline]
359fn write_nonprinting(b: u8, show_tabs: bool, out: &mut Vec<u8>) {
360 match b {
361 b'\t' if !show_tabs => out.push(b'\t'),
362 b'\n' => out.push(b'\n'),
363 0..=8 | 10..=31 => {
364 out.push(b'^');
365 out.push(b + 64);
366 }
367 9 => {
368 out.push(b'^');
370 out.push(b'I');
371 }
372 32..=126 => out.push(b),
373 127 => {
374 out.push(b'^');
375 out.push(b'?');
376 }
377 128..=159 => {
378 out.push(b'M');
379 out.push(b'-');
380 out.push(b'^');
381 out.push(b - 128 + 64);
382 }
383 160..=254 => {
384 out.push(b'M');
385 out.push(b'-');
386 out.push(b - 128);
387 }
388 255 => {
389 out.push(b'M');
390 out.push(b'-');
391 out.push(b'^');
392 out.push(b'?');
393 }
394 }
395}
396
397fn cat_show_all_fast(
400 data: &[u8],
401 show_tabs: bool,
402 show_ends: bool,
403 out: &mut impl Write,
404) -> io::Result<()> {
405 const BUF_SIZE: usize = 256 * 1024;
407 let cap = data.len().min(BUF_SIZE) + data.len().min(BUF_SIZE) / 2;
409 let mut buf = Vec::with_capacity(cap);
410 let mut pos = 0;
411
412 while pos < data.len() {
413 let start = pos;
415 while pos < data.len() && data[pos].wrapping_sub(32) <= 94 {
416 pos += 1;
417 }
418 if pos > start {
420 buf.extend_from_slice(&data[start..pos]);
421 }
422 if pos >= data.len() {
423 break;
424 }
425 let b = data[pos];
427 pos += 1;
428 match b {
429 b'\n' => {
430 if show_ends {
431 buf.extend_from_slice(b"$\n");
432 } else {
433 buf.push(b'\n');
434 }
435 }
436 b'\t' if show_tabs => buf.extend_from_slice(b"^I"),
437 b'\t' => buf.push(b'\t'),
438 0..=8 | 10..=31 => {
439 buf.push(b'^');
440 buf.push(b + 64);
441 }
442 127 => buf.extend_from_slice(b"^?"),
443 128..=159 => {
444 buf.push(b'M');
445 buf.push(b'-');
446 buf.push(b'^');
447 buf.push(b - 128 + 64);
448 }
449 160..=254 => {
450 buf.push(b'M');
451 buf.push(b'-');
452 buf.push(b - 128);
453 }
454 255 => buf.extend_from_slice(b"M-^?"),
455 _ => unreachable!(),
456 }
457
458 if buf.len() >= BUF_SIZE {
460 out.write_all(&buf)?;
461 buf.clear();
462 }
463 }
464
465 if !buf.is_empty() {
466 out.write_all(&buf)?;
467 }
468 Ok(())
469}
470
471#[inline(always)]
475unsafe fn write_line_number_raw(dst: *mut u8, num: u64) -> usize {
476 if num <= 999999 {
478 let mut n = num as u32;
481 let d5 = n / 100000;
482 n -= d5 * 100000;
483 let d4 = n / 10000;
484 n -= d4 * 10000;
485 let d3 = n / 1000;
486 n -= d3 * 1000;
487 let d2 = n / 100;
488 n -= d2 * 100;
489 let d1 = n / 10;
490 let d0 = n - d1 * 10;
491
492 let width = if num >= 100000 {
494 6
495 } else if num >= 10000 {
496 5
497 } else if num >= 1000 {
498 4
499 } else if num >= 100 {
500 3
501 } else if num >= 10 {
502 2
503 } else {
504 1
505 };
506 let pad = 6 - width;
507
508 unsafe {
510 for i in 0..pad {
511 *dst.add(i) = b' ';
512 }
513 }
514
515 let digits = [
517 d5 as u8 + b'0',
518 d4 as u8 + b'0',
519 d3 as u8 + b'0',
520 d2 as u8 + b'0',
521 d1 as u8 + b'0',
522 d0 as u8 + b'0',
523 ];
524 unsafe {
525 std::ptr::copy_nonoverlapping(digits[6 - width..].as_ptr(), dst.add(pad), width);
526 *dst.add(6) = b'\t';
527 }
528 7
529 } else {
530 let mut buf = itoa::Buffer::new();
532 let s = buf.format(num);
533 let pad = if s.len() < 6 { 6 - s.len() } else { 0 };
534 unsafe {
535 for i in 0..pad {
536 *dst.add(i) = b' ';
537 }
538 std::ptr::copy_nonoverlapping(s.as_ptr(), dst.add(pad), s.len());
539 *dst.add(pad + s.len()) = b'\t';
540 }
541 pad + s.len() + 1
542 }
543}
544
545#[cfg(target_os = "linux")]
552fn cat_stream_numbered(
553 fd: i32,
554 line_num: &mut u64,
555 nonblank: bool,
556 out: &mut impl Write,
557) -> io::Result<bool> {
558 const READ_BUF: usize = 4 * 1024 * 1024;
559 let mut buf = vec![0u8; READ_BUF];
560 let mut carry: Vec<u8> = Vec::new();
561
562 loop {
563 let n = loop {
564 let ret = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) };
565 if ret >= 0 {
566 break ret as usize;
567 }
568 let err = io::Error::last_os_error();
569 if err.kind() != io::ErrorKind::Interrupted {
570 return Err(err);
571 }
572 };
573 if n == 0 {
574 if !carry.is_empty() {
576 if nonblank {
577 cat_number_nonblank_fast(&carry, line_num, out)?;
578 } else {
579 cat_number_all_fast(&carry, line_num, out)?;
580 }
581 }
582 return Ok(true);
583 }
584
585 let chunk = &buf[..n];
586
587 if carry.is_empty() {
588 match memchr::memrchr(b'\n', chunk) {
590 Some(last_nl) => {
591 let complete = &chunk[..last_nl + 1];
592 if nonblank {
593 cat_number_nonblank_fast(complete, line_num, out)?;
594 } else {
595 cat_number_all_fast(complete, line_num, out)?;
596 }
597 if last_nl + 1 < n {
599 carry.extend_from_slice(&chunk[last_nl + 1..]);
600 }
601 }
602 None => {
603 carry.extend_from_slice(chunk);
605 }
606 }
607 } else {
608 match memchr::memchr(b'\n', chunk) {
610 Some(first_nl) => {
611 carry.extend_from_slice(&chunk[..first_nl + 1]);
613 if nonblank {
614 cat_number_nonblank_fast(&carry, line_num, out)?;
615 } else {
616 cat_number_all_fast(&carry, line_num, out)?;
617 }
618 carry.clear();
619
620 let rest = &chunk[first_nl + 1..];
622 if !rest.is_empty() {
623 match memchr::memrchr(b'\n', rest) {
624 Some(last_nl) => {
625 let complete = &rest[..last_nl + 1];
626 if nonblank {
627 cat_number_nonblank_fast(complete, line_num, out)?;
628 } else {
629 cat_number_all_fast(complete, line_num, out)?;
630 }
631 if last_nl + 1 < rest.len() {
632 carry.extend_from_slice(&rest[last_nl + 1..]);
633 }
634 }
635 None => {
636 carry.extend_from_slice(rest);
637 }
638 }
639 }
640 }
641 None => {
642 carry.extend_from_slice(chunk);
644 }
645 }
646 }
647 }
648}
649
650fn cat_number_all_fast(data: &[u8], line_num: &mut u64, out: &mut impl Write) -> io::Result<()> {
655 if data.is_empty() {
656 return Ok(());
657 }
658
659 let alloc = (data.len() * 2 + 256).min(64 * 1024 * 1024);
663 let mut output: Vec<u8> = Vec::with_capacity(alloc);
664 let mut out_ptr = output.as_mut_ptr();
665 let mut out_pos: usize = 0;
666
667 let mut num = *line_num;
668 let mut pos: usize = 0;
669
670 for nl_pos in memchr::memchr_iter(b'\n', data) {
671 let line_len = nl_pos + 1 - pos;
673 let needed = out_pos + line_len + 22; if needed > output.capacity() {
675 unsafe { output.set_len(out_pos) };
676 output.reserve(needed.saturating_sub(output.len()));
677 out_ptr = output.as_mut_ptr();
678 }
679
680 unsafe {
682 out_pos += write_line_number_raw(out_ptr.add(out_pos), num);
683 }
684 num += 1;
685
686 unsafe {
688 std::ptr::copy_nonoverlapping(data.as_ptr().add(pos), out_ptr.add(out_pos), line_len);
689 }
690 out_pos += line_len;
691 pos = nl_pos + 1;
692
693 if out_pos >= 8 * 1024 * 1024 {
695 unsafe { output.set_len(out_pos) };
696 out.write_all(&output)?;
697 output.clear();
698 out_pos = 0;
699 out_ptr = output.as_mut_ptr();
700 }
701 }
702
703 if pos < data.len() {
705 let remaining = data.len() - pos;
706 let needed = out_pos + remaining + 22;
707 if needed > output.capacity() {
708 unsafe { output.set_len(out_pos) };
709 output.reserve(needed.saturating_sub(output.len()));
710 out_ptr = output.as_mut_ptr();
711 }
712 unsafe {
713 out_pos += write_line_number_raw(out_ptr.add(out_pos), num);
714 }
715 num += 1;
716 unsafe {
717 std::ptr::copy_nonoverlapping(data.as_ptr().add(pos), out_ptr.add(out_pos), remaining);
718 }
719 out_pos += remaining;
720 }
721
722 *line_num = num;
723
724 unsafe { output.set_len(out_pos) };
725 if !output.is_empty() {
726 out.write_all(&output)?;
727 }
728
729 Ok(())
730}
731
732fn cat_number_nonblank_fast(
734 data: &[u8],
735 line_num: &mut u64,
736 out: &mut impl Write,
737) -> io::Result<()> {
738 if data.is_empty() {
739 return Ok(());
740 }
741
742 let alloc = (data.len() * 2 + 256).min(64 * 1024 * 1024);
743 let mut output: Vec<u8> = Vec::with_capacity(alloc);
744 let mut out_ptr = output.as_mut_ptr();
745 let mut out_pos: usize = 0;
746
747 let mut num = *line_num;
748 let mut pos: usize = 0;
749
750 for nl_pos in memchr::memchr_iter(b'\n', data) {
751 let line_len = nl_pos + 1 - pos;
752 let needed = out_pos + line_len + 22;
753 if needed > output.capacity() {
754 unsafe { output.set_len(out_pos) };
755 output.reserve(needed.saturating_sub(output.len()));
756 out_ptr = output.as_mut_ptr();
757 }
758
759 let is_blank = nl_pos == pos;
760 if !is_blank {
761 unsafe {
762 out_pos += write_line_number_raw(out_ptr.add(out_pos), num);
763 }
764 num += 1;
765 }
766
767 unsafe {
768 std::ptr::copy_nonoverlapping(data.as_ptr().add(pos), out_ptr.add(out_pos), line_len);
769 }
770 out_pos += line_len;
771 pos = nl_pos + 1;
772
773 if out_pos >= 8 * 1024 * 1024 {
774 unsafe { output.set_len(out_pos) };
775 out.write_all(&output)?;
776 output.clear();
777 out_pos = 0;
778 out_ptr = output.as_mut_ptr();
779 }
780 }
781
782 if pos < data.len() {
783 let remaining = data.len() - pos;
784 let needed = out_pos + remaining + 22;
785 if needed > output.capacity() {
786 unsafe { output.set_len(out_pos) };
787 output.reserve(needed.saturating_sub(output.len()));
788 out_ptr = output.as_mut_ptr();
789 }
790 unsafe {
791 out_pos += write_line_number_raw(out_ptr.add(out_pos), num);
792 }
793 num += 1;
794 unsafe {
795 std::ptr::copy_nonoverlapping(data.as_ptr().add(pos), out_ptr.add(out_pos), remaining);
796 }
797 out_pos += remaining;
798 }
799
800 *line_num = num;
801
802 unsafe { output.set_len(out_pos) };
803 if !output.is_empty() {
804 out.write_all(&output)?;
805 }
806
807 Ok(())
808}
809
810pub fn cat_with_options(
812 data: &[u8],
813 config: &CatConfig,
814 line_num: &mut u64,
815 pending_cr: &mut bool,
816 out: &mut impl Write,
817) -> io::Result<()> {
818 if data.is_empty() {
819 return Ok(());
820 }
821
822 if config.show_nonprinting && !config.number && !config.number_nonblank && !config.squeeze_blank
824 {
825 return cat_show_all_fast(data, config.show_tabs, config.show_ends, out);
826 }
827
828 if config.number
830 && !config.number_nonblank
831 && !config.show_ends
832 && !config.show_tabs
833 && !config.show_nonprinting
834 && !config.squeeze_blank
835 && !*pending_cr
836 {
837 return cat_number_all_fast(data, line_num, out);
838 }
839
840 if config.number_nonblank
842 && !config.number
843 && !config.show_ends
844 && !config.show_tabs
845 && !config.show_nonprinting
846 && !config.squeeze_blank
847 && !*pending_cr
848 {
849 return cat_number_nonblank_fast(data, line_num, out);
850 }
851
852 let estimated = data.len() + data.len() / 10 + 1024;
855 let mut buf = Vec::with_capacity(estimated.min(16 * 1024 * 1024));
856
857 let mut prev_blank = false;
858 let mut pos = 0;
859 let mut itoa_buf = itoa::Buffer::new();
860
861 if *pending_cr {
863 *pending_cr = false;
864 if config.show_ends
865 && !(config.show_nonprinting || config.show_tabs)
866 && !data.is_empty()
867 && data[0] == b'\n'
868 {
869 buf.extend_from_slice(b"^M$\n");
871 pos = 1;
872 } else {
873 buf.push(b'\r');
875 }
876 }
877
878 while pos < data.len() {
879 let line_end = memchr::memchr(b'\n', &data[pos..])
881 .map(|p| pos + p + 1)
882 .unwrap_or(data.len());
883
884 let line = &data[pos..line_end];
885 let is_blank = line == b"\n" || line.is_empty();
886
887 if config.squeeze_blank && is_blank && prev_blank {
889 pos = line_end;
890 continue;
891 }
892 prev_blank = is_blank;
893
894 if config.number_nonblank {
896 if !is_blank {
897 let s = itoa_buf.format(*line_num);
898 let pad = if s.len() < 6 { 6 - s.len() } else { 0 };
900 buf.extend(std::iter::repeat_n(b' ', pad));
901 buf.extend_from_slice(s.as_bytes());
902 buf.push(b'\t');
903 *line_num += 1;
904 }
905 } else if config.number {
906 let s = itoa_buf.format(*line_num);
907 let pad = if s.len() < 6 { 6 - s.len() } else { 0 };
908 buf.extend(std::iter::repeat_n(b' ', pad));
909 buf.extend_from_slice(s.as_bytes());
910 buf.push(b'\t');
911 *line_num += 1;
912 }
913
914 if config.show_nonprinting || config.show_tabs {
916 let content_end = if line.last() == Some(&b'\n') {
917 line.len() - 1
918 } else {
919 line.len()
920 };
921
922 for &b in &line[..content_end] {
923 if config.show_nonprinting {
924 write_nonprinting(b, config.show_tabs, &mut buf);
925 } else if config.show_tabs && b == b'\t' {
926 buf.extend_from_slice(b"^I");
927 } else {
928 buf.push(b);
929 }
930 }
931
932 if config.show_ends && line.last() == Some(&b'\n') {
933 buf.push(b'$');
934 }
935 if line.last() == Some(&b'\n') {
936 buf.push(b'\n');
937 }
938 } else {
939 if config.show_ends {
941 let has_newline = line.last() == Some(&b'\n');
942 let content_end = if has_newline {
943 line.len() - 1
944 } else {
945 line.len()
946 };
947 let content = &line[..content_end];
950 if has_newline && !content.is_empty() && content[content.len() - 1] == b'\r' {
951 buf.extend_from_slice(&content[..content.len() - 1]);
953 buf.extend_from_slice(b"^M");
954 } else if !has_newline && !content.is_empty() && content[content.len() - 1] == b'\r'
955 {
956 buf.extend_from_slice(&content[..content.len() - 1]);
959 *pending_cr = true;
960 } else {
961 buf.extend_from_slice(content);
962 }
963 if has_newline {
964 buf.push(b'$');
965 buf.push(b'\n');
966 }
967 } else {
968 buf.extend_from_slice(line);
969 }
970 }
971
972 if buf.len() >= 8 * 1024 * 1024 {
974 out.write_all(&buf)?;
975 buf.clear();
976 }
977
978 pos = line_end;
979 }
980
981 if !buf.is_empty() {
982 out.write_all(&buf)?;
983 }
984
985 Ok(())
986}
987
988pub fn cat_file(
990 filename: &str,
991 config: &CatConfig,
992 line_num: &mut u64,
993 pending_cr: &mut bool,
994 out: &mut impl Write,
995 tool_name: &str,
996) -> io::Result<bool> {
997 if filename == "-" {
998 if config.is_plain() {
999 match cat_plain_stdin(out) {
1000 Ok(()) => return Ok(true),
1001 Err(e) if e.kind() == io::ErrorKind::BrokenPipe => {
1002 std::process::exit(0);
1003 }
1004 Err(e) => {
1005 eprintln!(
1006 "{}: standard input: {}",
1007 tool_name,
1008 crate::common::io_error_msg(&e)
1009 );
1010 return Ok(false);
1011 }
1012 }
1013 }
1014 match read_stdin() {
1015 Ok(data) => {
1016 cat_with_options(&data, config, line_num, pending_cr, out)?;
1017 Ok(true)
1018 }
1019 Err(e) => {
1020 eprintln!(
1021 "{}: standard input: {}",
1022 tool_name,
1023 crate::common::io_error_msg(&e)
1024 );
1025 Ok(false)
1026 }
1027 }
1028 } else {
1029 let path = Path::new(filename);
1030
1031 if config.is_plain() {
1032 #[cfg(target_os = "linux")]
1035 {
1036 match cat_plain_file_linux(path) {
1037 Ok(true) => return Ok(true),
1038 Ok(false) => {
1039 }
1042 Err(CatPlainError::IsDirectory) => {
1043 eprintln!("{}: {}: Is a directory", tool_name, filename);
1044 return Ok(false);
1045 }
1046 Err(CatPlainError::InputIsOutput) => {
1047 eprintln!("{}: {}: input file is output file", tool_name, filename);
1048 return Ok(false);
1049 }
1050 Err(CatPlainError::Io(e)) if e.kind() == io::ErrorKind::BrokenPipe => {
1051 std::process::exit(0);
1052 }
1053 Err(CatPlainError::Io(e)) => {
1054 eprintln!(
1055 "{}: {}: {}",
1056 tool_name,
1057 filename,
1058 crate::common::io_error_msg(&e)
1059 );
1060 return Ok(false);
1061 }
1062 }
1063 }
1064
1065 if let Ok(file_meta) = std::fs::metadata(path) {
1069 if file_meta.is_dir() {
1070 eprintln!("{}: {}: Is a directory", tool_name, filename);
1071 return Ok(false);
1072 }
1073
1074 #[cfg(unix)]
1075 {
1076 use std::os::unix::fs::MetadataExt;
1077 let mut stdout_stat: libc::stat = unsafe { std::mem::zeroed() };
1078 if unsafe { libc::fstat(1, &mut stdout_stat) } == 0
1079 && file_meta.dev() == stdout_stat.st_dev as u64
1080 && file_meta.ino() == stdout_stat.st_ino as u64
1081 {
1082 eprintln!("{}: {}: input file is output file", tool_name, filename);
1083 return Ok(false);
1084 }
1085 }
1086 }
1087
1088 match read_file_direct(path) {
1090 Ok(data) => {
1091 if !data.is_empty() {
1092 out.write_all(&data)?;
1093 }
1094 return Ok(true);
1095 }
1096 Err(e) => {
1097 eprintln!(
1098 "{}: {}: {}",
1099 tool_name,
1100 filename,
1101 crate::common::io_error_msg(&e)
1102 );
1103 return Ok(false);
1104 }
1105 }
1106 }
1107
1108 #[cfg(target_os = "linux")]
1112 {
1113 use std::os::unix::io::AsRawFd;
1114 if let Ok(file) = crate::common::io::open_noatime(path) {
1115 let fd = file.as_raw_fd();
1116 let mut stat: libc::stat = unsafe { std::mem::zeroed() };
1117 if unsafe { libc::fstat(fd, &mut stat) } == 0 {
1118 if (stat.st_mode & libc::S_IFMT) == libc::S_IFDIR {
1120 eprintln!("{}: {}: Is a directory", tool_name, filename);
1121 return Ok(false);
1122 }
1123 let mut stdout_stat: libc::stat = unsafe { std::mem::zeroed() };
1125 if unsafe { libc::fstat(1, &mut stdout_stat) } == 0
1126 && stat.st_dev == stdout_stat.st_dev
1127 && stat.st_ino == stdout_stat.st_ino
1128 {
1129 eprintln!("{}: {}: input file is output file", tool_name, filename);
1130 return Ok(false);
1131 }
1132 }
1133
1134 if config.number
1136 && !config.number_nonblank
1137 && !config.show_ends
1138 && !config.show_tabs
1139 && !config.show_nonprinting
1140 && !config.squeeze_blank
1141 && !*pending_cr
1142 {
1143 unsafe {
1144 libc::posix_fadvise(fd, 0, 0, libc::POSIX_FADV_SEQUENTIAL);
1145 }
1146 return cat_stream_numbered(fd, line_num, false, out);
1147 }
1148
1149 if config.number_nonblank
1151 && !config.number
1152 && !config.show_ends
1153 && !config.show_tabs
1154 && !config.show_nonprinting
1155 && !config.squeeze_blank
1156 && !*pending_cr
1157 {
1158 unsafe {
1159 libc::posix_fadvise(fd, 0, 0, libc::POSIX_FADV_SEQUENTIAL);
1160 }
1161 return cat_stream_numbered(fd, line_num, true, out);
1162 }
1163
1164 let size = if stat.st_size > 0 {
1167 stat.st_size as usize
1168 } else {
1169 0
1170 };
1171 let mut data = Vec::with_capacity(size);
1172 use std::io::Read;
1173 if (&file).read_to_end(&mut data).is_ok() {
1174 cat_with_options(&data, config, line_num, pending_cr, out)?;
1175 return Ok(true);
1176 }
1177 }
1179 }
1180
1181 match std::fs::metadata(path) {
1184 Ok(meta) if meta.is_dir() => {
1185 eprintln!("{}: {}: Is a directory", tool_name, filename);
1186 return Ok(false);
1187 }
1188 _ => {}
1189 }
1190
1191 #[cfg(unix)]
1192 {
1193 use std::os::unix::fs::MetadataExt;
1194 if let Ok(file_meta) = std::fs::metadata(path) {
1195 let mut stdout_stat: libc::stat = unsafe { std::mem::zeroed() };
1196 if unsafe { libc::fstat(1, &mut stdout_stat) } == 0
1197 && file_meta.dev() == stdout_stat.st_dev as u64
1198 && file_meta.ino() == stdout_stat.st_ino as u64
1199 {
1200 eprintln!("{}: {}: input file is output file", tool_name, filename);
1201 return Ok(false);
1202 }
1203 }
1204 }
1205
1206 match read_file_direct(path) {
1207 Ok(data) => {
1208 cat_with_options(&data, config, line_num, pending_cr, out)?;
1209 Ok(true)
1210 }
1211 Err(e) => {
1212 eprintln!(
1213 "{}: {}: {}",
1214 tool_name,
1215 filename,
1216 crate::common::io_error_msg(&e)
1217 );
1218 Ok(false)
1219 }
1220 }
1221 }
1222}