1use flate2::{
2    read::{DeflateDecoder, GzDecoder},
3    write::{DeflateEncoder, GzEncoder},
4};
5use positioned_io::ReadAt;
6use std::{
7    fmt::{Debug, Formatter},
8    fs::{DirEntry, File, Permissions},
9    io::{Read, Seek, SeekFrom, Write},
10};
11
12mod varint;
13
14pub const FILE_SIGNATURE: &[u8] = b"DDUPBAK\0";
15
16#[derive(Debug, Clone, Copy)]
17pub enum CompressionFormat {
18    None,
19    Gzip,
20    Deflate,
21}
22
23impl CompressionFormat {
24    pub fn encode(&self) -> u8 {
25        match self {
26            CompressionFormat::None => 0,
27            CompressionFormat::Gzip => 1,
28            CompressionFormat::Deflate => 2,
29        }
30    }
31
32    pub fn decode(value: u8) -> Self {
33        match value {
34            0 => CompressionFormat::None,
35            1 => CompressionFormat::Gzip,
36            2 => CompressionFormat::Deflate,
37            _ => panic!("Invalid compression format"),
38        }
39    }
40}
41
42#[inline]
43fn encode_file_permissions(permissions: Permissions) -> u32 {
44    #[cfg(unix)]
45    {
46        use std::os::unix::fs::PermissionsExt;
47
48        permissions.mode()
49    }
50    #[cfg(windows)]
51    {
52        if permissions.readonly() { 1 } else { 0 }
53    }
54}
55#[inline]
56fn decode_file_permissions(mode: u32) -> Permissions {
57    #[cfg(unix)]
58    {
59        use std::os::unix::fs::PermissionsExt;
60
61        Permissions::from_mode(mode)
62    }
63    #[cfg(windows)]
64    {
65        let mut permissions = unsafe { std::mem::zeroed::<Permissions>() };
66        if mode == 1 {
67            permissions.set_readonly(true);
68        } else {
69            permissions.set_readonly(false);
70        }
71
72        permissions
73    }
74}
75
76#[inline]
77fn metadata_owner(_metadata: &std::fs::Metadata) -> (u32, u32) {
78    #[cfg(unix)]
79    {
80        use std::os::unix::fs::MetadataExt;
81
82        (_metadata.uid(), _metadata.gid())
83    }
84    #[cfg(windows)]
85    {
86        (0, 0)
87    }
88}
89#[inline]
90fn metadata_mtime(metadata: &std::fs::Metadata) -> i64 {
91    #[cfg(unix)]
92    {
93        use std::os::unix::fs::MetadataExt;
94
95        metadata.mtime()
96    }
97    #[cfg(windows)]
98    {
99        use std::os::windows::fs::MetadataExt;
100
101        metadata.last_write_time() as i64
102    }
103}
104
105pub struct FileEntry {
106    pub name: String,
107    pub mode: Permissions,
108    pub owner: (u32, u32),
109    pub mtime: i64,
110    pub compression: CompressionFormat,
111    pub size: u64,
112
113    file: File,
114    decoder: Option<Box<dyn Read + Send>>,
115    offset: u64,
116    consumed: u64,
117}
118
119impl Debug for FileEntry {
120    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
121        f.debug_struct("FileEntry")
122            .field("name", &self.name)
123            .field("mode", &self.mode)
124            .field("owner", &self.owner)
125            .field("mtime", &self.mtime)
126            .field("size", &self.size)
127            .field("offset", &self.offset)
128            .field("compression", &self.compression)
129            .finish()
130    }
131}
132
133impl Read for FileEntry {
134    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
135        if self.consumed >= self.size {
136            return Ok(0);
137        }
138
139        let remaining = self.size - self.consumed;
140
141        match self.compression {
142            CompressionFormat::None => {
143                let bytes_read = self.file.read_at(self.offset + self.consumed, buf)?;
144
145                if bytes_read > remaining as usize {
146                    self.consumed += remaining;
147                    return Ok(remaining as usize);
148                }
149
150                self.consumed += bytes_read as u64;
151                Ok(bytes_read)
152            }
153            CompressionFormat::Gzip => {
154                if self.decoder.is_none() {
155                    self.file
156                        .seek(SeekFrom::Start(self.offset + self.consumed))?;
157                    let decoder = GzDecoder::new(self.file.try_clone()?);
158                    self.decoder = Some(Box::new(decoder));
159                }
160
161                let decoder = self.decoder.as_mut().unwrap();
162                let bytes_read = decoder.read(buf)?;
163
164                if bytes_read > remaining as usize {
165                    self.decoder = None;
166                    self.consumed += remaining;
167                    return Ok(remaining as usize);
168                }
169
170                self.consumed += bytes_read as u64;
171                Ok(bytes_read)
172            }
173            CompressionFormat::Deflate => {
174                if self.decoder.is_none() {
175                    self.file
176                        .seek(SeekFrom::Start(self.offset + self.consumed))?;
177                    let decoder = DeflateDecoder::new(self.file.try_clone()?);
178                    self.decoder = Some(Box::new(decoder));
179                }
180
181                let decoder = self.decoder.as_mut().unwrap();
182                let bytes_read = decoder.read(buf)?;
183
184                if bytes_read > remaining as usize {
185                    self.decoder = None;
186                    self.consumed += remaining;
187                    return Ok(remaining as usize);
188                }
189
190                self.consumed += bytes_read as u64;
191                Ok(bytes_read)
192            }
193        }
194    }
195}
196
197#[derive(Debug)]
198pub struct DirectoryEntry {
199    pub name: String,
200    pub mode: Permissions,
201    pub owner: (u32, u32),
202    pub mtime: i64,
203    pub entries: Vec<Entry>,
204}
205
206#[derive(Debug)]
207pub struct SymlinkEntry {
208    pub name: String,
209    pub mode: Permissions,
210    pub owner: (u32, u32),
211    pub mtime: i64,
212    pub target: String,
213    pub target_dir: bool,
214}
215
216#[derive(Debug)]
217pub enum Entry {
218    File(Box<FileEntry>),
219    Directory(DirectoryEntry),
220    Symlink(SymlinkEntry),
221}
222
223impl Entry {
224    pub fn name(&self) -> &str {
228        match self {
229            Entry::File(entry) => &entry.name,
230            Entry::Directory(entry) => &entry.name,
231            Entry::Symlink(entry) => &entry.name,
232        }
233    }
234
235    pub fn mode(&self) -> Permissions {
238        match self {
239            Entry::File(entry) => entry.mode.clone(),
240            Entry::Directory(entry) => entry.mode.clone(),
241            Entry::Symlink(entry) => entry.mode.clone(),
242        }
243    }
244
245    pub fn owner(&self) -> (u32, u32) {
248        match self {
249            Entry::File(entry) => entry.owner,
250            Entry::Directory(entry) => entry.owner,
251            Entry::Symlink(entry) => entry.owner,
252        }
253    }
254
255    pub fn mtime(&self) -> i64 {
258        match self {
259            Entry::File(entry) => entry.mtime,
260            Entry::Directory(entry) => entry.mtime,
261            Entry::Symlink(entry) => entry.mtime,
262        }
263    }
264}
265
266type ProgressCallback = Option<fn(&std::path::PathBuf)>;
267type CompressionFormatCallback =
268    Option<fn(&std::path::PathBuf, &std::fs::Metadata) -> CompressionFormat>;
269
270pub struct Archive {
271    file: File,
272    compression_callback: CompressionFormatCallback,
273
274    entries: Vec<Entry>,
275    entries_offset: u64,
276}
277
278impl Debug for Archive {
279    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
280        f.debug_struct("Archive")
281            .field("entries", &self.entries)
282            .finish()
283    }
284}
285
286impl Archive {
287    pub fn new(mut file: File) -> Self {
291        file.set_len(0).unwrap();
292        file.write_all(FILE_SIGNATURE).unwrap();
293        file.sync_all().unwrap();
294
295        Self {
296            file,
297            compression_callback: None,
298            entries: Vec::new(),
299            entries_offset: 8,
300        }
301    }
302
303    pub fn open(path: &str) -> Result<Self, std::io::Error> {
306        let mut file = File::open(path)?;
307        let len = file.metadata()?.len();
308
309        let mut buffer = [0; 8];
310        file.read_exact(&mut buffer)?;
311        if buffer != FILE_SIGNATURE {
312            return Err(std::io::Error::new(
313                std::io::ErrorKind::InvalidData,
314                "Invalid file signature",
315            ));
316        }
317
318        file.read_exact_at(len - 16, &mut buffer)?;
319        let entries_count = u64::from_le_bytes(buffer);
320        file.read_exact_at(len - 8, &mut buffer)?;
321        let entries_offset = u64::from_le_bytes(buffer);
322
323        println!("Entries count: {}", entries_count);
324        println!("Entries offset: {}", entries_offset);
325
326        let mut entries = Vec::with_capacity(entries_count as usize);
327        file.seek(SeekFrom::Start(entries_offset))?;
328
329        let mut decoder = DeflateDecoder::new(file.try_clone()?);
330        for _ in 0..entries_count {
331            let file_clone = file.try_clone()?;
332            let entry = Self::decode_entry(&mut decoder, file_clone)?;
333            entries.push(entry);
334        }
335
336        Ok(Self {
337            file,
338            compression_callback: None,
339            entries,
340            entries_offset,
341        })
342    }
343
344    pub fn set_compression_callback(&mut self, callback: CompressionFormatCallback) -> &mut Self {
348        self.compression_callback = callback;
349
350        self
351    }
352
353    pub fn add_directory(
361        &mut self,
362        path: &str,
363        progress: ProgressCallback,
364    ) -> Result<&mut Self, std::io::Error> {
365        self.trim_end_header()?;
366
367        for entry in std::fs::read_dir(path)?.flatten() {
368            self.encode_entry(None, entry, progress)?;
369        }
370
371        self.write_end_header()?;
372
373        Ok(self)
374    }
375
376    pub fn entries(&self) -> &[Entry] {
378        &self.entries
379    }
380
381    pub fn into_entries(self) -> Vec<Entry> {
383        self.entries
384    }
385
386    pub fn add_entry(
394        &mut self,
395        entry: DirEntry,
396        progress: ProgressCallback,
397    ) -> Result<&mut Self, std::io::Error> {
398        self.trim_end_header()?;
399        self.encode_entry(None, entry, progress)?;
400
401        self.write_end_header()?;
402
403        Ok(self)
404    }
405
406    fn trim_end_header(&mut self) -> Result<(), std::io::Error> {
407        if self.entries_offset == 0 {
408            return Ok(());
409        }
410
411        self.file.set_len(self.entries_offset)?;
412
413        Ok(())
414    }
415
416    fn write_end_header(&mut self) -> Result<(), std::io::Error> {
417        let mut encoder = DeflateEncoder::new(&mut self.file, flate2::Compression::default());
418        for entry in &self.entries {
419            Self::encode_entry_metadata(&mut encoder, entry)?;
420        }
421
422        encoder.finish()?;
423
424        self.file
425            .write_all(&(self.entries.len() as u64).to_le_bytes())?;
426        self.file.write_all(&self.entries_offset.to_le_bytes())?;
427        self.file.sync_all()?;
428
429        Ok(())
430    }
431
432    fn encode_entry_metadata<S: Write>(
433        writer: &mut S,
434        entry: &Entry,
435    ) -> Result<(), std::io::Error> {
436        let name = entry.name();
437        let name_length = name.len() as u8;
438
439        let mut buffer = Vec::with_capacity(1 + name.len() + 4);
440
441        buffer.push(name_length);
442        buffer.extend_from_slice(name.as_bytes());
443
444        let mode = encode_file_permissions(entry.mode());
445        let compression = match entry {
446            Entry::File(file_entry) => file_entry.compression,
447            _ => CompressionFormat::None,
448        };
449        let entry_type = match entry {
450            Entry::File(_) => 0,
451            Entry::Directory(_) => 1,
452            Entry::Symlink(_) => 2,
453        };
454
455        let type_compression_mode =
456            (entry_type << 30) | ((compression.encode() as u32) << 26) | (mode & 0x3FFFFFFF);
457        buffer.extend_from_slice(&type_compression_mode.to_le_bytes()[..4]);
458
459        writer.write_all(&buffer)?;
460
461        let (uid, gid) = entry.owner();
462        writer.write_all(&varint::encode_u32(uid))?;
463        writer.write_all(&varint::encode_u32(gid))?;
464        writer.write_all(&varint::encode_i64(entry.mtime()))?;
465
466        match entry {
467            Entry::File(file_entry) => {
468                writer.write_all(&varint::encode_u64(file_entry.size))?;
469                writer.write_all(&varint::encode_u64(file_entry.offset))?;
470            }
471            Entry::Directory(dir_entry) => {
472                writer.write_all(&varint::encode_u64(dir_entry.entries.len() as u64))?;
473
474                for sub_entry in &dir_entry.entries {
475                    Self::encode_entry_metadata(writer, sub_entry)?;
476                }
477            }
478            Entry::Symlink(link_entry) => {
479                writer.write_all(&varint::encode_u64(link_entry.target.len() as u64))?;
480                writer.write_all(link_entry.target.as_bytes())?;
481                writer.write_all(&[link_entry.target_dir as u8])?;
482            }
483        }
484
485        Ok(())
486    }
487
488    fn encode_entry(
489        &mut self,
490        entries: Option<&mut Vec<Entry>>,
491        fs_entry: DirEntry,
492        progress: ProgressCallback,
493    ) -> Result<(), std::io::Error> {
494        let path = fs_entry.path();
495        if let Some(f) = progress {
496            f(&path)
497        }
498
499        let file_name = path.file_name().unwrap().to_string_lossy();
500        let metadata = path.symlink_metadata()?;
501
502        if metadata.is_file() {
503            let mut file = File::open(&path)?;
504            let mut buffer = vec![0; 1024 * 1024];
505            let mut bytes_read = file.read(&mut buffer)?;
506
507            let compression = self.compression_callback.map_or(
508                if metadata.len() > 16 {
509                    CompressionFormat::Deflate
510                } else {
511                    CompressionFormat::None
512                },
513                |f| f(&path, &metadata),
514            );
515
516            let entry = FileEntry {
517                name: file_name.to_string(),
518                mode: metadata.permissions(),
519                file: self.file.try_clone()?,
520                owner: metadata_owner(&metadata),
521                mtime: metadata_mtime(&metadata),
522                decoder: None,
523                size: metadata.len(),
524                offset: self.entries_offset,
525                consumed: 0,
526                compression,
527            };
528
529            if let Some(entries) = entries {
530                entries.push(Entry::File(Box::new(entry)));
531            } else {
532                self.entries.push(Entry::File(Box::new(entry)));
533            }
534
535            match compression {
536                CompressionFormat::None => loop {
537                    self.file.write_all(&buffer[..bytes_read])?;
538                    self.entries_offset += bytes_read as u64;
539
540                    bytes_read = file.read(&mut buffer)?;
541                    if bytes_read == 0 {
542                        break;
543                    }
544                },
545                CompressionFormat::Gzip => {
546                    let mut encoder =
547                        GzEncoder::new(&mut self.file, flate2::Compression::default());
548                    loop {
549                        encoder.write_all(&buffer[..bytes_read])?;
550
551                        bytes_read = file.read(&mut buffer)?;
552                        if bytes_read == 0 {
553                            break;
554                        }
555                    }
556                    encoder.finish()?;
557
558                    self.entries_offset = self.file.stream_position()?;
559                }
560                CompressionFormat::Deflate => {
561                    let mut encoder =
562                        DeflateEncoder::new(&mut self.file, flate2::Compression::default());
563                    loop {
564                        encoder.write_all(&buffer[..bytes_read])?;
565
566                        bytes_read = file.read(&mut buffer)?;
567                        if bytes_read == 0 {
568                            break;
569                        }
570                    }
571                    encoder.finish()?;
572
573                    self.entries_offset = self.file.stream_position()?;
574                }
575            }
576        } else if metadata.is_dir() {
577            let mut dir_entries = Vec::new();
578            for entry in std::fs::read_dir(&path)?.flatten() {
579                self.encode_entry(Some(&mut dir_entries), entry, progress)?;
580            }
581
582            let dir_entry = DirectoryEntry {
583                name: file_name.to_string(),
584                mode: metadata.permissions(),
585                owner: metadata_owner(&metadata),
586                mtime: metadata_mtime(&metadata),
587                entries: dir_entries,
588            };
589
590            if let Some(entries) = entries {
591                entries.push(Entry::Directory(dir_entry));
592            } else {
593                self.entries.push(Entry::Directory(dir_entry));
594            }
595        } else if metadata.is_symlink() {
596            let target = std::fs::read_link(&path)?;
597            let target = target.to_string_lossy().to_string();
598
599            let link_entry = SymlinkEntry {
600                name: file_name.to_string(),
601                mode: metadata.permissions(),
602                owner: metadata_owner(&metadata),
603                mtime: metadata_mtime(&metadata),
604                target,
605                target_dir: std::fs::metadata(&path)?.is_dir(),
606            };
607
608            if let Some(entries) = entries {
609                entries.push(Entry::Symlink(link_entry));
610            } else {
611                self.entries.push(Entry::Symlink(link_entry));
612            }
613        }
614
615        Ok(())
616    }
617
618    fn decode_entry<S: Read>(decoder: &mut S, file: File) -> Result<Entry, std::io::Error> {
619        let mut name_length = [0; 1];
620        decoder.read_exact(&mut name_length)?;
621        let name_length = name_length[0] as usize;
622
623        let mut name_bytes = vec![0; name_length];
624        decoder.read_exact(&mut name_bytes)?;
625        let name = String::from_utf8(name_bytes).unwrap();
626
627        let mut type_mode_bytes = [0; 4];
628        decoder.read_exact(&mut type_mode_bytes)?;
629        let type_compression_mode = u32::from_le_bytes(type_mode_bytes);
630
631        let entry_type = (type_compression_mode >> 30) & 0b11;
632        let compression = CompressionFormat::decode(((type_compression_mode >> 26) & 0b1111) as u8);
633        let mode = decode_file_permissions(type_compression_mode & 0x3FFFFFFF);
634
635        let uid = varint::decode_u32(decoder);
636        let gid = varint::decode_u32(decoder);
637        let mtime = varint::decode_i64(decoder);
638
639        let size = varint::decode_u64(decoder);
640
641        match entry_type {
642            0 => {
643                let offset = varint::decode_u64(decoder);
644
645                Ok(Entry::File(Box::new(FileEntry {
646                    name,
647                    mode,
648                    owner: (uid, gid),
649                    mtime,
650                    file,
651                    decoder: None,
652                    size,
653                    offset,
654                    consumed: 0,
655                    compression,
656                })))
657            }
658            1 => {
659                let mut entries: Vec<Entry> = Vec::with_capacity(size as usize);
660                for _ in 0..size {
661                    let entry = Self::decode_entry(decoder, file.try_clone()?)?;
662                    entries.push(entry);
663                }
664
665                Ok(Entry::Directory(DirectoryEntry {
666                    name,
667                    mode,
668                    owner: (uid, gid),
669                    mtime,
670                    entries,
671                }))
672            }
673            2 => {
674                let mut target_bytes = vec![0; size as usize];
675                decoder.read_exact(&mut target_bytes)?;
676
677                let target = String::from_utf8(target_bytes).unwrap();
678                let target = std::path::PathBuf::from(target);
679
680                let target = target
681                    .canonicalize()
682                    .unwrap_or_else(|_| target.clone())
683                    .to_string_lossy()
684                    .to_string();
685                let metadata = std::fs::metadata(&target)?;
686
687                Ok(Entry::Symlink(SymlinkEntry {
688                    name,
689                    mode,
690                    owner: (uid, gid),
691                    mtime,
692                    target,
693                    target_dir: metadata.is_dir(),
694                }))
695            }
696            _ => panic!("Unsupported entry type"),
697        }
698    }
699}