luminol_filesystem/archiver/
file.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 pin_project::pin_project;
19use std::io::{
20    prelude::*,
21    BufReader,
22    ErrorKind::{InvalidData, PermissionDenied},
23    SeekFrom,
24};
25use std::{pin::Pin, task::Poll};
26
27use super::util::{move_file_and_truncate, read_file_xor, regress_magic};
28use super::Trie;
29use crate::Metadata;
30use crate::{File as _, StdIoErrorExt};
31
32#[derive(Debug)]
33#[pin_project]
34pub struct File<T>
35where
36    T: crate::File,
37{
38    pub(super) archive: Option<std::sync::Arc<parking_lot::Mutex<T>>>,
39    pub(super) trie: Option<std::sync::Arc<parking_lot::RwLock<Trie>>>,
40    pub(super) path: camino::Utf8PathBuf,
41    pub(super) read_allowed: bool,
42    pub(super) modified: parking_lot::Mutex<bool>,
43    pub(super) version: u8,
44    pub(super) base_magic: u32,
45    #[pin]
46    pub(super) tmp: crate::host::File,
47}
48
49impl<T> std::io::Write for File<T>
50where
51    T: crate::File,
52{
53    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
54        let c = format!(
55            "While writing to file {:?} within a version {} archive",
56            self.path, self.version
57        );
58        if self.archive.is_some() {
59            let mut modified = self.modified.lock();
60            *modified = true;
61            self.tmp.write(buf).wrap_io_err_with(|| c.clone())
62        } else {
63            Err(std::io::Error::new(
64                PermissionDenied,
65                "Attempted to write to file with no write permissions",
66            ))
67            .wrap_io_err_with(|| c.clone())
68        }
69    }
70
71    fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
72        let c = format!(
73            "While writing (vectored) to file {:?} within a version {} archive",
74            self.path, self.version
75        );
76        if self.archive.is_some() {
77            let mut modified = self.modified.lock();
78            *modified = true;
79            self.tmp.write_vectored(bufs).wrap_io_err_with(|| c.clone())
80        } else {
81            Err(std::io::Error::new(
82                PermissionDenied,
83                "Attempted to write to file with no write permissions",
84            ))
85            .wrap_io_err_with(|| c.clone())
86        }
87    }
88
89    fn flush(&mut self) -> std::io::Result<()> {
90        let mut modified = self.modified.lock();
91        if !*modified {
92            return Ok(());
93        }
94        let c = format!(
95            "While flushing file {:?} within a version {} archive",
96            self.path, self.version
97        );
98
99        let mut archive = self
100            .archive
101            .as_ref()
102            .ok_or(std::io::Error::new(
103                PermissionDenied,
104                "Attempted to write to file with no write permissions",
105            ))
106            .wrap_io_err_with(|| c.clone())?
107            .lock();
108        let mut trie = self
109            .trie
110            .as_ref()
111            .ok_or(std::io::Error::new(
112                PermissionDenied,
113                "Attempted to write to file with no write permissions",
114            ))
115            .wrap_io_err_with(|| c.clone())?
116            .write();
117        let archive_len = archive.metadata()?.size;
118
119        let tmp_stream_position = self.tmp.stream_position().wrap_io_err_with(|| c.clone())?;
120        self.tmp.flush().wrap_io_err_with(|| c.clone())?;
121        self.tmp
122            .seek(SeekFrom::Start(0))
123            .wrap_io_err_with(|| c.clone())?;
124
125        // If the size of the file has changed, rotate the archive to place the file at the end of
126        // the archive before writing the new contents of the file
127        let mut entry = *trie
128            .get_file(&self.path)
129            .ok_or(std::io::Error::new(
130                InvalidData,
131                "Could not find the file within the archive",
132            ))
133            .wrap_io_err_with(|| c.clone())?;
134        let old_size = entry.size;
135        let new_size = self.tmp.metadata().wrap_io_err_with(|| c.clone())?.size;
136        if old_size != new_size {
137            move_file_and_truncate(
138                &mut archive,
139                &mut trie,
140                &self.path,
141                self.version,
142                self.base_magic,
143            )
144            .wrap_io_err("While relocating the file header to the end of the archive")
145            .wrap_io_err_with(|| c.clone())?;
146            entry = *trie
147                .get_file(&self.path)
148                .ok_or(std::io::Error::new(
149                    InvalidData,
150                    "Could not find the file within the archive",
151                ))
152                .wrap_io_err_with(|| c.clone())?;
153
154            // Write the new length of the file to the archive
155            match self.version {
156                1 | 2 => {
157                    let mut magic = entry.start_magic;
158                    regress_magic(&mut magic);
159                    archive
160                        .seek(SeekFrom::Start(
161                            entry.body_offset.checked_sub(4).ok_or(InvalidData)?,
162                        ))
163                        .wrap_io_err("While writing the file length to the archive")
164                        .wrap_io_err_with(|| c.clone())?;
165                    archive
166                        .write_all(&(new_size as u32 ^ magic).to_le_bytes())
167                        .wrap_io_err(
168                            "While writing the base magic value of the file to the archive",
169                        )
170                        .wrap_io_err_with(|| c.clone())?;
171                }
172
173                3 => {
174                    archive
175                        .seek(SeekFrom::Start(entry.header_offset + 4))
176                        .wrap_io_err("While writing the file length to the archive")
177                        .wrap_io_err_with(|| c.clone())?;
178                    archive
179                        .write_all(&(new_size as u32 ^ self.base_magic).to_le_bytes())
180                        .wrap_io_err(
181                            "While writing the base magic value of the file to the archive",
182                        )
183                        .wrap_io_err_with(|| c.clone())?;
184                }
185
186                _ => {
187                    return Err(std::io::Error::new(
188                        InvalidData,
189                        format!(
190                            "Invalid archive version: {} (supported versions are 1, 2 and 3)",
191                            self.version
192                        ),
193                    ))
194                }
195            }
196
197            // Write the new length of the file to the trie
198            trie.get_file_mut(&self.path)
199                .ok_or(std::io::Error::new(
200                    InvalidData,
201                    "Could not find the file within the archive",
202                ))
203                .wrap_io_err("After changing the file length within the archive")
204                .wrap_io_err_with(|| c.clone())?
205                .size = new_size;
206        }
207
208        // Now write the new contents of the file
209        archive
210            .seek(SeekFrom::Start(entry.body_offset))
211            .wrap_io_err("While writing the file contents to the archive")
212            .wrap_io_err_with(|| c.clone())?;
213        let mut reader = BufReader::new(&mut self.tmp);
214        std::io::copy(
215            &mut read_file_xor(&mut reader, entry.start_magic),
216            archive.as_file(),
217        )
218        .wrap_io_err("While writing the file contents to the archive")
219        .wrap_io_err_with(|| c.clone())?;
220        drop(reader);
221        self.tmp
222            .seek(SeekFrom::Start(tmp_stream_position))
223            .wrap_io_err("While writing the file contents to the archive")
224            .wrap_io_err_with(|| c.clone())?;
225
226        if old_size > new_size {
227            archive
228                .set_len(
229                    archive_len
230                        .checked_sub(old_size)
231                        .ok_or(std::io::Error::new(
232                            InvalidData,
233                            "Archive header is corrupt",
234                        ))
235                        .wrap_io_err("While truncating the archive")
236                        .wrap_io_err_with(|| c.clone())?
237                        .checked_add(new_size)
238                        .ok_or(std::io::Error::new(
239                            InvalidData,
240                            "Archive header is corrupt",
241                        ))
242                        .wrap_io_err("While truncating the archive")
243                        .wrap_io_err_with(|| c.clone())?,
244                )
245                .wrap_io_err("While truncating the archive")
246                .wrap_io_err_with(|| c.clone())?;
247        }
248        archive
249            .flush()
250            .wrap_io_err("While flushing the archive after writing its contents")
251            .wrap_io_err_with(|| c.clone())?;
252        *modified = false;
253        Ok(())
254    }
255}
256
257impl<T> std::io::Read for File<T>
258where
259    T: crate::File,
260{
261    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
262        let c = format!(
263            "While reading from file {:?} within a version {} archive",
264            self.path, self.version
265        );
266        if self.read_allowed {
267            self.tmp.read(buf).wrap_io_err_with(|| c.clone())
268        } else {
269            Err(std::io::Error::new(
270                PermissionDenied,
271                "Attempted to read from file with no read permissions",
272            ))
273            .wrap_io_err_with(|| c.clone())
274        }
275    }
276
277    fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result<usize> {
278        let c = format!(
279            "While reading (vectored) from file {:?} within a version {} archive",
280            self.path, self.version
281        );
282        if self.read_allowed {
283            self.tmp.read_vectored(bufs).wrap_io_err_with(|| c.clone())
284        } else {
285            Err(std::io::Error::new(
286                PermissionDenied,
287                "Attempted to read from file with no read permissions",
288            ))
289            .wrap_io_err_with(|| c.clone())
290        }
291    }
292
293    fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> {
294        let c = format!(
295            "While reading (exact) from file {:?} within a version {} archive",
296            self.path, self.version
297        );
298        if self.read_allowed {
299            self.tmp.read_exact(buf).wrap_io_err_with(|| c.clone())
300        } else {
301            Err(std::io::Error::new(
302                PermissionDenied,
303                "Attempted to read from file with no read permissions",
304            ))
305            .wrap_io_err_with(|| c.clone())
306        }
307    }
308}
309
310impl<T> futures_lite::AsyncRead for File<T>
311where
312    T: crate::File + futures_lite::AsyncRead,
313{
314    fn poll_read(
315        self: std::pin::Pin<&mut Self>,
316        cx: &mut std::task::Context<'_>,
317        buf: &mut [u8],
318    ) -> Poll<std::io::Result<usize>> {
319        let c = format!(
320            "While asynchronously reading from file {:?} within a version {} archive",
321            self.path, self.version
322        );
323        if self.read_allowed {
324            self.project()
325                .tmp
326                .poll_read(cx, buf)
327                .map(|r| r.wrap_io_err_with(|| c.clone()))
328        } else {
329            Poll::Ready(
330                Err(std::io::Error::new(
331                    PermissionDenied,
332                    "Attempted to read from file with no read permissions",
333                ))
334                .wrap_io_err_with(|| c.clone()),
335            )
336        }
337    }
338
339    fn poll_read_vectored(
340        self: Pin<&mut Self>,
341        cx: &mut std::task::Context<'_>,
342        bufs: &mut [std::io::IoSliceMut<'_>],
343    ) -> Poll<std::io::Result<usize>> {
344        let c = format!(
345            "While asynchronously reading (vectored) from file {:?} within a version {} archive",
346            self.path, self.version
347        );
348        if self.read_allowed {
349            self.project()
350                .tmp
351                .poll_read_vectored(cx, bufs)
352                .map(|r| r.wrap_io_err_with(|| c.clone()))
353        } else {
354            Poll::Ready(
355                Err(std::io::Error::new(
356                    PermissionDenied,
357                    "Attempted to read from file with no read permissions",
358                ))
359                .wrap_io_err_with(|| c.clone()),
360            )
361        }
362    }
363}
364
365impl<T> std::io::Seek for File<T>
366where
367    T: crate::File,
368{
369    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
370        let c = format!(
371            "While asynchronously seeking file {:?} within a version {} archive",
372            self.path, self.version
373        );
374        self.tmp.seek(pos).wrap_io_err(c)
375    }
376
377    fn stream_position(&mut self) -> std::io::Result<u64> {
378        let c = format!(
379            "While getting stream position for file {:?} within a version {} archive",
380            self.path, self.version
381        );
382        self.tmp.stream_position().wrap_io_err(c)
383    }
384}
385
386impl<T> futures_lite::AsyncSeek for File<T>
387where
388    T: crate::File + futures_lite::AsyncSeek,
389{
390    fn poll_seek(
391        self: Pin<&mut Self>,
392        cx: &mut std::task::Context<'_>,
393        pos: SeekFrom,
394    ) -> Poll<std::io::Result<u64>> {
395        let c = format!(
396            "While asynchronously seeking file {:?} within a version {} archive",
397            self.path, self.version
398        );
399        self.project()
400            .tmp
401            .poll_seek(cx, pos)
402            .map(|r| r.wrap_io_err(c))
403    }
404}
405
406impl<T> crate::File for File<T>
407where
408    T: crate::File,
409{
410    fn metadata(&self) -> std::io::Result<Metadata> {
411        let c = format!(
412            "While getting metadata for file {:?} within a version {} archive",
413            self.path, self.version
414        );
415        self.tmp.metadata().wrap_io_err(c)
416    }
417
418    fn set_len(&self, new_size: u64) -> std::io::Result<()> {
419        let c = format!(
420            "While setting length for file {:?} within a version {} archive",
421            self.path, self.version
422        );
423        if self.archive.is_some() {
424            let mut modified = self.modified.lock();
425            *modified = true;
426            self.tmp.set_len(new_size).wrap_io_err_with(|| c.clone())
427        } else {
428            Err(std::io::Error::new(
429                PermissionDenied,
430                "Attempted to write to file with no write permissions",
431            ))
432            .wrap_io_err_with(|| c.clone())
433        }
434    }
435}