#[cfg(test)]
mod tests;
use super::{ArtifactError, ArtifactIdentity, check_metadata, hash_reader, read_opened_file};
use rustix::fs::{Mode, OFlags, open};
use std::{
fs::{self, File},
io,
path::Path,
};
pub fn read_file_no_follow(path: &Path, max_bytes: usize) -> Result<Vec<u8>, ArtifactError> {
read_opened_file(open_no_follow(path)?, max_bytes)
}
pub fn hash_file_no_follow(path: &Path, max_bytes: u64) -> Result<ArtifactIdentity, ArtifactError> {
let file = open_no_follow(path)?;
check_metadata(&file.metadata()?, max_bytes)?;
hash_reader(file, max_bytes)
}
pub fn read_optional_file_no_follow(
path: &Path,
max_bytes: usize,
) -> Result<Option<Vec<u8>>, ArtifactError> {
open_optional_regular_file(path)?
.map(|file| read_opened_file(file, max_bytes))
.transpose()
}
pub(super) fn open_optional_regular_file(path: &Path) -> Result<Option<File>, ArtifactError> {
match fs::symlink_metadata(path) {
Ok(metadata) if !metadata.is_file() => return Err(ArtifactError::NotRegularFile),
Ok(_) => {}
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(None),
Err(error) => return Err(ArtifactError::Io(error)),
}
let file = open_no_follow(path)?;
if !file.metadata()?.is_file() {
return Err(ArtifactError::NotRegularFile);
}
Ok(Some(file))
}
fn open_no_follow(path: &Path) -> Result<File, ArtifactError> {
let fd = open(
path,
OFlags::RDONLY | OFlags::NOFOLLOW | OFlags::NONBLOCK | OFlags::CLOEXEC,
Mode::empty(),
)
.map_err(|error| ArtifactError::Io(io::Error::from_raw_os_error(error.raw_os_error())))?;
Ok(File::from(fd))
}