Initial commit
This commit is contained in:
commit
03e8cc56f8
5
.cargo/config.toml
Normal file
5
.cargo/config.toml
Normal file
@ -0,0 +1,5 @@
|
||||
[build]
|
||||
target = "x86_64-unknown-mikros"
|
||||
|
||||
[install]
|
||||
root = "../os_build/sysroot"
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
288
Cargo.lock
generated
Normal file
288
Cargo.lock
generated
Normal file
@ -0,0 +1,288 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "file_rpc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"parking_lot",
|
||||
"rmp-serde",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs_rpc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"parking_lot",
|
||||
"rmp-serde",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.155"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmp"
|
||||
version = "0.8.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"num-traits",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmp-serde"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"rmp",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.203"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.203"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tar"
|
||||
version = "0.4.41"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tarfs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"file_rpc",
|
||||
"fs_rpc",
|
||||
"parking_lot",
|
||||
"tar",
|
||||
"vfs_rpc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "vfs_rpc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"parking_lot",
|
||||
"rmp-serde",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "tarfs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
file_rpc = { version = "0.1.0", path = "../file_rpc" }
|
||||
fs_rpc = { version = "0.1.0", path = "../fs_rpc" }
|
||||
parking_lot = "0.12.3"
|
||||
tar = { version = "0.4.41", default-features = false, path = "tar-0.4.41" }
|
||||
vfs_rpc = { version = "0.1.0", path = "../vfs/vfs_rpc" }
|
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "dev-x86_64-unknown-mikros"
|
79
src/main.rs
Normal file
79
src/main.rs
Normal file
@ -0,0 +1,79 @@
|
||||
use std::{fs::File, io::{Read, Seek}, os::mikros::{ipc, syscalls}, path::PathBuf, sync::Arc, usize};
|
||||
|
||||
use parking_lot::RwLock;
|
||||
use tar::Archive;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Serv {
|
||||
mounts: Arc<RwLock<Vec<(File, Vec<(PathBuf, u64, usize)>)>>>,
|
||||
files: Arc<RwLock<Vec<(usize, u64, usize)>>>,
|
||||
}
|
||||
|
||||
impl fs_rpc::Server for Serv {
|
||||
fn mount(&self, dev: &std::path::Path, _path: &std::path::Path) -> Result<u64, ()> {
|
||||
let archive = File::open(dev).map_err(|_| ())?;
|
||||
let mut archive = Archive::new(archive);
|
||||
let entries = archive.entries_with_seek().unwrap().map(|entry| {
|
||||
let entry = entry.unwrap();
|
||||
let path = entry.path().unwrap().into_owned();
|
||||
let file_offset = entry.raw_file_position();
|
||||
(path, file_offset, entry.size() as usize)
|
||||
}).collect();
|
||||
self.mounts.write().push((archive.into_inner(), entries));
|
||||
Ok((self.mounts.read().len() - 1) as u64)
|
||||
}
|
||||
|
||||
fn open(&self, path: &std::path::Path, mount_id: u64) -> Result<(Option<u64>, u64), ()> {
|
||||
let mounts = self.mounts.read();
|
||||
let mount = &mounts[mount_id as usize];
|
||||
let (&file_offset, &file_size) = mount.1.iter().find(|(entry_path, _offset, _size)| entry_path == path).map(|(_x, y, z)| (y, z)).ok_or(())?;
|
||||
self.files.write().push((mount_id as usize, file_offset, file_size));
|
||||
Ok((None, (self.files.read().len() - 1) as u64))
|
||||
}
|
||||
}
|
||||
|
||||
impl file_rpc::Server for Serv {
|
||||
fn read(&self, fd: u64, pos: u64, len: usize) -> Result<Vec<u8>, ()> {
|
||||
let (mount_id, file_offset, size) = self.files.read()[fd as usize];
|
||||
let read_len = usize::min(len, size - (pos as usize));
|
||||
let mounts = self.mounts.read();
|
||||
let mount = &mounts[mount_id as usize];
|
||||
(&mount.0).seek(std::io::SeekFrom::Start(file_offset + pos)).unwrap();
|
||||
let mut buf = vec![0; read_len];
|
||||
let read_bytes = (&mount.0).read(&mut buf).map_err(|_| ()) ?;
|
||||
buf.truncate(read_bytes);
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn write(&self, _fd: u64, _pos: u64, _data: &[u8]) -> Result<(), ()> {
|
||||
Err(())
|
||||
}
|
||||
|
||||
fn close(&self, _fd: u64) {}
|
||||
|
||||
fn size(&self, fd: u64) -> Option<u64> {
|
||||
let (_mount_id, _file_offset, size) = self.files.read()[fd as usize];
|
||||
Some(size as u64)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let serv = Serv {
|
||||
mounts: Arc::new(RwLock::new(Vec::new())),
|
||||
files: Arc::new(RwLock::new(Vec::new())),
|
||||
};
|
||||
fs_rpc::register_server(Box::new(serv.clone()));
|
||||
file_rpc::register_server(Box::new(serv));
|
||||
|
||||
let vfs_pid;
|
||||
loop {
|
||||
if let Some(pid) = syscalls::try_get_registered(0) {
|
||||
vfs_pid = pid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
vfs_rpc::Client::new(vfs_pid).register_fs("tarfs").unwrap();
|
||||
loop {
|
||||
ipc::process_messages()
|
||||
}
|
||||
}
|
6
tar-0.4.41/.cargo_vcs_info.json
Normal file
6
tar-0.4.41/.cargo_vcs_info.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "2cb0c7b53f5748d84f83d0bc74abe8669f2d2187"
|
||||
},
|
||||
"path_in_vcs": ""
|
||||
}
|
8
tar-0.4.41/.github/dependabot.yml
vendored
Normal file
8
tar-0.4.41/.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: cargo
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "08:00"
|
||||
open-pull-requests-limit: 10
|
63
tar-0.4.41/.github/workflows/main.yml
vendored
Normal file
63
tar-0.4.41/.github/workflows/main.yml
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
name: CI
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
build: [stable, beta, nightly, macos, windows]
|
||||
include:
|
||||
- build: stable
|
||||
os: ubuntu-latest
|
||||
rust: stable
|
||||
- build: beta
|
||||
os: ubuntu-latest
|
||||
rust: beta
|
||||
- build: nightly
|
||||
os: ubuntu-latest
|
||||
rust: nightly
|
||||
- build: macos
|
||||
os: macos-latest
|
||||
rust: stable
|
||||
- build: windows
|
||||
os: windows-latest
|
||||
rust: stable
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Install Rust
|
||||
run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }}
|
||||
shell: bash
|
||||
- run: cargo test
|
||||
- run: cargo test --no-default-features
|
||||
- name: Run cargo test with root
|
||||
run: sudo -E $(which cargo) test
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
|
||||
rustfmt:
|
||||
name: Rustfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Install Rust
|
||||
run: rustup update stable && rustup default stable && rustup component add rustfmt
|
||||
- run: cargo fmt -- --check
|
||||
|
||||
publish_docs:
|
||||
name: Publish Documentation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Install Rust
|
||||
run: rustup update stable && rustup default stable
|
||||
- name: Build documentation
|
||||
run: cargo doc --no-deps --all-features
|
||||
- name: Publish documentation
|
||||
run: |
|
||||
cd target/doc
|
||||
git init
|
||||
git add .
|
||||
git -c user.name='ci' -c user.email='ci' commit -m init
|
||||
git push -f -q https://git:${{ secrets.github_token }}@github.com/${{ github.repository }} HEAD:gh-pages
|
||||
if: github.event_name == 'push' && github.event.ref == 'refs/heads/master'
|
2
tar-0.4.41/.gitignore
vendored
Normal file
2
tar-0.4.41/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
/Cargo.lock
|
46
tar-0.4.41/Cargo.toml
Normal file
46
tar-0.4.41/Cargo.toml
Normal file
@ -0,0 +1,46 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "tar"
|
||||
version = "0.4.41"
|
||||
authors = ["Alex Crichton <alex@alexcrichton.com>"]
|
||||
exclude = ["tests/archives/*"]
|
||||
description = """
|
||||
A Rust implementation of a TAR file reader and writer. This library does not
|
||||
currently handle compression, but it is abstract over all I/O readers and
|
||||
writers. Additionally, great lengths are taken to ensure that the entire
|
||||
contents are never required to be entirely resident in memory all at once.
|
||||
"""
|
||||
homepage = "https://github.com/alexcrichton/tar-rs"
|
||||
documentation = "https://docs.rs/tar"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"tar",
|
||||
"tarfile",
|
||||
"encoding",
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/alexcrichton/tar-rs"
|
||||
|
||||
[dev-dependencies.tempfile]
|
||||
version = "3"
|
||||
|
||||
[features]
|
||||
default = ["xattr"]
|
||||
|
||||
[target."cfg(unix)".dependencies.libc]
|
||||
version = "0.2"
|
||||
|
||||
[target."cfg(unix)".dependencies.xattr]
|
||||
version = "1.1.3"
|
||||
optional = true
|
32
tar-0.4.41/Cargo.toml.orig
Normal file
32
tar-0.4.41/Cargo.toml.orig
Normal file
@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "tar"
|
||||
version = "0.4.41"
|
||||
authors = ["Alex Crichton <alex@alexcrichton.com>"]
|
||||
homepage = "https://github.com/alexcrichton/tar-rs"
|
||||
repository = "https://github.com/alexcrichton/tar-rs"
|
||||
documentation = "https://docs.rs/tar"
|
||||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["tar", "tarfile", "encoding"]
|
||||
readme = "README.md"
|
||||
edition = "2021"
|
||||
exclude = ["tests/archives/*"]
|
||||
|
||||
description = """
|
||||
A Rust implementation of a TAR file reader and writer. This library does not
|
||||
currently handle compression, but it is abstract over all I/O readers and
|
||||
writers. Additionally, great lengths are taken to ensure that the entire
|
||||
contents are never required to be entirely resident in memory all at once.
|
||||
"""
|
||||
|
||||
[dependencies]
|
||||
filetime = "0.2.8"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
||||
[target."cfg(unix)".dependencies]
|
||||
xattr = { version = "1.1.3", optional = true }
|
||||
libc = "0.2"
|
||||
|
||||
[features]
|
||||
default = ["xattr"]
|
201
tar-0.4.41/LICENSE-APACHE
Normal file
201
tar-0.4.41/LICENSE-APACHE
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
25
tar-0.4.41/LICENSE-MIT
Normal file
25
tar-0.4.41/LICENSE-MIT
Normal file
@ -0,0 +1,25 @@
|
||||
Copyright (c) 2014 Alex Crichton
|
||||
|
||||
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.
|
76
tar-0.4.41/README.md
Normal file
76
tar-0.4.41/README.md
Normal file
@ -0,0 +1,76 @@
|
||||
# tar-rs
|
||||
|
||||
[Documentation](https://docs.rs/tar)
|
||||
|
||||
A tar archive reading/writing library for Rust.
|
||||
|
||||
```toml
|
||||
# Cargo.toml
|
||||
[dependencies]
|
||||
tar = "0.4"
|
||||
```
|
||||
|
||||
## Reading an archive
|
||||
|
||||
```rust,no_run
|
||||
extern crate tar;
|
||||
|
||||
use std::io::prelude::*;
|
||||
use std::fs::File;
|
||||
use tar::Archive;
|
||||
|
||||
fn main() {
|
||||
let file = File::open("foo.tar").unwrap();
|
||||
let mut a = Archive::new(file);
|
||||
|
||||
for file in a.entries().unwrap() {
|
||||
// Make sure there wasn't an I/O error
|
||||
let mut file = file.unwrap();
|
||||
|
||||
// Inspect metadata about the file
|
||||
println!("{:?}", file.header().path().unwrap());
|
||||
println!("{}", file.header().size().unwrap());
|
||||
|
||||
// files implement the Read trait
|
||||
let mut s = String::new();
|
||||
file.read_to_string(&mut s).unwrap();
|
||||
println!("{}", s);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Writing an archive
|
||||
|
||||
```rust,no_run
|
||||
extern crate tar;
|
||||
|
||||
use std::io::prelude::*;
|
||||
use std::fs::File;
|
||||
use tar::Builder;
|
||||
|
||||
fn main() {
|
||||
let file = File::create("foo.tar").unwrap();
|
||||
let mut a = Builder::new(file);
|
||||
|
||||
a.append_path("file1.txt").unwrap();
|
||||
a.append_file("file2.txt", &mut File::open("file3.txt").unwrap()).unwrap();
|
||||
}
|
||||
```
|
||||
|
||||
# License
|
||||
|
||||
This project is licensed under either of
|
||||
|
||||
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||
http://www.apache.org/licenses/LICENSE-2.0)
|
||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
||||
http://opensource.org/licenses/MIT)
|
||||
|
||||
at your option.
|
||||
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in this project by you, as defined in the Apache-2.0 license,
|
||||
shall be dual licensed as above, without any additional terms or conditions.
|
25
tar-0.4.41/examples/extract_file.rs
Normal file
25
tar-0.4.41/examples/extract_file.rs
Normal file
@ -0,0 +1,25 @@
|
||||
//! An example of extracting a file in an archive.
|
||||
//!
|
||||
//! Takes a tarball on standard input, looks for an entry with a listed file
|
||||
//! name as the first argument provided, and then prints the contents of that
|
||||
//! file to stdout.
|
||||
|
||||
extern crate tar;
|
||||
|
||||
use std::env::args_os;
|
||||
use std::io::{copy, stdin, stdout};
|
||||
use std::path::Path;
|
||||
|
||||
use tar::Archive;
|
||||
|
||||
fn main() {
|
||||
let first_arg = args_os().skip(1).next().unwrap();
|
||||
let filename = Path::new(&first_arg);
|
||||
let mut ar = Archive::new(stdin());
|
||||
for file in ar.entries().unwrap() {
|
||||
let mut f = file.unwrap();
|
||||
if f.path().unwrap() == filename {
|
||||
copy(&mut f, &mut stdout()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
17
tar-0.4.41/examples/list.rs
Normal file
17
tar-0.4.41/examples/list.rs
Normal file
@ -0,0 +1,17 @@
|
||||
//! An example of listing the file names of entries in an archive.
|
||||
//!
|
||||
//! Takes a tarball on stdin and prints out all of the entries inside.
|
||||
|
||||
extern crate tar;
|
||||
|
||||
use std::io::stdin;
|
||||
|
||||
use tar::Archive;
|
||||
|
||||
fn main() {
|
||||
let mut ar = Archive::new(stdin());
|
||||
for file in ar.entries().unwrap() {
|
||||
let f = file.unwrap();
|
||||
println!("{}", f.path().unwrap().display());
|
||||
}
|
||||
}
|
48
tar-0.4.41/examples/raw_list.rs
Normal file
48
tar-0.4.41/examples/raw_list.rs
Normal file
@ -0,0 +1,48 @@
|
||||
//! An example of listing raw entries in an archive.
|
||||
//!
|
||||
//! Takes a tarball on stdin and prints out all of the entries inside.
|
||||
|
||||
extern crate tar;
|
||||
|
||||
use std::io::stdin;
|
||||
|
||||
use tar::Archive;
|
||||
|
||||
fn main() {
|
||||
let mut ar = Archive::new(stdin());
|
||||
for (i, file) in ar.entries().unwrap().raw(true).enumerate() {
|
||||
println!("-------------------------- Entry {}", i);
|
||||
let mut f = file.unwrap();
|
||||
println!("path: {}", f.path().unwrap().display());
|
||||
println!("size: {}", f.header().size().unwrap());
|
||||
println!("entry size: {}", f.header().entry_size().unwrap());
|
||||
println!("link name: {:?}", f.link_name().unwrap());
|
||||
println!("file type: {:#x}", f.header().entry_type().as_byte());
|
||||
println!("mode: {:#o}", f.header().mode().unwrap());
|
||||
println!("uid: {}", f.header().uid().unwrap());
|
||||
println!("gid: {}", f.header().gid().unwrap());
|
||||
println!("mtime: {}", f.header().mtime().unwrap());
|
||||
println!("username: {:?}", f.header().username().unwrap());
|
||||
println!("groupname: {:?}", f.header().groupname().unwrap());
|
||||
|
||||
if f.header().as_ustar().is_some() {
|
||||
println!("kind: UStar");
|
||||
} else if f.header().as_gnu().is_some() {
|
||||
println!("kind: GNU");
|
||||
} else {
|
||||
println!("kind: normal");
|
||||
}
|
||||
|
||||
if let Ok(Some(extensions)) = f.pax_extensions() {
|
||||
println!("pax extensions:");
|
||||
for e in extensions {
|
||||
let e = e.unwrap();
|
||||
println!(
|
||||
"\t{:?} = {:?}",
|
||||
String::from_utf8_lossy(e.key_bytes()),
|
||||
String::from_utf8_lossy(e.value_bytes())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
13
tar-0.4.41/examples/write.rs
Normal file
13
tar-0.4.41/examples/write.rs
Normal file
@ -0,0 +1,13 @@
|
||||
extern crate tar;
|
||||
|
||||
use std::fs::File;
|
||||
use tar::Builder;
|
||||
|
||||
fn main() {
|
||||
let file = File::create("foo.tar").unwrap();
|
||||
let mut a = Builder::new(file);
|
||||
|
||||
a.append_path("README.md").unwrap();
|
||||
a.append_file("lib.rs", &mut File::open("src/lib.rs").unwrap())
|
||||
.unwrap();
|
||||
}
|
559
tar-0.4.41/src/archive.rs
Normal file
559
tar-0.4.41/src/archive.rs
Normal file
@ -0,0 +1,559 @@
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::cmp;
|
||||
use std::convert::TryFrom;
|
||||
use std::fs;
|
||||
use std::io::prelude::*;
|
||||
use std::io::{self, SeekFrom};
|
||||
use std::marker;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::entry::{EntryFields, EntryIo};
|
||||
use crate::error::TarError;
|
||||
use crate::other;
|
||||
use crate::pax::*;
|
||||
use crate::{Entry, GnuExtSparseHeader, GnuSparseHeader, Header};
|
||||
|
||||
/// A top-level representation of an archive file.
|
||||
///
|
||||
/// This archive can have an entry added to it and it can be iterated over.
|
||||
pub struct Archive<R: ?Sized + Read> {
|
||||
inner: ArchiveInner<R>,
|
||||
}
|
||||
|
||||
pub struct ArchiveInner<R: ?Sized> {
|
||||
pos: Cell<u64>,
|
||||
mask: u32,
|
||||
unpack_xattrs: bool,
|
||||
preserve_permissions: bool,
|
||||
preserve_ownerships: bool,
|
||||
preserve_mtime: bool,
|
||||
overwrite: bool,
|
||||
ignore_zeros: bool,
|
||||
obj: RefCell<R>,
|
||||
}
|
||||
|
||||
/// An iterator over the entries of an archive.
|
||||
pub struct Entries<'a, R: 'a + Read> {
|
||||
fields: EntriesFields<'a>,
|
||||
_ignored: marker::PhantomData<&'a Archive<R>>,
|
||||
}
|
||||
|
||||
trait SeekRead: Read + Seek {}
|
||||
impl<R: Read + Seek> SeekRead for R {}
|
||||
|
||||
struct EntriesFields<'a> {
|
||||
archive: &'a Archive<dyn Read + 'a>,
|
||||
seekable_archive: Option<&'a Archive<dyn SeekRead + 'a>>,
|
||||
next: u64,
|
||||
done: bool,
|
||||
raw: bool,
|
||||
}
|
||||
|
||||
impl<R: Read> Archive<R> {
|
||||
/// Create a new archive with the underlying object as the reader.
|
||||
pub fn new(obj: R) -> Archive<R> {
|
||||
Archive {
|
||||
inner: ArchiveInner {
|
||||
mask: u32::MIN,
|
||||
unpack_xattrs: false,
|
||||
preserve_permissions: false,
|
||||
preserve_ownerships: false,
|
||||
preserve_mtime: true,
|
||||
overwrite: true,
|
||||
ignore_zeros: false,
|
||||
obj: RefCell::new(obj),
|
||||
pos: Cell::new(0),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Unwrap this archive, returning the underlying object.
|
||||
pub fn into_inner(self) -> R {
|
||||
self.inner.obj.into_inner()
|
||||
}
|
||||
|
||||
/// Construct an iterator over the entries in this archive.
|
||||
///
|
||||
/// Note that care must be taken to consider each entry within an archive in
|
||||
/// sequence. If entries are processed out of sequence (from what the
|
||||
/// iterator returns), then the contents read for each entry may be
|
||||
/// corrupted.
|
||||
pub fn entries(&mut self) -> io::Result<Entries<R>> {
|
||||
let me: &mut Archive<dyn Read> = self;
|
||||
me._entries(None).map(|fields| Entries {
|
||||
fields: fields,
|
||||
_ignored: marker::PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Set the mask of the permission bits when unpacking this entry.
|
||||
///
|
||||
/// The mask will be inverted when applying against a mode, similar to how
|
||||
/// `umask` works on Unix. In logical notation it looks like:
|
||||
///
|
||||
/// ```text
|
||||
/// new_mode = old_mode & (~mask)
|
||||
/// ```
|
||||
///
|
||||
/// The mask is 0 by default and is currently only implemented on Unix.
|
||||
pub fn set_mask(&mut self, mask: u32) {
|
||||
self.inner.mask = mask;
|
||||
}
|
||||
|
||||
/// Indicate whether extended file attributes (xattrs on Unix) are preserved
|
||||
/// when unpacking this archive.
|
||||
///
|
||||
/// This flag is disabled by default and is currently only implemented on
|
||||
/// Unix using xattr support. This may eventually be implemented for
|
||||
/// Windows, however, if other archive implementations are found which do
|
||||
/// this as well.
|
||||
pub fn set_unpack_xattrs(&mut self, unpack_xattrs: bool) {
|
||||
self.inner.unpack_xattrs = unpack_xattrs;
|
||||
}
|
||||
|
||||
/// Indicate whether extended permissions (like suid on Unix) are preserved
|
||||
/// when unpacking this entry.
|
||||
///
|
||||
/// This flag is disabled by default and is currently only implemented on
|
||||
/// Unix.
|
||||
pub fn set_preserve_permissions(&mut self, preserve: bool) {
|
||||
self.inner.preserve_permissions = preserve;
|
||||
}
|
||||
|
||||
/// Indicate whether numeric ownership ids (like uid and gid on Unix)
|
||||
/// are preserved when unpacking this entry.
|
||||
///
|
||||
/// This flag is disabled by default and is currently only implemented on
|
||||
/// Unix.
|
||||
pub fn set_preserve_ownerships(&mut self, preserve: bool) {
|
||||
self.inner.preserve_ownerships = preserve;
|
||||
}
|
||||
|
||||
/// Indicate whether files and symlinks should be overwritten on extraction.
|
||||
pub fn set_overwrite(&mut self, overwrite: bool) {
|
||||
self.inner.overwrite = overwrite;
|
||||
}
|
||||
|
||||
/// Indicate whether access time information is preserved when unpacking
|
||||
/// this entry.
|
||||
///
|
||||
/// This flag is enabled by default.
|
||||
pub fn set_preserve_mtime(&mut self, preserve: bool) {
|
||||
self.inner.preserve_mtime = preserve;
|
||||
}
|
||||
|
||||
/// Ignore zeroed headers, which would otherwise indicate to the archive that it has no more
|
||||
/// entries.
|
||||
///
|
||||
/// This can be used in case multiple tar archives have been concatenated together.
|
||||
pub fn set_ignore_zeros(&mut self, ignore_zeros: bool) {
|
||||
self.inner.ignore_zeros = ignore_zeros;
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Seek + Read> Archive<R> {
|
||||
/// Construct an iterator over the entries in this archive for a seekable
|
||||
/// reader. Seek will be used to efficiently skip over file contents.
|
||||
///
|
||||
/// Note that care must be taken to consider each entry within an archive in
|
||||
/// sequence. If entries are processed out of sequence (from what the
|
||||
/// iterator returns), then the contents read for each entry may be
|
||||
/// corrupted.
|
||||
pub fn entries_with_seek(&mut self) -> io::Result<Entries<R>> {
|
||||
let me: &Archive<dyn Read> = self;
|
||||
let me_seekable: &Archive<dyn SeekRead> = self;
|
||||
me._entries(Some(me_seekable)).map(|fields| Entries {
|
||||
fields: fields,
|
||||
_ignored: marker::PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Archive<dyn Read + '_> {
|
||||
fn _entries<'a>(
|
||||
&'a self,
|
||||
seekable_archive: Option<&'a Archive<dyn SeekRead + 'a>>,
|
||||
) -> io::Result<EntriesFields<'a>> {
|
||||
if self.inner.pos.get() != 0 {
|
||||
return Err(other(
|
||||
"cannot call entries unless archive is at \
|
||||
position 0",
|
||||
));
|
||||
}
|
||||
Ok(EntriesFields {
|
||||
archive: self,
|
||||
seekable_archive,
|
||||
done: false,
|
||||
next: 0,
|
||||
raw: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, R: Read> Entries<'a, R> {
|
||||
/// Indicates whether this iterator will return raw entries or not.
|
||||
///
|
||||
/// If the raw list of entries are returned, then no preprocessing happens
|
||||
/// on account of this library, for example taking into account GNU long name
|
||||
/// or long link archive members. Raw iteration is disabled by default.
|
||||
pub fn raw(self, raw: bool) -> Entries<'a, R> {
|
||||
Entries {
|
||||
fields: EntriesFields {
|
||||
raw: raw,
|
||||
..self.fields
|
||||
},
|
||||
_ignored: marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'a, R: Read> Iterator for Entries<'a, R> {
|
||||
type Item = io::Result<Entry<'a, R>>;
|
||||
|
||||
fn next(&mut self) -> Option<io::Result<Entry<'a, R>>> {
|
||||
self.fields
|
||||
.next()
|
||||
.map(|result| result.map(|e| EntryFields::from(e).into_entry()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EntriesFields<'a> {
|
||||
fn next_entry_raw(
|
||||
&mut self,
|
||||
pax_extensions: Option<&[u8]>,
|
||||
) -> io::Result<Option<Entry<'a, io::Empty>>> {
|
||||
let mut header = Header::new_old();
|
||||
let mut header_pos = self.next;
|
||||
loop {
|
||||
// Seek to the start of the next header in the archive
|
||||
let delta = self.next - self.archive.inner.pos.get();
|
||||
self.skip(delta)?;
|
||||
|
||||
// EOF is an indicator that we are at the end of the archive.
|
||||
if !try_read_all(&mut &self.archive.inner, header.as_mut_bytes())? {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// If a header is not all zeros, we have another valid header.
|
||||
// Otherwise, check if we are ignoring zeros and continue, or break as if this is the
|
||||
// end of the archive.
|
||||
if !header.as_bytes().iter().all(|i| *i == 0) {
|
||||
self.next += 512;
|
||||
break;
|
||||
}
|
||||
|
||||
if !self.archive.inner.ignore_zeros {
|
||||
return Ok(None);
|
||||
}
|
||||
self.next += 512;
|
||||
header_pos = self.next;
|
||||
}
|
||||
|
||||
// Make sure the checksum is ok
|
||||
let sum = header.as_bytes()[..148]
|
||||
.iter()
|
||||
.chain(&header.as_bytes()[156..])
|
||||
.fold(0, |a, b| a + (*b as u32))
|
||||
+ 8 * 32;
|
||||
let cksum = header.cksum()?;
|
||||
if sum != cksum {
|
||||
return Err(other("archive header checksum mismatch"));
|
||||
}
|
||||
|
||||
let mut pax_size: Option<u64> = None;
|
||||
if let Some(pax_extensions_ref) = &pax_extensions {
|
||||
pax_size = pax_extensions_value(pax_extensions_ref, PAX_SIZE);
|
||||
|
||||
if let Some(pax_uid) = pax_extensions_value(pax_extensions_ref, PAX_UID) {
|
||||
header.set_uid(pax_uid);
|
||||
}
|
||||
|
||||
if let Some(pax_gid) = pax_extensions_value(pax_extensions_ref, PAX_GID) {
|
||||
header.set_gid(pax_gid);
|
||||
}
|
||||
}
|
||||
|
||||
let file_pos = self.next;
|
||||
let mut size = header.entry_size()?;
|
||||
if size == 0 {
|
||||
if let Some(pax_size) = pax_size {
|
||||
size = pax_size;
|
||||
}
|
||||
}
|
||||
let ret = EntryFields {
|
||||
size: size,
|
||||
header_pos: header_pos,
|
||||
file_pos: file_pos,
|
||||
data: vec![EntryIo::Data((&self.archive.inner).take(size))],
|
||||
header: header,
|
||||
long_pathname: None,
|
||||
long_linkname: None,
|
||||
pax_extensions: None,
|
||||
mask: self.archive.inner.mask,
|
||||
unpack_xattrs: self.archive.inner.unpack_xattrs,
|
||||
preserve_permissions: self.archive.inner.preserve_permissions,
|
||||
preserve_mtime: self.archive.inner.preserve_mtime,
|
||||
overwrite: self.archive.inner.overwrite,
|
||||
preserve_ownerships: self.archive.inner.preserve_ownerships,
|
||||
};
|
||||
|
||||
// Store where the next entry is, rounding up by 512 bytes (the size of
|
||||
// a header);
|
||||
let size = size
|
||||
.checked_add(511)
|
||||
.ok_or_else(|| other("size overflow"))?;
|
||||
self.next = self
|
||||
.next
|
||||
.checked_add(size & !(512 - 1))
|
||||
.ok_or_else(|| other("size overflow"))?;
|
||||
|
||||
Ok(Some(ret.into_entry()))
|
||||
}
|
||||
|
||||
fn next_entry(&mut self) -> io::Result<Option<Entry<'a, io::Empty>>> {
|
||||
if self.raw {
|
||||
return self.next_entry_raw(None);
|
||||
}
|
||||
|
||||
let mut gnu_longname = None;
|
||||
let mut gnu_longlink = None;
|
||||
let mut pax_extensions = None;
|
||||
let mut processed = 0;
|
||||
loop {
|
||||
processed += 1;
|
||||
let entry = match self.next_entry_raw(pax_extensions.as_deref())? {
|
||||
Some(entry) => entry,
|
||||
None if processed > 1 => {
|
||||
return Err(other(
|
||||
"members found describing a future member \
|
||||
but no future member found",
|
||||
));
|
||||
}
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let is_recognized_header =
|
||||
entry.header().as_gnu().is_some() || entry.header().as_ustar().is_some();
|
||||
|
||||
if is_recognized_header && entry.header().entry_type().is_gnu_longname() {
|
||||
if gnu_longname.is_some() {
|
||||
return Err(other(
|
||||
"two long name entries describing \
|
||||
the same member",
|
||||
));
|
||||
}
|
||||
gnu_longname = Some(EntryFields::from(entry).read_all()?);
|
||||
continue;
|
||||
}
|
||||
|
||||
if is_recognized_header && entry.header().entry_type().is_gnu_longlink() {
|
||||
if gnu_longlink.is_some() {
|
||||
return Err(other(
|
||||
"two long name entries describing \
|
||||
the same member",
|
||||
));
|
||||
}
|
||||
gnu_longlink = Some(EntryFields::from(entry).read_all()?);
|
||||
continue;
|
||||
}
|
||||
|
||||
if is_recognized_header && entry.header().entry_type().is_pax_local_extensions() {
|
||||
if pax_extensions.is_some() {
|
||||
return Err(other(
|
||||
"two pax extensions entries describing \
|
||||
the same member",
|
||||
));
|
||||
}
|
||||
pax_extensions = Some(EntryFields::from(entry).read_all()?);
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut fields = EntryFields::from(entry);
|
||||
fields.long_pathname = gnu_longname;
|
||||
fields.long_linkname = gnu_longlink;
|
||||
fields.pax_extensions = pax_extensions;
|
||||
self.parse_sparse_header(&mut fields)?;
|
||||
return Ok(Some(fields.into_entry()));
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_sparse_header(&mut self, entry: &mut EntryFields<'a>) -> io::Result<()> {
|
||||
if !entry.header.entry_type().is_gnu_sparse() {
|
||||
return Ok(());
|
||||
}
|
||||
let gnu = match entry.header.as_gnu() {
|
||||
Some(gnu) => gnu,
|
||||
None => return Err(other("sparse entry type listed but not GNU header")),
|
||||
};
|
||||
|
||||
// Sparse files are represented internally as a list of blocks that are
|
||||
// read. Blocks are either a bunch of 0's or they're data from the
|
||||
// underlying archive.
|
||||
//
|
||||
// Blocks of a sparse file are described by the `GnuSparseHeader`
|
||||
// structure, some of which are contained in `GnuHeader` but some of
|
||||
// which may also be contained after the first header in further
|
||||
// headers.
|
||||
//
|
||||
// We read off all the blocks here and use the `add_block` function to
|
||||
// incrementally add them to the list of I/O block (in `entry.data`).
|
||||
// The `add_block` function also validates that each chunk comes after
|
||||
// the previous, we don't overrun the end of the file, and each block is
|
||||
// aligned to a 512-byte boundary in the archive itself.
|
||||
//
|
||||
// At the end we verify that the sparse file size (`Header::size`) is
|
||||
// the same as the current offset (described by the list of blocks) as
|
||||
// well as the amount of data read equals the size of the entry
|
||||
// (`Header::entry_size`).
|
||||
entry.data.truncate(0);
|
||||
|
||||
let mut cur = 0;
|
||||
let mut remaining = entry.size;
|
||||
{
|
||||
let data = &mut entry.data;
|
||||
let reader = &self.archive.inner;
|
||||
let size = entry.size;
|
||||
let mut add_block = |block: &GnuSparseHeader| -> io::Result<_> {
|
||||
if block.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let off = block.offset()?;
|
||||
let len = block.length()?;
|
||||
if len != 0 && (size - remaining) % 512 != 0 {
|
||||
return Err(other(
|
||||
"previous block in sparse file was not \
|
||||
aligned to 512-byte boundary",
|
||||
));
|
||||
} else if off < cur {
|
||||
return Err(other(
|
||||
"out of order or overlapping sparse \
|
||||
blocks",
|
||||
));
|
||||
} else if cur < off {
|
||||
let block = io::repeat(0).take(off - cur);
|
||||
data.push(EntryIo::Pad(block));
|
||||
}
|
||||
cur = off
|
||||
.checked_add(len)
|
||||
.ok_or_else(|| other("more bytes listed in sparse file than u64 can hold"))?;
|
||||
remaining = remaining.checked_sub(len).ok_or_else(|| {
|
||||
other(
|
||||
"sparse file consumed more data than the header \
|
||||
listed",
|
||||
)
|
||||
})?;
|
||||
data.push(EntryIo::Data(reader.take(len)));
|
||||
Ok(())
|
||||
};
|
||||
for block in gnu.sparse.iter() {
|
||||
add_block(block)?
|
||||
}
|
||||
if gnu.is_extended() {
|
||||
let mut ext = GnuExtSparseHeader::new();
|
||||
ext.isextended[0] = 1;
|
||||
while ext.is_extended() {
|
||||
if !try_read_all(&mut &self.archive.inner, ext.as_mut_bytes())? {
|
||||
return Err(other("failed to read extension"));
|
||||
}
|
||||
|
||||
self.next += 512;
|
||||
for block in ext.sparse.iter() {
|
||||
add_block(block)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if cur != gnu.real_size()? {
|
||||
return Err(other(
|
||||
"mismatch in sparse file chunks and \
|
||||
size in header",
|
||||
));
|
||||
}
|
||||
entry.size = cur;
|
||||
if remaining > 0 {
|
||||
return Err(other(
|
||||
"mismatch in sparse file chunks and \
|
||||
entry size in header",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn skip(&mut self, mut amt: u64) -> io::Result<()> {
|
||||
if let Some(seekable_archive) = self.seekable_archive {
|
||||
let pos = io::SeekFrom::Current(
|
||||
i64::try_from(amt).map_err(|_| other("seek position out of bounds"))?,
|
||||
);
|
||||
(&seekable_archive.inner).seek(pos)?;
|
||||
} else {
|
||||
let mut buf = [0u8; 4096 * 8];
|
||||
while amt > 0 {
|
||||
let n = cmp::min(amt, buf.len() as u64);
|
||||
let n = (&self.archive.inner).read(&mut buf[..n as usize])?;
|
||||
if n == 0 {
|
||||
return Err(other("unexpected EOF during skip"));
|
||||
}
|
||||
amt -= n as u64;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for EntriesFields<'a> {
|
||||
type Item = io::Result<Entry<'a, io::Empty>>;
|
||||
|
||||
fn next(&mut self) -> Option<io::Result<Entry<'a, io::Empty>>> {
|
||||
if self.done {
|
||||
None
|
||||
} else {
|
||||
match self.next_entry() {
|
||||
Ok(Some(e)) => Some(Ok(e)),
|
||||
Ok(None) => {
|
||||
self.done = true;
|
||||
None
|
||||
}
|
||||
Err(e) => {
|
||||
self.done = true;
|
||||
Some(Err(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, R: ?Sized + Read> Read for &'a ArchiveInner<R> {
|
||||
fn read(&mut self, into: &mut [u8]) -> io::Result<usize> {
|
||||
let i = self.obj.borrow_mut().read(into)?;
|
||||
self.pos.set(self.pos.get() + i as u64);
|
||||
Ok(i)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, R: ?Sized + Seek> Seek for &'a ArchiveInner<R> {
|
||||
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
||||
let pos = self.obj.borrow_mut().seek(pos)?;
|
||||
self.pos.set(pos);
|
||||
Ok(pos)
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to fill the buffer from the reader.
|
||||
///
|
||||
/// If the reader reaches its end before filling the buffer at all, returns `false`.
|
||||
/// Otherwise returns `true`.
|
||||
fn try_read_all<R: Read>(r: &mut R, buf: &mut [u8]) -> io::Result<bool> {
|
||||
let mut read = 0;
|
||||
while read < buf.len() {
|
||||
match r.read(&mut buf[read..])? {
|
||||
0 => {
|
||||
if read == 0 {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
return Err(other("failed to read entire block"));
|
||||
}
|
||||
n => read += n,
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
663
tar-0.4.41/src/builder.rs
Normal file
663
tar-0.4.41/src/builder.rs
Normal file
@ -0,0 +1,663 @@
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::io::prelude::*;
|
||||
use std::path::Path;
|
||||
use std::str;
|
||||
|
||||
use crate::header::{path2bytes, HeaderMode};
|
||||
use crate::{other, EntryType, Header};
|
||||
|
||||
/// A structure for building archives
|
||||
///
|
||||
/// This structure has methods for building up an archive from scratch into any
|
||||
/// arbitrary writer.
|
||||
pub struct Builder<W: Write> {
|
||||
mode: HeaderMode,
|
||||
follow: bool,
|
||||
finished: bool,
|
||||
obj: Option<W>,
|
||||
}
|
||||
|
||||
impl<W: Write> Builder<W> {
|
||||
/// Create a new archive builder with the underlying object as the
|
||||
/// destination of all data written. The builder will use
|
||||
/// `HeaderMode::Complete` by default.
|
||||
pub fn new(obj: W) -> Builder<W> {
|
||||
Builder {
|
||||
mode: HeaderMode::Complete,
|
||||
follow: true,
|
||||
finished: false,
|
||||
obj: Some(obj),
|
||||
}
|
||||
}
|
||||
|
||||
/// Changes the HeaderMode that will be used when reading fs Metadata for
|
||||
/// methods that implicitly read metadata for an input Path. Notably, this
|
||||
/// does _not_ apply to `append(Header)`.
|
||||
pub fn mode(&mut self, mode: HeaderMode) {
|
||||
self.mode = mode;
|
||||
}
|
||||
|
||||
/// Follow symlinks, archiving the contents of the file they point to rather
|
||||
/// than adding a symlink to the archive. Defaults to true.
|
||||
pub fn follow_symlinks(&mut self, follow: bool) {
|
||||
self.follow = follow;
|
||||
}
|
||||
|
||||
/// Gets shared reference to the underlying object.
|
||||
pub fn get_ref(&self) -> &W {
|
||||
self.obj.as_ref().unwrap()
|
||||
}
|
||||
|
||||
/// Gets mutable reference to the underlying object.
|
||||
///
|
||||
/// Note that care must be taken while writing to the underlying
|
||||
/// object. But, e.g. `get_mut().flush()` is claimed to be safe and
|
||||
/// useful in the situations when one needs to be ensured that
|
||||
/// tar entry was flushed to the disk.
|
||||
pub fn get_mut(&mut self) -> &mut W {
|
||||
self.obj.as_mut().unwrap()
|
||||
}
|
||||
|
||||
/// Unwrap this archive, returning the underlying object.
|
||||
///
|
||||
/// This function will finish writing the archive if the `finish` function
|
||||
/// hasn't yet been called, returning any I/O error which happens during
|
||||
/// that operation.
|
||||
pub fn into_inner(mut self) -> io::Result<W> {
|
||||
if !self.finished {
|
||||
self.finish()?;
|
||||
}
|
||||
Ok(self.obj.take().unwrap())
|
||||
}
|
||||
|
||||
/// Adds a new entry to this archive.
|
||||
///
|
||||
/// This function will append the header specified, followed by contents of
|
||||
/// the stream specified by `data`. To produce a valid archive the `size`
|
||||
/// field of `header` must be the same as the length of the stream that's
|
||||
/// being written. Additionally the checksum for the header should have been
|
||||
/// set via the `set_cksum` method.
|
||||
///
|
||||
/// Note that this will not attempt to seek the archive to a valid position,
|
||||
/// so if the archive is in the middle of a read or some other similar
|
||||
/// operation then this may corrupt the archive.
|
||||
///
|
||||
/// Also note that after all entries have been written to an archive the
|
||||
/// `finish` function needs to be called to finish writing the archive.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error for any intermittent I/O error which
|
||||
/// occurs when either reading or writing.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tar::{Builder, Header};
|
||||
///
|
||||
/// let mut header = Header::new_gnu();
|
||||
/// header.set_path("foo").unwrap();
|
||||
/// header.set_size(4);
|
||||
/// header.set_cksum();
|
||||
///
|
||||
/// let mut data: &[u8] = &[1, 2, 3, 4];
|
||||
///
|
||||
/// let mut ar = Builder::new(Vec::new());
|
||||
/// ar.append(&header, data).unwrap();
|
||||
/// let data = ar.into_inner().unwrap();
|
||||
/// ```
|
||||
pub fn append<R: Read>(&mut self, header: &Header, mut data: R) -> io::Result<()> {
|
||||
append(self.get_mut(), header, &mut data)
|
||||
}
|
||||
|
||||
/// Adds a new entry to this archive with the specified path.
|
||||
///
|
||||
/// This function will set the specified path in the given header, which may
|
||||
/// require appending a GNU long-name extension entry to the archive first.
|
||||
/// The checksum for the header will be automatically updated via the
|
||||
/// `set_cksum` method after setting the path. No other metadata in the
|
||||
/// header will be modified.
|
||||
///
|
||||
/// Then it will append the header, followed by contents of the stream
|
||||
/// specified by `data`. To produce a valid archive the `size` field of
|
||||
/// `header` must be the same as the length of the stream that's being
|
||||
/// written.
|
||||
///
|
||||
/// Note that this will not attempt to seek the archive to a valid position,
|
||||
/// so if the archive is in the middle of a read or some other similar
|
||||
/// operation then this may corrupt the archive.
|
||||
///
|
||||
/// Also note that after all entries have been written to an archive the
|
||||
/// `finish` function needs to be called to finish writing the archive.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error for any intermittent I/O error which
|
||||
/// occurs when either reading or writing.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tar::{Builder, Header};
|
||||
///
|
||||
/// let mut header = Header::new_gnu();
|
||||
/// header.set_size(4);
|
||||
/// header.set_cksum();
|
||||
///
|
||||
/// let mut data: &[u8] = &[1, 2, 3, 4];
|
||||
///
|
||||
/// let mut ar = Builder::new(Vec::new());
|
||||
/// ar.append_data(&mut header, "really/long/path/to/foo", data).unwrap();
|
||||
/// let data = ar.into_inner().unwrap();
|
||||
/// ```
|
||||
pub fn append_data<P: AsRef<Path>, R: Read>(
|
||||
&mut self,
|
||||
header: &mut Header,
|
||||
path: P,
|
||||
data: R,
|
||||
) -> io::Result<()> {
|
||||
prepare_header_path(self.get_mut(), header, path.as_ref())?;
|
||||
header.set_cksum();
|
||||
self.append(&header, data)
|
||||
}
|
||||
|
||||
/// Adds a new link (symbolic or hard) entry to this archive with the specified path and target.
|
||||
///
|
||||
/// This function is similar to [`Self::append_data`] which supports long filenames,
|
||||
/// but also supports long link targets using GNU extensions if necessary.
|
||||
/// You must set the entry type to either [`EntryType::Link`] or [`EntryType::Symlink`].
|
||||
/// The `set_cksum` method will be invoked after setting the path. No other metadata in the
|
||||
/// header will be modified.
|
||||
///
|
||||
/// If you are intending to use GNU extensions, you must use this method over calling
|
||||
/// [`Header::set_link_name`] because that function will fail on long links.
|
||||
///
|
||||
/// Similar constraints around the position of the archive and completion
|
||||
/// apply as with [`Self::append_data`].
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error for any intermittent I/O error which
|
||||
/// occurs when either reading or writing.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tar::{Builder, Header, EntryType};
|
||||
///
|
||||
/// let mut ar = Builder::new(Vec::new());
|
||||
/// let mut header = Header::new_gnu();
|
||||
/// header.set_username("foo");
|
||||
/// header.set_entry_type(EntryType::Symlink);
|
||||
/// header.set_size(0);
|
||||
/// ar.append_link(&mut header, "really/long/path/to/foo", "other/really/long/target").unwrap();
|
||||
/// let data = ar.into_inner().unwrap();
|
||||
/// ```
|
||||
pub fn append_link<P: AsRef<Path>, T: AsRef<Path>>(
|
||||
&mut self,
|
||||
header: &mut Header,
|
||||
path: P,
|
||||
target: T,
|
||||
) -> io::Result<()> {
|
||||
self._append_link(header, path.as_ref(), target.as_ref())
|
||||
}
|
||||
|
||||
fn _append_link(&mut self, header: &mut Header, path: &Path, target: &Path) -> io::Result<()> {
|
||||
prepare_header_path(self.get_mut(), header, path)?;
|
||||
prepare_header_link(self.get_mut(), header, target)?;
|
||||
header.set_cksum();
|
||||
self.append(&header, std::io::empty())
|
||||
}
|
||||
|
||||
// /// Adds a file on the local filesystem to this archive.
|
||||
// ///
|
||||
// /// This function will open the file specified by `path` and insert the file
|
||||
// /// into the archive with the appropriate metadata set, returning any I/O
|
||||
// /// error which occurs while writing. The path name for the file inside of
|
||||
// /// this archive will be the same as `path`, and it is required that the
|
||||
// /// path is a relative path.
|
||||
// ///
|
||||
// /// Note that this will not attempt to seek the archive to a valid position,
|
||||
// /// so if the archive is in the middle of a read or some other similar
|
||||
// /// operation then this may corrupt the archive.
|
||||
// ///
|
||||
// /// Also note that after all files have been written to an archive the
|
||||
// /// `finish` function needs to be called to finish writing the archive.
|
||||
// ///
|
||||
// /// # Examples
|
||||
// ///
|
||||
// /// ```no_run
|
||||
// /// use tar::Builder;
|
||||
// ///
|
||||
// /// let mut ar = Builder::new(Vec::new());
|
||||
// ///
|
||||
// /// ar.append_path("foo/bar.txt").unwrap();
|
||||
// /// ```
|
||||
// pub fn append_path<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
||||
// let mode = self.mode.clone();
|
||||
// let follow = self.follow;
|
||||
// append_path_with_name(self.get_mut(), path.as_ref(), None, mode, follow)
|
||||
// }
|
||||
//
|
||||
// /// Adds a file on the local filesystem to this archive under another name.
|
||||
// ///
|
||||
// /// This function will open the file specified by `path` and insert the file
|
||||
// /// into the archive as `name` with appropriate metadata set, returning any
|
||||
// /// I/O error which occurs while writing. The path name for the file inside
|
||||
// /// of this archive will be `name` is required to be a relative path.
|
||||
// ///
|
||||
// /// Note that this will not attempt to seek the archive to a valid position,
|
||||
// /// so if the archive is in the middle of a read or some other similar
|
||||
// /// operation then this may corrupt the archive.
|
||||
// ///
|
||||
// /// Note if the `path` is a directory. This will just add an entry to the archive,
|
||||
// /// rather than contents of the directory.
|
||||
// ///
|
||||
// /// Also note that after all files have been written to an archive the
|
||||
// /// `finish` function needs to be called to finish writing the archive.
|
||||
// ///
|
||||
// /// # Examples
|
||||
// ///
|
||||
// /// ```no_run
|
||||
// /// use tar::Builder;
|
||||
// ///
|
||||
// /// let mut ar = Builder::new(Vec::new());
|
||||
// ///
|
||||
// /// // Insert the local file "foo/bar.txt" in the archive but with the name
|
||||
// /// // "bar/foo.txt".
|
||||
// /// ar.append_path_with_name("foo/bar.txt", "bar/foo.txt").unwrap();
|
||||
// /// ```
|
||||
// pub fn append_path_with_name<P: AsRef<Path>, N: AsRef<Path>>(
|
||||
// &mut self,
|
||||
// path: P,
|
||||
// name: N,
|
||||
// ) -> io::Result<()> {
|
||||
// let mode = self.mode.clone();
|
||||
// let follow = self.follow;
|
||||
// append_path_with_name(
|
||||
// self.get_mut(),
|
||||
// path.as_ref(),
|
||||
// Some(name.as_ref()),
|
||||
// mode,
|
||||
// follow,
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// /// Adds a file to this archive with the given path as the name of the file
|
||||
// /// in the archive.
|
||||
// ///
|
||||
// /// This will use the metadata of `file` to populate a `Header`, and it will
|
||||
// /// then append the file to the archive with the name `path`.
|
||||
// ///
|
||||
// /// Note that this will not attempt to seek the archive to a valid position,
|
||||
// /// so if the archive is in the middle of a read or some other similar
|
||||
// /// operation then this may corrupt the archive.
|
||||
// ///
|
||||
// /// Also note that after all files have been written to an archive the
|
||||
// /// `finish` function needs to be called to finish writing the archive.
|
||||
// ///
|
||||
// /// # Examples
|
||||
// ///
|
||||
// /// ```no_run
|
||||
// /// use std::fs::File;
|
||||
// /// use tar::Builder;
|
||||
// ///
|
||||
// /// let mut ar = Builder::new(Vec::new());
|
||||
// ///
|
||||
// /// // Open the file at one location, but insert it into the archive with a
|
||||
// /// // different name.
|
||||
// /// let mut f = File::open("foo/bar/baz.txt").unwrap();
|
||||
// /// ar.append_file("bar/baz.txt", &mut f).unwrap();
|
||||
// /// ```
|
||||
// pub fn append_file<P: AsRef<Path>>(&mut self, path: P, file: &mut fs::File) -> io::Result<()> {
|
||||
// let mode = self.mode.clone();
|
||||
// append_file(self.get_mut(), path.as_ref(), file, mode)
|
||||
// }
|
||||
|
||||
// /// Adds a directory to this archive with the given path as the name of the
|
||||
// /// directory in the archive.
|
||||
// ///
|
||||
// /// This will use `stat` to populate a `Header`, and it will then append the
|
||||
// /// directory to the archive with the name `path`.
|
||||
// ///
|
||||
// /// Note that this will not attempt to seek the archive to a valid position,
|
||||
// /// so if the archive is in the middle of a read or some other similar
|
||||
// /// operation then this may corrupt the archive.
|
||||
// ///
|
||||
// /// Note this will not add the contents of the directory to the archive.
|
||||
// /// See `append_dir_all` for recusively adding the contents of the directory.
|
||||
// ///
|
||||
// /// Also note that after all files have been written to an archive the
|
||||
// /// `finish` function needs to be called to finish writing the archive.
|
||||
// ///
|
||||
// /// # Examples
|
||||
// ///
|
||||
// /// ```
|
||||
// /// use std::fs;
|
||||
// /// use tar::Builder;
|
||||
// ///
|
||||
// /// let mut ar = Builder::new(Vec::new());
|
||||
// ///
|
||||
// /// // Use the directory at one location, but insert it into the archive
|
||||
// /// // with a different name.
|
||||
// /// ar.append_dir("bardir", ".").unwrap();
|
||||
// /// ```
|
||||
// pub fn append_dir<P, Q>(&mut self, path: P, src_path: Q) -> io::Result<()>
|
||||
// where
|
||||
// P: AsRef<Path>,
|
||||
// Q: AsRef<Path>,
|
||||
// {
|
||||
// let mode = self.mode.clone();
|
||||
// append_dir(self.get_mut(), path.as_ref(), src_path.as_ref(), mode)
|
||||
// }
|
||||
|
||||
// /// Adds a directory and all of its contents (recursively) to this archive
|
||||
// /// with the given path as the name of the directory in the archive.
|
||||
// ///
|
||||
// /// Note that this will not attempt to seek the archive to a valid position,
|
||||
// /// so if the archive is in the middle of a read or some other similar
|
||||
// /// operation then this may corrupt the archive.
|
||||
// ///
|
||||
// /// Also note that after all files have been written to an archive the
|
||||
// /// `finish` function needs to be called to finish writing the archive.
|
||||
// ///
|
||||
// /// # Examples
|
||||
// ///
|
||||
// /// ```
|
||||
// /// use std::fs;
|
||||
// /// use tar::Builder;
|
||||
// ///
|
||||
// /// let mut ar = Builder::new(Vec::new());
|
||||
// ///
|
||||
// /// // Use the directory at one location, but insert it into the archive
|
||||
// /// // with a different name.
|
||||
// /// ar.append_dir_all("bardir", ".").unwrap();
|
||||
// /// ```
|
||||
// pub fn append_dir_all<P, Q>(&mut self, path: P, src_path: Q) -> io::Result<()>
|
||||
// where
|
||||
// P: AsRef<Path>,
|
||||
// Q: AsRef<Path>,
|
||||
// {
|
||||
// let mode = self.mode.clone();
|
||||
// let follow = self.follow;
|
||||
// append_dir_all(
|
||||
// self.get_mut(),
|
||||
// path.as_ref(),
|
||||
// src_path.as_ref(),
|
||||
// mode,
|
||||
// follow,
|
||||
// )
|
||||
// }
|
||||
|
||||
/// Finish writing this archive, emitting the termination sections.
|
||||
///
|
||||
/// This function should only be called when the archive has been written
|
||||
/// entirely and if an I/O error happens the underlying object still needs
|
||||
/// to be acquired.
|
||||
///
|
||||
/// In most situations the `into_inner` method should be preferred.
|
||||
pub fn finish(&mut self) -> io::Result<()> {
|
||||
if self.finished {
|
||||
return Ok(());
|
||||
}
|
||||
self.finished = true;
|
||||
self.get_mut().write_all(&[0; 1024])
|
||||
}
|
||||
}
|
||||
|
||||
fn append(mut dst: &mut dyn Write, header: &Header, mut data: &mut dyn Read) -> io::Result<()> {
|
||||
dst.write_all(header.as_bytes())?;
|
||||
let len = io::copy(&mut data, &mut dst)?;
|
||||
|
||||
// Pad with zeros if necessary.
|
||||
let buf = [0; 512];
|
||||
let remaining = 512 - (len % 512);
|
||||
if remaining < 512 {
|
||||
dst.write_all(&buf[..remaining as usize])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// fn append_path_with_name(
|
||||
// dst: &mut dyn Write,
|
||||
// path: &Path,
|
||||
// name: Option<&Path>,
|
||||
// mode: HeaderMode,
|
||||
// follow: bool,
|
||||
// ) -> io::Result<()> {
|
||||
// let stat = if follow {
|
||||
// fs::metadata(path).map_err(|err| {
|
||||
// io::Error::new(
|
||||
// err.kind(),
|
||||
// format!("{} when getting metadata for {}", err, path.display()),
|
||||
// )
|
||||
// })?
|
||||
// } else {
|
||||
// fs::symlink_metadata(path).map_err(|err| {
|
||||
// io::Error::new(
|
||||
// err.kind(),
|
||||
// format!("{} when getting metadata for {}", err, path.display()),
|
||||
// )
|
||||
// })?
|
||||
// };
|
||||
// let ar_name = name.unwrap_or(path);
|
||||
// if stat.is_file() {
|
||||
// append_fs(dst, ar_name, &stat, &mut fs::File::open(path)?, mode, None)
|
||||
// } else if stat.is_dir() {
|
||||
// append_fs(dst, ar_name, &stat, &mut io::empty(), mode, None)
|
||||
// } else if stat.file_type().is_symlink() {
|
||||
// let link_name = fs::read_link(path)?;
|
||||
// append_fs(
|
||||
// dst,
|
||||
// ar_name,
|
||||
// &stat,
|
||||
// &mut io::empty(),
|
||||
// mode,
|
||||
// Some(&link_name),
|
||||
// )
|
||||
// } else {
|
||||
// #[cfg(unix)]
|
||||
// {
|
||||
// append_special(dst, path, &stat, mode)
|
||||
// }
|
||||
// #[cfg(not(unix))]
|
||||
// {
|
||||
// Err(other(&format!("{} has unknown file type", path.display())))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
#[cfg(unix)]
|
||||
fn append_special(
|
||||
dst: &mut dyn Write,
|
||||
path: &Path,
|
||||
stat: &fs::Metadata,
|
||||
mode: HeaderMode,
|
||||
) -> io::Result<()> {
|
||||
use ::std::os::unix::fs::{FileTypeExt, MetadataExt};
|
||||
|
||||
let file_type = stat.file_type();
|
||||
let entry_type;
|
||||
if file_type.is_socket() {
|
||||
// sockets can't be archived
|
||||
return Err(other(&format!(
|
||||
"{}: socket can not be archived",
|
||||
path.display()
|
||||
)));
|
||||
} else if file_type.is_fifo() {
|
||||
entry_type = EntryType::Fifo;
|
||||
} else if file_type.is_char_device() {
|
||||
entry_type = EntryType::Char;
|
||||
} else if file_type.is_block_device() {
|
||||
entry_type = EntryType::Block;
|
||||
} else {
|
||||
return Err(other(&format!("{} has unknown file type", path.display())));
|
||||
}
|
||||
|
||||
let mut header = Header::new_gnu();
|
||||
header.set_metadata_in_mode(stat, mode);
|
||||
prepare_header_path(dst, &mut header, path)?;
|
||||
|
||||
header.set_entry_type(entry_type);
|
||||
let dev_id = stat.rdev();
|
||||
let dev_major = ((dev_id >> 32) & 0xffff_f000) | ((dev_id >> 8) & 0x0000_0fff);
|
||||
let dev_minor = ((dev_id >> 12) & 0xffff_ff00) | ((dev_id) & 0x0000_00ff);
|
||||
header.set_device_major(dev_major as u32)?;
|
||||
header.set_device_minor(dev_minor as u32)?;
|
||||
|
||||
header.set_cksum();
|
||||
dst.write_all(header.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// fn append_file(
|
||||
// dst: &mut dyn Write,
|
||||
// path: &Path,
|
||||
// file: &mut fs::File,
|
||||
// mode: HeaderMode,
|
||||
// ) -> io::Result<()> {
|
||||
// let stat = file.metadata()?;
|
||||
// append_fs(dst, path, &stat, file, mode, None)
|
||||
// }
|
||||
//
|
||||
// fn append_dir(
|
||||
// dst: &mut dyn Write,
|
||||
// path: &Path,
|
||||
// src_path: &Path,
|
||||
// mode: HeaderMode,
|
||||
// ) -> io::Result<()> {
|
||||
// let stat = fs::metadata(src_path)?;
|
||||
// append_fs(dst, path, &stat, &mut io::empty(), mode, None)
|
||||
// }
|
||||
|
||||
fn prepare_header(size: u64, entry_type: u8) -> Header {
|
||||
let mut header = Header::new_gnu();
|
||||
let name = b"././@LongLink";
|
||||
header.as_gnu_mut().unwrap().name[..name.len()].clone_from_slice(&name[..]);
|
||||
header.set_mode(0o644);
|
||||
header.set_uid(0);
|
||||
header.set_gid(0);
|
||||
header.set_mtime(0);
|
||||
// + 1 to be compliant with GNU tar
|
||||
header.set_size(size + 1);
|
||||
header.set_entry_type(EntryType::new(entry_type));
|
||||
header.set_cksum();
|
||||
header
|
||||
}
|
||||
|
||||
fn prepare_header_path(dst: &mut dyn Write, header: &mut Header, path: &Path) -> io::Result<()> {
|
||||
// Try to encode the path directly in the header, but if it ends up not
|
||||
// working (probably because it's too long) then try to use the GNU-specific
|
||||
// long name extension by emitting an entry which indicates that it's the
|
||||
// filename.
|
||||
if let Err(e) = header.set_path(path) {
|
||||
let data = path2bytes(&path)?;
|
||||
let max = header.as_old().name.len();
|
||||
// Since `e` isn't specific enough to let us know the path is indeed too
|
||||
// long, verify it first before using the extension.
|
||||
if data.len() < max {
|
||||
return Err(e);
|
||||
}
|
||||
let header2 = prepare_header(data.len() as u64, b'L');
|
||||
// null-terminated string
|
||||
let mut data2 = data.chain(io::repeat(0).take(1));
|
||||
append(dst, &header2, &mut data2)?;
|
||||
|
||||
// Truncate the path to store in the header we're about to emit to
|
||||
// ensure we've got something at least mentioned. Note that we use
|
||||
// `str`-encoding to be compatible with Windows, but in general the
|
||||
// entry in the header itself shouldn't matter too much since extraction
|
||||
// doesn't look at it.
|
||||
let truncated = match str::from_utf8(&data[..max]) {
|
||||
Ok(s) => s,
|
||||
Err(e) => str::from_utf8(&data[..e.valid_up_to()]).unwrap(),
|
||||
};
|
||||
header.set_path(truncated)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prepare_header_link(
|
||||
dst: &mut dyn Write,
|
||||
header: &mut Header,
|
||||
link_name: &Path,
|
||||
) -> io::Result<()> {
|
||||
// Same as previous function but for linkname
|
||||
if let Err(e) = header.set_link_name(&link_name) {
|
||||
let data = path2bytes(&link_name)?;
|
||||
if data.len() < header.as_old().linkname.len() {
|
||||
return Err(e);
|
||||
}
|
||||
let header2 = prepare_header(data.len() as u64, b'K');
|
||||
let mut data2 = data.chain(io::repeat(0).take(1));
|
||||
append(dst, &header2, &mut data2)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// fn append_fs(
|
||||
// dst: &mut dyn Write,
|
||||
// path: &Path,
|
||||
// meta: &fs::Metadata,
|
||||
// read: &mut dyn Read,
|
||||
// mode: HeaderMode,
|
||||
// link_name: Option<&Path>,
|
||||
// ) -> io::Result<()> {
|
||||
// let mut header = Header::new_gnu();
|
||||
//
|
||||
// prepare_header_path(dst, &mut header, path)?;
|
||||
// header.set_metadata_in_mode(meta, mode);
|
||||
// if let Some(link_name) = link_name {
|
||||
// prepare_header_link(dst, &mut header, link_name)?;
|
||||
// }
|
||||
// header.set_cksum();
|
||||
// append(dst, &header, read)
|
||||
// }
|
||||
|
||||
// fn append_dir_all(
|
||||
// dst: &mut dyn Write,
|
||||
// path: &Path,
|
||||
// src_path: &Path,
|
||||
// mode: HeaderMode,
|
||||
// follow: bool,
|
||||
// ) -> io::Result<()> {
|
||||
// let mut stack = vec![(src_path.to_path_buf(), true, false)];
|
||||
// while let Some((src, is_dir, is_symlink)) = stack.pop() {
|
||||
// let dest = path.join(src.strip_prefix(&src_path).unwrap());
|
||||
// // In case of a symlink pointing to a directory, is_dir is false, but src.is_dir() will return true
|
||||
// if is_dir || (is_symlink && follow && src.is_dir()) {
|
||||
// for entry in fs::read_dir(&src)? {
|
||||
// let entry = entry?;
|
||||
// let file_type = entry.file_type()?;
|
||||
// stack.push((entry.path(), file_type.is_dir(), file_type.is_symlink()));
|
||||
// }
|
||||
// if dest != Path::new("") {
|
||||
// append_dir(dst, &dest, &src, mode)?;
|
||||
// }
|
||||
// } else if !follow && is_symlink {
|
||||
// let stat = fs::symlink_metadata(&src)?;
|
||||
// let link_name = fs::read_link(&src)?;
|
||||
// append_fs(dst, &dest, &stat, &mut io::empty(), mode, Some(&link_name))?;
|
||||
// } else {
|
||||
// #[cfg(unix)]
|
||||
// {
|
||||
// let stat = fs::metadata(&src)?;
|
||||
// if !stat.is_file() {
|
||||
// append_special(dst, &dest, &stat, mode)?;
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
// append_file(dst, &dest, &mut fs::File::open(src)?, mode)?;
|
||||
// }
|
||||
// }
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
impl<W: Write> Drop for Builder<W> {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.finish();
|
||||
}
|
||||
}
|
388
tar-0.4.41/src/entry.rs
Normal file
388
tar-0.4.41/src/entry.rs
Normal file
@ -0,0 +1,388 @@
|
||||
use std::borrow::Cow;
|
||||
use std::cmp;
|
||||
use std::fs;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::prelude::*;
|
||||
use std::io::{self, Error, ErrorKind, SeekFrom};
|
||||
use std::marker;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
use crate::archive::ArchiveInner;
|
||||
use crate::error::TarError;
|
||||
use crate::header::bytes2path;
|
||||
use crate::other;
|
||||
use crate::{Archive, Header, PaxExtensions};
|
||||
|
||||
/// A read-only view into an entry of an archive.
|
||||
///
|
||||
/// This structure is a window into a portion of a borrowed archive which can
|
||||
/// be inspected. It acts as a file handle by implementing the Reader trait. An
|
||||
/// entry cannot be rewritten once inserted into an archive.
|
||||
pub struct Entry<'a, R: 'a + Read> {
|
||||
fields: EntryFields<'a>,
|
||||
_ignored: marker::PhantomData<&'a Archive<R>>,
|
||||
}
|
||||
|
||||
// private implementation detail of `Entry`, but concrete (no type parameters)
|
||||
// and also all-public to be constructed from other modules.
|
||||
pub struct EntryFields<'a> {
|
||||
pub long_pathname: Option<Vec<u8>>,
|
||||
pub long_linkname: Option<Vec<u8>>,
|
||||
pub pax_extensions: Option<Vec<u8>>,
|
||||
pub mask: u32,
|
||||
pub header: Header,
|
||||
pub size: u64,
|
||||
pub header_pos: u64,
|
||||
pub file_pos: u64,
|
||||
pub data: Vec<EntryIo<'a>>,
|
||||
pub unpack_xattrs: bool,
|
||||
pub preserve_permissions: bool,
|
||||
pub preserve_ownerships: bool,
|
||||
pub preserve_mtime: bool,
|
||||
pub overwrite: bool,
|
||||
}
|
||||
|
||||
pub enum EntryIo<'a> {
|
||||
Pad(io::Take<io::Repeat>),
|
||||
Data(io::Take<&'a ArchiveInner<dyn Read + 'a>>),
|
||||
}
|
||||
|
||||
/// When unpacking items the unpacked thing is returned to allow custom
|
||||
/// additional handling by users. Today the File is returned, in future
|
||||
/// the enum may be extended with kinds for links, directories etc.
|
||||
#[derive(Debug)]
|
||||
pub enum Unpacked {
|
||||
/// A file was unpacked.
|
||||
File(std::fs::File),
|
||||
/// A directory, hardlink, symlink, or other node was unpacked.
|
||||
#[doc(hidden)]
|
||||
__Nonexhaustive,
|
||||
}
|
||||
|
||||
impl<'a, R: Read> Entry<'a, R> {
|
||||
/// Returns the path name for this entry.
|
||||
///
|
||||
/// This method may fail if the pathname is not valid Unicode and this is
|
||||
/// called on a Windows platform.
|
||||
///
|
||||
/// Note that this function will convert any `\` characters to directory
|
||||
/// separators, and it will not always return the same value as
|
||||
/// `self.header().path()` as some archive formats have support for longer
|
||||
/// path names described in separate entries.
|
||||
///
|
||||
/// It is recommended to use this method instead of inspecting the `header`
|
||||
/// directly to ensure that various archive formats are handled correctly.
|
||||
pub fn path(&self) -> io::Result<Cow<Path>> {
|
||||
self.fields.path()
|
||||
}
|
||||
|
||||
/// Returns the raw bytes listed for this entry.
|
||||
///
|
||||
/// Note that this function will convert any `\` characters to directory
|
||||
/// separators, and it will not always return the same value as
|
||||
/// `self.header().path_bytes()` as some archive formats have support for
|
||||
/// longer path names described in separate entries.
|
||||
pub fn path_bytes(&self) -> Cow<[u8]> {
|
||||
self.fields.path_bytes()
|
||||
}
|
||||
|
||||
/// Returns the link name for this entry, if any is found.
|
||||
///
|
||||
/// This method may fail if the pathname is not valid Unicode and this is
|
||||
/// called on a Windows platform. `Ok(None)` being returned, however,
|
||||
/// indicates that the link name was not present.
|
||||
///
|
||||
/// Note that this function will convert any `\` characters to directory
|
||||
/// separators, and it will not always return the same value as
|
||||
/// `self.header().link_name()` as some archive formats have support for
|
||||
/// longer path names described in separate entries.
|
||||
///
|
||||
/// It is recommended to use this method instead of inspecting the `header`
|
||||
/// directly to ensure that various archive formats are handled correctly.
|
||||
pub fn link_name(&self) -> io::Result<Option<Cow<Path>>> {
|
||||
self.fields.link_name()
|
||||
}
|
||||
|
||||
/// Returns the link name for this entry, in bytes, if listed.
|
||||
///
|
||||
/// Note that this will not always return the same value as
|
||||
/// `self.header().link_name_bytes()` as some archive formats have support for
|
||||
/// longer path names described in separate entries.
|
||||
pub fn link_name_bytes(&self) -> Option<Cow<[u8]>> {
|
||||
self.fields.link_name_bytes()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the pax extensions contained in this entry.
|
||||
///
|
||||
/// Pax extensions are a form of archive where extra metadata is stored in
|
||||
/// key/value pairs in entries before the entry they're intended to
|
||||
/// describe. For example this can be used to describe long file name or
|
||||
/// other metadata like atime/ctime/mtime in more precision.
|
||||
///
|
||||
/// The returned iterator will yield key/value pairs for each extension.
|
||||
///
|
||||
/// `None` will be returned if this entry does not indicate that it itself
|
||||
/// contains extensions, or if there were no previous extensions describing
|
||||
/// it.
|
||||
///
|
||||
/// Note that global pax extensions are intended to be applied to all
|
||||
/// archive entries.
|
||||
///
|
||||
/// Also note that this function will read the entire entry if the entry
|
||||
/// itself is a list of extensions.
|
||||
pub fn pax_extensions(&mut self) -> io::Result<Option<PaxExtensions>> {
|
||||
self.fields.pax_extensions()
|
||||
}
|
||||
|
||||
/// Returns access to the header of this entry in the archive.
|
||||
///
|
||||
/// This provides access to the metadata for this entry in the archive.
|
||||
pub fn header(&self) -> &Header {
|
||||
&self.fields.header
|
||||
}
|
||||
|
||||
/// Returns access to the size of this entry in the archive.
|
||||
///
|
||||
/// In the event the size is stored in a pax extension, that size value
|
||||
/// will be referenced. Otherwise, the entry size will be stored in the header.
|
||||
pub fn size(&self) -> u64 {
|
||||
self.fields.size
|
||||
}
|
||||
|
||||
/// Returns the starting position, in bytes, of the header of this entry in
|
||||
/// the archive.
|
||||
///
|
||||
/// The header is always a contiguous section of 512 bytes, so if the
|
||||
/// underlying reader implements `Seek`, then the slice from `header_pos` to
|
||||
/// `header_pos + 512` contains the raw header bytes.
|
||||
pub fn raw_header_position(&self) -> u64 {
|
||||
self.fields.header_pos
|
||||
}
|
||||
|
||||
/// Returns the starting position, in bytes, of the file of this entry in
|
||||
/// the archive.
|
||||
///
|
||||
/// If the file of this entry is continuous (e.g. not a sparse file), and
|
||||
/// if the underlying reader implements `Seek`, then the slice from
|
||||
/// `file_pos` to `file_pos + entry_size` contains the raw file bytes.
|
||||
pub fn raw_file_position(&self) -> u64 {
|
||||
self.fields.file_pos
|
||||
}
|
||||
|
||||
/// Set the mask of the permission bits when unpacking this entry.
|
||||
///
|
||||
/// The mask will be inverted when applying against a mode, similar to how
|
||||
/// `umask` works on Unix. In logical notation it looks like:
|
||||
///
|
||||
/// ```text
|
||||
/// new_mode = old_mode & (~mask)
|
||||
/// ```
|
||||
///
|
||||
/// The mask is 0 by default and is currently only implemented on Unix.
|
||||
pub fn set_mask(&mut self, mask: u32) {
|
||||
self.fields.mask = mask;
|
||||
}
|
||||
|
||||
/// Indicate whether extended file attributes (xattrs on Unix) are preserved
|
||||
/// when unpacking this entry.
|
||||
///
|
||||
/// This flag is disabled by default and is currently only implemented on
|
||||
/// Unix using xattr support. This may eventually be implemented for
|
||||
/// Windows, however, if other archive implementations are found which do
|
||||
/// this as well.
|
||||
pub fn set_unpack_xattrs(&mut self, unpack_xattrs: bool) {
|
||||
self.fields.unpack_xattrs = unpack_xattrs;
|
||||
}
|
||||
|
||||
/// Indicate whether extended permissions (like suid on Unix) are preserved
|
||||
/// when unpacking this entry.
|
||||
///
|
||||
/// This flag is disabled by default and is currently only implemented on
|
||||
/// Unix.
|
||||
pub fn set_preserve_permissions(&mut self, preserve: bool) {
|
||||
self.fields.preserve_permissions = preserve;
|
||||
}
|
||||
|
||||
/// Indicate whether access time information is preserved when unpacking
|
||||
/// this entry.
|
||||
///
|
||||
/// This flag is enabled by default.
|
||||
pub fn set_preserve_mtime(&mut self, preserve: bool) {
|
||||
self.fields.preserve_mtime = preserve;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, R: Read> Read for Entry<'a, R> {
|
||||
fn read(&mut self, into: &mut [u8]) -> io::Result<usize> {
|
||||
self.fields.read(into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EntryFields<'a> {
|
||||
pub fn from<R: Read>(entry: Entry<R>) -> EntryFields {
|
||||
entry.fields
|
||||
}
|
||||
|
||||
pub fn into_entry<R: Read>(self) -> Entry<'a, R> {
|
||||
Entry {
|
||||
fields: self,
|
||||
_ignored: marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_all(&mut self) -> io::Result<Vec<u8>> {
|
||||
// Preallocate some data but don't let ourselves get too crazy now.
|
||||
let cap = cmp::min(self.size, 128 * 1024);
|
||||
let mut v = Vec::with_capacity(cap as usize);
|
||||
self.read_to_end(&mut v).map(|_| v)
|
||||
}
|
||||
|
||||
fn path(&self) -> io::Result<Cow<Path>> {
|
||||
bytes2path(self.path_bytes())
|
||||
}
|
||||
|
||||
fn path_bytes(&self) -> Cow<[u8]> {
|
||||
match self.long_pathname {
|
||||
Some(ref bytes) => {
|
||||
if let Some(&0) = bytes.last() {
|
||||
Cow::Borrowed(&bytes[..bytes.len() - 1])
|
||||
} else {
|
||||
Cow::Borrowed(bytes)
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if let Some(ref pax) = self.pax_extensions {
|
||||
let pax = PaxExtensions::new(pax)
|
||||
.filter_map(|f| f.ok())
|
||||
.find(|f| f.key_bytes() == b"path")
|
||||
.map(|f| f.value_bytes());
|
||||
if let Some(field) = pax {
|
||||
return Cow::Borrowed(field);
|
||||
}
|
||||
}
|
||||
self.header.path_bytes()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the path in a "lossy" way, used for error reporting ONLY.
|
||||
fn path_lossy(&self) -> String {
|
||||
String::from_utf8_lossy(&self.path_bytes()).to_string()
|
||||
}
|
||||
|
||||
fn link_name(&self) -> io::Result<Option<Cow<Path>>> {
|
||||
match self.link_name_bytes() {
|
||||
Some(bytes) => bytes2path(bytes).map(Some),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn link_name_bytes(&self) -> Option<Cow<[u8]>> {
|
||||
match self.long_linkname {
|
||||
Some(ref bytes) => {
|
||||
if let Some(&0) = bytes.last() {
|
||||
Some(Cow::Borrowed(&bytes[..bytes.len() - 1]))
|
||||
} else {
|
||||
Some(Cow::Borrowed(bytes))
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if let Some(ref pax) = self.pax_extensions {
|
||||
let pax = PaxExtensions::new(pax)
|
||||
.filter_map(|f| f.ok())
|
||||
.find(|f| f.key_bytes() == b"linkpath")
|
||||
.map(|f| f.value_bytes());
|
||||
if let Some(field) = pax {
|
||||
return Some(Cow::Borrowed(field));
|
||||
}
|
||||
}
|
||||
self.header.link_name_bytes()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pax_extensions(&mut self) -> io::Result<Option<PaxExtensions>> {
|
||||
if self.pax_extensions.is_none() {
|
||||
if !self.header.entry_type().is_pax_global_extensions()
|
||||
&& !self.header.entry_type().is_pax_local_extensions()
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
self.pax_extensions = Some(self.read_all()?);
|
||||
}
|
||||
Ok(Some(PaxExtensions::new(
|
||||
self.pax_extensions.as_ref().unwrap(),
|
||||
)))
|
||||
}
|
||||
|
||||
fn ensure_dir_created(&self, dst: &Path, dir: &Path) -> io::Result<()> {
|
||||
let mut ancestor = dir;
|
||||
let mut dirs_to_create = Vec::new();
|
||||
while ancestor.symlink_metadata().is_err() {
|
||||
dirs_to_create.push(ancestor);
|
||||
if let Some(parent) = ancestor.parent() {
|
||||
ancestor = parent;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for ancestor in dirs_to_create.into_iter().rev() {
|
||||
if let Some(parent) = ancestor.parent() {
|
||||
self.validate_inside_dst(dst, parent)?;
|
||||
}
|
||||
fs::create_dir_all(ancestor)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_inside_dst(&self, dst: &Path, file_dst: &Path) -> io::Result<PathBuf> {
|
||||
// Abort if target (canonical) parent is outside of `dst`
|
||||
let canon_parent = file_dst.canonicalize().map_err(|err| {
|
||||
Error::new(
|
||||
err.kind(),
|
||||
format!("{} while canonicalizing {}", err, file_dst.display()),
|
||||
)
|
||||
})?;
|
||||
let canon_target = dst.canonicalize().map_err(|err| {
|
||||
Error::new(
|
||||
err.kind(),
|
||||
format!("{} while canonicalizing {}", err, dst.display()),
|
||||
)
|
||||
})?;
|
||||
if !canon_parent.starts_with(&canon_target) {
|
||||
let err = TarError::new(
|
||||
format!(
|
||||
"trying to unpack outside of destination path: {}",
|
||||
canon_target.display()
|
||||
),
|
||||
// TODO: use ErrorKind::InvalidInput here? (minor breaking change)
|
||||
Error::new(ErrorKind::Other, "Invalid argument"),
|
||||
);
|
||||
return Err(err.into());
|
||||
}
|
||||
Ok(canon_target)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Read for EntryFields<'a> {
|
||||
fn read(&mut self, into: &mut [u8]) -> io::Result<usize> {
|
||||
loop {
|
||||
match self.data.get_mut(0).map(|io| io.read(into)) {
|
||||
Some(Ok(0)) => {
|
||||
self.data.remove(0);
|
||||
}
|
||||
Some(r) => return r,
|
||||
None => return Ok(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Read for EntryIo<'a> {
|
||||
fn read(&mut self, into: &mut [u8]) -> io::Result<usize> {
|
||||
match *self {
|
||||
EntryIo::Pad(ref mut io) => io.read(into),
|
||||
EntryIo::Data(ref mut io) => io.read(into),
|
||||
}
|
||||
}
|
||||
}
|
199
tar-0.4.41/src/entry_type.rs
Normal file
199
tar-0.4.41/src/entry_type.rs
Normal file
@ -0,0 +1,199 @@
|
||||
// See https://en.wikipedia.org/wiki/Tar_%28computing%29#UStar_format
|
||||
/// Indicate for the type of file described by a header.
|
||||
///
|
||||
/// Each `Header` has an `entry_type` method returning an instance of this type
|
||||
/// which can be used to inspect what the header is describing.
|
||||
|
||||
/// A non-exhaustive enum representing the possible entry types
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum EntryType {
|
||||
/// Regular file
|
||||
Regular,
|
||||
/// Hard link
|
||||
Link,
|
||||
/// Symbolic link
|
||||
Symlink,
|
||||
/// Character device
|
||||
Char,
|
||||
/// Block device
|
||||
Block,
|
||||
/// Directory
|
||||
Directory,
|
||||
/// Named pipe (fifo)
|
||||
Fifo,
|
||||
/// Implementation-defined 'high-performance' type, treated as regular file
|
||||
Continuous,
|
||||
/// GNU extension - long file name
|
||||
GNULongName,
|
||||
/// GNU extension - long link name (link target)
|
||||
GNULongLink,
|
||||
/// GNU extension - sparse file
|
||||
GNUSparse,
|
||||
/// Global extended header
|
||||
XGlobalHeader,
|
||||
/// Extended Header
|
||||
XHeader,
|
||||
/// Hints that destructuring should not be exhaustive.
|
||||
///
|
||||
/// This enum may grow additional variants, so this makes sure clients
|
||||
/// don't count on exhaustive matching. (Otherwise, adding a new variant
|
||||
/// could break existing code.)
|
||||
#[doc(hidden)]
|
||||
__Nonexhaustive(u8),
|
||||
}
|
||||
|
||||
impl EntryType {
|
||||
/// Creates a new entry type from a raw byte.
|
||||
///
|
||||
/// Note that the other named constructors of entry type may be more
|
||||
/// appropriate to create a file type from.
|
||||
pub fn new(byte: u8) -> EntryType {
|
||||
match byte {
|
||||
b'\x00' | b'0' => EntryType::Regular,
|
||||
b'1' => EntryType::Link,
|
||||
b'2' => EntryType::Symlink,
|
||||
b'3' => EntryType::Char,
|
||||
b'4' => EntryType::Block,
|
||||
b'5' => EntryType::Directory,
|
||||
b'6' => EntryType::Fifo,
|
||||
b'7' => EntryType::Continuous,
|
||||
b'x' => EntryType::XHeader,
|
||||
b'g' => EntryType::XGlobalHeader,
|
||||
b'L' => EntryType::GNULongName,
|
||||
b'K' => EntryType::GNULongLink,
|
||||
b'S' => EntryType::GNUSparse,
|
||||
b => EntryType::__Nonexhaustive(b),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the raw underlying byte that this entry type represents.
|
||||
pub fn as_byte(&self) -> u8 {
|
||||
match *self {
|
||||
EntryType::Regular => b'0',
|
||||
EntryType::Link => b'1',
|
||||
EntryType::Symlink => b'2',
|
||||
EntryType::Char => b'3',
|
||||
EntryType::Block => b'4',
|
||||
EntryType::Directory => b'5',
|
||||
EntryType::Fifo => b'6',
|
||||
EntryType::Continuous => b'7',
|
||||
EntryType::XHeader => b'x',
|
||||
EntryType::XGlobalHeader => b'g',
|
||||
EntryType::GNULongName => b'L',
|
||||
EntryType::GNULongLink => b'K',
|
||||
EntryType::GNUSparse => b'S',
|
||||
EntryType::__Nonexhaustive(b) => b,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new entry type representing a regular file.
|
||||
pub fn file() -> EntryType {
|
||||
EntryType::Regular
|
||||
}
|
||||
|
||||
/// Creates a new entry type representing a hard link.
|
||||
pub fn hard_link() -> EntryType {
|
||||
EntryType::Link
|
||||
}
|
||||
|
||||
/// Creates a new entry type representing a symlink.
|
||||
pub fn symlink() -> EntryType {
|
||||
EntryType::Symlink
|
||||
}
|
||||
|
||||
/// Creates a new entry type representing a character special device.
|
||||
pub fn character_special() -> EntryType {
|
||||
EntryType::Char
|
||||
}
|
||||
|
||||
/// Creates a new entry type representing a block special device.
|
||||
pub fn block_special() -> EntryType {
|
||||
EntryType::Block
|
||||
}
|
||||
|
||||
/// Creates a new entry type representing a directory.
|
||||
pub fn dir() -> EntryType {
|
||||
EntryType::Directory
|
||||
}
|
||||
|
||||
/// Creates a new entry type representing a FIFO.
|
||||
pub fn fifo() -> EntryType {
|
||||
EntryType::Fifo
|
||||
}
|
||||
|
||||
/// Creates a new entry type representing a contiguous file.
|
||||
pub fn contiguous() -> EntryType {
|
||||
EntryType::Continuous
|
||||
}
|
||||
|
||||
/// Returns whether this type represents a regular file.
|
||||
pub fn is_file(&self) -> bool {
|
||||
self == &EntryType::Regular
|
||||
}
|
||||
|
||||
/// Returns whether this type represents a hard link.
|
||||
pub fn is_hard_link(&self) -> bool {
|
||||
self == &EntryType::Link
|
||||
}
|
||||
|
||||
/// Returns whether this type represents a symlink.
|
||||
pub fn is_symlink(&self) -> bool {
|
||||
self == &EntryType::Symlink
|
||||
}
|
||||
|
||||
/// Returns whether this type represents a character special device.
|
||||
pub fn is_character_special(&self) -> bool {
|
||||
self == &EntryType::Char
|
||||
}
|
||||
|
||||
/// Returns whether this type represents a block special device.
|
||||
pub fn is_block_special(&self) -> bool {
|
||||
self == &EntryType::Block
|
||||
}
|
||||
|
||||
/// Returns whether this type represents a directory.
|
||||
pub fn is_dir(&self) -> bool {
|
||||
self == &EntryType::Directory
|
||||
}
|
||||
|
||||
/// Returns whether this type represents a FIFO.
|
||||
pub fn is_fifo(&self) -> bool {
|
||||
self == &EntryType::Fifo
|
||||
}
|
||||
|
||||
/// Returns whether this type represents a contiguous file.
|
||||
pub fn is_contiguous(&self) -> bool {
|
||||
self == &EntryType::Continuous
|
||||
}
|
||||
|
||||
/// Returns whether this type represents a GNU long name header.
|
||||
pub fn is_gnu_longname(&self) -> bool {
|
||||
self == &EntryType::GNULongName
|
||||
}
|
||||
|
||||
/// Returns whether this type represents a GNU sparse header.
|
||||
pub fn is_gnu_sparse(&self) -> bool {
|
||||
self == &EntryType::GNUSparse
|
||||
}
|
||||
|
||||
/// Returns whether this type represents a GNU long link header.
|
||||
pub fn is_gnu_longlink(&self) -> bool {
|
||||
self == &EntryType::GNULongLink
|
||||
}
|
||||
|
||||
/// Returns whether this type represents PAX global extensions, that
|
||||
/// should affect all following entries. For more, see [PAX].
|
||||
///
|
||||
/// [PAX]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html
|
||||
pub fn is_pax_global_extensions(&self) -> bool {
|
||||
self == &EntryType::XGlobalHeader
|
||||
}
|
||||
|
||||
/// Returns whether this type represents PAX local extensions; these
|
||||
/// only affect the current entry. For more, see [PAX].
|
||||
///
|
||||
/// [PAX]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html
|
||||
pub fn is_pax_local_extensions(&self) -> bool {
|
||||
self == &EntryType::XHeader
|
||||
}
|
||||
}
|
41
tar-0.4.41/src/error.rs
Normal file
41
tar-0.4.41/src/error.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use std::borrow::Cow;
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
use std::io::{self, Error};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TarError {
|
||||
desc: Cow<'static, str>,
|
||||
io: io::Error,
|
||||
}
|
||||
|
||||
impl TarError {
|
||||
pub fn new(desc: impl Into<Cow<'static, str>>, err: Error) -> TarError {
|
||||
TarError {
|
||||
desc: desc.into(),
|
||||
io: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for TarError {
|
||||
fn description(&self) -> &str {
|
||||
&self.desc
|
||||
}
|
||||
|
||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||
Some(&self.io)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for TarError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.desc.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TarError> for Error {
|
||||
fn from(t: TarError) -> Error {
|
||||
Error::new(t.io.kind(), t)
|
||||
}
|
||||
}
|
1647
tar-0.4.41/src/header.rs
Normal file
1647
tar-0.4.41/src/header.rs
Normal file
File diff suppressed because it is too large
Load Diff
44
tar-0.4.41/src/lib.rs
Normal file
44
tar-0.4.41/src/lib.rs
Normal file
@ -0,0 +1,44 @@
|
||||
//! A library for reading and writing TAR archives
|
||||
//!
|
||||
//! This library provides utilities necessary to manage [TAR archives][1]
|
||||
//! abstracted over a reader or writer. Great strides are taken to ensure that
|
||||
//! an archive is never required to be fully resident in memory, and all objects
|
||||
//! provide largely a streaming interface to read bytes from.
|
||||
//!
|
||||
//! [1]: http://en.wikipedia.org/wiki/Tar_%28computing%29
|
||||
|
||||
// More docs about the detailed tar format can also be found here:
|
||||
// http://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5&manpath=FreeBSD+8-current
|
||||
|
||||
// NB: some of the coding patterns and idioms here may seem a little strange.
|
||||
// This is currently attempting to expose a super generic interface while
|
||||
// also not forcing clients to codegen the entire crate each time they use
|
||||
// it. To that end lots of work is done to ensure that concrete
|
||||
// implementations are all found in this crate and the generic functions are
|
||||
// all just super thin wrappers (e.g. easy to codegen).
|
||||
|
||||
#![doc(html_root_url = "https://docs.rs/tar/0.4")]
|
||||
#![deny(missing_docs)]
|
||||
#![cfg_attr(test, deny(warnings))]
|
||||
|
||||
use std::io::{Error, ErrorKind};
|
||||
|
||||
pub use crate::archive::{Archive, Entries};
|
||||
pub use crate::builder::Builder;
|
||||
pub use crate::entry::{Entry, Unpacked};
|
||||
pub use crate::entry_type::EntryType;
|
||||
pub use crate::header::GnuExtSparseHeader;
|
||||
pub use crate::header::{GnuHeader, GnuSparseHeader, Header, HeaderMode, OldHeader, UstarHeader};
|
||||
pub use crate::pax::{PaxExtension, PaxExtensions};
|
||||
|
||||
mod archive;
|
||||
mod builder;
|
||||
mod entry;
|
||||
mod entry_type;
|
||||
mod error;
|
||||
mod header;
|
||||
mod pax;
|
||||
|
||||
fn other(msg: &str) -> Error {
|
||||
Error::new(ErrorKind::Other, msg)
|
||||
}
|
147
tar-0.4.41/src/pax.rs
Normal file
147
tar-0.4.41/src/pax.rs
Normal file
@ -0,0 +1,147 @@
|
||||
#![allow(dead_code)]
|
||||
use std::io;
|
||||
use std::slice;
|
||||
use std::str;
|
||||
|
||||
use crate::other;
|
||||
|
||||
// Keywords for PAX extended header records.
|
||||
pub const PAX_NONE: &str = ""; // Indicates that no PAX key is suitable
|
||||
pub const PAX_PATH: &str = "path";
|
||||
pub const PAX_LINKPATH: &str = "linkpath";
|
||||
pub const PAX_SIZE: &str = "size";
|
||||
pub const PAX_UID: &str = "uid";
|
||||
pub const PAX_GID: &str = "gid";
|
||||
pub const PAX_UNAME: &str = "uname";
|
||||
pub const PAX_GNAME: &str = "gname";
|
||||
pub const PAX_MTIME: &str = "mtime";
|
||||
pub const PAX_ATIME: &str = "atime";
|
||||
pub const PAX_CTIME: &str = "ctime"; // Removed from later revision of PAX spec, but was valid
|
||||
pub const PAX_CHARSET: &str = "charset"; // Currently unused
|
||||
pub const PAX_COMMENT: &str = "comment"; // Currently unused
|
||||
|
||||
pub const PAX_SCHILYXATTR: &str = "SCHILY.xattr.";
|
||||
|
||||
// Keywords for GNU sparse files in a PAX extended header.
|
||||
pub const PAX_GNUSPARSE: &str = "GNU.sparse.";
|
||||
pub const PAX_GNUSPARSENUMBLOCKS: &str = "GNU.sparse.numblocks";
|
||||
pub const PAX_GNUSPARSEOFFSET: &str = "GNU.sparse.offset";
|
||||
pub const PAX_GNUSPARSENUMBYTES: &str = "GNU.sparse.numbytes";
|
||||
pub const PAX_GNUSPARSEMAP: &str = "GNU.sparse.map";
|
||||
pub const PAX_GNUSPARSENAME: &str = "GNU.sparse.name";
|
||||
pub const PAX_GNUSPARSEMAJOR: &str = "GNU.sparse.major";
|
||||
pub const PAX_GNUSPARSEMINOR: &str = "GNU.sparse.minor";
|
||||
pub const PAX_GNUSPARSESIZE: &str = "GNU.sparse.size";
|
||||
pub const PAX_GNUSPARSEREALSIZE: &str = "GNU.sparse.realsize";
|
||||
|
||||
/// An iterator over the pax extensions in an archive entry.
|
||||
///
|
||||
/// This iterator yields structures which can themselves be parsed into
|
||||
/// key/value pairs.
|
||||
pub struct PaxExtensions<'entry> {
|
||||
data: slice::Split<'entry, u8, fn(&u8) -> bool>,
|
||||
}
|
||||
|
||||
impl<'entry> PaxExtensions<'entry> {
|
||||
/// Create new pax extensions iterator from the given entry data.
|
||||
pub fn new(a: &'entry [u8]) -> Self {
|
||||
fn is_newline(a: &u8) -> bool {
|
||||
*a == b'\n'
|
||||
}
|
||||
PaxExtensions {
|
||||
data: a.split(is_newline),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A key/value pair corresponding to a pax extension.
|
||||
pub struct PaxExtension<'entry> {
|
||||
key: &'entry [u8],
|
||||
value: &'entry [u8],
|
||||
}
|
||||
|
||||
pub fn pax_extensions_value(a: &[u8], key: &str) -> Option<u64> {
|
||||
for extension in PaxExtensions::new(a) {
|
||||
let current_extension = match extension {
|
||||
Ok(ext) => ext,
|
||||
Err(_) => return None,
|
||||
};
|
||||
if current_extension.key() != Ok(key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let value = match current_extension.value() {
|
||||
Ok(value) => value,
|
||||
Err(_) => return None,
|
||||
};
|
||||
let result = match value.parse::<u64>() {
|
||||
Ok(result) => result,
|
||||
Err(_) => return None,
|
||||
};
|
||||
return Some(result);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
impl<'entry> Iterator for PaxExtensions<'entry> {
|
||||
type Item = io::Result<PaxExtension<'entry>>;
|
||||
|
||||
fn next(&mut self) -> Option<io::Result<PaxExtension<'entry>>> {
|
||||
let line = match self.data.next() {
|
||||
Some(line) if line.is_empty() => return None,
|
||||
Some(line) => line,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
Some(
|
||||
line.iter()
|
||||
.position(|b| *b == b' ')
|
||||
.and_then(|i| {
|
||||
str::from_utf8(&line[..i])
|
||||
.ok()
|
||||
.and_then(|len| len.parse::<usize>().ok().map(|j| (i + 1, j)))
|
||||
})
|
||||
.and_then(|(kvstart, reported_len)| {
|
||||
if line.len() + 1 == reported_len {
|
||||
line[kvstart..]
|
||||
.iter()
|
||||
.position(|b| *b == b'=')
|
||||
.map(|equals| (kvstart, equals))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|(kvstart, equals)| PaxExtension {
|
||||
key: &line[kvstart..kvstart + equals],
|
||||
value: &line[kvstart + equals + 1..],
|
||||
})
|
||||
.ok_or_else(|| other("malformed pax extension")),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'entry> PaxExtension<'entry> {
|
||||
/// Returns the key for this key/value pair parsed as a string.
|
||||
///
|
||||
/// May fail if the key isn't actually utf-8.
|
||||
pub fn key(&self) -> Result<&'entry str, str::Utf8Error> {
|
||||
str::from_utf8(self.key)
|
||||
}
|
||||
|
||||
/// Returns the underlying raw bytes for the key of this key/value pair.
|
||||
pub fn key_bytes(&self) -> &'entry [u8] {
|
||||
self.key
|
||||
}
|
||||
|
||||
/// Returns the value for this key/value pair parsed as a string.
|
||||
///
|
||||
/// May fail if the value isn't actually utf-8.
|
||||
pub fn value(&self) -> Result<&'entry str, str::Utf8Error> {
|
||||
str::from_utf8(self.value)
|
||||
}
|
||||
|
||||
/// Returns the underlying raw bytes for this value of this key/value pair.
|
||||
pub fn value_bytes(&self) -> &'entry [u8] {
|
||||
self.value
|
||||
}
|
||||
}
|
1514
tar-0.4.41/tests/all.rs
Normal file
1514
tar-0.4.41/tests/all.rs
Normal file
File diff suppressed because it is too large
Load Diff
410
tar-0.4.41/tests/entry.rs
Normal file
410
tar-0.4.41/tests/entry.rs
Normal file
@ -0,0 +1,410 @@
|
||||
extern crate tar;
|
||||
extern crate tempfile;
|
||||
|
||||
use std::fs::{create_dir, File};
|
||||
use std::io::Read;
|
||||
|
||||
use tempfile::Builder;
|
||||
|
||||
macro_rules! t {
|
||||
($e:expr) => {
|
||||
match $e {
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!("{} returned {}", stringify!($e), e),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn absolute_symlink() {
|
||||
let mut ar = tar::Builder::new(Vec::new());
|
||||
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(0);
|
||||
header.set_entry_type(tar::EntryType::Symlink);
|
||||
t!(header.set_path("foo"));
|
||||
t!(header.set_link_name("/bar"));
|
||||
header.set_cksum();
|
||||
t!(ar.append(&header, &[][..]));
|
||||
|
||||
let bytes = t!(ar.into_inner());
|
||||
let mut ar = tar::Archive::new(&bytes[..]);
|
||||
|
||||
let td = t!(Builder::new().prefix("tar").tempdir());
|
||||
t!(ar.unpack(td.path()));
|
||||
|
||||
t!(td.path().join("foo").symlink_metadata());
|
||||
|
||||
let mut ar = tar::Archive::new(&bytes[..]);
|
||||
let mut entries = t!(ar.entries());
|
||||
let entry = t!(entries.next().unwrap());
|
||||
assert_eq!(&*entry.link_name_bytes().unwrap(), b"/bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn absolute_hardlink() {
|
||||
let td = t!(Builder::new().prefix("tar").tempdir());
|
||||
let mut ar = tar::Builder::new(Vec::new());
|
||||
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(0);
|
||||
header.set_entry_type(tar::EntryType::Regular);
|
||||
t!(header.set_path("foo"));
|
||||
header.set_cksum();
|
||||
t!(ar.append(&header, &[][..]));
|
||||
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(0);
|
||||
header.set_entry_type(tar::EntryType::Link);
|
||||
t!(header.set_path("bar"));
|
||||
// This absolute path under tempdir will be created at unpack time
|
||||
t!(header.set_link_name(td.path().join("foo")));
|
||||
header.set_cksum();
|
||||
t!(ar.append(&header, &[][..]));
|
||||
|
||||
let bytes = t!(ar.into_inner());
|
||||
let mut ar = tar::Archive::new(&bytes[..]);
|
||||
|
||||
t!(ar.unpack(td.path()));
|
||||
t!(td.path().join("foo").metadata());
|
||||
t!(td.path().join("bar").metadata());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relative_hardlink() {
|
||||
let mut ar = tar::Builder::new(Vec::new());
|
||||
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(0);
|
||||
header.set_entry_type(tar::EntryType::Regular);
|
||||
t!(header.set_path("foo"));
|
||||
header.set_cksum();
|
||||
t!(ar.append(&header, &[][..]));
|
||||
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(0);
|
||||
header.set_entry_type(tar::EntryType::Link);
|
||||
t!(header.set_path("bar"));
|
||||
t!(header.set_link_name("foo"));
|
||||
header.set_cksum();
|
||||
t!(ar.append(&header, &[][..]));
|
||||
|
||||
let bytes = t!(ar.into_inner());
|
||||
let mut ar = tar::Archive::new(&bytes[..]);
|
||||
|
||||
let td = t!(Builder::new().prefix("tar").tempdir());
|
||||
t!(ar.unpack(td.path()));
|
||||
t!(td.path().join("foo").metadata());
|
||||
t!(td.path().join("bar").metadata());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn absolute_link_deref_error() {
|
||||
let mut ar = tar::Builder::new(Vec::new());
|
||||
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(0);
|
||||
header.set_entry_type(tar::EntryType::Symlink);
|
||||
t!(header.set_path("foo"));
|
||||
t!(header.set_link_name("/"));
|
||||
header.set_cksum();
|
||||
t!(ar.append(&header, &[][..]));
|
||||
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(0);
|
||||
header.set_entry_type(tar::EntryType::Regular);
|
||||
t!(header.set_path("foo/bar"));
|
||||
header.set_cksum();
|
||||
t!(ar.append(&header, &[][..]));
|
||||
|
||||
let bytes = t!(ar.into_inner());
|
||||
let mut ar = tar::Archive::new(&bytes[..]);
|
||||
|
||||
let td = t!(Builder::new().prefix("tar").tempdir());
|
||||
assert!(ar.unpack(td.path()).is_err());
|
||||
t!(td.path().join("foo").symlink_metadata());
|
||||
assert!(File::open(td.path().join("foo").join("bar")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relative_link_deref_error() {
|
||||
let mut ar = tar::Builder::new(Vec::new());
|
||||
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(0);
|
||||
header.set_entry_type(tar::EntryType::Symlink);
|
||||
t!(header.set_path("foo"));
|
||||
t!(header.set_link_name("../../../../"));
|
||||
header.set_cksum();
|
||||
t!(ar.append(&header, &[][..]));
|
||||
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(0);
|
||||
header.set_entry_type(tar::EntryType::Regular);
|
||||
t!(header.set_path("foo/bar"));
|
||||
header.set_cksum();
|
||||
t!(ar.append(&header, &[][..]));
|
||||
|
||||
let bytes = t!(ar.into_inner());
|
||||
let mut ar = tar::Archive::new(&bytes[..]);
|
||||
|
||||
let td = t!(Builder::new().prefix("tar").tempdir());
|
||||
assert!(ar.unpack(td.path()).is_err());
|
||||
t!(td.path().join("foo").symlink_metadata());
|
||||
assert!(File::open(td.path().join("foo").join("bar")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn directory_maintains_permissions() {
|
||||
use ::std::os::unix::fs::PermissionsExt;
|
||||
|
||||
let mut ar = tar::Builder::new(Vec::new());
|
||||
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(0);
|
||||
header.set_entry_type(tar::EntryType::Directory);
|
||||
t!(header.set_path("foo"));
|
||||
header.set_mode(0o777);
|
||||
header.set_cksum();
|
||||
t!(ar.append(&header, &[][..]));
|
||||
|
||||
let bytes = t!(ar.into_inner());
|
||||
let mut ar = tar::Archive::new(&bytes[..]);
|
||||
|
||||
let td = t!(Builder::new().prefix("tar").tempdir());
|
||||
t!(ar.unpack(td.path()));
|
||||
let f = t!(File::open(td.path().join("foo")));
|
||||
let md = t!(f.metadata());
|
||||
assert!(md.is_dir());
|
||||
assert_eq!(md.permissions().mode(), 0o40777);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn set_entry_mask() {
|
||||
use ::std::os::unix::fs::PermissionsExt;
|
||||
|
||||
let mut ar = tar::Builder::new(Vec::new());
|
||||
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(0);
|
||||
header.set_entry_type(tar::EntryType::Regular);
|
||||
t!(header.set_path("foo"));
|
||||
header.set_mode(0o777);
|
||||
header.set_cksum();
|
||||
t!(ar.append(&header, &[][..]));
|
||||
|
||||
let bytes = t!(ar.into_inner());
|
||||
let mut ar = tar::Archive::new(&bytes[..]);
|
||||
let td = t!(Builder::new().prefix("tar").tempdir());
|
||||
let foo_path = td.path().join("foo");
|
||||
|
||||
let mut entries = t!(ar.entries());
|
||||
let mut foo = t!(entries.next().unwrap());
|
||||
foo.set_mask(0o027);
|
||||
t!(foo.unpack(&foo_path));
|
||||
|
||||
let f = t!(File::open(foo_path));
|
||||
let md = t!(f.metadata());
|
||||
assert!(md.is_file());
|
||||
assert_eq!(md.permissions().mode(), 0o100750);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))] // dangling symlinks have weird permissions
|
||||
fn modify_link_just_created() {
|
||||
let mut ar = tar::Builder::new(Vec::new());
|
||||
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(0);
|
||||
header.set_entry_type(tar::EntryType::Symlink);
|
||||
t!(header.set_path("foo"));
|
||||
t!(header.set_link_name("bar"));
|
||||
header.set_cksum();
|
||||
t!(ar.append(&header, &[][..]));
|
||||
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(0);
|
||||
header.set_entry_type(tar::EntryType::Regular);
|
||||
t!(header.set_path("bar/foo"));
|
||||
header.set_cksum();
|
||||
t!(ar.append(&header, &[][..]));
|
||||
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(0);
|
||||
header.set_entry_type(tar::EntryType::Regular);
|
||||
t!(header.set_path("foo/bar"));
|
||||
header.set_cksum();
|
||||
t!(ar.append(&header, &[][..]));
|
||||
|
||||
let bytes = t!(ar.into_inner());
|
||||
let mut ar = tar::Archive::new(&bytes[..]);
|
||||
|
||||
let td = t!(Builder::new().prefix("tar").tempdir());
|
||||
t!(ar.unpack(td.path()));
|
||||
|
||||
t!(File::open(td.path().join("bar/foo")));
|
||||
t!(File::open(td.path().join("bar/bar")));
|
||||
t!(File::open(td.path().join("foo/foo")));
|
||||
t!(File::open(td.path().join("foo/bar")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))] // dangling symlinks have weird permissions
|
||||
fn modify_outside_with_relative_symlink() {
|
||||
let mut ar = tar::Builder::new(Vec::new());
|
||||
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(0);
|
||||
header.set_entry_type(tar::EntryType::Symlink);
|
||||
t!(header.set_path("symlink"));
|
||||
t!(header.set_link_name(".."));
|
||||
header.set_cksum();
|
||||
t!(ar.append(&header, &[][..]));
|
||||
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(0);
|
||||
header.set_entry_type(tar::EntryType::Regular);
|
||||
t!(header.set_path("symlink/foo/bar"));
|
||||
header.set_cksum();
|
||||
t!(ar.append(&header, &[][..]));
|
||||
|
||||
let bytes = t!(ar.into_inner());
|
||||
let mut ar = tar::Archive::new(&bytes[..]);
|
||||
|
||||
let td = t!(Builder::new().prefix("tar").tempdir());
|
||||
let tar_dir = td.path().join("tar");
|
||||
create_dir(&tar_dir).unwrap();
|
||||
assert!(ar.unpack(tar_dir).is_err());
|
||||
assert!(!td.path().join("foo").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parent_paths_error() {
|
||||
let mut ar = tar::Builder::new(Vec::new());
|
||||
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(0);
|
||||
header.set_entry_type(tar::EntryType::Symlink);
|
||||
t!(header.set_path("foo"));
|
||||
t!(header.set_link_name(".."));
|
||||
header.set_cksum();
|
||||
t!(ar.append(&header, &[][..]));
|
||||
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(0);
|
||||
header.set_entry_type(tar::EntryType::Regular);
|
||||
t!(header.set_path("foo/bar"));
|
||||
header.set_cksum();
|
||||
t!(ar.append(&header, &[][..]));
|
||||
|
||||
let bytes = t!(ar.into_inner());
|
||||
let mut ar = tar::Archive::new(&bytes[..]);
|
||||
|
||||
let td = t!(Builder::new().prefix("tar").tempdir());
|
||||
assert!(ar.unpack(td.path()).is_err());
|
||||
t!(td.path().join("foo").symlink_metadata());
|
||||
assert!(File::open(td.path().join("foo").join("bar")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn good_parent_paths_ok() {
|
||||
use std::path::PathBuf;
|
||||
let mut ar = tar::Builder::new(Vec::new());
|
||||
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(0);
|
||||
header.set_entry_type(tar::EntryType::Symlink);
|
||||
t!(header.set_path(PathBuf::from("foo").join("bar")));
|
||||
t!(header.set_link_name(PathBuf::from("..").join("bar")));
|
||||
header.set_cksum();
|
||||
t!(ar.append(&header, &[][..]));
|
||||
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(0);
|
||||
header.set_entry_type(tar::EntryType::Regular);
|
||||
t!(header.set_path("bar"));
|
||||
header.set_cksum();
|
||||
t!(ar.append(&header, &[][..]));
|
||||
|
||||
let bytes = t!(ar.into_inner());
|
||||
let mut ar = tar::Archive::new(&bytes[..]);
|
||||
|
||||
let td = t!(Builder::new().prefix("tar").tempdir());
|
||||
t!(ar.unpack(td.path()));
|
||||
t!(td.path().join("foo").join("bar").read_link());
|
||||
let dst = t!(td.path().join("foo").join("bar").canonicalize());
|
||||
t!(File::open(dst));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn modify_hard_link_just_created() {
|
||||
let mut ar = tar::Builder::new(Vec::new());
|
||||
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(0);
|
||||
header.set_entry_type(tar::EntryType::Link);
|
||||
t!(header.set_path("foo"));
|
||||
t!(header.set_link_name("../test"));
|
||||
header.set_cksum();
|
||||
t!(ar.append(&header, &[][..]));
|
||||
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(1);
|
||||
header.set_entry_type(tar::EntryType::Regular);
|
||||
t!(header.set_path("foo"));
|
||||
header.set_cksum();
|
||||
t!(ar.append(&header, &b"x"[..]));
|
||||
|
||||
let bytes = t!(ar.into_inner());
|
||||
let mut ar = tar::Archive::new(&bytes[..]);
|
||||
|
||||
let td = t!(Builder::new().prefix("tar").tempdir());
|
||||
|
||||
let test = td.path().join("test");
|
||||
t!(File::create(&test));
|
||||
|
||||
let dir = td.path().join("dir");
|
||||
assert!(ar.unpack(&dir).is_err());
|
||||
|
||||
let mut contents = Vec::new();
|
||||
t!(t!(File::open(&test)).read_to_end(&mut contents));
|
||||
assert_eq!(contents.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn modify_symlink_just_created() {
|
||||
let mut ar = tar::Builder::new(Vec::new());
|
||||
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(0);
|
||||
header.set_entry_type(tar::EntryType::Symlink);
|
||||
t!(header.set_path("foo"));
|
||||
t!(header.set_link_name("../test"));
|
||||
header.set_cksum();
|
||||
t!(ar.append(&header, &[][..]));
|
||||
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(1);
|
||||
header.set_entry_type(tar::EntryType::Regular);
|
||||
t!(header.set_path("foo"));
|
||||
header.set_cksum();
|
||||
t!(ar.append(&header, &b"x"[..]));
|
||||
|
||||
let bytes = t!(ar.into_inner());
|
||||
let mut ar = tar::Archive::new(&bytes[..]);
|
||||
|
||||
let td = t!(Builder::new().prefix("tar").tempdir());
|
||||
|
||||
let test = td.path().join("test");
|
||||
t!(File::create(&test));
|
||||
|
||||
let dir = td.path().join("dir");
|
||||
t!(ar.unpack(&dir));
|
||||
|
||||
let mut contents = Vec::new();
|
||||
t!(t!(File::open(&test)).read_to_end(&mut contents));
|
||||
assert_eq!(contents.len(), 0);
|
||||
}
|
247
tar-0.4.41/tests/header/mod.rs
Normal file
247
tar-0.4.41/tests/header/mod.rs
Normal file
@ -0,0 +1,247 @@
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, Write};
|
||||
use std::path::Path;
|
||||
use std::{iter, mem, thread, time};
|
||||
|
||||
use tempfile::Builder;
|
||||
|
||||
use tar::{GnuHeader, Header, HeaderMode};
|
||||
|
||||
#[test]
|
||||
fn default_gnu() {
|
||||
let mut h = Header::new_gnu();
|
||||
assert!(h.as_gnu().is_some());
|
||||
assert!(h.as_gnu_mut().is_some());
|
||||
assert!(h.as_ustar().is_none());
|
||||
assert!(h.as_ustar_mut().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_old() {
|
||||
let mut h = Header::new_old();
|
||||
assert!(h.as_gnu().is_none());
|
||||
assert!(h.as_gnu_mut().is_none());
|
||||
assert!(h.as_ustar().is_none());
|
||||
assert!(h.as_ustar_mut().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_ustar() {
|
||||
let mut h = Header::new_ustar();
|
||||
assert!(h.as_gnu().is_none());
|
||||
assert!(h.as_gnu_mut().is_none());
|
||||
assert!(h.as_ustar().is_some());
|
||||
assert!(h.as_ustar_mut().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn link_name() {
|
||||
let mut h = Header::new_gnu();
|
||||
t!(h.set_link_name("foo"));
|
||||
assert_eq!(t!(h.link_name()).unwrap().to_str(), Some("foo"));
|
||||
t!(h.set_link_name("../foo"));
|
||||
assert_eq!(t!(h.link_name()).unwrap().to_str(), Some("../foo"));
|
||||
t!(h.set_link_name("foo/bar"));
|
||||
assert_eq!(t!(h.link_name()).unwrap().to_str(), Some("foo/bar"));
|
||||
t!(h.set_link_name("foo\\ba"));
|
||||
if cfg!(windows) {
|
||||
assert_eq!(t!(h.link_name()).unwrap().to_str(), Some("foo/ba"));
|
||||
} else {
|
||||
assert_eq!(t!(h.link_name()).unwrap().to_str(), Some("foo\\ba"));
|
||||
}
|
||||
|
||||
let name = "foo\\bar\0";
|
||||
for (slot, val) in h.as_old_mut().linkname.iter_mut().zip(name.as_bytes()) {
|
||||
*slot = *val;
|
||||
}
|
||||
assert_eq!(t!(h.link_name()).unwrap().to_str(), Some("foo\\bar"));
|
||||
|
||||
assert!(h.set_link_name("\0").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mtime() {
|
||||
let h = Header::new_gnu();
|
||||
assert_eq!(t!(h.mtime()), 0);
|
||||
|
||||
let h = Header::new_ustar();
|
||||
assert_eq!(t!(h.mtime()), 0);
|
||||
|
||||
let h = Header::new_old();
|
||||
assert_eq!(t!(h.mtime()), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn user_and_group_name() {
|
||||
let mut h = Header::new_gnu();
|
||||
t!(h.set_username("foo"));
|
||||
t!(h.set_groupname("bar"));
|
||||
assert_eq!(t!(h.username()), Some("foo"));
|
||||
assert_eq!(t!(h.groupname()), Some("bar"));
|
||||
|
||||
h = Header::new_ustar();
|
||||
t!(h.set_username("foo"));
|
||||
t!(h.set_groupname("bar"));
|
||||
assert_eq!(t!(h.username()), Some("foo"));
|
||||
assert_eq!(t!(h.groupname()), Some("bar"));
|
||||
|
||||
h = Header::new_old();
|
||||
assert_eq!(t!(h.username()), None);
|
||||
assert_eq!(t!(h.groupname()), None);
|
||||
assert!(h.set_username("foo").is_err());
|
||||
assert!(h.set_groupname("foo").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dev_major_minor() {
|
||||
let mut h = Header::new_gnu();
|
||||
t!(h.set_device_major(1));
|
||||
t!(h.set_device_minor(2));
|
||||
assert_eq!(t!(h.device_major()), Some(1));
|
||||
assert_eq!(t!(h.device_minor()), Some(2));
|
||||
|
||||
h = Header::new_ustar();
|
||||
t!(h.set_device_major(1));
|
||||
t!(h.set_device_minor(2));
|
||||
assert_eq!(t!(h.device_major()), Some(1));
|
||||
assert_eq!(t!(h.device_minor()), Some(2));
|
||||
|
||||
h.as_ustar_mut().unwrap().dev_minor[0] = 0x7f;
|
||||
h.as_ustar_mut().unwrap().dev_major[0] = 0x7f;
|
||||
assert!(h.device_major().is_err());
|
||||
assert!(h.device_minor().is_err());
|
||||
|
||||
h.as_ustar_mut().unwrap().dev_minor[0] = b'g';
|
||||
h.as_ustar_mut().unwrap().dev_major[0] = b'h';
|
||||
assert!(h.device_major().is_err());
|
||||
assert!(h.device_minor().is_err());
|
||||
|
||||
h = Header::new_old();
|
||||
assert_eq!(t!(h.device_major()), None);
|
||||
assert_eq!(t!(h.device_minor()), None);
|
||||
assert!(h.set_device_major(1).is_err());
|
||||
assert!(h.set_device_minor(1).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_path() {
|
||||
let mut h = Header::new_gnu();
|
||||
t!(h.set_path("foo"));
|
||||
assert_eq!(t!(h.path()).to_str(), Some("foo"));
|
||||
t!(h.set_path("foo/"));
|
||||
assert_eq!(t!(h.path()).to_str(), Some("foo/"));
|
||||
t!(h.set_path("foo/bar"));
|
||||
assert_eq!(t!(h.path()).to_str(), Some("foo/bar"));
|
||||
t!(h.set_path("foo\\bar"));
|
||||
if cfg!(windows) {
|
||||
assert_eq!(t!(h.path()).to_str(), Some("foo/bar"));
|
||||
} else {
|
||||
assert_eq!(t!(h.path()).to_str(), Some("foo\\bar"));
|
||||
}
|
||||
|
||||
// set_path documentation explictly states it removes any ".", signfying the
|
||||
// current directory, from the path. This test ensures that documented
|
||||
// beavhior occurs
|
||||
t!(h.set_path("./control"));
|
||||
assert_eq!(t!(h.path()).to_str(), Some("control"));
|
||||
|
||||
let long_name = iter::repeat("foo").take(100).collect::<String>();
|
||||
let medium1 = iter::repeat("foo").take(52).collect::<String>();
|
||||
let medium2 = iter::repeat("fo/").take(52).collect::<String>();
|
||||
|
||||
assert!(h.set_path(&long_name).is_err());
|
||||
assert!(h.set_path(&medium1).is_err());
|
||||
assert!(h.set_path(&medium2).is_err());
|
||||
assert!(h.set_path("\0").is_err());
|
||||
|
||||
assert!(h.set_path("..").is_err());
|
||||
assert!(h.set_path("foo/..").is_err());
|
||||
assert!(h.set_path("foo/../bar").is_err());
|
||||
|
||||
h = Header::new_ustar();
|
||||
t!(h.set_path("foo"));
|
||||
assert_eq!(t!(h.path()).to_str(), Some("foo"));
|
||||
|
||||
assert!(h.set_path(&long_name).is_err());
|
||||
assert!(h.set_path(&medium1).is_err());
|
||||
t!(h.set_path(&medium2));
|
||||
assert_eq!(t!(h.path()).to_str(), Some(&medium2[..]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_ustar_path_hard() {
|
||||
let mut h = Header::new_ustar();
|
||||
let p = Path::new("a").join(&vec!["a"; 100].join(""));
|
||||
t!(h.set_path(&p));
|
||||
assert_eq!(t!(h.path()), p);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_metadata_deterministic() {
|
||||
let td = t!(Builder::new().prefix("tar-rs").tempdir());
|
||||
let tmppath = td.path().join("tmpfile");
|
||||
|
||||
fn mk_header(path: &Path, readonly: bool) -> Result<Header, io::Error> {
|
||||
let mut file = t!(File::create(path));
|
||||
t!(file.write_all(b"c"));
|
||||
let mut perms = t!(file.metadata()).permissions();
|
||||
perms.set_readonly(readonly);
|
||||
t!(fs::set_permissions(path, perms));
|
||||
let mut h = Header::new_ustar();
|
||||
h.set_metadata_in_mode(&t!(path.metadata()), HeaderMode::Deterministic);
|
||||
Ok(h)
|
||||
}
|
||||
|
||||
// Create "the same" File twice in a row, one second apart, with differing readonly values.
|
||||
let one = t!(mk_header(tmppath.as_path(), false));
|
||||
thread::sleep(time::Duration::from_millis(1050));
|
||||
let two = t!(mk_header(tmppath.as_path(), true));
|
||||
|
||||
// Always expected to match.
|
||||
assert_eq!(t!(one.size()), t!(two.size()));
|
||||
assert_eq!(t!(one.path()), t!(two.path()));
|
||||
assert_eq!(t!(one.mode()), t!(two.mode()));
|
||||
|
||||
// Would not match without `Deterministic`.
|
||||
assert_eq!(t!(one.mtime()), t!(two.mtime()));
|
||||
assert_eq!(t!(one.mtime()), 1153704088);
|
||||
// TODO: No great way to validate that these would not be filled, but
|
||||
// check them anyway.
|
||||
assert_eq!(t!(one.uid()), t!(two.uid()));
|
||||
assert_eq!(t!(one.gid()), t!(two.gid()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extended_numeric_format() {
|
||||
let mut h: GnuHeader = unsafe { mem::zeroed() };
|
||||
h.as_header_mut().set_size(42);
|
||||
assert_eq!(h.size, [48, 48, 48, 48, 48, 48, 48, 48, 48, 53, 50, 0]);
|
||||
h.as_header_mut().set_size(8589934593);
|
||||
assert_eq!(h.size, [0x80, 0, 0, 0, 0, 0, 0, 0x02, 0, 0, 0, 1]);
|
||||
h.size = [0x80, 0, 0, 0, 0, 0, 0, 0x02, 0, 0, 0, 0];
|
||||
assert_eq!(h.as_header().entry_size().unwrap(), 0x0200000000);
|
||||
h.size = [48, 48, 48, 48, 48, 48, 48, 48, 48, 53, 51, 0];
|
||||
assert_eq!(h.as_header().entry_size().unwrap(), 43);
|
||||
|
||||
h.as_header_mut().set_gid(42);
|
||||
assert_eq!(h.gid, [48, 48, 48, 48, 48, 53, 50, 0]);
|
||||
assert_eq!(h.as_header().gid().unwrap(), 42);
|
||||
h.as_header_mut().set_gid(0x7fffffffffffffff);
|
||||
assert_eq!(h.gid, [0xff; 8]);
|
||||
assert_eq!(h.as_header().gid().unwrap(), 0x7fffffffffffffff);
|
||||
h.uid = [0x80, 0x00, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78];
|
||||
assert_eq!(h.as_header().uid().unwrap(), 0x12345678);
|
||||
|
||||
h.mtime = [
|
||||
0x80, 0, 0, 0, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
|
||||
];
|
||||
assert_eq!(h.as_header().mtime().unwrap(), 0x0123456789abcdef);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn byte_slice_conversion() {
|
||||
let h = Header::new_gnu();
|
||||
let b: &[u8] = h.as_bytes();
|
||||
let b_conv: &[u8] = Header::from_byte_slice(h.as_bytes()).as_bytes();
|
||||
assert_eq!(b, b_conv);
|
||||
}
|
Loading…
Reference in New Issue
Block a user