use std::fs::File;
use std::io::{self, Read};
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use super::{Error, Limits, MultiPackIndex};
use crate::{ObjectId, Repository};
impl Repository {
pub fn verify_multi_pack_index(&self) -> Result<(), Error> {
self.verify_multi_pack_index_with(Limits::default(), &AtomicBool::new(false))
}
pub fn verify_multi_pack_index_with(
&self,
mut limits: Limits,
cancel: &AtomicBool,
) -> Result<(), Error> {
check(cancel)?;
let directory = self.object_dir().join("pack");
if let Some(bytes) = read(&directory.join("multi-pack-index"), limits.max_bytes)? {
let index = MultiPackIndex::parse_with(&bytes, self.object_format(), limits)?;
let objects =
self.objects_controlled(Default::default(), Default::default(), cancel)?;
index.verify(&objects)?;
return check(cancel);
}
let directory = directory.join("multi-pack-index.d");
let chain = read(
&directory.join("multi-pack-index-chain"),
256 * (self.object_format().digest_len() * 2 + 1),
)?
.ok_or(Error::MissingIndex)?;
let chain = std::str::from_utf8(&chain).map_err(|_| Error::Malformed("chain encoding"))?;
let objects = self.objects_controlled(Default::default(), Default::default(), cancel)?;
let mut seen = std::collections::BTreeSet::new();
for name in chain.lines() {
check(cancel)?;
if seen.len() >= 256 {
return Err(Error::Limit("chain layers"));
}
let hash = ObjectId::from_hex(self.object_format(), name)
.map_err(|_| Error::Malformed("chain identity"))?;
if !seen.insert(hash) {
return Err(Error::Malformed("duplicate chain layer"));
}
let path = directory.join(format!("multi-pack-index-{hash}.midx"));
let bytes = read(&path, limits.max_bytes)?.ok_or(Error::MissingIndex)?;
let layer = MultiPackIndex::parse_with(&bytes, self.object_format(), limits)?;
let end = bytes.len() - self.object_format().digest_len();
if bytes[end..] != *hash.as_bytes() {
return Err(Error::Malformed("chain filename checksum"));
}
layer.verify(&objects)?;
limits.max_bytes -= bytes.len();
limits.max_objects -= layer.len();
check(cancel)?;
}
if seen.is_empty() {
return Err(Error::Malformed("empty chain"));
}
Ok(())
}
}
fn read(path: &Path, max_bytes: usize) -> Result<Option<Vec<u8>>, Error> {
let at = |source| Error::Path {
path: path.to_owned(),
source,
};
let file = match File::open(path) {
Ok(file) => file,
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(None),
Err(error) => return Err(at(error)),
};
if file.metadata().map_err(at)?.len() > max_bytes as u64 {
return Err(Error::Limit("bytes"));
}
let mut bytes = Vec::new();
file.take((max_bytes as u64).saturating_add(1))
.read_to_end(&mut bytes)
.map_err(at)?;
if bytes.len() > max_bytes {
return Err(Error::Limit("bytes"));
}
Ok(Some(bytes))
}
fn check(cancel: &AtomicBool) -> Result<(), Error> {
if cancel.load(Ordering::Relaxed) {
return Err(Error::Cancelled);
}
Ok(())
}