use std::path::Path;
use super::{
DIR_FLAG_OPAQUE, DirRecord, ENTRY_FLAG_WHITEOUT, EntryRecord, HardlinkRef, INDEX_MAGIC,
INDEX_VERSION, IndexHeader,
};
pub struct MmapIndex {
ptr: *const u8,
len: usize,
pool_offset: usize,
}
pub struct TombstoneIter<'a> {
data: &'a [u8],
remaining: u16,
}
unsafe impl Send for MmapIndex {}
unsafe impl Sync for MmapIndex {}
impl MmapIndex {
pub fn open(path: &Path) -> Option<Self> {
use scopeguard::ScopeGuard;
let file = std::fs::File::open(path).ok()?;
let metadata = file.metadata().ok()?;
let file_len = metadata.len() as usize;
if file_len < size_of::<IndexHeader>() {
return None;
}
use std::os::fd::AsRawFd;
let raw = unsafe {
libc::mmap(
std::ptr::null_mut(),
file_len,
libc::PROT_READ,
libc::MAP_PRIVATE,
file.as_raw_fd(),
0,
)
};
if raw == libc::MAP_FAILED {
return None;
}
let ptr = raw as *const u8;
let guard = scopeguard::guard((ptr, file_len), |(p, len)| unsafe {
libc::munmap(p as *mut libc::c_void, len);
});
let header = unsafe { &*(ptr as *const IndexHeader) };
if header.magic != INDEX_MAGIC || header.version != INDEX_VERSION {
return None;
}
let header_size = size_of::<IndexHeader>();
let pool_offset = header_size
+ (header.dir_count as usize) * size_of::<DirRecord>()
+ (header.entry_count as usize) * size_of::<EntryRecord>()
+ (header.hardlink_ref_count as usize) * size_of::<HardlinkRef>();
let expected_size = pool_offset + header.string_pool_size as usize;
if expected_size != file_len {
return None;
}
let data = unsafe { std::slice::from_raw_parts(ptr, file_len) };
let crc = crc32c::crc32c(&data[..28]);
let crc = crc32c::crc32c_append(crc, &[0u8; 4]);
let crc = crc32c::crc32c_append(crc, &data[header_size..]);
if crc != header.checksum {
return None;
}
ScopeGuard::into_inner(guard);
Some(Self {
ptr,
len: file_len,
pool_offset,
})
}
fn header(&self) -> &IndexHeader {
unsafe { &*(self.ptr as *const IndexHeader) }
}
pub fn dir_records(&self) -> &[DirRecord] {
let header = self.header();
let offset = size_of::<IndexHeader>();
let count = header.dir_count as usize;
unsafe { std::slice::from_raw_parts(self.ptr.add(offset) as *const DirRecord, count) }
}
fn entry_records(&self) -> &[EntryRecord] {
let header = self.header();
let offset =
size_of::<IndexHeader>() + (header.dir_count as usize) * size_of::<DirRecord>();
let count = header.entry_count as usize;
unsafe { std::slice::from_raw_parts(self.ptr.add(offset) as *const EntryRecord, count) }
}
pub fn get_str(&self, off: u32, len: u16) -> &[u8] {
self.pool_slice(off as usize, len as usize)
}
fn get_str_u32(&self, off: u32, len: u32) -> &[u8] {
self.pool_slice(off as usize, len as usize)
}
fn pool_slice(&self, off: usize, len: usize) -> &[u8] {
let start = self.pool_offset + off;
let end = start + len;
if end > self.len {
return b"";
}
unsafe { std::slice::from_raw_parts(self.ptr.add(start), len) }
}
pub fn find_dir(&self, path: &[u8]) -> Option<(usize, &DirRecord)> {
let dirs = self.dir_records();
let idx = dirs
.binary_search_by(|rec| self.get_str(rec.path_off, rec.path_len).cmp(path))
.ok()?;
Some((idx, &dirs[idx]))
}
pub fn find_entry<'a>(&'a self, dir: &DirRecord, name: &[u8]) -> Option<&'a EntryRecord> {
let entries = self.dir_entries(dir);
let idx = entries
.binary_search_by(|rec| self.get_str(rec.name_off, rec.name_len).cmp(name))
.ok()?;
Some(&entries[idx])
}
pub fn dir_entries(&self, dir: &DirRecord) -> &[EntryRecord] {
let all = self.entry_records();
let start = dir.first_entry as usize;
let end = start + dir.entry_count as usize;
if end > all.len() {
return &[];
}
&all[start..end]
}
pub fn is_opaque(&self, dir: &DirRecord) -> bool {
dir.flags & DIR_FLAG_OPAQUE != 0
}
pub fn has_whiteout(&self, dir: &DirRecord, name: &[u8]) -> bool {
if let Some(entry) = self.find_entry(dir, name) {
entry.flags & ENTRY_FLAG_WHITEOUT != 0
} else {
false
}
}
pub fn tombstone_names<'a>(&'a self, dir: &DirRecord) -> TombstoneIter<'a> {
if dir.tombstone_count == 0 {
return TombstoneIter {
data: &[],
remaining: 0,
};
}
let start = self.pool_offset + dir.tombstone_off as usize;
let data = if start < self.len {
unsafe { std::slice::from_raw_parts(self.ptr.add(start), self.len - start) }
} else {
&[]
};
TombstoneIter {
data,
remaining: dir.tombstone_count,
}
}
pub fn hardlink_refs(&self) -> &[HardlinkRef] {
let header = self.header();
let count = header.hardlink_ref_count as usize;
let offset = self.pool_offset - count * size_of::<HardlinkRef>();
unsafe { std::slice::from_raw_parts(self.ptr.add(offset) as *const HardlinkRef, count) }
}
pub fn find_aliases(&self, ino: u64) -> &[HardlinkRef] {
let refs = self.hardlink_refs();
let start = refs.partition_point(|r| r.host_ino < ino);
let end = start
+ refs[start..]
.iter()
.take_while(|r| r.host_ino == ino)
.count();
&refs[start..end]
}
pub fn hardlink_path(&self, href: &HardlinkRef) -> &[u8] {
self.get_str_u32(href.path_off, href.path_len)
}
}
impl<'a> Iterator for TombstoneIter<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<Self::Item> {
if self.remaining == 0 || self.data.len() < 2 {
return None;
}
let len = u16::from_le_bytes([self.data[0], self.data[1]]) as usize;
self.data = &self.data[2..];
if self.data.len() < len {
self.remaining = 0;
return None;
}
let name = &self.data[..len];
self.data = &self.data[len..];
self.remaining -= 1;
Some(name)
}
}
impl Drop for MmapIndex {
fn drop(&mut self) {
if !self.ptr.is_null() && self.len > 0 {
unsafe {
libc::munmap(self.ptr as *mut libc::c_void, self.len);
}
}
}
}