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