ddup_bak/archive/
mod.rs

1use crate::varint;
2use entries::EntryMode;
3use flate2::{
4    read::DeflateDecoder,
5    write::{DeflateEncoder, GzEncoder},
6};
7use positioned_io::ReadAt;
8use std::{
9    ffi::OsStr,
10    fmt::{Debug, Formatter},
11    fs::{DirEntry, File, Metadata},
12    io::{Read, Seek, SeekFrom, Write},
13    path::Path,
14    sync::Arc,
15    time::SystemTime,
16};
17
18pub mod entries;
19
20pub const FILE_SIGNATURE: [u8; 7] = *b"DDUPBAK";
21pub const FILE_VERSION: u8 = 1;
22
23#[repr(u8)]
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25pub enum CompressionFormat {
26    None,
27    Gzip,
28    Deflate,
29    Brotli,
30}
31
32impl CompressionFormat {
33    pub const fn encode(&self) -> u8 {
34        match self {
35            CompressionFormat::None => 0,
36            CompressionFormat::Gzip => 1,
37            CompressionFormat::Deflate => 2,
38            CompressionFormat::Brotli => 3,
39        }
40    }
41
42    pub const fn decode(value: u8) -> Self {
43        match value {
44            0 => CompressionFormat::None,
45            1 => CompressionFormat::Gzip,
46            2 => CompressionFormat::Deflate,
47            3 => CompressionFormat::Brotli,
48            _ => panic!("Invalid compression format"),
49        }
50    }
51}
52
53impl Default for CompressionFormat {
54    #[inline]
55    fn default() -> Self {
56        CompressionFormat::None
57    }
58}
59
60#[inline]
61fn metadata_owner(_metadata: &Metadata) -> (u32, u32) {
62    #[cfg(unix)]
63    {
64        use std::os::unix::fs::MetadataExt;
65
66        (_metadata.uid(), _metadata.gid())
67    }
68    #[cfg(not(unix))]
69    {
70        (0, 0)
71    }
72}
73
74pub type ProgressCallback = Option<Arc<dyn Fn(&Path) + Send + Sync + 'static>>;
75pub type CompressionFormatCallback =
76    Option<Arc<dyn Fn(&Path, &Metadata) -> CompressionFormat + Send + Sync>>;
77type RealSizeCallback = Option<Arc<dyn Fn(&Path) -> u64 + Send + Sync + 'static>>;
78
79pub struct Archive {
80    file: Arc<File>,
81    version: u8,
82    compression_callback: CompressionFormatCallback,
83    real_size_callback: RealSizeCallback,
84
85    pub entries: Vec<entries::Entry>,
86    entries_offset: u64,
87}
88
89impl Debug for Archive {
90    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
91        f.debug_struct("Archive")
92            .field("version", &self.version)
93            .field("entries", &self.entries)
94            .finish()
95    }
96}
97
98impl Archive {
99    /// Creates a new archive file.
100    /// The file signature is written to the beginning of the file.
101    /// The file is truncated to 0 bytes.
102    pub fn new(mut file: File) -> Self {
103        file.set_len(0).unwrap();
104        file.write_all(&FILE_SIGNATURE).unwrap();
105        file.write_all(&[FILE_VERSION]).unwrap();
106        file.sync_all().unwrap();
107
108        Self {
109            file: Arc::new(file),
110            version: FILE_VERSION,
111            compression_callback: None,
112            real_size_callback: None,
113            entries: Vec::new(),
114            entries_offset: 8,
115        }
116    }
117
118    /// Opens an existing archive file for reading and writing.
119    /// This will not overwrite the file, but append to it.
120    pub fn open(path: &str) -> std::io::Result<Self> {
121        let file = File::open(path)?;
122
123        Self::open_file(file)
124    }
125
126    /// Opens an existing archive file for reading and writing.
127    /// This will not overwrite the file, but append to it.
128    pub fn open_file(mut file: File) -> std::io::Result<Self> {
129        let len = file.metadata()?.len();
130
131        let mut buffer = [0; 8];
132        file.read_exact(&mut buffer)?;
133        if !buffer.starts_with(&FILE_SIGNATURE) {
134            return Err(std::io::Error::new(
135                std::io::ErrorKind::InvalidData,
136                "Invalid file signature",
137            ));
138        }
139        let version = buffer[7];
140
141        file.read_exact_at(len - 16, &mut buffer)?;
142        let entries_count = u64::from_le_bytes(buffer);
143        file.read_exact_at(len - 8, &mut buffer)?;
144        let entries_offset = u64::from_le_bytes(buffer);
145
146        let mut entries = Vec::with_capacity(entries_count as usize);
147        file.seek(SeekFrom::Start(entries_offset))?;
148
149        let mut decoder = DeflateDecoder::new(file.try_clone()?);
150        let file = Arc::new(file);
151        for _ in 0..entries_count {
152            let entry = Self::decode_entry(&mut decoder, file.clone())?;
153            entries.push(entry);
154        }
155
156        Ok(Self {
157            file,
158            version,
159            compression_callback: None,
160            real_size_callback: None,
161            entries,
162            entries_offset,
163        })
164    }
165
166    /// Retrieves the format version of the archive.
167    /// This is the version of the archive format.
168    #[inline]
169    pub const fn version(&self) -> u8 {
170        self.version
171    }
172
173    /// Sets the compression callback for the archive.
174    /// This callback is called for each added file entry in the archive.
175    /// The callback should return the compression format to use for the file.
176    #[inline]
177    pub fn set_compression_callback(&mut self, callback: CompressionFormatCallback) -> &mut Self {
178        self.compression_callback = callback;
179
180        self
181    }
182
183    /// Sets the "real" size callback for the archive.
184    /// This callback is called for each added file entry in the archive.
185    /// The callback should return the "real" size of the file.
186    #[inline]
187    pub fn set_real_size_callback(&mut self, callback: RealSizeCallback) -> &mut Self {
188        self.real_size_callback = callback;
189
190        self
191    }
192
193    /// Adds all files in the given directory to the archive. (including subdirectories)
194    /// This will append the directory to the end of the archive, if this directory already exists, it will not be replaced.
195    ///
196    /// After this function is called, the existing header will be trimmed to the end of the archive, then readded upon completion.
197    ///
198    /// # Panics
199    /// This function will panic if any filename is not valid UTF-8 or longer than 255 bytes.
200    pub fn add_directory(
201        &mut self,
202        path: &str,
203        progress: ProgressCallback,
204    ) -> std::io::Result<&mut Self> {
205        self.trim_end_header()?;
206
207        for entry in std::fs::read_dir(path)?.flatten() {
208            self.encode_entry(None, entry, progress.clone())?;
209        }
210
211        self.write_end_header()?;
212
213        Ok(self)
214    }
215
216    /// Returns the entries in the archive.
217    #[inline]
218    pub fn entries(&self) -> &[entries::Entry] {
219        &self.entries
220    }
221
222    /// Consumes the archive and returns the entries.
223    #[inline]
224    pub fn into_entries(self) -> Vec<entries::Entry> {
225        self.entries
226    }
227
228    /// Writes a new file entry to the archive.
229    /// This will NOT append the entry to the archive, it will write the content of the file to the archive and
230    /// return the entry.
231    ///
232    /// # Important
233    /// This function does not trim the end header or readd the end header, you will need to do that manually
234    /// after calling this function.
235    #[allow(clippy::too_many_arguments)]
236    pub fn write_file_entry(
237        &mut self,
238        mut reader: impl Read,
239        size_real: Option<u64>,
240        name: impl Into<String>,
241        mode: EntryMode,
242        mtime: SystemTime,
243        owner: (u32, u32),
244        compression: CompressionFormat,
245    ) -> std::io::Result<Box<entries::FileEntry>> {
246        let offset = self.file.stream_position()?;
247
248        let mut buffer = [0; 4096];
249        let mut bytes_read = 0;
250        let mut total_bytes = 0;
251        match compression {
252            CompressionFormat::None => {
253                loop {
254                    self.file.write_all(&buffer[..bytes_read])?;
255                    total_bytes += bytes_read;
256
257                    bytes_read = reader.read(&mut buffer)?;
258                    if bytes_read == 0 {
259                        break;
260                    }
261                }
262
263                self.file.flush()?;
264            }
265            CompressionFormat::Gzip => {
266                let mut encoder = GzEncoder::new(&mut self.file, flate2::Compression::default());
267                loop {
268                    encoder.write_all(&buffer[..bytes_read])?;
269                    total_bytes += bytes_read;
270
271                    bytes_read = reader.read(&mut buffer)?;
272                    if bytes_read == 0 {
273                        break;
274                    }
275                }
276
277                encoder.flush()?;
278                encoder.finish()?;
279            }
280            CompressionFormat::Deflate => {
281                let mut encoder =
282                    DeflateEncoder::new(&mut self.file, flate2::Compression::default());
283                loop {
284                    encoder.write_all(&buffer[..bytes_read])?;
285                    total_bytes += bytes_read;
286
287                    bytes_read = reader.read(&mut buffer)?;
288                    if bytes_read == 0 {
289                        break;
290                    }
291                }
292
293                encoder.flush()?;
294                encoder.finish()?;
295            }
296
297            #[cfg(feature = "brotli")]
298            CompressionFormat::Brotli => {
299                let mut encoder = brotli::CompressorWriter::new(&mut self.file, 4096, 11, 22);
300                loop {
301                    encoder.write_all(&buffer[..bytes_read])?;
302                    total_bytes += bytes_read;
303
304                    bytes_read = reader.read(&mut buffer)?;
305                    if bytes_read == 0 {
306                        break;
307                    }
308                }
309            }
310            #[cfg(not(feature = "brotli"))]
311            CompressionFormat::Brotli => {
312                Err(std::io::Error::new(
313                    std::io::ErrorKind::Unsupported,
314                    "Brotli support is not enabled. Please enable the 'brotli' feature.",
315                ))?;
316            }
317        }
318
319        let size_compressed = match compression {
320            CompressionFormat::None => None,
321            _ => Some(self.file.stream_position()? - offset),
322        };
323        let size_real = size_real.unwrap_or(total_bytes as u64);
324
325        let entry = Box::new(entries::FileEntry {
326            name: name.into(),
327            mode,
328            file: self.file.clone(),
329            owner,
330            mtime,
331            decoder: None,
332            size_compressed,
333            size_real,
334            size: total_bytes as u64,
335            offset,
336            consumed: 0,
337            compression,
338        });
339
340        self.entries_offset = self.file.stream_position()?;
341
342        Ok(entry)
343    }
344
345    /// Adds a single file entry to the archive. (including subdirectories)
346    /// This will append the entry to the end of the archive, if this entry already exists, it will not be replaced.
347    ///
348    /// After this function is called, the existing header will be trimmed to the end of the archive, then readded upon completion.
349    ///
350    /// # Panics
351    /// This function will panic if any filename is not valid UTF-8 or longer than 255 bytes.
352    pub fn add_entries(
353        &mut self,
354        entries: Vec<DirEntry>,
355        progress: ProgressCallback,
356    ) -> std::io::Result<&mut Self> {
357        self.trim_end_header()?;
358
359        for entry in entries {
360            self.encode_entry(None, entry, progress.clone())?;
361        }
362
363        self.write_end_header()?;
364
365        Ok(self)
366    }
367
368    fn recursive_find_archive_entry<'a>(
369        entry: &'a entries::Entry,
370        entry_parts: &[&OsStr],
371        current_depth: usize,
372    ) -> Option<&'a entries::Entry> {
373        if entry_parts.len() > current_depth + 1 {
374            return None;
375        }
376
377        let current_part = entry_parts.first()?;
378        let entry_name: &OsStr = entry.name().as_ref();
379        if entry_name != *current_part {
380            return None;
381        }
382
383        if entry_parts.len() == 1 {
384            return Some(entry);
385        }
386
387        if let entries::Entry::Directory(dir_entry) = entry {
388            let remaining_parts = &entry_parts[1..];
389
390            for sub_entry in &dir_entry.entries {
391                if let Some(found) = Self::recursive_find_archive_entry(
392                    sub_entry,
393                    remaining_parts,
394                    current_depth - 1,
395                ) {
396                    return Some(found);
397                }
398            }
399        }
400
401        None
402    }
403
404    fn recursive_find_archive_entry_mut<'a>(
405        entry: &'a mut entries::Entry,
406        entry_parts: &[&OsStr],
407        current_depth: usize,
408    ) -> Option<&'a mut entries::Entry> {
409        if entry_parts.len() > current_depth + 1 {
410            return None;
411        }
412
413        let current_part = entry_parts.first()?;
414        let entry_name: &OsStr = entry.name().as_ref();
415        if entry_name != *current_part {
416            return None;
417        }
418
419        if entry_parts.len() == 1 {
420            return Some(entry);
421        }
422
423        if let entries::Entry::Directory(dir_entry) = entry {
424            let remaining_parts = &entry_parts[1..];
425
426            for sub_entry in &mut dir_entry.entries {
427                if let Some(found) = Self::recursive_find_archive_entry_mut(
428                    sub_entry,
429                    remaining_parts,
430                    current_depth - 1,
431                ) {
432                    return Some(found);
433                }
434            }
435        }
436
437        None
438    }
439
440    /// Finds an entry in the archive by name.
441    /// Returns `None` if the entry is not found.
442    /// The entry name is the path inside the archive.
443    /// Example: "world/user/level.dat" would be a valid entry name.
444    #[inline]
445    pub fn find_archive_entry(&self, entry_name: &Path) -> Option<&entries::Entry> {
446        let entry_parts = entry_name
447            .components()
448            .map(|c| c.as_os_str())
449            .collect::<Vec<&OsStr>>();
450        for entry in self.entries() {
451            if let Some(found) =
452                Self::recursive_find_archive_entry(entry, &entry_parts, entry_parts.len())
453            {
454                return Some(found);
455            }
456        }
457
458        None
459    }
460
461    /// Finds an entry in the archive by name.
462    /// Returns `None` if the entry is not found.
463    /// The entry name is the path inside the archive.
464    /// Example: "world/user/level.dat" would be a valid entry name.
465    #[inline]
466    pub fn find_archive_entry_mut(&mut self, entry_name: &Path) -> Option<&mut entries::Entry> {
467        let entry_parts = entry_name
468            .components()
469            .map(|c| c.as_os_str())
470            .collect::<Vec<&OsStr>>();
471        for entry in &mut self.entries {
472            if let Some(found) =
473                Self::recursive_find_archive_entry_mut(entry, &entry_parts, entry_parts.len())
474            {
475                return Some(found);
476            }
477        }
478
479        None
480    }
481
482    pub fn trim_end_header(&mut self) -> std::io::Result<()> {
483        if self.entries_offset == 0 {
484            return Ok(());
485        }
486
487        self.file.set_len(self.entries_offset)?;
488        self.file.flush()?;
489        self.file.seek(SeekFrom::Start(self.entries_offset))?;
490
491        Ok(())
492    }
493
494    pub fn write_end_header(&mut self) -> std::io::Result<()> {
495        let mut encoder = DeflateEncoder::new(&mut self.file, flate2::Compression::default());
496        for entry in &self.entries {
497            Self::encode_entry_metadata(&mut encoder, entry)?;
498        }
499
500        encoder.flush()?;
501        encoder.finish()?;
502        self.file.flush()?;
503
504        self.file
505            .write_all(&(self.entries.len() as u64).to_le_bytes())?;
506        self.file.write_all(&self.entries_offset.to_le_bytes())?;
507        self.file.flush()?;
508        self.file.sync_all()?;
509
510        Ok(())
511    }
512
513    fn encode_entry_metadata<S: Write>(
514        writer: &mut S,
515        entry: &entries::Entry,
516    ) -> std::io::Result<()> {
517        let name = entry.name();
518        let name_length = name.len() as u8;
519
520        writer.write_all(&varint::encode_u32(name_length as u32))?;
521
522        let mut buffer = Vec::with_capacity(name.len() + 4);
523        buffer.extend_from_slice(name.as_bytes());
524
525        let mode = entry.mode().bits();
526        let compression = match entry {
527            entries::Entry::File(file_entry) => file_entry.compression,
528            _ => CompressionFormat::None,
529        };
530        let entry_type = match entry {
531            entries::Entry::File(_) => 0,
532            entries::Entry::Directory(_) => 1,
533            entries::Entry::Symlink(_) => 2,
534        };
535
536        let type_compression_mode =
537            (entry_type << 30) | ((compression.encode() as u32) << 26) | (mode & 0x3FFFFFFF);
538        buffer.extend_from_slice(&type_compression_mode.to_le_bytes()[..4]);
539
540        writer.write_all(&buffer)?;
541
542        let (uid, gid) = entry.owner();
543        writer.write_all(&varint::encode_u32(uid))?;
544        writer.write_all(&varint::encode_u32(gid))?;
545
546        let mtime = entry
547            .mtime()
548            .duration_since(SystemTime::UNIX_EPOCH)
549            .unwrap();
550        writer.write_all(&varint::encode_u64(mtime.as_secs()))?;
551
552        match entry {
553            entries::Entry::File(file_entry) => {
554                writer.write_all(&varint::encode_u64(file_entry.size))?;
555
556                if let Some(size_compressed) = file_entry.size_compressed {
557                    writer.write_all(&varint::encode_u64(size_compressed))?;
558                }
559                writer.write_all(&varint::encode_u64(file_entry.size_real))?;
560                writer.write_all(&varint::encode_u64(file_entry.offset))?;
561            }
562            entries::Entry::Directory(dir_entry) => {
563                writer.write_all(&varint::encode_u64(dir_entry.entries.len() as u64))?;
564
565                for sub_entry in &dir_entry.entries {
566                    Self::encode_entry_metadata(writer, sub_entry)?;
567                }
568            }
569            entries::Entry::Symlink(link_entry) => {
570                writer.write_all(&varint::encode_u64(link_entry.target.len() as u64))?;
571                writer.write_all(link_entry.target.as_bytes())?;
572                writer.write_all(&[link_entry.target_dir as u8])?;
573            }
574        }
575
576        Ok(())
577    }
578
579    fn encode_entry(
580        &mut self,
581        entries: Option<&mut Vec<entries::Entry>>,
582        fs_entry: DirEntry,
583        progress: ProgressCallback,
584    ) -> std::io::Result<()> {
585        let path = fs_entry.path();
586
587        let file_name = path.file_name().unwrap().to_string_lossy().to_string();
588        let metadata = path.symlink_metadata()?;
589
590        if metadata.is_file() {
591            let mut file = File::open(&path)?;
592            let mut buffer = [0; 4096];
593            let mut bytes_read = file.read(&mut buffer)?;
594
595            let compression = match self.compression_callback {
596                Some(ref f) => f(&path, &metadata),
597                None => {
598                    if metadata.len() > 16 {
599                        CompressionFormat::Deflate
600                    } else {
601                        CompressionFormat::None
602                    }
603                }
604            };
605
606            match compression {
607                CompressionFormat::None => {
608                    loop {
609                        self.file.write_all(&buffer[..bytes_read])?;
610
611                        bytes_read = file.read(&mut buffer)?;
612                        if bytes_read == 0 {
613                            break;
614                        }
615                    }
616
617                    self.file.flush()?;
618                }
619                CompressionFormat::Gzip => {
620                    let mut encoder =
621                        GzEncoder::new(&mut self.file, flate2::Compression::default());
622                    loop {
623                        encoder.write_all(&buffer[..bytes_read])?;
624
625                        bytes_read = file.read(&mut buffer)?;
626                        if bytes_read == 0 {
627                            break;
628                        }
629                    }
630
631                    encoder.flush()?;
632                    encoder.finish()?;
633                }
634                CompressionFormat::Deflate => {
635                    let mut encoder =
636                        DeflateEncoder::new(&mut self.file, flate2::Compression::default());
637                    loop {
638                        encoder.write_all(&buffer[..bytes_read])?;
639
640                        bytes_read = file.read(&mut buffer)?;
641                        if bytes_read == 0 {
642                            break;
643                        }
644                    }
645
646                    encoder.flush()?;
647                    encoder.finish()?;
648                }
649
650                #[cfg(feature = "brotli")]
651                CompressionFormat::Brotli => {
652                    let mut encoder = brotli::CompressorWriter::new(&mut self.file, 4096, 11, 22);
653                    loop {
654                        encoder.write_all(&buffer[..bytes_read])?;
655
656                        bytes_read = file.read(&mut buffer)?;
657                        if bytes_read == 0 {
658                            break;
659                        }
660                    }
661                }
662                #[cfg(not(feature = "brotli"))]
663                CompressionFormat::Brotli => {
664                    Err(std::io::Error::new(
665                        std::io::ErrorKind::Unsupported,
666                        "Brotli support is not enabled. Please enable the 'brotli' feature.",
667                    ))?;
668                }
669            }
670
671            let entry = entries::FileEntry {
672                name: file_name,
673                mode: metadata.permissions().into(),
674                file: self.file.clone(),
675                owner: metadata_owner(&metadata),
676                mtime: metadata.modified()?,
677                decoder: None,
678                size_compressed: match compression {
679                    CompressionFormat::None => None,
680                    _ => Some(self.file.stream_position()? - self.entries_offset),
681                },
682                size_real: match self.real_size_callback {
683                    Some(ref f) => f(&path),
684                    None => metadata.len(),
685                },
686                size: metadata.len(),
687                offset: self.entries_offset,
688                consumed: 0,
689                compression,
690            };
691
692            self.entries_offset = self.file.stream_position()?;
693
694            if let Some(entries) = entries {
695                entries.push(entries::Entry::File(Box::new(entry)));
696            } else {
697                self.entries.push(entries::Entry::File(Box::new(entry)));
698            }
699        } else if metadata.is_dir() {
700            let mut dir_entries = Vec::new();
701            for entry in std::fs::read_dir(&path)?.flatten() {
702                self.encode_entry(Some(&mut dir_entries), entry, progress.clone())?;
703            }
704
705            let dir_entry = entries::DirectoryEntry {
706                name: file_name,
707                mode: metadata.permissions().into(),
708                owner: metadata_owner(&metadata),
709                mtime: metadata.modified()?,
710                entries: dir_entries,
711            };
712
713            if let Some(entries) = entries {
714                entries.push(entries::Entry::Directory(Box::new(dir_entry)));
715            } else {
716                self.entries
717                    .push(entries::Entry::Directory(Box::new(dir_entry)));
718            }
719        } else if metadata.is_symlink() {
720            if let Ok(Ok(target)) = std::fs::read_link(&path).map(|p| p.canonicalize()) {
721                let target = target.to_string_lossy().to_string();
722
723                let link_entry = entries::SymlinkEntry {
724                    name: file_name,
725                    mode: metadata.permissions().into(),
726                    owner: metadata_owner(&metadata),
727                    mtime: metadata.modified()?,
728                    target,
729                    target_dir: std::fs::metadata(&path)?.is_dir(),
730                };
731
732                if let Some(entries) = entries {
733                    entries.push(entries::Entry::Symlink(Box::new(link_entry)));
734                } else {
735                    self.entries
736                        .push(entries::Entry::Symlink(Box::new(link_entry)));
737                }
738            }
739        }
740
741        if let Some(f) = progress {
742            f(&path)
743        }
744
745        Ok(())
746    }
747
748    fn decode_entry<S: Read>(decoder: &mut S, file: Arc<File>) -> std::io::Result<entries::Entry> {
749        let name_length = varint::decode_u32(decoder) as usize;
750
751        let mut name_bytes = vec![0; name_length];
752        decoder.read_exact(&mut name_bytes)?;
753        let name = String::from_utf8(name_bytes).unwrap();
754
755        let mut type_mode_bytes = [0; 4];
756        decoder.read_exact(&mut type_mode_bytes)?;
757        let type_compression_mode = u32::from_le_bytes(type_mode_bytes);
758
759        let entry_type = (type_compression_mode >> 30) & 0b11;
760        let compression = CompressionFormat::decode(((type_compression_mode >> 26) & 0b1111) as u8);
761        let mode = EntryMode::from(type_compression_mode & 0x3FFFFFFF);
762
763        let uid = varint::decode_u32(decoder);
764        let gid = varint::decode_u32(decoder);
765
766        let mtime = varint::decode_u64(decoder);
767        let mtime = SystemTime::UNIX_EPOCH + std::time::Duration::new(mtime, 0);
768
769        let size = varint::decode_u64(decoder);
770
771        match entry_type {
772            0 => {
773                let size_compressed = match compression {
774                    CompressionFormat::None => None,
775                    _ => Some(varint::decode_u64(decoder)),
776                };
777                let size_real = varint::decode_u64(decoder);
778                let offset = varint::decode_u64(decoder);
779
780                Ok(entries::Entry::File(Box::new(entries::FileEntry {
781                    name,
782                    mode,
783                    owner: (uid, gid),
784                    mtime,
785                    file,
786                    decoder: None,
787                    size_compressed,
788                    size_real,
789                    size,
790                    offset,
791                    consumed: 0,
792                    compression,
793                })))
794            }
795            1 => {
796                let mut entries: Vec<entries::Entry> = Vec::with_capacity(size as usize);
797                for _ in 0..size {
798                    let entry = Self::decode_entry(decoder, file.clone())?;
799                    entries.push(entry);
800                }
801
802                Ok(entries::Entry::Directory(Box::new(
803                    entries::DirectoryEntry {
804                        name,
805                        mode,
806                        owner: (uid, gid),
807                        mtime,
808                        entries,
809                    },
810                )))
811            }
812            2 => {
813                let mut target_bytes = vec![0; size as usize];
814                decoder.read_exact(&mut target_bytes)?;
815
816                let target = String::from_utf8(target_bytes).unwrap();
817
818                let mut target_dir_bytes = [0; 1];
819                decoder.read_exact(&mut target_dir_bytes)?;
820                let target_dir = target_dir_bytes[0] != 0;
821
822                Ok(entries::Entry::Symlink(Box::new(entries::SymlinkEntry {
823                    name,
824                    mode,
825                    owner: (uid, gid),
826                    mtime,
827                    target,
828                    target_dir,
829                })))
830            }
831            _ => panic!("Unsupported entry type"),
832        }
833    }
834}