luminol_filesystem/archiver/
file.rs1use 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 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 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 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 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}