From 3c868636652283baf302ecd74a5167a3215604f8 Mon Sep 17 00:00:00 2001 From: Gaspard WITRAND Date: Tue, 22 Oct 2024 09:36:01 +0200 Subject: [PATCH 1/2] 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 --- src/pax.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++++ tests/all.rs | 36 ++++++++++++++++++++++++++++++++++-- tests/entry.rs | 3 ++- 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/src/pax.rs b/src/pax.rs index 6e83edce..5a06967f 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,48 @@ 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<'a>( + &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, "{} {}={}\n", len, key, value)?; + } + + // 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 27a6fcf1..9f93de49 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,38 @@ 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 mut pax_builder: Vec<(String, String)> = Vec::new(); + let key: String = "arbitrary_pax_key".to_string(); + let value: String = "arbitrary_pax_value".to_string(); + pax_builder.push((key, value)); + + t!(ar.append_pax_extensions(pax_builder.into_iter())); + 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")); + + 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; From 6d47fbceb9df2417465a1c2c2e1c0f3f6b264a6a Mon Sep 17 00:00:00 2001 From: Gaspard WITRAND Date: Wed, 23 Oct 2024 10:21:36 +0200 Subject: [PATCH 2/2] 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 | 8 +++++--- tests/all.rs | 14 ++++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/pax.rs b/src/pax.rs index 5a06967f..d1494282 100644 --- a/src/pax.rs +++ b/src/pax.rs @@ -154,9 +154,9 @@ impl crate::Builder { /// 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<'a>( + pub fn append_pax_extensions<'key, 'value>( &mut self, - headers: impl IntoIterator, + headers: impl IntoIterator, ) -> Result<(), io::Error> { // Store the headers formatted before write let mut data: Vec = Vec::new(); @@ -172,7 +172,9 @@ impl crate::Builder { max_len *= 10; } let len = rest_len + len_len; - write!(&mut data, "{} {}={}\n", len, key, value)?; + write!(&mut data, "{} {}=", len, key)?; + data.extend_from_slice(value); + data.push(b'\n'); } // Ignore the header append if it's empty. diff --git a/tests/all.rs b/tests/all.rs index 9f93de49..3caff4b6 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -932,12 +932,12 @@ fn pax_simple_write() { let file: File = t!(File::create(&pax_path)); let mut ar: Builder> = Builder::new(BufWriter::new(file)); - let mut pax_builder: Vec<(String, String)> = Vec::new(); - let key: String = "arbitrary_pax_key".to_string(); - let value: String = "arbitrary_pax_value".to_string(); - pax_builder.push((key, value)); + 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_builder.into_iter())); + t!(ar.append_pax_extensions(pax_extensions)); t!(ar.append_file("test2", &mut t!(File::open(&pax_path)))); t!(ar.finish()); drop(ar); @@ -950,9 +950,11 @@ fn pax_simple_write() { 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()); }