'alloc'-feature and owning tar archive type (v0.1.5)
This commit is contained in:
parent
b35f7a5179
commit
da12b748dc
12
.github/workflows/rust.yml
vendored
12
.github/workflows/rust.yml
vendored
@ -21,14 +21,14 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build
|
||||
run: cargo build --verbose
|
||||
# use some no_std target
|
||||
run: cargo build --all-targets --verbose --features all
|
||||
# use some arbitrary no_std target
|
||||
- name: Install no_std target thumbv7em-none-eabihf
|
||||
run: rustup target add thumbv7em-none-eabihf
|
||||
- name: Build (no_std)
|
||||
run: cargo build --verbose --target thumbv7em-none-eabihf
|
||||
run: cargo build --verbose --target thumbv7em-none-eabihf --features all
|
||||
- name: Run tests
|
||||
run: cargo test --verbose
|
||||
run: cargo test --verbose --features all
|
||||
|
||||
style_checks:
|
||||
runs-on: ubuntu-latest
|
||||
@ -43,6 +43,6 @@ jobs:
|
||||
- name: Rustfmt
|
||||
run: cargo fmt -- --check
|
||||
- name: Clippy
|
||||
run: cargo clippy
|
||||
run: cargo clippy --features all
|
||||
- name: Rustdoc
|
||||
run: cargo doc
|
||||
run: cargo doc --features all
|
||||
|
@ -7,7 +7,7 @@ as GNU Longname. The maximum supported file name length is 100 characters includ
|
||||
The maximum supported file size is 8GiB. Also, directories are not supported yet but only flat
|
||||
collections of files.
|
||||
"""
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
edition = "2018"
|
||||
keywords = ["tar", "tarball", "archive"]
|
||||
categories = ["data-structures", "no-std", "parser-implementations"]
|
||||
@ -20,6 +20,11 @@ documentation = "https://docs.rs/tar-no-std"
|
||||
# required because "env_logger" uses "log" but with dependency to std..
|
||||
resolver = "2"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
alloc = []
|
||||
all = ["alloc"]
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1.3"
|
||||
arrayvec = { version = "0.7", default-features = false }
|
||||
|
12
README.md
12
README.md
@ -19,8 +19,10 @@ GNU Extensions such as sparse files, incremental archives, and long filename ext
|
||||
[This link](https://www.gnu.org/software/tar/manual/html_section/Formats.html) gives a good overview over possible
|
||||
archive formats and their limitations.
|
||||
|
||||
## Example
|
||||
## Example (without `alloc`-feature)
|
||||
```rust
|
||||
use tar_no_std::TarArchiveRef;
|
||||
|
||||
fn main() {
|
||||
// log: not mandatory
|
||||
std::env::set_var("RUST_LOG", "trace");
|
||||
@ -28,7 +30,7 @@ fn main() {
|
||||
|
||||
// also works in no_std environment (except the println!, of course)
|
||||
let archive = include_bytes!("../tests/gnu_tar_default.tar");
|
||||
let archive = TarArchive::new(archive);
|
||||
let archive = TarArchiveRef::new(archive);
|
||||
// Vec needs an allocator of course, but the library itself doesn't need one
|
||||
let entries = archive.entries().collect::<Vec<_>>();
|
||||
println!("{:#?}", entries);
|
||||
@ -37,7 +39,11 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
## Compression
|
||||
## Alloc Feature
|
||||
This crate allows the additional Cargo build time feature `alloc`. When this is used, the crate
|
||||
also provides the type `TarArchive`, which owns the data on the heap.
|
||||
|
||||
## Compression (`tar.gz`)
|
||||
If your tar file is compressed, e.g. by `.tar.gz`/`gzip`, you need to uncompress the bytes first
|
||||
(e.g. by a *gzip* library). Afterwards, this crate can read and write the Tar archive format from the bytes.
|
||||
|
||||
|
11
build.sh
Executable file
11
build.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
cargo build --all-targets --verbose --features all
|
||||
# use some random no_std target
|
||||
rustup target add thumbv7em-none-eabihf
|
||||
cargo build --verbose --target thumbv7em-none-eabihf --features all
|
||||
cargo test --verbose --features all
|
||||
|
||||
cargo fmt -- --check
|
||||
cargo clippy --features all
|
||||
cargo doc --features all
|
41
examples/alloc_feature.rs
Normal file
41
examples/alloc_feature.rs
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Philipp Schuster
|
||||
|
||||
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.
|
||||
*/
|
||||
use tar_no_std::TarArchive;
|
||||
|
||||
/// This example needs the `alloc` feature.
|
||||
fn main() {
|
||||
// log: not mandatory
|
||||
std::env::set_var("RUST_LOG", "trace");
|
||||
env_logger::init();
|
||||
|
||||
// also works in no_std environment (except the println!, of course)
|
||||
let archive = include_bytes!("../tests/gnu_tar_default.tar");
|
||||
let archive_heap_owned = archive.to_vec().into_boxed_slice();
|
||||
let archive = TarArchive::new(archive_heap_owned);
|
||||
// Vec needs an allocator of course, but the library itself doesn't need one
|
||||
let entries = archive.entries().collect::<Vec<_>>();
|
||||
println!("{:#?}", entries);
|
||||
println!("content of last file:");
|
||||
println!("{:#?}", entries[2].data_as_str().expect("Invalid UTF-8"));
|
||||
}
|
@ -21,7 +21,7 @@ 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.
|
||||
*/
|
||||
use tar_no_std::TarArchive;
|
||||
use tar_no_std::TarArchiveRef;
|
||||
|
||||
fn main() {
|
||||
// log: not mandatory
|
||||
@ -30,10 +30,10 @@ fn main() {
|
||||
|
||||
// also works in no_std environment (except the println!, of course)
|
||||
let archive = include_bytes!("../tests/gnu_tar_default.tar");
|
||||
let archive = TarArchive::new(archive);
|
||||
let archive = TarArchiveRef::new(archive);
|
||||
// Vec needs an allocator of course, but the library itself doesn't need one
|
||||
let entries = archive.entries().collect::<Vec<_>>();
|
||||
println!("{:#?}", entries);
|
||||
println!("content of last file:");
|
||||
println!("{:#?}", entries[2].data_as_str().expect("Invalid UTF-8") );
|
||||
println!("{:#?}", entries[2].data_as_str().expect("Invalid UTF-8"));
|
||||
}
|
||||
|
117
src/archive.rs
117
src/archive.rs
@ -21,10 +21,13 @@ 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.
|
||||
*/
|
||||
//! Module for [`TarArchive`].
|
||||
//! Module for [`TarArchiveRef`]. If the `alloc`-feature is enabled, this crate
|
||||
//! also exports `TarArchive`, which owns data on the heap.
|
||||
|
||||
use crate::header::PosixHeader;
|
||||
use crate::{TypeFlag, BLOCKSIZE};
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::boxed::Box;
|
||||
use arrayvec::ArrayString;
|
||||
use core::fmt::{Debug, Formatter};
|
||||
use core::str::{FromStr, Utf8Error};
|
||||
@ -78,15 +81,72 @@ impl<'a> Debug for ArchiveEntry<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper type around the bytes, which represents an archive.
|
||||
/// Type that owns bytes on the heap, that represents a Tar archive.
|
||||
/// Unlike [`TarArchive`], this type is useful, if you need to own the
|
||||
/// data as long as you need the archive, but not longer.
|
||||
///
|
||||
/// This is only available with the `alloc` feature of this crate.
|
||||
#[cfg(feature = "alloc")]
|
||||
#[derive(Debug)]
|
||||
pub struct TarArchive<'a> {
|
||||
pub struct TarArchive {
|
||||
data: Box<[u8]>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl TarArchive {
|
||||
/// Creates a new archive type, that owns the data on the heap. The provided byte array is
|
||||
/// interpreted as bytes in Tar archive format.
|
||||
pub fn new(data: Box<[u8]>) -> Self {
|
||||
assert_eq!(
|
||||
data.len() % BLOCKSIZE,
|
||||
0,
|
||||
"data must be a multiple of BLOCKSIZE={}, len is {}",
|
||||
BLOCKSIZE,
|
||||
data.len(),
|
||||
);
|
||||
Self { data }
|
||||
}
|
||||
|
||||
/// Iterates over all entries of the Tar archive.
|
||||
/// Returns items of type [`ArchiveEntry`].
|
||||
/// See also [`ArchiveIterator`].
|
||||
pub fn entries(&self) -> ArchiveIterator {
|
||||
ArchiveIterator::new(self.data.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl From<Box<[u8]>> for TarArchive {
|
||||
fn from(data: Box<[u8]>) -> Self {
|
||||
Self::new(data)
|
||||
}
|
||||
}
|
||||
|
||||
/*#[cfg(feature = "alloc")]
|
||||
impl Into<Box<[u8]>> for TarArchive {
|
||||
fn into(self) -> Box<[u8]> {
|
||||
self.data
|
||||
}
|
||||
}*/
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl From<TarArchive> for Box<[u8]> {
|
||||
fn from(ar: TarArchive) -> Self {
|
||||
ar.data
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper type around bytes, which represents a Tar archive.
|
||||
/// Unlike [`TarArchiveRef`], this uses only a reference to data.
|
||||
#[derive(Debug)]
|
||||
pub struct TarArchiveRef<'a> {
|
||||
data: &'a [u8],
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl<'a> TarArchive<'a> {
|
||||
/// Interprets the provided byte array as Tar archive.
|
||||
impl<'a> TarArchiveRef<'a> {
|
||||
/// Creates a new archive wrapper type. The provided byte array is interpreted as
|
||||
/// bytes in Tar archive format.
|
||||
pub fn new(data: &'a [u8]) -> Self {
|
||||
assert_eq!(
|
||||
data.len() % BLOCKSIZE,
|
||||
@ -97,32 +157,33 @@ impl<'a> TarArchive<'a> {
|
||||
Self { data }
|
||||
}
|
||||
|
||||
/// Iterates over all entries of the TAR Archive.
|
||||
/// Iterates over all entries of the Tar archive.
|
||||
/// Returns items of type [`ArchiveEntry`].
|
||||
/// See also [`ArchiveIterator`].
|
||||
pub const fn entries(&self) -> ArchiveIterator {
|
||||
ArchiveIterator::new(self)
|
||||
ArchiveIterator::new(self.data)
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator over the files. Each iteration step starts
|
||||
/// Iterator over the files of the archive. Each iteration starts
|
||||
/// at the next Tar header entry.
|
||||
#[derive(Debug)]
|
||||
pub struct ArchiveIterator<'a> {
|
||||
archive: &'a TarArchive<'a>,
|
||||
archive_data: &'a [u8],
|
||||
block_index: usize,
|
||||
}
|
||||
|
||||
impl<'a> ArchiveIterator<'a> {
|
||||
pub const fn new(archive: &'a TarArchive<'a>) -> Self {
|
||||
pub const fn new(archive: &'a [u8]) -> Self {
|
||||
Self {
|
||||
archive,
|
||||
archive_data: archive,
|
||||
block_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the next Header.
|
||||
fn next_hdr(&self, block_index: usize) -> &'a PosixHeader {
|
||||
let hdr_ptr = &self.archive.data[block_index * BLOCKSIZE];
|
||||
let hdr_ptr = &self.archive_data[block_index * BLOCKSIZE];
|
||||
unsafe { (hdr_ptr as *const u8).cast::<PosixHeader>().as_ref() }.unwrap()
|
||||
}
|
||||
}
|
||||
@ -131,7 +192,7 @@ impl<'a> Iterator for ArchiveIterator<'a> {
|
||||
type Item = ArchiveEntry<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.block_index * BLOCKSIZE >= self.archive.data.len() {
|
||||
if self.block_index * BLOCKSIZE >= self.archive_data.len() {
|
||||
log::warn!("Reached end of Tar archive data without finding zero/end blocks!");
|
||||
return None;
|
||||
}
|
||||
@ -170,7 +231,7 @@ impl<'a> Iterator for ArchiveIterator<'a> {
|
||||
let i_begin = (self.block_index + 1) * BLOCKSIZE;
|
||||
// i_end is the exclusive byte end index of the data of the current file
|
||||
let i_end = i_begin + data_block_count * BLOCKSIZE;
|
||||
let file_block_bytes = &self.archive.data[i_begin..i_end];
|
||||
let file_block_bytes = &self.archive_data[i_begin..i_end];
|
||||
// because each block is 512 bytes long, the file is not necessarily a multiple of 512 bytes
|
||||
let file_bytes = &file_block_bytes[0..hdr.size.val()];
|
||||
|
||||
@ -192,22 +253,38 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_archive_list() {
|
||||
let archive = TarArchive::new(include_bytes!("../tests/gnu_tar_default.tar"));
|
||||
let archive = TarArchiveRef::new(include_bytes!("../tests/gnu_tar_default.tar"));
|
||||
let entries = archive.entries().collect::<Vec<_>>();
|
||||
println!("{:#?}", entries);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_archive_entries() {
|
||||
let archive = TarArchive::new(include_bytes!("../tests/gnu_tar_default.tar"));
|
||||
#[cfg(feature = "alloc")]
|
||||
{
|
||||
let data = include_bytes!("../tests/gnu_tar_default.tar")
|
||||
.to_vec()
|
||||
.into_boxed_slice();
|
||||
let archive = TarArchive::new(data.clone());
|
||||
let entries = archive.entries().collect::<Vec<_>>();
|
||||
assert_archive_content(&entries);
|
||||
|
||||
let archive = TarArchive::from(data.clone());
|
||||
let entries = archive.entries().collect::<Vec<_>>();
|
||||
assert_archive_content(&entries);
|
||||
|
||||
assert_eq!(data, archive.into());
|
||||
}
|
||||
|
||||
let archive = TarArchiveRef::new(include_bytes!("../tests/gnu_tar_default.tar"));
|
||||
let entries = archive.entries().collect::<Vec<_>>();
|
||||
assert_archive_content(&entries);
|
||||
|
||||
let archive = TarArchive::new(include_bytes!("../tests/gnu_tar_gnu.tar"));
|
||||
let archive = TarArchiveRef::new(include_bytes!("../tests/gnu_tar_gnu.tar"));
|
||||
let entries = archive.entries().collect::<Vec<_>>();
|
||||
assert_archive_content(&entries);
|
||||
|
||||
let archive = TarArchive::new(include_bytes!("../tests/gnu_tar_oldgnu.tar"));
|
||||
let archive = TarArchiveRef::new(include_bytes!("../tests/gnu_tar_oldgnu.tar"));
|
||||
let entries = archive.entries().collect::<Vec<_>>();
|
||||
assert_archive_content(&entries);
|
||||
|
||||
@ -221,11 +298,11 @@ mod tests {
|
||||
let entries = archive.entries().collect::<Vec<_>>();
|
||||
assert_archive_content(&entries);*/
|
||||
|
||||
let archive = TarArchive::new(include_bytes!("../tests/gnu_tar_ustar.tar"));
|
||||
let archive = TarArchiveRef::new(include_bytes!("../tests/gnu_tar_ustar.tar"));
|
||||
let entries = archive.entries().collect::<Vec<_>>();
|
||||
assert_archive_content(&entries);
|
||||
|
||||
let archive = TarArchive::new(include_bytes!("../tests/gnu_tar_v7.tar"));
|
||||
let archive = TarArchiveRef::new(include_bytes!("../tests/gnu_tar_v7.tar"));
|
||||
let entries = archive.entries().collect::<Vec<_>>();
|
||||
assert_archive_content(&entries);
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ SOFTWARE.
|
||||
//!
|
||||
//! # Example
|
||||
//! ```rust
|
||||
//! use tar_no_std::TarArchive;
|
||||
//! use tar_no_std::TarArchiveRef;
|
||||
//!
|
||||
//! // log: not mandatory
|
||||
//! std::env::set_var("RUST_LOG", "trace");
|
||||
@ -49,7 +49,7 @@ SOFTWARE.
|
||||
//!
|
||||
//! // also works in no_std environment (except the println!, of course)
|
||||
//! let archive = include_bytes!("../tests/gnu_tar_default.tar");
|
||||
//! let archive = TarArchive::new(archive);
|
||||
//! let archive = TarArchiveRef::new(archive);
|
||||
//! // Vec needs an allocator of course, but the library itself doesn't need one
|
||||
//! let entries = archive.entries().collect::<Vec<_>>();
|
||||
//! println!("{:#?}", entries);
|
||||
@ -69,6 +69,9 @@ SOFTWARE.
|
||||
#[cfg(test)]
|
||||
extern crate std;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
extern crate alloc;
|
||||
|
||||
/// Each Archive Entry (either Header or Data Block) is a block of 512 bytes.
|
||||
const BLOCKSIZE: usize = 512;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user