1use prikk_error::{PrikkError, Result};
43
44use crate::fsutil::{append_file_required, truncate_file_empty_required};
45use crate::generation::{self, GenerationRecord};
46use crate::layout::{LockableContainer, RepositoryLayout};
47use crate::lock::acquire_container_locks;
48use crate::received_index::{
49 ReceivedIndexEntry, encode_received_index_record, replay_received_index,
50};
51use crate::refs::{PointerIndexEntry, encode_pointer_index_record, replay_pointer_index};
52use crate::trust_index::{encode_trust_policy_record, replay_trust_policy};
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub struct CompactionReport {
61 pub container: LockableContainer,
63 pub entries_before: usize,
65 pub entries_after: usize,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72enum CompactionMode {
73 Execute,
75 PlanOnly,
77}
78
79fn run_ref_pointer_index_compaction(
80 layout: &RepositoryLayout,
81 mode: CompactionMode,
82) -> Result<CompactionReport> {
83 layout.require_current_format()?;
84 let _lock = acquire_container_locks(layout, &[LockableContainer::RefPointerIndex])?;
85 let generation_log_path = layout.ref_pointer_index_generation_log_path();
86 let live_slot = generation::resolve_live_slot(layout, &generation_log_path)?;
87
88 let replay = replay_pointer_index(layout)?;
89 if replay.has_item_failure() {
90 return Err(PrikkError::Integrity(
91 "ref pointer index has a damaged entry; compaction refuses to run on a corrupt \
92 container -- run doctor first"
93 .to_string(),
94 ));
95 }
96 let entries_before = replay.entries.len();
97 let mut compacted: Vec<PointerIndexEntry> = Vec::new();
98 for entry in replay.entries {
99 compacted
100 .retain(|existing: &PointerIndexEntry| existing.ref_name_key != entry.ref_name_key);
101 compacted.push(entry);
102 }
103 let entries_after = compacted.len();
104
105 if mode == CompactionMode::Execute {
106 let target_slot = live_slot.other();
107 let target_relative =
108 layout.repository_relative(&layout.ref_pointer_index_slot_path(target_slot))?;
109 truncate_file_empty_required(layout.repository_mutation_root(), &target_relative)?;
110 let mut buffer = Vec::new();
111 for entry in &compacted {
112 buffer.extend_from_slice(&encode_pointer_index_record(entry)?);
113 }
114 append_file_required(layout.repository_mutation_root(), &target_relative, &buffer)?;
115 generation::append_generation_record(
116 layout,
117 &generation_log_path,
118 &GenerationRecord {
119 live_slot: target_slot,
120 },
121 )?;
122 }
123
124 Ok(CompactionReport {
125 container: LockableContainer::RefPointerIndex,
126 entries_before,
127 entries_after,
128 })
129}
130
131pub fn compact_ref_pointer_index(layout: &RepositoryLayout) -> Result<CompactionReport> {
135 run_ref_pointer_index_compaction(layout, CompactionMode::Execute)
136}
137
138pub fn plan_compact_ref_pointer_index(layout: &RepositoryLayout) -> Result<CompactionReport> {
140 run_ref_pointer_index_compaction(layout, CompactionMode::PlanOnly)
141}
142
143fn run_received_index_compaction(
144 layout: &RepositoryLayout,
145 mode: CompactionMode,
146) -> Result<CompactionReport> {
147 layout.require_current_format()?;
148 let _lock = acquire_container_locks(layout, &[LockableContainer::ReceivedIndex])?;
149 let generation_log_path = layout.received_index_generation_log_path();
150 let live_slot = generation::resolve_live_slot(layout, &generation_log_path)?;
151
152 let replay = replay_received_index(layout)?;
153 if replay.has_item_failure() {
154 return Err(PrikkError::Integrity(
155 "received-ref index has a damaged entry; compaction refuses to run on a corrupt \
156 container -- run doctor first"
157 .to_string(),
158 ));
159 }
160 let entries_before = replay.entries.len();
161 let mut compacted: Vec<ReceivedIndexEntry> = Vec::new();
162 for entry in replay.entries {
163 compacted
164 .retain(|existing: &ReceivedIndexEntry| existing.ref_name_key != entry.ref_name_key);
165 compacted.push(entry);
166 }
167 let entries_after = compacted.len();
168
169 if mode == CompactionMode::Execute {
170 let target_slot = live_slot.other();
171 let target_relative =
172 layout.repository_relative(&layout.received_index_slot_path(target_slot))?;
173 truncate_file_empty_required(layout.repository_mutation_root(), &target_relative)?;
174 let mut buffer = Vec::new();
175 for entry in &compacted {
176 buffer.extend_from_slice(&encode_received_index_record(entry)?);
177 }
178 append_file_required(layout.repository_mutation_root(), &target_relative, &buffer)?;
179 generation::append_generation_record(
180 layout,
181 &generation_log_path,
182 &GenerationRecord {
183 live_slot: target_slot,
184 },
185 )?;
186 }
187
188 Ok(CompactionReport {
189 container: LockableContainer::ReceivedIndex,
190 entries_before,
191 entries_after,
192 })
193}
194
195pub fn compact_received_index(layout: &RepositoryLayout) -> Result<CompactionReport> {
198 run_received_index_compaction(layout, CompactionMode::Execute)
199}
200
201pub fn plan_compact_received_index(layout: &RepositoryLayout) -> Result<CompactionReport> {
203 run_received_index_compaction(layout, CompactionMode::PlanOnly)
204}
205
206fn run_trust_policy_compaction(
207 layout: &RepositoryLayout,
208 mode: CompactionMode,
209) -> Result<CompactionReport> {
210 layout.require_current_format()?;
211 let _lock = acquire_container_locks(layout, &[LockableContainer::TrustPolicy])?;
212 let generation_log_path = layout.trust_policy_generation_log_path();
213 let live_slot = generation::resolve_live_slot(layout, &generation_log_path)?;
214
215 let replay = replay_trust_policy(layout)?;
216 if replay.has_item_failure() {
217 return Err(PrikkError::Integrity(
218 "trust policy container has a damaged snapshot; compaction refuses to run on a \
219 corrupt container -- run doctor first"
220 .to_string(),
221 ));
222 }
223 let entries_before = replay.entries.len();
224 let last_snapshot = replay.entries.into_iter().next_back();
225 let entries_after = usize::from(last_snapshot.is_some());
226
227 if mode == CompactionMode::Execute {
228 let target_slot = live_slot.other();
229 let target_relative =
230 layout.repository_relative(&layout.trust_policy_container_slot_path(target_slot))?;
231 truncate_file_empty_required(layout.repository_mutation_root(), &target_relative)?;
232 if let Some(entry) = &last_snapshot {
233 let record = encode_trust_policy_record(entry)?;
234 append_file_required(layout.repository_mutation_root(), &target_relative, &record)?;
235 }
236 generation::append_generation_record(
237 layout,
238 &generation_log_path,
239 &GenerationRecord {
240 live_slot: target_slot,
241 },
242 )?;
243 }
244
245 Ok(CompactionReport {
246 container: LockableContainer::TrustPolicy,
247 entries_before,
248 entries_after,
249 })
250}
251
252pub fn compact_trust_policy(layout: &RepositoryLayout) -> Result<CompactionReport> {
257 run_trust_policy_compaction(layout, CompactionMode::Execute)
258}
259
260pub fn plan_compact_trust_policy(layout: &RepositoryLayout) -> Result<CompactionReport> {
262 run_trust_policy_compaction(layout, CompactionMode::PlanOnly)
263}
264
265#[cfg(test)]
266mod tests;