1use clap::ArgMatches;
9use memchr::memrchr_iter;
10use std::ffi::OsString;
11use std::fs::File;
12use std::io::{self, BufWriter, Read, Seek, SeekFrom, Write};
13use std::num::TryFromIntError;
14#[cfg(unix)]
15use std::os::fd::AsFd;
16#[cfg(windows)]
17use std::path::Path;
18use std::path::PathBuf;
19use thiserror::Error;
20use uucore::diagnostics::OptionValue;
21use uucore::display::{Quotable, print_verbatim};
22use uucore::error::{FromIo, UError, UResult, USimpleError};
23use uucore::line_ending::LineEnding;
24use uucore::parser::parse_signed_num::number_offset;
25use uucore::parser::parse_size::ParseSizeError;
26use uucore::show;
27use uucore::translate;
28
29const BUF_SIZE: usize = 65536;
30
31mod cli;
32use crate::cli::options;
33pub use crate::cli::uu_app;
34
35mod parse;
36mod take;
37use take::copy_all_but_n_bytes;
38use take::copy_all_but_n_lines;
39use take::take_lines;
40
41#[derive(Error, Debug)]
42enum HeadError {
43 #[error("{}", translate!("head-error-reading-file", "name" => name.quote(), "err" => err))]
45 Io { name: PathBuf, err: io::Error },
46
47 #[error("{}", translate!("head-error-parse-error", "err" => 0))]
48 ParseError(String),
49
50 #[error("{}", translate!("head-error-num-too-large"))]
51 NumTooLarge(#[from] TryFromIntError),
52
53 #[error("{}", translate!("head-error-clap", "err" => 0))]
54 Clap(#[from] clap::Error),
55
56 #[error("{0}")]
57 MatchOption(String),
58}
59
60impl UError for HeadError {
61 fn code(&self) -> i32 {
62 1
63 }
64}
65
66type HeadResult<T> = Result<T, HeadError>;
67
68#[derive(Debug, PartialEq)]
69enum Mode {
70 FirstLines(u64),
71 AllButLastLines(u64),
72 FirstBytes(u64),
73 AllButLastBytes(u64),
74}
75
76impl Default for Mode {
77 fn default() -> Self {
78 Self::FirstLines(10)
79 }
80}
81
82pub struct SizeError {
88 pub message: String,
89 option: OptionValue,
90 error: ParseSizeError,
91}
92
93impl SizeError {
94 fn into_error(self, diag_args: Option<&[OsString]>) -> Box<dyn UError> {
97 self.error.size_value_error(
98 diag_args,
99 &self.option,
100 number_offset(&self.option.value),
102 &self.message,
103 HeadError::MatchOption(self.message.clone()),
104 )
105 }
106}
107
108impl Mode {
109 fn from(matches: &ArgMatches) -> Result<Self, SizeError> {
110 fn failed(
111 value: &str,
112 short: char,
113 long: &'static str,
114 key: &'static str,
115 ) -> impl FnOnce(ParseSizeError) -> SizeError {
116 let option = OptionValue::new(value, short, long);
117 move |error| SizeError {
118 message: translate!(key, "err" => &error),
119 option,
120 error,
121 }
122 }
123
124 if let Some(v) = matches.get_one::<String>(options::BYTES) {
125 let (n, all_but_last) =
126 parse::parse_num(v).map_err(failed(v, 'c', "bytes", "head-error-invalid-bytes"))?;
127 if all_but_last {
128 Ok(Self::AllButLastBytes(n))
129 } else {
130 Ok(Self::FirstBytes(n))
131 }
132 } else if let Some(v) = matches.get_one::<String>(options::LINES) {
133 let (n, all_but_last) =
134 parse::parse_num(v).map_err(failed(v, 'n', "lines", "head-error-invalid-lines"))?;
135 if all_but_last {
136 Ok(Self::AllButLastLines(n))
137 } else {
138 Ok(Self::FirstLines(n))
139 }
140 } else {
141 Ok(Self::default())
142 }
143 }
144}
145
146fn arg_iterate<'a>(
147 mut args: impl uucore::Args + 'a,
148) -> HeadResult<Box<dyn Iterator<Item = OsString> + 'a>> {
149 let first = args.next().unwrap();
151 if let Some(second) = args.next() {
152 if let Some(s) = second.to_str() {
153 if let Some(v) = parse::parse_obsolete(s) {
154 match v {
155 Ok(iter) => Ok(Box::new(vec![first].into_iter().chain(iter).chain(args))),
156 Err(parse::ParseError) => Err(HeadError::ParseError(
157 translate!("head-error-bad-argument-format", "arg" => s.quote()),
158 )),
159 }
160 } else {
161 Ok(Box::new(vec![first, second].into_iter().chain(args)))
164 }
165 } else {
166 Ok(Box::new(vec![first, second].into_iter().chain(args)))
169 }
170 } else {
171 Ok(Box::new(vec![first].into_iter()))
172 }
173}
174
175#[derive(Debug, PartialEq, Default)]
176struct HeadOptions {
177 pub quiet: bool,
178 pub verbose: bool,
179 pub line_ending: LineEnding,
180 pub presume_input_pipe: bool,
181 pub mode: Mode,
182 pub files: Vec<OsString>,
183}
184
185impl HeadOptions {
186 pub fn get_from(matches: &ArgMatches) -> Result<Self, SizeError> {
188 let mut options = Self::default();
189
190 options.quiet = matches.get_flag(options::QUIET);
191 options.verbose = matches.get_flag(options::VERBOSE);
192 options.line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO));
193 options.presume_input_pipe = matches.get_flag(options::PRESUME_INPUT_PIPE);
194
195 options.mode = Mode::from(matches)?;
196 options.files = matches
198 .get_many::<OsString>(options::FILES)
199 .unwrap()
200 .cloned()
201 .collect();
202
203 Ok(options)
204 }
205}
206
207#[inline]
208fn wrap_in_stdout_error(err: io::Error) -> io::Error {
209 io::Error::new(
210 err.kind(),
211 translate!("head-error-writing-stdout", "err" => uucore::error::strip_errno(&err)),
212 )
213}
214
215#[cfg(any(target_os = "linux", target_os = "android"))]
217fn print_n_bytes(input: impl AsFd, n: u64) -> io::Result<u64> {
218 let out = io::stdout();
219 uucore::pipes::send_n_bytes(input, &out, n).map_err(wrap_in_stdout_error)
220}
221
222#[cfg(not(any(target_os = "linux", target_os = "android")))]
223fn print_n_bytes(input: impl Read, n: u64) -> io::Result<u64> {
224 let mut reader = input.take(n);
226
227 let stdout = io::stdout();
229 let mut stdout = stdout.lock();
230
231 let bytes_written = io::copy(&mut reader, &mut stdout).map_err(wrap_in_stdout_error)?;
232
233 stdout.flush().map_err(wrap_in_stdout_error)?;
235
236 Ok(bytes_written)
237}
238
239fn print_n_lines(input: &mut impl io::BufRead, n: u64, separator: u8) -> io::Result<u64> {
240 let mut reader = take_lines(input, n, separator);
242
243 let stdout = io::stdout();
245 let stdout = stdout.lock();
246 let mut writer = BufWriter::with_capacity(BUF_SIZE, stdout);
247
248 let bytes_written = io::copy(&mut reader, &mut writer).map_err(wrap_in_stdout_error)?;
249
250 writer.flush().map_err(wrap_in_stdout_error)?;
254
255 Ok(bytes_written)
256}
257
258fn catch_too_large_numbers_in_backwards_bytes_or_lines(n: u64) -> Option<usize> {
259 usize::try_from(n).ok()
260}
261
262fn print_but_last_n_bytes(mut input: impl Read, n: u64) -> io::Result<u64> {
263 let mut bytes_written: u64 = 0;
264 if let Some(n) = catch_too_large_numbers_in_backwards_bytes_or_lines(n) {
265 let stdout = io::stdout();
266 let mut stdout = stdout.lock();
267
268 bytes_written = copy_all_but_n_bytes(&mut input, &mut stdout, n)
269 .map_err(wrap_in_stdout_error)?
270 .try_into()
271 .unwrap();
272
273 stdout.flush().map_err(wrap_in_stdout_error)?;
277 }
278 Ok(bytes_written)
279}
280
281fn print_but_last_n_lines(mut input: impl Read, n: u64, separator: u8) -> io::Result<u64> {
282 let stdout = io::stdout();
283 let mut stdout = stdout.lock();
284 if n == 0 {
285 return io::copy(&mut input, &mut stdout).map_err(wrap_in_stdout_error);
286 }
287 let mut bytes_written: u64 = 0;
288 if let Some(n) = catch_too_large_numbers_in_backwards_bytes_or_lines(n) {
289 bytes_written = copy_all_but_n_lines(input, &mut stdout, n, separator)
290 .map_err(wrap_in_stdout_error)?
291 .try_into()
292 .unwrap();
293 stdout.flush().map_err(wrap_in_stdout_error)?;
297 }
298 Ok(bytes_written)
299}
300
301fn find_nth_line_from_end<R>(input: &mut R, n: u64, separator: u8) -> io::Result<u64>
335where
336 R: Read + Seek,
337{
338 let file_size = input.seek(SeekFrom::End(0))?;
339
340 let mut buffer = [0u8; BUF_SIZE];
341
342 let mut lines = 0u64;
343 let mut check_last_byte_first_loop = true;
344 let mut bytes_remaining_to_search = file_size;
345
346 loop {
347 let bytes_to_read_this_loop =
349 bytes_remaining_to_search.min(buffer.len().try_into().unwrap());
350 let read_start_offset = bytes_remaining_to_search - bytes_to_read_this_loop;
351 let buffer = &mut buffer[..bytes_to_read_this_loop.try_into().unwrap()];
352 bytes_remaining_to_search -= bytes_to_read_this_loop;
353
354 input.seek(SeekFrom::Start(read_start_offset))?;
355 input.read_exact(buffer)?;
356
357 if check_last_byte_first_loop {
363 check_last_byte_first_loop = false;
364 if buffer.last().is_some_and(|&b| b != separator) {
365 if n == 0 {
366 input.rewind()?;
367 return Ok(file_size);
368 }
369 assert_eq!(lines, 0);
370 lines = 1;
371 }
372 }
373
374 for separator_offset in memrchr_iter(separator, &buffer[..]) {
375 lines += 1;
376 if lines > n {
377 input.rewind()?;
378 return Ok(read_start_offset
379 + TryInto::<u64>::try_into(separator_offset).unwrap()
380 + 1);
381 }
382 }
383 if read_start_offset == 0 {
384 input.rewind()?;
385 return Ok(0);
386 }
387 }
388}
389
390fn is_seekable(input: &mut File) -> bool {
391 let current_pos = input.stream_position();
392 current_pos.is_ok()
393 && input.seek(SeekFrom::End(0)).is_ok()
394 && input.seek(SeekFrom::Start(current_pos.unwrap())).is_ok()
395}
396
397fn head_backwards_file(input: &mut File, options: &HeadOptions) -> io::Result<u64> {
398 let st = input.metadata()?;
399 let seekable = is_seekable(input);
400 let blksize_limit = uucore::fs::sane_blksize::sane_blksize_from_metadata(&st);
401 if !seekable || st.len() <= blksize_limit || options.presume_input_pipe {
402 head_backwards_without_seek_file(input, options)
403 } else {
404 head_backwards_on_seekable_file(input, options)
405 }
406}
407
408fn head_backwards_without_seek_file(input: &mut File, options: &HeadOptions) -> io::Result<u64> {
409 match options.mode {
410 Mode::AllButLastBytes(n) => print_but_last_n_bytes(input, n),
411 Mode::AllButLastLines(n) => print_but_last_n_lines(input, n, options.line_ending.into()),
412 _ => unreachable!(),
413 }
414}
415
416fn head_backwards_on_seekable_file(input: &mut File, options: &HeadOptions) -> io::Result<u64> {
417 match options.mode {
418 Mode::AllButLastBytes(n) => {
419 let size = input.metadata()?.len();
420 if n >= size {
421 Ok(0)
422 } else {
423 print_n_bytes(input, size - n)
424 }
425 }
426 Mode::AllButLastLines(n) => {
427 let found = find_nth_line_from_end(input, n, options.line_ending.into())?;
428 print_n_bytes(input, found)
429 }
430 _ => unreachable!(),
431 }
432}
433
434fn head_file(input: &mut File, options: &HeadOptions) -> io::Result<u64> {
435 match options.mode {
436 Mode::FirstBytes(n) => print_n_bytes(input, n),
437 Mode::FirstLines(n) => print_n_lines(
438 &mut io::BufReader::with_capacity(BUF_SIZE, input),
439 n,
440 options.line_ending.into(),
441 ),
442 Mode::AllButLastBytes(_) | Mode::AllButLastLines(_) => head_backwards_file(input, options),
443 }
444}
445
446#[allow(clippy::cognitive_complexity)]
447fn uu_head(options: &HeadOptions) -> UResult<()> {
448 let mut stdout = io::stdout().lock();
449 let mut first = true;
450 for file in &options.files {
451 let res = if file == "-" {
452 if (options.files.len() > 1 && !options.quiet) || options.verbose {
453 if !first {
454 writeln!(stdout)?;
455 }
456 writeln!(stdout, "{}", translate!("head-header-stdin"))?;
457 }
458 let stdin = io::stdin();
459
460 #[cfg(unix)]
461 {
462 let stdin_owned_fd = stdin.as_fd().try_clone_to_owned()?;
463 let mut stdin_file = File::from(stdin_owned_fd);
464 let current_pos = stdin_file.stream_position();
465 if let Ok(current_pos) = current_pos {
466 let bytes_read = head_file(&mut stdin_file, options)?;
471 stdin_file.seek(SeekFrom::Start(current_pos + bytes_read))?;
472 } else {
473 let _bytes_read = head_file(&mut stdin_file, options)?;
474 }
475 }
476
477 #[cfg(not(unix))]
478 {
479 let mut stdin = stdin.lock();
480
481 match options.mode {
482 Mode::FirstBytes(n) => print_n_bytes(&mut stdin, n),
483 Mode::AllButLastBytes(n) => print_but_last_n_bytes(&mut stdin, n),
484 Mode::FirstLines(n) => print_n_lines(&mut stdin, n, options.line_ending.into()),
485 Mode::AllButLastLines(n) => {
486 print_but_last_n_lines(&mut stdin, n, options.line_ending.into())
487 }
488 }?;
489 }
490
491 Ok(())
492 } else {
493 let zero_output = matches!(options.mode, Mode::FirstBytes(0) | Mode::FirstLines(0));
497
498 let mut print_header = || -> UResult<()> {
502 if (options.files.len() > 1 && !options.quiet) || options.verbose {
503 if !first {
504 writeln!(stdout)?;
505 }
506 write!(stdout, "==> ")?;
507 print_verbatim(file)?;
508 writeln!(stdout, " <==")?;
509 first = false;
510 }
511 Ok(())
512 };
513
514 let mut file_handle = match File::open(file) {
515 Ok(f) => f,
516 Err(err) => {
517 #[cfg(windows)]
518 if err.kind() == io::ErrorKind::PermissionDenied
520 && Path::new(file).metadata().is_ok_and(|m| m.is_dir())
521 {
522 print_header()?;
524 if !zero_output {
525 show!(USimpleError::new(
526 1,
527 translate!("head-error-reading-file", "name" => file.quote(), "err" => "Is a directory")
528 ));
529 }
530 continue;
531 }
532
533 show!(err.map_err_context(
534 || translate!("head-error-cannot-open", "name" => file.quote())
535 ));
536 continue;
537 }
538 };
539
540 let metadata = match file_handle.metadata() {
541 Ok(m) => m,
542 Err(err) => {
543 show!(err.map_err_context(
544 || translate!("head-error-cannot-open", "name" => file.quote())
545 ));
546 continue;
547 }
548 };
549
550 print_header()?;
551 if metadata.is_dir() {
552 if !zero_output {
553 show!(USimpleError::new(
554 1,
555 translate!("head-error-reading-file", "name" => file.quote(), "err" => "Is a directory")
556 ));
557 }
558 continue;
559 }
560 head_file(&mut file_handle, options)?;
561 Ok(())
562 };
563 if let Err(err) = res {
564 let name = if file == "-" {
565 "standard input".into()
566 } else {
567 file.into()
568 };
569 return Err(HeadError::Io { name, err }.into());
570 }
571 first = false;
572 }
573 Ok(())
578}
579
580#[uucore::main]
581pub fn uumain(args: impl uucore::Args) -> UResult<()> {
582 let raw_args: Vec<_> = args.collect();
583 let diag_args = uucore::diagnostics::capture(&raw_args);
585 let args: Vec<_> = arg_iterate(raw_args.into_iter())?.collect();
586 let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?;
587 let options =
588 HeadOptions::get_from(&matches).map_err(|e| e.into_error(diag_args.as_deref()))?;
589 uu_head(&options)
590}
591
592#[cfg(test)]
593mod tests {
594 use io::Cursor;
595 use std::ffi::OsString;
596
597 use super::*;
598
599 fn options(args: &str) -> Result<HeadOptions, String> {
600 let combined = "head ".to_owned() + args;
602 let args = combined.split_whitespace().map(OsString::from);
603 let matches = uu_app()
604 .get_matches_from(arg_iterate(args).map_err(|_| String::from("Arg iterate failed"))?);
605 HeadOptions::get_from(&matches).map_err(|e| e.message)
606 }
607
608 #[test]
609 fn test_args_modes() {
610 let args = options("-n -10M -vz").unwrap();
611 assert_eq!(args.line_ending, LineEnding::Nul);
612 assert!(args.verbose);
613 assert_eq!(args.mode, Mode::AllButLastLines(10 * 1024 * 1024));
614 }
615
616 #[test]
617 fn test_gnu_compatibility() {
618 let args = options("-n 1 -c 1 -n 5 -c kiB -vqvqv").unwrap(); assert_eq!(args.mode, Mode::FirstBytes(1024));
620 assert!(args.verbose);
621 assert_eq!(options("-5").unwrap().mode, Mode::FirstLines(5));
622 assert_eq!(options("-2b").unwrap().mode, Mode::FirstBytes(1024));
623 assert_eq!(options("-5 -c 1").unwrap().mode, Mode::FirstBytes(1));
624 }
625
626 #[test]
627 #[allow(clippy::cognitive_complexity)]
628 fn all_args_test() {
629 assert!(options("--silent").unwrap().quiet);
630 assert!(options("--quiet").unwrap().quiet);
631 assert!(options("-q").unwrap().quiet);
632 assert!(options("--verbose").unwrap().verbose);
633 assert!(options("-v").unwrap().verbose);
634 assert_eq!(
635 options("--zero-terminated").unwrap().line_ending,
636 LineEnding::Nul
637 );
638 assert_eq!(options("-z").unwrap().line_ending, LineEnding::Nul);
639 assert_eq!(options("--lines 15").unwrap().mode, Mode::FirstLines(15));
640 assert_eq!(options("-n 15").unwrap().mode, Mode::FirstLines(15));
641 assert_eq!(options("--bytes 15").unwrap().mode, Mode::FirstBytes(15));
642 assert_eq!(options("-c 15").unwrap().mode, Mode::FirstBytes(15));
643 }
644
645 #[test]
646 fn test_options_errors() {
647 assert!(options("-n IsThisTheRealLife?").is_err());
648 assert!(options("-c IsThisJustFantasy").is_err());
649 }
650
651 #[test]
652 fn test_options_correct_defaults() {
653 let opts = HeadOptions::default();
654
655 assert!(!opts.verbose);
656 assert!(!opts.quiet);
657 assert_eq!(opts.line_ending, LineEnding::Newline);
658 assert_eq!(opts.mode, Mode::FirstLines(10));
659 assert!(opts.files.is_empty());
660 }
661
662 fn arg_outputs(src: &str) -> Result<String, ()> {
663 let split = src.split_whitespace().map(OsString::from);
664 match arg_iterate(split) {
665 Ok(args) => {
666 let vec = args
667 .map(|s| s.to_str().unwrap().to_owned())
668 .collect::<Vec<_>>();
669 Ok(vec.join(" "))
670 }
671 Err(_) => Err(()),
672 }
673 }
674
675 #[test]
676 fn test_arg_iterate() {
677 assert_eq!(
679 arg_outputs("head -n -5 -zv"),
680 Ok("head -n -5 -zv".to_owned())
681 );
682 assert_eq!(
684 arg_outputs("head -to_be_or_not_to_be,..."),
685 Ok("head -to_be_or_not_to_be,...".to_owned())
686 );
687 assert_eq!(
689 arg_outputs("head -123qvqvqzc"), Ok("head -q -z -c 123".to_owned())
691 );
692 assert!(arg_outputs("head -123FooBar").is_err());
694 assert!(arg_outputs("head -100000000000000000000000000000000000000000").is_ok());
696 assert_eq!(arg_outputs("head"), Ok("head".to_owned()));
698 }
699
700 #[test]
701 #[cfg(target_os = "linux")]
702 fn test_arg_iterate_bad_encoding() {
703 use std::os::unix::ffi::OsStringExt;
704 let invalid = OsString::from_vec(vec![b'\x80', b'\x81']);
705 assert!(arg_iterate(vec![OsString::from("head"), invalid].into_iter()).is_ok());
707 }
708
709 #[test]
710 #[cfg(not(any(target_os = "linux", target_os = "android")))] fn read_early_exit() {
712 let mut empty = io::BufReader::new(Cursor::new(Vec::new()));
713 assert!(print_n_bytes(&mut empty, 0).is_ok());
714 assert!(print_n_lines(&mut empty, 0, b'\n').is_ok());
715 }
716
717 #[test]
718 fn test_find_nth_line_from_end() {
719 let minimum_buffer_size = BUF_SIZE * 4;
732 let mut input_buffer = vec![];
733 let mut loop_iteration: u64 = 0;
734 while input_buffer.len() < minimum_buffer_size {
735 for _n in 0..4 {
736 input_buffer.push(b'a');
737 }
738 loop_iteration += 1;
739 input_buffer.push(b'\n');
740 }
741
742 let lines_in_input_file = loop_iteration;
743 let input_length = lines_in_input_file * 5;
744 assert_eq!(input_length, input_buffer.len().try_into().unwrap());
745 let mut input = Cursor::new(input_buffer);
746 let step_size = 511;
751 for n in (0..lines_in_input_file).filter(|v| v % step_size == 0) {
752 assert_eq!(
754 find_nth_line_from_end(&mut input, n, b'\n').unwrap(),
755 input_length - 5 * n
756 );
757 }
758
759 assert_eq!(
762 find_nth_line_from_end(&mut input, lines_in_input_file, b'\n').unwrap(),
763 0
764 );
765 assert_eq!(
766 find_nth_line_from_end(&mut input, lines_in_input_file + 1, b'\n').unwrap(),
767 0
768 );
769 assert_eq!(
770 find_nth_line_from_end(&mut input, lines_in_input_file + 1000, b'\n').unwrap(),
771 0
772 );
773 }
774
775 #[test]
776 fn test_find_nth_line_from_end_non_terminated() {
777 let input_file = "a\nb";
780 let mut input = Cursor::new(input_file);
781 assert_eq!(find_nth_line_from_end(&mut input, 0, b'\n').unwrap(), 3);
782 assert_eq!(find_nth_line_from_end(&mut input, 1, b'\n').unwrap(), 2);
783 }
784
785 #[test]
786 fn test_find_nth_line_from_end_empty() {
787 let input_file = "";
789 let mut input = Cursor::new(input_file);
790 assert_eq!(find_nth_line_from_end(&mut input, 0, b'\n').unwrap(), 0);
791 assert_eq!(find_nth_line_from_end(&mut input, 1, b'\n').unwrap(), 0);
792 }
793}