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 extract<P: AsRef<Path>>(
171 &self,
172 path: &BoxPath,
173 output_path: P,
174 ) -> Result<(), ExtractError> {
175 let output_path = output_path.as_ref();
176 let record = self
177 .meta
178 .inode(path)
179 .and_then(|x| self.meta.record(x))
180 .ok_or_else(|| ExtractError::NotFoundInArchive(path.to_path_buf()))?;
181 self.extract_inner(path, record, output_path)
182 }
183
184 #[inline(always)]
185 pub fn extract_recursive<P: AsRef<Path>>(
186 &self,
187 path: &BoxPath,
188 output_path: P,
189 ) -> Result<(), ExtractError> {
190 let output_path = output_path.as_ref();
191
192 let inode = self
193 .meta
194 .inode(path)
195 .ok_or_else(|| ExtractError::NotFoundInArchive(path.to_path_buf()))?;
196
197 Records::new(&self.meta, &[inode], None).try_for_each(
198 |RecordsItem { path, record, .. }| self.extract_inner(&path, record, output_path),
199 )
200 }
201
202 #[inline(always)]
203 pub fn extract_all<P: AsRef<Path>>(&self, output_path: P) -> Result<(), ExtractError> {
204 let output_path = output_path.as_ref();
205 self.meta
206 .iter()
207 .try_for_each(|RecordsItem { path, record, .. }| {
208 self.extract_inner(&path, record, output_path)
209 })
210 }
211
212 #[inline(always)]
213 pub fn resolve_link(&self, link: &LinkRecord) -> io::Result<RecordsItem> {
214 match self.meta.inode(&link.target) {
215 Some(inode) => Ok(RecordsItem {
216 inode,
217 path: link.target.to_owned(),
218 record: self.meta.record(inode).unwrap(),
219 }),
220 None => Err(io::Error::new(
221 io::ErrorKind::NotFound,
222 format!("No inode for link target: {}", link.target),
223 )),
224 }
225 }
226
227 #[inline(always)]
228 pub fn read_bytes(&self, record: &FileRecord) -> io::Result<io::Take<File>> {
229 let mut file = OpenOptions::new().read(true).open(&self.path)?;
230
231 file.seek(io::SeekFrom::Start(self.offset + record.data.get()))?;
232 Ok(file.take(record.length))
233 }
234
235 #[inline(always)]
241 pub unsafe fn memory_map(&self, record: &FileRecord) -> io::Result<Mmap> {
242 MmapOptions::new()
243 .offset(self.offset + record.data.get())
244 .len(record.length as usize)
245 .map(self.file.get_ref())
246 }
247
248 #[inline(always)]
249 fn extract_inner(
250 &self,
251 path: &BoxPath,
252 record: &Record,
253 output_path: &Path,
254 ) -> Result<(), ExtractError> {
255 match record {
257 Record::File(file) => {
258 fs::create_dir_all(&output_path)
259 .map_err(|e| ExtractError::CreateDirFailed(e, output_path.to_path_buf()))?;
260 let out_path = output_path.join(path.to_path_buf());
261 let mut out_file = std::fs::OpenOptions::new();
262
263 #[cfg(unix)]
264 {
265 use std::os::unix::fs::OpenOptionsExt;
266
267 let mode: Option<u32> = record
268 .attr(self.metadata(), "unix.mode")
269 .filter(|x| x.len() == 4)
270 .map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]]));
271
272 if let Some(mode) = mode {
273 out_file.mode(mode);
274 }
275 }
276
277 let out_file = out_file
278 .create(true)
279 .write(true)
280 .open(&out_path)
281 .map_err(|e| ExtractError::CreateFileFailed(e, out_path.to_path_buf()))?;
282
283 let out_file = BufWriter::new(out_file);
284 self.decompress(file, out_file)
285 .map_err(|e| ExtractError::DecompressionFailed(e, path.to_path_buf()))?;
286
287 Ok(())
288 }
289 Record::Directory(_dir) => {
290 fs::create_dir_all(&output_path)
291 .map_err(|e| ExtractError::CreateDirFailed(e, output_path.to_path_buf()))?;
292 let new_dir = output_path.join(path.to_path_buf());
293 fs::create_dir(&new_dir).map_err(|e| ExtractError::CreateDirFailed(e, new_dir))
294 }
295 #[cfg(unix)]
296 Record::Link(link) => {
297 let link_target = self
298 .resolve_link(link)
299 .map_err(|e| ExtractError::ResolveLinkFailed(e, link.clone()))?;
300
301 let source = output_path.join(path.to_path_buf());
302 let destination = output_path.join(link_target.path.to_path_buf());
303
304 std::os::unix::fs::symlink(&source, &destination)
305 .map_err(|e| ExtractError::CreateLinkFailed(e, source, destination))
306 }
307 #[cfg(windows)]
308 Record::Link(link) => {
309 let link_target = self
310 .resolve_link(link)
311 .map_err(|e| ExtractError::ResolveLinkFailed(e, link.clone()))?;
312
313 let source = output_path.join(path.to_path_buf());
314 let destination = output_path.join(link_target.path.to_path_buf());
315
316 if link_target.record.as_directory().is_some() {
317 std::os::windows::fs::symlink_dir(&source, &destination)
318 .map_err(|e| ExtractError::CreateLinkFailed(e, source, destination))
319 } else {
320 std::os::windows::fs::symlink_file(&source, &destination)
321 .map_err(|e| ExtractError::CreateLinkFailed(e, source, destination))
322 }
323 }
324 }
325 }
326}