1use clap::{Arg, ArgAction, ArgMatches, Command};
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::{AsRawFd, FromRawFd};
16use thiserror::Error;
17use uucore::display::Quotable;
18use uucore::error::{FromIo, UError, UResult};
19use uucore::line_ending::LineEnding;
20use uucore::translate;
21use uucore::{format_usage, show};
22
23const BUF_SIZE: usize = 65536;
24
25mod options {
26 pub const BYTES: &str = "BYTES";
27 pub const LINES: &str = "LINES";
28 pub const QUIET: &str = "QUIET";
29 pub const VERBOSE: &str = "VERBOSE";
30 pub const ZERO: &str = "ZERO";
31 pub const FILES: &str = "FILE";
32 pub const PRESUME_INPUT_PIPE: &str = "-PRESUME-INPUT-PIPE";
33}
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.clone(), "err" => err))]
45 Io { name: String, 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
68pub fn uu_app() -> Command {
69 Command::new(uucore::util_name())
70 .version(uucore::crate_version!())
71 .help_template(uucore::localized_help_template(uucore::util_name()))
72 .about(translate!("head-about"))
73 .override_usage(format_usage(&translate!("head-usage")))
74 .infer_long_args(true)
75 .arg(
76 Arg::new(options::BYTES)
77 .short('c')
78 .long("bytes")
79 .value_name("[-]NUM")
80 .help(translate!("head-help-bytes"))
81 .overrides_with_all([options::BYTES, options::LINES])
82 .allow_hyphen_values(true),
83 )
84 .arg(
85 Arg::new(options::LINES)
86 .short('n')
87 .long("lines")
88 .value_name("[-]NUM")
89 .help(translate!("head-help-lines"))
90 .overrides_with_all([options::LINES, options::BYTES])
91 .allow_hyphen_values(true),
92 )
93 .arg(
94 Arg::new(options::QUIET)
95 .short('q')
96 .long("quiet")
97 .visible_alias("silent")
98 .help(translate!("head-help-quiet"))
99 .overrides_with_all([options::VERBOSE, options::QUIET])
100 .action(ArgAction::SetTrue),
101 )
102 .arg(
103 Arg::new(options::VERBOSE)
104 .short('v')
105 .long("verbose")
106 .help(translate!("head-help-verbose"))
107 .overrides_with_all([options::QUIET, options::VERBOSE])
108 .action(ArgAction::SetTrue),
109 )
110 .arg(
111 Arg::new(options::PRESUME_INPUT_PIPE)
112 .long("presume-input-pipe")
113 .alias("-presume-input-pipe")
114 .hide(true)
115 .action(ArgAction::SetTrue),
116 )
117 .arg(
118 Arg::new(options::ZERO)
119 .short('z')
120 .long("zero-terminated")
121 .help(translate!("head-help-zero-terminated"))
122 .overrides_with(options::ZERO)
123 .action(ArgAction::SetTrue),
124 )
125 .arg(
126 Arg::new(options::FILES)
127 .action(ArgAction::Append)
128 .value_parser(clap::value_parser!(OsString))
129 .value_hint(clap::ValueHint::FilePath),
130 )
131}
132
133#[derive(Debug, PartialEq)]
134enum Mode {
135 FirstLines(u64),
136 AllButLastLines(u64),
137 FirstBytes(u64),
138 AllButLastBytes(u64),
139}
140
141impl Default for Mode {
142 fn default() -> Self {
143 Self::FirstLines(10)
144 }
145}
146
147impl Mode {
148 fn from(matches: &ArgMatches) -> Result<Self, String> {
149 if let Some(v) = matches.get_one::<String>(options::BYTES) {
150 let (n, all_but_last) = parse::parse_num(v)
151 .map_err(|err| translate!("head-error-invalid-bytes", "err" => err))?;
152 if all_but_last {
153 Ok(Self::AllButLastBytes(n))
154 } else {
155 Ok(Self::FirstBytes(n))
156 }
157 } else if let Some(v) = matches.get_one::<String>(options::LINES) {
158 let (n, all_but_last) = parse::parse_num(v)
159 .map_err(|err| translate!("head-error-invalid-lines", "err" => err))?;
160 if all_but_last {
161 Ok(Self::AllButLastLines(n))
162 } else {
163 Ok(Self::FirstLines(n))
164 }
165 } else {
166 Ok(Self::default())
167 }
168 }
169}
170
171fn arg_iterate<'a>(
172 mut args: impl uucore::Args + 'a,
173) -> HeadResult<Box<dyn Iterator<Item = OsString> + 'a>> {
174 let first = args.next().unwrap();
176 if let Some(second) = args.next() {
177 if let Some(s) = second.to_str() {
178 if let Some(v) = parse::parse_obsolete(s) {
179 match v {
180 Ok(iter) => Ok(Box::new(vec![first].into_iter().chain(iter).chain(args))),
181 Err(parse::ParseError) => Err(HeadError::ParseError(
182 translate!("head-error-bad-argument-format", "arg" => s.quote()),
183 )),
184 }
185 } else {
186 Ok(Box::new(vec![first, second].into_iter().chain(args)))
189 }
190 } else {
191 Ok(Box::new(vec![first, second].into_iter().chain(args)))
194 }
195 } else {
196 Ok(Box::new(vec![first].into_iter()))
197 }
198}
199
200#[derive(Debug, PartialEq, Default)]
201struct HeadOptions {
202 pub quiet: bool,
203 pub verbose: bool,
204 pub line_ending: LineEnding,
205 pub presume_input_pipe: bool,
206 pub mode: Mode,
207 pub files: Vec<OsString>,
208}
209
210impl HeadOptions {
211 pub fn get_from(matches: &ArgMatches) -> Result<Self, String> {
213 let mut options = Self::default();
214
215 options.quiet = matches.get_flag(options::QUIET);
216 options.verbose = matches.get_flag(options::VERBOSE);
217 options.line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO));
218 options.presume_input_pipe = matches.get_flag(options::PRESUME_INPUT_PIPE);
219
220 options.mode = Mode::from(matches)?;
221
222 options.files = match matches.get_many::<OsString>(options::FILES) {
223 Some(v) => v.cloned().collect(),
224 None => vec![OsString::from("-")],
225 };
226
227 Ok(options)
228 }
229}
230
231#[inline]
232fn wrap_in_stdout_error(err: io::Error) -> io::Error {
233 io::Error::new(
234 err.kind(),
235 translate!("head-error-writing-stdout", "err" => err),
236 )
237}
238
239fn read_n_bytes(input: impl Read, n: u64) -> io::Result<u64> {
240 let mut reader = input.take(n);
242
243 let stdout = io::stdout();
245 let mut stdout = stdout.lock();
246
247 let bytes_written = io::copy(&mut reader, &mut stdout).map_err(wrap_in_stdout_error)?;
248
249 stdout.flush().map_err(wrap_in_stdout_error)?;
253
254 Ok(bytes_written)
255}
256
257fn read_n_lines(input: &mut impl io::BufRead, n: u64, separator: u8) -> io::Result<u64> {
258 let mut reader = take_lines(input, n, separator);
260
261 let stdout = io::stdout();
263 let stdout = stdout.lock();
264 let mut writer = BufWriter::with_capacity(BUF_SIZE, stdout);
265
266 let bytes_written = io::copy(&mut reader, &mut writer).map_err(wrap_in_stdout_error)?;
267
268 writer.flush().map_err(wrap_in_stdout_error)?;
272
273 Ok(bytes_written)
274}
275
276fn catch_too_large_numbers_in_backwards_bytes_or_lines(n: u64) -> Option<usize> {
277 usize::try_from(n).ok()
278}
279
280fn read_but_last_n_bytes(mut input: impl Read, n: u64) -> io::Result<u64> {
281 let mut bytes_written: u64 = 0;
282 if let Some(n) = catch_too_large_numbers_in_backwards_bytes_or_lines(n) {
283 let stdout = io::stdout();
284 let mut stdout = stdout.lock();
285
286 bytes_written = copy_all_but_n_bytes(&mut input, &mut stdout, n)
287 .map_err(wrap_in_stdout_error)?
288 .try_into()
289 .unwrap();
290
291 stdout.flush().map_err(wrap_in_stdout_error)?;
295 }
296 Ok(bytes_written)
297}
298
299fn read_but_last_n_lines(mut input: impl Read, n: u64, separator: u8) -> io::Result<u64> {
300 let stdout = io::stdout();
301 let mut stdout = stdout.lock();
302 if n == 0 {
303 return io::copy(&mut input, &mut stdout).map_err(wrap_in_stdout_error);
304 }
305 let mut bytes_written: u64 = 0;
306 if let Some(n) = catch_too_large_numbers_in_backwards_bytes_or_lines(n) {
307 bytes_written = copy_all_but_n_lines(input, &mut stdout, n, separator)
308 .map_err(wrap_in_stdout_error)?
309 .try_into()
310 .unwrap();
311 stdout.flush().map_err(wrap_in_stdout_error)?;
315 }
316 Ok(bytes_written)
317}
318
319fn find_nth_line_from_end<R>(input: &mut R, n: u64, separator: u8) -> io::Result<u64>
353where
354 R: Read + Seek,
355{
356 let file_size = input.seek(SeekFrom::End(0))?;
357
358 let mut buffer = [0u8; BUF_SIZE];
359
360 let mut lines = 0u64;
361 let mut check_last_byte_first_loop = true;
362 let mut bytes_remaining_to_search = file_size;
363
364 loop {
365 let bytes_to_read_this_loop =
367 bytes_remaining_to_search.min(buffer.len().try_into().unwrap());
368 let read_start_offset = bytes_remaining_to_search - bytes_to_read_this_loop;
369 let buffer = &mut buffer[..bytes_to_read_this_loop.try_into().unwrap()];
370 bytes_remaining_to_search -= bytes_to_read_this_loop;
371
372 input.seek(SeekFrom::Start(read_start_offset))?;
373 input.read_exact(buffer)?;
374
375 if check_last_byte_first_loop {
381 check_last_byte_first_loop = false;
382 if let Some(last_byte_of_file) = buffer.last() {
383 if last_byte_of_file != &separator {
384 if n == 0 {
385 input.rewind()?;
386 return Ok(file_size);
387 }
388 assert_eq!(lines, 0);
389 lines = 1;
390 }
391 }
392 }
393
394 for separator_offset in memrchr_iter(separator, &buffer[..]) {
395 lines += 1;
396 if lines == n + 1 {
397 input.rewind()?;
398 return Ok(read_start_offset
399 + TryInto::<u64>::try_into(separator_offset).unwrap()
400 + 1);
401 }
402 }
403 if read_start_offset == 0 {
404 input.rewind()?;
405 return Ok(0);
406 }
407 }
408}
409
410fn is_seekable(input: &mut File) -> bool {
411 let current_pos = input.stream_position();
412 current_pos.is_ok()
413 && input.seek(SeekFrom::End(0)).is_ok()
414 && input.seek(SeekFrom::Start(current_pos.unwrap())).is_ok()
415}
416
417fn head_backwards_file(input: &mut File, options: &HeadOptions) -> io::Result<u64> {
418 let st = input.metadata()?;
419 let seekable = is_seekable(input);
420 let blksize_limit = uucore::fs::sane_blksize::sane_blksize_from_metadata(&st);
421 if !seekable || st.len() <= blksize_limit || options.presume_input_pipe {
422 head_backwards_without_seek_file(input, options)
423 } else {
424 head_backwards_on_seekable_file(input, options)
425 }
426}
427
428fn head_backwards_without_seek_file(input: &mut File, options: &HeadOptions) -> io::Result<u64> {
429 match options.mode {
430 Mode::AllButLastBytes(n) => read_but_last_n_bytes(input, n),
431 Mode::AllButLastLines(n) => read_but_last_n_lines(input, n, options.line_ending.into()),
432 _ => unreachable!(),
433 }
434}
435
436fn head_backwards_on_seekable_file(input: &mut File, options: &HeadOptions) -> io::Result<u64> {
437 match options.mode {
438 Mode::AllButLastBytes(n) => {
439 let size = input.metadata()?.len();
440 if n >= size {
441 Ok(0)
442 } else {
443 read_n_bytes(input, size - n)
444 }
445 }
446 Mode::AllButLastLines(n) => {
447 let found = find_nth_line_from_end(input, n, options.line_ending.into())?;
448 read_n_bytes(input, found)
449 }
450 _ => unreachable!(),
451 }
452}
453
454fn head_file(input: &mut File, options: &HeadOptions) -> io::Result<u64> {
455 match options.mode {
456 Mode::FirstBytes(n) => read_n_bytes(input, n),
457 Mode::FirstLines(n) => read_n_lines(
458 &mut io::BufReader::with_capacity(BUF_SIZE, input),
459 n,
460 options.line_ending.into(),
461 ),
462 Mode::AllButLastBytes(_) | Mode::AllButLastLines(_) => head_backwards_file(input, options),
463 }
464}
465
466#[allow(clippy::cognitive_complexity)]
467fn uu_head(options: &HeadOptions) -> UResult<()> {
468 let mut first = true;
469 for file in &options.files {
470 let res = if file == "-" {
471 if (options.files.len() > 1 && !options.quiet) || options.verbose {
472 if !first {
473 println!();
474 }
475 println!("{}", translate!("head-header-stdin"));
476 }
477 let stdin = io::stdin();
478
479 #[cfg(unix)]
480 {
481 let stdin_raw_fd = stdin.as_raw_fd();
482 let mut stdin_file = unsafe { File::from_raw_fd(stdin_raw_fd) };
483 let current_pos = stdin_file.stream_position();
484 if let Ok(current_pos) = current_pos {
485 let bytes_read = head_file(&mut stdin_file, options)?;
490 stdin_file.seek(SeekFrom::Start(current_pos + bytes_read))?;
491 } else {
492 let _bytes_read = head_file(&mut stdin_file, options)?;
493 }
494 }
495
496 #[cfg(not(unix))]
497 {
498 let mut stdin = stdin.lock();
499
500 match options.mode {
501 Mode::FirstBytes(n) => read_n_bytes(&mut stdin, n),
502 Mode::AllButLastBytes(n) => read_but_last_n_bytes(&mut stdin, n),
503 Mode::FirstLines(n) => read_n_lines(&mut stdin, n, options.line_ending.into()),
504 Mode::AllButLastLines(n) => {
505 read_but_last_n_lines(&mut stdin, n, options.line_ending.into())
506 }
507 }?;
508 }
509
510 Ok(())
511 } else {
512 let mut file_handle = match File::open(file) {
513 Ok(f) => f,
514 Err(err) => {
515 show!(err.map_err_context(
516 || translate!("head-error-cannot-open", "name" => file.to_string_lossy().quote())
517 ));
518 continue;
519 }
520 };
521 if (options.files.len() > 1 && !options.quiet) || options.verbose {
522 if !first {
523 println!();
524 }
525 match file.to_str() {
526 Some(name) => println!("==> {name} <=="),
527 None => println!("==> {} <==", file.to_string_lossy()),
528 }
529 }
530 head_file(&mut file_handle, options)?;
531 Ok(())
532 };
533 if let Err(e) = res {
534 let name = if file == "-" {
535 "standard input".to_string()
536 } else {
537 file.to_string_lossy().into_owned()
538 };
539 return Err(HeadError::Io {
540 name: name.to_string(),
541 err: e,
542 }
543 .into());
544 }
545 first = false;
546 }
547 Ok(())
552}
553
554#[uucore::main]
555pub fn uumain(args: impl uucore::Args) -> UResult<()> {
556 let args: Vec<_> = arg_iterate(args)?.collect();
557 let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?;
558 let options = HeadOptions::get_from(&matches).map_err(HeadError::MatchOption)?;
559 uu_head(&options)
560}
561
562#[cfg(test)]
563mod tests {
564 use io::Cursor;
565 use std::ffi::OsString;
566
567 use super::*;
568
569 fn options(args: &str) -> Result<HeadOptions, String> {
570 let combined = "head ".to_owned() + args;
571 let args = combined.split_whitespace().map(OsString::from);
572 let matches = uu_app()
573 .get_matches_from(arg_iterate(args).map_err(|_| String::from("Arg iterate failed"))?);
574 HeadOptions::get_from(&matches)
575 }
576
577 #[test]
578 fn test_args_modes() {
579 let args = options("-n -10M -vz").unwrap();
580 assert_eq!(args.line_ending, LineEnding::Nul);
581 assert!(args.verbose);
582 assert_eq!(args.mode, Mode::AllButLastLines(10 * 1024 * 1024));
583 }
584
585 #[test]
586 fn test_gnu_compatibility() {
587 let args = options("-n 1 -c 1 -n 5 -c kiB -vqvqv").unwrap(); assert_eq!(args.mode, Mode::FirstBytes(1024));
589 assert!(args.verbose);
590 assert_eq!(options("-5").unwrap().mode, Mode::FirstLines(5));
591 assert_eq!(options("-2b").unwrap().mode, Mode::FirstBytes(1024));
592 assert_eq!(options("-5 -c 1").unwrap().mode, Mode::FirstBytes(1));
593 }
594
595 #[test]
596 #[allow(clippy::cognitive_complexity)]
597 fn all_args_test() {
598 assert!(options("--silent").unwrap().quiet);
599 assert!(options("--quiet").unwrap().quiet);
600 assert!(options("-q").unwrap().quiet);
601 assert!(options("--verbose").unwrap().verbose);
602 assert!(options("-v").unwrap().verbose);
603 assert_eq!(
604 options("--zero-terminated").unwrap().line_ending,
605 LineEnding::Nul
606 );
607 assert_eq!(options("-z").unwrap().line_ending, LineEnding::Nul);
608 assert_eq!(options("--lines 15").unwrap().mode, Mode::FirstLines(15));
609 assert_eq!(options("-n 15").unwrap().mode, Mode::FirstLines(15));
610 assert_eq!(options("--bytes 15").unwrap().mode, Mode::FirstBytes(15));
611 assert_eq!(options("-c 15").unwrap().mode, Mode::FirstBytes(15));
612 }
613
614 #[test]
615 fn test_options_errors() {
616 assert!(options("-n IsThisTheRealLife?").is_err());
617 assert!(options("-c IsThisJustFantasy").is_err());
618 }
619
620 #[test]
621 fn test_options_correct_defaults() {
622 let opts = HeadOptions::default();
623
624 assert!(!opts.verbose);
625 assert!(!opts.quiet);
626 assert_eq!(opts.line_ending, LineEnding::Newline);
627 assert_eq!(opts.mode, Mode::FirstLines(10));
628 assert!(opts.files.is_empty());
629 }
630
631 fn arg_outputs(src: &str) -> Result<String, ()> {
632 let split = src.split_whitespace().map(OsString::from);
633 match arg_iterate(split) {
634 Ok(args) => {
635 let vec = args
636 .map(|s| s.to_str().unwrap().to_owned())
637 .collect::<Vec<_>>();
638 Ok(vec.join(" "))
639 }
640 Err(_) => Err(()),
641 }
642 }
643
644 #[test]
645 fn test_arg_iterate() {
646 assert_eq!(
648 arg_outputs("head -n -5 -zv"),
649 Ok("head -n -5 -zv".to_owned())
650 );
651 assert_eq!(
653 arg_outputs("head -to_be_or_not_to_be,..."),
654 Ok("head -to_be_or_not_to_be,...".to_owned())
655 );
656 assert_eq!(
658 arg_outputs("head -123qvqvqzc"), Ok("head -q -z -c 123".to_owned())
660 );
661 assert!(arg_outputs("head -123FooBar").is_err());
663 assert!(arg_outputs("head -100000000000000000000000000000000000000000").is_ok());
665 assert_eq!(arg_outputs("head"), Ok("head".to_owned()));
667 }
668
669 #[test]
670 #[cfg(target_os = "linux")]
671 fn test_arg_iterate_bad_encoding() {
672 use std::os::unix::ffi::OsStringExt;
673 let invalid = OsString::from_vec(vec![b'\x80', b'\x81']);
674 assert!(arg_iterate(vec![OsString::from("head"), invalid].into_iter()).is_ok());
676 }
677
678 #[test]
679 fn read_early_exit() {
680 let mut empty = io::BufReader::new(Cursor::new(Vec::new()));
681 assert!(read_n_bytes(&mut empty, 0).is_ok());
682 assert!(read_n_lines(&mut empty, 0, b'\n').is_ok());
683 }
684
685 #[test]
686 fn test_find_nth_line_from_end() {
687 let minimum_buffer_size = BUF_SIZE * 4;
700 let mut input_buffer = vec![];
701 let mut loop_iteration: u64 = 0;
702 while input_buffer.len() < minimum_buffer_size {
703 for _n in 0..4 {
704 input_buffer.push(b'a');
705 }
706 loop_iteration += 1;
707 input_buffer.push(b'\n');
708 }
709
710 let lines_in_input_file = loop_iteration;
711 let input_length = lines_in_input_file * 5;
712 assert_eq!(input_length, input_buffer.len().try_into().unwrap());
713 let mut input = Cursor::new(input_buffer);
714 let step_size = 511;
719 for n in (0..lines_in_input_file).filter(|v| v % step_size == 0) {
720 assert_eq!(
722 find_nth_line_from_end(&mut input, n, b'\n').unwrap(),
723 input_length - 5 * n
724 );
725 }
726
727 assert_eq!(
730 find_nth_line_from_end(&mut input, lines_in_input_file, b'\n').unwrap(),
731 0
732 );
733 assert_eq!(
734 find_nth_line_from_end(&mut input, lines_in_input_file + 1, b'\n').unwrap(),
735 0
736 );
737 assert_eq!(
738 find_nth_line_from_end(&mut input, lines_in_input_file + 1000, b'\n').unwrap(),
739 0
740 );
741 }
742
743 #[test]
744 fn test_find_nth_line_from_end_non_terminated() {
745 let input_file = "a\nb";
748 let mut input = Cursor::new(input_file);
749 assert_eq!(find_nth_line_from_end(&mut input, 0, b'\n').unwrap(), 3);
750 assert_eq!(find_nth_line_from_end(&mut input, 1, b'\n').unwrap(), 2);
751 }
752
753 #[test]
754 fn test_find_nth_line_from_end_empty() {
755 let input_file = "";
757 let mut input = Cursor::new(input_file);
758 assert_eq!(find_nth_line_from_end(&mut input, 0, b'\n').unwrap(), 0);
759 assert_eq!(find_nth_line_from_end(&mut input, 1, b'\n').unwrap(), 0);
760 }
761}