use std::io::{Read, Seek};
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Instant, SystemTime};
#[cfg(test)]
use filetime::FileTime;
use rabs_protocol::raw_bytes::RawBytes;
use rabs_protocol::result_identity::{OutputRole, TypedDigest};
use crate::blob_store::RAW_PROFILE_V1;
use crate::digest_set::{DigestRequest, StreamingObjectWriter};
use crate::metadata_store::{RabsMetadataStore, SqlValue, StoreError, digest_key};
static MATERIALIZE_COUNTER: AtomicU64 = AtomicU64::new(0);
pub fn publish_new_directory(staging: &Path, destination: &Path) -> std::io::Result<()> {
#[cfg(any(target_os = "linux", target_os = "macos"))]
{
let parent = staging
.parent()
.filter(|parent| {
staging.is_absolute()
&& destination.is_absolute()
&& Some(*parent) == destination.parent()
})
.ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"output directories must be absolute siblings",
)
})?;
let source = staging.file_name().ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"staging directory needs a name",
)
})?;
let target = destination.file_name().ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"output directory needs a name",
)
})?;
if !std::fs::symlink_metadata(staging)?.is_dir() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"staging must be an ordinary directory",
));
}
let directory = std::fs::File::open(parent)?;
rustix::fs::renameat_with(
&directory,
source,
&directory,
target,
rustix::fs::RenameFlags::NOREPLACE,
)?;
directory.sync_all()
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
{
let _ = (staging, destination);
Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"atomic output directory installation is unsupported on this platform",
))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MaterializeError {
ModeUnsupported(MaterializationMode),
NoUsableCopy {
object: String,
},
QuarantinedObject {
object: String,
},
Unreadable {
path: String,
error: String,
},
ContentMismatch {
expected: String,
found: String,
path: String,
},
Io {
step: &'static str,
error: String,
},
Store(String),
DuplicateDestination {
path: String,
},
OverlappingDestinations {
parent: String,
child: String,
},
UnsafeDestination {
path: String,
},
}
impl std::fmt::Display for MaterializeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ModeUnsupported(mode) => write!(f, "materialization mode {mode:?} unimplemented"),
Self::NoUsableCopy { object } => write!(f, "no usable copy of {object}"),
Self::QuarantinedObject { object } => {
write!(f, "logical object {object} is quarantined")
}
Self::Unreadable { path, error } => write!(f, "unreadable copy {path}: {error}"),
Self::ContentMismatch {
expected,
found,
path,
} => write!(f, "{path} holds {found}, not {expected}"),
Self::Io { step, error } => write!(f, "{step}: {error}"),
Self::Store(error) => write!(f, "store: {error}"),
Self::DuplicateDestination { path } => {
write!(f, "action plan declares {path} as a destination twice")
}
Self::OverlappingDestinations { parent, child } => {
write!(f, "action output {parent} is an ancestor of output {child}")
}
Self::UnsafeDestination { path } => {
write!(f, "action destination {path} is not a safe named file")
}
}
}
}
fn io_err(step: &'static str) -> impl Fn(std::io::Error) -> MaterializeError {
move |error| MaterializeError::Io {
step,
error: error.to_string(),
}
}
pub fn materialize_object(
store: &mut dyn RabsMetadataStore,
object: &TypedDigest,
destination: &Path,
mode: MaterializationMode,
) -> Result<u64, MaterializeError> {
materialize_object_prepared(store, object, destination, mode, |_| Ok(()))
}
fn materialize_object_prepared(
store: &mut dyn RabsMetadataStore,
object: &TypedDigest,
destination: &Path,
mode: MaterializationMode,
prepare_metadata: impl Fn(&std::fs::File) -> std::io::Result<()>,
) -> Result<u64, MaterializeError> {
if mode == MaterializationMode::ReadOnlyBind {
return Err(MaterializeError::ModeUnsupported(mode));
}
let key = digest_key(object);
if !store
.query(
"SELECT 1 FROM quarantines WHERE scope = 'logical-object' AND subject = ?1 LIMIT 1",
&[SqlValue::Text(key.clone())],
)
.map_err(|error| MaterializeError::Store(format!("{error:?}")))?
.is_empty()
{
return Err(MaterializeError::QuarantinedObject { object: key });
}
let locations = store
.object_locations(object)
.map_err(|e: StoreError| MaterializeError::Store(format!("{e:?}")))?;
let raw: Vec<String> = locations
.into_iter()
.filter(|(_, encoding, _)| encoding == RAW_PROFILE_V1)
.map(|(path, _, _)| path)
.collect();
if raw.is_empty() {
return Err(MaterializeError::NoUsableCopy { object: key });
}
let parent = destination
.parent()
.ok_or_else(|| MaterializeError::Io {
step: "destination-parent",
error: "destination has no parent directory".to_owned(),
})?
.to_path_buf();
std::fs::create_dir_all(&parent).map_err(io_err("create-destination-dir"))?;
let mut last: Option<MaterializeError> = None;
for source in raw {
match copy_verified(
&source,
object,
&key,
&parent,
destination,
mode,
&prepare_metadata,
) {
Ok((bytes, _reflinked)) => return Ok(bytes),
Err(error @ MaterializeError::ContentMismatch { .. }) => {
store
.set_location_quarantined(object, &source, true)
.map_err(|quarantine| MaterializeError::Store(format!("{quarantine:?}")))?;
return Err(error);
}
Err(error) => last = Some(error),
}
}
Err(last.unwrap_or(MaterializeError::NoUsableCopy { object: key }))
}
fn open_cas_copy(source: &str) -> Result<std::fs::File, MaterializeError> {
let unreadable = |error: std::io::Error| MaterializeError::Unreadable {
path: source.to_owned(),
error: error.to_string(),
};
let not_regular = || {
unreadable(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"CAS copy is not a regular file",
))
};
if !std::fs::symlink_metadata(source)
.map_err(unreadable)?
.file_type()
.is_file()
{
return Err(not_regular());
}
#[cfg(target_os = "linux")]
let file = {
use rustix::fs::{Mode, OFlags, open};
open(
source,
OFlags::RDONLY | OFlags::CLOEXEC | OFlags::NOFOLLOW | OFlags::NONBLOCK,
Mode::empty(),
)
.map(std::fs::File::from)
.map_err(|error| unreadable(std::io::Error::from(error)))?
};
#[cfg(not(target_os = "linux"))]
let file = std::fs::File::open(source).map_err(unreadable)?;
if !file.metadata().map_err(unreadable)?.is_file() {
return Err(not_regular());
}
Ok(file)
}
fn copy_verified(
source: &str,
object: &TypedDigest,
key: &str,
parent: &Path,
destination: &Path,
mode: MaterializationMode,
prepare_metadata: &impl Fn(&std::fs::File) -> std::io::Result<()>,
) -> Result<(u64, bool), MaterializeError> {
let mut input = open_cas_copy(source)?;
let staging = staging_path(parent, destination);
let mut output = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create_new(true)
.open(&staging)
.map_err(io_err("create-staging"))?;
let reflinked = mode == MaterializationMode::VerifiedCowReflink
&& try_verified_reflink(&input, &output, parent);
let prepare = if reflinked {
output
.try_clone()
.map(|clone| input = clone)
.map_err(io_err("read-cloned-staging"))
} else {
output
.set_len(0)
.map_err(io_err("reset-staging"))
.and_then(|()| output.rewind().map_err(io_err("rewind-staging")))
};
if let Err(error) = prepare {
let _ = std::fs::remove_file(&staging);
return Err(error);
}
let mut writer = StreamingObjectWriter::new(DigestRequest::default(), None);
let mut buffer = vec![0_u8; 64 * 1024];
let outcome = loop {
let read = match input.read(&mut buffer) {
Ok(0) => break Ok(()),
Ok(n) => n,
Err(e) => {
break Err(MaterializeError::Unreadable {
path: source.to_owned(),
error: e.to_string(),
});
}
};
if let Err(e) = writer.write(&buffer[..read]) {
break Err(MaterializeError::Io {
step: "digest",
error: format!("{e:?}"),
});
}
if !reflinked && let Err(e) = std::io::Write::write_all(&mut output, &buffer[..read]) {
break Err(MaterializeError::Io {
step: "write-staging",
error: e.to_string(),
});
}
};
if let Err(error) = outcome {
let _ = std::fs::remove_file(&staging);
return Err(error);
}
let computed = match writer.finish() {
Ok(set) => set.atp_content_id,
Err(e) => {
let _ = std::fs::remove_file(&staging);
return Err(MaterializeError::Io {
step: "digest-finish",
error: format!("{e:?}"),
});
}
};
if computed != *object {
let _ = std::fs::remove_file(&staging);
return Err(MaterializeError::ContentMismatch {
expected: key.to_owned(),
found: digest_key(&computed),
path: source.to_owned(),
});
}
if let Err(error) = prepare_metadata(&output) {
drop(input);
drop(output);
let _ = std::fs::remove_file(&staging);
return Err(io_err("prepare-staging-metadata")(error));
}
let written = match output.metadata() {
Ok(metadata) => metadata.len(),
Err(error) => {
drop(input);
drop(output);
let _ = std::fs::remove_file(&staging);
return Err(io_err("prepared-staging-metadata")(error));
}
};
if let Err(error) = std::fs::rename(&staging, destination) {
let _ = std::fs::remove_file(&staging);
return Err(MaterializeError::Io {
step: "rename-into-place",
error: error.to_string(),
});
}
Ok((written, reflinked))
}
#[cfg(all(
target_os = "linux",
not(any(target_arch = "sparc", target_arch = "sparc64"))
))]
fn try_verified_reflink(input: &std::fs::File, output: &std::fs::File, parent: &Path) -> bool {
use std::os::unix::fs::MetadataExt;
let (Ok(source), Ok(target)) = (input.metadata(), output.metadata()) else {
return false;
};
if !source.is_file()
|| !target.is_file()
|| source.dev() != target.dev()
|| source.ino() == target.ino()
{
return false;
}
verify_reflink_isolation(parent).unwrap_or(false)
&& rustix::fs::ioctl_ficlone(output, input).is_ok()
}
#[cfg(not(all(
target_os = "linux",
not(any(target_arch = "sparc", target_arch = "sparc64"))
)))]
fn try_verified_reflink(_input: &std::fs::File, _output: &std::fs::File, _parent: &Path) -> bool {
false
}
#[cfg(all(
target_os = "linux",
not(any(target_arch = "sparc", target_arch = "sparc64"))
))]
fn verify_reflink_isolation(parent: &Path) -> std::io::Result<bool> {
use rustix::fs::{Mode, OFlags, open};
use std::io::Write;
use std::os::unix::fs::{MetadataExt, PermissionsExt};
let anonymous = || {
open(
parent,
OFlags::TMPFILE | OFlags::RDWR | OFlags::CLOEXEC,
Mode::RUSR | Mode::WUSR,
)
.map(std::fs::File::from)
.map_err(std::io::Error::from)
};
let mut source = anonymous()?;
let mut clone = anonymous()?;
let bytes = [0x5a; 4096];
source.write_all(&bytes)?;
source.set_modified(SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(100))?;
let before = source.metadata()?;
if rustix::fs::ioctl_ficlone(&clone, &source).is_err()
|| clone.metadata()?.ino() == before.ino()
{
return Ok(false);
}
clone.write_all(b"changed private content")?;
clone.set_len(23)?;
clone.set_permissions(std::fs::Permissions::from_mode(0o640))?;
clone.set_modified(SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(200))?;
let after = source.metadata()?;
source.rewind()?;
let mut original = Vec::new();
source.read_to_end(&mut original)?;
Ok(original == bytes
&& before.len() == after.len()
&& before.mode() == after.mode()
&& before.modified()? == after.modified()?)
}
fn staging_path(parent: &Path, destination: &Path) -> PathBuf {
let n = MATERIALIZE_COUNTER.fetch_add(1, Ordering::SeqCst);
let name = destination
.file_name()
.map_or_else(|| "object".to_owned(), |n| n.to_string_lossy().into_owned());
parent.join(format!(".rabs-mat-{}-{n}-{name}.tmp", std::process::id()))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MaterializationMode {
PrivateCopy,
VerifiedCowReflink,
ReadOnlyBind,
}
impl MaterializationMode {
#[must_use]
pub const fn mutation_permitted(self) -> bool {
matches!(self, Self::PrivateCopy | Self::VerifiedCowReflink)
}
#[must_use]
pub const fn mtime_permitted(self) -> bool {
self.mutation_permitted()
}
}
#[must_use]
pub const fn decide_materialization(
destination_mutable: bool,
reflink_available: bool,
reflink_isolation_verified: bool,
) -> MaterializationMode {
if !destination_mutable {
return MaterializationMode::ReadOnlyBind;
}
if reflink_available && reflink_isolation_verified {
return MaterializationMode::VerifiedCowReflink;
}
MaterializationMode::PrivateCopy
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PlannedActionOutput {
pub role: OutputRole,
pub virtual_path: RawBytes,
pub object: TypedDigest,
pub destination: PathBuf,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OutputMaterialized {
pub role: OutputRole,
pub virtual_path: RawBytes,
pub destination: PathBuf,
pub bytes: u64,
pub nanos: u128,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ActionMaterializationReceipt {
pub total_nanos: u128,
pub head_nanos: u128,
pub tail_nanos: u128,
pub installed: Vec<OutputMaterialized>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ActionMaterializeFailure {
pub installed: Vec<OutputMaterialized>,
pub error: MaterializeError,
}
fn role_rank(role: OutputRole) -> u8 {
match role {
OutputRole::ProvisionalMetadata => 0,
OutputRole::DepInfo => 1,
OutputRole::BuildScriptMetadata => 2,
OutputRole::TestSideEffect => 3,
OutputRole::Materializable => 4,
}
}
fn planned_order_key(p: &PlannedActionOutput) -> (Vec<u8>, u8, PathBuf) {
(
p.virtual_path.as_bytes().to_vec(),
role_rank(p.role),
p.destination.clone(),
)
}
fn install_one(
store: &mut dyn RabsMetadataStore,
out: &PlannedActionOutput,
freshness: SystemTime,
mode: MaterializationMode,
prepare: &impl Fn(&PlannedActionOutput, &std::fs::File) -> std::io::Result<()>,
) -> Result<OutputMaterialized, MaterializeError> {
let began = Instant::now();
let bytes =
materialize_object_prepared(store, &out.object, &out.destination, mode, |staged| {
prepare(out, staged)?;
if mode.mtime_permitted() {
staged.set_modified(freshness)
} else {
Ok(())
}
})?;
Ok(OutputMaterialized {
role: out.role,
virtual_path: out.virtual_path.clone(),
destination: out.destination.clone(),
bytes,
nanos: began.elapsed().as_nanos(),
})
}
fn validate_action_destinations(outputs: &[PlannedActionOutput]) -> Result<(), MaterializeError> {
use std::path::Component;
if outputs.is_empty() {
return Ok(());
}
let cwd = std::env::current_dir().map_err(io_err("destination-working-directory"))?;
let mut seen = std::collections::BTreeSet::<PathBuf>::new();
for out in outputs {
let path = &out.destination;
if path.file_name().is_none() || path.components().any(|part| part == Component::ParentDir)
{
return Err(MaterializeError::UnsafeDestination {
path: path.to_string_lossy().into_owned(),
});
}
let absolute = cwd.join(path);
if !absolute.is_absolute() {
return Err(MaterializeError::UnsafeDestination {
path: path.to_string_lossy().into_owned(),
});
}
let key: PathBuf = absolute
.components()
.filter(|part| *part != Component::CurDir)
.collect();
if !seen.insert(key.clone()) {
return Err(MaterializeError::DuplicateDestination {
path: key.to_string_lossy().into_owned(),
});
}
}
let mut previous: Option<&PathBuf> = None;
for path in &seen {
if let Some(parent) = previous
&& path.starts_with(parent)
{
return Err(MaterializeError::OverlappingDestinations {
parent: parent.to_string_lossy().into_owned(),
child: path.to_string_lossy().into_owned(),
});
}
previous = Some(path);
}
Ok(())
}
pub fn materialize_action_outputs(
store: &mut dyn RabsMetadataStore,
outputs: &[PlannedActionOutput],
mode: MaterializationMode,
) -> Result<ActionMaterializationReceipt, ActionMaterializeFailure> {
materialize_action_outputs_prepared(store, outputs, mode, SystemTime::now(), &|_, _| Ok(()))
}
pub fn materialize_action_outputs_prepared(
store: &mut dyn RabsMetadataStore,
outputs: &[PlannedActionOutput],
mode: MaterializationMode,
freshness: SystemTime,
prepare: &impl Fn(&PlannedActionOutput, &std::fs::File) -> std::io::Result<()>,
) -> Result<ActionMaterializationReceipt, ActionMaterializeFailure> {
validate_action_destinations(outputs).map_err(|error| ActionMaterializeFailure {
installed: Vec::new(),
error,
})?;
let mut ordered: Vec<&PlannedActionOutput> = outputs.iter().collect();
ordered.sort_by_key(|p| planned_order_key(p));
let (heads, tails): (Vec<_>, Vec<_>) = ordered
.into_iter()
.partition(|out| out.role == OutputRole::ProvisionalMetadata);
let started = Instant::now();
let mut installed = Vec::with_capacity(outputs.len());
for out in &heads {
match install_one(store, out, freshness, mode, prepare) {
Ok(done) => installed.push(done),
Err(error) => {
return Err(ActionMaterializeFailure { installed, error });
}
}
}
let head_nanos = started.elapsed().as_nanos();
let tail_started = Instant::now();
for out in &tails {
match install_one(store, out, freshness, mode, prepare) {
Ok(done) => installed.push(done),
Err(error) => {
return Err(ActionMaterializeFailure { installed, error });
}
}
}
let tail_nanos = tail_started.elapsed().as_nanos();
Ok(ActionMaterializationReceipt {
total_nanos: started.elapsed().as_nanos(),
head_nanos,
tail_nanos,
installed,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StoredArtifactNotification {
pub exact_line: Vec<u8>,
pub announced_destination: PathBuf,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ReplayPlanError {
UnboundLine {
path: String,
},
DuplicateAnnouncement {
path: String,
},
EmptyLine,
}
impl std::fmt::Display for ReplayPlanError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnboundLine { path } => {
write!(f, "notification names {path}, which is not in the plan")
}
Self::DuplicateAnnouncement { path } => {
write!(f, "two notifications announce {path}")
}
Self::EmptyLine => write!(f, "stored notification line is empty"),
}
}
}
impl std::error::Error for ReplayPlanError {}
pub fn plan_notification_replay(
receipt: &ActionMaterializationReceipt,
lines: &[StoredArtifactNotification],
) -> Result<Vec<Vec<u8>>, ReplayPlanError> {
if lines.iter().any(|l| l.exact_line.is_empty()) {
return Err(ReplayPlanError::EmptyLine);
}
let mut by_destination: std::collections::HashMap<&Path, &StoredArtifactNotification> =
std::collections::HashMap::with_capacity(lines.len());
for line in lines {
if by_destination
.insert(line.announced_destination.as_path(), line)
.is_some()
{
return Err(ReplayPlanError::DuplicateAnnouncement {
path: line.announced_destination.to_string_lossy().into_owned(),
});
}
}
let installed_by_destination: std::collections::HashMap<&Path, usize> = receipt
.installed
.iter()
.enumerate()
.map(|(position, done)| (done.destination.as_path(), position))
.collect();
for line in lines {
if !installed_by_destination.contains_key(line.announced_destination.as_path()) {
return Err(ReplayPlanError::UnboundLine {
path: line.announced_destination.to_string_lossy().into_owned(),
});
}
}
let mut stream = Vec::with_capacity(lines.len());
for done in &receipt.installed {
if let Some(line) = by_destination.get(done.destination.as_path()) {
stream.push(line.exact_line.clone());
}
}
Ok(stream)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use std::path::PathBuf;
fn fingerprint(path: &PathBuf) -> u64 {
let bytes = fs::read(path).unwrap();
let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
for byte in bytes {
hash ^= u64::from(byte);
hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
}
hash
}
fn scratch_dir(name: &str) -> PathBuf {
let dir = std::env::temp_dir()
.join("rabs-d023-tests")
.join(format!("{}-{name}", std::process::id()));
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).unwrap();
dir
}
#[test]
fn writable_hardlinks_are_unrepresentable_and_fallbacks_apply() {
for mode in [
MaterializationMode::PrivateCopy,
MaterializationMode::VerifiedCowReflink,
MaterializationMode::ReadOnlyBind,
] {
match mode {
MaterializationMode::PrivateCopy
| MaterializationMode::VerifiedCowReflink
| MaterializationMode::ReadOnlyBind => {}
}
}
assert_eq!(
decide_materialization(false, true, true),
MaterializationMode::ReadOnlyBind
);
assert_eq!(
decide_materialization(true, true, false),
MaterializationMode::PrivateCopy,
"unverified reflink implementations fall back to copy"
);
assert_eq!(
decide_materialization(true, false, false),
MaterializationMode::PrivateCopy
);
assert_eq!(
decide_materialization(true, true, true),
MaterializationMode::VerifiedCowReflink
);
}
#[test]
fn private_copy_mutation_never_changes_cas_bytes() {
let dir = scratch_dir("private-copy");
let cas_object = dir.join("cas-object.rlib");
fs::write(&cas_object, b"immutable cas bytes").unwrap();
let before = fingerprint(&cas_object);
let materialized = dir.join("target-out.rlib");
fs::copy(&cas_object, &materialized).unwrap();
fs::write(&materialized, b"locally rewritten output").unwrap();
assert_eq!(
fingerprint(&cas_object),
before,
"CAS digest must never change after materialization mutation"
);
let _ = fs::remove_dir_all(&dir);
}
fn store_with_object(
dir: &Path,
bytes: &[u8],
) -> (
crate::metadata_store::SqlMetadataStore<crate::metadata_store::RusqliteEngine>,
crate::blob_store::BlobStoreLayout,
TypedDigest,
) {
use crate::blob_store::{BlobStoreLayout, DurabilityPolicy, PutLimits, put_if_absent};
use crate::digest_set::digest_set;
use crate::metadata_store::{RusqliteEngine, SqlMetadataStore};
let layout = BlobStoreLayout::open(&dir.join("blobs")).unwrap();
let engine = RusqliteEngine::open(&dir.join("meta.sqlite")).unwrap();
let mut store = SqlMetadataStore::open(engine).unwrap();
let declared = digest_set(bytes, DigestRequest::default(), None)
.unwrap()
.atp_content_id;
let mut reader = bytes;
put_if_absent(
&layout,
&mut store,
&declared,
&mut reader,
PutLimits::default(),
DurabilityPolicy::FULL,
)
.expect("put");
(store, layout, declared)
}
#[test]
fn h017_production_materialization_preserves_cas_content_and_metadata() {
let dir = tempfile::tempdir().unwrap().keep();
let bytes = b"shared immutable artifact".repeat(4096);
let (mut store, _layout, object) = store_with_object(&dir, &bytes);
let source = PathBuf::from(&store.object_locations(&object).unwrap()[0].0);
let before = fs::metadata(&source).unwrap();
let target = dir.join("target.rlib");
assert_eq!(
materialize_object(
&mut store,
&object,
&target,
MaterializationMode::VerifiedCowReflink
)
.unwrap(),
bytes.len() as u64
);
assert_eq!(fs::read(&target).unwrap(), bytes);
#[cfg(unix)]
{
use std::os::unix::fs::{MetadataExt, PermissionsExt};
assert_ne!(before.ino(), fs::metadata(&target).unwrap().ino());
fs::set_permissions(&target, fs::Permissions::from_mode(0o600)).unwrap();
assert_eq!(fs::metadata(&source).unwrap().mode(), before.mode());
}
filetime::set_file_mtime(&target, FileTime::from_unix_time(123, 0)).unwrap();
fs::write(&target, b"mutated subscriber output").unwrap();
assert_eq!(fs::read(&source).unwrap(), bytes);
assert_eq!(
fs::metadata(&source).unwrap().modified().unwrap(),
before.modified().unwrap()
);
eprintln!(
"H017 production content/metadata isolation evidence: {}",
dir.display()
);
}
#[test]
fn h017_reflink_request_still_refuses_corrupt_bytes_before_publication() {
let dir = tempfile::tempdir().unwrap().keep();
let (mut store, _layout, object) = store_with_object(&dir, b"original object");
let source = &store.object_locations(&object).unwrap()[0].0;
fs::write(source, b"corrupt object").unwrap();
let target = dir.join("existing.rlib");
fs::write(&target, b"previous artifact").unwrap();
assert!(matches!(
materialize_object(
&mut store,
&object,
&target,
MaterializationMode::VerifiedCowReflink
),
Err(MaterializeError::ContentMismatch { .. })
));
assert_eq!(fs::read(&target).unwrap(), b"previous artifact");
}
#[test]
fn h017_empty_object_replaces_existing_output_without_stale_suffix() {
let dir = tempfile::tempdir().unwrap().keep();
let (mut store, _layout, object) = store_with_object(&dir, b"");
let target = dir.join("empty.rlib");
fs::write(&target, b"previous nonempty output").unwrap();
assert_eq!(
materialize_object(
&mut store,
&object,
&target,
MaterializationMode::VerifiedCowReflink
)
.unwrap(),
0
);
assert!(fs::read(&target).unwrap().is_empty());
}
#[cfg(all(
target_os = "linux",
not(any(target_arch = "sparc", target_arch = "sparc64"))
))]
#[test]
fn h017_tmpfs_unsupported_reflink_falls_back_to_verified_copy() {
let dir = tempfile::tempdir_in("/dev/shm").unwrap().keep();
let source = dir.join("source");
let target = dir.join("target");
let bytes = b"tmpfs fallback artifact";
fs::write(&source, bytes).unwrap();
assert!(!verify_reflink_isolation(&dir).unwrap());
let object = crate::digest_set::digest_set(bytes, DigestRequest::default(), None)
.unwrap()
.atp_content_id;
let (count, reflinked) = copy_verified(
source.to_str().unwrap(),
&object,
&digest_key(&object),
&dir,
&target,
MaterializationMode::VerifiedCowReflink,
&|_| Ok(()),
)
.unwrap();
assert!(!reflinked);
assert_eq!(count, bytes.len() as u64);
assert_eq!(fs::read(&target).unwrap(), bytes);
fs::write(&target, b"independent").unwrap();
assert_eq!(fs::read(&source).unwrap(), bytes);
eprintln!(
"H017 tmpfs verified-copy fallback evidence: {}",
dir.display()
);
}
#[cfg(all(
target_os = "linux",
not(any(target_arch = "sparc", target_arch = "sparc64"))
))]
#[test]
#[ignore = "requires RABS_REFLINK_TEST_ROOT on a real reflink-capable filesystem"]
fn h017_real_reflink_backend_is_required_and_mutation_independent() {
use std::os::unix::fs::{MetadataExt, PermissionsExt};
let root = std::env::var_os("RABS_REFLINK_TEST_ROOT").expect("explicit capable filesystem");
let dir = tempfile::tempdir_in(root).unwrap().keep();
assert!(
verify_reflink_isolation(&dir).unwrap(),
"filesystem must support real reflinks"
);
let source = dir.join("source");
let target = dir.join("target");
let bytes = b"reflink immutable artifact".repeat(4096);
fs::write(&source, &bytes).unwrap();
let before = fs::metadata(&source).unwrap();
let object = crate::digest_set::digest_set(&bytes, DigestRequest::default(), None)
.unwrap()
.atp_content_id;
let (count, reflinked) = copy_verified(
source.to_str().unwrap(),
&object,
&digest_key(&object),
&dir,
&target,
MaterializationMode::VerifiedCowReflink,
&|_| Ok(()),
)
.unwrap();
assert!(
reflinked,
"copy fallback must not satisfy the positive reflink gate"
);
assert_eq!(count, bytes.len() as u64);
assert_eq!(fs::read(&target).unwrap(), bytes);
assert_ne!(before.ino(), fs::metadata(&target).unwrap().ino());
fs::set_permissions(&target, fs::Permissions::from_mode(0o600)).unwrap();
filetime::set_file_mtime(&target, FileTime::from_unix_time(123, 0)).unwrap();
fs::write(&target, b"target changed").unwrap();
assert_eq!(fs::read(&source).unwrap(), bytes);
let after = fs::metadata(&source).unwrap();
assert_eq!(after.mode(), before.mode());
assert_eq!(after.modified().unwrap(), before.modified().unwrap());
fs::write(&source, b"source changed later").unwrap();
assert_eq!(fs::read(&target).unwrap(), b"target changed");
eprintln!(
"H017 real reflink content+metadata evidence: {}",
dir.display()
);
}
#[test]
fn materializes_real_bytes_and_the_copy_is_private() {
let dir = scratch_dir("materialize");
let bytes = b"the committed artifact bytes".repeat(1000);
let (mut store, _layout, object) = store_with_object(&dir, &bytes);
let destination = dir
.join("worktree")
.join("target")
.join("debug")
.join("lib.rlib");
let written = materialize_object(
&mut store,
&object,
&destination,
MaterializationMode::PrivateCopy,
)
.expect("materialize");
assert_eq!(written as usize, bytes.len());
assert_eq!(fs::read(&destination).unwrap(), bytes);
let cas_path = store.object_locations(&object).unwrap()[0].0.clone();
let before = fingerprint(&PathBuf::from(&cas_path));
fs::write(&destination, b"cargo rewrote its output").unwrap();
assert_eq!(
fingerprint(&PathBuf::from(&cas_path)),
before,
"materialization must never alias the CAS inode"
);
let leftovers: Vec<_> = fs::read_dir(destination.parent().unwrap())
.unwrap()
.flatten()
.filter(|e| e.file_name().to_string_lossy().starts_with(".rabs-mat-"))
.collect();
assert!(leftovers.is_empty(), "staging files left behind");
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn a_corrupt_store_copy_is_refused_and_nothing_is_installed() {
let dir = scratch_dir("corrupt");
let bytes = b"honest bytes";
let (mut store, _layout, object) = store_with_object(&dir, bytes);
let cas_path = store.object_locations(&object).unwrap()[0].0.clone();
fs::write(&cas_path, b"tampered!!!!").unwrap();
let destination = dir.join("out.rlib");
let outcome = materialize_object(
&mut store,
&object,
&destination,
MaterializationMode::PrivateCopy,
);
assert!(
matches!(outcome, Err(MaterializeError::ContentMismatch { .. })),
"corrupt bytes must be refused, got {outcome:?}"
);
assert!(
!destination.exists(),
"a refused materialization must install nothing"
);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn an_object_with_no_copy_and_an_unimplemented_mode_are_typed_refusals() {
let dir = scratch_dir("no-copy");
let (mut store, _layout, object) = store_with_object(&dir, b"present");
let absent = crate::digest_set::digest_set(b"never stored", DigestRequest::default(), None)
.unwrap()
.atp_content_id;
assert!(matches!(
materialize_object(
&mut store,
&absent,
&dir.join("a.rlib"),
MaterializationMode::PrivateCopy
),
Err(MaterializeError::NoUsableCopy { .. })
));
assert_eq!(
materialize_object(
&mut store,
&object,
&dir.join("b.rlib"),
MaterializationMode::ReadOnlyBind,
),
Err(MaterializeError::ModeUnsupported(
MaterializationMode::ReadOnlyBind
))
);
assert!(!dir.join("b.rlib").exists());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn logical_quarantine_blocks_byte_correct_artifacts_before_destination_creation() {
use crate::metadata_store::QuarantineScope;
let dir = tempfile::tempdir().unwrap();
let (mut store, _layout, object) = store_with_object(dir.path(), b"valid bytes");
let key = digest_key(&object);
store
.add_quarantine(QuarantineScope::LogicalObject, &key, "unresolved incident")
.unwrap();
for mode in [
MaterializationMode::PrivateCopy,
MaterializationMode::VerifiedCowReflink,
] {
let destination = dir.path().join("subscriber/target/out.rlib");
assert_eq!(
materialize_object(&mut store, &object, &destination, mode),
Err(MaterializeError::QuarantinedObject {
object: key.clone()
})
);
assert!(!dir.path().join("subscriber").exists());
}
let existing = dir.path().join("existing.rlib");
fs::write(&existing, b"older output").unwrap();
assert!(matches!(
materialize_object(
&mut store,
&object,
&existing,
MaterializationMode::PrivateCopy
),
Err(MaterializeError::QuarantinedObject { .. })
));
assert_eq!(fs::read(&existing).unwrap(), b"older output");
}
#[test]
fn corrupt_materialization_durably_excludes_the_bad_location() {
let dir = tempfile::tempdir().unwrap();
let (mut store, _layout, object) = store_with_object(dir.path(), b"valid bytes");
let source = PathBuf::from(&store.object_locations(&object).unwrap()[0].0);
fs::write(&source, b"corrupt bytes").unwrap();
let destination = dir.path().join("out.rlib");
fs::write(&destination, b"old output").unwrap();
assert!(matches!(
materialize_object(
&mut store,
&object,
&destination,
MaterializationMode::PrivateCopy
),
Err(MaterializeError::ContentMismatch { .. })
));
assert_eq!(fs::read(&destination).unwrap(), b"old output");
assert_eq!(fs::read(&source).unwrap(), b"corrupt bytes");
drop(store);
let engine = RusqliteEngine::open(&dir.path().join("meta.sqlite")).unwrap();
let mut reopened = SqlMetadataStore::open(engine).unwrap();
reopened.intern_domain(object.domain);
assert!(reopened.object_locations(&object).unwrap().is_empty());
assert!(matches!(
materialize_object(
&mut reopened,
&object,
&destination,
MaterializationMode::PrivateCopy
),
Err(MaterializeError::NoUsableCopy { .. })
));
assert_eq!(fs::read(&destination).unwrap(), b"old output");
}
#[test]
fn cas_copy_opening_accepts_only_regular_files() {
let dir = tempfile::tempdir().unwrap();
let source = dir.path().join("copy");
fs::write(&source, b"regular bytes").unwrap();
let mut file = open_cas_copy(source.to_str().unwrap()).unwrap();
let mut bytes = Vec::new();
file.read_to_end(&mut bytes).unwrap();
assert_eq!(bytes, b"regular bytes");
assert!(matches!(
open_cas_copy(dir.path().to_str().unwrap()),
Err(MaterializeError::Unreadable { .. })
));
assert!(matches!(
open_cas_copy(dir.path().join("missing").to_str().unwrap()),
Err(MaterializeError::Unreadable { .. })
));
}
#[cfg(unix)]
#[test]
fn cas_copy_opening_refuses_symlinks_and_unbounded_devices() {
let dir = tempfile::tempdir().unwrap();
let source = dir.path().join("copy");
let alias = dir.path().join("alias");
fs::write(&source, b"valid object bytes").unwrap();
std::os::unix::fs::symlink(&source, &alias).unwrap();
assert!(matches!(
open_cas_copy(alias.to_str().unwrap()),
Err(MaterializeError::Unreadable { .. })
));
assert!(matches!(
open_cas_copy("/dev/zero"),
Err(MaterializeError::Unreadable { .. })
));
}
#[test]
fn materializing_over_an_existing_file_replaces_it_atomically() {
let dir = scratch_dir("replace");
let bytes = b"new committed output";
let (mut store, _layout, object) = store_with_object(&dir, bytes);
let destination = dir.join("out.rlib");
fs::write(&destination, b"a stale artifact from an older build").unwrap();
materialize_object(
&mut store,
&object,
&destination,
MaterializationMode::PrivateCopy,
)
.expect("materialize");
assert_eq!(fs::read(&destination).unwrap(), bytes);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn failed_staging_metadata_preserves_existing_bytes_and_freshness() {
for mode in [
MaterializationMode::PrivateCopy,
MaterializationMode::VerifiedCowReflink,
] {
let dir = tempfile::tempdir().unwrap();
let (mut store, _layout, object) = store_with_object(dir.path(), b"new bytes");
let destination = dir.path().join("out.rmeta");
fs::write(&destination, b"previous output").unwrap();
let previous = FileTime::from_unix_time(1_000_000_000, 0);
filetime::set_file_mtime(&destination, previous).unwrap();
let original_stamp = fs::metadata(&destination).unwrap().modified().unwrap();
let calls = std::cell::Cell::new(0);
let failure =
materialize_object_prepared(&mut store, &object, &destination, mode, |staged| {
calls.set(calls.get() + 1);
assert_eq!(staged.metadata()?.len(), 9);
assert_eq!(fs::read(&destination)?, b"previous output");
staged.set_modified(SystemTime::UNIX_EPOCH)?;
Err(std::io::Error::other("injected metadata failure"))
})
.unwrap_err();
assert_eq!(
calls.get(),
1,
"metadata is prepared before one publication"
);
assert!(matches!(
failure,
MaterializeError::Io {
step: "prepare-staging-metadata",
..
}
));
assert_eq!(fs::read(&destination).unwrap(), b"previous output");
assert_eq!(
fs::metadata(&destination).unwrap().modified().unwrap(),
original_stamp
);
assert!(fs::read_dir(dir.path()).unwrap().all(|entry| {
!entry
.unwrap()
.file_name()
.to_string_lossy()
.starts_with(".rabs-mat-")
}));
}
}
#[test]
fn failed_staging_metadata_does_not_publish_a_new_destination() {
let dir = tempfile::tempdir().unwrap();
let (mut store, _layout, object) = store_with_object(dir.path(), b"new bytes");
let destination = dir.path().join("new.rmeta");
let result = materialize_object_prepared(
&mut store,
&object,
&destination,
MaterializationMode::PrivateCopy,
|_| Err(std::io::Error::other("injected metadata failure")),
);
assert!(result.is_err());
assert!(!destination.exists());
assert!(fs::read_dir(dir.path()).unwrap().all(|entry| {
!entry
.unwrap()
.file_name()
.to_string_lossy()
.starts_with(".rabs-mat-")
}));
}
#[test]
fn action_freshness_is_installed_without_changing_cas_metadata() {
for mode in [
MaterializationMode::PrivateCopy,
MaterializationMode::VerifiedCowReflink,
] {
let dir = tempfile::tempdir().unwrap();
let (mut store, _layout, object) = store_with_object(dir.path(), b"metadata bytes");
let source = PathBuf::from(&store.object_locations(&object).unwrap()[0].0);
let source_stamp = fs::metadata(&source).unwrap().modified().unwrap();
let output = PlannedActionOutput {
role: OutputRole::ProvisionalMetadata,
virtual_path: RawBytes::from("out.rmeta"),
object,
destination: dir.path().join("out.rmeta"),
};
let freshness = SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_234_567_890);
let receipt =
install_one(&mut store, &output, freshness, mode, &|_, _| Ok(())).unwrap();
assert_eq!(receipt.bytes, 14);
assert_eq!(receipt.destination, output.destination);
assert_eq!(fs::read(&output.destination).unwrap(), b"metadata bytes");
assert_eq!(
fs::metadata(&output.destination)
.unwrap()
.modified()
.unwrap(),
freshness
);
assert_eq!(
fs::metadata(&source).unwrap().modified().unwrap(),
source_stamp
);
}
}
#[test]
fn the_forbidden_hardlink_mode_demonstrably_corrupts() {
let dir = scratch_dir("hardlink-hazard");
let cas_object = dir.join("cas-object.rlib");
fs::write(&cas_object, b"immutable cas bytes").unwrap();
let before = fingerprint(&cas_object);
let alias = dir.join("aliased-out.rlib");
fs::hard_link(&cas_object, &alias).unwrap();
fs::write(&alias, b"corrupted through the alias").unwrap();
assert_ne!(
fingerprint(&cas_object),
before,
"the hazard is real: alias mutation rewrites CAS bytes — \
which is exactly why no writable-hardlink mode exists"
);
let _ = fs::remove_dir_all(&dir);
}
use crate::blob_store::{BlobStoreLayout, DurabilityPolicy, PutLimits, put_if_absent};
use crate::digest_set::digest_set;
use crate::metadata_store::{RusqliteEngine, SqlMetadataStore};
use rabs_protocol::raw_bytes::RawBytes;
use rabs_protocol::result_identity::OutputRole;
type TestStore = SqlMetadataStore<RusqliteEngine>;
fn k004_fixture(name: &str) -> (PathBuf, BlobStoreLayout, TestStore) {
let dir = scratch_dir(name);
let layout = BlobStoreLayout::open(&dir.join("blobs")).unwrap();
let engine = RusqliteEngine::open(&dir.join("meta.sqlite")).unwrap();
let store = SqlMetadataStore::open(engine).unwrap();
(dir, layout, store)
}
fn planned(
dir: &Path,
layout: &BlobStoreLayout,
store: &mut TestStore,
role: OutputRole,
name: &str,
bytes: &[u8],
) -> PlannedActionOutput {
let declared = digest_set(bytes, DigestRequest::default(), None)
.unwrap()
.atp_content_id;
let mut reader = bytes;
put_if_absent(
layout,
store,
&declared,
&mut reader,
PutLimits::default(),
DurabilityPolicy::FULL,
)
.expect("put");
PlannedActionOutput {
role,
virtual_path: RawBytes::new(name.as_bytes().to_vec()),
object: declared,
destination: dir.join(name),
}
}
fn rot(store: &mut TestStore, object: &TypedDigest) {
let cas_path = store.object_locations(object).unwrap()[0].0.clone();
fs::write(cas_path, b"tampered!!!!").unwrap();
}
#[test]
fn rmeta_installs_before_everything_else_even_when_tail_copy_is_corrupt() {
let (dir, layout, mut store) = k004_fixture("k004-head-first");
let head = planned(
&dir,
&layout,
&mut store,
OutputRole::ProvisionalMetadata,
"libfeat.rmeta",
b"the exact provisional metadata",
);
let tail_a = planned(
&dir,
&layout,
&mut store,
OutputRole::Materializable,
"b_libfeat.rlib",
b"codegen a",
);
let tail_b = planned(
&dir,
&layout,
&mut store,
OutputRole::Materializable,
"a_libfeat.rlib",
b"codegen b",
);
rot(&mut store, &tail_b.object);
let failure = materialize_action_outputs(
&mut store,
&[tail_a, tail_b.clone(), head.clone()],
MaterializationMode::PrivateCopy,
)
.expect_err("corrupt tail copy must abort");
assert!(
matches!(failure.error, MaterializeError::ContentMismatch { .. }),
"unexpected error: {:?}",
failure.error
);
assert_eq!(
fs::read(&head.destination).unwrap(),
b"the exact provisional metadata",
".rmeta must be fully installed before the tail failed"
);
assert_eq!(failure.installed.len(), 1);
assert_eq!(failure.installed[0].virtual_path, head.virtual_path);
assert!(
!tail_b.destination.exists(),
"the corrupt copy must install nothing"
);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn corrupt_rmeta_aborts_before_any_tail_output_is_installed() {
let (dir, layout, mut store) = k004_fixture("k004-corrupt-head");
let head = planned(
&dir,
&layout,
&mut store,
OutputRole::ProvisionalMetadata,
"libx.rmeta",
b"provisional metadata bytes",
);
let tail = planned(
&dir,
&layout,
&mut store,
OutputRole::Materializable,
"libx.rlib",
b"codegen",
);
rot(&mut store, &head.object);
let failure = materialize_action_outputs(
&mut store,
&[tail, head.clone()],
MaterializationMode::PrivateCopy,
)
.expect_err("corrupt head must abort");
assert!(matches!(
failure.error,
MaterializeError::ContentMismatch { .. }
));
assert!(failure.installed.is_empty());
assert!(!dir.join("libx.rlib").exists(), "no tail output may exist");
assert!(!head.destination.exists());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn freshness_stamp_is_one_coherent_instant_newer_than_preexisting_state() {
let (dir, layout, mut store) = k004_fixture("k004-freshness");
let head = planned(
&dir,
&layout,
&mut store,
OutputRole::ProvisionalMetadata,
"m.rmeta",
b"meta",
);
let depinfo = planned(
&dir,
&layout,
&mut store,
OutputRole::DepInfo,
"lib.d",
b"dep info",
);
let rlib = planned(
&dir,
&layout,
&mut store,
OutputRole::Materializable,
"lib.rlib",
b"codegen",
);
for out in [&head, &depinfo, &rlib] {
fs::write(&out.destination, b"stale older build").unwrap();
}
let stale = filetime::FileTime::from_system_time(
std::time::SystemTime::now() - std::time::Duration::from_secs(3600),
);
for out in [&head, &depinfo, &rlib] {
filetime::set_file_mtime(&out.destination, stale).unwrap();
}
let receipt = materialize_action_outputs(
&mut store,
&[rlib, depinfo, head],
MaterializationMode::PrivateCopy,
)
.expect("hit");
assert_eq!(receipt.installed.len(), 3);
let stamps: Vec<_> = receipt
.installed
.iter()
.map(|done| {
filetime::FileTime::from_last_modification_time(
&fs::metadata(&done.destination).unwrap(),
)
})
.collect();
assert_eq!(stamps[0], stamps[1], "one coherent bundle stamp");
assert_eq!(stamps[1], stamps[2], "one coherent bundle stamp");
assert!(
stamps[0]
> filetime::FileTime::from_system_time(
std::time::SystemTime::now() - std::time::Duration::from_secs(60)
),
"hit outputs must be newer than recent inputs"
);
assert_eq!(fs::read(dir.join("m.rmeta")).unwrap(), b"meta");
assert_eq!(fs::read(dir.join("lib.rlib")).unwrap(), b"codegen");
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn receipt_order_is_deterministic_and_head_phase_is_measured() {
let (dir, layout, mut store) = k004_fixture("k004-receipt");
let head = planned(
&dir,
&layout,
&mut store,
OutputRole::ProvisionalMetadata,
"z.rmeta",
b"m",
);
let t1 = planned(
&dir,
&layout,
&mut store,
OutputRole::Materializable,
"c.rlib",
b"1",
);
let t2 = planned(
&dir,
&layout,
&mut store,
OutputRole::Materializable,
"a.rlib",
b"2",
);
let t3 = planned(&dir, &layout, &mut store, OutputRole::DepInfo, "b.d", b"3");
let receipt = materialize_action_outputs(
&mut store,
&[t3, t1, head.clone(), t2],
MaterializationMode::PrivateCopy,
)
.expect("hit");
let names: Vec<String> = receipt
.installed
.iter()
.map(|d| d.virtual_path.as_utf8().unwrap().to_owned())
.collect();
assert_eq!(
names,
vec![
"z.rmeta".to_owned(),
"a.rlib".to_owned(),
"b.d".to_owned(),
"c.rlib".to_owned()
],
"head first, then deterministic virtual-path byte order"
);
assert!(receipt.head_nanos >= receipt.installed[0].nanos);
assert!(receipt.tail_nanos > 0);
assert!(receipt.total_nanos >= receipt.head_nanos + receipt.tail_nanos);
assert!(
receipt.head_nanos < 50_000_000,
"small-artifact head phase took {}ns; the <50ms hit target is \
a property this layer must exhibit on trivial inputs",
receipt.head_nanos
);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn duplicate_destinations_are_refused_before_any_installation() {
let (dir, layout, mut store) = k004_fixture("k004-duplicate");
let a = planned(
&dir,
&layout,
&mut store,
OutputRole::Materializable,
"same.rlib",
b"1",
);
let other = planned(
&dir,
&layout,
&mut store,
OutputRole::DepInfo,
"other.d",
b"2",
);
let collision = PlannedActionOutput {
role: OutputRole::Materializable,
virtual_path: RawBytes::new(b"different-virtual".to_vec()),
object: other.object.clone(),
destination: a.destination.clone(),
};
let failure = materialize_action_outputs(
&mut store,
&[a.clone(), collision],
MaterializationMode::PrivateCopy,
)
.expect_err("duplicate destination");
assert!(matches!(
failure.error,
MaterializeError::DuplicateDestination { .. }
));
assert!(failure.installed.is_empty());
assert!(!a.destination.exists(), "nothing installed");
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn lexical_destination_aliases_are_refused_without_installation() {
let dir = tempfile::tempdir().unwrap();
let (mut store, _layout, object) = store_with_object(dir.path(), b"artifact");
let first = PlannedActionOutput {
role: OutputRole::Materializable,
virtual_path: RawBytes::from("first"),
object,
destination: dir.path().join("out.rlib"),
};
let alias = PlannedActionOutput {
virtual_path: RawBytes::from("second"),
destination: dir.path().join(".").join("out.rlib"),
..first.clone()
};
let failure = materialize_action_outputs(
&mut store,
&[first.clone(), alias],
MaterializationMode::PrivateCopy,
)
.unwrap_err();
assert!(matches!(
failure.error,
MaterializeError::DuplicateDestination { .. }
));
assert!(failure.installed.is_empty());
assert!(!first.destination.exists());
let relative = PlannedActionOutput {
destination: PathBuf::from("relative-output.rlib"),
..first.clone()
};
let absolute = PlannedActionOutput {
destination: std::env::current_dir().unwrap().join(&relative.destination),
..first
};
assert!(matches!(
validate_action_destinations(&[relative, absolute]),
Err(MaterializeError::DuplicateDestination { .. })
));
}
#[test]
fn ancestor_destinations_are_refused_before_either_installation_order() {
let dir = tempfile::tempdir().unwrap();
let (mut store, _layout, object) = store_with_object(dir.path(), b"artifact");
let parent = PlannedActionOutput {
role: OutputRole::Materializable,
virtual_path: RawBytes::from("parent"),
object,
destination: dir.path().join("bundle"),
};
let child = PlannedActionOutput {
role: OutputRole::ProvisionalMetadata,
virtual_path: RawBytes::from("child"),
destination: parent.destination.join("child.rmeta"),
..parent.clone()
};
for outputs in [
[parent.clone(), child.clone()],
[child.clone(), parent.clone()],
] {
let failure =
materialize_action_outputs(&mut store, &outputs, MaterializationMode::PrivateCopy)
.unwrap_err();
assert!(matches!(
failure.error,
MaterializeError::OverlappingDestinations { .. }
));
assert!(failure.installed.is_empty());
assert!(!parent.destination.exists());
}
}
#[test]
fn parent_traversal_is_not_lexically_collapsed_through_possible_symlinks() {
let dir = tempfile::tempdir().unwrap();
let (mut store, _layout, object) = store_with_object(dir.path(), b"artifact");
let output = PlannedActionOutput {
role: OutputRole::ProvisionalMetadata,
virtual_path: RawBytes::from("out.rmeta"),
object,
destination: dir.path().join("untrusted/../out.rmeta"),
};
let failure =
materialize_action_outputs(&mut store, &[output], MaterializationMode::PrivateCopy)
.unwrap_err();
assert!(matches!(
failure.error,
MaterializeError::UnsafeDestination { .. }
));
assert!(failure.installed.is_empty());
assert!(!dir.path().join("untrusted").exists());
assert!(!dir.path().join("out.rmeta").exists());
}
#[cfg(unix)]
#[test]
fn distinct_non_utf8_destinations_do_not_collide_through_lossy_display() {
use std::os::unix::ffi::OsStringExt;
let dir = tempfile::tempdir().unwrap();
let (mut store, _layout, object) = store_with_object(dir.path(), b"artifact");
let first = PlannedActionOutput {
role: OutputRole::Materializable,
virtual_path: RawBytes::from("first"),
object,
destination: dir
.path()
.join(std::ffi::OsString::from_vec(b"out-\xff".to_vec())),
};
let second = PlannedActionOutput {
virtual_path: RawBytes::from("second"),
destination: dir
.path()
.join(std::ffi::OsString::from_vec(b"out-\xfe".to_vec())),
..first.clone()
};
assert_eq!(
first.destination.to_string_lossy(),
second.destination.to_string_lossy()
);
let receipt = materialize_action_outputs(
&mut store,
&[first.clone(), second.clone()],
MaterializationMode::PrivateCopy,
)
.unwrap();
assert_eq!(receipt.installed.len(), 2);
assert_eq!(fs::read(&first.destination).unwrap(), b"artifact");
assert_eq!(fs::read(&second.destination).unwrap(), b"artifact");
}
#[test]
fn empty_plan_is_a_vacuous_success_and_unsupported_modes_refuse() {
let (dir, layout, mut store) = k004_fixture("k004-empty");
let receipt = materialize_action_outputs(&mut store, &[], MaterializationMode::PrivateCopy)
.expect("empty plan");
assert_eq!(receipt.installed, Vec::new());
assert!(receipt.head_nanos < 50_000_000);
assert!(receipt.tail_nanos < 50_000_000);
let out = planned(
&dir,
&layout,
&mut store,
OutputRole::Materializable,
"x.rlib",
b"x",
);
assert!(matches!(
materialize_action_outputs(&mut store, &[out], MaterializationMode::ReadOnlyBind),
Err(ActionMaterializeFailure {
error: MaterializeError::ModeUnsupported(MaterializationMode::ReadOnlyBind),
..
})
));
let _ = fs::remove_dir_all(&dir);
}
use crate::materialization::StoredArtifactNotification;
fn notification(destination: &Path, body: &str) -> StoredArtifactNotification {
StoredArtifactNotification {
exact_line: body.as_bytes().to_vec(),
announced_destination: destination.to_path_buf(),
}
}
#[test]
fn replay_is_gated_on_materialization_and_byte_verbatim() {
let (dir, layout, mut store) = k004_fixture("k005-gated");
let head = planned(
&dir,
&layout,
&mut store,
OutputRole::ProvisionalMetadata,
"libg.rmeta",
b"meta",
);
let depinfo = planned(
&dir,
&layout,
&mut store,
OutputRole::DepInfo,
"libg.d",
b"d",
);
let rlib = planned(
&dir,
&layout,
&mut store,
OutputRole::Materializable,
"libg.rlib",
b"code",
);
let receipt = materialize_action_outputs(
&mut store,
&[depinfo.clone(), rlib.clone(), head.clone()],
MaterializationMode::PrivateCopy,
)
.expect("hit");
let lines = [
notification(
&rlib.destination,
r#"{"artifact":"/wt/target/debug/libg.rlib","notification":true}"#,
),
notification(
&head.destination,
r#"{"artifact":"/wt/target/debug/libg.rmeta","notification":true}"#,
),
];
let stream = plan_notification_replay(&receipt, &lines).expect("stream");
assert_eq!(stream.len(), 2, "exactly the stored lines, once each");
assert!(
stream[0].windows(11).any(|w| w == b"libg.rmeta\""),
".rmeta notification must lead: it is what unblocks dependents"
);
assert_eq!(
stream[1], lines[0].exact_line,
"tail line is byte-verbatim after its output"
);
}
#[test]
fn unbound_duplicate_and_empty_lines_are_typed_refusals() {
let (dir, layout, mut store) = k004_fixture("k005-refusals");
let head = planned(
&dir,
&layout,
&mut store,
OutputRole::ProvisionalMetadata,
"m.rmeta",
b"m",
);
let receipt = materialize_action_outputs(
&mut store,
std::slice::from_ref(&head),
MaterializationMode::PrivateCopy,
)
.expect("hit");
let stranger = dir.join("not-in-plan.rlib");
assert_eq!(
plan_notification_replay(&receipt, &[notification(&stranger, r#"{"artifact":"x"}"#)],),
Err(ReplayPlanError::UnboundLine {
path: stranger.to_string_lossy().into_owned()
}),
"no proof of completeness -> refuse, never emit"
);
let line = notification(&head.destination, r#"{"artifact":"m"}"#);
assert_eq!(
plan_notification_replay(&receipt, &[line.clone(), line]),
Err(ReplayPlanError::DuplicateAnnouncement {
path: head.destination.to_string_lossy().into_owned()
}),
"exactly-once makes duplicates unrepresentable"
);
assert_eq!(
plan_notification_replay(&receipt, &[notification(&head.destination, "")]),
Err(ReplayPlanError::EmptyLine),
);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn cache_hit_stream_matches_stock_pipelining() {
let (dir, layout, mut store) = k004_fixture("k005-stock-pipelining");
let head = planned(
&dir,
&layout,
&mut store,
OutputRole::ProvisionalMetadata,
"libs.rmeta",
b"meta",
);
let rlib = planned(
&dir,
&layout,
&mut store,
OutputRole::Materializable,
"libs.rlib",
b"code",
);
let receipt = materialize_action_outputs(
&mut store,
&[rlib.clone(), head.clone()],
MaterializationMode::PrivateCopy,
)
.expect("hit");
let rmeta_line =
br#"{"artifact":"/wt/target/debug/deps/libs.rmeta","focus":["Meta"]}"#.to_vec();
let rlib_line =
br#"{"artifact":"/wt/target/debug/deps/libs.rlib","focus":["Codegen"]}"#.to_vec();
let stock_order = vec![rmeta_line.clone(), rlib_line.clone()];
let stream = plan_notification_replay(
&receipt,
&[
StoredArtifactNotification {
exact_line: rlib_line,
announced_destination: rlib.destination.clone(),
},
StoredArtifactNotification {
exact_line: rmeta_line,
announced_destination: head.destination.clone(),
},
],
)
.expect("stream");
assert_eq!(
stream, stock_order,
".rmeta leads; each line follows its file"
);
for (emitted, stored) in stream.iter().zip(stock_order.iter()) {
assert_eq!(emitted, stored);
}
let _ = fs::remove_dir_all(&dir);
}
}