luminol_filesystem/archiver/filesystem/
impls.rs

1// Copyright (C) 2024 Melody Madeline Lyons
2//
3// This file is part of Luminol.
4//
5// Luminol is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Luminol is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Luminol.  If not, see <http://www.gnu.org/licenses/>.
17
18use color_eyre::eyre::WrapErr;
19use itertools::Itertools;
20use rand::Rng;
21use std::io::{
22    prelude::*,
23    BufReader, BufWriter,
24    ErrorKind::{AlreadyExists, InvalidData},
25    SeekFrom,
26};
27
28use super::super::util::{
29    advance_magic, move_file_and_truncate, read_file_xor, read_u32_xor, regress_magic,
30};
31use super::{Entry, File, FileSystem, MAGIC};
32use crate::{DirEntry, Error, Metadata, OpenFlags, Result};
33
34impl<T> crate::FileSystem for FileSystem<T>
35where
36    T: crate::File,
37{
38    type File = File<T>;
39
40    fn open_file(
41        &self,
42        path: impl AsRef<camino::Utf8Path>,
43        flags: OpenFlags,
44    ) -> Result<Self::File> {
45        let path = path.as_ref();
46        let c = format!(
47            "While opening file {path:?} in a version {} archive",
48            self.version
49        );
50        let mut tmp = crate::host::File::new()
51            .wrap_err("While creating a temporary file")
52            .wrap_err_with(|| c.clone())?;
53        let mut created = false;
54
55        {
56            let mut archive = self.archive.lock();
57            let mut trie = self.trie.write();
58
59            if flags.contains(OpenFlags::Create) && !trie.contains_file(path) {
60                created = true;
61                match self.version {
62                    1 | 2 => {
63                        archive
64                            .seek(SeekFrom::Start(8))
65                            .wrap_err("While reading the header of the archive")
66                            .wrap_err_with(|| c.clone())?;
67                        let mut reader = BufReader::new(archive.as_file());
68                        let mut magic = MAGIC;
69                        let mut i = 0;
70                        while let Ok(path_len) =
71                            read_u32_xor(&mut reader, advance_magic(&mut magic))
72                        {
73                            for _ in 0..path_len {
74                                advance_magic(&mut magic);
75                            }
76                            reader.seek(SeekFrom::Current(path_len as i64)).wrap_err_with(|| format!("While reading the file length (path length = {path_len}) of file #{i} in the archive")).wrap_err_with(|| c.clone())?;
77                            let entry_len = read_u32_xor(&mut reader, advance_magic(&mut magic)).wrap_err_with(|| format!("While reading the file length (path length = {path_len}) of file #{i} in the archive")).wrap_err_with(|| c.clone())?;
78                            reader.seek(SeekFrom::Current(entry_len as i64)).wrap_err_with(|| format!("While seeking forward by {entry_len} bytes to read file #{} in the archive", i + 1)).wrap_err_with(|| c.clone())?;
79                            i += 1;
80                        }
81                        drop(reader);
82                        regress_magic(&mut magic);
83
84                        let archive_len = archive
85                            .seek(SeekFrom::End(0))
86                            .wrap_err(
87                                "While writing the path length of the new file to the archive",
88                            )
89                            .wrap_err_with(|| c.clone())?;
90                        let mut writer = BufWriter::new(archive.as_file());
91                        writer
92                            .write_all(
93                                &(path.as_str().bytes().len() as u32 ^ advance_magic(&mut magic))
94                                    .to_le_bytes(),
95                            )
96                            .wrap_err(
97                                "While writing the path length of the new file to the archive",
98                            )
99                            .wrap_err_with(|| c.clone())?;
100                        writer
101                            .write_all(
102                                &path
103                                    .as_str()
104                                    .bytes()
105                                    .map(|b| {
106                                        let b = if b == b'/' { b'\\' } else { b };
107                                        b ^ advance_magic(&mut magic) as u8
108                                    })
109                                    .collect_vec(),
110                            )
111                            .wrap_err("While writing the path of the new file to the archive")
112                            .wrap_err_with(|| c.clone())?;
113                        writer
114                            .write_all(&advance_magic(&mut magic).to_le_bytes())
115                            .wrap_err(
116                                "While writing the file length of the new file to the archive",
117                            )
118                            .wrap_err_with(|| c.clone())?;
119                        writer
120                            .flush()
121                            .wrap_err("While flushing the archive after writing its contents")
122                            .wrap_err_with(|| c.clone())?;
123                        drop(writer);
124
125                        trie.create_file(
126                            path,
127                            Entry {
128                                header_offset: archive_len,
129                                body_offset: archive_len + path.as_str().bytes().len() as u64 + 8,
130                                size: 0,
131                                start_magic: magic,
132                            },
133                        );
134                    }
135
136                    3 => {
137                        let mut tmp = crate::host::File::new()
138                            .wrap_err("While creating a temporary file")
139                            .wrap_err_with(|| c.clone())?;
140
141                        let extra_data_len = path.as_str().bytes().len() as u32 + 16;
142                        let mut headers = Vec::new();
143
144                        archive
145                            .seek(SeekFrom::Start(12))
146                            .wrap_err("While reading the header of the archive")
147                            .wrap_err_with(|| c.clone())?;
148                        let mut reader = BufReader::new(archive.as_file());
149                        let mut position = 12;
150                        let mut i = 0;
151                        while let Ok(offset) = read_u32_xor(&mut reader, self.base_magic) {
152                            if offset == 0 {
153                                break;
154                            }
155                            headers.push((position, offset));
156                            reader.seek(SeekFrom::Current(8)).wrap_err_with(|| format!("While reading the path length (file offset = {offset}) of file #{i} in the archive")).wrap_err_with(|| c.clone())?;
157                            let path_len = read_u32_xor(&mut reader, self.base_magic).wrap_err_with(|| format!("While reading the path length (file offset = {offset}) of file #{i} in the archive")).wrap_err_with(|| c.clone())?;
158                            position = reader.seek(SeekFrom::Current(path_len as i64)).wrap_err_with(|| format!("While seeking forward by {path_len} bytes to read file #{} in the archive", i + 1)).wrap_err_with(|| c.clone())?;
159                            i += 1;
160                        }
161                        drop(reader);
162
163                        archive
164                            .seek(SeekFrom::Start(position))
165                            .wrap_err("While copying the archive body into a temporary file")
166                            .wrap_err_with(|| c.clone())?;
167                        std::io::copy(archive.as_file(), &mut tmp)
168                            .wrap_err("While copying the archive body into a temporary file")
169                            .wrap_err_with(|| c.clone())?;
170                        tmp.flush()
171                            .wrap_err("While copying the archive body into a temporary file")
172                            .wrap_err_with(|| c.clone())?;
173
174                        let magic: u32 = rand::thread_rng().gen();
175                        let archive_len = archive
176                            .metadata()
177                            .wrap_err("While getting the size of the archive")
178                            .wrap_err_with(|| c.clone())?
179                            .size as u32
180                            + extra_data_len;
181                        let mut writer = BufWriter::new(archive.as_file());
182                        for (i, (position, offset)) in headers.into_iter().enumerate() {
183                            writer
184                                .seek(SeekFrom::Start(position))
185                                .wrap_err_with(|| {
186                                    format!("While rewriting the file offset of file #{i} to the archive")
187                                })
188                                .wrap_err_with(|| c.clone())?;
189                            writer
190                                .write_all(
191                                    &((offset + extra_data_len) ^ self.base_magic).to_le_bytes(),
192                                )
193                                .wrap_err_with(|| {
194                                    format!("While rewriting the file offset of file #{i} to the archive")
195                                })
196                                .wrap_err_with(|| c.clone())?;
197                        }
198                        writer
199                            .seek(SeekFrom::Start(position))
200                            .wrap_err(
201                                "While writing the file offset of the new file to the archive",
202                            )
203                            .wrap_err_with(|| c.clone())?;
204                        writer
205                            .write_all(&(archive_len ^ self.base_magic).to_le_bytes())
206                            .wrap_err(
207                                "While writing the file offset of the new file to the archive",
208                            )
209                            .wrap_err_with(|| c.clone())?;
210                        writer
211                            .write_all(&self.base_magic.to_le_bytes())
212                            .wrap_err(
213                                "While writing the file length of the new file to the archive",
214                            )
215                            .wrap_err_with(|| c.clone())?;
216                        writer
217                            .write_all(&(magic ^ self.base_magic).to_le_bytes())
218                            .wrap_err(
219                                "While writing the base magic value of the new file to the archive",
220                            )
221                            .wrap_err_with(|| c.clone())?;
222                        writer
223                            .write_all(
224                                &(path.as_str().bytes().len() as u32 ^ self.base_magic)
225                                    .to_le_bytes(),
226                            )
227                            .wrap_err(
228                                "While writing the path length of the new file to the archive",
229                            )
230                            .wrap_err_with(|| c.clone())?;
231                        writer
232                            .write_all(
233                                &path
234                                    .as_str()
235                                    .bytes()
236                                    .enumerate()
237                                    .map(|(i, b)| {
238                                        let b = if b == b'/' { b'\\' } else { b };
239                                        b ^ (self.base_magic >> (8 * (i % 4))) as u8
240                                    })
241                                    .collect_vec(),
242                            )
243                            .wrap_err("While writing the path of the new file to the archive")
244                            .wrap_err_with(|| c.clone())?;
245                        tmp.seek(SeekFrom::Start(0)).wrap_err("While copying a temporary file containing the archive body into the archive").wrap_err_with(|| c.clone())?;
246                        std::io::copy(&mut tmp, &mut writer).wrap_err("While copying a temporary file containing the archive body into the archive").wrap_err_with(|| c.clone())?;
247                        writer.flush().wrap_err("While copying a temporary file containing the archive body into the archive").wrap_err_with(|| c.clone())?;
248                        drop(writer);
249
250                        trie.create_file(
251                            path,
252                            Entry {
253                                header_offset: position,
254                                body_offset: archive_len as u64,
255                                size: 0,
256                                start_magic: magic,
257                            },
258                        );
259                    }
260
261                    _ => return Err(Error::InvalidArchiveVersion(self.version).into()),
262                }
263            } else if !flags.contains(OpenFlags::Truncate) {
264                let entry = *trie
265                    .get_file(path)
266                    .ok_or(Error::NotExist)
267                    .wrap_err("While copying the file within the archive into a temporary file")
268                    .wrap_err_with(|| c.clone())?;
269                archive
270                    .seek(SeekFrom::Start(entry.body_offset))
271                    .wrap_err("While copying the file within the archive into a temporary file")
272                    .wrap_err_with(|| c.clone())?;
273
274                let mut adapter = BufReader::new(archive.as_file().take(entry.size));
275                std::io::copy(
276                    &mut read_file_xor(&mut adapter, entry.start_magic),
277                    &mut tmp,
278                )
279                .wrap_err("While copying the file within the archive into a temporary file")
280                .wrap_err_with(|| c.clone())?;
281                tmp.flush()
282                    .wrap_err("While copying the file within the archive into a temporary file")
283                    .wrap_err_with(|| c.clone())?;
284            } else if !trie.contains_file(path) {
285                return Err(Error::NotExist.into());
286            }
287        }
288
289        tmp.seek(SeekFrom::Start(0))
290            .wrap_err("While copying the file within the archive into a temporary file")
291            .wrap_err_with(|| c.clone())?;
292        Ok(File {
293            archive: flags
294                .contains(OpenFlags::Write)
295                .then(|| self.archive.clone()),
296            trie: flags.contains(OpenFlags::Write).then(|| self.trie.clone()),
297            path: path.to_owned(),
298            read_allowed: flags.contains(OpenFlags::Read),
299            tmp,
300            modified: parking_lot::Mutex::new(
301                !created && flags.contains(OpenFlags::Write) && flags.contains(OpenFlags::Truncate),
302            ),
303            version: self.version,
304            base_magic: self.base_magic,
305        })
306    }
307
308    fn metadata(&self, path: impl AsRef<camino::Utf8Path>) -> Result<Metadata> {
309        let path = path.as_ref();
310        let trie = self.trie.read();
311        if let Some(entry) = trie.get_file(path) {
312            Ok(Metadata {
313                is_file: true,
314                size: entry.size,
315            })
316        } else if let Some(size) = trie.get_dir_size(path) {
317            Ok(Metadata {
318                is_file: false,
319                size: size as u64,
320            })
321        } else {
322            Err(Error::NotExist.into())
323        }
324    }
325
326    fn rename(
327        &self,
328        from: impl AsRef<camino::Utf8Path>,
329        to: impl AsRef<camino::Utf8Path>,
330    ) -> Result<()> {
331        let from = from.as_ref();
332        let to = to.as_ref();
333
334        let mut archive = self.archive.lock();
335        let mut trie = self.trie.write();
336        let c = format!(
337            "While renaming {from:?} to {to:?} in a version {} archive",
338            self.version
339        );
340
341        if trie.contains_dir(from) {
342            return Err(Error::NotSupported.into());
343        }
344        if trie.contains(to) {
345            return Err(Error::IoError(AlreadyExists.into()).into());
346        }
347        if !trie.contains_dir(
348            from.parent()
349                .ok_or(Error::NotExist)
350                .wrap_err_with(|| c.clone())?,
351        ) {
352            return Err(Error::NotExist.into());
353        }
354        let Some(old_entry) = trie.get_file(from).copied() else {
355            return Err(Error::NotExist.into());
356        };
357
358        let archive_len = archive
359            .metadata()
360            .wrap_err("While getting the length of the archive")
361            .wrap_err_with(|| c.clone())?
362            .size;
363        let from_len = from.as_str().bytes().len();
364        let to_len = to.as_str().bytes().len();
365
366        if from_len != to_len {
367            match self.version {
368                1 | 2 => {
369                    // Move the file contents into a temporary file
370                    let mut tmp = crate::host::File::new()
371                        .wrap_err("While creating a temporary file")
372                        .wrap_err_with(|| c.clone())?;
373                    archive.seek(SeekFrom::Start(old_entry.body_offset)).wrap_err("While copying the contents of the file within the archive into a temporary file").wrap_err_with(|| c.clone())?;
374                    let mut reader = BufReader::new(archive.as_file().take(old_entry.size));
375                    std::io::copy(
376                        &mut read_file_xor(&mut reader, old_entry.start_magic),
377                        &mut tmp,
378                    ).wrap_err("While copying the contents of the file within the archive into a temporary file").wrap_err_with(|| c.clone())?;
379                    tmp.flush().wrap_err("While copying the contents of the file within the archive into a temporary file").wrap_err_with(|| c.clone())?;
380                    drop(reader);
381
382                    // Move the file to the end so that we can change the header size
383                    move_file_and_truncate(
384                        &mut archive,
385                        &mut trie,
386                        from,
387                        self.version,
388                        self.base_magic,
389                    )
390                    .wrap_err("While relocating the file header to the end of the archive")
391                    .wrap_err_with(|| c.clone())?;
392                    let mut new_entry = *trie
393                        .get_file(from)
394                        .ok_or(Error::InvalidHeader)
395                        .wrap_err("While relocating the file header to the end of the archive")
396                        .wrap_err_with(|| c.clone())?;
397                    trie.remove_file(from)
398                        .ok_or(Error::InvalidHeader)
399                        .wrap_err("While relocating the file header to the end of the archive")
400                        .wrap_err_with(|| c.clone())?;
401                    new_entry.size = old_entry.size;
402
403                    let mut magic = new_entry.start_magic;
404                    regress_magic(&mut magic);
405                    regress_magic(&mut magic);
406                    for _ in from.as_str().bytes() {
407                        regress_magic(&mut magic);
408                    }
409
410                    // Regenerate the header
411                    archive
412                        .seek(SeekFrom::Start(new_entry.header_offset))
413                        .wrap_err("While rewriting the path length of the file to the archive")
414                        .wrap_err_with(|| c.clone())?;
415                    let mut writer = BufWriter::new(archive.as_file());
416                    writer
417                        .write_all(&(to_len as u32 ^ advance_magic(&mut magic)).to_le_bytes())
418                        .wrap_err("While rewriting the path length of the file to the archive")
419                        .wrap_err_with(|| c.clone())?;
420                    writer
421                        .write_all(
422                            &to.as_str()
423                                .bytes()
424                                .map(|b| {
425                                    let b = if b == b'/' { b'\\' } else { b };
426                                    b ^ advance_magic(&mut magic) as u8
427                                })
428                                .collect_vec(),
429                        )
430                        .wrap_err("While rewriting the path of the file to the archive")
431                        .wrap_err_with(|| c.clone())?;
432                    writer
433                        .write_all(
434                            &(old_entry.size as u32 ^ advance_magic(&mut magic)).to_le_bytes(),
435                        )
436                        .wrap_err("While rewriting the file length of the file to the archive")
437                        .wrap_err_with(|| c.clone())?;
438
439                    new_entry.start_magic = magic;
440
441                    // Move the file contents to the end
442                    tmp.seek(SeekFrom::Start(0))
443                        .wrap_err("While relocating the file contents to the end of the archive")
444                        .wrap_err_with(|| c.clone())?;
445                    let mut reader = BufReader::new(&mut tmp);
446                    std::io::copy(&mut read_file_xor(&mut reader, magic), &mut writer)
447                        .wrap_err("While relocating the file contents to the end of the archive")
448                        .wrap_err_with(|| c.clone())?;
449                    writer
450                        .flush()
451                        .wrap_err("While relocating the file contents to the end of the archive")
452                        .wrap_err_with(|| c.clone())?;
453                    drop(writer);
454
455                    trie.create_file(to, new_entry);
456                }
457
458                3 => {
459                    // Move everything after the header into a temporary file
460                    let mut tmp = crate::host::File::new()
461                        .wrap_err("While creating a temporary file")
462                        .wrap_err_with(|| c.clone())?;
463                    archive
464                        .seek(SeekFrom::Start(
465                            old_entry.header_offset + from_len as u64 + 16,
466                        ))
467                        .wrap_err("While copying the contents of the archive into a temporary file")
468                        .wrap_err_with(|| c.clone())?;
469                    std::io::copy(archive.as_file(), &mut tmp)
470                        .wrap_err("While copying the contents of the archive into a temporary file")
471                        .wrap_err_with(|| c.clone())?;
472                    tmp.flush()
473                        .wrap_err("While copying the contents of the archive into a temporary file")
474                        .wrap_err_with(|| c.clone())?;
475
476                    // Change the path
477                    archive
478                        .seek(SeekFrom::Start(old_entry.header_offset + 12))
479                        .wrap_err("While rewriting the path length of the file to the archive")
480                        .wrap_err_with(|| c.clone())?;
481                    let mut writer = BufWriter::new(archive.as_file());
482                    writer
483                        .write_all(&(to_len as u32 ^ self.base_magic).to_le_bytes())
484                        .wrap_err("While rewriting the path length of the file to the archive")
485                        .wrap_err_with(|| c.clone())?;
486                    writer
487                        .write_all(
488                            &to.as_str()
489                                .bytes()
490                                .enumerate()
491                                .map(|(i, b)| {
492                                    let b = if b == b'/' { b'\\' } else { b };
493                                    b ^ (self.base_magic >> (8 * (i % 4))) as u8
494                                })
495                                .collect_vec(),
496                        )
497                        .wrap_err("While rewriting the path of the file to the archive")
498                        .wrap_err_with(|| c.clone())?;
499                    trie.remove_file(from)
500                        .ok_or(Error::InvalidHeader)
501                        .wrap_err("While rewriting the header of the file to the archive")
502                        .wrap_err_with(|| c.clone())?;
503                    trie.create_file(to, old_entry);
504
505                    // Move everything else back
506                    tmp.seek(SeekFrom::Start(0)).wrap_err("While copying a temporary file containing the archive body into the archive").wrap_err_with(|| c.clone())?;
507                    std::io::copy(&mut tmp, &mut writer).wrap_err("While copying a temporary file containing the archive body into the archive").wrap_err_with(|| c.clone())?;
508                    writer.flush().wrap_err("While copying a temporary file containing the archive body into the archive").wrap_err_with(|| c.clone())?;
509                    drop(writer);
510
511                    // Update all of the offsets in the headers
512                    archive
513                        .seek(SeekFrom::Start(12))
514                        .wrap_err("While rewriting the header of the archive")
515                        .wrap_err_with(|| c.clone())?;
516                    let mut reader = BufReader::new(archive.as_file());
517                    let mut headers = Vec::new();
518                    let mut i = 0;
519                    while let Ok(current_body_offset) = read_u32_xor(&mut reader, self.base_magic) {
520                        if current_body_offset == 0 {
521                            break;
522                        }
523                        let current_header_offset = reader
524                            .stream_position()
525                            .wrap_err_with(|| {
526                                format!("While reading the path length of file #{i} in the archive")
527                            })
528                            .wrap_err_with(|| c.clone())?
529                            .checked_sub(4)
530                            .ok_or(Error::InvalidHeader)
531                            .wrap_err_with(|| {
532                                format!("While reading the path length of file #{i} in the archive")
533                            })
534                            .wrap_err_with(|| c.clone())?;
535                        reader
536                            .seek(SeekFrom::Current(8))
537                            .wrap_err_with(|| {
538                                format!("While reading the path length of file #{i} in the archive")
539                            })
540                            .wrap_err_with(|| c.clone())?;
541                        let current_path_len = read_u32_xor(&mut reader, self.base_magic)
542                            .wrap_err_with(|| {
543                                format!("While reading the path length of file #{i} in the archive")
544                            })
545                            .wrap_err_with(|| c.clone())?;
546
547                        let mut current_path = vec![0; current_path_len as usize];
548                        reader.read_exact(&mut current_path).wrap_err_with(|| format!("While reading the path (path length = {current_path_len} of file #{i}) in the archive")).wrap_err_with(|| c.clone())?;
549                        for (i, byte) in current_path.iter_mut().enumerate() {
550                            let char = *byte ^ (self.base_magic >> (8 * (i % 4))) as u8;
551                            if char == b'\\' {
552                                *byte = b'/';
553                            } else {
554                                *byte = char;
555                            }
556                        }
557                        let current_path =
558                            String::from_utf8(current_path).map_err(|_| Error::PathUtf8Error).wrap_err_with(|| format!("While reading the path (path length = {current_path_len}) of file #{i} in the archive")).wrap_err_with(|| c.clone())?;
559
560                        let current_body_offset = (current_body_offset as u64)
561                            .checked_add_signed(to_len as i64 - from_len as i64)
562                            .ok_or(Error::InvalidHeader)
563                            .wrap_err_with(|| {
564                                format!(
565                                    "While reading the header (path = {current_path}) of file #{i} in the archive"
566                                )
567                            })
568                            .wrap_err_with(|| c.clone())?;
569                        trie.get_file_mut(&current_path)
570                            .ok_or(Error::InvalidHeader)
571                            .wrap_err_with(|| {
572                                format!(
573                                    "While reading the header (path = {current_path}) of file #{i} in the archive"
574                                )
575                            })
576                            .wrap_err_with(|| c.clone())?
577                            .body_offset = current_body_offset;
578                        headers.push((current_header_offset, current_body_offset as u32));
579                        i += 1;
580                    }
581                    drop(reader);
582                    let mut writer = BufWriter::new(archive.as_file());
583                    for (i, (position, offset)) in headers.into_iter().enumerate() {
584                        writer.seek(SeekFrom::Start(position))?;
585                        writer
586                            .write_all(&(offset ^ self.base_magic).to_le_bytes())
587                            .wrap_err_with(|| {
588                                format!(
589                                    "While rewriting the file offset of file #{i} to the archive"
590                                )
591                            })
592                            .wrap_err_with(|| c.clone())?;
593                    }
594                    writer
595                        .flush()
596                        .wrap_err("While flushing the archive after writing its contents")
597                        .wrap_err_with(|| c.clone())?;
598                    drop(writer);
599                }
600
601                _ => return Err(Error::InvalidHeader.into()),
602            }
603
604            if to_len < from_len {
605                archive.set_len(
606                    archive_len
607                        .checked_add_signed(to_len as i64 - from_len as i64)
608                        .ok_or(Error::InvalidHeader)
609                        .wrap_err("While truncating the archive")
610                        .wrap_err_with(|| c.clone())?,
611                )?;
612                archive
613                    .flush()
614                    .wrap_err("While flushing the archive after writing its contents")
615                    .wrap_err_with(|| c.clone())?;
616            }
617        } else {
618            match self.version {
619                1 | 2 => {
620                    let mut magic = old_entry.start_magic;
621                    for _ in from.as_str().bytes() {
622                        regress_magic(&mut magic);
623                    }
624                    archive
625                        .seek(SeekFrom::Start(old_entry.header_offset + 4))
626                        .wrap_err("While rewriting the path of the file in-place to the archive")
627                        .wrap_err_with(|| c.clone())?;
628                    archive
629                        .write_all(
630                            &to.as_str()
631                                .bytes()
632                                .map(|b| {
633                                    let b = if b == b'/' { b'\\' } else { b };
634                                    b ^ advance_magic(&mut magic) as u8
635                                })
636                                .collect_vec(),
637                        )
638                        .wrap_err("While rewriting the path of the file in-place to the archive")
639                        .wrap_err_with(|| c.clone())?;
640                    archive
641                        .flush()
642                        .wrap_err("While rewriting the path of the file in-place to the archive")
643                        .wrap_err_with(|| c.clone())?;
644                }
645
646                3 => {
647                    archive
648                        .seek(SeekFrom::Start(old_entry.header_offset + 16))
649                        .wrap_err("While rewriting the path of the file in-place to the archive")
650                        .wrap_err_with(|| c.clone())?;
651                    archive
652                        .write_all(
653                            &to.as_str()
654                                .bytes()
655                                .enumerate()
656                                .map(|(i, b)| {
657                                    let b = if b == b'/' { b'\\' } else { b };
658                                    b ^ (self.base_magic >> (8 * (i % 4))) as u8
659                                })
660                                .collect_vec(),
661                        )
662                        .wrap_err("While rewriting the path of the file in-place to the archive")
663                        .wrap_err_with(|| c.clone())?;
664                    archive
665                        .flush()
666                        .wrap_err("While rewriting the path of the file in-place to the archive")
667                        .wrap_err_with(|| c.clone())?;
668                }
669
670                _ => return Err(Error::InvalidHeader.into()),
671            }
672        }
673
674        Ok(())
675    }
676
677    fn create_dir(&self, path: impl AsRef<camino::Utf8Path>) -> Result<()> {
678        let path = path.as_ref();
679        let mut trie = self.trie.write();
680        if trie.contains_file(path) {
681            return Err(Error::IoError(AlreadyExists.into())).wrap_err_with(|| {
682                format!(
683                    "While creating a directory at {path:?} within a version {} archive",
684                    self.version
685                )
686            });
687        }
688        trie.create_dir(path);
689        Ok(())
690    }
691
692    fn exists(&self, path: impl AsRef<camino::Utf8Path>) -> Result<bool> {
693        let trie = self.trie.read();
694        Ok(trie.contains(path))
695    }
696
697    fn remove_dir(&self, path: impl AsRef<camino::Utf8Path>) -> Result<()> {
698        let path = path.as_ref();
699        let c = format!(
700            "While removing a directory at {path:?} within a version {} archive",
701            self.version
702        );
703        if !self.trie.read().contains_dir(path) {
704            return Err(Error::NotExist).wrap_err_with(|| c.clone());
705        }
706
707        let paths = self
708            .trie
709            .read()
710            .iter_prefix(path)
711            .ok_or(Error::NotExist)
712            .wrap_err_with(|| c.clone())?
713            .map(|(k, _)| k)
714            .collect_vec();
715        for file_path in paths {
716            self.remove_file(&file_path)
717                .wrap_err_with(|| format!("While removing a file {file_path:?} within the archive"))
718                .wrap_err_with(|| c.clone())?;
719        }
720
721        self.trie
722            .write()
723            .remove_dir(path)
724            .then_some(())
725            .ok_or(Error::NotExist)
726            .wrap_err_with(|| c.clone())?;
727        Ok(())
728    }
729
730    fn remove_file(&self, path: impl AsRef<camino::Utf8Path>) -> Result<()> {
731        let path = path.as_ref();
732        let path_len = path.as_str().bytes().len() as u64;
733        let mut archive = self.archive.lock();
734        let mut trie = self.trie.write();
735        let c = format!(
736            "While removing a file at {path:?} within a version {} archive",
737            self.version
738        );
739
740        let entry = *trie
741            .get_file(path)
742            .ok_or(Error::NotExist)
743            .wrap_err_with(|| c.clone())?;
744        let archive_len = archive.metadata().wrap_err_with(|| c.clone())?.size;
745
746        move_file_and_truncate(&mut archive, &mut trie, path, self.version, self.base_magic)
747            .wrap_err("While relocating the file header to the end of the archive")
748            .wrap_err_with(|| c.clone())?;
749
750        match self.version {
751            1 | 2 => {
752                archive
753                    .set_len(
754                        archive_len
755                            .checked_sub(entry.size + path_len + 8)
756                            .ok_or(Error::IoError(InvalidData.into()))
757                            .wrap_err("While truncating the archive")
758                            .wrap_err_with(|| c.clone())?,
759                    )
760                    .wrap_err("While truncating the archive")
761                    .wrap_err_with(|| c.clone())?;
762                archive
763                    .flush()
764                    .wrap_err("While flushing the archive after writing its contents")
765                    .wrap_err_with(|| c.clone())?;
766            }
767
768            3 => {
769                // Remove the header of the deleted file
770                let mut tmp = crate::host::File::new()
771                    .wrap_err("While creating a temporary file")
772                    .wrap_err_with(|| c.clone())?;
773                archive
774                    .seek(SeekFrom::Start(entry.header_offset + path_len + 16))
775                    .wrap_err("While copying the header of the archive into a temporary file")
776                    .wrap_err_with(|| c.clone())?;
777                std::io::copy(archive.as_file(), &mut tmp)
778                    .wrap_err("While copying the header of the archive into a temporary file")
779                    .wrap_err_with(|| c.clone())?;
780                tmp.flush()
781                    .wrap_err("While copying the header of the archive into a temporary file")
782                    .wrap_err_with(|| c.clone())?;
783                tmp.seek(SeekFrom::Start(0)).wrap_err("While copying a temporary file containing the archive header into the archive").wrap_err_with(|| c.clone())?;
784                archive.seek(SeekFrom::Start(entry.header_offset)).wrap_err("While copying a temporary file containing the archive header into the archive").wrap_err_with(|| c.clone())?;
785                std::io::copy(&mut tmp, archive.as_file()).wrap_err("While copying a temporary file containing the archive header into the archive").wrap_err_with(|| c.clone())?;
786
787                archive
788                    .set_len(
789                        archive_len
790                            .checked_sub(entry.size + path_len + 16)
791                            .ok_or(Error::IoError(InvalidData.into()))
792                            .wrap_err("While truncating the archive")
793                            .wrap_err_with(|| c.clone())?,
794                    )
795                    .wrap_err("While truncating the archive")
796                    .wrap_err_with(|| c.clone())?;
797                archive
798                    .flush()
799                    .wrap_err("While flushing the archive after writing its contents")
800                    .wrap_err_with(|| c.clone())?;
801            }
802
803            _ => {
804                return Err(Error::InvalidArchiveVersion(self.version)).wrap_err_with(|| c.clone())
805            }
806        }
807
808        trie.remove_file(path);
809        Ok(())
810    }
811
812    fn read_dir(&self, path: impl AsRef<camino::Utf8Path>) -> Result<Vec<DirEntry>> {
813        let path = path.as_ref();
814        let trie = self.trie.read();
815        let c = format!(
816            "While reading the contents of the directory {path:?} in a version {} archive",
817            self.version
818        );
819        if let Some(iter) = trie.iter_dir(path) {
820            iter.map(|(name, _)| {
821                let path = if path == "" {
822                    name.into()
823                } else {
824                    format!("{path}/{name}").into()
825                };
826                let metadata = self
827                    .metadata(&path)
828                    .wrap_err_with(|| {
829                        format!("While getting the metadata of {path:?} in the archive")
830                    })
831                    .wrap_err_with(|| c.clone())?;
832                Ok(DirEntry { path, metadata })
833            })
834            .try_collect()
835        } else {
836            Err(Error::NotExist).wrap_err_with(|| c.clone())
837        }
838    }
839}