Merge remote-tracking branch 'libarchive/master' into develop

This commit is contained in:
DecDuck
2026-03-30 19:42:56 +11:00
14 changed files with 1307 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
target
Cargo.lock
.tags
.tags1
+21
View File
@@ -0,0 +1,21 @@
language: rust
rust: stable
sudo: false
addons:
apt:
packages:
- libarchive13
script:
- cargo build
- cargo test
- cargo doc
after_success: |
[ $TRAVIS_BRANCH = master ] &&
[ $TRAVIS_PULL_REQUEST = false ] &&
echo '<meta http-equiv=refresh content=0;url=libarchive/index.html>' > target/doc/index.html &&
pip install --user ghp-import &&
$HOME/.local/bin/ghp-import -n target/doc &&
git push -qf https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages
env:
global:
secure: KKPYXC3dn8CR8YRPuqkBSEu9VUjt7qSTLENnPYz4B2+lFT0iI265MqZGaHFhBv3YHjV/ONjvOj6Tn1l1JFIzLyBbiJ9EeY/5u/kh+tN2fjR8AniaqPG+SZuRERZwe92T/8xr+fGdzlG9hKr7XgAXQAdB7hAlQoU+cuiwuB0+FDMBciDELnLokIRCoKzW0LSY5EvQQ2BkPHuWv3n04isd7WF11IsidqN0HMDtSaVSC6FbfpW/MdlRqVzey/VxG/BpY0FWvCkRayB7NVLEWXtr/rWyn3jY6pKqev+I0w6eIL/hvKrQUrmSP3y0RJcCgq/7asdNQDeIFXbT1emFXw3dKmA9ZdSzBnm5Z2NO18DNAb3yUQjCJztSWaCfyEbKFbrV/V6cl8I61gHzGzS8kWG8kAbC6dSQxaAtm3wrcrPI1q4bHj0JAXJ27ICKAHoxMUWX50+tMZDchv8exQjBPu35gPi+5tJGCuGeSnZ33w+XHPTdJtb1Hqt87CSEs8umJyZbjT/NpIWzgKJz/2dTX1dpvEnJ0THRMTd2W2TQra9Y9e6GxUZlV6MVI6pDSBKdee6fGTxAtfmVzgL6Bfey5vP+70RanHmLTN/FJXw/thEfZaSsZnK298XsmJ6WXksCEBo40E0wINDe6R7R4egzTzoFqUzsv/uPNu/0QXXrM8DWTo4=
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "libarchive-drop"
version = "0.1.1"
authors = ["Jamie Winsor <reset@chef.io>", "Drop OSS"]
license = "Apache-2.0"
repository = "https://github.com/Drop-OSS/libarchive-rust"
description = "A safe Rust API for authoring and extracting archives with libarchive"
keywords = ["libarchive", "archive", "tar", "zip"]
[dependencies]
libc = ">= 0.2.0"
libarchive3-sys = "0.1"
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Jamie Winsor
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+37
View File
@@ -0,0 +1,37 @@
# libarchive-rust
[![Build Status](https://travis-ci.org/chef/libarchive-rust.svg?branch=master)](https://travis-ci.org/chef/libarchive-rust)
[![crates.io](https://meritbadge.herokuapp.com/gpgme)](https://crates.io/crates/libarchive)
A Rust crate for interacting with archives using [libarchive](http://www.libarchive.org)
## Requirements
Version 3 of libarchive is required to use this library.
The required libraries and binaries can be installed by running:
#### Debian / Ubuntu
```shell
$ sudo apt-get install libarchive13
```
#### Mac OS X
```shell
$ brew install libarchive
```
## Usage
Put this in your `Cargo.toml`:
```toml
[dependencies]
libarchive = "*"
```
And this in your crate root:
```rust
extern crate libarchive;
```
+303
View File
@@ -0,0 +1,303 @@
use std::default::Default;
use std::ffi::{CStr, CString};
use std::path::PathBuf;
use std::str;
use error::ErrCode;
use libarchive3_sys::ffi;
pub enum ReadCompression {
All,
Bzip2,
Compress,
Gzip,
Lzip,
Lzma,
None,
Program(String),
Rpm,
Uu,
Xz,
}
pub enum ReadFormat {
SevenZip,
All,
Ar,
Cab,
Cpio,
Empty,
Gnutar,
Iso9660,
Lha,
Mtree,
Rar,
Raw,
Tar,
Xar,
Zip,
}
pub enum ReadFilter {
All,
Bzip2,
Compress,
Gzip,
Grzip,
Lrzip,
Lzip,
Lzma,
Lzop,
None,
Program(String),
ProgramSignature(String, Option<extern "C" fn() -> ()>, usize),
Rpm,
Uu,
Xz,
}
pub enum WriteFormat {
SevenZip,
ArBsd,
ArSvr4,
Cpio,
CpioNewc,
Gnutar,
Iso9660,
Mtree,
MtreeClassic,
Pax,
PaxRestricted,
Shar,
SharDump,
Ustar,
V7tar,
Xar,
Zip,
}
pub enum WriteFilter {
B64Encode,
Bzip2,
Compress,
Grzip,
Gzip,
Lrzip,
Lzip,
Lzma,
Lzop,
None,
Program(String),
UuEncode,
Xz,
}
pub enum FileType {
BlockDevice,
SymbolicLink,
Socket,
CharacterDevice,
Directory,
NamedPipe,
Mount,
RegularFile,
}
pub trait Handle {
unsafe fn handle(&self) -> *mut ffi::Struct_archive;
fn err_code(&self) -> ErrCode {
let code = unsafe { ffi::archive_errno(self.handle()) };
ErrCode(code)
}
fn err_msg(&self) -> String {
unsafe {
let c_str = CStr::from_ptr(ffi::archive_error_string(self.handle()));
let buf = c_str.to_bytes();
String::from(str::from_utf8(buf).unwrap())
}
}
}
pub trait Entry {
unsafe fn entry(&self) -> *mut ffi::Struct_archive_entry;
fn filetype(&self) -> FileType {
unsafe {
match ffi::archive_entry_filetype(self.entry()) as u32 {
ffi::AE_IFBLK => FileType::BlockDevice,
ffi::AE_IFCHR => FileType::CharacterDevice,
ffi::AE_IFLNK => FileType::SymbolicLink,
ffi::AE_IFDIR => FileType::Directory,
ffi::AE_IFIFO => FileType::NamedPipe,
ffi::AE_IFMT => FileType::Mount,
ffi::AE_IFREG => FileType::RegularFile,
ffi::AE_IFSOCK => FileType::Socket,
code => unreachable!("undefined filetype: {}", code),
}
}
}
fn hardlink(&self) -> Option<&str> {
let c_str: &CStr = unsafe {
let ptr = ffi::archive_entry_hardlink(self.entry());
if ptr.is_null() {
return None;
}
CStr::from_ptr(ptr)
};
let buf: &[u8] = c_str.to_bytes();
Some(str::from_utf8(buf).unwrap())
}
fn pathname(&self) -> &str {
let c_str: &CStr = unsafe { CStr::from_ptr(ffi::archive_entry_pathname(self.entry())) };
let buf: &[u8] = c_str.to_bytes();
str::from_utf8(buf).unwrap()
}
fn size(&self) -> i64 {
unsafe { ffi::archive_entry_size(self.entry()) }
}
fn symlink(&self) -> &str {
let c_str: &CStr = unsafe { CStr::from_ptr(ffi::archive_entry_symlink(self.entry())) };
let buf: &[u8] = c_str.to_bytes();
str::from_utf8(buf).unwrap()
}
fn set_filetype(&mut self, file_type: FileType) {
unsafe {
let file_type = match file_type {
FileType::BlockDevice => ffi::AE_IFBLK,
FileType::CharacterDevice => ffi::AE_IFCHR,
FileType::SymbolicLink => ffi::AE_IFLNK,
FileType::Directory => ffi::AE_IFDIR,
FileType::NamedPipe => ffi::AE_IFIFO,
FileType::Mount => ffi::AE_IFMT,
FileType::RegularFile => ffi::AE_IFREG,
FileType::Socket => ffi::AE_IFSOCK,
};
ffi::archive_entry_set_filetype(self.entry(), file_type);
}
}
fn set_link(&mut self, path: &PathBuf) {
unsafe {
let c_str = CString::new(path.to_str().unwrap()).unwrap();
ffi::archive_entry_set_link(self.entry(), c_str.as_ptr());
}
}
fn set_pathname(&mut self, path: &PathBuf) {
unsafe {
let c_str = CString::new(path.to_str().unwrap()).unwrap();
ffi::archive_entry_set_pathname(self.entry(), c_str.as_ptr());
}
}
}
pub enum ExtractOption {
// The user and group IDs should be set on the restored file. By default, the user and group
// IDs are not restored.
Owner,
// Full permissions (including SGID, SUID, and sticky bits) should be restored exactly as
// specified, without obeying the current umask. Note that SUID and SGID bits can only be
// restored if the user and group ID of the object on disk are correct. If
// `ExtractOption::Owner` is not specified, then SUID and SGID bits will only be restored if
// the default user and group IDs of newly-created objects on disk happen to match those
// specified in the archive entry. By default, only basic permissions are restored, and umask
// is obeyed.
Permissions,
// The timestamps (mtime, ctime, and atime) should be restored. By default, they are ignored.
// Note that restoring of atime is not currently supported.
Time,
// Existing files on disk will not be overwritten. By default, existing regular files are
// truncated and overwritten; existing directories will have their permissions updated; other
// pre-existing objects are unlinked and recreated from scratch.
NoOverwrite,
// Existing files on disk will be unlinked before any attempt to create them. In some cases,
// this can prove to be a significant performance improvement. By default, existing files are
// truncated and rewritten, but the file is not recreated. In particular, the default behavior
// does not break existing hard links.
Unlink,
// Attempt to restore ACLs. By default, extended ACLs are ignored.
ACL,
// Attempt to restore extended file flags. By default, file flags are ignored.
FFlags,
// Attempt to restore POSIX.1e extended attributes. By default, they are ignored.
XAttr,
// Refuse to extract any object whose final location would be altered by a symlink on disk.
// This is intended to help guard against a variety of mischief caused by archives that
// (deliberately or otherwise) extract files outside of the current directory. The default is
// not to perform this check. If ARCHIVE_EXTRACT_UNLINK is specified together with this option,
// the library will remove any intermediate symlinks it finds and return an error only if such
// symlink could not be removed.
SecureSymlinks,
// Refuse to extract a path that contains a `..` element anywhere within it. The default is to
// not refuse such paths. Note that paths ending in `..` always cause an error, regardless of
// this flag.
SecureNoDotDot,
// Default: Create parent directories as needed
NoAutoDir,
// Default: Overwrite files, even if one on disk is newer
NoOverwriteNewer,
// Scan data for blocks of NUL bytes and try to recreate them with holes. This results in
// sparse files, independent of whether the archive format supports or uses them.
Sparse,
// Default: Do not restore Mac extended metadata
// This has no effect except on Mac OS
MacMetadata,
// Default: Use HFS+ compression if it was compressed
// This has no effect except on Mac OS v10.6 or later
NoHFSCompression,
// Default: Do not use HFS+ compression if it was not compressed
// This has no effect except on Mac OS v10.6 or later
HFSCompressionForced,
// Default: Do not reject entries with absolute paths */
SecureNoAbsolutePaths,
// Default: Do not clear no-change flags when unlinking object */
ClearNoChangeFFlags,
}
pub struct ExtractOptions {
pub flags: i32,
}
impl ExtractOptions {
pub fn new() -> Self {
ExtractOptions::default()
}
pub fn add(&mut self, opt: ExtractOption) -> &mut Self {
let flag = match opt {
ExtractOption::Owner => ffi::ARCHIVE_EXTRACT_OWNER,
ExtractOption::Permissions => ffi::ARCHIVE_EXTRACT_PERM,
ExtractOption::Time => ffi::ARCHIVE_EXTRACT_TIME,
ExtractOption::NoOverwrite => ffi::ARCHIVE_EXTRACT_NO_OVERWRITE,
ExtractOption::Unlink => ffi::ARCHIVE_EXTRACT_UNLINK,
ExtractOption::ACL => ffi::ARCHIVE_EXTRACT_ACL,
ExtractOption::FFlags => ffi::ARCHIVE_EXTRACT_FFLAGS,
ExtractOption::XAttr => ffi::ARCHIVE_EXTRACT_XATTR,
ExtractOption::SecureSymlinks => ffi::ARCHIVE_EXTRACT_SECURE_SYMLINKS,
ExtractOption::SecureNoDotDot => ffi::ARCHIVE_EXTRACT_SECURE_NODOTDOT,
ExtractOption::NoAutoDir => ffi::ARCHIVE_EXTRACT_NO_AUTODIR,
ExtractOption::NoOverwriteNewer => ffi::ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER,
ExtractOption::Sparse => ffi::ARCHIVE_EXTRACT_SPARSE,
ExtractOption::MacMetadata => ffi::ARCHIVE_EXTRACT_MAC_METADATA,
ExtractOption::NoHFSCompression => ffi::ARCHIVE_EXTRACT_NO_HFS_COMPRESSION,
ExtractOption::HFSCompressionForced => ffi::ARCHIVE_EXTRACT_HFS_COMPRESSION_FORCED,
ExtractOption::SecureNoAbsolutePaths => ffi::ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS,
ExtractOption::ClearNoChangeFFlags => ffi::ARCHIVE_EXTRACT_CLEAR_NOCHANGE_FFLAGS,
};
self.flags |= flag;
self
}
}
impl Default for ExtractOptions {
fn default() -> ExtractOptions {
ExtractOptions { flags: 0 }
}
}
+58
View File
@@ -0,0 +1,58 @@
use archive;
use std::error;
use std::fmt;
pub type ArchiveResult<T> = Result<T, ArchiveError>;
#[derive(Debug)]
pub struct ErrCode(pub i32);
impl fmt::Display for ErrCode {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", self.0)
}
}
#[derive(Debug)]
pub enum ArchiveError {
Consumed,
HeaderPosition,
Sys(ErrCode, String),
}
impl error::Error for ArchiveError {
fn description(&self) -> &str {
match self {
&ArchiveError::Consumed => "Builder already consumed",
&ArchiveError::HeaderPosition => "Header position expected to be 0",
&ArchiveError::Sys(_, _) => "libarchive system error",
}
}
}
impl fmt::Display for ArchiveError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
&ArchiveError::Consumed => write!(fmt, "Builder already consumed"),
&ArchiveError::HeaderPosition => write!(fmt, "Header position expected to be 0"),
&ArchiveError::Sys(ref code, ref msg) => {
write!(fmt, "{} (libarchive err_code={})", msg, code)
}
}
}
}
impl<'a> From<&'a dyn archive::Handle> for ArchiveError {
fn from(handle: &'a dyn archive::Handle) -> ArchiveError {
ArchiveError::Sys(handle.err_code(), handle.err_msg())
}
}
impl<'a> From<&'a dyn archive::Handle> for ArchiveResult<()> {
fn from(handle: &'a dyn archive::Handle) -> ArchiveResult<()> {
match handle.err_code() {
ErrCode(0) => Ok(()),
_ => Err(ArchiveError::from(handle)),
}
}
}
+7
View File
@@ -0,0 +1,7 @@
extern crate libarchive3_sys;
extern crate libc;
pub mod archive;
pub mod error;
pub mod reader;
pub mod writer;
+388
View File
@@ -0,0 +1,388 @@
use std::any::Any;
use std::default::Default;
use std::ffi::CString;
use std::io::{self, Read};
use std::mem;
use std::path::Path;
use std::ptr;
use std::slice;
use libarchive3_sys::ffi;
use libc::{c_void, ssize_t};
use archive::{Entry, Handle, ReadCompression, ReadFilter, ReadFormat};
use error::{ArchiveError, ArchiveResult};
const BLOCK_SIZE: usize = 10240;
unsafe extern "C" fn stream_read_callback(
handle: *mut ffi::Struct_archive,
data: *mut c_void,
buff: *mut *const c_void,
) -> ssize_t {
let pipe: &mut Pipe = &mut *(data as *mut Pipe);
*buff = pipe.buffer.as_mut_ptr() as *mut c_void;
match pipe.read_bytes() {
Ok(size) => size as ssize_t,
Err(e) => {
let desc = CString::new(e.to_string()).unwrap();
ffi::archive_set_error(handle, e.raw_os_error().unwrap_or(0), desc.as_ptr());
-1 as ssize_t
}
}
}
pub trait Reader: Handle {
fn entry(&mut self) -> &mut ReaderEntry;
fn header_position(&self) -> i64 {
unsafe { ffi::archive_read_header_position(self.handle()) }
}
fn next_header(&mut self) -> Option<&mut ReaderEntry> {
let res = unsafe { ffi::archive_read_next_header(self.handle(), &mut self.entry().handle) };
if res == 0 {
Some(self.entry())
} else {
None
}
}
fn read_block(&self) -> ArchiveResult<Option<&[u8]>> {
let mut buff = ptr::null();
let mut size = 0;
let mut offset = 0;
unsafe {
match ffi::archive_read_data_block(self.handle(), &mut buff, &mut size, &mut offset) {
ffi::ARCHIVE_EOF => Ok(None),
ffi::ARCHIVE_OK => {
if buff != ptr::null() {
Ok(Some(slice::from_raw_parts(buff as *const u8, size)))
} else {
return self.read_block();
}
}
_ => Err(ArchiveError::Sys(self.err_code(), self.err_msg())),
}
}
}
}
pub struct FileReader {
handle: *mut ffi::Struct_archive,
entry: ReaderEntry,
}
unsafe impl Send for FileReader {}
pub struct StreamReader {
handle: *mut ffi::Struct_archive,
entry: ReaderEntry,
_pipe: Box<Pipe>,
}
pub struct Builder {
handle: *mut ffi::Struct_archive,
consumed: bool,
}
pub struct ReaderEntry {
handle: *mut ffi::Struct_archive_entry,
}
struct Pipe {
reader: Box<dyn Read>,
buffer: Vec<u8>,
}
impl Pipe {
fn new<T: Any + Read>(src: T) -> Self {
Pipe {
reader: Box::new(src),
buffer: vec![0; 8192],
}
}
fn read_bytes(&mut self) -> io::Result<usize> {
self.reader.read(&mut self.buffer[..])
}
}
impl FileReader {
pub fn open<T: AsRef<Path>>(mut builder: Builder, file: T) -> ArchiveResult<Self> {
builder.check_consumed()?;
let c_file = CString::new(file.as_ref().to_string_lossy().as_bytes()).unwrap();
unsafe {
match ffi::archive_read_open_filename(builder.handle(), c_file.as_ptr(), BLOCK_SIZE) {
ffi::ARCHIVE_OK => {
builder.consume();
Ok(Self::new(builder.handle()))
}
_ => Err(ArchiveError::from(&builder as &dyn Handle)),
}
}
}
fn new(handle: *mut ffi::Struct_archive) -> Self {
FileReader {
handle: handle,
entry: ReaderEntry::default(),
}
}
}
impl Handle for FileReader {
unsafe fn handle(&self) -> *mut ffi::Struct_archive {
self.handle
}
}
impl Reader for FileReader {
fn entry(&mut self) -> &mut ReaderEntry {
&mut self.entry
}
}
impl Drop for FileReader {
fn drop(&mut self) {
unsafe {
ffi::archive_read_free(self.handle());
}
}
}
impl StreamReader {
pub fn open<T: Any + Read>(mut builder: Builder, src: T) -> ArchiveResult<Self> {
unsafe {
let mut pipe = Box::new(Pipe::new(src));
let pipe_ptr: *mut c_void = &mut *pipe as *mut Pipe as *mut c_void;
match ffi::archive_read_open(
builder.handle(),
pipe_ptr,
None,
Some(stream_read_callback),
None,
) {
ffi::ARCHIVE_OK => {
let reader = StreamReader {
handle: builder.handle(),
entry: ReaderEntry::default(),
_pipe: pipe,
};
builder.consume();
Ok(reader)
}
_ => {
builder.consume();
Err(ArchiveError::from(&builder as &dyn Handle))
}
}
}
}
}
impl Handle for StreamReader {
unsafe fn handle(&self) -> *mut ffi::Struct_archive {
self.handle
}
}
impl Reader for StreamReader {
fn entry(&mut self) -> &mut ReaderEntry {
&mut self.entry
}
}
impl Drop for StreamReader {
fn drop(&mut self) {
unsafe {
ffi::archive_read_free(self.handle());
}
}
}
impl Builder {
pub fn new() -> Self {
Builder::default()
}
pub fn support_compression(&mut self, compression: ReadCompression) -> ArchiveResult<()> {
let result = match compression {
ReadCompression::All => unsafe {
ffi::archive_read_support_compression_all(self.handle)
},
ReadCompression::Bzip2 => unsafe {
ffi::archive_read_support_compression_bzip2(self.handle)
},
ReadCompression::Compress => unsafe {
ffi::archive_read_support_compression_compress(self.handle)
},
ReadCompression::Gzip => unsafe {
ffi::archive_read_support_compression_gzip(self.handle)
},
ReadCompression::Lzip => unsafe {
ffi::archive_read_support_compression_lzip(self.handle)
},
ReadCompression::Lzma => unsafe {
ffi::archive_read_support_compression_lzma(self.handle)
},
ReadCompression::None => unsafe {
ffi::archive_read_support_compression_none(self.handle)
},
ReadCompression::Program(prog) => {
let c_prog = CString::new(prog).unwrap();
unsafe {
ffi::archive_read_support_compression_program(self.handle, c_prog.as_ptr())
}
}
ReadCompression::Rpm => unsafe {
ffi::archive_read_support_compression_rpm(self.handle)
},
ReadCompression::Uu => unsafe { ffi::archive_read_support_compression_uu(self.handle) },
ReadCompression::Xz => unsafe { ffi::archive_read_support_compression_xz(self.handle) },
};
match result {
ffi::ARCHIVE_OK => Ok(()),
_ => ArchiveResult::from(self as &dyn Handle),
}
}
pub fn support_filter(&mut self, filter: ReadFilter) -> ArchiveResult<()> {
let result = match filter {
ReadFilter::All => unsafe { ffi::archive_read_support_filter_all(self.handle) },
ReadFilter::Bzip2 => unsafe { ffi::archive_read_support_filter_bzip2(self.handle) },
ReadFilter::Compress => unsafe {
ffi::archive_read_support_filter_compress(self.handle)
},
ReadFilter::Grzip => unsafe { ffi::archive_read_support_filter_grzip(self.handle) },
ReadFilter::Gzip => unsafe { ffi::archive_read_support_filter_gzip(self.handle) },
ReadFilter::Lrzip => unsafe { ffi::archive_read_support_filter_lrzip(self.handle) },
ReadFilter::Lzip => unsafe { ffi::archive_read_support_filter_lzip(self.handle) },
ReadFilter::Lzma => unsafe { ffi::archive_read_support_filter_lzma(self.handle) },
ReadFilter::Lzop => unsafe { ffi::archive_read_support_filter_lzop(self.handle) },
ReadFilter::None => unsafe { ffi::archive_read_support_filter_none(self.handle) },
ReadFilter::Program(prog) => {
let c_prog = CString::new(prog).unwrap();
unsafe { ffi::archive_read_support_filter_program(self.handle, c_prog.as_ptr()) }
}
ReadFilter::ProgramSignature(prog, cb, size) => {
let c_prog = CString::new(prog).unwrap();
unsafe {
ffi::archive_read_support_filter_program_signature(
self.handle,
c_prog.as_ptr(),
mem::transmute(cb),
size,
)
}
}
ReadFilter::Rpm => unsafe { ffi::archive_read_support_filter_rpm(self.handle) },
ReadFilter::Uu => unsafe { ffi::archive_read_support_filter_uu(self.handle) },
ReadFilter::Xz => unsafe { ffi::archive_read_support_filter_xz(self.handle) },
};
match result {
ffi::ARCHIVE_OK => Ok(()),
_ => ArchiveResult::from(self as &dyn Handle),
}
}
pub fn support_format(&self, format: ReadFormat) -> ArchiveResult<()> {
let result = match format {
ReadFormat::SevenZip => unsafe { ffi::archive_read_support_format_7zip(self.handle()) },
ReadFormat::All => unsafe { ffi::archive_read_support_format_all(self.handle()) },
ReadFormat::Ar => unsafe { ffi::archive_read_support_format_ar(self.handle()) },
ReadFormat::Cab => unsafe { ffi::archive_read_support_format_cab(self.handle()) },
ReadFormat::Cpio => unsafe { ffi::archive_read_support_format_cpio(self.handle()) },
ReadFormat::Empty => unsafe { ffi::archive_read_support_format_empty(self.handle()) },
ReadFormat::Gnutar => unsafe { ffi::archive_read_support_format_gnutar(self.handle()) },
ReadFormat::Iso9660 => unsafe {
ffi::archive_read_support_format_iso9660(self.handle())
},
ReadFormat::Lha => unsafe { ffi::archive_read_support_format_lha(self.handle()) },
ReadFormat::Mtree => unsafe { ffi::archive_read_support_format_mtree(self.handle()) },
ReadFormat::Rar => unsafe { ffi::archive_read_support_format_rar(self.handle()) },
ReadFormat::Raw => unsafe { ffi::archive_read_support_format_raw(self.handle()) },
ReadFormat::Tar => unsafe { ffi::archive_read_support_format_tar(self.handle()) },
ReadFormat::Xar => unsafe { ffi::archive_read_support_format_xar(self.handle()) },
ReadFormat::Zip => unsafe { ffi::archive_read_support_format_zip(self.handle()) },
};
match result {
ffi::ARCHIVE_OK => Ok(()),
_ => ArchiveResult::from(self as &dyn Handle),
}
}
pub fn open_file<T: AsRef<Path>>(self, file: T) -> ArchiveResult<FileReader> {
self.check_consumed()?;
FileReader::open(self, file)
}
pub fn open_stream<T: Any + Read>(self, src: T) -> ArchiveResult<StreamReader> {
self.check_consumed()?;
StreamReader::open(self, src)
}
fn check_consumed(&self) -> ArchiveResult<()> {
if self.consumed {
Err(ArchiveError::Consumed)
} else {
Ok(())
}
}
fn consume(&mut self) {
self.consumed = true;
}
}
impl Handle for Builder {
unsafe fn handle(&self) -> *mut ffi::Struct_archive {
self.handle
}
}
impl Drop for Builder {
fn drop(&mut self) {
if !self.consumed {
unsafe {
ffi::archive_read_free(self.handle);
}
}
}
}
impl Default for Builder {
fn default() -> Self {
unsafe {
let handle = ffi::archive_read_new();
if handle.is_null() {
panic!("Allocation error");
}
Builder {
handle: handle,
consumed: false,
}
}
}
}
impl ReaderEntry {
pub fn new(handle: *mut ffi::Struct_archive_entry) -> Self {
ReaderEntry { handle: handle }
}
}
impl Default for ReaderEntry {
fn default() -> Self {
ReaderEntry {
handle: ptr::null_mut(),
}
}
}
impl Entry for ReaderEntry {
unsafe fn entry(&self) -> *mut ffi::Struct_archive_entry {
self.handle
}
}
+329
View File
@@ -0,0 +1,329 @@
use std::default::Default;
use std::ffi::CString;
use std::path::Path;
use std::ptr;
use libarchive3_sys::ffi;
use archive::{Entry, ExtractOptions, Handle, WriteFilter, WriteFormat};
use error::{ArchiveError, ArchiveResult};
use reader::{Reader, ReaderEntry};
pub struct Writer {
handle: *mut ffi::Struct_archive,
}
pub struct Disk {
handle: *mut ffi::Struct_archive,
}
pub struct Builder {
handle: *mut ffi::Struct_archive,
consumed: bool,
}
impl Writer {
pub fn new(handle: *mut ffi::Struct_archive) -> Self {
Writer { handle: handle }
}
}
impl Handle for Writer {
unsafe fn handle(&self) -> *mut ffi::Struct_archive {
self.handle
}
}
impl Drop for Writer {
fn drop(&mut self) {
unsafe {
ffi::archive_write_free(self.handle());
}
}
}
impl Disk {
pub fn new() -> Self {
Disk::default()
}
// Retrieve the currently-set value for last block size. A value of -1 here indicates that the
// library should use default values.
pub fn bytes_in_last_block(&self) -> i32 {
unsafe { ffi::archive_write_get_bytes_in_last_block(self.handle) }
}
// Retrieve the block size to be used for writing. A value of -1 here indicates that the
// library should use default values. A value of zero indicates that internal blocking is
// suppressed.
pub fn bytes_per_block(&self) -> i32 {
unsafe { ffi::archive_write_get_bytes_per_block(self.handle) }
}
pub fn set_bytes_per_block(&mut self, count: i32) -> ArchiveResult<()> {
unsafe {
match ffi::archive_write_set_bytes_per_block(self.handle, count) {
ffi::ARCHIVE_OK => Ok(()),
_ => ArchiveResult::from(self as &dyn Handle),
}
}
}
pub fn set_bytes_in_last_block(&mut self, count: i32) -> ArchiveResult<()> {
unsafe {
match ffi::archive_write_set_bytes_in_last_block(self.handle, count) {
ffi::ARCHIVE_OK => Ok(()),
_ => ArchiveResult::from(self as &dyn Handle),
}
}
}
// Set options for extraction built from `ExtractOptions`
pub fn set_options(&self, eopt: &ExtractOptions) -> ArchiveResult<()> {
unsafe {
match ffi::archive_write_disk_set_options(self.handle, eopt.flags) {
ffi::ARCHIVE_OK => Ok(()),
_ => ArchiveResult::from(self as &dyn Handle),
}
}
}
// This convenience function installs a standard set of user and group lookup functions. These
// functions use getpwnam(3) and getgrnam(3) to convert names to ids, defaulting to the ids if
// the names cannot be looked up. These functions also implement a simple memory cache to
// reduce the number of calls to getpwnam(3) and getgrnam(3).
pub fn set_standard_lookup(&self) -> ArchiveResult<()> {
unsafe {
match ffi::archive_write_disk_set_standard_lookup(self.handle) {
ffi::ARCHIVE_OK => Ok(()),
_ => ArchiveResult::from(self as &dyn Handle),
}
}
}
// * Failures - HeaderPosition
pub fn write<T: Reader>(&self, reader: &mut T, prefix: Option<&str>) -> ArchiveResult<usize> {
if reader.header_position() != 0 {
return Err(ArchiveError::HeaderPosition);
}
let mut bytes: usize = 0;
let mut write_pending: bool = false;
loop {
{
if let Some(entry) = reader.next_header() {
if let Some(pfx) = prefix {
let path = Path::new(pfx).join(entry.pathname());
entry.set_pathname(&path);
if entry.hardlink().is_some() {
let path = Path::new(pfx).join(entry.hardlink().unwrap());
entry.set_link(&path);
}
}
match self.write_header(entry) {
Ok(()) => (),
Err(e) => return Err(e),
}
if entry.size() > 0 {
write_pending = true
}
} else {
break;
}
}
if write_pending {
bytes += self.write_data(reader)?;
write_pending = false;
}
}
unsafe {
match ffi::archive_write_finish_entry(self.handle()) {
ffi::ARCHIVE_OK => Ok(bytes),
_ => Err(ArchiveError::from(self as &dyn Handle)),
}
}
}
pub fn close(&self) -> ArchiveResult<()> {
unsafe {
match ffi::archive_write_close(self.handle()) {
ffi::ARCHIVE_OK => Ok(()),
_ => ArchiveResult::from(self as &dyn Handle),
}
}
}
fn write_data<T: Reader>(&self, reader: &T) -> ArchiveResult<usize> {
let mut buff = ptr::null();
let mut size = 0;
let mut offset = 0;
unsafe {
loop {
match ffi::archive_read_data_block(
reader.handle(),
&mut buff,
&mut size,
&mut offset,
) {
ffi::ARCHIVE_EOF => return Ok(size),
ffi::ARCHIVE_OK => {
if ffi::archive_write_data_block(self.handle, buff, size, offset)
!= ffi::ARCHIVE_OK as isize
{
return Err(ArchiveError::from(self as &dyn Handle));
}
}
_ => return Err(ArchiveError::from(reader as &dyn Handle)),
}
}
}
}
fn write_header(&self, entry: &ReaderEntry) -> ArchiveResult<()> {
unsafe {
match ffi::archive_write_header(self.handle, entry.entry()) {
ffi::ARCHIVE_OK => Ok(()),
_ => ArchiveResult::from(self as &dyn Handle),
}
}
}
}
impl Handle for Disk {
unsafe fn handle(&self) -> *mut ffi::Struct_archive {
self.handle
}
}
impl Default for Disk {
fn default() -> Self {
unsafe {
let handle = ffi::archive_write_disk_new();
if handle.is_null() {
panic!("Allocation error");
}
Disk { handle: handle }
}
}
}
impl Drop for Disk {
fn drop(&mut self) {
unsafe {
ffi::archive_write_free(self.handle());
}
}
}
impl Builder {
pub fn new() -> Self {
Builder::default()
}
pub fn add_filter(&mut self, filter: WriteFilter) -> ArchiveResult<()> {
let result = match filter {
WriteFilter::B64Encode => unsafe {
ffi::archive_write_add_filter_b64encode(self.handle)
},
WriteFilter::Bzip2 => unsafe { ffi::archive_write_add_filter_bzip2(self.handle) },
WriteFilter::Compress => unsafe { ffi::archive_write_add_filter_compress(self.handle) },
WriteFilter::Grzip => unsafe { ffi::archive_write_add_filter_grzip(self.handle) },
WriteFilter::Gzip => unsafe { ffi::archive_write_add_filter_gzip(self.handle) },
WriteFilter::Lrzip => unsafe { ffi::archive_write_add_filter_lrzip(self.handle) },
WriteFilter::Lzip => unsafe { ffi::archive_write_add_filter_lzip(self.handle) },
WriteFilter::Lzma => unsafe { ffi::archive_write_add_filter_lzma(self.handle) },
WriteFilter::Lzop => unsafe { ffi::archive_write_add_filter_lzop(self.handle) },
WriteFilter::None => unsafe { ffi::archive_write_add_filter_none(self.handle) },
WriteFilter::Program(prog) => {
let c_prog = CString::new(prog).unwrap();
unsafe { ffi::archive_write_add_filter_program(self.handle, c_prog.as_ptr()) }
}
WriteFilter::UuEncode => unsafe { ffi::archive_write_add_filter_uuencode(self.handle) },
WriteFilter::Xz => unsafe { ffi::archive_write_add_filter_xz(self.handle) },
};
match result {
ffi::ARCHIVE_OK => Ok(()),
_ => ArchiveResult::from(self as &dyn Handle),
}
}
pub fn set_format(&self, format: WriteFormat) -> ArchiveResult<()> {
let result = match format {
WriteFormat::SevenZip => unsafe { ffi::archive_write_set_format_7zip(self.handle) },
WriteFormat::ArBsd => unsafe { ffi::archive_write_set_format_ar_bsd(self.handle) },
WriteFormat::ArSvr4 => unsafe { ffi::archive_write_set_format_ar_svr4(self.handle) },
WriteFormat::Cpio => unsafe { ffi::archive_write_set_format_cpio(self.handle) },
WriteFormat::CpioNewc => unsafe {
ffi::archive_write_set_format_cpio_newc(self.handle)
},
WriteFormat::Gnutar => unsafe { ffi::archive_write_set_format_gnutar(self.handle) },
WriteFormat::Iso9660 => unsafe { ffi::archive_write_set_format_iso9660(self.handle) },
WriteFormat::Mtree => unsafe { ffi::archive_write_set_format_mtree(self.handle) },
WriteFormat::MtreeClassic => unsafe {
ffi::archive_write_set_format_mtree_classic(self.handle)
},
WriteFormat::Pax => unsafe { ffi::archive_write_set_format_pax(self.handle) },
WriteFormat::PaxRestricted => unsafe {
ffi::archive_write_set_format_pax_restricted(self.handle)
},
WriteFormat::Shar => unsafe { ffi::archive_write_set_format_shar(self.handle) },
WriteFormat::SharDump => unsafe {
ffi::archive_write_set_format_shar_dump(self.handle)
},
WriteFormat::Ustar => unsafe { ffi::archive_write_set_format_ustar(self.handle) },
WriteFormat::V7tar => unsafe { ffi::archive_write_set_format_v7tar(self.handle) },
WriteFormat::Xar => unsafe { ffi::archive_write_set_format_xar(self.handle) },
WriteFormat::Zip => unsafe { ffi::archive_write_set_format_zip(self.handle) },
};
match result {
ffi::ARCHIVE_OK => Ok(()),
_ => ArchiveResult::from(self as &dyn Handle),
}
}
pub fn open_file<T: AsRef<Path>>(mut self, file: T) -> ArchiveResult<Writer> {
if self.consumed {
return Err(ArchiveError::Consumed);
}
let c_file = CString::new(file.as_ref().to_string_lossy().as_bytes()).unwrap();
let res = unsafe { ffi::archive_write_open_filename(self.handle, c_file.as_ptr()) };
match res {
ffi::ARCHIVE_OK => {
self.consumed = true;
Ok(Writer::new(self.handle))
}
_ => Err(ArchiveError::from(&self as &dyn Handle)),
}
}
}
impl Default for Builder {
fn default() -> Self {
unsafe {
let handle = ffi::archive_write_new();
if handle.is_null() {
panic!("Allocation error");
}
Builder {
handle: handle,
consumed: false,
}
}
}
}
impl Handle for Builder {
unsafe fn handle(&self) -> *mut ffi::Struct_archive {
self.handle
}
}
impl Drop for Builder {
fn drop(&mut self) {
if !self.consumed {
unsafe {
ffi::archive_write_free(self.handle);
}
}
}
}
Binary file not shown.
+101
View File
@@ -0,0 +1,101 @@
extern crate libarchive;
pub mod util;
use libarchive::archive::{self, ReadFilter, ReadFormat};
use libarchive::reader::{self, Reader};
use libarchive::writer;
use std::fs::File;
#[test]
fn reading_from_file() {
let tar = util::path::fixture("sample.tar.gz");
let mut builder = reader::Builder::new();
builder.support_format(ReadFormat::All).ok();
builder.support_filter(ReadFilter::All).ok();
let mut reader = builder.open_file(tar).ok().unwrap();
reader.next_header();
// let entry: &archive::Entry = &reader.entry;
// println!("{:?}", entry.pathname());
// println!("{:?}", entry.size());
// for entry in reader.entries() {
// let file = entry as &archive::Entry;
// println!("{:?}", file.pathname());
// println!("{:?}", file.size());
// }
assert_eq!(4, 4);
}
#[test]
fn read_archive_from_stream() {
let tar = util::path::fixture("sample.tar.gz");
let f = File::open(tar).ok().unwrap();
let mut builder = reader::Builder::new();
builder.support_format(ReadFormat::All).ok();
builder.support_filter(ReadFilter::All).ok();
match builder.open_stream(f) {
Ok(mut reader) => {
assert_eq!(reader.header_position(), 0);
let writer = writer::Disk::new();
let count = writer
.write(&mut reader, Some("/opt/bldr/fucks"))
.ok()
.unwrap();
assert_eq!(count, 14);
assert_eq!(reader.header_position(), 1024);
assert_eq!(4, 4);
}
Err(e) => {
println!("{:?}", e);
}
}
}
#[test]
fn extracting_from_file() {
let tar = util::path::fixture("sample.tar.gz");
let mut builder = reader::Builder::new();
builder.support_format(ReadFormat::All).ok();
builder.support_filter(ReadFilter::All).ok();
let mut reader = builder.open_file(tar).ok().unwrap();
println!("{:?}", reader.header_position());
let writer = writer::Disk::new();
writer.write(&mut reader, None).ok();
println!("{:?}", reader.header_position());
assert_eq!(4, 4)
}
#[test]
fn extracting_an_archive_with_options() {
let tar = util::path::fixture("sample.tar.gz");
let mut builder = reader::Builder::new();
builder.support_format(ReadFormat::All).ok();
builder.support_filter(ReadFilter::All).ok();
let mut reader = builder.open_file(tar).ok().unwrap();
println!("{:?}", reader.header_position());
let mut opts = archive::ExtractOptions::new();
opts.add(archive::ExtractOption::Time);
let writer = writer::Disk::new();
writer.set_options(&opts).ok();
writer.write(&mut reader, None).ok();
println!("{:?}", reader.header_position());
assert_eq!(4, 4)
}
#[test]
fn extracting_a_reader_twice() {
let tar = util::path::fixture("sample.tar.gz");
let mut builder = reader::Builder::new();
builder.support_format(ReadFormat::All).ok();
builder.support_filter(ReadFilter::All).ok();
let mut reader = builder.open_file(tar).ok().unwrap();
println!("{:?}", reader.header_position());
let writer = writer::Disk::new();
writer.write(&mut reader, None).ok();
println!("{:?}", reader.header_position());
match writer.write(&mut reader, None) {
Ok(_) => println!("oops"),
Err(_) => println!("nice"),
}
assert_eq!(4, 4)
}
+1
View File
@@ -0,0 +1 @@
pub mod path;
+25
View File
@@ -0,0 +1,25 @@
use std::env;
use std::path::PathBuf;
pub fn exe_path() -> PathBuf {
env::current_exe().unwrap()
}
pub fn root() -> PathBuf {
exe_path()
.parent()
.unwrap()
.parent()
.unwrap()
.parent()
.unwrap()
.join("tests")
}
pub fn fixtures() -> PathBuf {
root().join("fixtures")
}
pub fn fixture(name: &str) -> PathBuf {
fixtures().join(name)
}