diff --git a/libraries/libarchive/.gitignore b/libraries/libarchive/.gitignore new file mode 100644 index 00000000..866ed041 --- /dev/null +++ b/libraries/libarchive/.gitignore @@ -0,0 +1,4 @@ +target +Cargo.lock +.tags +.tags1 diff --git a/libraries/libarchive/.travis.yml b/libraries/libarchive/.travis.yml new file mode 100644 index 00000000..68e89ccb --- /dev/null +++ b/libraries/libarchive/.travis.yml @@ -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 '' > 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= diff --git a/libraries/libarchive/Cargo.toml b/libraries/libarchive/Cargo.toml new file mode 100644 index 00000000..11e0573a --- /dev/null +++ b/libraries/libarchive/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "libarchive-drop" +version = "0.1.1" +authors = ["Jamie Winsor ", "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" diff --git a/libraries/libarchive/LICENSE b/libraries/libarchive/LICENSE new file mode 100644 index 00000000..55c6a20f --- /dev/null +++ b/libraries/libarchive/LICENSE @@ -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. diff --git a/libraries/libarchive/README.md b/libraries/libarchive/README.md new file mode 100644 index 00000000..d66d0221 --- /dev/null +++ b/libraries/libarchive/README.md @@ -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; +``` diff --git a/libraries/libarchive/src/archive.rs b/libraries/libarchive/src/archive.rs new file mode 100644 index 00000000..71559923 --- /dev/null +++ b/libraries/libarchive/src/archive.rs @@ -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 ()>, 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 } + } +} diff --git a/libraries/libarchive/src/error.rs b/libraries/libarchive/src/error.rs new file mode 100644 index 00000000..4f574257 --- /dev/null +++ b/libraries/libarchive/src/error.rs @@ -0,0 +1,58 @@ +use archive; +use std::error; +use std::fmt; + +pub type ArchiveResult = Result; + +#[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)), + } + } +} diff --git a/libraries/libarchive/src/lib.rs b/libraries/libarchive/src/lib.rs new file mode 100644 index 00000000..8006328e --- /dev/null +++ b/libraries/libarchive/src/lib.rs @@ -0,0 +1,7 @@ +extern crate libarchive3_sys; +extern crate libc; + +pub mod archive; +pub mod error; +pub mod reader; +pub mod writer; diff --git a/libraries/libarchive/src/reader.rs b/libraries/libarchive/src/reader.rs new file mode 100644 index 00000000..6211d5f1 --- /dev/null +++ b/libraries/libarchive/src/reader.rs @@ -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> { + 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, +} + +pub struct Builder { + handle: *mut ffi::Struct_archive, + consumed: bool, +} + +pub struct ReaderEntry { + handle: *mut ffi::Struct_archive_entry, +} + +struct Pipe { + reader: Box, + buffer: Vec, +} + +impl Pipe { + fn new(src: T) -> Self { + Pipe { + reader: Box::new(src), + buffer: vec![0; 8192], + } + } + + fn read_bytes(&mut self) -> io::Result { + self.reader.read(&mut self.buffer[..]) + } +} + +impl FileReader { + pub fn open>(mut builder: Builder, file: T) -> ArchiveResult { + 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(mut builder: Builder, src: T) -> ArchiveResult { + 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>(self, file: T) -> ArchiveResult { + self.check_consumed()?; + FileReader::open(self, file) + } + + pub fn open_stream(self, src: T) -> ArchiveResult { + 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 + } +} diff --git a/libraries/libarchive/src/writer.rs b/libraries/libarchive/src/writer.rs new file mode 100644 index 00000000..ac29d42e --- /dev/null +++ b/libraries/libarchive/src/writer.rs @@ -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(&self, reader: &mut T, prefix: Option<&str>) -> ArchiveResult { + 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(&self, reader: &T) -> ArchiveResult { + 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>(mut self, file: T) -> ArchiveResult { + 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); + } + } + } +} diff --git a/libraries/libarchive/tests/fixtures/sample.tar.gz b/libraries/libarchive/tests/fixtures/sample.tar.gz new file mode 100644 index 00000000..4d9aa8e7 Binary files /dev/null and b/libraries/libarchive/tests/fixtures/sample.tar.gz differ diff --git a/libraries/libarchive/tests/lib.rs b/libraries/libarchive/tests/lib.rs new file mode 100644 index 00000000..d35c5373 --- /dev/null +++ b/libraries/libarchive/tests/lib.rs @@ -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) +} diff --git a/libraries/libarchive/tests/util/mod.rs b/libraries/libarchive/tests/util/mod.rs new file mode 100644 index 00000000..4da97892 --- /dev/null +++ b/libraries/libarchive/tests/util/mod.rs @@ -0,0 +1 @@ +pub mod path; diff --git a/libraries/libarchive/tests/util/path.rs b/libraries/libarchive/tests/util/path.rs new file mode 100644 index 00000000..882a2973 --- /dev/null +++ b/libraries/libarchive/tests/util/path.rs @@ -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) +}