From adbbc29d94566607206e49160ae830911aaf70d6 Mon Sep 17 00:00:00 2001 From: gwitrand-ovh Date: Thu, 24 Oct 2024 19:32:52 +0200 Subject: [PATCH] Very basic Pax header writer (#382) * Implementation of a PAX header writer This crate currently supports reading the PAX headers. This adds a way to write some PAX headers in a Builder. Also implemented tests to showcase usage * Pax header handler, switch to not owned data types Since we can keep the ownership of the data when calling append_pax_extensions, we can use &str and &[u8] according to the lib documentation --- src/pax.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ tests/all.rs | 38 ++++++++++++++++++++++++++++++++++++-- tests/entry.rs | 3 ++- 3 files changed, 86 insertions(+), 3 deletions(-) diff --git a/src/pax.rs b/src/pax.rs index 6e83edce..d1494282 100644 --- a/src/pax.rs +++ b/src/pax.rs @@ -1,5 +1,6 @@ #![allow(dead_code)] use std::io; +use std::io::Write; use std::slice; use std::str; @@ -145,3 +146,50 @@ impl<'entry> PaxExtension<'entry> { self.value } } + +/// Extension trait for `Builder` to append PAX extended headers. +impl crate::Builder { + /// Append PAX extended headers to the archive. + /// + /// Takes in an iterator over the list of headers to add to convert it into a header set formatted. + /// + /// Returns io::Error if an error occurs, else it returns () + pub fn append_pax_extensions<'key, 'value>( + &mut self, + headers: impl IntoIterator, + ) -> Result<(), io::Error> { + // Store the headers formatted before write + let mut data: Vec = Vec::new(); + + // For each key in headers, convert into a sized space and add it to data. + // This will then be written in the file + for (key, value) in headers { + let mut len_len = 1; + let mut max_len = 10; + let rest_len = 3 + key.len() + value.len(); + while rest_len + len_len >= max_len { + len_len += 1; + max_len *= 10; + } + let len = rest_len + len_len; + write!(&mut data, "{} {}=", len, key)?; + data.extend_from_slice(value); + data.push(b'\n'); + } + + // Ignore the header append if it's empty. + if data.is_empty() { + return Ok(()); + } + + // Create a header of type XHeader, set the size to the length of the + // data, set the entry type to XHeader, and set the checksum + // then append the header and the data to the archive. + let mut header = crate::Header::new_ustar(); + let data_as_bytes: &[u8] = &data; + header.set_size(data_as_bytes.len() as u64); + header.set_entry_type(crate::EntryType::XHeader); + header.set_cksum(); + self.append(&header, data_as_bytes) + } +} diff --git a/tests/all.rs b/tests/all.rs index 0d1b79db..0e1c2613 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -6,12 +6,12 @@ extern crate xattr; use std::fs::{self, File}; use std::io::prelude::*; -use std::io::{self, Cursor}; +use std::io::{self, BufWriter, Cursor}; use std::iter::repeat; use std::path::{Path, PathBuf}; use filetime::FileTime; -use tar::{Archive, Builder, Entries, EntryType, Header, HeaderMode}; +use tar::{Archive, Builder, Entries, Entry, EntryType, Header, HeaderMode}; use tempfile::{Builder as TempBuilder, TempDir}; macro_rules! t { @@ -925,6 +925,40 @@ fn pax_simple() { assert_eq!(third.value(), Ok("1453146164.953123768")); } +#[test] +fn pax_simple_write() { + let td = t!(TempBuilder::new().prefix("tar-rs").tempdir()); + let pax_path = td.path().join("pax.tar"); + let file: File = t!(File::create(&pax_path)); + let mut ar: Builder> = Builder::new(BufWriter::new(file)); + + let pax_extensions = [ + ("arbitrary_pax_key", b"arbitrary_pax_value".as_slice()), + ("SCHILY.xattr.security.selinux", b"foo_t"), + ]; + + t!(ar.append_pax_extensions(pax_extensions)); + t!(ar.append_file("test2", &mut t!(File::open(&pax_path)))); + t!(ar.finish()); + drop(ar); + + let mut archive_opened = Archive::new(t!(File::open(pax_path))); + let mut entries = t!(archive_opened.entries()); + let mut f: Entry = t!(entries.next().unwrap()); + let pax_headers = t!(f.pax_extensions()); + + assert!(pax_headers.is_some(), "pax_headers is None"); + let mut pax_headers = pax_headers.unwrap(); + let pax_arbitrary = t!(pax_headers.next().unwrap()); + assert_eq!(pax_arbitrary.key(), Ok("arbitrary_pax_key")); + assert_eq!(pax_arbitrary.value(), Ok("arbitrary_pax_value")); + let xattr = t!(pax_headers.next().unwrap()); + assert_eq!(xattr.key().unwrap(), pax_extensions[1].0); + assert_eq!(xattr.value_bytes(), pax_extensions[1].1); + + assert!(entries.next().is_none()); +} + #[test] fn pax_path() { let mut ar = Archive::new(tar!("pax2.tar")); diff --git a/tests/entry.rs b/tests/entry.rs index 62df663e..4d612e2c 100644 --- a/tests/entry.rs +++ b/tests/entry.rs @@ -1,7 +1,8 @@ extern crate tar; extern crate tempfile; -use std::fs::{create_dir, File}; +use std::fs::create_dir; +use std::fs::File; use std::io::Read; use tempfile::Builder;