ic_host_fs/read/private/
mod.rs1use super::no_follow::open_optional_no_follow;
4use ic_host_artifacts::artifact::ArtifactError;
5use std::{
6 fs::File,
7 io::{self, Read},
8 os::unix::fs::MetadataExt as _,
9 path::Path,
10};
11
12#[cfg(test)]
13mod tests;
14
15#[derive(Debug, thiserror::Error)]
17pub enum PrivateFileReadError {
18 #[error("private file read failed")]
20 Read(#[source] ArtifactError),
21 #[error("private file permissions are {mode:o}, expected 600")]
23 Permissions {
24 mode: u32,
26 },
27 #[error("private file has {actual} links, expected one")]
29 LinkCount {
30 actual: u64,
32 },
33 #[error("private file must contain exactly {expected} bytes")]
35 Length {
36 expected: usize,
38 },
39}
40
41pub fn read_private_bytes<const N: usize>(
57 path: &Path,
58) -> Result<Option<[u8; N]>, PrivateFileReadError> {
59 open_optional_no_follow(path)
60 .map_err(PrivateFileReadError::Read)?
61 .map(read_private_descriptor)
62 .transpose()
63}
64
65fn read_private_descriptor<const N: usize>(
66 mut file: File,
67) -> Result<[u8; N], PrivateFileReadError> {
68 let io_error = |source| PrivateFileReadError::Read(ArtifactError::Io(source));
69 let metadata = file.metadata().map_err(io_error)?;
70 if !metadata.is_file() {
71 return Err(PrivateFileReadError::Read(ArtifactError::NotRegularFile));
72 }
73 if metadata.len() != N as u64 {
74 return Err(PrivateFileReadError::Length { expected: N });
75 }
76 let mode = metadata.mode() & 0o777;
77 if mode != 0o600 {
78 return Err(PrivateFileReadError::Permissions { mode });
79 }
80 if metadata.nlink() != 1 {
81 return Err(PrivateFileReadError::LinkCount {
82 actual: metadata.nlink(),
83 });
84 }
85 read_exact_private(&mut file)
86}
87
88fn read_exact_private<const N: usize>(
89 reader: &mut impl Read,
90) -> Result<[u8; N], PrivateFileReadError> {
91 let io_error = |source| PrivateFileReadError::Read(ArtifactError::Io(source));
92 let mut bytes = [0; N];
93 reader.read_exact(&mut bytes).map_err(io_error)?;
94 let mut extra = [0];
95 loop {
96 match reader.read(&mut extra) {
97 Ok(0) => return Ok(bytes),
98 Ok(_) => return Err(PrivateFileReadError::Length { expected: N }),
99 Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
100 Err(error) => return Err(io_error(error)),
101 }
102 }
103}