]>
Commit | Line | Data |
---|---|---|
6cd4f635 WB |
1 | //! Random access for PXAR files. |
2 | ||
dc4a2854 | 3 | use std::ffi::{OsStr, OsString}; |
6cd4f635 | 4 | use std::io; |
dc4a2854 | 5 | use std::mem::{self, size_of, size_of_val, MaybeUninit}; |
6cd4f635 WB |
6 | use std::ops::Range; |
7 | use std::os::unix::ffi::{OsStrExt, OsStringExt}; | |
8 | use std::path::{Path, PathBuf}; | |
9 | use std::pin::Pin; | |
9d8af6f2 | 10 | use std::sync::Arc; |
6cd4f635 WB |
11 | use std::task::{Context, Poll}; |
12 | ||
13 | use endian_trait::Endian; | |
14 | ||
fbddffdc | 15 | use crate::binary_tree_array; |
6cd4f635 WB |
16 | use crate::decoder::{self, DecoderImpl}; |
17 | use crate::format::{self, GoodbyeItem}; | |
18 | use crate::poll_fn::poll_fn; | |
19 | use crate::util; | |
98b894a9 | 20 | use crate::{Entry, EntryKind}; |
6cd4f635 WB |
21 | |
22 | pub mod aio; | |
9d8af6f2 | 23 | pub mod cache; |
6cd4f635 WB |
24 | pub mod sync; |
25 | ||
26 | #[doc(inline)] | |
2c23bd09 | 27 | pub use sync::{Accessor, DirEntry, Directory, FileEntry, ReadDir}; |
6cd4f635 | 28 | |
9d8af6f2 WB |
29 | use cache::Cache; |
30 | ||
6cd4f635 WB |
31 | /// Random access read implementation. |
32 | pub trait ReadAt { | |
33 | fn poll_read_at( | |
34 | self: Pin<&Self>, | |
35 | cx: &mut Context, | |
36 | buf: &mut [u8], | |
37 | offset: u64, | |
38 | ) -> Poll<io::Result<usize>>; | |
39 | } | |
40 | ||
41 | /// We do not want to bother with actual polling, so we implement `async fn` variants of the above | |
42 | /// on `dyn ReadAt`. | |
43 | /// | |
44 | /// The reason why this is not an internal `ReadAtExt` trait like `AsyncReadExt` is simply that | |
45 | /// we'd then need to define all the `Future` types they return manually and explicitly. Since we | |
46 | /// have no use for them, all we want is the ability to use `async fn`... | |
47 | /// | |
48 | /// The downside is that we need some `(&mut self.input as &mut dyn ReadAt)` casts in the | |
49 | /// decoder's code, but that's fine. | |
50 | impl<'a> dyn ReadAt + 'a { | |
51 | /// awaitable version of `poll_read_at`. | |
52 | async fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> { | |
53 | poll_fn(|cx| unsafe { Pin::new_unchecked(self).poll_read_at(cx, buf, offset) }).await | |
54 | } | |
55 | ||
56 | /// `read_exact_at` - since that's what we _actually_ want most of the time. | |
57 | async fn read_exact_at(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> { | |
58 | while !buf.is_empty() { | |
59 | match self.read_at(buf, offset).await? { | |
60 | 0 => io_bail!("unexpected EOF"), | |
61 | got => { | |
62 | buf = &mut buf[got..]; | |
63 | offset += got as u64; | |
64 | } | |
65 | } | |
66 | } | |
67 | Ok(()) | |
68 | } | |
69 | ||
70 | /// Helper to read into an `Endian`-implementing `struct`. | |
71 | async fn read_entry_at<T: Endian>(&self, offset: u64) -> io::Result<T> { | |
72 | let mut data = MaybeUninit::<T>::uninit(); | |
73 | let buf = | |
74 | unsafe { std::slice::from_raw_parts_mut(data.as_mut_ptr() as *mut u8, size_of::<T>()) }; | |
75 | self.read_exact_at(buf, offset).await?; | |
76 | Ok(unsafe { data.assume_init().from_le() }) | |
77 | } | |
78 | ||
79 | /// Helper to read into an allocated byte vector. | |
80 | async fn read_exact_data_at(&self, size: usize, offset: u64) -> io::Result<Vec<u8>> { | |
81 | let mut data = util::vec_new(size); | |
82 | self.read_exact_at(&mut data[..], offset).await?; | |
83 | Ok(data) | |
84 | } | |
85 | } | |
86 | ||
29c17fc0 WB |
87 | /// Allow using trait objects for `T: ReadAt` |
88 | impl<'a> ReadAt for &(dyn ReadAt + 'a) { | |
89 | fn poll_read_at( | |
90 | self: Pin<&Self>, | |
91 | cx: &mut Context, | |
92 | buf: &mut [u8], | |
93 | offset: u64, | |
94 | ) -> Poll<io::Result<usize>> { | |
d3a83ee3 | 95 | unsafe { Pin::new_unchecked(&**self).poll_read_at(cx, buf, offset) } |
29c17fc0 WB |
96 | } |
97 | } | |
98 | ||
b764a2b1 | 99 | #[derive(Clone)] |
9d8af6f2 WB |
100 | struct Caches { |
101 | /// The goodbye table cache maps goodbye table offsets to cache entries. | |
102 | gbt_cache: Option<Arc<dyn Cache<u64, [GoodbyeItem]> + Send + Sync>>, | |
103 | } | |
104 | ||
105 | impl Default for Caches { | |
106 | fn default() -> Self { | |
107 | Self { gbt_cache: None } | |
108 | } | |
109 | } | |
110 | ||
6cd4f635 | 111 | /// The random access state machine implementation. |
5cf335be | 112 | pub(crate) struct AccessorImpl<T> { |
6cd4f635 WB |
113 | input: T, |
114 | size: u64, | |
9d8af6f2 | 115 | caches: Arc<Caches>, |
6cd4f635 WB |
116 | } |
117 | ||
118 | impl<T: ReadAt> AccessorImpl<T> { | |
119 | pub async fn new(input: T, size: u64) -> io::Result<Self> { | |
120 | if size < (size_of::<GoodbyeItem>() as u64) { | |
121 | io_bail!("too small to contain a pxar archive"); | |
122 | } | |
9d8af6f2 WB |
123 | |
124 | Ok(Self { | |
125 | input, | |
126 | size, | |
127 | caches: Arc::new(Caches::default()), | |
128 | }) | |
6cd4f635 WB |
129 | } |
130 | ||
a2530fb7 WB |
131 | pub fn size(&self) -> u64 { |
132 | self.size | |
133 | } | |
134 | ||
29c17fc0 | 135 | pub async fn open_root_ref<'a>(&'a self) -> io::Result<DirectoryImpl<&'a dyn ReadAt>> { |
9d8af6f2 WB |
136 | DirectoryImpl::open_at_end( |
137 | &self.input as &dyn ReadAt, | |
138 | self.size, | |
139 | "/".into(), | |
140 | Arc::clone(&self.caches), | |
141 | ) | |
142 | .await | |
29c17fc0 | 143 | } |
b764a2b1 WB |
144 | |
145 | pub fn set_goodbye_table_cache( | |
146 | &mut self, | |
147 | cache: Option<Arc<dyn Cache<u64, [GoodbyeItem]> + Send + Sync>>, | |
148 | ) { | |
149 | let new_caches = Arc::new(Caches { | |
150 | gbt_cache: cache, | |
151 | ..*self.caches | |
152 | }); | |
153 | self.caches = new_caches; | |
154 | } | |
29c17fc0 WB |
155 | } |
156 | ||
6b9e2478 WB |
157 | async fn get_decoder<T: ReadAt>( |
158 | input: T, | |
159 | entry_range: Range<u64>, | |
160 | path: PathBuf, | |
161 | ) -> io::Result<DecoderImpl<SeqReadAtAdapter<T>>> { | |
d3a83ee3 | 162 | Ok(DecoderImpl::new_full(SeqReadAtAdapter::new(input, entry_range), path).await?) |
6b9e2478 WB |
163 | } |
164 | ||
29c17fc0 WB |
165 | impl<T: Clone + ReadAt> AccessorImpl<T> { |
166 | pub async fn open_root(&self) -> io::Result<DirectoryImpl<T>> { | |
9d8af6f2 WB |
167 | DirectoryImpl::open_at_end( |
168 | self.input.clone(), | |
169 | self.size, | |
170 | "/".into(), | |
171 | Arc::clone(&self.caches), | |
172 | ) | |
173 | .await | |
6cd4f635 | 174 | } |
ceb83806 WB |
175 | |
176 | /// Allow opening a directory at a specified offset. | |
177 | pub async unsafe fn open_dir_at_end(&self, offset: u64) -> io::Result<DirectoryImpl<T>> { | |
178 | DirectoryImpl::open_at_end( | |
179 | self.input.clone(), | |
180 | offset, | |
181 | "/".into(), | |
182 | Arc::clone(&self.caches), | |
183 | ) | |
184 | .await | |
185 | } | |
6b9e2478 WB |
186 | |
187 | /// Allow opening a regular file from a specified range. | |
188 | pub async unsafe fn open_file_at_range( | |
189 | &self, | |
190 | range: Range<u64>, | |
191 | ) -> io::Result<FileEntryImpl<T>> { | |
192 | let mut decoder = get_decoder(self.input.clone(), range.clone(), PathBuf::new()).await?; | |
193 | let entry = decoder | |
194 | .next() | |
195 | .await | |
196 | .ok_or_else(|| io_format_err!("unexpected EOF while decoding file entry"))??; | |
197 | Ok(FileEntryImpl { | |
198 | input: self.input.clone(), | |
199 | entry, | |
200 | entry_range: range, | |
201 | caches: Arc::clone(&self.caches), | |
202 | }) | |
203 | } | |
204 | ||
205 | /// Allow opening arbitrary contents from a specific range. | |
206 | pub unsafe fn open_contents_at_range(&self, range: Range<u64>) -> FileContentsImpl<T> { | |
207 | FileContentsImpl::new(self.input.clone(), range) | |
208 | } | |
6cd4f635 WB |
209 | } |
210 | ||
211 | /// The directory random-access state machine implementation. | |
5cf335be | 212 | pub(crate) struct DirectoryImpl<T> { |
29c17fc0 | 213 | input: T, |
6cd4f635 WB |
214 | entry_ofs: u64, |
215 | goodbye_ofs: u64, | |
216 | size: u64, | |
9d8af6f2 | 217 | table: Arc<[GoodbyeItem]>, |
6cd4f635 | 218 | path: PathBuf, |
9d8af6f2 | 219 | caches: Arc<Caches>, |
6cd4f635 WB |
220 | } |
221 | ||
29c17fc0 | 222 | impl<T: Clone + ReadAt> DirectoryImpl<T> { |
6cd4f635 | 223 | /// Open a directory ending at the specified position. |
9d8af6f2 | 224 | async fn open_at_end( |
29c17fc0 | 225 | input: T, |
6cd4f635 WB |
226 | end_offset: u64, |
227 | path: PathBuf, | |
9d8af6f2 | 228 | caches: Arc<Caches>, |
29c17fc0 WB |
229 | ) -> io::Result<DirectoryImpl<T>> { |
230 | let tail = Self::read_tail_entry(&input, end_offset).await?; | |
6cd4f635 WB |
231 | |
232 | if end_offset < tail.size { | |
233 | io_bail!("goodbye tail size out of range"); | |
234 | } | |
235 | ||
236 | let goodbye_ofs = end_offset - tail.size; | |
237 | ||
238 | if goodbye_ofs < tail.offset { | |
239 | io_bail!("goodbye offset out of range"); | |
240 | } | |
241 | ||
242 | let entry_ofs = goodbye_ofs - tail.offset; | |
243 | let size = end_offset - entry_ofs; | |
244 | ||
9d8af6f2 WB |
245 | let table: Option<Arc<[GoodbyeItem]>> = caches |
246 | .gbt_cache | |
247 | .as_ref() | |
248 | .and_then(|cache| cache.fetch(goodbye_ofs)); | |
249 | ||
6cd4f635 WB |
250 | let mut this = Self { |
251 | input, | |
252 | entry_ofs, | |
253 | goodbye_ofs, | |
254 | size, | |
9d8af6f2 | 255 | table: table.as_ref().map_or_else(|| Arc::new([]), Arc::clone), |
6cd4f635 | 256 | path, |
9d8af6f2 | 257 | caches, |
6cd4f635 WB |
258 | }; |
259 | ||
260 | // sanity check: | |
261 | if this.table_size() % (size_of::<GoodbyeItem>() as u64) != 0 { | |
262 | io_bail!("invalid goodbye table size: {}", this.table_size()); | |
263 | } | |
264 | ||
9d8af6f2 WB |
265 | if table.is_none() { |
266 | this.table = this.load_table().await?; | |
267 | if let Some(ref cache) = this.caches.gbt_cache { | |
268 | cache.insert(goodbye_ofs, Arc::clone(&this.table)); | |
269 | } | |
270 | } | |
6cd4f635 WB |
271 | |
272 | Ok(this) | |
273 | } | |
274 | ||
275 | /// Load the entire goodbye table: | |
9d8af6f2 | 276 | async fn load_table(&self) -> io::Result<Arc<[GoodbyeItem]>> { |
6cd4f635 WB |
277 | let len = self.len(); |
278 | let mut data = Vec::with_capacity(self.len()); | |
279 | unsafe { | |
280 | data.set_len(len); | |
281 | let slice = std::slice::from_raw_parts_mut( | |
282 | data.as_mut_ptr() as *mut u8, | |
2c23bd09 | 283 | len * size_of::<GoodbyeItem>(), |
6cd4f635 | 284 | ); |
29c17fc0 WB |
285 | (&self.input as &dyn ReadAt) |
286 | .read_exact_at(slice, self.table_offset()) | |
287 | .await?; | |
6cd4f635 WB |
288 | drop(slice); |
289 | } | |
9d8af6f2 | 290 | Ok(Arc::from(data)) |
6cd4f635 WB |
291 | } |
292 | ||
293 | #[inline] | |
294 | fn end_offset(&self) -> u64 { | |
295 | self.entry_ofs + self.size | |
296 | } | |
297 | ||
dc4a2854 WB |
298 | #[inline] |
299 | fn entry_range(&self) -> Range<u64> { | |
300 | self.entry_ofs..self.end_offset() | |
301 | } | |
302 | ||
6cd4f635 WB |
303 | #[inline] |
304 | fn table_size(&self) -> u64 { | |
305 | (self.end_offset() - self.goodbye_ofs) - (size_of::<format::Header>() as u64) | |
306 | } | |
307 | ||
308 | #[inline] | |
309 | fn table_offset(&self) -> u64 { | |
310 | self.goodbye_ofs + (size_of::<format::Header>() as u64) | |
311 | } | |
312 | ||
313 | /// Length *excluding* the tail marker! | |
314 | #[inline] | |
315 | fn len(&self) -> usize { | |
316 | (self.table_size() / (size_of::<GoodbyeItem>() as u64)) as usize - 1 | |
317 | } | |
318 | ||
319 | /// Read the goodbye tail and perform some sanity checks. | |
29c17fc0 | 320 | async fn read_tail_entry(input: &'_ dyn ReadAt, end_offset: u64) -> io::Result<GoodbyeItem> { |
6cd4f635 WB |
321 | if end_offset < (size_of::<GoodbyeItem>() as u64) { |
322 | io_bail!("goodbye tail does not fit"); | |
323 | } | |
324 | ||
325 | let tail_offset = end_offset - (size_of::<GoodbyeItem>() as u64); | |
326 | let tail: GoodbyeItem = input.read_entry_at(tail_offset).await?; | |
327 | ||
328 | if tail.hash != format::PXAR_GOODBYE_TAIL_MARKER { | |
329 | io_bail!("no goodbye tail marker found"); | |
330 | } | |
331 | ||
332 | Ok(tail) | |
333 | } | |
334 | ||
335 | /// Get a decoder for the directory contents. | |
29c17fc0 | 336 | pub(crate) async fn decode_full(&self) -> io::Result<DecoderImpl<SeqReadAtAdapter<T>>> { |
dc4a2854 | 337 | let (dir, decoder) = self.decode_one_entry(self.entry_range(), None).await?; |
6cd4f635 WB |
338 | if !dir.is_dir() { |
339 | io_bail!("directory does not seem to be a directory"); | |
340 | } | |
341 | Ok(decoder) | |
342 | } | |
343 | ||
344 | async fn get_decoder( | |
345 | &self, | |
346 | entry_range: Range<u64>, | |
347 | file_name: Option<&Path>, | |
29c17fc0 | 348 | ) -> io::Result<DecoderImpl<SeqReadAtAdapter<T>>> { |
6b9e2478 WB |
349 | get_decoder( |
350 | self.input.clone(), | |
351 | entry_range, | |
6cd4f635 WB |
352 | match file_name { |
353 | None => self.path.clone(), | |
354 | Some(file) => self.path.join(file), | |
355 | }, | |
d3a83ee3 WB |
356 | ) |
357 | .await | |
6cd4f635 WB |
358 | } |
359 | ||
360 | async fn decode_one_entry( | |
361 | &self, | |
362 | entry_range: Range<u64>, | |
363 | file_name: Option<&Path>, | |
29c17fc0 | 364 | ) -> io::Result<(Entry, DecoderImpl<SeqReadAtAdapter<T>>)> { |
6cd4f635 WB |
365 | let mut decoder = self.get_decoder(entry_range, file_name).await?; |
366 | let entry = decoder | |
367 | .next() | |
368 | .await | |
369 | .ok_or_else(|| io_format_err!("unexpected EOF while decoding directory entry"))??; | |
370 | Ok((entry, decoder)) | |
371 | } | |
372 | ||
fbddffdc WB |
373 | fn lookup_hash_position(&self, hash: u64, start: usize, skip: usize) -> Option<usize> { |
374 | binary_tree_array::search_by(&self.table, start, skip, |i| hash.cmp(&i.hash)) | |
6cd4f635 WB |
375 | } |
376 | ||
a5922fbc | 377 | pub async fn lookup_self(&self) -> io::Result<FileEntryImpl<T>> { |
c76d3f98 | 378 | let (entry, _decoder) = self.decode_one_entry(self.entry_range(), None).await?; |
dc4a2854 WB |
379 | Ok(FileEntryImpl { |
380 | input: self.input.clone(), | |
381 | entry, | |
ceb83806 | 382 | entry_range: self.entry_range(), |
9d8af6f2 | 383 | caches: Arc::clone(&self.caches), |
dc4a2854 WB |
384 | }) |
385 | } | |
386 | ||
6cd4f635 | 387 | /// Lookup a directory entry. |
29c17fc0 | 388 | pub async fn lookup(&self, path: &Path) -> io::Result<Option<FileEntryImpl<T>>> { |
dc4a2854 WB |
389 | let mut cur: Option<FileEntryImpl<T>> = None; |
390 | ||
391 | let mut first = true; | |
392 | for component in path.components() { | |
393 | use std::path::Component; | |
394 | ||
395 | let first = mem::replace(&mut first, false); | |
396 | ||
397 | let component = match component { | |
398 | Component::Normal(path) => path, | |
399 | Component::ParentDir => io_bail!("cannot enter parent directory in archive"), | |
400 | Component::RootDir | Component::CurDir if first => { | |
401 | cur = Some(self.lookup_self().await?); | |
402 | continue; | |
403 | } | |
404 | Component::CurDir => continue, | |
405 | _ => io_bail!("invalid component in path"), | |
406 | }; | |
407 | ||
408 | let next = match cur { | |
409 | Some(entry) => { | |
410 | entry | |
411 | .enter_directory() | |
412 | .await? | |
413 | .lookup_component(component) | |
414 | .await? | |
415 | } | |
416 | None => self.lookup_component(component).await?, | |
417 | }; | |
418 | ||
419 | if next.is_none() { | |
420 | return Ok(None); | |
421 | } | |
422 | ||
423 | cur = next; | |
424 | } | |
425 | ||
426 | Ok(cur) | |
427 | } | |
428 | ||
429 | /// Lookup a single directory entry component (does not handle multiple components in path) | |
430 | pub async fn lookup_component(&self, path: &OsStr) -> io::Result<Option<FileEntryImpl<T>>> { | |
431 | let hash = format::hash_filename(path.as_bytes()); | |
fbddffdc | 432 | let first_index = match self.lookup_hash_position(hash, 0, 0) { |
6cd4f635 WB |
433 | Some(index) => index, |
434 | None => return Ok(None), | |
435 | }; | |
436 | ||
fbddffdc WB |
437 | // Lookup FILENAME, if the hash matches but the filename doesn't, check for a duplicate |
438 | // hash once found, use the GoodbyeItem's offset+size as well as the file's Entry to return | |
439 | // a DirEntry::Dir or Dir::Entry. | |
440 | // | |
441 | let mut dup = 0; | |
442 | loop { | |
443 | let index = match self.lookup_hash_position(hash, first_index, dup) { | |
444 | Some(index) => index, | |
445 | None => return Ok(None), | |
446 | }; | |
6cd4f635 | 447 | |
6cd4f635 WB |
448 | let cursor = self.get_cursor(index).await?; |
449 | if cursor.file_name == path { | |
aabb78a4 | 450 | return Ok(Some(cursor.decode_entry().await?)); |
6cd4f635 | 451 | } |
6cd4f635 | 452 | |
fbddffdc WB |
453 | dup += 1; |
454 | } | |
6cd4f635 WB |
455 | } |
456 | ||
29c17fc0 | 457 | async fn get_cursor<'a>(&'a self, index: usize) -> io::Result<DirEntryImpl<'a, T>> { |
6cd4f635 WB |
458 | let entry = &self.table[index]; |
459 | let file_goodbye_ofs = entry.offset; | |
460 | if self.goodbye_ofs < file_goodbye_ofs { | |
461 | io_bail!("invalid file offset"); | |
462 | } | |
463 | ||
464 | let file_ofs = self.goodbye_ofs - file_goodbye_ofs; | |
465 | let (file_name, entry_ofs) = self.read_filename_entry(file_ofs).await?; | |
466 | ||
70acf637 WB |
467 | let entry_range = Range { |
468 | start: entry_ofs, | |
469 | end: file_ofs + entry.size, | |
470 | }; | |
471 | if entry_range.end < entry_range.start { | |
472 | io_bail!( | |
473 | "bad file: invalid entry ranges for {:?}: \ | |
474 | start=0x{:x}, file_ofs=0x{:x}, size=0x{:x}", | |
475 | file_name, | |
476 | entry_ofs, | |
477 | file_ofs, | |
478 | entry.size, | |
479 | ); | |
480 | } | |
481 | ||
6cd4f635 WB |
482 | Ok(DirEntryImpl { |
483 | dir: self, | |
484 | file_name, | |
70acf637 | 485 | entry_range, |
9d8af6f2 | 486 | caches: Arc::clone(&self.caches), |
6cd4f635 WB |
487 | }) |
488 | } | |
489 | ||
490 | async fn read_filename_entry(&self, file_ofs: u64) -> io::Result<(PathBuf, u64)> { | |
29c17fc0 | 491 | let head: format::Header = (&self.input as &dyn ReadAt).read_entry_at(file_ofs).await?; |
6cd4f635 WB |
492 | if head.htype != format::PXAR_FILENAME { |
493 | io_bail!("expected PXAR_FILENAME header, found: {:x}", head.htype); | |
494 | } | |
495 | ||
29c17fc0 | 496 | let mut path = (&self.input as &dyn ReadAt) |
6cd4f635 WB |
497 | .read_exact_data_at( |
498 | head.content_size() as usize, | |
499 | file_ofs + (size_of_val(&head) as u64), | |
500 | ) | |
501 | .await?; | |
502 | ||
503 | if path.pop() != Some(0) { | |
504 | io_bail!("invalid file name (missing terminating zero)"); | |
505 | } | |
506 | ||
507 | if path.is_empty() { | |
508 | io_bail!("invalid empty file name"); | |
509 | } | |
510 | ||
511 | let file_name = PathBuf::from(OsString::from_vec(path)); | |
512 | format::check_file_name(&file_name)?; | |
513 | ||
514 | Ok((file_name, file_ofs + head.full_size())) | |
515 | } | |
516 | ||
29c17fc0 | 517 | pub fn read_dir(&self) -> ReadDirImpl<T> { |
6cd4f635 WB |
518 | ReadDirImpl::new(self, 0) |
519 | } | |
d3a83ee3 WB |
520 | |
521 | pub fn entry_count(&self) -> usize { | |
522 | self.table.len() | |
523 | } | |
6cd4f635 WB |
524 | } |
525 | ||
526 | /// A file entry retrieved from a Directory. | |
5cf335be | 527 | pub(crate) struct FileEntryImpl<T: Clone + ReadAt> { |
29c17fc0 | 528 | input: T, |
6cd4f635 | 529 | entry: Entry, |
ceb83806 | 530 | entry_range: Range<u64>, |
9d8af6f2 | 531 | caches: Arc<Caches>, |
6cd4f635 WB |
532 | } |
533 | ||
29c17fc0 WB |
534 | impl<T: Clone + ReadAt> FileEntryImpl<T> { |
535 | pub async fn enter_directory(&self) -> io::Result<DirectoryImpl<T>> { | |
6cd4f635 WB |
536 | if !self.entry.is_dir() { |
537 | io_bail!("enter_directory() on a non-directory"); | |
538 | } | |
539 | ||
9d8af6f2 WB |
540 | DirectoryImpl::open_at_end( |
541 | self.input.clone(), | |
ceb83806 | 542 | self.entry_range.end, |
9d8af6f2 WB |
543 | self.entry.path.clone(), |
544 | Arc::clone(&self.caches), | |
545 | ) | |
546 | .await | |
6cd4f635 WB |
547 | } |
548 | ||
6b9e2478 WB |
549 | /// For use with unsafe accessor methods. |
550 | pub fn content_range(&self) -> io::Result<Option<Range<u64>>> { | |
98b894a9 | 551 | match self.entry.kind { |
c76d3f98 WB |
552 | EntryKind::File { offset: None, .. } => { |
553 | io_bail!("cannot open file, reader provided no offset") | |
554 | } | |
555 | EntryKind::File { | |
556 | size, | |
557 | offset: Some(offset), | |
6b9e2478 WB |
558 | } => Ok(Some(offset..(offset + size))), |
559 | _ => Ok(None), | |
560 | } | |
561 | } | |
562 | ||
563 | pub async fn contents(&self) -> io::Result<FileContentsImpl<T>> { | |
564 | match self.content_range()? { | |
565 | Some(range) => Ok(FileContentsImpl::new(self.input.clone(), range)), | |
566 | None => io_bail!("not a file"), | |
98b894a9 WB |
567 | } |
568 | } | |
569 | ||
6cd4f635 WB |
570 | #[inline] |
571 | pub fn into_entry(self) -> Entry { | |
572 | self.entry | |
573 | } | |
574 | ||
575 | #[inline] | |
576 | pub fn entry(&self) -> &Entry { | |
577 | &self.entry | |
578 | } | |
ceb83806 WB |
579 | |
580 | /// Exposed for raw by-offset access methods (use with `open_dir_at_end`). | |
581 | #[inline] | |
582 | pub fn entry_range(&self) -> Range<u64> { | |
583 | self.entry_range.clone() | |
584 | } | |
6cd4f635 WB |
585 | } |
586 | ||
587 | /// An iterator over the contents of a directory. | |
5cf335be | 588 | pub(crate) struct ReadDirImpl<'a, T> { |
29c17fc0 | 589 | dir: &'a DirectoryImpl<T>, |
6cd4f635 WB |
590 | at: usize, |
591 | } | |
592 | ||
29c17fc0 | 593 | impl<'a, T: Clone + ReadAt> ReadDirImpl<'a, T> { |
5cf335be | 594 | fn new(dir: &'a DirectoryImpl<T>, at: usize) -> Self { |
6cd4f635 WB |
595 | Self { dir, at } |
596 | } | |
597 | ||
98b894a9 | 598 | /// Get the next entry. |
29c17fc0 | 599 | pub async fn next(&mut self) -> io::Result<Option<DirEntryImpl<'a, T>>> { |
6cd4f635 WB |
600 | if self.at == self.dir.table.len() { |
601 | Ok(None) | |
602 | } else { | |
603 | let cursor = self.dir.get_cursor(self.at).await?; | |
604 | self.at += 1; | |
605 | Ok(Some(cursor)) | |
606 | } | |
607 | } | |
98b894a9 WB |
608 | |
609 | /// Efficient alternative to `Iterator::skip`. | |
610 | #[inline] | |
611 | pub fn skip(self, n: usize) -> Self { | |
612 | Self { | |
613 | at: (self.at + n).min(self.dir.table.len()), | |
614 | dir: self.dir, | |
615 | } | |
616 | } | |
617 | ||
618 | /// Efficient alternative to `Iterator::count`. | |
619 | #[inline] | |
620 | pub fn count(self) -> usize { | |
621 | self.dir.table.len() | |
622 | } | |
6cd4f635 WB |
623 | } |
624 | ||
625 | /// A cursor pointing to a file in a directory. | |
626 | /// | |
627 | /// At this point only the file name has been read and we remembered the position for finding the | |
628 | /// actual data. This can be upgraded into a FileEntryImpl. | |
5cf335be | 629 | pub(crate) struct DirEntryImpl<'a, T: Clone + ReadAt> { |
29c17fc0 | 630 | dir: &'a DirectoryImpl<T>, |
6cd4f635 WB |
631 | file_name: PathBuf, |
632 | entry_range: Range<u64>, | |
9d8af6f2 | 633 | caches: Arc<Caches>, |
6cd4f635 WB |
634 | } |
635 | ||
29c17fc0 | 636 | impl<'a, T: Clone + ReadAt> DirEntryImpl<'a, T> { |
6cd4f635 WB |
637 | pub fn file_name(&self) -> &Path { |
638 | &self.file_name | |
639 | } | |
640 | ||
aabb78a4 | 641 | async fn decode_entry(&self) -> io::Result<FileEntryImpl<T>> { |
c76d3f98 | 642 | let (entry, _decoder) = self |
6cd4f635 WB |
643 | .dir |
644 | .decode_one_entry(self.entry_range.clone(), Some(&self.file_name)) | |
645 | .await?; | |
6cd4f635 WB |
646 | |
647 | Ok(FileEntryImpl { | |
29c17fc0 | 648 | input: self.dir.input.clone(), |
6cd4f635 | 649 | entry, |
ceb83806 | 650 | entry_range: self.entry_range(), |
9d8af6f2 | 651 | caches: Arc::clone(&self.caches), |
6cd4f635 WB |
652 | }) |
653 | } | |
ceb83806 WB |
654 | |
655 | /// Exposed for raw by-offset access methods. | |
656 | #[inline] | |
657 | pub fn entry_range(&self) -> Range<u64> { | |
658 | self.entry_range.clone() | |
659 | } | |
6cd4f635 WB |
660 | } |
661 | ||
98b894a9 | 662 | /// A reader for file contents. |
5cf335be | 663 | pub(crate) struct FileContentsImpl<T> { |
98b894a9 WB |
664 | input: T, |
665 | ||
666 | /// Absolute offset inside the `input`. | |
667 | range: Range<u64>, | |
668 | } | |
669 | ||
670 | impl<T: Clone + ReadAt> FileContentsImpl<T> { | |
671 | pub fn new(input: T, range: Range<u64>) -> Self { | |
672 | Self { input, range } | |
673 | } | |
674 | ||
675 | #[inline] | |
676 | pub fn file_size(&self) -> u64 { | |
677 | self.range.end - self.range.start | |
678 | } | |
679 | ||
680 | async fn read_at(&self, mut buf: &mut [u8], offset: u64) -> io::Result<usize> { | |
681 | let size = self.file_size(); | |
682 | if offset >= size { | |
683 | return Ok(0); | |
684 | } | |
685 | let remaining = size - offset; | |
686 | ||
687 | if remaining < buf.len() as u64 { | |
688 | buf = &mut buf[..(remaining as usize)]; | |
689 | } | |
690 | ||
c76d3f98 WB |
691 | (&self.input as &dyn ReadAt) |
692 | .read_at(buf, self.range.start + offset) | |
693 | .await | |
98b894a9 WB |
694 | } |
695 | } | |
696 | ||
d3a83ee3 WB |
697 | impl<T: Clone + ReadAt> ReadAt for FileContentsImpl<T> { |
698 | fn poll_read_at( | |
699 | self: Pin<&Self>, | |
700 | cx: &mut Context, | |
701 | mut buf: &mut [u8], | |
702 | offset: u64, | |
703 | ) -> Poll<io::Result<usize>> { | |
704 | let size = self.file_size(); | |
705 | if offset >= size { | |
706 | return Poll::Ready(Ok(0)); | |
707 | } | |
708 | let remaining = size - offset; | |
709 | ||
710 | if remaining < buf.len() as u64 { | |
711 | buf = &mut buf[..(remaining as usize)]; | |
712 | } | |
713 | ||
714 | let offset = self.range.start + offset; | |
715 | unsafe { self.map_unchecked(|this| &this.input) }.poll_read_at(cx, buf, offset) | |
716 | } | |
717 | } | |
718 | ||
6cd4f635 | 719 | #[doc(hidden)] |
29c17fc0 WB |
720 | pub struct SeqReadAtAdapter<T> { |
721 | input: T, | |
6cd4f635 WB |
722 | range: Range<u64>, |
723 | } | |
724 | ||
29c17fc0 WB |
725 | impl<T: ReadAt> SeqReadAtAdapter<T> { |
726 | pub fn new(input: T, range: Range<u64>) -> Self { | |
70acf637 WB |
727 | if range.end < range.start { |
728 | panic!("BAD SEQ READ AT ADAPTER"); | |
729 | } | |
6cd4f635 WB |
730 | Self { input, range } |
731 | } | |
732 | ||
733 | #[inline] | |
734 | fn remaining(&self) -> usize { | |
735 | (self.range.end - self.range.start) as usize | |
736 | } | |
737 | } | |
738 | ||
29c17fc0 | 739 | impl<T: ReadAt> decoder::SeqRead for SeqReadAtAdapter<T> { |
6cd4f635 WB |
740 | fn poll_seq_read( |
741 | self: Pin<&mut Self>, | |
742 | cx: &mut Context, | |
743 | buf: &mut [u8], | |
744 | ) -> Poll<io::Result<usize>> { | |
745 | let len = buf.len().min(self.remaining()); | |
746 | let buf = &mut buf[..len]; | |
747 | ||
29c17fc0 | 748 | let this = unsafe { self.get_unchecked_mut() }; |
6cd4f635 WB |
749 | |
750 | let got = ready!(unsafe { | |
29c17fc0 | 751 | Pin::new_unchecked(&this.input).poll_read_at(cx, buf, this.range.start) |
6cd4f635 WB |
752 | })?; |
753 | this.range.start += got as u64; | |
754 | Poll::Ready(Ok(got)) | |
755 | } | |
756 | ||
757 | fn poll_position(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Option<io::Result<u64>>> { | |
758 | Poll::Ready(Some(Ok(self.range.start))) | |
759 | } | |
760 | } |