use std::{
fs, io,
path::{Path, PathBuf},
sync::atomic::{AtomicU64, Ordering},
};
use ic_host_fs::{durable::write_bytes, read::read_file};
use super::cache_fs::remove_path_if_present;
const STAGE_ATTEMPTS: usize = 64;
static STAGE_SEQUENCE: AtomicU64 = AtomicU64::new(0);
#[derive(Debug)]
pub(super) struct OwnedDirectory {
path: PathBuf,
armed: bool,
}
impl OwnedDirectory {
pub(super) const fn new(path: PathBuf) -> Self {
Self { path, armed: true }
}
pub(super) fn path(&self) -> &Path {
&self.path
}
pub(super) const fn is_armed(&self) -> bool {
self.armed
}
pub(super) const fn disarm(&mut self) {
self.armed = false;
}
pub(super) fn preserve(mut self) {
self.disarm();
}
pub(super) fn try_cleanup(&mut self) -> io::Result<()> {
remove_path_if_present(&self.path)?;
self.disarm();
Ok(())
}
pub(super) fn cleanup(mut self) -> io::Result<()> {
self.try_cleanup()
}
pub(super) fn cleanup_on_drop(&mut self) {
if self.armed {
let _ = remove_path_if_present(&self.path);
self.disarm();
}
}
}
impl Drop for OwnedDirectory {
fn drop(&mut self) {
self.cleanup_on_drop();
}
}
#[derive(Debug)]
pub struct ArtifactStagingDirectory {
directory: OwnedDirectory,
}
impl ArtifactStagingDirectory {
pub fn new(parent: &Path) -> io::Result<Self> {
for _ in 0..STAGE_ATTEMPTS {
let sequence = STAGE_SEQUENCE.fetch_add(1, Ordering::Relaxed);
let root = parent.join(format!(
".ic-build-artifact-stage-{}-{sequence}",
std::process::id()
));
match fs::create_dir(&root) {
Ok(()) => {
return Ok(Self {
directory: OwnedDirectory::new(root),
});
}
Err(source) if source.kind() == io::ErrorKind::AlreadyExists => {}
Err(source) => return Err(source),
}
}
Err(io::Error::new(
io::ErrorKind::AlreadyExists,
format!(
"failed to allocate artifact staging beneath {}",
parent.display()
),
))
}
#[must_use]
pub fn path(&self) -> &Path {
self.directory.path()
}
pub fn close(mut self) -> io::Result<()> {
self.directory.disarm();
remove_path_if_present(self.directory.path())
}
}
impl Drop for ArtifactStagingDirectory {
fn drop(&mut self) {
self.directory.cleanup_on_drop();
}
}
#[derive(Debug)]
pub struct CapturedArtifact {
staging: ArtifactStagingDirectory,
path: PathBuf,
}
impl CapturedArtifact {
pub fn capture(source: &Path, parent: &Path, maximum_bytes: usize) -> io::Result<Self> {
let bytes = read_file(source, maximum_bytes).map_err(io::Error::from)?;
let staging = ArtifactStagingDirectory::new(parent)?;
let path = staging.path().join("input.artifact");
write_bytes(&path, &bytes).map_err(io::Error::other)?;
Ok(Self { staging, path })
}
#[must_use]
pub fn path(&self) -> &Path {
&self.path
}
#[must_use]
pub fn retain(mut self) -> PathBuf {
self.staging.directory.disarm();
self.path
}
pub fn close(self) -> io::Result<()> {
self.staging.close()
}
}
#[cfg(test)]
mod tests;