luminol_filesystem/archiver/filesystem/
mod.rs1use async_std::io::{BufReader as AsyncBufReader, BufWriter as AsyncBufWriter};
19use color_eyre::eyre::WrapErr;
20use itertools::Itertools;
21use rand::Rng;
22use std::io::{prelude::*, BufReader, SeekFrom};
23
24use super::util::{advance_magic, read_file_xor_async, read_header, read_u32_xor};
25use super::{Entry, File, Trie, HEADER, MAGIC};
26use crate::{Error, Result};
27
28mod impls;
29
30#[derive(Debug, Default)]
31pub struct FileSystem<T> {
32 pub(super) trie: std::sync::Arc<parking_lot::RwLock<Trie>>,
33 pub(super) archive: std::sync::Arc<parking_lot::Mutex<T>>,
34 pub(super) version: u8,
35 pub(super) base_magic: u32,
36}
37
38impl<T> Clone for FileSystem<T> {
39 fn clone(&self) -> Self {
40 Self {
41 trie: self.trie.clone(),
42 archive: self.archive.clone(),
43 version: self.version,
44 base_magic: self.base_magic,
45 }
46 }
47}
48
49impl<T> FileSystem<T>
50where
51 T: crate::File,
52{
53 pub fn new(mut file: T) -> Result<Self> {
55 file.seek(SeekFrom::Start(0))
56 .wrap_err("While detecting archive version")?;
57 let mut reader = BufReader::new(&mut file);
58
59 let version = read_header(&mut reader).wrap_err("While detecting archive version")?;
60
61 let mut trie = crate::FileSystemTrie::new();
62
63 let mut base_magic = MAGIC;
64
65 let c = format!(
66 "While performing initial parsing of the header of a version {version} archive"
67 );
68
69 match version {
70 1 | 2 => {
71 let mut magic = MAGIC;
72
73 let mut i = 0;
74
75 while let Ok(path_len) = read_u32_xor(&mut reader, advance_magic(&mut magic)) {
76 let mut path = vec![0; path_len as usize];
77 reader.read_exact(&mut path).wrap_err("").wrap_err_with(|| format!("While reading the path (path length = {path_len}) of file #{i} in the archive")).wrap_err_with(|| c.clone())?;
78 for byte in path.iter_mut() {
79 let char = *byte ^ advance_magic(&mut magic) as u8;
80 if char == b'\\' {
81 *byte = b'/';
82 } else {
83 *byte = char;
84 }
85 }
86 let path = camino::Utf8PathBuf::from(String::from_utf8(path).wrap_err_with(|| format!("While reading the path (path length = {path_len}) of file #{i} in the archive)")).wrap_err_with(|| c.clone())?);
87
88 let entry_len = read_u32_xor(&mut reader, advance_magic(&mut magic))
89 .wrap_err_with(|| {
90 format!("While reading the file length (path = {path:?}) of file #{i} in the archive")
91 })
92 .wrap_err_with(|| c.clone())?;
93
94 let stream_position = reader
95 .stream_position()
96 .wrap_err_with(|| {
97 format!("While reading the file length (path = {path:?}) of file #{i} in the archive")
98 })
99 .wrap_err_with(|| c.clone())?;
100 let entry = Entry {
101 size: entry_len as u64,
102 header_offset: stream_position
103 .checked_sub(path_len as u64 + 8)
104 .ok_or(Error::InvalidHeader).wrap_err_with(|| format!("While reading the file length (path = {path:?}) of file #{i} in the archive")).wrap_err_with(|| c.clone())?,
105 body_offset: stream_position,
106 start_magic: magic,
107 };
108
109 trie.create_file(path, entry);
110
111 reader
112 .seek(SeekFrom::Start(entry.body_offset + entry.size))
113 .wrap_err_with(|| {
114 format!(
115 "While seeking to offset {} to read file #{} in the archive",
116 entry.body_offset + entry.size,
117 i + 1
118 )
119 })
120 .wrap_err_with(|| c.clone())?;
121 i += 1;
122 }
123 }
124 3 => {
125 let mut u32_buf = [0; 4];
126 reader
127 .read_exact(&mut u32_buf)
128 .wrap_err("While reading the base magic value of the archive")
129 .wrap_err_with(|| c.clone())?;
130
131 base_magic = u32::from_le_bytes(u32_buf);
132 base_magic = base_magic.wrapping_mul(9).wrapping_add(3);
133
134 let mut i = 0;
135
136 while let Ok(body_offset) = read_u32_xor(&mut reader, base_magic) {
137 if body_offset == 0 {
138 break;
139 }
140 let header_offset = reader
141 .stream_position()
142 .wrap_err_with(|| {
143 format!("While reading the file offset of file #{i} in the archive")
144 })
145 .wrap_err_with(|| c.clone())?
146 .checked_sub(4)
147 .ok_or(Error::InvalidHeader)
148 .wrap_err_with(|| {
149 format!("While reading the file offset of file #{i} in the archive")
150 })
151 .wrap_err_with(|| c.clone())?;
152
153 let entry_len = read_u32_xor(&mut reader, base_magic).wrap_err_with(|| format!("While reading the file length (file offset = {body_offset}) of file #{i} in the archive")).wrap_err_with(|| c.clone())?;
154 let magic = read_u32_xor(&mut reader, base_magic).wrap_err_with(|| format!("While reading the magic value (file offset = {body_offset}, file length = {entry_len}) of file #{i} in the archive")).wrap_err_with(|| c.clone())?;
155 let path_len = read_u32_xor(&mut reader, base_magic).wrap_err_with(|| format!("While reading the path length (file offset = {body_offset}, file length = {entry_len}) of file #{i} in the archive")).wrap_err_with(|| c.clone())?;
156
157 let mut path = vec![0; path_len as usize];
158 reader.read_exact(&mut path).wrap_err_with(|| format!("While reading the path (file offset = {body_offset}, file length = {entry_len}, path length = {path_len}) of file #{i} in the archive")).wrap_err_with(|| c.clone())?;
159 for (i, byte) in path.iter_mut().enumerate() {
160 let char = *byte ^ (base_magic >> (8 * (i % 4))) as u8;
161 if char == b'\\' {
162 *byte = b'/';
163 } else {
164 *byte = char;
165 }
166 }
167 let path = camino::Utf8PathBuf::from(String::from_utf8(path).wrap_err_with(|| format!("While reading the path (file offset = {body_offset}, file length = {entry_len}, path length = {path_len}) of file #{i} in the archive")).wrap_err_with(|| c.clone())?);
168
169 let entry = Entry {
170 size: entry_len as u64,
171 header_offset,
172 body_offset: body_offset as u64,
173 start_magic: magic,
174 };
175 trie.create_file(path, entry);
176 i += 1;
177 }
178 }
179 _ => return Err(Error::InvalidArchiveVersion(version).into()),
180 }
181
182 Ok(Self {
183 trie: std::sync::Arc::new(parking_lot::RwLock::new(trie)),
184 archive: std::sync::Arc::new(parking_lot::Mutex::new(file)),
185 version,
186 base_magic,
187 })
188 }
189
190 pub async fn from_buffer_and_files<'a, I, P, R>(
193 mut buffer: T,
194 version: u8,
195 files: I,
196 ) -> Result<Self>
197 where
198 T: futures_lite::AsyncWrite + futures_lite::AsyncSeek + Unpin,
199 I: Iterator<Item = Result<(&'a P, u32, R)>>,
200 P: AsRef<camino::Utf8Path> + 'a,
201 R: futures_lite::AsyncRead + Unpin,
202 {
203 use futures_lite::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
204
205 let c = format!("While creating a new version {version} archive");
206
207 buffer
208 .set_len(0)
209 .wrap_err("While clearing the archive")
210 .wrap_err_with(|| c.clone())?;
211 AsyncSeekExt::seek(&mut buffer, SeekFrom::Start(0))
212 .await
213 .wrap_err("While clearing the archive")
214 .wrap_err_with(|| c.clone())?;
215
216 let mut writer = AsyncBufWriter::new(&mut buffer);
217 writer
218 .write_all(HEADER)
219 .await
220 .wrap_err("While writing the archive version")
221 .wrap_err_with(|| c.clone())?;
222 writer
223 .write_all(&[version])
224 .await
225 .wrap_err("While writing the archive version")
226 .wrap_err_with(|| c.clone())?;
227
228 let mut trie = Trie::new();
229
230 match version {
231 1 | 2 => {
232 let mut magic = MAGIC;
233 let mut header_offset = 8;
234
235 for (i, result) in files.enumerate() {
236 let (path, size, file) = result
237 .wrap_err_with(|| {
238 format!(
239 "While getting file #{i} to add to the archive from the iterator"
240 )
241 })
242 .wrap_err_with(|| c.clone())?;
243 let reader = AsyncBufReader::new(file.take(size as u64));
244 let path = path.as_ref();
245 let header_size = path.as_str().bytes().len() as u64 + 8;
246
247 writer
249 .write_all(
250 &(path.as_str().bytes().len() as u32 ^ advance_magic(&mut magic))
251 .to_le_bytes(),
252 )
253 .await.wrap_err_with(|| format!("While writing the path length of file #{i} (path = {path:?}, file length = {size}) to the archive")).wrap_err_with(|| c.clone())?;
254 writer
255 .write_all(
256 &path
257 .as_str()
258 .bytes()
259 .map(|b| {
260 let b = if b == b'/' { b'\\' } else { b };
261 b ^ advance_magic(&mut magic) as u8
262 })
263 .collect_vec(),
264 )
265 .await.wrap_err_with(|| format!("While writing the path of file #{i} (path = {path:?}, file length = {size}) to the archive")).wrap_err_with(|| c.clone())?;
266 writer
267 .write_all(&(size ^ advance_magic(&mut magic)).to_le_bytes())
268 .await.wrap_err_with(|| format!("While writing the file length of file #{i} (path = {path:?}, file length = {size}) to the archive")).wrap_err_with(|| c.clone())?;
269
270 async_std::io::copy(&mut read_file_xor_async(reader, magic), &mut writer)
272 .await.wrap_err_with(|| format!("While writing the contents of file #{i} (path = {path:?}, file length = {size}) to the archive")).wrap_err_with(|| c.clone())?;
273
274 trie.create_file(
275 path,
276 Entry {
277 header_offset,
278 body_offset: header_offset + header_size,
279 size: size as u64,
280 start_magic: magic,
281 },
282 );
283
284 header_offset += header_size + size as u64;
285 }
286
287 writer
288 .flush()
289 .await
290 .wrap_err("While flushing the archive after writing its contents")
291 .wrap_err_with(|| c.clone())?;
292 drop(writer);
293 Ok(Self {
294 trie: std::sync::Arc::new(parking_lot::RwLock::new(trie)),
295 archive: std::sync::Arc::new(parking_lot::Mutex::new(buffer)),
296 version,
297 base_magic: MAGIC,
298 })
299 }
300
301 3 => {
302 let mut tmp = crate::host::File::new()
303 .wrap_err("While creating a temporary file")
304 .wrap_err_with(|| c.clone())?;
305 let mut tmp_writer = AsyncBufWriter::new(&mut tmp);
306 let mut entries = if let (_, Some(upper_bound)) = files.size_hint() {
307 Vec::with_capacity(upper_bound)
308 } else {
309 Vec::new()
310 };
311
312 let base_magic: u32 = rand::thread_rng().gen();
313 writer
314 .write_all(&(base_magic.wrapping_sub(3).wrapping_mul(954437177)).to_le_bytes())
315 .await
316 .wrap_err("While writing the archive base magic value")
317 .wrap_err_with(|| c.clone())?;
318 let mut header_offset = 12;
319 let mut body_offset = 0;
320
321 for (i, result) in files.enumerate() {
322 let (path, size, file) = result
323 .wrap_err_with(|| {
324 format!(
325 "While getting file #{i} to write to the archive from the iterator"
326 )
327 })
328 .wrap_err_with(|| c.clone())?;
329 let reader = AsyncBufReader::new(file.take(size as u64));
330 let path = path.as_ref();
331 let entry_magic: u32 = rand::thread_rng().gen();
332
333 writer.seek(SeekFrom::Current(4)).await.wrap_err_with(|| format!("While writing the file length of file #{i} (path = {path:?}, file length = {size}) to the archive")).wrap_err_with(|| c.clone())?;
335 writer.write_all(&(size ^ base_magic).to_le_bytes()).await.wrap_err_with(|| format!("While writing the file length of file #{i} (path = {path:?}, file length = {size}) to the archive")).wrap_err_with(|| c.clone())?;
336 writer
337 .write_all(&(entry_magic ^ base_magic).to_le_bytes())
338 .await.wrap_err_with(|| format!("While writing the magic value of file #{i} (path = {path:?}, file length = {size}) to the archive")).wrap_err_with(|| c.clone())?;
339 writer
340 .write_all(&(path.as_str().bytes().len() as u32 ^ base_magic).to_le_bytes())
341 .await.wrap_err_with(|| format!("While writing the path length of file #{i} (path = {path:?}, file length = {size}) to the archive")).wrap_err_with(|| c.clone())?;
342 writer
343 .write_all(
344 &path
345 .as_str()
346 .bytes()
347 .enumerate()
348 .map(|(i, b)| {
349 let b = if b == b'/' { b'\\' } else { b };
350 b ^ (base_magic >> (8 * (i % 4))) as u8
351 })
352 .collect_vec(),
353 )
354 .await.wrap_err_with(|| format!("While writing the path of file #{i} (path = {path:?}, file length = {size}) to the archive")).wrap_err_with(|| c.clone())?;
355
356 async_std::io::copy(
358 &mut read_file_xor_async(reader, entry_magic),
359 &mut tmp_writer,
360 )
361 .await.wrap_err_with(|| format!("While writing the contents of file #{i} (path = {path:?}, file length = {size}) to a temporary file before writing it to the archive")).wrap_err_with(|| c.clone())?;
362
363 entries.push((
364 path.to_owned(),
365 Entry {
366 header_offset,
367 body_offset,
368 size: size as u64,
369 start_magic: entry_magic,
370 },
371 ));
372
373 header_offset += path.as_str().bytes().len() as u64 + 16;
374 body_offset += size as u64;
375 }
376
377 writer
379 .write_all(&base_magic.to_le_bytes())
380 .await
381 .wrap_err("While writing the header terminator to the archive")
382 .wrap_err_with(|| c.clone())?;
383
384 tmp_writer
386 .flush()
387 .await
388 .wrap_err("While flushing a temporary file containing the archive body")
389 .wrap_err_with(|| c.clone())?;
390 drop(tmp_writer);
391 AsyncSeekExt::seek(&mut tmp, SeekFrom::Start(0)).await.wrap_err("While copying a temporary file containing the archive body into the archive").wrap_err_with(|| c.clone())?;
392 async_std::io::copy(&mut tmp, &mut writer).await.wrap_err("While copying a temporary file containin the archive body into the archive").wrap_err_with(|| c.clone())?;
393
394 let header_size = header_offset + 4;
396 for (i, (path, mut entry)) in entries.into_iter().enumerate() {
397 entry.body_offset += header_size;
398 writer.seek(SeekFrom::Start(entry.header_offset)).await.wrap_err_with(|| format!("While writing the file offset of file #{i} (path = {path:?}, file length = {}, file offset = {})", entry.size, entry.body_offset)).wrap_err_with(|| c.clone())?;
399 writer
400 .write_all(&(entry.body_offset as u32 ^ base_magic).to_le_bytes())
401 .await.wrap_err_with(|| format!("While writing the file offset of file #{i} (path = {path:?}, file length = {}, file offset = {}) to the archive", entry.size, entry.body_offset)).wrap_err_with(|| c.clone())?;
402 trie.create_file(path, entry);
403 }
404
405 writer
406 .flush()
407 .await
408 .wrap_err("While flushing the archive after writing its contents")
409 .wrap_err_with(|| c.clone())?;
410 drop(writer);
411 Ok(Self {
412 trie: std::sync::Arc::new(parking_lot::RwLock::new(trie)),
413 archive: std::sync::Arc::new(parking_lot::Mutex::new(buffer)),
414 version,
415 base_magic,
416 })
417 }
418
419 _ => Err(Error::NotSupported.into()),
420 }
421 }
422}