use std::io::Write;
use std::os::fd::{AsFd as _, OwnedFd};
use std::sync::Arc;
use anyhow::{Context as _, Result};
use composefs::fsverity::FsVerityHashValue;
use composefs::repository::Repository;
use composefs_splitdirfdstream::{
FdLimitError, build_layer_fd_layout, spawn_self_reaping_producer, split_fds_into_frames,
};
use crate::layer_sync::produce_layer_splitdirfdstream;
pub trait LayerSource: Send + 'static {
fn open(&self) -> Result<(Vec<OwnedFd>, Box<dyn Send + 'static>)>;
fn produce(&self, dirfd_index_map: &[u32], out: Box<dyn Write + Send>) -> Result<()>;
}
pub struct RepoLayerSource<ObjectID: FsVerityHashValue> {
pub repo: Arc<Repository<ObjectID>>,
pub layer_verity: ObjectID,
}
impl<ObjectID> std::fmt::Debug for RepoLayerSource<ObjectID>
where
ObjectID: FsVerityHashValue + std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RepoLayerSource")
.field("layer_verity", &self.layer_verity)
.finish_non_exhaustive()
}
}
impl<ObjectID> LayerSource for RepoLayerSource<ObjectID>
where
ObjectID: FsVerityHashValue,
{
fn open(&self) -> Result<(Vec<OwnedFd>, Box<dyn Send + 'static>)> {
let objects_dir = self
.repo
.objects_dir()
.context("opening repository objects dir")?;
let dup = rustix::io::dup(objects_dir.as_fd())
.map_err(std::io::Error::from)
.context("dup objects_dir fd")?;
Ok((vec![dup], Box::new(())))
}
fn produce(&self, dirfd_index_map: &[u32], out: Box<dyn Write + Send>) -> Result<()> {
let objects_dirfd_index = dirfd_index_map
.first()
.copied()
.context("dirfd_index_map is empty: expected at least one real dir")?;
produce_layer_splitdirfdstream(&self.repo, &self.layer_verity, objects_dirfd_index, out)
}
}
#[derive(Debug)]
pub struct GetLayerFrames {
pub dir_count: u32,
pub batches: Vec<Vec<OwnedFd>>,
}
#[derive(Debug)]
pub enum ServeGetLayerError {
FdLimitExceeded(FdLimitError),
Other(anyhow::Error),
}
impl From<anyhow::Error> for ServeGetLayerError {
fn from(e: anyhow::Error) -> Self {
ServeGetLayerError::Other(e)
}
}
pub fn serve_get_layer(
source: impl LayerSource,
seed: u64,
more: bool,
) -> std::result::Result<GetLayerFrames, ServeGetLayerError> {
let (real_fds, guard) = source.open()?;
let (pipe_read, pipe_write) = rustix::pipe::pipe_with(rustix::pipe::PipeFlags::CLOEXEC)
.map_err(|e| anyhow::anyhow!("pipe: {e}"))?;
let layout = build_layer_fd_layout(pipe_read, real_fds, seed)
.map_err(|e| anyhow::anyhow!("build_layer_fd_layout: {e}"))?;
let dir_count = layout.dir_count;
let real_indices = layout.real_indices.clone();
let keepalive_read = layout.keepalive_read;
let batches = split_fds_into_frames(layout.fds_all, seed, more)
.map_err(|(_fds, e)| ServeGetLayerError::FdLimitExceeded(e))?;
spawn_self_reaping_producer(pipe_write, keepalive_read, guard, move |wf| {
if let Err(e) = source.produce(&real_indices, Box::new(wf)) {
tracing::warn!("GetLayer producer error: {e:#}");
}
});
Ok(GetLayerFrames { dir_count, batches })
}