1use std::collections::HashSet;
7use std::io::{self, Cursor, Read};
8use std::ops::ControlFlow;
9use std::path::Path;
10use std::sync::Arc;
11
12use flate2::{Decompress, FlushDecompress, Status};
13
14use crate::error::{Error, Result};
15use crate::objects::{HashAlgo, Object, ObjectId, ObjectIdPrefix, ObjectInfo, ObjectKind};
16use crate::pack::{self, read_local_pack_indexes_cached, reprepare_pack_directory_on_miss};
17use crate::pack_index::PackIndex;
18use crate::pack_store::PackStore;
19use crate::zlib_inflate::{LOOSE_INFLATE_INPUT_CHUNK, LOOSE_INFLATE_OUTPUT_CHUNK};
20
21use super::{ObjectStore, ObjectStream};
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub struct PackFilter {
26 pub include_promisor: bool,
28 pub include_cruft: bool,
30}
31
32impl Default for PackFilter {
33 fn default() -> Self {
34 Self {
35 include_promisor: false,
36 include_cruft: true,
37 }
38 }
39}
40
41pub struct PackedObjects {
43 store: Arc<PackStore>,
44}
45
46impl std::fmt::Debug for PackedObjects {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 f.debug_struct("PackedObjects")
49 .field("objects_dir", &self.store.objects_dir())
50 .finish()
51 }
52}
53
54impl PackedObjects {
55 #[must_use]
57 pub fn new(store: Arc<PackStore>) -> Self {
58 Self { store }
59 }
60
61 #[must_use]
63 pub fn pack_store(&self) -> &Arc<PackStore> {
64 &self.store
65 }
66
67 pub fn object_ids_from_index_path(idx_path: &Path) -> Result<HashSet<ObjectId>> {
73 let idx = pack::read_pack_index(idx_path)?;
74 let mut out = HashSet::new();
75 for entry in idx.iter() {
76 if let Ok(oid) = ObjectId::from_bytes(entry.oid()) {
77 out.insert(oid);
78 }
79 }
80 Ok(out)
81 }
82
83 pub fn contains_filtered(&self, oid: &ObjectId, filter: PackFilter) -> Result<bool> {
92 self.with_pack(|objects_dir| {
93 for idx in filtered_indexes(objects_dir, filter)? {
94 if idx.contains(oid)? {
95 return Ok(true);
96 }
97 }
98 Ok(false)
99 })
100 }
101
102 pub fn for_each_promisor_object(
108 &self,
109 f: &mut dyn FnMut(&ObjectId) -> ControlFlow<()>,
110 ) -> Result<()> {
111 self.with_pack(|objects_dir| {
112 let indexes = read_local_pack_indexes_cached(objects_dir)?;
113 let mut seen = HashSet::new();
114 for idx in indexes {
115 if !idx.is_promisor {
116 continue;
117 }
118 for entry in idx.iter() {
119 let Ok(oid) = ObjectId::from_bytes(entry.oid()) else {
120 continue;
121 };
122 if !seen.insert(oid) {
123 continue;
124 }
125 if f(&oid).is_break() {
126 return Ok(());
127 }
128 }
129 }
130 Ok(())
131 })
132 }
133
134 pub fn kept_pack_object_ids(&self) -> Result<HashSet<ObjectId>> {
140 self.with_pack(collect_kept_pack_object_ids)
141 }
142
143 pub fn for_each_removable_pack_object(
152 &self,
153 f: &mut dyn FnMut(&ObjectId) -> ControlFlow<()>,
154 ) -> Result<()> {
155 self.with_pack(|objects_dir| for_each_removable_pack_object_ids(objects_dir, f))
156 }
157
158 pub fn freshen_pack_containing(&self, oid: &ObjectId) -> Result<bool> {
164 self.with_pack(|objects_dir| {
165 let indexes = read_local_pack_indexes_cached(objects_dir)?;
166 for idx in &indexes {
167 if !idx.contains(oid)? {
168 continue;
169 }
170 if idx.is_cruft {
171 return Ok(false);
172 }
173 return Ok(touch_pack_mtime(&idx.pack_path));
174 }
175 Ok(false)
176 })
177 }
178
179 fn with_pack<R>(&self, f: impl FnOnce(&std::path::Path) -> Result<R>) -> Result<R> {
180 if PackStore::read_context_matches_objects_dir(self.store.objects_dir()) {
181 return f(self.store.objects_dir());
182 }
183 PackStore::with_context(Arc::clone(&self.store), || f(self.store.objects_dir()))
184 }
185
186 fn hash_algo_from_indexes(&self) -> HashAlgo {
187 self.with_pack(|objects_dir| {
188 let Ok(indexes) = read_local_pack_indexes_cached(objects_dir) else {
189 return Ok(HashAlgo::Sha1);
190 };
191 Ok(indexes
192 .first()
193 .and_then(|idx| HashAlgo::from_len(idx.hash_bytes()))
194 .unwrap_or(HashAlgo::Sha1))
195 })
196 .unwrap_or(HashAlgo::Sha1)
197 }
198
199 fn read_from_packs(&self, oid: &ObjectId) -> Result<Option<Object>> {
200 self.with_pack(
201 |objects_dir| match pack::read_object_from_packs(objects_dir, oid) {
202 Ok(obj) => Ok(Some(obj)),
203 Err(Error::ObjectNotFound(_)) => Ok(None),
204 Err(err) => Err(err),
205 },
206 )
207 }
208
209 fn read_from_packs_into(
210 &self,
211 oid: &ObjectId,
212 buf: &mut Vec<u8>,
213 ) -> Result<Option<ObjectKind>> {
214 self.with_pack(|objects_dir| {
215 match pack::read_object_from_packs_into(objects_dir, oid, buf) {
216 Ok(kind) => Ok(Some(kind)),
217 Err(Error::ObjectNotFound(_)) => Ok(None),
218 Err(err) => Err(err),
219 }
220 })
221 }
222
223 fn read_info_from_packs(&self, oid: &ObjectId) -> Result<Option<ObjectInfo>> {
224 self.with_pack(
225 |objects_dir| match pack::read_object_info_from_packs(objects_dir, oid) {
226 Ok(info) => Ok(Some(info)),
227 Err(Error::ObjectNotFound(_)) => Ok(None),
228 Err(err) => Err(err),
229 },
230 )
231 }
232}
233
234impl ObjectStore for PackedObjects {
235 fn hash_algo(&self) -> HashAlgo {
236 self.hash_algo_from_indexes()
237 }
238
239 fn read(&self, oid: &ObjectId) -> Result<Option<Object>> {
240 self.read_from_packs(oid)
241 }
242
243 fn read_into(&self, oid: &ObjectId, buf: &mut Vec<u8>) -> Result<Option<ObjectKind>> {
244 self.read_from_packs_into(oid, buf)
245 }
246
247 fn read_info(&self, oid: &ObjectId) -> Result<Option<ObjectInfo>> {
248 self.read_info_from_packs(oid)
249 }
250
251 fn open_stream(&self, oid: &ObjectId) -> Result<Option<ObjectStream<'_>>> {
252 self.with_pack(|dir| {
253 let verify_crc = pack::count_local_packs_holding(dir, oid)? > 1;
254 let mut entry = pack::locate_packed_whole_object(dir, oid, verify_crc)?;
255 if matches!(entry, pack::PackedEntry::Absent) && reprepare_pack_directory_on_miss(dir)?
256 {
257 entry = pack::locate_packed_whole_object(dir, oid, verify_crc)?;
258 }
259 let pack::PackedEntry::Whole(whole) = entry else {
260 return stream_from_buffered_read(self, oid);
261 };
262 Ok(Some(ObjectStream {
263 kind: whole.kind,
264 size: whole.size,
265 reader: Box::new(PackPayloadStream::new(whole)),
266 }))
267 })
268 }
269
270 fn for_each_object(&self, f: &mut dyn FnMut(&ObjectId) -> ControlFlow<()>) -> Result<()> {
271 self.with_pack(|objects_dir| {
272 let indexes = read_local_pack_indexes_cached(objects_dir)?;
273 let mut seen = HashSet::new();
274 for idx in &indexes {
275 for entry in idx.iter() {
276 let Ok(oid) = ObjectId::from_bytes(entry.oid()) else {
277 continue;
278 };
279 if !seen.insert(oid) {
280 continue;
281 }
282 if f(&oid).is_break() {
283 return Ok(());
284 }
285 }
286 }
287 Ok(())
288 })
289 }
290
291 fn lookup_prefix(&self, prefix: &str, limit: usize, out: &mut Vec<ObjectId>) -> Result<()> {
292 if prefix.len() > self.hash_algo().hex_len() {
293 return Ok(());
294 }
295 let prefix = ObjectIdPrefix::from_hex(prefix, self.hash_algo())?;
296 self.with_pack(|objects_dir| {
297 let indexes = read_local_pack_indexes_cached(objects_dir)?;
298 let mut seen = HashSet::new();
299 let mut count = 0usize;
300 for idx in &indexes {
301 collect_prefix_from_index(idx, &prefix, limit, &mut count, out, &mut seen)?;
302 if limit != 0 && count >= limit {
303 break;
304 }
305 }
306 Ok(())
307 })
308 }
309
310 fn refresh(&self) -> Result<bool> {
311 self.with_pack(reprepare_pack_directory_on_miss)
312 }
313}
314
315fn for_each_removable_pack_object_ids(
316 objects_dir: &Path,
317 f: &mut dyn FnMut(&ObjectId) -> ControlFlow<()>,
318) -> Result<()> {
319 let pack_dir = objects_dir.join("pack");
320 let indexes = read_local_pack_indexes_cached(objects_dir)?;
321 let mut seen = HashSet::new();
322 for idx in indexes {
323 if idx.is_promisor || pack_index_has_keep_marker(&pack_dir, &idx.pack_path) {
324 continue;
325 }
326 let hash_bytes = idx.hash_bytes();
327 for entry in idx.iter() {
328 if entry.oid().len() != hash_bytes {
329 continue;
330 }
331 let Ok(oid) = ObjectId::from_bytes(entry.oid()) else {
332 continue;
333 };
334 if !seen.insert(oid) {
335 continue;
336 }
337 if f(&oid).is_break() {
338 return Ok(());
339 }
340 }
341 }
342 Ok(())
343}
344
345fn pack_index_has_keep_marker(pack_dir: &Path, pack_path: &Path) -> bool {
346 let Some(stem) = pack_path.file_stem().and_then(|s| s.to_str()) else {
347 return false;
348 };
349 pack_dir.join(format!("{stem}.keep")).is_file()
350}
351
352fn collect_kept_pack_object_ids(objects_dir: &Path) -> Result<HashSet<ObjectId>> {
353 let pack_dir = objects_dir.join("pack");
354 let mut kept = HashSet::new();
355 if !pack_dir.is_dir() {
356 return Ok(kept);
357 }
358 for entry in std::fs::read_dir(&pack_dir).map_err(Error::Io)? {
359 let entry = entry.map_err(Error::Io)?;
360 let path = entry.path();
361 if path.extension().is_some_and(|ext| ext == "keep") {
362 let idx_path = path.with_extension("idx");
363 if idx_path.is_file() {
364 if let Ok(oids) = pack::read_idx_object_ids(&idx_path) {
365 kept.extend(oids);
366 }
367 }
368 }
369 }
370 Ok(kept)
371}
372
373fn filtered_indexes(
374 objects_dir: &std::path::Path,
375 filter: PackFilter,
376) -> Result<Vec<Arc<PackIndex>>> {
377 let indexes = read_local_pack_indexes_cached(objects_dir)?;
378 Ok(indexes
379 .into_iter()
380 .filter(|idx| {
381 (filter.include_promisor || !idx.is_promisor) && (filter.include_cruft || !idx.is_cruft)
382 })
383 .collect())
384}
385
386fn collect_prefix_from_index(
387 idx: &PackIndex,
388 prefix: &ObjectIdPrefix,
389 limit: usize,
390 count: &mut usize,
391 out: &mut Vec<ObjectId>,
392 seen: &mut HashSet<ObjectId>,
393) -> Result<()> {
394 let lo = lower_bound_oid(idx, prefix.lowest().as_bytes());
395 let hi = upper_bound_oid(idx, prefix.highest().as_bytes());
396 for i in lo..hi {
397 let digest = idx.oid_at(i);
398 if !prefix.matches_digest(digest) {
399 continue;
400 }
401 let Ok(oid) = ObjectId::from_bytes(digest) else {
402 continue;
403 };
404 if !seen.insert(oid) {
405 continue;
406 }
407 out.push(oid);
408 *count += 1;
409 if limit != 0 && *count >= limit {
410 break;
411 }
412 }
413 Ok(())
414}
415
416fn lower_bound_oid(idx: &PackIndex, needle: &[u8]) -> usize {
417 let mut lo = 0usize;
418 let mut hi = idx.len();
419 while lo < hi {
420 let mid = lo + (hi - lo) / 2;
421 if idx.oid_at(mid) < needle {
422 lo = mid + 1;
423 } else {
424 hi = mid;
425 }
426 }
427 lo
428}
429
430fn upper_bound_oid(idx: &PackIndex, needle: &[u8]) -> usize {
431 let mut lo = 0usize;
432 let mut hi = idx.len();
433 while lo < hi {
434 let mid = lo + (hi - lo) / 2;
435 if idx.oid_at(mid) <= needle {
436 lo = mid + 1;
437 } else {
438 hi = mid;
439 }
440 }
441 lo
442}
443
444pub(crate) fn touch_pack_mtime(pack_path: &std::path::Path) -> bool {
447 let Some(touched) = crate::fs_readonly::set_file_times_now(pack_path) else {
448 return false;
449 };
450 pack::refresh_pack_bytes_signature(pack_path, touched);
451 true
452}
453
454const STREAM_RESIDENT_PACK_BYTES: usize = 4 * 1024 * 1024;
458
459struct PackPayloadStream {
464 whole: pack::PackedWholeObject,
465 dec: Decompress,
466 pos: usize,
467 remaining: u64,
468 stream_ended: bool,
469 resident: usize,
471 index_crc: Option<(u32, crc32fast::Hasher)>,
473}
474
475impl PackPayloadStream {
476 fn new(whole: pack::PackedWholeObject) -> Self {
477 let entry_start = whole.entry_offset as usize;
478 let index_crc = whole.index_crc.map(|expected| {
479 let mut hasher = crc32fast::Hasher::new();
480 hasher.update(&whole.pack[entry_start..whole.zlib_offset]);
481 (expected, hasher)
482 });
483 Self {
484 pos: whole.zlib_offset,
485 remaining: whole.size,
486 whole,
487 dec: Decompress::new(true),
488 stream_ended: false,
489 resident: 0,
490 index_crc,
491 }
492 }
493
494 fn note_compressed_consumed(&mut self, start: usize, end: usize) {
495 if let Some((_, hasher)) = &mut self.index_crc {
496 hasher.update(&self.whole.pack[start..end]);
497 }
498 }
499
500 fn verify_index_crc_if_due(&mut self) -> io::Result<()> {
501 if self.pos < self.whole.entry_end {
502 return Ok(());
503 }
504 let Some((expected, hasher)) = self.index_crc.take() else {
505 return Ok(());
506 };
507 if hasher.finalize() != expected {
508 return Err(corrupt_pack_stream("pack index CRC mismatch"));
509 }
510 Ok(())
511 }
512
513 fn inflate(&mut self, out: &mut [u8]) -> io::Result<usize> {
515 let cap = out.len().min(LOOSE_INFLATE_OUTPUT_CHUNK);
516 let out = &mut out[..cap];
517 let input_end = self.whole.data_end.min(self.whole.entry_end);
518 if self.pos >= input_end {
519 if self.stream_ended {
520 return Ok(0);
521 }
522 return Err(corrupt_pack_stream("zlib stream truncated"));
523 }
524 let input_take = (input_end - self.pos).min(LOOSE_INFLATE_INPUT_CHUNK);
525 let input = &self.whole.pack[self.pos..self.pos + input_take];
526 let before_in = self.dec.total_in();
527 let before_out = self.dec.total_out();
528 let status = self
529 .dec
530 .decompress(input, out, FlushDecompress::None)
531 .map_err(|e| corrupt_pack_stream(&e.to_string()))?;
532 let consumed = (self.dec.total_in() - before_in) as usize;
533 let produced = (self.dec.total_out() - before_out) as usize;
534 let from = self.pos;
535 self.pos += consumed;
536 self.note_compressed_consumed(from, self.pos);
537 self.resident += consumed;
538 if self.resident >= STREAM_RESIDENT_PACK_BYTES {
539 self.whole.pack.release_resident_through(self.pos);
540 self.resident = 0;
541 }
542 if status == Status::StreamEnd {
543 self.stream_ended = true;
544 } else if consumed == 0 && produced == 0 {
545 return Err(corrupt_pack_stream("zlib stream truncated"));
546 }
547 Ok(produced)
548 }
549}
550
551fn corrupt_pack_stream(detail: &str) -> io::Error {
552 io::Error::new(
553 io::ErrorKind::InvalidData,
554 format!("corrupt packed object: {detail}"),
555 )
556}
557
558impl Read for PackPayloadStream {
559 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
560 if self.remaining == 0 {
561 let mut excess = [0u8; 1];
562 while !self.stream_ended {
563 if self.inflate(&mut excess)? > 0 {
564 return Err(corrupt_pack_stream("more data than declared size"));
565 }
566 }
567 while self.pos < self.whole.entry_end {
568 let take = (self.whole.entry_end - self.pos).min(LOOSE_INFLATE_INPUT_CHUNK);
569 let from = self.pos;
570 self.pos += take;
571 self.note_compressed_consumed(from, self.pos);
572 }
573 self.verify_index_crc_if_due()?;
574 return Ok(0);
575 }
576 if buf.is_empty() {
577 return Ok(0);
578 }
579 let max = usize::try_from(self.remaining).map_or(buf.len(), |r| r.min(buf.len()));
580 loop {
581 if self.stream_ended {
582 return Err(corrupt_pack_stream("less data than declared size"));
583 }
584 let produced = self.inflate(&mut buf[..max])?;
585 if produced > 0 {
586 self.remaining -= produced as u64;
587 return Ok(produced);
588 }
589 }
590 }
591}
592
593fn stream_from_buffered_read(
594 store: &PackedObjects,
595 oid: &ObjectId,
596) -> Result<Option<ObjectStream<'static>>> {
597 let Some(obj) = store.read(oid)? else {
598 return Ok(None);
599 };
600 let size = u64::try_from(obj.data.len())
601 .map_err(|_| Error::CorruptObject(format!("object size overflow for {}", oid.to_hex())))?;
602 Ok(Some(ObjectStream {
603 kind: obj.kind,
604 size,
605 reader: Box::new(Cursor::new(obj.data)),
606 }))
607}
608
609#[cfg(test)]
610mod tests {
611 use super::*;
612 use flate2::write::ZlibEncoder;
613 use flate2::Compression;
614 use std::io::Write;
615
616 use crate::pack_map::PackData;
617
618 const TRAILER: usize = 20;
619
620 fn zlib(data: &[u8]) -> Vec<u8> {
621 let mut enc = ZlibEncoder::new(Vec::new(), Compression::default());
622 enc.write_all(data).expect("deflate");
623 enc.finish().expect("deflate")
624 }
625
626 fn whole(zlib_stream: &[u8], declared_size: u64) -> pack::PackedWholeObject {
628 let mut bytes = b"PACK\0\0\0\x02\0\0\0\x01\xb0".to_vec();
629 let zlib_offset = bytes.len();
630 bytes.extend_from_slice(zlib_stream);
631 let data_end = bytes.len();
632 bytes.extend_from_slice(&[0u8; TRAILER]);
633 pack::PackedWholeObject {
634 pack: PackData::from_owned(bytes),
635 entry_offset: 12,
636 entry_end: data_end,
637 zlib_offset,
638 data_end,
639 kind: ObjectKind::Blob,
640 size: declared_size,
641 index_crc: None,
642 }
643 }
644
645 fn read_all(whole: pack::PackedWholeObject) -> io::Result<Vec<u8>> {
646 let mut stream = PackPayloadStream::new(whole);
647 let mut out = Vec::new();
648 let mut chunk = [0u8; 1000];
649 loop {
650 match stream.read(&mut chunk)? {
651 0 => return Ok(out),
652 n => out.extend_from_slice(&chunk[..n]),
653 }
654 }
655 }
656
657 fn data() -> Vec<u8> {
658 (0..200_000u32)
659 .flat_map(|i| (i % 251).to_le_bytes())
660 .collect()
661 }
662
663 #[test]
664 fn pack_payload_stream_yields_the_whole_payload() {
665 let data = data();
666 let out = read_all(whole(&zlib(&data), data.len() as u64)).expect("stream");
667 assert!(out == data);
668 }
669
670 #[test]
671 fn pack_payload_stream_rejects_truncated_entry() {
672 let data = data();
673 let stream = zlib(&data);
674 let err =
675 read_all(whole(&stream[..stream.len() / 2], data.len() as u64)).expect_err("truncated");
676 assert_eq!(err.kind(), io::ErrorKind::InvalidData, "{err}");
677 }
678
679 #[test]
680 fn pack_payload_stream_rejects_size_mismatch_both_ways() {
681 let data = data();
682 let stream = zlib(&data);
683 let err = read_all(whole(&stream, data.len() as u64 + 1)).expect_err("short");
684 assert!(err.to_string().contains("less data"), "{err}");
685 let err = read_all(whole(&stream, data.len() as u64 - 1)).expect_err("long");
686 assert!(err.to_string().contains("more data"), "{err}");
687 }
688
689 #[test]
690 fn pack_payload_stream_rejects_bad_checksum() {
691 let data = data();
692 let mut stream = zlib(&data);
693 let last = stream.len() - 1;
694 stream[last] ^= 0xff;
695 let err = read_all(whole(&stream, data.len() as u64)).expect_err("adler mismatch");
696 assert_eq!(err.kind(), io::ErrorKind::InvalidData, "{err}");
697 }
698}