nuts_archive/entry/
immut.rs

1// MIT License
2//
3// Copyright (c) 2023,2024 Robin Doer
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to
7// deal in the Software without restriction, including without limitation the
8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9// sell copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in
13// all copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21// IN THE SOFTWARE.
22
23#[cfg(test)]
24mod tests;
25
26use log::{debug, error, warn};
27use nuts_backend::Backend;
28use std::cmp;
29use std::convert::{TryFrom, TryInto};
30
31use crate::entry::{populate_mode_api, populate_tstamp_api, Inner};
32use crate::error::{ArchiveResult, Error};
33use crate::id::Id;
34use crate::pager::Pager;
35use crate::tree::Tree;
36
37/// An entry of the archive.
38///
39/// An instance of `Entry` represents an entry of the archive.
40///
41/// You can traverse through the archive using the [`Entry::next()`] method.
42/// The first entry of the archive is returned by
43/// [`Archive::first()`](crate::Archive::first).
44pub enum Entry<'a, B: Backend> {
45    /// The entry represents a file.
46    File(FileEntry<'a, B>),
47
48    /// The entry represents a directory.
49    Directory(DirectoryEntry<'a, B>),
50
51    /// The entry represents a symlink.
52    Symlink(SymlinkEntry<'a, B>),
53}
54
55impl<'a, B: Backend> Entry<'a, B> {
56    /// Returns the next entry in the archive.
57    ///
58    /// If this is the last entry [`None`] is returned, which means that there
59    /// are no further entries available.
60    pub fn next(self) -> Option<ArchiveResult<Entry<'a, B>, B>> {
61        match self.into_inner_entry().next() {
62            Some(Ok(entry)) => Some(entry.try_into()),
63            Some(Err(err)) => Some(Err(err)),
64            None => None,
65        }
66    }
67
68    /// Returns the name of the entry.
69    pub fn name(&self) -> &str {
70        &self.inner_entry().inner.name
71    }
72
73    /// Returns the size of the entry.
74    pub fn size(&self) -> u64 {
75        self.inner_entry().inner.size
76    }
77
78    populate_mode_api!();
79    populate_tstamp_api!();
80
81    /// Tests whether this entry represents a file.
82    pub fn is_file(&self) -> bool {
83        match self {
84            Self::File(_) => true,
85            Self::Directory(_) => false,
86            Self::Symlink(_) => false,
87        }
88    }
89
90    /// Returns a reference to the inner [`FileEntry`] instance if this entry
91    /// represents a file.
92    ///
93    /// If this entry is a file then a reference to the inner [`FileEntry`]
94    /// wrapped into a [`Some`] is returned. If this is not a file [`None`] is
95    /// returned.
96    pub fn as_file(&self) -> Option<&FileEntry<'a, B>> {
97        match self {
98            Self::File(value) => Some(value),
99            Self::Directory(_) => None,
100            Self::Symlink(_) => None,
101        }
102    }
103
104    /// Returns a mutable reference to the inner [`FileEntry`] instance if this
105    /// entry represents a file.
106    ///
107    /// If this entry is a file then a reference to the inner [`FileEntry`]
108    /// wrapped into a [`Some`] is returned. If this is not a file [`None`] is
109    /// returned.
110    pub fn as_file_mut(&mut self) -> Option<&mut FileEntry<'a, B>> {
111        match self {
112            Entry::File(value) => Some(value),
113            Entry::Directory(_) => None,
114            Entry::Symlink(_) => None,
115        }
116    }
117
118    /// Returns the inner [`FileEntry`] instance if this entry represents a
119    /// file.
120    ///
121    /// If this entry is a file then the inner [`FileEntry`] wrapped into a
122    /// [`Some`] is returned. If this is not a file [`None`] is returned.
123    pub fn into_file(self) -> Option<FileEntry<'a, B>> {
124        match self {
125            Self::File(value) => Some(value),
126            Self::Directory(_) => None,
127            Self::Symlink(_) => None,
128        }
129    }
130
131    /// Tests whether this entry represents a directory.
132    pub fn is_directory(&self) -> bool {
133        match self {
134            Self::File(_) => false,
135            Self::Directory(_) => true,
136            Self::Symlink(_) => false,
137        }
138    }
139
140    /// Returns a reference to the inner [`DirectoryEntry`] instance if this
141    /// entry represents a directory.
142    ///
143    /// If this entry is a directory then a reference to the inner
144    /// [`DirectoryEntry`] wrapped into a [`Some`] is returned. If this is not
145    /// a directory [`None`] is returned.
146    pub fn as_directory(&self) -> Option<&DirectoryEntry<'a, B>> {
147        match self {
148            Self::File(_) => None,
149            Self::Directory(value) => Some(value),
150            Self::Symlink(_) => None,
151        }
152    }
153
154    /// Returns the inner [`DirectoryEntry`] instance if this entry represents
155    /// a directory.
156    ///
157    /// If this entry is a directory then the inner [`DirectoryEntry`] wrapped
158    /// into a [`Some`] is returned. If this is not a directory [`None`] is
159    /// returned.
160    pub fn into_directory(self) -> Option<DirectoryEntry<'a, B>> {
161        match self {
162            Self::File(_) => None,
163            Self::Directory(value) => Some(value),
164            Self::Symlink(_) => None,
165        }
166    }
167
168    /// Tests whether this entry represents a symlink.
169    pub fn is_symlink(&self) -> bool {
170        match self {
171            Self::File(_) => false,
172            Self::Directory(_) => false,
173            Self::Symlink(_) => true,
174        }
175    }
176
177    /// Returns a reference to the inner [`SymlinkEntry`] instance if this
178    /// entry represents a symlink.
179    ///
180    /// If this entry is a symlink then a reference to the inner
181    /// [`SymlinkEntry`] wrapped into a [`Some`] is returned. If this is not a
182    /// symlink [`None`] is returned.
183    pub fn as_symlink(&self) -> Option<&SymlinkEntry<'a, B>> {
184        match self {
185            Self::File(_) => None,
186            Self::Directory(_) => None,
187            Self::Symlink(value) => Some(value),
188        }
189    }
190
191    /// Returns the inner [`SymlinkEntry`] instance if this entry represents a
192    /// symlink.
193    ///
194    /// If this entry is a symlink then the inner [`SymlinkEntry`] wrapped into
195    /// a [`Some`] is returned. If this is not a symlink [`None`] is returned.
196    pub fn into_symlink(self) -> Option<SymlinkEntry<'a, B>> {
197        match self {
198            Self::File(_) => None,
199            Self::Directory(_) => None,
200            Self::Symlink(value) => Some(value),
201        }
202    }
203
204    fn inner_entry(&'a self) -> &InnerEntry<'a, B> {
205        match self {
206            Self::File(inner) => &inner.0,
207            Self::Directory(inner) => &inner.0,
208            Self::Symlink(inner) => &inner.shared,
209        }
210    }
211
212    fn into_inner_entry(self) -> InnerEntry<'a, B> {
213        match self {
214            Self::File(inner) => inner.0,
215            Self::Directory(inner) => inner.0,
216            Self::Symlink(inner) => inner.shared,
217        }
218    }
219
220    fn inner(&self) -> &Inner {
221        &self.inner_entry().inner
222    }
223}
224
225impl<'a, B: Backend> TryFrom<InnerEntry<'a, B>> for Entry<'a, B> {
226    type Error = Error<B>;
227
228    fn try_from(src: InnerEntry<'a, B>) -> ArchiveResult<Self, B> {
229        if src.inner.mode.is_file() {
230            Ok(Self::File(FileEntry(src)))
231        } else if src.inner.mode.is_directory() {
232            Ok(Self::Directory(DirectoryEntry(src)))
233        } else if src.inner.mode.is_symlink() {
234            Ok(Self::Symlink(SymlinkEntry::new(src)?))
235        } else {
236            error!(
237                "could not detect entry type at {} from mode {:?}",
238                src.idx, src.inner.mode
239            );
240
241            match src.tree.lookup(src.pager, src.idx) {
242                Some(Ok(id)) => Err(Error::InvalidType(Some(id.as_ref().clone()))),
243                Some(Err(err)) => {
244                    error!("id lookup failed for idx {}", src.idx);
245                    Err(err)
246                }
247                None => {
248                    warn!("no id found at idx {}", src.idx);
249                    Err(Error::InvalidType(None))
250                }
251            }
252        }
253    }
254}
255
256/// A file entry of the archive.
257///
258/// An instance of this type is attached to the [`Entry::File`] variant and
259/// provides file specific options.
260///
261/// One of the `read*` methods can be used to get the content of the file.
262pub struct FileEntry<'a, B: Backend>(InnerEntry<'a, B>);
263
264impl<'a, B: Backend> FileEntry<'a, B> {
265    /// Returns the name of the file.
266    pub fn name(&self) -> &str {
267        &self.0.inner.name
268    }
269
270    /// Returns the size of the file.
271    pub fn size(&self) -> u64 {
272        self.0.inner.size
273    }
274
275    populate_mode_api!();
276    populate_tstamp_api!();
277
278    /// Reads data from the entry.
279    ///
280    /// Reads up to [`buf.len()`] bytes and puts them into `buf`.
281    ///
282    /// The methods returns the number of bytes actually read, which cannot be
283    /// greater than the [`buf.len()`].
284    ///
285    /// [`buf.len()`]: slice::len
286    pub fn read(&mut self, buf: &mut [u8]) -> ArchiveResult<usize, B> {
287        self.0.read(buf)
288    }
289
290    /// Read the exact number of bytes required to fill `buf`
291    ///
292    /// This function reads as many bytes as necessary to completely fill the
293    /// specified buffer `buf`.
294    ///
295    /// # Errors
296    ///
297    /// If this function encounters an "end of file" before completely filling
298    /// the buffer, it returns an [`Error::UnexpectedEof`] error. The contents
299    /// of `buf` are unspecified in this case.
300    pub fn read_all(&mut self, mut buf: &mut [u8]) -> ArchiveResult<(), B> {
301        while !buf.is_empty() {
302            match self.read(buf) {
303                Ok(0) => break,
304                Ok(n) => {
305                    let tmp = buf;
306                    buf = &mut tmp[n..];
307                }
308                Err(e) => return Err(e),
309            }
310        }
311
312        if !buf.is_empty() {
313            Err(Error::UnexpectedEof)
314        } else {
315            Ok(())
316        }
317    }
318
319    /// Reads all bytes until EOF and collects them into a [`Vec`] which is
320    /// returned.
321    pub fn read_vec(&mut self) -> ArchiveResult<Vec<u8>, B> {
322        let mut vec = vec![0; self.0.inner.size as usize];
323        self.read_all(&mut vec).map(|()| vec)
324    }
325
326    fn inner(&self) -> &Inner {
327        &self.0.inner
328    }
329}
330
331/// A directory entry of the archive.
332///
333/// An instance of this type is attached to the [`Entry::Directory`] variant
334/// and provides directory specific options.
335pub struct DirectoryEntry<'a, B: Backend>(InnerEntry<'a, B>);
336
337impl<'a, B: Backend> DirectoryEntry<'a, B> {
338    /// Returns the name of the directory.
339    pub fn name(&self) -> &str {
340        &self.0.inner.name
341    }
342
343    populate_mode_api!();
344    populate_tstamp_api!();
345
346    fn inner(&self) -> &Inner {
347        &self.0.inner
348    }
349}
350
351/// A symlink entry of the archive.
352///
353/// An instance of this type is attached to the [`Entry::Symlink`] variant and
354/// provides symlink specific options.
355pub struct SymlinkEntry<'a, B: Backend> {
356    shared: InnerEntry<'a, B>,
357    target: String,
358}
359
360impl<'a, B: Backend> SymlinkEntry<'a, B> {
361    fn new(mut shared: InnerEntry<'a, B>) -> ArchiveResult<SymlinkEntry<'a, B>, B> {
362        let target = Self::read_target(&mut shared)?;
363
364        Ok(SymlinkEntry { shared, target })
365    }
366
367    /// Returns the name of the symlink.
368    pub fn name(&self) -> &str {
369        &self.shared.inner.name
370    }
371
372    /// Returns the target of the symlink.
373    ///
374    /// This is the path, where the symlink points to.
375    pub fn target(&self) -> &str {
376        &self.target
377    }
378
379    populate_mode_api!();
380    populate_tstamp_api!();
381
382    fn read_target(shared: &mut InnerEntry<'a, B>) -> ArchiveResult<String, B> {
383        const CHUNK: usize = 64;
384        let mut vec = vec![];
385        let mut nbytes = 0;
386
387        loop {
388            vec.resize(vec.len() + CHUNK, 0);
389
390            let n = shared.read(&mut vec[nbytes..nbytes + CHUNK])?;
391            nbytes += n;
392
393            vec.resize(nbytes, 0);
394
395            if n == 0 {
396                break;
397            }
398        }
399
400        Ok(String::from_utf8_lossy(&vec).to_string())
401    }
402
403    fn inner(&self) -> &Inner {
404        &self.shared.inner
405    }
406}
407
408pub struct InnerEntry<'a, B: Backend> {
409    pager: &'a mut Pager<B>,
410    tree: &'a mut Tree<B>,
411    inner: Inner,
412    idx: usize,
413    rcache: Vec<u8>,
414    ridx: usize,
415}
416
417impl<'a, B: Backend> InnerEntry<'a, B> {
418    pub fn load(
419        pager: &'a mut Pager<B>,
420        tree: &'a mut Tree<B>,
421        idx: usize,
422        id: &Id<B>,
423    ) -> ArchiveResult<InnerEntry<'a, B>, B> {
424        let inner = Inner::load(pager, id)?;
425
426        Ok(InnerEntry {
427            pager,
428            tree,
429            inner,
430            idx,
431            rcache: vec![],
432            ridx: 0,
433        })
434    }
435
436    pub fn first(
437        pager: &'a mut Pager<B>,
438        tree: &'a mut Tree<B>,
439    ) -> Option<ArchiveResult<InnerEntry<'a, B>, B>> {
440        match tree.lookup(pager, 0) {
441            Some(Ok(id)) => {
442                debug!("lookup first at {}: {}", 0, id);
443                let id = id.clone();
444                Some(Self::load(pager, tree, 0, &id))
445            }
446            Some(Err(err)) => {
447                error!("lookup first at {}: {}", 0, err);
448                Some(Err(err))
449            }
450            None => {
451                debug!("lookup first at {}: none", 0);
452                None
453            }
454        }
455    }
456
457    fn next(self) -> Option<ArchiveResult<InnerEntry<'a, B>, B>> {
458        let content_blocks = self.content_blocks() as usize;
459        let next_idx = self.idx + content_blocks + 1;
460
461        debug!(
462            "next_idx={} (idx={}, size={}, content_blocks={})",
463            next_idx, self.idx, self.inner.size, content_blocks
464        );
465
466        match self.tree.lookup(self.pager, next_idx) {
467            Some(Ok(id)) => {
468                debug!("lookup next at {}: {}", next_idx, id);
469
470                let id = id.clone();
471                Some(Self::load(self.pager, self.tree, next_idx, &id))
472            }
473            Some(Err(err)) => {
474                error!("lookup next at {}: {}", next_idx, err);
475                Some(Err(err))
476            }
477            None => {
478                debug!("lookup next at {}: none", next_idx);
479                None
480            }
481        }
482    }
483
484    fn read(&mut self, buf: &mut [u8]) -> ArchiveResult<usize, B> {
485        if self.rcache.is_empty() {
486            let blocks = self.content_blocks();
487
488            debug!("fill cache: idx={}, blocks={}", self.ridx, blocks);
489
490            if self.ridx >= blocks as usize {
491                return Ok(0);
492            }
493
494            let block_size = self.pager.block_size() as usize;
495            let remaining = self.inner.size as usize - self.ridx * block_size;
496            let cache_size = cmp::min(remaining, block_size);
497
498            debug!(
499                "fill cache: remaining={}, cache_size={}",
500                remaining, cache_size
501            );
502
503            self.rcache.resize(cache_size, 0);
504
505            let idx = self.idx + self.ridx + 1;
506
507            match self.tree.lookup(self.pager, idx) {
508                Some(Ok(id)) => {
509                    let n = self.pager.read(id, self.rcache.as_mut_slice())?;
510
511                    assert_eq!(n, cache_size);
512
513                    self.ridx += 1;
514                }
515                Some(Err(err)) => return Err(err),
516                None => {
517                    warn!("premature end of archive, no block at {}", idx);
518                    return Ok(0);
519                }
520            };
521        }
522
523        let len = cmp::min(self.rcache.len(), buf.len());
524
525        self.rcache
526            .drain(..len)
527            .enumerate()
528            .for_each(|(i, n)| buf[i] = n);
529
530        Ok(len)
531    }
532
533    fn content_blocks(&self) -> u64 {
534        let block_size = self.pager.block_size() as u64;
535
536        if self.inner.size % block_size == 0 {
537            self.inner.size / block_size
538        } else {
539            self.inner.size / block_size + 1
540        }
541    }
542}