Skip to content

Commit

Permalink
Rework the api now using libarchive, instead of shell
Browse files Browse the repository at this point in the history
- fix the lzip file;
- add uncompress_archive func;
- add uncompress_archive_file func;
- add uncompres_archive_file func;
- add integration tests;
- rework error enum;
- add a binding script generator;
- rework the example;

Signed-off-by: asakiz <asakizin@gmail.com>
  • Loading branch information
Asakiz authored and otavio committed Apr 13, 2020
1 parent 234334a commit c433259
Show file tree
Hide file tree
Showing 13 changed files with 779 additions and 371 deletions.
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ jobs:
name: Check
runs-on: ubuntu-latest
steps:
- name: Install Dependencies
run: sudo apt-get update; sudo apt-get install libarchive-dev

- name: Checkout sources
uses: actions/checkout@v1

Expand All @@ -26,6 +29,9 @@ jobs:
name: Test Suite
runs-on: ubuntu-latest
steps:
- name: Install Dependencies
run: sudo apt-get update; sudo apt-get install libarchive-dev

- name: Checkout sources
uses: actions/checkout@v1

Expand All @@ -45,6 +51,9 @@ jobs:
name: Lints
runs-on: ubuntu-latest
steps:
- name: Install Dependencies
run: sudo apt-get update; sudo apt-get install libarchive-dev

- name: Checkout sources
uses: actions/checkout@v1

Expand Down
14 changes: 8 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@ keywords = ["compression", "archive"]
license = "MIT OR Apache-2.0"
readme = "README.md"
edition = "2018"
build = "build.rs"
build = "src/build.rs"

[badges]
travis-ci = { repository = "OSSystems/compress-tools-rs" }

[dependencies]
pipers = "1"
derive_more = "0.99"
thiserror = "1"
nix = "0.17.0"

[build-dependencies]
pkg-config = "0.3.17"

[dev-dependencies]
exitfailure = "0.5"
structopt = "0.2"
tempfile = "3"
tempfile = "3.1.0"
argh = "0.1.3"
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,29 @@

The library provides tools for handling compressed and archive files.

## Examples
## Examples Uncompress
### Archive
```rust
let dir = tempfile::tempdir().unwrap();
uncompress("tests/fixtures/tree.tar.gz", dir.path(), Kind::TarGZip).unwrap();
let dir = tempfile::TempDir::new().expect("Failed to create the tmp directory");
let mut source = std::fs::File::open("tests/fixtures/tree.tar").unwrap();

uncompress_archive(&mut source, dir.path())?;
```

### Archive file
```rust
let mut source = std::fs::File::open("tests/fixtures/tree.tar").unwrap();
let mut target = Vec::default();

uncompress_archive_file(&mut source, &mut target, &"tree/branch2/leaf")?;
```

### File
```rust
let mut source = std::fs::File::open("tests/fixtures/file.txt.gz").unwrap();
let mut target = Vec::default();

uncompress_file(&mut source, &mut target)?;
```

## License
Expand Down
73 changes: 73 additions & 0 deletions binding_script.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// cargo-deps: bindgen = "0.51.1", pkg-config = "0.3.17"

use std::path::PathBuf;

fn main() {
let mut lib = pkg_config::Config::new()
.atleast_version("3.2.2")
.probe("libarchive")
.expect("Fail to detect the libarchive library");

let include_path = lib
.include_paths
.pop()
.unwrap_or(PathBuf::from("usr/include"));

let bindings = bindgen::Builder::default()
// Set rustfmt setting
.rustfmt_configuration_file(Some(".rustfmt.toml".into()))

// Set include path
.header(format!("{}/archive.h", include_path.display()))
.header(format!("{}/archive_entry.h", include_path.display()))

// We need to add this as raw_line to pass cargo clippy warning about
// convert to upper camel case
.raw_line("#![allow(non_camel_case_types)]\n")

// We need to add this as raw_line otherwise bindgen generates this as
// u32, causing type mismatch
.raw_line("pub const ARCHIVE_EOF: i32 = 1;")
.raw_line("pub const ARCHIVE_OK: i32 = 0;")

// Binding whitelist
.whitelist_var("ARCHIVE_EXTRACT_TIME")
.whitelist_var("ARCHIVE_EXTRACT_PERM")
.whitelist_var("ARCHIVE_EXTRACT_ACL")
.whitelist_var("ARCHIVE_EXTRACT_FFLAGS")
.whitelist_var("ARCHIVE_EXTRACT_OWNER")
.whitelist_var("ARCHIVE_EXTRACT_FFLAGS")
.whitelist_var("ARCHIVE_EXTRACT_XATTR")
.whitelist_function("archive_read_new")
.whitelist_function("archive_read_support_filter_all")
.whitelist_function("archive_read_support_format_all")
.whitelist_function("archive_read_support_format_raw")
.whitelist_function("archive_read_close")
.whitelist_function("archive_read_free")
.whitelist_function("archive_read_data_block")
.whitelist_function("archive_read_next_header")
.whitelist_function("archive_read_open")
.whitelist_function("archive_write_disk_new")
.whitelist_function("archive_write_disk_set_options")
.whitelist_function("archive_write_disk_set_standard_lookup")
.whitelist_function("archive_write_header")
.whitelist_function("archive_write_finish_entry")
.whitelist_function("archive_write_data_block")
.whitelist_function("archive_write_close")
.whitelist_function("archive_write_free")
.whitelist_function("archive_entry_pathname")
.whitelist_function("archive_entry_free")
.whitelist_function("archive_entry_set_pathname")
.whitelist_function("archive_entry_set_hardlink")
.whitelist_function("archive_entry_hardlink")
.whitelist_function("archive_set_error")
.whitelist_function("archive_error_string")
.generate()
.expect("Unable to generate bindings");

bindings
.write_to_file(PathBuf::from("src/ffi.rs"))
.expect("Couldn't write bindings!");

println!("Sucessfully generated bindings for libarchive.");
}
5 changes: 0 additions & 5 deletions build.rs

This file was deleted.

106 changes: 80 additions & 26 deletions examples/uncompress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,90 @@
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use compress_tools::{uncompress, Result};
use std::path::PathBuf;
use structopt::StructOpt;

#[derive(StructOpt, Debug)]
#[structopt(name = "uncompress")]
enum Opt {
/// Increase the verboseness level
#[structopt(name = "tar_gz")]
TarGz(Args),
#[structopt(name = "tar_xz")]
TarXz(Args),
#[structopt(name = "tar")]
Tar(Args),
use argh::FromArgs;
use compress_tools::*;
use std::path::Path;

#[derive(FromArgs, PartialEq, Debug)]
/// Top-level command.
struct TopLevel {
#[argh(subcommand)]
nested: CmdLine,
}

#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand)]
enum CmdLine {
UncompressFile(SubCommandUncompressFile),
UncompressArchiveFile(SubCommandUncompressArchiveFile),
UncompressArchive(SubCommandUncompressArchive),
}

#[derive(FromArgs, PartialEq, Debug)]
/// Uncompress subcommand.
#[argh(subcommand, name = "uncompress-file")]
struct SubCommandUncompressFile {
/// source path
#[argh(positional)]
source_path: String,

/// target path
#[argh(positional)]
target_path: String,
}

#[derive(StructOpt, Debug)]
#[structopt(name = "compression-tools")]
struct Args {
/// compressed file to use as input
input: PathBuf,
#[derive(FromArgs, PartialEq, Debug)]
/// Uncompress archive file subcommand.
#[argh(subcommand, name = "uncompress-archive-file")]
struct SubCommandUncompressArchiveFile {
/// source path
#[argh(positional)]
source_path: String,

/// Path to output the uncompressed result
output: PathBuf,
/// target path
#[argh(positional)]
target_path: String,

/// target file
#[argh(positional)]
target_file: String,
}

fn main() -> Result<()> {
match Opt::from_args() {
Opt::TarGz(arg) => uncompress(arg.input, arg.output, compress_tools::Kind::TarGZip),
Opt::TarXz(arg) => uncompress(arg.input, arg.output, compress_tools::Kind::TarXz),
Opt::Tar(arg) => uncompress(arg.input, arg.output, compress_tools::Kind::Tar),
#[derive(FromArgs, PartialEq, Debug)]
/// Uncompress archive subcommand.
#[argh(subcommand, name = "uncompress-archive")]
struct SubCommandUncompressArchive {
/// source path
#[argh(positional)]
source_path: String,

/// target path
#[argh(positional)]
target_path: String,
}

fn main() -> compress_tools::Result<()> {
let cmd: TopLevel = argh::from_env();

match cmd.nested {
CmdLine::UncompressFile(input) => {
let mut source = std::fs::File::open(input.source_path)?;
let mut target = std::fs::File::open(input.target_path)?;

uncompress_file(&mut source, &mut target)?;
}
CmdLine::UncompressArchiveFile(input) => {
let mut source = std::fs::File::open(input.source_path)?;
let mut target = std::fs::File::open(input.target_path)?;

uncompress_archive_file(&mut source, &mut target, &input.target_file)?;
}
CmdLine::UncompressArchive(input) => {
let mut source = std::fs::File::open(input.source_path)?;

uncompress_archive(&mut source, Path::new(&input.target_path))?;
}
}

Ok(())
}
8 changes: 8 additions & 0 deletions src/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
fn main() {
// This forces the tests to run in a single thread. This is
// required for use of the mocks as we run mocked binaries.
pkg_config::Config::new()
.atleast_version("3.2.2")
.probe("libarchive")
.expect("Fail to detect the libarchive library");
}
57 changes: 57 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (C) 2019 O.S. Systems Sofware LTDA
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use crate::ffi;
use std::ffi::CStr;
use thiserror::Error;

pub type Result<T> = std::result::Result<T, Error>;

#[derive(Error, Debug)]
pub enum Error {
#[error("Extraction error: '{0}'")]
ExtractionError(String),

#[error("Nix error: '{0}'")]
Nix(#[from] nix::Error),

#[error("Io error: '{0}'")]
Io(#[from] std::io::Error),

#[error("Utf error: '{0}'")]
Utf(#[from] std::str::Utf8Error),

#[error("Try from int error: '{0}'")]
TryInt(#[from] std::num::TryFromIntError),

#[error("Error to create the archive struct, is null")]
ArchiveNull,

#[error("The entry is null, failed to set the pathname")]
EntryNull,

#[error("File not found")]
FileNotFound,

#[error("The end of file")]
EndOfFile,
}

pub(crate) fn archive_result(value: i32, archive: *mut ffi::archive) -> Result<()> {
if value != ffi::ARCHIVE_OK {
return Err(Error::from(archive));
}

Ok(())
}

#[allow(clippy::not_unsafe_ptr_arg_deref)]
impl From<*mut ffi::archive> for Error {
fn from(input: *mut ffi::archive) -> Self {
unsafe {
let input = ffi::archive_error_string(input);
Error::ExtractionError(CStr::from_ptr(input).to_string_lossy().to_string())
}
}
}
Loading

0 comments on commit c433259

Please sign in to comment.