Skip to content

Commit

Permalink
Fix error when uncompressing specific files from 7z archives
Browse files Browse the repository at this point in the history
tests: Add a test case for `uncompress_archive_file` using 7z file

Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>
Signed-off-by: asakiz <asakizin@gmail.com>
  • Loading branch information
otavio committed Feb 10, 2021
1 parent 5e96f37 commit e6fa5b0
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 56 deletions.
1 change: 1 addition & 0 deletions binding_script.rs
Expand Up @@ -39,6 +39,7 @@ fn main() {
.whitelist_var("ARCHIVE_EXTRACT_FFLAGS")
.whitelist_var("ARCHIVE_EXTRACT_XATTR")
.whitelist_function("archive_read_new")
.whitelist_function("archive_read_set_seek_callback")
.whitelist_function("archive_read_support_filter_all")
.whitelist_function("archive_read_support_format_all")
.whitelist_function("archive_read_support_format_raw")
Expand Down
10 changes: 9 additions & 1 deletion src/async_support.rs
Expand Up @@ -19,7 +19,7 @@ use futures_util::{
};
use std::{
future::Future,
io::{ErrorKind, Read, Write},
io::{ErrorKind, Read, Seek, SeekFrom, Write},
path::Path,
};

Expand Down Expand Up @@ -53,6 +53,14 @@ impl Read for AsyncReadWrapper {
}
}

// Hints Rust compiler that the seek is indeed supported, but
// underlying, it is done by the libarchive_seek_callback() callback.
impl Seek for AsyncReadWrapper {
fn seek(&mut self, _: SeekFrom) -> std::io::Result<u64> {
unreachable!("We need to use libarchive_seek_callback() underlying.")
}
}

fn make_async_read_wrapper_and_worker<R>(
mut read: R,
) -> (AsyncReadWrapper, impl Future<Output = Result<()>>)
Expand Down
14 changes: 14 additions & 0 deletions src/ffi.rs
Expand Up @@ -32,6 +32,14 @@ pub type archive_read_callback = ::std::option::Option<
_buffer: *mut *const ::std::os::raw::c_void,
) -> la_ssize_t,
>;
pub type archive_seek_callback = ::std::option::Option<
unsafe extern "C" fn(
arg1: *mut archive,
_client_data: *mut ::std::os::raw::c_void,
offset: la_int64_t,
whence: ::std::os::raw::c_int,
) -> la_int64_t,
>;
pub type archive_open_callback = ::std::option::Option<
unsafe extern "C" fn(
arg1: *mut archive,
Expand All @@ -56,6 +64,12 @@ extern "C" {
extern "C" {
pub fn archive_read_support_format_raw(arg1: *mut archive) -> ::std::os::raw::c_int;
}
extern "C" {
pub fn archive_read_set_seek_callback(
arg1: *mut archive,
arg2: archive_seek_callback,
) -> ::std::os::raw::c_int;
}
extern "C" {
pub fn archive_read_open(
arg1: *mut archive,
Expand Down
201 changes: 146 additions & 55 deletions src/lib.rs
Expand Up @@ -59,10 +59,11 @@ pub mod tokio_support;

use error::archive_result;
pub use error::{Error, Result};
use io::{Seek, SeekFrom};
use std::{
ffi::{CStr, CString},
io::{self, Read, Write},
os::raw::c_void,
os::raw::{c_int, c_void},
path::Path,
slice,
};
Expand All @@ -78,13 +79,20 @@ pub enum Ownership {
Ignore,
}

struct Pipe<'a> {
struct ReaderPipe<'a> {
reader: &'a mut dyn Read,
buffer: &'a mut [u8],
}

trait ReadAndSeek: Read + Seek {}
impl<T> ReadAndSeek for T where T: Read + Seek {}

struct SeekableReaderPipe<'a> {
reader: &'a mut dyn ReadAndSeek,
buffer: &'a mut [u8],
}

enum Mode {
AllFormat,
RawFormat,
WriteDisk { ownership: Ownership },
}
Expand All @@ -105,29 +113,25 @@ enum Mode {
/// ```
pub fn list_archive_files<R>(source: R) -> Result<Vec<String>>
where
R: Read,
R: Read + Seek,
{
run_with_archive(
Mode::AllFormat,
source,
|archive_reader, _, mut entry| unsafe {
#[allow(clippy::vec_init_then_push)]
let mut file_list = Vec::new();
loop {
match ffi::archive_read_next_header(archive_reader, &mut entry) {
ffi::ARCHIVE_OK => {
file_list.push(
CStr::from_ptr(ffi::archive_entry_pathname(entry))
.to_str()?
.to_string(),
);
}
ffi::ARCHIVE_EOF => return Ok(file_list),
_ => return Err(Error::from(archive_reader)),
run_with_seekable_archive(source, |archive_reader, _, mut entry| unsafe {
#[allow(clippy::vec_init_then_push)]
let mut file_list = Vec::new();
loop {
match ffi::archive_read_next_header(archive_reader, &mut entry) {
ffi::ARCHIVE_OK => {
file_list.push(
CStr::from_ptr(ffi::archive_entry_pathname(entry))
.to_str()?
.to_string(),
);
}
ffi::ARCHIVE_EOF => return Ok(file_list),
_ => return Err(Error::from(archive_reader)),
}
},
)
}
})
}

/// Uncompress a file using the `source` need as reader and the `target` as a
Expand Down Expand Up @@ -199,7 +203,7 @@ where
/// ```
pub fn uncompress_archive<R>(source: R, dest: &Path, ownership: Ownership) -> Result<()>
where
R: Read,
R: Read + Seek,
{
run_with_archive(
Mode::WriteDisk { ownership },
Expand Down Expand Up @@ -265,35 +269,30 @@ where
/// ```
pub fn uncompress_archive_file<R, W>(source: R, target: W, path: &str) -> Result<usize>
where
R: Read,
R: Read + Seek,
W: Write,
{
run_with_archive(
Mode::AllFormat,
source,
|archive_reader, _, mut entry| unsafe {
loop {
match ffi::archive_read_next_header(archive_reader, &mut entry) {
ffi::ARCHIVE_OK => {
let file_name =
CStr::from_ptr(ffi::archive_entry_pathname(entry)).to_str()?;
if file_name == path {
break;
}
}
ffi::ARCHIVE_EOF => {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!("path {} doesn't exist inside archive", path),
)
.into())
run_with_seekable_archive(source, |archive_reader, _, mut entry| unsafe {
loop {
match ffi::archive_read_next_header(archive_reader, &mut entry) {
ffi::ARCHIVE_OK => {
let file_name = CStr::from_ptr(ffi::archive_entry_pathname(entry)).to_str()?;
if file_name == path {
break;
}
_ => return Err(Error::from(archive_reader)),
}
ffi::ARCHIVE_EOF => {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!("path {} doesn't exist inside archive", path),
)
.into())
}
_ => return Err(Error::from(archive_reader)),
}
libarchive_write_data_block(archive_reader, target)
},
)
}
libarchive_write_data_block(archive_reader, target)
})
}

fn run_with_archive<F, R, T>(mode: Mode, mut reader: R, f: F) -> Result<T>
Expand All @@ -317,10 +316,6 @@ where
ffi::archive_read_support_format_raw(archive_reader),
archive_reader,
)?,
Mode::AllFormat => archive_result(
ffi::archive_read_support_format_all(archive_reader),
archive_reader,
)?,
Mode::WriteDisk { ownership } => {
let mut writer_flags = ffi::ARCHIVE_EXTRACT_TIME
| ffi::ARCHIVE_EXTRACT_PERM
Expand Down Expand Up @@ -355,15 +350,15 @@ where
return Err(Error::NullArchive);
}

let mut pipe = Pipe {
let mut pipe = ReaderPipe {
reader: &mut reader,
buffer: &mut [0; READER_BUFFER_SIZE],
};

archive_result(
ffi::archive_read_open(
archive_reader,
(&mut pipe as *mut Pipe) as *mut c_void,
(&mut pipe as *mut ReaderPipe) as *mut c_void,
None,
Some(libarchive_read_callback),
None,
Expand All @@ -386,6 +381,62 @@ where
}
}

fn run_with_seekable_archive<F, R, T>(mut reader: R, f: F) -> Result<T>
where
F: FnOnce(*mut ffi::archive, *mut ffi::archive, *mut ffi::archive_entry) -> Result<T>,
R: Read + Seek,
{
unsafe {
let archive_entry: *mut ffi::archive_entry = std::ptr::null_mut();
let archive_reader = ffi::archive_read_new();
let archive_writer = ffi::archive_write_disk_new();

let res = (|| {
archive_result(
ffi::archive_read_support_format_all(archive_reader),
archive_reader,
)?;

archive_result(
ffi::archive_read_set_seek_callback(archive_reader, Some(libarchive_seek_callback)),
archive_reader,
)?;

if archive_reader.is_null() || archive_writer.is_null() {
return Err(Error::NullArchive);
}

let mut pipe = SeekableReaderPipe {
reader: &mut reader,
buffer: &mut [0; READER_BUFFER_SIZE],
};

archive_result(
ffi::archive_read_open(
archive_reader,
(&mut pipe as *mut SeekableReaderPipe) as *mut c_void,
None,
Some(libarchive_seekable_read_callback),
None,
),
archive_reader,
)?;

f(archive_reader, archive_writer, archive_entry)
})();

archive_result(ffi::archive_read_close(archive_reader), archive_reader)?;
archive_result(ffi::archive_read_free(archive_reader), archive_reader)?;

archive_result(ffi::archive_write_close(archive_writer), archive_writer)?;
archive_result(ffi::archive_write_free(archive_writer), archive_writer)?;

ffi::archive_entry_free(archive_entry);

res
}
}

fn libarchive_copy_data(
archive_reader: *mut ffi::archive,
archive_writer: *mut ffi::archive,
Expand Down Expand Up @@ -436,12 +487,52 @@ where
}
}

unsafe extern "C" fn libarchive_seek_callback(
_: *mut ffi::archive,
client_data: *mut c_void,
offset: ffi::la_int64_t,
whence: c_int,
) -> i64 {
let pipe = (client_data as *mut SeekableReaderPipe).as_mut().unwrap();

match pipe.reader.seek(match whence {
0 => SeekFrom::Start(offset as u64),
1 => SeekFrom::Current(offset),
2 => SeekFrom::End(offset),
_ => return -1,
}) {
Ok(offset) => offset as i64,
Err(_) => -1,
}
}

unsafe extern "C" fn libarchive_seekable_read_callback(
archive: *mut ffi::archive,
client_data: *mut c_void,
buffer: *mut *const c_void,
) -> ffi::la_ssize_t {
let pipe = (client_data as *mut SeekableReaderPipe).as_mut().unwrap();

*buffer = pipe.buffer.as_ptr() as *const c_void;

match pipe.reader.read(&mut pipe.buffer) {
Ok(size) => size as ffi::la_ssize_t,
Err(e) => {
let description = CString::new(e.to_string()).unwrap();

ffi::archive_set_error(archive, e.raw_os_error().unwrap_or(0), description.as_ptr());

-1
}
}
}

unsafe extern "C" fn libarchive_read_callback(
archive: *mut ffi::archive,
client_data: *mut c_void,
buffer: *mut *const c_void,
) -> ffi::la_ssize_t {
let pipe = (client_data as *mut Pipe).as_mut().unwrap();
let pipe = (client_data as *mut ReaderPipe).as_mut().unwrap();

*buffer = pipe.buffer.as_ptr() as *const c_void;

Expand Down
Binary file added tests/fixtures/tree.7z
Binary file not shown.
15 changes: 15 additions & 0 deletions tests/integration_test.rs
Expand Up @@ -71,6 +71,21 @@ fn get_a_file_from_tar() {
assert_eq!(written, 14, "Uncompressed bytes count did not match");
}

#[test]
fn get_a_file_from_7z() {
let mut source = std::fs::File::open("tests/fixtures/tree.7z").unwrap();
let mut target = Vec::default();

let written = uncompress_archive_file(&mut source, &mut target, &"tree/branch2/leaf")
.expect("Failed to get the file");
assert_eq!(
String::from_utf8_lossy(&target),
"Goodbye World\n",
"Uncompressed file did not match",
);
assert_eq!(written, 14, "Uncompressed bytes count did not match");
}

#[async_std::test]
#[cfg(feature = "futures_support")]
async fn get_a_file_from_tar_futures() {
Expand Down

0 comments on commit e6fa5b0

Please sign in to comment.