#[cfg(feature = "async_support")]
pub mod async_support;
mod error;
mod ffi;
#[cfg(feature = "futures_support")]
pub mod futures_support;
#[cfg(feature = "tokio_support")]
pub mod tokio_support;
use error::archive_result;
pub use error::{Error, Result};
use std::{
ffi::{CStr, CString},
io::{self, Read, Write},
os::raw::c_void,
path::Path,
slice,
};
const READER_BUFFER_SIZE: usize = 1024;
pub enum Ownership {
Preserve,
Ignore,
}
struct Pipe<'a> {
reader: &'a mut dyn Read,
buffer: &'a mut [u8],
}
enum Mode {
AllFormat,
RawFormat,
WriteDisk { ownership: Ownership },
}
pub fn list_archive_files<R>(source: R) -> Result<Vec<String>>
where
R: Read,
{
run_with_archive(
Mode::AllFormat,
source,
|archive_reader, _, mut entry| unsafe {
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)),
}
}
},
)
}
pub fn uncompress_data<R, W>(source: R, target: W) -> Result<usize>
where
R: Read,
W: Write,
{
run_with_archive(
Mode::RawFormat,
source,
|archive_reader, _, mut entry| unsafe {
archive_result(
ffi::archive_read_next_header(archive_reader, &mut entry),
archive_reader,
)?;
libarchive_write_data_block(archive_reader, target)
},
)
}
pub fn uncompress_archive<R>(source: R, dest: &Path, ownership: Ownership) -> Result<()>
where
R: Read,
{
run_with_archive(
Mode::WriteDisk { ownership },
source,
|archive_reader, archive_writer, mut entry| unsafe {
loop {
match ffi::archive_read_next_header(archive_reader, &mut entry) {
ffi::ARCHIVE_EOF => return Ok(()),
ffi::ARCHIVE_OK => {
let target_path = {
let p = dest
.join(CStr::from_ptr(ffi::archive_entry_pathname(entry)).to_str()?);
let p = p.to_str().unwrap();
CString::new(p).unwrap()
};
ffi::archive_entry_set_pathname(entry, target_path.as_ptr());
let link_name = ffi::archive_entry_hardlink(entry);
if !link_name.is_null() {
let target_path = {
let p = dest.join(CStr::from_ptr(link_name).to_str()?);
let p = p.to_str().unwrap();
CString::new(p).unwrap()
};
ffi::archive_entry_set_hardlink(entry, target_path.as_ptr());
}
ffi::archive_write_header(archive_writer, entry);
libarchive_copy_data(archive_reader, archive_writer)?;
if ffi::archive_write_finish_entry(archive_writer) != ffi::ARCHIVE_OK {
return Err(Error::from(archive_writer));
}
}
_ => return Err(Error::from(archive_reader)),
}
}
},
)
}
pub fn uncompress_archive_file<R, W>(source: R, target: W, path: &str) -> Result<usize>
where
R: Read,
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())
}
_ => return Err(Error::from(archive_reader)),
}
}
libarchive_write_data_block(archive_reader, target)
},
)
}
fn run_with_archive<F, R, T>(mode: Mode, mut reader: R, f: F) -> Result<T>
where
F: FnOnce(*mut ffi::archive, *mut ffi::archive, *mut ffi::archive_entry) -> Result<T>,
R: Read,
{
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_filter_all(archive_reader),
archive_reader,
)?;
match mode {
Mode::RawFormat => archive_result(
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
| ffi::ARCHIVE_EXTRACT_ACL
| ffi::ARCHIVE_EXTRACT_FFLAGS
| ffi::ARCHIVE_EXTRACT_XATTR;
if let Ownership::Preserve = ownership {
writer_flags |= ffi::ARCHIVE_EXTRACT_OWNER;
};
archive_result(
ffi::archive_write_disk_set_options(archive_writer, writer_flags as i32),
archive_writer,
)?;
archive_result(
ffi::archive_write_disk_set_standard_lookup(archive_writer),
archive_writer,
)?;
archive_result(
ffi::archive_read_support_format_all(archive_reader),
archive_reader,
)?;
archive_result(
ffi::archive_read_support_format_raw(archive_reader),
archive_reader,
)?;
}
}
if archive_reader.is_null() || archive_writer.is_null() {
return Err(Error::NullArchive);
}
let mut pipe = Pipe {
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,
None,
Some(libarchive_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,
) -> Result<()> {
let mut buffer = std::ptr::null();
let mut offset = 0;
let mut size = 0;
unsafe {
loop {
match ffi::archive_read_data_block(archive_reader, &mut buffer, &mut size, &mut offset)
{
ffi::ARCHIVE_EOF => return Ok(()),
ffi::ARCHIVE_OK => {
archive_result(
ffi::archive_write_data_block(archive_writer, buffer, size, offset) as i32,
archive_writer,
)?;
}
_ => return Err(Error::from(archive_reader)),
}
}
}
}
unsafe fn libarchive_write_data_block<W>(
archive_reader: *mut ffi::archive,
mut target: W,
) -> Result<usize>
where
W: Write,
{
let mut buffer = std::ptr::null();
let mut offset = 0;
let mut size = 0;
let mut written = 0;
loop {
match ffi::archive_read_data_block(archive_reader, &mut buffer, &mut size, &mut offset) {
ffi::ARCHIVE_EOF => return Ok(written),
ffi::ARCHIVE_OK => {
let content = slice::from_raw_parts(buffer as *const u8, size);
target.write_all(content)?;
written += size;
}
_ => return Err(Error::from(archive_reader)),
}
}
}
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();
*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
}
}
}