use super::no_follow::open_optional_no_follow;
use ic_host_artifacts::artifact::ArtifactError;
use std::{
fmt,
fs::File,
io::{self, Read},
os::unix::fs::MetadataExt as _,
path::Path,
};
#[cfg(test)]
mod tests;
#[derive(Debug)]
pub enum PrivateFileReadError {
Read(ArtifactError),
Permissions {
mode: u32,
},
LinkCount {
actual: u64,
},
Length {
expected: usize,
},
}
impl fmt::Display for PrivateFileReadError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Read(_) => formatter.write_str("private file read failed"),
Self::Permissions { mode } => write!(
formatter,
"private file permissions are {mode:o}, expected 600"
),
Self::LinkCount { actual } => {
write!(formatter, "private file has {actual} links, expected one")
}
Self::Length { expected } => write!(
formatter,
"private file must contain exactly {expected} bytes"
),
}
}
}
impl std::error::Error for PrivateFileReadError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Read(source) => Some(source),
_ => None,
}
}
}
pub fn read_private_bytes<const N: usize>(
path: &Path,
) -> Result<Option<[u8; N]>, PrivateFileReadError> {
open_optional_no_follow(path)
.map_err(PrivateFileReadError::Read)?
.map(read_private_descriptor)
.transpose()
}
fn read_private_descriptor<const N: usize>(
mut file: File,
) -> Result<[u8; N], PrivateFileReadError> {
let io_error = |source| PrivateFileReadError::Read(ArtifactError::Io(source));
let metadata = file.metadata().map_err(io_error)?;
if !metadata.is_file() {
return Err(PrivateFileReadError::Read(ArtifactError::NotRegularFile));
}
if metadata.len() != N as u64 {
return Err(PrivateFileReadError::Length { expected: N });
}
let mode = metadata.mode() & 0o777;
if mode != 0o600 {
return Err(PrivateFileReadError::Permissions { mode });
}
if metadata.nlink() != 1 {
return Err(PrivateFileReadError::LinkCount {
actual: metadata.nlink(),
});
}
read_exact_private(&mut file)
}
fn read_exact_private<const N: usize>(
reader: &mut impl Read,
) -> Result<[u8; N], PrivateFileReadError> {
let io_error = |source| PrivateFileReadError::Read(ArtifactError::Io(source));
let mut bytes = [0; N];
reader.read_exact(&mut bytes).map_err(io_error)?;
let mut extra = [0];
loop {
match reader.read(&mut extra) {
Ok(0) => return Ok(bytes),
Ok(_) => return Err(PrivateFileReadError::Length { expected: N }),
Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
Err(error) => return Err(io_error(error)),
}
}
}