microsandbox_utils/index/
reader.rs1use std::path::Path;
10
11use super::{
12 DIR_FLAG_OPAQUE, DirRecord, ENTRY_FLAG_WHITEOUT, EntryRecord, HardlinkRef, INDEX_MAGIC,
13 INDEX_VERSION, IndexHeader,
14};
15
16pub struct MmapIndex {
25 ptr: *const u8,
27 len: usize,
29 pool_offset: usize,
31}
32
33pub struct TombstoneIter<'a> {
37 data: &'a [u8],
38 remaining: u16,
39}
40
41unsafe impl Send for MmapIndex {}
43unsafe impl Sync for MmapIndex {}
44
45impl MmapIndex {
50 pub fn open(path: &Path) -> Option<Self> {
55 use scopeguard::ScopeGuard;
56
57 let file = std::fs::File::open(path).ok()?;
58 let metadata = file.metadata().ok()?;
59 let file_len = metadata.len() as usize;
60
61 if file_len < size_of::<IndexHeader>() {
63 return None;
64 }
65
66 use std::os::fd::AsRawFd;
68 let raw = unsafe {
69 libc::mmap(
70 std::ptr::null_mut(),
71 file_len,
72 libc::PROT_READ,
73 libc::MAP_PRIVATE,
74 file.as_raw_fd(),
75 0,
76 )
77 };
78 if raw == libc::MAP_FAILED {
79 return None;
80 }
81 let ptr = raw as *const u8;
82
83 let guard = scopeguard::guard((ptr, file_len), |(p, len)| unsafe {
85 libc::munmap(p as *mut libc::c_void, len);
86 });
87
88 let header = unsafe { &*(ptr as *const IndexHeader) };
90 if header.magic != INDEX_MAGIC || header.version != INDEX_VERSION {
91 return None;
92 }
93
94 let header_size = size_of::<IndexHeader>();
96 let pool_offset = header_size
97 + (header.dir_count as usize) * size_of::<DirRecord>()
98 + (header.entry_count as usize) * size_of::<EntryRecord>()
99 + (header.hardlink_ref_count as usize) * size_of::<HardlinkRef>();
100 let expected_size = pool_offset + header.string_pool_size as usize;
101 if expected_size != file_len {
102 return None;
103 }
104
105 let data = unsafe { std::slice::from_raw_parts(ptr, file_len) };
108 let crc = crc32c::crc32c(&data[..28]);
109 let crc = crc32c::crc32c_append(crc, &[0u8; 4]);
110 let crc = crc32c::crc32c_append(crc, &data[header_size..]);
111 if crc != header.checksum {
112 return None;
113 }
114
115 ScopeGuard::into_inner(guard);
117
118 Some(Self {
119 ptr,
120 len: file_len,
121 pool_offset,
122 })
123 }
124
125 fn header(&self) -> &IndexHeader {
127 unsafe { &*(self.ptr as *const IndexHeader) }
128 }
129
130 pub fn dir_records(&self) -> &[DirRecord] {
132 let header = self.header();
133 let offset = size_of::<IndexHeader>();
134 let count = header.dir_count as usize;
135 unsafe { std::slice::from_raw_parts(self.ptr.add(offset) as *const DirRecord, count) }
136 }
137
138 fn entry_records(&self) -> &[EntryRecord] {
140 let header = self.header();
141 let offset =
142 size_of::<IndexHeader>() + (header.dir_count as usize) * size_of::<DirRecord>();
143 let count = header.entry_count as usize;
144 unsafe { std::slice::from_raw_parts(self.ptr.add(offset) as *const EntryRecord, count) }
145 }
146
147 pub fn get_str(&self, off: u32, len: u16) -> &[u8] {
149 self.pool_slice(off as usize, len as usize)
150 }
151
152 fn get_str_u32(&self, off: u32, len: u32) -> &[u8] {
154 self.pool_slice(off as usize, len as usize)
155 }
156
157 fn pool_slice(&self, off: usize, len: usize) -> &[u8] {
159 let start = self.pool_offset + off;
160 let end = start + len;
161 if end > self.len {
162 return b"";
163 }
164 unsafe { std::slice::from_raw_parts(self.ptr.add(start), len) }
165 }
166
167 pub fn find_dir(&self, path: &[u8]) -> Option<(usize, &DirRecord)> {
171 let dirs = self.dir_records();
172 let idx = dirs
173 .binary_search_by(|rec| self.get_str(rec.path_off, rec.path_len).cmp(path))
174 .ok()?;
175 Some((idx, &dirs[idx]))
176 }
177
178 pub fn find_entry<'a>(&'a self, dir: &DirRecord, name: &[u8]) -> Option<&'a EntryRecord> {
182 let entries = self.dir_entries(dir);
183 let idx = entries
184 .binary_search_by(|rec| self.get_str(rec.name_off, rec.name_len).cmp(name))
185 .ok()?;
186 Some(&entries[idx])
187 }
188
189 pub fn dir_entries(&self, dir: &DirRecord) -> &[EntryRecord] {
191 let all = self.entry_records();
192 let start = dir.first_entry as usize;
193 let end = start + dir.entry_count as usize;
194 if end > all.len() {
195 return &[];
196 }
197 &all[start..end]
198 }
199
200 pub fn is_opaque(&self, dir: &DirRecord) -> bool {
202 dir.flags & DIR_FLAG_OPAQUE != 0
203 }
204
205 pub fn has_whiteout(&self, dir: &DirRecord, name: &[u8]) -> bool {
207 if let Some(entry) = self.find_entry(dir, name) {
208 entry.flags & ENTRY_FLAG_WHITEOUT != 0
209 } else {
210 false
211 }
212 }
213
214 pub fn tombstone_names<'a>(&'a self, dir: &DirRecord) -> TombstoneIter<'a> {
216 if dir.tombstone_count == 0 {
217 return TombstoneIter {
218 data: &[],
219 remaining: 0,
220 };
221 }
222
223 let start = self.pool_offset + dir.tombstone_off as usize;
225 let data = if start < self.len {
227 unsafe { std::slice::from_raw_parts(self.ptr.add(start), self.len - start) }
228 } else {
229 &[]
230 };
231
232 TombstoneIter {
233 data,
234 remaining: dir.tombstone_count,
235 }
236 }
237
238 pub fn hardlink_refs(&self) -> &[HardlinkRef] {
240 let header = self.header();
241 let count = header.hardlink_ref_count as usize;
242 let offset = self.pool_offset - count * size_of::<HardlinkRef>();
243 unsafe { std::slice::from_raw_parts(self.ptr.add(offset) as *const HardlinkRef, count) }
244 }
245
246 pub fn find_aliases(&self, ino: u64) -> &[HardlinkRef] {
248 let refs = self.hardlink_refs();
249 let start = refs.partition_point(|r| r.host_ino < ino);
250 let end = start
251 + refs[start..]
252 .iter()
253 .take_while(|r| r.host_ino == ino)
254 .count();
255 &refs[start..end]
256 }
257
258 pub fn hardlink_path(&self, href: &HardlinkRef) -> &[u8] {
260 self.get_str_u32(href.path_off, href.path_len)
261 }
262}
263
264impl<'a> Iterator for TombstoneIter<'a> {
269 type Item = &'a [u8];
270
271 fn next(&mut self) -> Option<Self::Item> {
272 if self.remaining == 0 || self.data.len() < 2 {
273 return None;
274 }
275 let len = u16::from_le_bytes([self.data[0], self.data[1]]) as usize;
276 self.data = &self.data[2..];
277 if self.data.len() < len {
278 self.remaining = 0;
279 return None;
280 }
281 let name = &self.data[..len];
282 self.data = &self.data[len..];
283 self.remaining -= 1;
284 Some(name)
285 }
286}
287
288impl Drop for MmapIndex {
289 fn drop(&mut self) {
290 if !self.ptr.is_null() && self.len > 0 {
291 unsafe {
292 libc::munmap(self.ptr as *mut libc::c_void, self.len);
293 }
294 }
295 }
296}