1use std::io::{self, Read, Write};
2use std::path::Path;
3
4use memchr::{memchr_iter, memrchr_iter};
5
6use crate::common::io::{FileData, read_file, read_stdin};
7
8#[derive(Clone, Debug)]
10pub enum HeadMode {
11 Lines(u64),
13 LinesFromEnd(u64),
15 Bytes(u64),
17 BytesFromEnd(u64),
19}
20
21#[derive(Clone, Debug)]
23pub struct HeadConfig {
24 pub mode: HeadMode,
25 pub zero_terminated: bool,
26}
27
28impl Default for HeadConfig {
29 fn default() -> Self {
30 Self {
31 mode: HeadMode::Lines(10),
32 zero_terminated: false,
33 }
34 }
35}
36
37pub fn parse_size(s: &str) -> Result<u64, String> {
41 let s = s.trim();
42 if s.is_empty() {
43 return Err("empty size".to_string());
44 }
45
46 let mut num_end = 0;
48 for (i, c) in s.char_indices() {
49 if c.is_ascii_digit() || (i == 0 && (c == '+' || c == '-')) {
50 num_end = i + c.len_utf8();
51 } else {
52 break;
53 }
54 }
55
56 if num_end == 0 {
57 return Err(format!("invalid number: '{}'", s));
58 }
59
60 let num_str = &s[..num_end];
61 let suffix = &s[num_end..];
62
63 let num: u64 = match num_str.parse() {
64 Ok(n) => n,
65 Err(_) => {
66 let digits = num_str
69 .strip_prefix('+')
70 .or_else(|| num_str.strip_prefix('-'))
71 .unwrap_or(num_str);
72 if !digits.is_empty() && digits.chars().all(|c| c.is_ascii_digit()) {
73 u64::MAX
74 } else {
75 return Err(format!("invalid number: '{}'", num_str));
76 }
77 }
78 };
79
80 let multiplier: u64 = match suffix {
81 "" => 1,
82 "b" => 512,
83 "kB" => 1000,
84 "k" | "K" | "KiB" => 1024,
85 "MB" => 1_000_000,
86 "M" | "MiB" => 1_048_576,
87 "GB" => 1_000_000_000,
88 "G" | "GiB" => 1_073_741_824,
89 "TB" => 1_000_000_000_000,
90 "T" | "TiB" => 1_099_511_627_776,
91 "PB" => 1_000_000_000_000_000,
92 "P" | "PiB" => 1_125_899_906_842_624,
93 "EB" => 1_000_000_000_000_000_000,
94 "E" | "EiB" => 1_152_921_504_606_846_976,
95 "ZB" | "Z" | "ZiB" | "YB" | "Y" | "YiB" => {
97 if num > 0 {
98 return Ok(u64::MAX);
99 }
100 return Ok(0);
101 }
102 _ => return Err(format!("invalid suffix in '{}'", s)),
103 };
104
105 num.checked_mul(multiplier)
106 .ok_or_else(|| format!("number too large: '{}'", s))
107}
108
109pub fn head_lines(data: &[u8], n: u64, delimiter: u8, out: &mut impl Write) -> io::Result<()> {
111 if n == 0 || data.is_empty() {
112 return Ok(());
113 }
114
115 let mut count = 0u64;
116 for pos in memchr_iter(delimiter, data) {
117 count += 1;
118 if count == n {
119 return out.write_all(&data[..=pos]);
120 }
121 }
122
123 out.write_all(data)
125}
126
127pub fn head_lines_from_end(
130 data: &[u8],
131 n: u64,
132 delimiter: u8,
133 out: &mut impl Write,
134) -> io::Result<()> {
135 if n == 0 {
136 return out.write_all(data);
137 }
138 if data.is_empty() {
139 return Ok(());
140 }
141
142 let mut count = if !data.is_empty() && *data.last().unwrap() != delimiter {
147 1u64
148 } else {
149 0u64
150 };
151 for pos in memrchr_iter(delimiter, data) {
152 count += 1;
153 if count > n {
154 return out.write_all(&data[..=pos]);
155 }
156 }
157
158 Ok(())
160}
161
162pub fn head_bytes(data: &[u8], n: u64, out: &mut impl Write) -> io::Result<()> {
164 let n = n.min(data.len() as u64) as usize;
165 if n > 0 {
166 out.write_all(&data[..n])?;
167 }
168 Ok(())
169}
170
171pub fn head_bytes_from_end(data: &[u8], n: u64, out: &mut impl Write) -> io::Result<()> {
173 if n >= data.len() as u64 {
174 return Ok(());
175 }
176 let end = data.len() - n as usize;
177 if end > 0 {
178 out.write_all(&data[..end])?;
179 }
180 Ok(())
181}
182
183#[cfg(target_os = "linux")]
185pub fn sendfile_bytes(path: &Path, n: u64, out_fd: i32) -> io::Result<bool> {
186 use std::os::unix::fs::OpenOptionsExt;
187
188 let file = std::fs::OpenOptions::new()
189 .read(true)
190 .custom_flags(libc::O_NOATIME)
191 .open(path)
192 .or_else(|_| std::fs::File::open(path))?;
193
194 let metadata = file.metadata()?;
195 let file_size = metadata.len();
196 let to_send = n.min(file_size) as usize;
197
198 if to_send == 0 {
199 return Ok(true);
200 }
201
202 use std::os::unix::io::AsRawFd;
203 let in_fd = file.as_raw_fd();
204 let mut offset: libc::off_t = 0;
205 let mut remaining = to_send;
206
207 while remaining > 0 {
208 let chunk = remaining.min(0x7ffff000); let ret = unsafe { libc::sendfile(out_fd, in_fd, &mut offset, chunk) };
210 if ret > 0 {
211 remaining -= ret as usize;
212 } else if ret == 0 {
213 break;
214 } else {
215 let err = io::Error::last_os_error();
216 if err.kind() == io::ErrorKind::Interrupted {
217 continue;
218 }
219 return Err(err);
220 }
221 }
222
223 Ok(true)
224}
225
226fn head_lines_streaming_file(
231 path: &Path,
232 n: u64,
233 delimiter: u8,
234 out: &mut impl Write,
235) -> io::Result<bool> {
236 if n == 0 {
237 return Ok(true);
238 }
239
240 #[cfg(target_os = "linux")]
241 let file = {
242 use std::os::unix::fs::OpenOptionsExt;
243 std::fs::OpenOptions::new()
244 .read(true)
245 .custom_flags(libc::O_NOATIME)
246 .open(path)
247 .or_else(|_| std::fs::File::open(path))?
248 };
249 #[cfg(not(target_os = "linux"))]
250 let file = std::fs::File::open(path)?;
251
252 let mut file = file;
253 let mut buf = [0u8; 65536];
254 let mut count = 0u64;
255
256 loop {
257 let bytes_read = match file.read(&mut buf) {
258 Ok(0) => break,
259 Ok(n) => n,
260 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
261 Err(e) => return Err(e),
262 };
263
264 let chunk = &buf[..bytes_read];
265
266 for pos in memchr_iter(delimiter, chunk) {
267 count += 1;
268 if count == n {
269 out.write_all(&chunk[..=pos])?;
270 return Ok(true);
271 }
272 }
273
274 out.write_all(chunk)?;
275 }
276
277 Ok(true)
278}
279
280pub fn head_file(
282 filename: &str,
283 config: &HeadConfig,
284 out: &mut impl Write,
285 tool_name: &str,
286) -> io::Result<bool> {
287 let delimiter = if config.zero_terminated { b'\0' } else { b'\n' };
288
289 if filename != "-" {
290 let path = Path::new(filename);
291
292 match &config.mode {
294 HeadMode::Lines(n) => {
295 match head_lines_streaming_file(path, *n, delimiter, out) {
297 Ok(true) => return Ok(true),
298 Err(e) => {
299 eprintln!(
300 "{}: cannot open '{}' for reading: {}",
301 tool_name,
302 filename,
303 crate::common::io_error_msg(&e)
304 );
305 return Ok(false);
306 }
307 _ => {}
308 }
309 }
310 HeadMode::Bytes(n) => {
311 #[cfg(target_os = "linux")]
313 {
314 use std::os::unix::io::AsRawFd;
315 let stdout = io::stdout();
316 let out_fd = stdout.as_raw_fd();
317 if let Ok(true) = sendfile_bytes(path, *n, out_fd) {
318 return Ok(true);
319 }
320 }
321 #[cfg(not(target_os = "linux"))]
323 {
324 if let Ok(true) = head_bytes_streaming_file(path, *n, out) {
325 return Ok(true);
326 }
327 }
328 }
329 _ => {
330 }
332 }
333 }
334
335 let data: FileData = if filename == "-" {
337 match read_stdin() {
338 Ok(d) => FileData::Owned(d),
339 Err(e) => {
340 eprintln!(
341 "{}: standard input: {}",
342 tool_name,
343 crate::common::io_error_msg(&e)
344 );
345 return Ok(false);
346 }
347 }
348 } else {
349 match read_file(Path::new(filename)) {
350 Ok(d) => d,
351 Err(e) => {
352 eprintln!(
353 "{}: cannot open '{}' for reading: {}",
354 tool_name,
355 filename,
356 crate::common::io_error_msg(&e)
357 );
358 return Ok(false);
359 }
360 }
361 };
362
363 match &config.mode {
364 HeadMode::Lines(n) => head_lines(&data, *n, delimiter, out)?,
365 HeadMode::LinesFromEnd(n) => head_lines_from_end(&data, *n, delimiter, out)?,
366 HeadMode::Bytes(n) => head_bytes(&data, *n, out)?,
367 HeadMode::BytesFromEnd(n) => head_bytes_from_end(&data, *n, out)?,
368 }
369
370 Ok(true)
371}
372
373#[cfg(not(target_os = "linux"))]
375fn head_bytes_streaming_file(path: &Path, n: u64, out: &mut impl Write) -> io::Result<bool> {
376 let mut file = std::fs::File::open(path)?;
377 let mut remaining = n as usize;
378 let mut buf = [0u8; 65536];
379
380 while remaining > 0 {
381 let to_read = remaining.min(buf.len());
382 let bytes_read = match file.read(&mut buf[..to_read]) {
383 Ok(0) => break,
384 Ok(n) => n,
385 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
386 Err(e) => return Err(e),
387 };
388 out.write_all(&buf[..bytes_read])?;
389 remaining -= bytes_read;
390 }
391
392 Ok(true)
393}
394
395pub fn head_stdin_lines_streaming(n: u64, delimiter: u8, out: &mut impl Write) -> io::Result<()> {
398 if n == 0 {
399 return Ok(());
400 }
401
402 let stdin = io::stdin();
403 let mut reader = stdin.lock();
404 let mut buf = [0u8; 262144];
405 let mut count = 0u64;
406
407 loop {
408 let bytes_read = match reader.read(&mut buf) {
409 Ok(0) => break,
410 Ok(n) => n,
411 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
412 Err(e) => return Err(e),
413 };
414
415 let chunk = &buf[..bytes_read];
416
417 for pos in memchr_iter(delimiter, chunk) {
419 count += 1;
420 if count == n {
421 out.write_all(&chunk[..=pos])?;
422 return Ok(());
423 }
424 }
425
426 out.write_all(chunk)?;
428 }
429
430 Ok(())
431}