1use std::io::{self, Read, Seek, Write};
2use std::path::Path;
3
4use memchr::{memchr_iter, memrchr_iter};
5
6use crate::common::io::{FileData, read_file, read_stdin};
7
8#[cfg(target_os = "linux")]
10fn open_noatime(path: &Path) -> io::Result<std::fs::File> {
11 use std::os::unix::fs::OpenOptionsExt;
12 std::fs::OpenOptions::new()
13 .read(true)
14 .custom_flags(libc::O_NOATIME)
15 .open(path)
16 .or_else(|_| std::fs::File::open(path))
17}
18
19#[cfg(target_os = "linux")]
21pub fn open_file_noatime(path: &Path) -> io::Result<std::fs::File> {
22 open_noatime(path)
23}
24
25#[cfg(target_os = "linux")]
27fn write_all_fd(fd: i32, mut data: &[u8]) -> io::Result<()> {
28 while !data.is_empty() {
29 let ret = unsafe { libc::write(fd, data.as_ptr() as *const libc::c_void, data.len()) };
30 if ret > 0 {
31 data = &data[ret as usize..];
32 } else if ret == 0 {
33 return Err(io::Error::new(io::ErrorKind::WriteZero, "write returned 0"));
34 } else {
35 let err = io::Error::last_os_error();
36 if err.kind() == io::ErrorKind::Interrupted {
37 continue;
38 }
39 return Err(err);
40 }
41 }
42 Ok(())
43}
44
45fn find_tail_start_byte(
52 reader: &mut (impl Read + Seek),
53 file_size: u64,
54 n: u64,
55 delimiter: u8,
56) -> io::Result<u64> {
57 if file_size <= 8192 {
59 let mut stack_buf = [0u8; 8192];
60 return find_tail_start_byte_inner(reader, file_size, n, delimiter, &mut stack_buf);
61 }
62
63 const CHUNK: usize = 262144;
64 let mut buf = vec![0u8; CHUNK];
65 find_tail_start_byte_inner(reader, file_size, n, delimiter, &mut buf)
66}
67
68fn find_tail_start_byte_inner(
70 reader: &mut (impl Read + Seek),
71 file_size: u64,
72 n: u64,
73 delimiter: u8,
74 buf: &mut [u8],
75) -> io::Result<u64> {
76 let chunk_size = buf.len() as u64;
77 let mut pos = file_size;
78 let mut count = 0u64;
79
80 while pos > 0 {
81 let read_start = if pos > chunk_size {
82 pos - chunk_size
83 } else {
84 0
85 };
86 let read_len = (pos - read_start) as usize;
87
88 reader.seek(io::SeekFrom::Start(read_start))?;
89 reader.read_exact(&mut buf[..read_len])?;
90
91 let search_end = if pos == file_size && read_len > 0 && buf[read_len - 1] == delimiter {
93 read_len - 1
94 } else {
95 read_len
96 };
97
98 for rpos in memrchr_iter(delimiter, &buf[..search_end]) {
99 count += 1;
100 if count == n {
101 return Ok(read_start + rpos as u64 + 1);
102 }
103 }
104
105 pos = read_start;
106 }
107
108 Ok(0)
109}
110
111#[derive(Clone, Debug)]
113pub enum TailMode {
114 Lines(u64),
116 LinesFrom(u64),
118 Bytes(u64),
120 BytesFrom(u64),
122}
123
124#[derive(Clone, Debug, PartialEq)]
126pub enum FollowMode {
127 None,
128 Descriptor,
129 Name,
130}
131
132#[derive(Clone, Debug)]
134pub struct TailConfig {
135 pub mode: TailMode,
136 pub follow: FollowMode,
137 pub retry: bool,
138 pub pid: Option<u32>,
139 pub sleep_interval: f64,
140 pub max_unchanged_stats: u64,
141 pub zero_terminated: bool,
142}
143
144impl Default for TailConfig {
145 fn default() -> Self {
146 Self {
147 mode: TailMode::Lines(10),
148 follow: FollowMode::None,
149 retry: false,
150 pid: None,
151 sleep_interval: 1.0,
152 max_unchanged_stats: 5,
153 zero_terminated: false,
154 }
155 }
156}
157
158pub fn parse_size(s: &str) -> Result<u64, String> {
160 crate::head::parse_size(s)
161}
162
163pub fn tail_lines(data: &[u8], n: u64, delimiter: u8, out: &mut impl Write) -> io::Result<()> {
165 if n == 0 || data.is_empty() {
166 return Ok(());
167 }
168
169 let mut count = 0u64;
171
172 let search_end = if !data.is_empty() && data[data.len() - 1] == delimiter {
174 data.len() - 1
175 } else {
176 data.len()
177 };
178
179 for pos in memrchr_iter(delimiter, &data[..search_end]) {
180 count += 1;
181 if count == n {
182 return out.write_all(&data[pos + 1..]);
183 }
184 }
185
186 out.write_all(data)
188}
189
190pub fn tail_lines_from(data: &[u8], n: u64, delimiter: u8, out: &mut impl Write) -> io::Result<()> {
192 if data.is_empty() {
193 return Ok(());
194 }
195
196 if n <= 1 {
197 return out.write_all(data);
198 }
199
200 let skip = n - 1;
202 let mut count = 0u64;
203
204 for pos in memchr_iter(delimiter, data) {
205 count += 1;
206 if count == skip {
207 let start = pos + 1;
208 if start < data.len() {
209 return out.write_all(&data[start..]);
210 }
211 return Ok(());
212 }
213 }
214
215 Ok(())
217}
218
219pub fn tail_bytes(data: &[u8], n: u64, out: &mut impl Write) -> io::Result<()> {
221 if n == 0 || data.is_empty() {
222 return Ok(());
223 }
224
225 let n = n.min(data.len() as u64) as usize;
226 out.write_all(&data[data.len() - n..])
227}
228
229pub fn tail_bytes_from(data: &[u8], n: u64, out: &mut impl Write) -> io::Result<()> {
231 if data.is_empty() {
232 return Ok(());
233 }
234
235 if n <= 1 {
236 return out.write_all(data);
237 }
238
239 let start = ((n - 1) as usize).min(data.len());
240 if start < data.len() {
241 out.write_all(&data[start..])
242 } else {
243 Ok(())
244 }
245}
246
247#[cfg(target_os = "linux")]
250pub fn sendfile_tail_bytes(path: &Path, n: u64, out_fd: i32) -> io::Result<bool> {
251 let file = open_noatime(path)?;
252
253 let metadata = file.metadata()?;
254 let file_size = metadata.len();
255
256 if file_size == 0 {
257 return Ok(true);
258 }
259
260 let n = n.min(file_size);
261 let start = file_size - n;
262
263 use std::os::unix::io::AsRawFd;
264 let in_fd = file.as_raw_fd();
265 let _ = unsafe {
266 libc::posix_fadvise(
267 in_fd,
268 start as libc::off_t,
269 n as libc::off_t,
270 libc::POSIX_FADV_SEQUENTIAL,
271 )
272 };
273 let mut offset: libc::off_t = start as libc::off_t;
274 let mut remaining = n;
275 let total = n;
276
277 while remaining > 0 {
278 let chunk = remaining.min(0x7fff_f000) as usize;
279 let ret = unsafe { libc::sendfile(out_fd, in_fd, &mut offset, chunk) };
280 if ret > 0 {
281 remaining -= ret as u64;
282 } else if ret == 0 {
283 break;
284 } else {
285 let err = io::Error::last_os_error();
286 if err.kind() == io::ErrorKind::Interrupted {
287 continue;
288 }
289 if err.raw_os_error() == Some(libc::EINVAL) && remaining == total {
291 let mut file = file;
292 file.seek(io::SeekFrom::Start(start))?;
293 let mut buf = [0u8; 65536];
294 let mut left = n as usize;
295 while left > 0 {
296 let to_read = left.min(buf.len());
297 let nr = match file.read(&mut buf[..to_read]) {
298 Ok(0) => break,
299 Ok(nr) => nr,
300 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
301 Err(e) => return Err(e),
302 };
303 write_all_fd(out_fd, &buf[..nr])?;
304 left -= nr;
305 }
306 return Ok(true);
307 }
308 return Err(err);
309 }
310 }
311
312 Ok(true)
313}
314
315#[cfg(target_os = "linux")]
318fn sendfile_tail_lines(
319 file: std::fs::File,
320 file_size: u64,
321 n: u64,
322 delimiter: u8,
323 out_fd: i32,
324) -> io::Result<bool> {
325 use std::os::unix::io::AsRawFd;
326
327 if n == 0 || file_size == 0 {
328 return Ok(true);
329 }
330
331 let in_fd = file.as_raw_fd();
332
333 let _ = unsafe { libc::posix_fadvise(in_fd, 0, 0, libc::POSIX_FADV_RANDOM) };
335
336 let mut reader = file;
337 let start_byte = find_tail_start_byte(&mut reader, file_size, n, delimiter)?;
338
339 let remaining = file_size - start_byte;
341 let _ = unsafe {
342 libc::posix_fadvise(
343 in_fd,
344 start_byte as libc::off_t,
345 remaining as libc::off_t,
346 libc::POSIX_FADV_SEQUENTIAL,
347 )
348 };
349
350 let mut offset = start_byte as libc::off_t;
352 let mut left = remaining;
353 while left > 0 {
354 let chunk = left.min(0x7fff_f000) as usize;
355 let ret = unsafe { libc::sendfile(out_fd, in_fd, &mut offset, chunk) };
356 if ret > 0 {
357 left -= ret as u64;
358 } else if ret == 0 {
359 break;
360 } else {
361 let err = io::Error::last_os_error();
362 if err.kind() == io::ErrorKind::Interrupted {
363 continue;
364 }
365 if err.raw_os_error() == Some(libc::EINVAL) && left == remaining {
367 reader.seek(io::SeekFrom::Start(start_byte))?;
368 let mut buf = [0u8; 65536];
369 loop {
370 let n = match reader.read(&mut buf) {
371 Ok(0) => break,
372 Ok(n) => n,
373 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
374 Err(e) => return Err(e),
375 };
376 write_all_fd(out_fd, &buf[..n])?;
377 }
378 return Ok(true);
379 }
380 return Err(err);
381 }
382 }
383
384 Ok(true)
385}
386
387#[cfg(target_os = "linux")]
392pub fn tail_file_direct(filename: &str, n: u64, delimiter: u8) -> io::Result<bool> {
393 use std::os::unix::fs::OpenOptionsExt;
394
395 if n == 0 {
396 return Ok(true);
397 }
398
399 let path = Path::new(filename);
400
401 let file = std::fs::OpenOptions::new()
402 .read(true)
403 .custom_flags(libc::O_NOATIME)
404 .open(path)
405 .or_else(|_| std::fs::File::open(path));
406 let mut file = match file {
407 Ok(f) => f,
408 Err(e) => {
409 eprintln!(
410 "tail: cannot open '{}' for reading: {}",
411 filename,
412 crate::common::io_error_msg(&e)
413 );
414 return Ok(false);
415 }
416 };
417
418 let file_size = file.metadata()?.len();
419 if file_size == 0 {
420 return Ok(true);
421 }
422
423 if file_size <= 65536 {
426 let mut buf = [0u8; 65536];
427 let buf = &mut buf[..file_size as usize];
428 let mut total_read = 0;
429 while total_read < buf.len() {
430 match file.read(&mut buf[total_read..]) {
431 Ok(0) => break,
432 Ok(nr) => total_read += nr,
433 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
434 Err(e) => return Err(e),
435 }
436 }
437 let data = &buf[..total_read];
438 let mut count = 0u64;
440 let search_end = if !data.is_empty() && data[data.len() - 1] == delimiter {
441 data.len() - 1
442 } else {
443 data.len()
444 };
445 for rpos in memrchr_iter(delimiter, &data[..search_end]) {
446 count += 1;
447 if count == n {
448 write_all_fd(1, &data[rpos + 1..])?;
449 return Ok(true);
450 }
451 }
452 write_all_fd(1, data)?;
454 return Ok(true);
455 }
456
457 use std::os::unix::io::AsRawFd;
459 let in_fd = file.as_raw_fd();
460 let _ = unsafe { libc::posix_fadvise(in_fd, 0, 0, libc::POSIX_FADV_RANDOM) };
461
462 let start_byte = find_tail_start_byte(&mut file, file_size, n, delimiter)?;
463 let remaining = file_size - start_byte;
464
465 let _ = unsafe {
466 libc::posix_fadvise(
467 in_fd,
468 start_byte as libc::off_t,
469 remaining as libc::off_t,
470 libc::POSIX_FADV_SEQUENTIAL,
471 )
472 };
473
474 let mut sf_offset = start_byte as libc::off_t;
476 let mut sf_left = remaining;
477 let sf_total = remaining;
478 while sf_left > 0 {
479 let chunk = sf_left.min(0x7fff_f000) as usize;
480 let ret = unsafe { libc::sendfile(1, in_fd, &mut sf_offset, chunk) };
481 if ret > 0 {
482 sf_left -= ret as u64;
483 } else if ret == 0 {
484 break;
485 } else {
486 let err = io::Error::last_os_error();
487 if err.kind() == io::ErrorKind::Interrupted {
488 continue;
489 }
490 if err.raw_os_error() == Some(libc::EINVAL) && sf_left == sf_total {
491 file.seek(io::SeekFrom::Start(start_byte))?;
492 let mut rbuf = [0u8; 65536];
493 loop {
494 let nr = match file.read(&mut rbuf) {
495 Ok(0) => break,
496 Ok(nr) => nr,
497 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
498 Err(e) => return Err(e),
499 };
500 write_all_fd(1, &rbuf[..nr])?;
501 }
502 return Ok(true);
503 }
504 return Err(err);
505 }
506 }
507
508 Ok(true)
509}
510
511#[cfg(target_os = "linux")]
513pub fn tail_file_bytes_direct(filename: &str, n: u64) -> io::Result<bool> {
514 use std::os::unix::fs::OpenOptionsExt;
515
516 if n == 0 {
517 return Ok(true);
518 }
519
520 let path = Path::new(filename);
521
522 let file = std::fs::OpenOptions::new()
523 .read(true)
524 .custom_flags(libc::O_NOATIME)
525 .open(path)
526 .or_else(|_| std::fs::File::open(path));
527 let mut file = match file {
528 Ok(f) => f,
529 Err(e) => {
530 eprintln!(
531 "tail: cannot open '{}' for reading: {}",
532 filename,
533 crate::common::io_error_msg(&e)
534 );
535 return Ok(false);
536 }
537 };
538
539 let file_size = file.metadata()?.len();
540 if file_size == 0 {
541 return Ok(true);
542 }
543
544 let n = n.min(file_size);
545 let start = file_size - n;
546
547 if n <= 65536 {
549 file.seek(io::SeekFrom::Start(start))?;
550 let mut buf = [0u8; 65536];
551 let buf = &mut buf[..n as usize];
552 let mut total_read = 0;
553 while total_read < buf.len() {
554 match file.read(&mut buf[total_read..]) {
555 Ok(0) => break,
556 Ok(nr) => total_read += nr,
557 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
558 Err(e) => return Err(e),
559 }
560 }
561 write_all_fd(1, &buf[..total_read])?;
562 return Ok(true);
563 }
564
565 use std::os::unix::io::AsRawFd;
567 let in_fd = file.as_raw_fd();
568 let _ = unsafe {
569 libc::posix_fadvise(
570 in_fd,
571 start as libc::off_t,
572 n as libc::off_t,
573 libc::POSIX_FADV_SEQUENTIAL,
574 )
575 };
576
577 let mut sf_offset = start as libc::off_t;
578 let mut sf_remaining = n;
579 let sf_total = n;
580 while sf_remaining > 0 {
581 let chunk = sf_remaining.min(0x7fff_f000) as usize;
582 let ret = unsafe { libc::sendfile(1, in_fd, &mut sf_offset, chunk) };
583 if ret > 0 {
584 sf_remaining -= ret as u64;
585 } else if ret == 0 {
586 break;
587 } else {
588 let err = io::Error::last_os_error();
589 if err.kind() == io::ErrorKind::Interrupted {
590 continue;
591 }
592 if err.raw_os_error() == Some(libc::EINVAL) && sf_remaining == sf_total {
593 file.seek(io::SeekFrom::Start(start))?;
594 let mut rbuf = [0u8; 65536];
595 let mut left = n as usize;
596 while left > 0 {
597 let to_read = left.min(rbuf.len());
598 let nr = match file.read(&mut rbuf[..to_read]) {
599 Ok(0) => break,
600 Ok(nr) => nr,
601 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
602 Err(e) => return Err(e),
603 };
604 write_all_fd(1, &rbuf[..nr])?;
605 left -= nr;
606 }
607 return Ok(true);
608 }
609 return Err(err);
610 }
611 }
612
613 Ok(true)
614}
615
616#[cfg(not(target_os = "linux"))]
619fn tail_lines_streaming_file(
620 mut file: std::fs::File,
621 file_size: u64,
622 n: u64,
623 delimiter: u8,
624 out: &mut impl Write,
625) -> io::Result<bool> {
626 if n == 0 || file_size == 0 {
627 return Ok(true);
628 }
629
630 let start_byte = find_tail_start_byte(&mut file, file_size, n, delimiter)?;
631 file.seek(io::SeekFrom::Start(start_byte))?;
632 io::copy(&mut file, out)?;
633
634 Ok(true)
635}
636
637fn tail_lines_from_streaming_file(
644 file: std::fs::File,
645 n: u64,
646 delimiter: u8,
647 out: &mut impl Write,
648) -> io::Result<bool> {
649 if n <= 1 {
650 #[cfg(target_os = "linux")]
652 {
653 use std::os::unix::io::AsRawFd;
654 let in_fd = file.as_raw_fd();
655 let stdout = io::stdout();
656 let out_fd = stdout.as_raw_fd();
657 let file_size = file.metadata()?.len();
658 return sendfile_to_stdout_raw(in_fd, file_size, out_fd);
659 }
660 #[cfg(not(target_os = "linux"))]
661 {
662 let mut reader = io::BufReader::with_capacity(1024 * 1024, file);
663 let mut buf = [0u8; 262144];
664 loop {
665 let n = match reader.read(&mut buf) {
666 Ok(0) => break,
667 Ok(n) => n,
668 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
669 Err(e) => return Err(e),
670 };
671 out.write_all(&buf[..n])?;
672 }
673 return Ok(true);
674 }
675 }
676
677 let skip = n - 1;
678 let mut reader = io::BufReader::with_capacity(1024 * 1024, file);
679 let mut buf = [0u8; 262144];
680 let mut count = 0u64;
681 let mut skipping = true;
682
683 loop {
684 let bytes_read = match reader.read(&mut buf) {
685 Ok(0) => break,
686 Ok(n) => n,
687 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
688 Err(e) => return Err(e),
689 };
690
691 let chunk = &buf[..bytes_read];
692
693 if skipping {
694 for pos in memchr_iter(delimiter, chunk) {
695 count += 1;
696 if count == skip {
697 let start = pos + 1;
699 if start < chunk.len() {
700 out.write_all(&chunk[start..])?;
701 }
702 skipping = false;
703 break;
704 }
705 }
706 } else {
707 out.write_all(chunk)?;
708 }
709 }
710
711 Ok(true)
712}
713
714#[cfg(target_os = "linux")]
716fn sendfile_to_stdout_raw(in_fd: i32, file_size: u64, out_fd: i32) -> io::Result<bool> {
717 let mut offset: libc::off_t = 0;
718 let mut remaining = file_size;
719 while remaining > 0 {
720 let chunk = remaining.min(0x7fff_f000) as usize;
721 let ret = unsafe { libc::sendfile(out_fd, in_fd, &mut offset, chunk) };
722 if ret > 0 {
723 remaining -= ret as u64;
724 } else if ret == 0 {
725 break;
726 } else {
727 let err = io::Error::last_os_error();
728 if err.kind() == io::ErrorKind::Interrupted {
729 continue;
730 }
731 return Err(err);
732 }
733 }
734 Ok(true)
735}
736
737pub fn tail_file(
743 filename: &str,
744 config: &TailConfig,
745 out: &mut impl Write,
746 tool_name: &str,
747) -> io::Result<bool> {
748 let delimiter = if config.zero_terminated { b'\0' } else { b'\n' };
749
750 if filename != "-" {
751 let path = Path::new(filename);
752
753 match &config.mode {
754 TailMode::Lines(n) => {
755 #[cfg(target_os = "linux")]
757 let file = match open_noatime(path) {
758 Ok(f) => f,
759 Err(e) => {
760 eprintln!(
761 "{}: cannot open '{}' for reading: {}",
762 tool_name,
763 filename,
764 crate::common::io_error_msg(&e)
765 );
766 return Ok(false);
767 }
768 };
769 #[cfg(not(target_os = "linux"))]
770 let file = match std::fs::File::open(path) {
771 Ok(f) => f,
772 Err(e) => {
773 eprintln!(
774 "{}: cannot open '{}' for reading: {}",
775 tool_name,
776 filename,
777 crate::common::io_error_msg(&e)
778 );
779 return Ok(false);
780 }
781 };
782 let file_size = match file.metadata() {
783 Ok(m) => m.len(),
784 Err(e) => {
785 eprintln!(
786 "{}: error reading '{}': {}",
787 tool_name,
788 filename,
789 crate::common::io_error_msg(&e)
790 );
791 return Ok(false);
792 }
793 };
794 #[cfg(target_os = "linux")]
795 {
796 use std::os::unix::io::AsRawFd;
797 out.flush()?;
798 let stdout = io::stdout();
799 let out_fd = stdout.as_raw_fd();
800 match sendfile_tail_lines(file, file_size, *n, delimiter, out_fd) {
801 Ok(_) => return Ok(true),
802 Err(e) => {
803 eprintln!(
804 "{}: error reading '{}': {}",
805 tool_name,
806 filename,
807 crate::common::io_error_msg(&e)
808 );
809 return Ok(false);
810 }
811 }
812 }
813 #[cfg(not(target_os = "linux"))]
814 {
815 match tail_lines_streaming_file(file, file_size, *n, delimiter, out) {
816 Ok(_) => return Ok(true),
817 Err(e) => {
818 eprintln!(
819 "{}: error reading '{}': {}",
820 tool_name,
821 filename,
822 crate::common::io_error_msg(&e)
823 );
824 return Ok(false);
825 }
826 }
827 }
828 }
829 TailMode::LinesFrom(n) => {
830 out.flush()?;
831 #[cfg(target_os = "linux")]
832 let file = match open_noatime(path) {
833 Ok(f) => f,
834 Err(e) => {
835 eprintln!(
836 "{}: cannot open '{}' for reading: {}",
837 tool_name,
838 filename,
839 crate::common::io_error_msg(&e)
840 );
841 return Ok(false);
842 }
843 };
844 #[cfg(not(target_os = "linux"))]
845 let file = match std::fs::File::open(path) {
846 Ok(f) => f,
847 Err(e) => {
848 eprintln!(
849 "{}: cannot open '{}' for reading: {}",
850 tool_name,
851 filename,
852 crate::common::io_error_msg(&e)
853 );
854 return Ok(false);
855 }
856 };
857 match tail_lines_from_streaming_file(file, *n, delimiter, out) {
858 Ok(_) => return Ok(true),
859 Err(e) => {
860 eprintln!(
861 "{}: error reading '{}': {}",
862 tool_name,
863 filename,
864 crate::common::io_error_msg(&e)
865 );
866 return Ok(false);
867 }
868 }
869 }
870 TailMode::Bytes(_n) => {
871 #[cfg(target_os = "linux")]
872 {
873 use std::os::unix::io::AsRawFd;
874 out.flush()?;
875 let stdout = io::stdout();
876 let out_fd = stdout.as_raw_fd();
877 match sendfile_tail_bytes(path, *_n, out_fd) {
878 Ok(true) => return Ok(true),
879 Ok(false) => {}
880 Err(e) => {
881 eprintln!(
882 "{}: error reading '{}': {}",
883 tool_name,
884 filename,
885 crate::common::io_error_msg(&e)
886 );
887 return Ok(false);
888 }
889 }
890 }
891 }
892 TailMode::BytesFrom(_n) => {
893 #[cfg(target_os = "linux")]
894 {
895 use std::os::unix::io::AsRawFd;
896 out.flush()?;
897 let stdout = io::stdout();
898 let out_fd = stdout.as_raw_fd();
899 match sendfile_tail_bytes_from(path, *_n, out_fd) {
900 Ok(true) => return Ok(true),
901 Ok(false) => {}
902 Err(e) => {
903 eprintln!(
904 "{}: error reading '{}': {}",
905 tool_name,
906 filename,
907 crate::common::io_error_msg(&e)
908 );
909 return Ok(false);
910 }
911 }
912 }
913 }
914 }
915 }
916
917 let data: FileData = if filename == "-" {
919 match read_stdin() {
920 Ok(d) => FileData::Owned(d),
921 Err(e) => {
922 eprintln!(
923 "{}: standard input: {}",
924 tool_name,
925 crate::common::io_error_msg(&e)
926 );
927 return Ok(false);
928 }
929 }
930 } else {
931 match read_file(Path::new(filename)) {
932 Ok(d) => d,
933 Err(e) => {
934 eprintln!(
935 "{}: cannot open '{}' for reading: {}",
936 tool_name,
937 filename,
938 crate::common::io_error_msg(&e)
939 );
940 return Ok(false);
941 }
942 }
943 };
944
945 match &config.mode {
946 TailMode::Lines(n) => tail_lines(&data, *n, delimiter, out)?,
947 TailMode::LinesFrom(n) => tail_lines_from(&data, *n, delimiter, out)?,
948 TailMode::Bytes(n) => tail_bytes(&data, *n, out)?,
949 TailMode::BytesFrom(n) => tail_bytes_from(&data, *n, out)?,
950 }
951
952 Ok(true)
953}
954
955#[cfg(target_os = "linux")]
958fn sendfile_tail_bytes_from(path: &Path, n: u64, out_fd: i32) -> io::Result<bool> {
959 let file = open_noatime(path)?;
960
961 let metadata = file.metadata()?;
962 let file_size = metadata.len();
963
964 if file_size == 0 {
965 return Ok(true);
966 }
967
968 let start = if n <= 1 { 0 } else { (n - 1).min(file_size) };
969
970 if start >= file_size {
971 return Ok(true);
972 }
973
974 use std::os::unix::io::AsRawFd;
975 let in_fd = file.as_raw_fd();
976 let output_len = file_size - start;
977 let _ = unsafe {
978 libc::posix_fadvise(
979 in_fd,
980 start as libc::off_t,
981 output_len as libc::off_t,
982 libc::POSIX_FADV_SEQUENTIAL,
983 )
984 };
985 let mut offset: libc::off_t = start as libc::off_t;
986 let mut remaining = output_len;
987 let total = output_len;
988
989 while remaining > 0 {
990 let chunk = remaining.min(0x7fff_f000) as usize;
991 let ret = unsafe { libc::sendfile(out_fd, in_fd, &mut offset, chunk) };
992 if ret > 0 {
993 remaining -= ret as u64;
994 } else if ret == 0 {
995 break;
996 } else {
997 let err = io::Error::last_os_error();
998 if err.kind() == io::ErrorKind::Interrupted {
999 continue;
1000 }
1001 if err.raw_os_error() == Some(libc::EINVAL) && remaining == total {
1003 let mut file = file;
1004 file.seek(io::SeekFrom::Start(start))?;
1005 let mut buf = [0u8; 65536];
1006 loop {
1007 let nr = match file.read(&mut buf) {
1008 Ok(0) => break,
1009 Ok(nr) => nr,
1010 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
1011 Err(e) => return Err(e),
1012 };
1013 write_all_fd(out_fd, &buf[..nr])?;
1014 }
1015 return Ok(true);
1016 }
1017 return Err(err);
1018 }
1019 }
1020
1021 Ok(true)
1022}
1023
1024#[cfg(target_os = "linux")]
1026pub fn follow_file(filename: &str, config: &TailConfig, out: &mut impl Write) -> io::Result<()> {
1027 use std::thread;
1028 use std::time::Duration;
1029
1030 let sleep_duration = Duration::from_secs_f64(config.sleep_interval);
1031 let path = Path::new(filename);
1032
1033 let mut last_size = match std::fs::metadata(path) {
1034 Ok(m) => m.len(),
1035 Err(_) => 0,
1036 };
1037
1038 loop {
1039 if let Some(pid) = config.pid {
1041 if unsafe { libc::kill(pid as i32, 0) } != 0 {
1042 break;
1043 }
1044 }
1045
1046 thread::sleep(sleep_duration);
1047
1048 let current_size = match std::fs::metadata(path) {
1049 Ok(m) => m.len(),
1050 Err(_) => {
1051 if config.retry {
1052 continue;
1053 }
1054 break;
1055 }
1056 };
1057
1058 if current_size > last_size {
1059 let file = std::fs::File::open(path)?;
1061 use std::os::unix::io::AsRawFd;
1062 let in_fd = file.as_raw_fd();
1063 let stdout = io::stdout();
1064 let out_fd = stdout.as_raw_fd();
1065 let mut offset = last_size as libc::off_t;
1066 let mut remaining = current_size - last_size; while remaining > 0 {
1069 let chunk = remaining.min(0x7fff_f000) as usize;
1070 let ret = unsafe { libc::sendfile(out_fd, in_fd, &mut offset, chunk) };
1071 if ret > 0 {
1072 remaining -= ret as u64;
1073 } else if ret == 0 {
1074 break;
1075 } else {
1076 let err = io::Error::last_os_error();
1077 if err.kind() == io::ErrorKind::Interrupted {
1078 continue;
1079 }
1080 return Err(err);
1081 }
1082 }
1083 let _ = out.flush();
1084 last_size = current_size;
1085 } else if current_size < last_size {
1086 last_size = current_size;
1088 }
1089 }
1090
1091 Ok(())
1092}
1093
1094#[cfg(not(target_os = "linux"))]
1095pub fn follow_file(filename: &str, config: &TailConfig, out: &mut impl Write) -> io::Result<()> {
1096 use std::io::{Read, Seek};
1097 use std::thread;
1098 use std::time::Duration;
1099
1100 let sleep_duration = Duration::from_secs_f64(config.sleep_interval);
1101 let path = Path::new(filename);
1102
1103 let mut last_size = match std::fs::metadata(path) {
1104 Ok(m) => m.len(),
1105 Err(_) => 0,
1106 };
1107
1108 loop {
1109 thread::sleep(sleep_duration);
1110
1111 let current_size = match std::fs::metadata(path) {
1112 Ok(m) => m.len(),
1113 Err(_) => {
1114 if config.retry {
1115 continue;
1116 }
1117 break;
1118 }
1119 };
1120
1121 if current_size > last_size {
1122 let mut file = std::fs::File::open(path)?;
1123 file.seek(io::SeekFrom::Start(last_size))?;
1124 let mut buf = vec![0u8; (current_size - last_size) as usize];
1125 file.read_exact(&mut buf)?;
1126 out.write_all(&buf)?;
1127 out.flush()?;
1128 last_size = current_size;
1129 } else if current_size < last_size {
1130 last_size = current_size;
1131 }
1132 }
1133
1134 Ok(())
1135}