use std::{
fmt, fs, io,
num::NonZeroUsize,
path::{Path, PathBuf},
};
use ic_host_artifacts::artifact::{ArtifactError, CopyError, copy_reader};
use ic_host_fs::{
directory::create_unique_directory,
durable::{NamedWriteError, PublicationMode, WriteOptions, write_with},
owned_directory::OwnedDirectory,
read::open_regular_file,
};
mod produce;
pub use produce::produce_and_retain_artifact;
fn staging_directory(parent: &Path) -> io::Result<OwnedDirectory> {
create_unique_directory(
parent,
".ic-build-artifact-stage",
0o777,
NonZeroUsize::new(64),
)
.map(OwnedDirectory::new)
}
#[derive(Debug)]
pub struct CapturedArtifact {
staging: OwnedDirectory,
path: PathBuf,
}
#[derive(Debug)]
pub struct CaptureRetentionError {
pub source: io::Error,
pub retained_path: PathBuf,
pub staging_directory: PathBuf,
}
impl fmt::Display for CaptureRetentionError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"capture retained at {} (staging {}): {}",
self.retained_path.display(),
self.staging_directory.display(),
self.source
)
}
}
impl std::error::Error for CaptureRetentionError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.source)
}
}
impl CapturedArtifact {
pub fn capture(source: &Path, parent: &Path, maximum_bytes: usize) -> io::Result<Self> {
let source = open_regular_file(source).map_err(io::Error::from)?;
let limit = maximum_bytes as u64;
if source.metadata()?.len() > limit {
return Err(ArtifactError::LimitExceeded { limit }.into());
}
let staging = staging_directory(parent)?;
let path = staging.path().join("input.artifact");
write_with(
&path,
WriteOptions {
mode: PublicationMode::Replace,
permissions: 0o666,
},
|file| copy_reader(source, file, limit).map(|_| ()),
)
.map_err(|error| match error {
NamedWriteError::Producer {
source: CopyError::Input(source),
cleanup_error: None,
} => io::Error::from(source),
other => io::Error::other(other),
})?;
Ok(Self { staging, path })
}
#[must_use]
pub fn path(&self) -> &Path {
&self.path
}
#[must_use]
pub fn retain(mut self) -> PathBuf {
self.staging.disarm();
self.path
}
pub fn retain_to(self, destination: &Path) -> Result<PathBuf, CaptureRetentionError> {
let staging = self.staging.path().to_owned();
let retained = self.retain();
fs::rename(&retained, destination).map_err(|source| CaptureRetentionError {
source,
retained_path: retained.clone(),
staging_directory: staging.clone(),
})?;
fs::remove_dir(&staging).map_err(|source| CaptureRetentionError {
source,
retained_path: destination.to_owned(),
staging_directory: staging,
})?;
Ok(destination.to_owned())
}
pub fn close(self) -> io::Result<()> {
self.staging.cleanup()
}
}
#[cfg(test)]
mod tests;