Skip to main content

simple_archive/
reader.rs

1use crate::{
2    carchive::{self, archive_entry, archive_read_free},
3    prelude::*,
4};
5
6use libc::{c_int, c_void};
7
8use crate::carchive::archive;
9use std::{
10    ffi::CString,
11    io::{Error as IOError, ErrorKind, Read, Seek, SeekFrom},
12    mem::MaybeUninit,
13};
14
15const BUFFER_SIZE: usize = 16384;
16
17/// A reader for compressed archives.
18///
19/// `ArchiveReader` allows reading from any source that implements `Read + Seek`.
20/// It provides an iterator over the entries in the archive and implements `Read`
21/// to extract the content of the current entry.
22pub struct ArchiveReader<R: Read + Seek> {
23    archive_reader: Option<*mut archive>,
24    #[allow(dead_code)]
25    fileref: Box<SourceReader<R>>,
26    current_entry: Option<Metadata>,
27}
28
29struct SourceReader<R: Read + Seek> {
30    obj: R,
31    buffer: Box<[u8]>,
32}
33
34unsafe extern "C" fn archivereader_read<R: Read + Seek>(
35    archive: *mut carchive::archive,
36    client_data: *mut c_void,
37    buffer: *mut *const c_void,
38) -> carchive::la_ssize_t {
39    let reader = (client_data as *mut SourceReader<R>).as_mut().unwrap();
40    *buffer = reader.buffer.as_ptr() as *const c_void;
41
42    match reader.obj.read(reader.buffer.as_mut()) {
43        Ok(size) => size as carchive::la_ssize_t,
44        Err(e) => {
45            let description = CString::new(e.to_string()).unwrap();
46
47            carchive::archive_set_error(
48                archive,
49                e.raw_os_error().unwrap_or(0),
50                description.as_ptr(),
51            );
52
53            -1
54        }
55    }
56}
57
58unsafe extern "C" fn archivereader_seek<R: Read + Seek>(
59    _: *mut carchive::archive,
60    client_data: *mut c_void,
61    offset: carchive::la_int64_t,
62    whence: c_int,
63) -> i64 {
64    let seeker = (client_data as *mut SourceReader<R>).as_mut().unwrap();
65    let whence = match whence {
66        0 => SeekFrom::Start(offset as u64),
67        1 => SeekFrom::Current(offset),
68        2 => SeekFrom::End(offset),
69        _ => return -1,
70    };
71
72    match seeker.obj.seek(whence) {
73        Ok(offset) => offset as i64,
74        Err(_) => -1,
75    }
76}
77
78impl<R: Read + Seek> ArchiveReader<R> {
79    /// Creates a new `ArchiveReader` from a source that implements `Read + Seek`.
80    pub fn new(source: R) -> Result<Self> {
81        let buffer = [0; BUFFER_SIZE];
82        let mut fref = Box::new(SourceReader {
83            obj: source,
84            buffer: Box::new(buffer),
85        });
86
87        unsafe {
88            Ok(ArchiveReader {
89                archive_reader: Some(ArchiveReader::start(&mut fref)?),
90                fileref: fref,
91                current_entry: Option::None,
92            })
93        }
94    }
95
96    unsafe fn start(fref: &mut Box<SourceReader<R>>) -> Result<*mut archive> {
97        let archive_reader = carchive::archive_read_new();
98
99        if archive_reader.is_null() {
100            return Err(Error::NullArchive);
101        }
102
103        match carchive::archive_read_support_filter_all(archive_reader) {
104            carchive::ARCHIVE_OK | carchive::ARCHIVE_WARN => (),
105            _ => return Err(Error::from(archive_reader)),
106        };
107
108        match carchive::archive_read_support_format_all(archive_reader) {
109            carchive::ARCHIVE_OK | carchive::ARCHIVE_WARN => (),
110            _ => return Err(Error::from(archive_reader)),
111        };
112
113        match carchive::archive_read_set_seek_callback(
114            archive_reader,
115            Some(archivereader_seek::<R>),
116        ) {
117            carchive::ARCHIVE_OK | carchive::ARCHIVE_WARN => (),
118            _ => return Err(Error::from(archive_reader)),
119        }
120
121        match carchive::archive_read_open(
122            archive_reader,
123            std::ptr::addr_of_mut!(**fref) as *mut c_void,
124            None,
125            Some(archivereader_read::<R>),
126            None,
127        ) {
128            carchive::ARCHIVE_OK | carchive::ARCHIVE_WARN => (),
129            _ => return Err(Error::from(archive_reader)),
130        };
131
132        Ok(archive_reader)
133    }
134
135    /// Returns a list of all file metadata in the archive.
136    ///
137    /// Note: This will consume the reader as it iterates through the whole archive.
138    pub fn list_files(self) -> Result<Vec<Metadata>> {
139        let archive = self.get_archive()?;
140
141        let mut outlist = Vec::<Metadata>::new();
142
143        loop {
144            unsafe {
145                let mut entry = std::mem::MaybeUninit::<*mut archive_entry>::uninit();
146                match carchive::archive_read_next_header(archive, entry.as_mut_ptr()) {
147                    carchive::ARCHIVE_EOF => break,
148                    carchive::ARCHIVE_OK | carchive::ARCHIVE_WARN => {
149                        let entry = entry.assume_init();
150                        outlist.push(entry.into());
151                    }
152                    _ => return Err(Error::from(archive)),
153                };
154            }
155        }
156
157        Ok(outlist)
158    }
159
160    /// Returns the metadata of the current entry being read.
161    pub fn current_metadata(&self) -> Option<&Metadata> {
162        self.current_entry.as_ref()
163    }
164
165    /// Extracts the whole archive to the specified destination directory.
166    ///
167    /// The `flags` parameter controls extraction behavior (e.g., `ARCHIVE_EXTRACT_TIME`, `ARCHIVE_EXTRACT_PERM`).
168    pub fn reader_whole_archive(self, dest_path: &str, flags: i32) -> Result<()> {
169        let archive = self.get_archive()?;
170        let current_dir = std::env::current_dir()?;
171        std::fs::create_dir_all(dest_path)?;
172        std::env::set_current_dir(dest_path)?;
173
174        loop {
175            unsafe {
176                let mut entry = std::mem::MaybeUninit::<*mut archive_entry>::uninit();
177                match carchive::archive_read_next_header(archive, entry.as_mut_ptr()) {
178                    carchive::ARCHIVE_EOF => break,
179                    carchive::ARCHIVE_OK | carchive::ARCHIVE_WARN => {
180                        let entry_ptr = entry.assume_init();
181                        match carchive::archive_read_extract(archive, entry_ptr, flags) {
182                            carchive::ARCHIVE_OK | carchive::ARCHIVE_WARN => (),
183                            _ => {
184                                let _ = std::env::set_current_dir(current_dir);
185                                return Err(Error::from(archive));
186                            }
187                        }
188                    }
189                    _ => {
190                        let _ = std::env::set_current_dir(current_dir);
191                        return Err(Error::from(archive));
192                    }
193                };
194            }
195        }
196
197        std::env::set_current_dir(current_dir)?;
198        Ok(())
199    }
200
201    /// Seeks to a specific file within the archive by its name.
202    ///
203    /// This will reset the internal libarchive state and read from the beginning
204    /// until the specified file is found.
205    pub fn reader_seek_obj(&mut self, filename: &str) -> Result<()> {
206        let archive = self.get_archive()?;
207
208        unsafe {
209            self.fileref.obj.seek(SeekFrom::Start(0))?;
210            match archive_read_free(archive) {
211                carchive::ARCHIVE_OK | carchive::ARCHIVE_WARN => (),
212                _ => return Err(archive.into()),
213            };
214
215            let archive = ArchiveReader::start(&mut self.fileref)?;
216            self.archive_reader = Some(archive);
217
218            loop {
219                let mut entry = std::mem::MaybeUninit::<*mut archive_entry>::uninit();
220                let hdr_result = carchive::archive_read_next_header(archive, entry.as_mut_ptr());
221                let entry = entry.assume_init();
222                match hdr_result {
223                    carchive::ARCHIVE_EOF => {
224                        return Err(IOError::new(
225                            ErrorKind::NotFound,
226                            format!("path {} doesn't exist inside archive", filename),
227                        )
228                        .into());
229                    }
230                    carchive::ARCHIVE_OK | carchive::ARCHIVE_WARN => (),
231                    _ => return Err(archive.into()),
232                };
233
234                let meta: Metadata = entry.into();
235                if meta.filepath() == filename {
236                    self.current_entry = Some(meta);
237                    return Ok(());
238                }
239            }
240        }
241    }
242
243    fn get_archive(&self) -> Result<*mut archive> {
244        if let Some(a) = self.archive_reader {
245            Ok(a)
246        } else {
247            Err(Error::NullArchive)
248        }
249    }
250
251    // this free is not meant to called directly. Only by borrow system
252    fn free(&mut self) -> Result<()> {
253        let archive = self.get_archive()?;
254        match unsafe { archive_read_free(archive) } {
255            carchive::ARCHIVE_OK | carchive::ARCHIVE_WARN => Ok(()),
256            _ => Err(Error::from(archive)),
257        }
258    }
259}
260
261impl<R: Read + Seek> Read for ArchiveReader<R> {
262    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
263        let archive = self.get_archive()?;
264        let read_size = unsafe {
265            carchive::archive_read_data(archive, buf.as_mut_ptr() as *mut c_void, buf.len())
266        };
267
268        if read_size >= 0 {
269            Ok(read_size as usize)
270        } else {
271            Err(Error::from(archive).into())
272        }
273    }
274}
275
276impl<R: Read + Seek> Iterator for ArchiveReader<R> {
277    type Item = Metadata;
278
279    fn next(&mut self) -> Option<Self::Item> {
280        let archive;
281        let mut entry = MaybeUninit::<*mut archive_entry>::uninit();
282
283        if let Ok(res) = self.get_archive() {
284            archive = res;
285        } else {
286            return Option::None;
287        }
288
289        unsafe {
290            match carchive::archive_read_next_header(archive, entry.as_mut_ptr()) {
291                carchive::ARCHIVE_OK | carchive::ARCHIVE_WARN => {
292                    let entry_ptr = entry.assume_init();
293                    let meta: Metadata = entry_ptr.into();
294                    self.current_entry = Some(meta.clone());
295                    Some(meta)
296                }
297                _ => {
298                    self.current_entry = None;
299                    Option::None
300                }
301            }
302        }
303    }
304}
305
306impl<R: Read + Seek> Drop for ArchiveReader<R> {
307    fn drop(&mut self) {
308        drop(self.free());
309    }
310}
311
312impl Drop for archive_entry {
313    fn drop(&mut self) {
314        unsafe { carchive::archive_entry_free(self) };
315    }
316}