use prikk_error::{PrikkError, Result};
use crate::fsutil::{append_file_required, truncate_file_empty_required};
use crate::generation::{self, GenerationRecord};
use crate::layout::{LockableContainer, RepositoryLayout};
use crate::lock::acquire_container_locks;
use crate::received_index::{
ReceivedIndexEntry, encode_received_index_record, replay_received_index,
};
use crate::refs::{PointerIndexEntry, encode_pointer_index_record, replay_pointer_index};
use crate::trust_index::{encode_trust_policy_record, replay_trust_policy};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CompactionReport {
pub container: LockableContainer,
pub entries_before: usize,
pub entries_after: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum CompactionMode {
Execute,
PlanOnly,
}
fn run_ref_pointer_index_compaction(
layout: &RepositoryLayout,
mode: CompactionMode,
) -> Result<CompactionReport> {
layout.require_current_format()?;
let _lock = acquire_container_locks(layout, &[LockableContainer::RefPointerIndex])?;
let generation_log_path = layout.ref_pointer_index_generation_log_path();
let live_slot = generation::resolve_live_slot(layout, &generation_log_path)?;
let replay = replay_pointer_index(layout)?;
if replay.has_item_failure() {
return Err(PrikkError::Integrity(
"ref pointer index has a damaged entry; compaction refuses to run on a corrupt \
container -- run doctor first"
.to_string(),
));
}
let entries_before = replay.entries.len();
let mut compacted: Vec<PointerIndexEntry> = Vec::new();
for entry in replay.entries {
compacted
.retain(|existing: &PointerIndexEntry| existing.ref_name_key != entry.ref_name_key);
compacted.push(entry);
}
let entries_after = compacted.len();
if mode == CompactionMode::Execute {
let target_slot = live_slot.other();
let target_relative =
layout.repository_relative(&layout.ref_pointer_index_slot_path(target_slot))?;
truncate_file_empty_required(layout.repository_mutation_root(), &target_relative)?;
let mut buffer = Vec::new();
for entry in &compacted {
buffer.extend_from_slice(&encode_pointer_index_record(entry)?);
}
append_file_required(layout.repository_mutation_root(), &target_relative, &buffer)?;
generation::append_generation_record(
layout,
&generation_log_path,
&GenerationRecord {
live_slot: target_slot,
},
)?;
}
Ok(CompactionReport {
container: LockableContainer::RefPointerIndex,
entries_before,
entries_after,
})
}
pub fn compact_ref_pointer_index(layout: &RepositoryLayout) -> Result<CompactionReport> {
run_ref_pointer_index_compaction(layout, CompactionMode::Execute)
}
pub fn plan_compact_ref_pointer_index(layout: &RepositoryLayout) -> Result<CompactionReport> {
run_ref_pointer_index_compaction(layout, CompactionMode::PlanOnly)
}
fn run_received_index_compaction(
layout: &RepositoryLayout,
mode: CompactionMode,
) -> Result<CompactionReport> {
layout.require_current_format()?;
let _lock = acquire_container_locks(layout, &[LockableContainer::ReceivedIndex])?;
let generation_log_path = layout.received_index_generation_log_path();
let live_slot = generation::resolve_live_slot(layout, &generation_log_path)?;
let replay = replay_received_index(layout)?;
if replay.has_item_failure() {
return Err(PrikkError::Integrity(
"received-ref index has a damaged entry; compaction refuses to run on a corrupt \
container -- run doctor first"
.to_string(),
));
}
let entries_before = replay.entries.len();
let mut compacted: Vec<ReceivedIndexEntry> = Vec::new();
for entry in replay.entries {
compacted
.retain(|existing: &ReceivedIndexEntry| existing.ref_name_key != entry.ref_name_key);
compacted.push(entry);
}
let entries_after = compacted.len();
if mode == CompactionMode::Execute {
let target_slot = live_slot.other();
let target_relative =
layout.repository_relative(&layout.received_index_slot_path(target_slot))?;
truncate_file_empty_required(layout.repository_mutation_root(), &target_relative)?;
let mut buffer = Vec::new();
for entry in &compacted {
buffer.extend_from_slice(&encode_received_index_record(entry)?);
}
append_file_required(layout.repository_mutation_root(), &target_relative, &buffer)?;
generation::append_generation_record(
layout,
&generation_log_path,
&GenerationRecord {
live_slot: target_slot,
},
)?;
}
Ok(CompactionReport {
container: LockableContainer::ReceivedIndex,
entries_before,
entries_after,
})
}
pub fn compact_received_index(layout: &RepositoryLayout) -> Result<CompactionReport> {
run_received_index_compaction(layout, CompactionMode::Execute)
}
pub fn plan_compact_received_index(layout: &RepositoryLayout) -> Result<CompactionReport> {
run_received_index_compaction(layout, CompactionMode::PlanOnly)
}
fn run_trust_policy_compaction(
layout: &RepositoryLayout,
mode: CompactionMode,
) -> Result<CompactionReport> {
layout.require_current_format()?;
let _lock = acquire_container_locks(layout, &[LockableContainer::TrustPolicy])?;
let generation_log_path = layout.trust_policy_generation_log_path();
let live_slot = generation::resolve_live_slot(layout, &generation_log_path)?;
let replay = replay_trust_policy(layout)?;
if replay.has_item_failure() {
return Err(PrikkError::Integrity(
"trust policy container has a damaged snapshot; compaction refuses to run on a \
corrupt container -- run doctor first"
.to_string(),
));
}
let entries_before = replay.entries.len();
let last_snapshot = replay.entries.into_iter().next_back();
let entries_after = usize::from(last_snapshot.is_some());
if mode == CompactionMode::Execute {
let target_slot = live_slot.other();
let target_relative =
layout.repository_relative(&layout.trust_policy_container_slot_path(target_slot))?;
truncate_file_empty_required(layout.repository_mutation_root(), &target_relative)?;
if let Some(entry) = &last_snapshot {
let record = encode_trust_policy_record(entry)?;
append_file_required(layout.repository_mutation_root(), &target_relative, &record)?;
}
generation::append_generation_record(
layout,
&generation_log_path,
&GenerationRecord {
live_slot: target_slot,
},
)?;
}
Ok(CompactionReport {
container: LockableContainer::TrustPolicy,
entries_before,
entries_after,
})
}
pub fn compact_trust_policy(layout: &RepositoryLayout) -> Result<CompactionReport> {
run_trust_policy_compaction(layout, CompactionMode::Execute)
}
pub fn plan_compact_trust_policy(layout: &RepositoryLayout) -> Result<CompactionReport> {
run_trust_policy_compaction(layout, CompactionMode::PlanOnly)
}
#[cfg(test)]
mod tests;