use std::{
fs::File,
io::{self, Read},
path::Path,
};
pub(super) const MAX_FILE_BYTES: u64 = 4 * 1024 * 1024;
#[derive(Debug)]
pub(super) enum ReadOutcome {
Bytes {
bytes: Vec<u8>,
bytes_scanned: u64,
truncated: bool,
scan_byte_limit_reached: bool,
},
Skipped,
ReadErrorSkipped,
ScanByteLimitReached,
}
pub(super) fn read_file_bytes(path: &Path, remaining_bytes: u64) -> io::Result<ReadOutcome> {
if remaining_bytes == 0 {
return Ok(ReadOutcome::ScanByteLimitReached);
}
let metadata = match path.symlink_metadata() {
Ok(metadata) => metadata,
Err(error) if is_recoverable(&error) => return Ok(ReadOutcome::ReadErrorSkipped),
Err(error) => return Err(error),
};
if !metadata.file_type().is_file() {
return Ok(ReadOutcome::Skipped);
}
let size = metadata.len();
if size == 0 {
return Ok(ReadOutcome::Bytes {
bytes: Vec::new(),
bytes_scanned: 0,
truncated: false,
scan_byte_limit_reached: false,
});
}
let file = match File::open(path) {
Ok(file) => file,
Err(error) if is_recoverable(&error) => return Ok(ReadOutcome::ReadErrorSkipped),
Err(error) => return Err(error),
};
read_opened_file(file, size, remaining_bytes)
}
fn read_opened_file(file: impl Read, size: u64, remaining_bytes: u64) -> io::Result<ReadOutcome> {
let read_limit = MAX_FILE_BYTES.min(remaining_bytes).min(size);
let probe = size <= MAX_FILE_BYTES && read_limit < remaining_bytes;
let read_request = read_limit.saturating_add(u64::from(probe));
let mut bytes = Vec::with_capacity(read_request as usize);
if let Err(error) = file.take(read_request).read_to_end(&mut bytes) {
if is_recoverable(&error) {
return Ok(ReadOutcome::ReadErrorSkipped);
}
return Err(error);
}
let grew = bytes.len() as u64 > read_limit;
bytes.truncate(read_limit as usize);
let file_prefix_truncated = size > MAX_FILE_BYTES || grew;
let scan_byte_limit_reached = size > remaining_bytes && remaining_bytes < MAX_FILE_BYTES;
Ok(ReadOutcome::Bytes {
bytes_scanned: bytes.len() as u64,
bytes,
truncated: file_prefix_truncated,
scan_byte_limit_reached,
})
}
fn is_recoverable(error: &io::Error) -> bool {
matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::PermissionDenied
)
}