1use std::fs::File;
2use std::fs::{self, OpenOptions};
3use std::io::{self, prelude::*, BufReader, BufWriter, SeekFrom};
4use std::num::NonZeroU64;
5use std::path::{Path, PathBuf};
6
7use comde::Decompress;
8use memmap2::{Mmap, MmapOptions};
9
10use super::meta::Records;
11use super::{meta::RecordsItem, BoxMetadata};
12use crate::path::IntoBoxPathError;
13use crate::{
14 de::DeserializeOwned,
15 header::BoxHeader,
16 path::BoxPath,
17 record::{FileRecord, LinkRecord, Record},
18};
19
20#[derive(Debug)]
21pub struct BoxFileReader {
22 pub(crate) file: BufReader<File>,
23 pub(crate) path: PathBuf,
24 pub(crate) header: BoxHeader,
25 pub(crate) meta: BoxMetadata,
26 pub(crate) offset: u64,
27}
28
29#[inline(always)]
30pub(super) fn read_header<R: Read + Seek>(file: &mut R, offset: u64) -> io::Result<BoxHeader> {
31 file.seek(SeekFrom::Start(offset))?;
32 BoxHeader::deserialize_owned(file)
33}
34
35#[inline(always)]
36pub(super) fn read_trailer<R: Read + Seek, P: AsRef<Path>>(
37 reader: &mut R,
38 ptr: NonZeroU64,
39 _path: P,
40 offset: u64,
41) -> io::Result<BoxMetadata> {
42 reader.seek(SeekFrom::Start(offset + ptr.get()))?;
43 let meta = BoxMetadata::deserialize_owned(reader)?;
44
45 Ok(meta)
46}
47
48#[derive(Debug, thiserror::Error)]
49pub enum OpenError {
50 #[error("Could not find trailer (the end of the file is missing).")]
51 MissingTrailer,
52
53 #[error("Invalid trailer data (the data that describes where all the files are is invalid).")]
54 InvalidTrailer(#[source] std::io::Error),
55
56 #[error("Could not read header. Is this a valid Box archive?")]
57 MissingHeader(#[source] std::io::Error),
58
59 #[error("Invalid path to Box file. Path: '{}'", .1.display())]
60 InvalidPath(#[source] std::io::Error, PathBuf),
61
62 #[error("Failed to read Box file. Path: '{}'", .1.display())]
63 ReadFailed(#[source] std::io::Error, PathBuf),
64}
65
66#[derive(Debug, thiserror::Error)]
67pub enum ExtractError {
68 #[error("Creating directory failed. Path: '{}'", .1.display())]
69 CreateDirFailed(#[source] std::io::Error, PathBuf),
70
71 #[error("Creating file failed. Path: '{}'", .1.display())]
72 CreateFileFailed(#[source] std::io::Error, PathBuf),
73
74 #[error("Path not found in archive. Path: '{}'", .0.display())]
75 NotFoundInArchive(PathBuf),
76
77 #[error("Decompressing file failed. Path: '{}'", .1.display())]
78 DecompressionFailed(#[source] std::io::Error, PathBuf),
79
80 #[error("Creating link failed. Path: '{}' -> '{}'", .1.display(), .2.display())]
81 CreateLinkFailed(#[source] std::io::Error, PathBuf, PathBuf),
82
83 #[error("Resolving link failed: Path: '{}' -> '{}'", .1.name, .1.target)]
84 ResolveLinkFailed(#[source] std::io::Error, LinkRecord),
85
86 #[error("Could not convert to a valid Box path. Path suffix: '{}'", .1)]
87 ResolveBoxPathFailed(#[source] IntoBoxPathError, String),
88}
89
90impl BoxFileReader {
91 pub fn open_at_offset<P: AsRef<Path>>(
93 path: P,
94 offset: u64,
95 ) -> Result<BoxFileReader, OpenError> {
96 let path = path.as_ref().to_path_buf();
97 let path = path
98 .canonicalize()
99 .map_err(|e| OpenError::InvalidPath(e, path.to_path_buf()))?;
100
101 let mut file = OpenOptions::new()
102 .read(true)
103 .open(&path)
104 .map_err(|e| OpenError::ReadFailed(e, path.clone()))?;
105
106 let (header, meta) = {
109 let mut reader = BufReader::new(&mut file);
110 let header = read_header(&mut reader, offset).map_err(OpenError::MissingHeader)?;
111 let ptr = header.trailer.ok_or(OpenError::MissingTrailer)?;
112 let meta =
113 read_trailer(&mut reader, ptr, &path, offset).map_err(OpenError::InvalidTrailer)?;
114
115 (header, meta)
116 };
117
118 let f = BoxFileReader {
119 file: BufReader::new(file),
120 path,
121 header,
122 meta,
123 offset,
124 };
125
126 Ok(f)
127 }
128
129 #[inline]
131 pub fn open<P: AsRef<Path>>(path: P) -> Result<BoxFileReader, OpenError> {
132 Self::open_at_offset(path, 0)
133 }
134
135 #[inline(always)]
136 pub fn path(&self) -> &Path {
137 &self.path
138 }
139
140 #[inline(always)]
141 pub fn alignment(&self) -> u64 {
142 self.header.alignment
143 }
144
145 #[inline(always)]
146 pub fn version(&self) -> u32 {
147 self.header.version
148 }
149
150 #[inline(always)]
151 pub fn metadata(&self) -> &BoxMetadata {
152 &self.meta
153 }
154
155 #[inline(always)]
156 pub fn decompress_value<V: Decompress>(&self, record: &FileRecord) -> io::Result<V> {
157 let mmap = unsafe { self.memory_map(record)? };
158 record.compression.decompress(io::Cursor::new(mmap))
159 }
160
161 #[inline(always)]
162 pub fn decompress<W: Write>(&self, record: &FileRecord, dest: W) -> io::Result<()> {
163 let mmap = unsafe { self.memory_map(record)? };
164 record
165 .compression
166 .decompress_write(io::Cursor::new(mmap), dest)
167 }
168
169 #[inline(always)]
170 pub fn find(&self, path: &BoxPath) -> Result<&Record, ExtractError> {
171 let record = self
172 .meta
173 .inode(path)
174 .and_then(|x| self.meta.record(x))
175 .ok_or_else(|| ExtractError::NotFoundInArchive(path.to_path_buf()))?;
176 Ok(record)
177 }
178
179 #[inline(always)]
180 pub fn extract<P: AsRef<Path>>(
181 &self,
182 path: &BoxPath,
183 output_path: P,
184 ) -> Result<(), ExtractError> {
185 let output_path = output_path.as_ref();
186 let record = self
187 .meta
188 .inode(path)
189 .and_then(|x| self.meta.record(x))
190 .ok_or_else(|| ExtractError::NotFoundInArchive(path.to_path_buf()))?;
191 self.extract_inner(path, record, output_path)
192 }
193
194 #[inline(always)]
195 pub fn extract_recursive<P: AsRef<Path>>(
196 &self,
197 path: &BoxPath,
198 output_path: P,
199 ) -> Result<(), ExtractError> {
200 let output_path = output_path.as_ref();
201
202 let inode = self
203 .meta
204 .inode(path)
205 .ok_or_else(|| ExtractError::NotFoundInArchive(path.to_path_buf()))?;
206
207 Records::new(&self.meta, &[inode], None).try_for_each(
208 |RecordsItem { path, record, .. }| self.extract_inner(&path, record, output_path),
209 )
210 }
211
212 #[inline(always)]
213 pub fn extract_all<P: AsRef<Path>>(&self, output_path: P) -> Result<(), ExtractError> {
214 let output_path = output_path.as_ref();
215 self.meta
216 .iter()
217 .try_for_each(|RecordsItem { path, record, .. }| {
218 self.extract_inner(&path, record, output_path)
219 })
220 }
221
222 #[inline(always)]
223 pub fn resolve_link(&self, link: &LinkRecord) -> io::Result<RecordsItem> {
224 match self.meta.inode(&link.target) {
225 Some(inode) => Ok(RecordsItem {
226 inode,
227 path: link.target.to_owned(),
228 record: self.meta.record(inode).unwrap(),
229 }),
230 None => Err(io::Error::new(
231 io::ErrorKind::NotFound,
232 format!("No inode for link target: {}", link.target),
233 )),
234 }
235 }
236
237 #[inline(always)]
238 pub fn read_bytes(&self, record: &FileRecord) -> io::Result<io::Take<File>> {
239 let mut file = OpenOptions::new().read(true).open(&self.path)?;
240
241 file.seek(io::SeekFrom::Start(self.offset + record.data.get()))?;
242 Ok(file.take(record.length))
243 }
244
245 #[inline(always)]
251 pub unsafe fn memory_map(&self, record: &FileRecord) -> io::Result<Mmap> {
252 MmapOptions::new()
253 .offset(self.offset + record.data.get())
254 .len(record.length as usize)
255 .map(self.file.get_ref())
256 }
257
258 #[inline(always)]
259 fn extract_inner(
260 &self,
261 path: &BoxPath,
262 record: &Record,
263 output_path: &Path,
264 ) -> Result<(), ExtractError> {
265 match record {
267 Record::File(file) => {
268 fs::create_dir_all(output_path)
269 .map_err(|e| ExtractError::CreateDirFailed(e, output_path.to_path_buf()))?;
270 let out_path = output_path.join(path.to_path_buf());
271 let mut out_file = std::fs::OpenOptions::new();
272
273 #[cfg(unix)]
274 {
275 use std::os::unix::fs::OpenOptionsExt;
276
277 let mode: Option<u32> = record
278 .attr(self.metadata(), "unix.mode")
279 .filter(|x| x.len() == 4)
280 .map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]]));
281
282 if let Some(mode) = mode {
283 out_file.mode(mode);
284 }
285 }
286
287 let out_file = out_file
288 .create(true)
289 .write(true)
290 .open(&out_path)
291 .map_err(|e| ExtractError::CreateFileFailed(e, out_path.to_path_buf()))?;
292
293 let out_file = BufWriter::new(out_file);
294 self.decompress(file, out_file)
295 .map_err(|e| ExtractError::DecompressionFailed(e, path.to_path_buf()))?;
296
297 Ok(())
298 }
299 Record::Directory(_dir) => {
300 fs::create_dir_all(output_path)
301 .map_err(|e| ExtractError::CreateDirFailed(e, output_path.to_path_buf()))?;
302 let new_dir = output_path.join(path.to_path_buf());
303 fs::create_dir(&new_dir).map_err(|e| ExtractError::CreateDirFailed(e, new_dir))
304 }
305 #[cfg(unix)]
306 Record::Link(link) => {
307 let link_target = self
308 .resolve_link(link)
309 .map_err(|e| ExtractError::ResolveLinkFailed(e, link.clone()))?;
310
311 let source = output_path.join(path.to_path_buf());
312 let destination = output_path.join(link_target.path.to_path_buf());
313
314 std::os::unix::fs::symlink(&source, &destination)
315 .map_err(|e| ExtractError::CreateLinkFailed(e, source, destination))
316 }
317 #[cfg(windows)]
318 Record::Link(link) => {
319 let link_target = self
320 .resolve_link(link)
321 .map_err(|e| ExtractError::ResolveLinkFailed(e, link.clone()))?;
322
323 let source = output_path.join(path.to_path_buf());
324 let destination = output_path.join(link_target.path.to_path_buf());
325
326 if link_target.record.as_directory().is_some() {
327 std::os::windows::fs::symlink_dir(&source, &destination)
328 .map_err(|e| ExtractError::CreateLinkFailed(e, source, destination))
329 } else {
330 std::os::windows::fs::symlink_file(&source, &destination)
331 .map_err(|e| ExtractError::CreateLinkFailed(e, source, destination))
332 }
333 }
334 }
335 }
336}