From dc035980efc8faf9366906f990e6fe3e0d3afa98 Mon Sep 17 00:00:00 2001 From: Attila Repka Date: Sun, 9 Apr 2023 17:01:01 +0200 Subject: [PATCH 01/10] Add case for sparse r/w from same source --- tests/all.rs | 206 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) diff --git a/tests/all.rs b/tests/all.rs index 1e7b264e..3e58a071 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -4,6 +4,7 @@ extern crate tempfile; #[cfg(all(unix, feature = "xattr"))] extern crate xattr; +use std::convert::TryInto; use std::fs::{self, File}; use std::io::prelude::*; use std::io::{self, Cursor}; @@ -1029,6 +1030,211 @@ fn encoded_long_name_has_trailing_nul() { assert!(header_name.starts_with(b"././@LongLink\x00")); } +#[test] +fn write_sparse_to_vec_and_read_again() { + let rdr = Cursor::new(tar!("sparse.tar")); + let mut ar = Archive::new(rdr); + let mut entries = t!(ar.entries()); + + let mut tar = tar::Builder::new(Vec::new()); + + let mut a = t!(entries.next().unwrap()); + let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; + a.read_exact(&mut bytes).unwrap(); + assert_eq!(&*a.header().path_bytes(), b"sparse_begin.txt"); + assert_eq!(std::str::from_utf8(&bytes[..5]).unwrap(), "test\n"); + let mut b = String::new(); + a.read_to_string(&mut b).unwrap(); + let data = String::from("test\n"); + tar.append(&mut a.header().to_owned(), data.as_bytes()) + .unwrap(); + + let mut a = t!(entries.next().unwrap()); + let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; + a.read_exact(&mut bytes).unwrap(); + assert_eq!(&*a.header().path_bytes(), b"sparse_end.txt"); + assert_eq!( + std::str::from_utf8(&bytes[bytes.len() - 9..]).unwrap(), + "test_end\n" + ); + let data = String::from("test_end\n"); + tar.append(&mut a.header().to_owned(), data.as_bytes()) + .unwrap(); + + let mut a = t!(entries.next().unwrap()); + let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; + a.read_exact(&mut bytes).unwrap(); + assert_eq!(&*a.header().path_bytes(), b"sparse_ext.txt"); + assert!(std::str::from_utf8(&bytes[..0x1000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0x1000..0x1000 + 5]).unwrap(), + "text\n" + ); + assert!(std::str::from_utf8(&bytes[0x1000 + 5..0x3000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0x3000..0x3000 + 5]).unwrap(), + "text\n" + ); + assert!(std::str::from_utf8(&bytes[0x3000 + 5..0x5000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0x5000..0x5000 + 5]).unwrap(), + "text\n" + ); + assert!(std::str::from_utf8(&bytes[0x5000 + 5..0x7000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0x7000..0x7000 + 5]).unwrap(), + "text\n" + ); + assert!(std::str::from_utf8(&bytes[0x7000 + 5..0x9000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0x9000..0x9000 + 5]).unwrap(), + "text\n" + ); + assert!(std::str::from_utf8(&bytes[0x9000 + 5..0xb000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0xb000..0xb000 + 5]).unwrap(), + "text\n" + ); + let data = String::from("text\n"); + tar.append(&mut a.header().to_owned(), data.as_bytes()) + .unwrap(); + tar.append(&mut a.header().to_owned(), data.as_bytes()) + .unwrap(); + tar.append(&mut a.header().to_owned(), data.as_bytes()) + .unwrap(); + tar.append(&mut a.header().to_owned(), data.as_bytes()) + .unwrap(); + tar.append(&mut a.header().to_owned(), data.as_bytes()) + .unwrap(); + tar.append(&mut a.header().to_owned(), data.as_bytes()) + .unwrap(); + + let mut a = t!(entries.next().unwrap()); + let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; + a.read_exact(&mut bytes).unwrap(); + assert_eq!(&*a.header().path_bytes(), b"sparse.txt"); + assert_eq!( + std::str::from_utf8(&bytes[0x1000..0x1000 + 6]).unwrap(), + "hello\n" + ); + assert_eq!( + std::str::from_utf8(&bytes[0x2fa0..0x2fa0 + 6]).unwrap(), + "world\n" + ); + tar.append(&mut a.header().to_owned(), &*bytes).unwrap(); + + assert!(entries.next().is_none()); + + let rdr = Cursor::new(t!(tar.into_inner())); + let mut ar = Archive::new(rdr); + let mut entries = t!(ar.entries()); + + let mut a = t!(entries.next().unwrap()); + let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; + a.read_exact(&mut bytes).unwrap(); + assert_eq!(&*a.header().path_bytes(), b"sparse_begin.txt"); + assert_eq!(std::str::from_utf8(&bytes[..5]).unwrap(), "test\n"); + + let mut a = t!(entries.next().unwrap()); + let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; + a.read_exact(&mut bytes).unwrap(); + assert_eq!(&*a.header().path_bytes(), b"sparse_end.txt"); + assert!(std::str::from_utf8(&bytes[..bytes.len() - 9]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[bytes.len() - 9..]).unwrap(), + "test_end\n" + ); + + let mut a = t!(entries.next().unwrap()); + let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; + a.read_exact(&mut bytes).unwrap(); + assert_eq!(&*a.header().path_bytes(), b"sparse_ext.txt"); + assert!(std::str::from_utf8(&bytes[..0x1000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0x1000..0x1000 + 5]).unwrap(), + "text\n" + ); + assert!(std::str::from_utf8(&bytes[0x1000 + 5..0x3000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0x3000..0x3000 + 5]).unwrap(), + "text\n" + ); + assert!(std::str::from_utf8(&bytes[0x3000 + 5..0x5000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0x5000..0x5000 + 5]).unwrap(), + "text\n" + ); + assert!(std::str::from_utf8(&bytes[0x5000 + 5..0x7000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0x7000..0x7000 + 5]).unwrap(), + "text\n" + ); + assert!(std::str::from_utf8(&bytes[0x7000 + 5..0x9000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0x9000..0x9000 + 5]).unwrap(), + "text\n" + ); + assert!(std::str::from_utf8(&bytes[0x9000 + 5..0xb000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0xb000..0xb000 + 5]).unwrap(), + "text\n" + ); + + let mut a = t!(entries.next().unwrap()); + let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; + a.read_exact(&mut bytes).unwrap(); + assert_eq!(&*a.header().path_bytes(), b"sparse.txt"); + assert_eq!( + std::str::from_utf8(&bytes[0x1000..0x1000 + 6]).unwrap(), + "hello\n" + ); + assert_eq!( + std::str::from_utf8(&bytes[0x2fa0..0x2fa0 + 6]).unwrap(), + "world\n" + ); + + assert!(entries.next().is_none()); +} + #[test] fn reading_sparse() { let rdr = Cursor::new(tar!("sparse.tar")); From 6933005dfb16dbed05702447a0f5e1ef83d7b441 Mon Sep 17 00:00:00 2001 From: Attila Repka Date: Mon, 10 Apr 2023 16:18:00 +0200 Subject: [PATCH 02/10] wip: try to fix gnu sparse case --- tests/all.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/tests/all.rs b/tests/all.rs index 3e58a071..8b3af24e 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -1043,22 +1043,19 @@ fn write_sparse_to_vec_and_read_again() { a.read_exact(&mut bytes).unwrap(); assert_eq!(&*a.header().path_bytes(), b"sparse_begin.txt"); assert_eq!(std::str::from_utf8(&bytes[..5]).unwrap(), "test\n"); - let mut b = String::new(); - a.read_to_string(&mut b).unwrap(); - let data = String::from("test\n"); - tar.append(&mut a.header().to_owned(), data.as_bytes()) + tar.append(&a.header(), &*bytes.get(..512).unwrap()) .unwrap(); let mut a = t!(entries.next().unwrap()); let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; a.read_exact(&mut bytes).unwrap(); + assert_eq!(&*a.header().path_bytes(), b"sparse_end.txt"); assert_eq!( std::str::from_utf8(&bytes[bytes.len() - 9..]).unwrap(), "test_end\n" ); - let data = String::from("test_end\n"); - tar.append(&mut a.header().to_owned(), data.as_bytes()) + tar.append(&a.header(), &*bytes.get(bytes.len() - 425..).unwrap()) .unwrap(); let mut a = t!(entries.next().unwrap()); @@ -1113,18 +1110,18 @@ fn write_sparse_to_vec_and_read_again() { std::str::from_utf8(&bytes[0xb000..0xb000 + 5]).unwrap(), "text\n" ); - let data = String::from("text\n"); - tar.append(&mut a.header().to_owned(), data.as_bytes()) + tar.append(&a.header(), &*bytes.get(4096..4096 + 512).unwrap()) .unwrap(); - tar.append(&mut a.header().to_owned(), data.as_bytes()) + tar.append(&a.header(), &*bytes.get(12288..12288 + 512).unwrap()) .unwrap(); - tar.append(&mut a.header().to_owned(), data.as_bytes()) + tar.append(&a.header(), &*bytes.get(20480..20480 + 512).unwrap()) .unwrap(); - tar.append(&mut a.header().to_owned(), data.as_bytes()) + tar.append(&a.header(), &*bytes.get(28672..28672 + 512).unwrap()) .unwrap(); - tar.append(&mut a.header().to_owned(), data.as_bytes()) + // TODO: get extended header + tar.append(&a.header(), &*bytes.get(36864..36864 + 512).unwrap()) .unwrap(); - tar.append(&mut a.header().to_owned(), data.as_bytes()) + tar.append(&a.header(), &*bytes.get(45056..45056 + 5).unwrap()) .unwrap(); let mut a = t!(entries.next().unwrap()); @@ -1139,10 +1136,11 @@ fn write_sparse_to_vec_and_read_again() { std::str::from_utf8(&bytes[0x2fa0..0x2fa0 + 6]).unwrap(), "world\n" ); - tar.append(&mut a.header().to_owned(), &*bytes).unwrap(); + tar.append(&a.header(), &*bytes).unwrap(); assert!(entries.next().is_none()); + // read back entries from memory let rdr = Cursor::new(t!(tar.into_inner())); let mut ar = Archive::new(rdr); let mut entries = t!(ar.entries()); From e1c9ccb303a72735cfa12195a36ba54e94d480da Mon Sep 17 00:00:00 2001 From: Attila Repka Date: Mon, 22 May 2023 17:13:28 +0200 Subject: [PATCH 03/10] refactor: cleanup testcase --- tests/all.rs | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/tests/all.rs b/tests/all.rs index 8b3af24e..389e5ce1 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -1043,8 +1043,7 @@ fn write_sparse_to_vec_and_read_again() { a.read_exact(&mut bytes).unwrap(); assert_eq!(&*a.header().path_bytes(), b"sparse_begin.txt"); assert_eq!(std::str::from_utf8(&bytes[..5]).unwrap(), "test\n"); - tar.append(&a.header(), &*bytes.get(..512).unwrap()) - .unwrap(); + tar.append(&a.header(), bytes.get(..512).unwrap()).unwrap(); let mut a = t!(entries.next().unwrap()); let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; @@ -1055,13 +1054,15 @@ fn write_sparse_to_vec_and_read_again() { std::str::from_utf8(&bytes[bytes.len() - 9..]).unwrap(), "test_end\n" ); - tar.append(&a.header(), &*bytes.get(bytes.len() - 425..).unwrap()) + tar.append(&a.header(), bytes.get(bytes.len() - 425..).unwrap()) .unwrap(); let mut a = t!(entries.next().unwrap()); let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; a.read_exact(&mut bytes).unwrap(); + assert_eq!(&*a.header().path_bytes(), b"sparse_ext.txt"); + assert!(std::str::from_utf8(&bytes[..0x1000]) .unwrap() .chars() @@ -1110,33 +1111,39 @@ fn write_sparse_to_vec_and_read_again() { std::str::from_utf8(&bytes[0xb000..0xb000 + 5]).unwrap(), "text\n" ); - tar.append(&a.header(), &*bytes.get(4096..4096 + 512).unwrap()) + + tar.append(&a.header(), bytes.get(4096..4096 + 512).unwrap()) .unwrap(); - tar.append(&a.header(), &*bytes.get(12288..12288 + 512).unwrap()) + tar.append(&a.header(), bytes.get(12288..12288 + 512).unwrap()) .unwrap(); - tar.append(&a.header(), &*bytes.get(20480..20480 + 512).unwrap()) + tar.append(&a.header(), bytes.get(20480..20480 + 512).unwrap()) .unwrap(); - tar.append(&a.header(), &*bytes.get(28672..28672 + 512).unwrap()) + tar.append(&a.header(), bytes.get(28672..28672 + 512).unwrap()) .unwrap(); // TODO: get extended header - tar.append(&a.header(), &*bytes.get(36864..36864 + 512).unwrap()) + tar.append(&a.header(), bytes.get(36864..36864 + 512).unwrap()) .unwrap(); - tar.append(&a.header(), &*bytes.get(45056..45056 + 5).unwrap()) + tar.append(&a.header(), bytes.get(45056..45056 + 5).unwrap()) .unwrap(); let mut a = t!(entries.next().unwrap()); let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; a.read_exact(&mut bytes).unwrap(); + assert_eq!(&*a.header().path_bytes(), b"sparse.txt"); assert_eq!( - std::str::from_utf8(&bytes[0x1000..0x1000 + 6]).unwrap(), + std::str::from_utf8(bytes.get(0x1000..0x1000 + 6).unwrap()).unwrap(), "hello\n" ); assert_eq!( - std::str::from_utf8(&bytes[0x2fa0..0x2fa0 + 6]).unwrap(), + std::str::from_utf8(bytes.get(0x2fa0..0x2fa0 + 6).unwrap()).unwrap(), "world\n" ); - tar.append(&a.header(), &*bytes).unwrap(); + // TODO: implement append_sparse + tar.append(&a.header(), bytes.get(0x1000..0x1000 + 6).unwrap()) + .unwrap(); + // tar.append(&a.header(), bytes.get(0x2fa0..0x2fa0 + 6).unwrap()) + // .unwrap(); assert!(entries.next().is_none()); @@ -1220,6 +1227,7 @@ fn write_sparse_to_vec_and_read_again() { let mut a = t!(entries.next().unwrap()); let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; a.read_exact(&mut bytes).unwrap(); + assert_eq!(&*a.header().path_bytes(), b"sparse.txt"); assert_eq!( std::str::from_utf8(&bytes[0x1000..0x1000 + 6]).unwrap(), From 7f34b28275e9833ea8ea0758d044271876c83ec7 Mon Sep 17 00:00:00 2001 From: Attila Repka Date: Fri, 26 May 2023 12:29:55 +0200 Subject: [PATCH 04/10] feat: add append_sparse to builder wip --- src/builder.rs | 141 ++++++++++++++++++++++++++++++++++++-- tests/all.rs | 136 ++---------------------------------- tests/archives/sparse.tar | Bin 10240 -> 10240 bytes 3 files changed, 141 insertions(+), 136 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index a26eb31d..ddd2589f 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -5,6 +5,8 @@ use std::path::Path; use std::str; use crate::header::{path2bytes, HeaderMode}; +use crate::GnuExtSparseHeader; +use crate::GnuSparseHeader; use crate::{other, EntryType, Header}; /// A structure for building archives @@ -71,6 +73,18 @@ impl Builder { Ok(self.obj.take().unwrap()) } + /// BLbalblaa + /// + /// Blablabal + /// + /// # Errors + /// + /// This function will return an error if . + // TODO: documentation + pub fn append_sparse_all(&mut self, header: &Header, mut data: R) -> io::Result<()> { + append_sparse_all(self.get_mut(), header, &mut data) + } + /// Adds a new entry to this archive. /// /// This function will append the header specified, followed by contents of @@ -406,16 +420,123 @@ impl Builder { } } +fn append_sparse_all( + mut dst: &mut dyn Write, + header: &Header, + data: &mut dyn Read, +) -> io::Result<()> { + if !header.entry_type().is_gnu_sparse() { + return Ok(()); + } + let gnu = match header.as_gnu() { + Some(gnu) => gnu, + None => return Err(other("sparse entry type listed but not GNU header")), + }; + + dst.write_all(header.as_bytes())?; + + let mut cur = 0; + let mut remaining = header.entry_size()?; + { + let size = header.entry_size()?; + let mut reader = data; + let mut add_block = |r: &mut dyn Read, 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 mut buf = vec![Default::default(); (off - cur) as usize]; + r.read_exact(&mut buf)?; + } + 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", + ) + })?; + let mut buf = vec![0; len as usize]; + r.read_exact(&mut buf)?; + dst.write_all(&mut buf)?; + + if len > 0 && len < 512 { + pad_with_zeroes(&mut dst, len)?; + } + + Ok(()) + }; + for block in gnu.sparse.iter() { + add_block(&mut reader, block)? + } + + if gnu.is_extended() { + let try_read_all = |r: &mut dyn Read, buf: &mut [u8]| -> io::Result { + 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) + }; + let mut ext = GnuExtSparseHeader::new(); + ext.isextended[0] = 1; + while ext.is_extended() { + if !try_read_all(&mut reader, ext.as_mut_bytes())? { + return Err(other("failed to read extension")); + } + + for block in ext.sparse.iter() { + add_block(&mut reader, block)?; + } + } + } + } + + if cur != gnu.real_size()? { + return Err(other( + "mismatch in sparse file chunks and \ + size in header", + )); + } + + if remaining > 0 { + return Err(other( + "mismatch in sparse file chunks and \ + entry size in header", + )); + } + + Ok(()) +} + 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])?; - } + pad_with_zeroes(&mut dst, len)?; Ok(()) } @@ -533,6 +654,16 @@ fn append_dir( append_fs(dst, path, &stat, &mut io::empty(), mode, None) } +fn pad_with_zeroes(dst: &mut dyn Write, len: u64) -> io::Result<()> { + let buf = [0; 512]; + let remaining = 512 - (len % 512); + if remaining < 512 { + dst.write_all(&buf[..remaining as usize])?; + } + + Ok(()) +} + fn prepare_header(size: u64, entry_type: u8) -> Header { let mut header = Header::new_gnu(); let name = b"././@LongLink"; diff --git a/tests/all.rs b/tests/all.rs index 389e5ce1..d883b34d 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -1043,7 +1043,7 @@ fn write_sparse_to_vec_and_read_again() { a.read_exact(&mut bytes).unwrap(); assert_eq!(&*a.header().path_bytes(), b"sparse_begin.txt"); assert_eq!(std::str::from_utf8(&bytes[..5]).unwrap(), "test\n"); - tar.append(&a.header(), bytes.get(..512).unwrap()).unwrap(); + tar.append_sparse_all(&a.header(), &*bytes).unwrap(); let mut a = t!(entries.next().unwrap()); let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; @@ -1054,77 +1054,7 @@ fn write_sparse_to_vec_and_read_again() { std::str::from_utf8(&bytes[bytes.len() - 9..]).unwrap(), "test_end\n" ); - tar.append(&a.header(), bytes.get(bytes.len() - 425..).unwrap()) - .unwrap(); - - let mut a = t!(entries.next().unwrap()); - let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; - a.read_exact(&mut bytes).unwrap(); - - assert_eq!(&*a.header().path_bytes(), b"sparse_ext.txt"); - - assert!(std::str::from_utf8(&bytes[..0x1000]) - .unwrap() - .chars() - .all(|x| x == '\u{0}')); - assert_eq!( - std::str::from_utf8(&bytes[0x1000..0x1000 + 5]).unwrap(), - "text\n" - ); - assert!(std::str::from_utf8(&bytes[0x1000 + 5..0x3000]) - .unwrap() - .chars() - .all(|x| x == '\u{0}')); - assert_eq!( - std::str::from_utf8(&bytes[0x3000..0x3000 + 5]).unwrap(), - "text\n" - ); - assert!(std::str::from_utf8(&bytes[0x3000 + 5..0x5000]) - .unwrap() - .chars() - .all(|x| x == '\u{0}')); - assert_eq!( - std::str::from_utf8(&bytes[0x5000..0x5000 + 5]).unwrap(), - "text\n" - ); - assert!(std::str::from_utf8(&bytes[0x5000 + 5..0x7000]) - .unwrap() - .chars() - .all(|x| x == '\u{0}')); - assert_eq!( - std::str::from_utf8(&bytes[0x7000..0x7000 + 5]).unwrap(), - "text\n" - ); - assert!(std::str::from_utf8(&bytes[0x7000 + 5..0x9000]) - .unwrap() - .chars() - .all(|x| x == '\u{0}')); - assert_eq!( - std::str::from_utf8(&bytes[0x9000..0x9000 + 5]).unwrap(), - "text\n" - ); - assert!(std::str::from_utf8(&bytes[0x9000 + 5..0xb000]) - .unwrap() - .chars() - .all(|x| x == '\u{0}')); - assert_eq!( - std::str::from_utf8(&bytes[0xb000..0xb000 + 5]).unwrap(), - "text\n" - ); - - tar.append(&a.header(), bytes.get(4096..4096 + 512).unwrap()) - .unwrap(); - tar.append(&a.header(), bytes.get(12288..12288 + 512).unwrap()) - .unwrap(); - tar.append(&a.header(), bytes.get(20480..20480 + 512).unwrap()) - .unwrap(); - tar.append(&a.header(), bytes.get(28672..28672 + 512).unwrap()) - .unwrap(); - // TODO: get extended header - tar.append(&a.header(), bytes.get(36864..36864 + 512).unwrap()) - .unwrap(); - tar.append(&a.header(), bytes.get(45056..45056 + 5).unwrap()) - .unwrap(); + tar.append_sparse_all(&a.header(), &*bytes).unwrap(); let mut a = t!(entries.next().unwrap()); let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; @@ -1135,15 +1065,12 @@ fn write_sparse_to_vec_and_read_again() { std::str::from_utf8(bytes.get(0x1000..0x1000 + 6).unwrap()).unwrap(), "hello\n" ); + assert_eq!( std::str::from_utf8(bytes.get(0x2fa0..0x2fa0 + 6).unwrap()).unwrap(), "world\n" ); - // TODO: implement append_sparse - tar.append(&a.header(), bytes.get(0x1000..0x1000 + 6).unwrap()) - .unwrap(); - // tar.append(&a.header(), bytes.get(0x2fa0..0x2fa0 + 6).unwrap()) - // .unwrap(); + tar.append_sparse_all(&a.header(), &*bytes).unwrap(); assert!(entries.next().is_none()); @@ -1171,59 +1098,6 @@ fn write_sparse_to_vec_and_read_again() { "test_end\n" ); - let mut a = t!(entries.next().unwrap()); - let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; - a.read_exact(&mut bytes).unwrap(); - assert_eq!(&*a.header().path_bytes(), b"sparse_ext.txt"); - assert!(std::str::from_utf8(&bytes[..0x1000]) - .unwrap() - .chars() - .all(|x| x == '\u{0}')); - assert_eq!( - std::str::from_utf8(&bytes[0x1000..0x1000 + 5]).unwrap(), - "text\n" - ); - assert!(std::str::from_utf8(&bytes[0x1000 + 5..0x3000]) - .unwrap() - .chars() - .all(|x| x == '\u{0}')); - assert_eq!( - std::str::from_utf8(&bytes[0x3000..0x3000 + 5]).unwrap(), - "text\n" - ); - assert!(std::str::from_utf8(&bytes[0x3000 + 5..0x5000]) - .unwrap() - .chars() - .all(|x| x == '\u{0}')); - assert_eq!( - std::str::from_utf8(&bytes[0x5000..0x5000 + 5]).unwrap(), - "text\n" - ); - assert!(std::str::from_utf8(&bytes[0x5000 + 5..0x7000]) - .unwrap() - .chars() - .all(|x| x == '\u{0}')); - assert_eq!( - std::str::from_utf8(&bytes[0x7000..0x7000 + 5]).unwrap(), - "text\n" - ); - assert!(std::str::from_utf8(&bytes[0x7000 + 5..0x9000]) - .unwrap() - .chars() - .all(|x| x == '\u{0}')); - assert_eq!( - std::str::from_utf8(&bytes[0x9000..0x9000 + 5]).unwrap(), - "text\n" - ); - assert!(std::str::from_utf8(&bytes[0x9000 + 5..0xb000]) - .unwrap() - .chars() - .all(|x| x == '\u{0}')); - assert_eq!( - std::str::from_utf8(&bytes[0xb000..0xb000 + 5]).unwrap(), - "text\n" - ); - let mut a = t!(entries.next().unwrap()); let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; a.read_exact(&mut bytes).unwrap(); @@ -1293,7 +1167,7 @@ fn reading_sparse() { #[test] fn extract_sparse() { - let rdr = Cursor::new(tar!("sparse.tar")); + let rdr = Cursor::new(tar!("sparse-ext.tar")); let mut ar = Archive::new(rdr); let td = t!(TempBuilder::new().prefix("tar-rs").tempdir()); t!(ar.unpack(td.path())); diff --git a/tests/archives/sparse.tar b/tests/archives/sparse.tar index 216aed1d780cf5ec5e1b2143e284bdbc362124aa..6f870c0e33de708b8f1ef003b6595b49d84f5111 100644 GIT binary patch delta 22 ccmZn&Xb9NA#<7`A;urts1F{ZWK;i%^0AN1|XaE2J delta 257 zcmZn&Xb9NA#=#MvT2Z1`Qc*H-Vc5hJc_~u^15*Y=BXdIoa}y&IV`BybBV#iYLk5N5 z$%0HNlNmYZ`xzSm0RxBt0z)8%vVmIRY-DkB3~@$~h=C!; Date: Sun, 28 May 2023 20:43:12 +0200 Subject: [PATCH 05/10] feat: add append_sparse_all wip --- src/archive.rs | 2 + src/builder.rs | 51 +++++----------- src/entry.rs | 7 +++ tests/all.rs | 120 ++++++++++++++++++++++++++++++++++++-- tests/archives/sparse.tar | Bin 10240 -> 10240 bytes 5 files changed, 140 insertions(+), 40 deletions(-) diff --git a/src/archive.rs b/src/archive.rs index 760b2cb8..e71a9e6f 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -341,6 +341,7 @@ impl<'a> EntriesFields<'a> { file_pos: file_pos, data: vec![EntryIo::Data((&self.archive.inner).take(size))], header: header, + ext_header: GnuExtSparseHeader::new(), long_pathname: None, long_linkname: None, pax_extensions: None, @@ -516,6 +517,7 @@ impl<'a> EntriesFields<'a> { add_block(block)?; } } + entry.ext_header = ext; } } if cur != gnu.real_size()? { diff --git a/src/builder.rs b/src/builder.rs index ddd2589f..06eadcc5 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -3,6 +3,7 @@ use std::io; use std::io::prelude::*; use std::path::Path; use std::str; +use std::vec; use crate::header::{path2bytes, HeaderMode}; use crate::GnuExtSparseHeader; @@ -73,16 +74,15 @@ impl Builder { Ok(self.obj.take().unwrap()) } - /// BLbalblaa - /// - /// Blablabal - /// - /// # Errors - /// - /// This function will return an error if . // TODO: documentation - pub fn append_sparse_all(&mut self, header: &Header, mut data: R) -> io::Result<()> { - append_sparse_all(self.get_mut(), header, &mut data) + #[allow(missing_docs)] + pub fn append_sparse_all( + &mut self, + header: &Header, + ext_header: &GnuExtSparseHeader, + mut data: R, + ) -> io::Result<()> { + append_sparse_all(self.get_mut(), header, ext_header, &mut data) } /// Adds a new entry to this archive. @@ -423,6 +423,7 @@ impl Builder { fn append_sparse_all( mut dst: &mut dyn Write, header: &Header, + ext_header: &GnuExtSparseHeader, data: &mut dyn Read, ) -> io::Result<()> { if !header.entry_type().is_gnu_sparse() { @@ -434,6 +435,9 @@ fn append_sparse_all( }; dst.write_all(header.as_bytes())?; + if gnu.is_extended() { + dst.write_all(ext_header.as_bytes())?; + } let mut cur = 0; let mut remaining = header.entry_size()?; @@ -482,34 +486,9 @@ fn append_sparse_all( for block in gnu.sparse.iter() { add_block(&mut reader, block)? } - if gnu.is_extended() { - let try_read_all = |r: &mut dyn Read, buf: &mut [u8]| -> io::Result { - 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) - }; - let mut ext = GnuExtSparseHeader::new(); - ext.isextended[0] = 1; - while ext.is_extended() { - if !try_read_all(&mut reader, ext.as_mut_bytes())? { - return Err(other("failed to read extension")); - } - - for block in ext.sparse.iter() { - add_block(&mut reader, block)?; - } + for block in ext_header.sparse.iter() { + add_block(&mut reader, block)?; } } } diff --git a/src/entry.rs b/src/entry.rs index c5cef6b7..61bb8a61 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -13,6 +13,7 @@ use crate::archive::ArchiveInner; use crate::error::TarError; use crate::header::bytes2path; use crate::other; +use crate::GnuExtSparseHeader; use crate::{Archive, Header, PaxExtensions}; /// A read-only view into an entry of an archive. @@ -33,6 +34,7 @@ pub struct EntryFields<'a> { pub pax_extensions: Option>, pub mask: u32, pub header: Header, + pub ext_header: GnuExtSparseHeader, pub size: u64, pub header_pos: u64, pub file_pos: u64, @@ -143,6 +145,11 @@ impl<'a, R: Read> Entry<'a, R> { &self.fields.header } + #[allow(missing_docs)] + pub fn ext_header(&self) -> &GnuExtSparseHeader { + &self.fields.ext_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 diff --git a/tests/all.rs b/tests/all.rs index d883b34d..826dabbe 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -1043,7 +1043,8 @@ fn write_sparse_to_vec_and_read_again() { a.read_exact(&mut bytes).unwrap(); assert_eq!(&*a.header().path_bytes(), b"sparse_begin.txt"); assert_eq!(std::str::from_utf8(&bytes[..5]).unwrap(), "test\n"); - tar.append_sparse_all(&a.header(), &*bytes).unwrap(); + tar.append_sparse_all(&a.header(), a.ext_header(), &*bytes) + .unwrap(); let mut a = t!(entries.next().unwrap()); let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; @@ -1054,7 +1055,63 @@ fn write_sparse_to_vec_and_read_again() { std::str::from_utf8(&bytes[bytes.len() - 9..]).unwrap(), "test_end\n" ); - tar.append_sparse_all(&a.header(), &*bytes).unwrap(); + tar.append_sparse_all(&a.header(), a.ext_header(), &*bytes) + .unwrap(); + + let mut a = t!(entries.next().unwrap()); + let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; + a.read_exact(&mut bytes).unwrap(); + assert_eq!(&*a.header().path_bytes(), b"sparse_ext.txt"); + assert!(std::str::from_utf8(&bytes[..0x1000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0x1000..0x1000 + 5]).unwrap(), + "text\n" + ); + assert!(std::str::from_utf8(&bytes[0x1000 + 5..0x3000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0x3000..0x3000 + 5]).unwrap(), + "text\n" + ); + assert!(std::str::from_utf8(&bytes[0x3000 + 5..0x5000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0x5000..0x5000 + 5]).unwrap(), + "text\n" + ); + assert!(std::str::from_utf8(&bytes[0x5000 + 5..0x7000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0x7000..0x7000 + 5]).unwrap(), + "text\n" + ); + assert!(std::str::from_utf8(&bytes[0x7000 + 5..0x9000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0x9000..0x9000 + 5]).unwrap(), + "text\n" + ); + assert!(std::str::from_utf8(&bytes[0x9000 + 5..0xb000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0xb000..0xb000 + 5]).unwrap(), + "text\n" + ); + tar.append_sparse_all(&a.header(), a.ext_header(), &*bytes) + .unwrap(); let mut a = t!(entries.next().unwrap()); let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; @@ -1070,7 +1127,8 @@ fn write_sparse_to_vec_and_read_again() { std::str::from_utf8(bytes.get(0x2fa0..0x2fa0 + 6).unwrap()).unwrap(), "world\n" ); - tar.append_sparse_all(&a.header(), &*bytes).unwrap(); + tar.append_sparse_all(&a.header(), a.ext_header(), &*bytes) + .unwrap(); assert!(entries.next().is_none()); @@ -1102,6 +1160,60 @@ fn write_sparse_to_vec_and_read_again() { let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; a.read_exact(&mut bytes).unwrap(); + assert_eq!(&*a.header().path_bytes(), b"sparse_ext.txt"); + assert!(std::str::from_utf8(&bytes[..0x1000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0x1000..0x1000 + 5]).unwrap(), + "text\n" + ); + assert!(std::str::from_utf8(&bytes[0x1000 + 5..0x3000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0x3000..0x3000 + 5]).unwrap(), + "text\n" + ); + assert!(std::str::from_utf8(&bytes[0x3000 + 5..0x5000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0x5000..0x5000 + 5]).unwrap(), + "text\n" + ); + assert!(std::str::from_utf8(&bytes[0x5000 + 5..0x7000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0x7000..0x7000 + 5]).unwrap(), + "text\n" + ); + assert!(std::str::from_utf8(&bytes[0x7000 + 5..0x9000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0x9000..0x9000 + 5]).unwrap(), + "text\n" + ); + assert!(std::str::from_utf8(&bytes[0x9000 + 5..0xb000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0xb000..0xb000 + 5]).unwrap(), + "text\n" + ); + + let mut a = t!(entries.next().unwrap()); + let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; + a.read_exact(&mut bytes).unwrap(); + assert_eq!(&*a.header().path_bytes(), b"sparse.txt"); assert_eq!( std::str::from_utf8(&bytes[0x1000..0x1000 + 6]).unwrap(), @@ -1167,7 +1279,7 @@ fn reading_sparse() { #[test] fn extract_sparse() { - let rdr = Cursor::new(tar!("sparse-ext.tar")); + let rdr = Cursor::new(tar!("sparse.tar")); let mut ar = Archive::new(rdr); let td = t!(TempBuilder::new().prefix("tar-rs").tempdir()); t!(ar.unpack(td.path())); diff --git a/tests/archives/sparse.tar b/tests/archives/sparse.tar index 6f870c0e33de708b8f1ef003b6595b49d84f5111..216aed1d780cf5ec5e1b2143e284bdbc362124aa 100644 GIT binary patch delta 257 zcmZn&Xb9NA#=#MvT2Z1`Qc*H-Vc5hJc_~u^15*Y=BXdIoa}y&IV`BybBV#iYLk5N5 z$%0HNlNmYZ`xzSm0RxBt0z)8%vVmIRY-DkB3~@$~h=C!; Date: Mon, 29 May 2023 12:48:27 +0200 Subject: [PATCH 06/10] feat: add missing sparse builder methods --- src/builder.rs | 51 +++++++++++++++++++++++++--- src/header.rs | 90 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 135 insertions(+), 6 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 06eadcc5..ed1a4069 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -74,15 +74,56 @@ impl Builder { Ok(self.obj.take().unwrap()) } - // TODO: documentation - #[allow(missing_docs)] - pub fn append_sparse_all( + /// Adds a new sparse entry to this archive. + /// + /// This function will append the header (and external headers if) specified, + /// followed by contents of the stream specified by `data`. + /// You must set the entry type to [`EntryType::GNUSparse`]. + /// 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, GnuExtSparseHeader, Header}; + /// + /// let mut header = Header::new_gnu(); + /// header.set_path("foo").unwrap(); + /// header.set_entry_type(EntryType::GNUSparse); + /// header.set_sparse(sparse).unwrap(); + /// header.set_extended(false).unwrap(); + /// header.set_cksum(); + /// + /// let mut ext_header = GnuExtSparseHeader::new(); + /// TODO: ext_header + /// + /// let mut data: &[u8] = &[1, 2, 3, 4]; + /// + /// let mut ar = Builder::new(Vec::new()); + /// ar.append_sparse(&header, &ext_header, data).unwrap(); + /// let data = ar.into_inner().unwrap(); + /// ``` + pub fn append_sparse( &mut self, header: &Header, ext_header: &GnuExtSparseHeader, mut data: R, ) -> io::Result<()> { - append_sparse_all(self.get_mut(), header, ext_header, &mut data) + append_sparse(self.get_mut(), header, ext_header, &mut data) } /// Adds a new entry to this archive. @@ -420,7 +461,7 @@ impl Builder { } } -fn append_sparse_all( +fn append_sparse( mut dst: &mut dyn Write, header: &Header, ext_header: &GnuExtSparseHeader, diff --git a/src/header.rs b/src/header.rs index 65f665e0..d7248a17 100644 --- a/src/header.rs +++ b/src/header.rs @@ -1,3 +1,4 @@ +use std::convert::TryInto; #[cfg(unix)] use std::os::unix::prelude::*; #[cfg(windows)] @@ -123,6 +124,7 @@ pub struct GnuHeader { /// Specifies the offset/number of bytes of a chunk of data in octal. #[repr(C)] #[allow(missing_docs)] +#[derive(Copy)] pub struct GnuSparseHeader { pub offset: [u8; 12], pub numbytes: [u8; 12], @@ -570,6 +572,27 @@ impl Header { } } + /// Sets the sparse entries inside this header. + /// + /// This function will return an error if sparse entries are greater than + /// max allowed [GnuSparseHeader; 4] + pub fn set_sparse(&mut self, sparse: Vec) -> io::Result<()> { + if let Some(gnu) = self.as_gnu_mut() { + gnu.set_sparse(sparse) + } else { + Err(other("not a gnu archive, cannot set sparse headers")) + } + } + + /// Sets the extended sparse flag inside this header. + pub fn set_extended(&mut self, extended: bool) -> io::Result<()> { + if let Some(gnu) = self.as_gnu_mut() { + gnu.set_extended(extended) + } else { + Err(other("not a gnu archive, cannot set extended flag")) + } + } + /// Return the group name of the owner of this file. /// /// A return value of `Ok(Some(..))` indicates that the group name was @@ -1148,6 +1171,41 @@ impl GnuHeader { }) } + /// See `Header::set_sparse` + pub fn set_sparse(&mut self, sparse: Vec) -> io::Result<()> { + if sparse.len() > self.sparse.len() { + return Err(other(&format!( + "reached max allowed 4 sparse header size with: {}", + sparse.len() + ))); + } + let mut sparse_index = 0; + let mut real_size = 0; + let mut length = 0; + octal_into(&mut self.realsize, 0); + + let mut numbytes = 0; + for header in sparse.into_iter() { + numbytes = numbytes + header.length()?; + real_size = header.offset()?; + length = header.length()?; + + self.sparse[sparse_index] = header; + sparse_index = sparse_index + 1; + } + octal_into(&mut self.realsize, real_size + length); // size + octal_into(&mut self.size, numbytes); // entry_size + + Ok(()) + } + + /// See `Header::set_extended` + pub fn set_extended(&mut self, isextended: bool) -> io::Result<()> { + self.isextended[0] = isextended.try_into().unwrap(); // TODO: proper err handling + + Ok(()) + } + /// See `Header::groupname_bytes` pub fn groupname_bytes(&self) -> &[u8] { truncate(&self.gname) @@ -1313,6 +1371,21 @@ impl<'a> fmt::Debug for DebugSparseHeaders<'a> { } impl GnuSparseHeader { + /// Creates a new zero'd out sparse header entry. + pub fn new() -> GnuSparseHeader { + unsafe { mem::zeroed() } + } + + /// Sets the offset field of this header. + pub fn set_offset(&mut self, offset: u64) { + octal_into(&mut self.offset, offset); + } + + /// Sets the numbytes field of this header. + pub fn set_numbytes(&mut self, numbytes: u64) { + octal_into(&mut self.numbytes, numbytes); + } + /// Returns true if block is empty pub fn is_empty(&self) -> bool { self.offset[0] == 0 || self.numbytes[0] == 0 @@ -1343,6 +1416,21 @@ impl GnuSparseHeader { } } +impl Default for GnuSparseHeader { + fn default() -> Self { + Self::new() + } +} + +impl Clone for GnuSparseHeader { + fn clone(&self) -> GnuSparseHeader { + GnuSparseHeader { + offset: self.offset, + numbytes: self.numbytes, + } + } +} + impl fmt::Debug for GnuSparseHeader { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut f = f.debug_struct("GnuSparseHeader"); @@ -1357,7 +1445,7 @@ impl fmt::Debug for GnuSparseHeader { } impl GnuExtSparseHeader { - /// Crates a new zero'd out sparse header entry. + /// Creates a new zero'd out sparse header entry. pub fn new() -> GnuExtSparseHeader { unsafe { mem::zeroed() } } From bf820b246e536f751e40b8c61059495ec9e9ef54 Mon Sep 17 00:00:00 2001 From: Attila Repka Date: Mon, 29 May 2023 12:48:31 +0200 Subject: [PATCH 07/10] tests: add sparse related tests wip --- tests/all.rs | 287 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 281 insertions(+), 6 deletions(-) diff --git a/tests/all.rs b/tests/all.rs index 826dabbe..922bf045 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -12,7 +12,9 @@ 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, EntryType, GnuExtSparseHeader, GnuSparseHeader, Header, HeaderMode, +}; use tempfile::{Builder as TempBuilder, TempDir}; macro_rules! t { @@ -1031,7 +1033,7 @@ fn encoded_long_name_has_trailing_nul() { } #[test] -fn write_sparse_to_vec_and_read_again() { +fn write_sparse_to_mem_and_read_again() { let rdr = Cursor::new(tar!("sparse.tar")); let mut ar = Archive::new(rdr); let mut entries = t!(ar.entries()); @@ -1043,7 +1045,7 @@ fn write_sparse_to_vec_and_read_again() { a.read_exact(&mut bytes).unwrap(); assert_eq!(&*a.header().path_bytes(), b"sparse_begin.txt"); assert_eq!(std::str::from_utf8(&bytes[..5]).unwrap(), "test\n"); - tar.append_sparse_all(&a.header(), a.ext_header(), &*bytes) + tar.append_sparse(&a.header(), a.ext_header(), &*bytes) .unwrap(); let mut a = t!(entries.next().unwrap()); @@ -1055,7 +1057,7 @@ fn write_sparse_to_vec_and_read_again() { std::str::from_utf8(&bytes[bytes.len() - 9..]).unwrap(), "test_end\n" ); - tar.append_sparse_all(&a.header(), a.ext_header(), &*bytes) + tar.append_sparse(&a.header(), a.ext_header(), &*bytes) .unwrap(); let mut a = t!(entries.next().unwrap()); @@ -1110,7 +1112,7 @@ fn write_sparse_to_vec_and_read_again() { std::str::from_utf8(&bytes[0xb000..0xb000 + 5]).unwrap(), "text\n" ); - tar.append_sparse_all(&a.header(), a.ext_header(), &*bytes) + tar.append_sparse(&a.header(), a.ext_header(), &*bytes) .unwrap(); let mut a = t!(entries.next().unwrap()); @@ -1127,7 +1129,7 @@ fn write_sparse_to_vec_and_read_again() { std::str::from_utf8(bytes.get(0x2fa0..0x2fa0 + 6).unwrap()).unwrap(), "world\n" ); - tar.append_sparse_all(&a.header(), a.ext_header(), &*bytes) + tar.append_sparse(&a.header(), a.ext_header(), &*bytes) .unwrap(); assert!(entries.next().is_none()); @@ -1227,6 +1229,279 @@ fn write_sparse_to_vec_and_read_again() { assert!(entries.next().is_none()); } +#[test] +fn sparse_builder_one() { + let mut sparse_1 = GnuSparseHeader::new(); + sparse_1.set_offset(7680); + sparse_1.set_numbytes(425); + + let mut sparse = Vec::new(); + sparse.push(sparse_1); + + let mut header = Header::new_gnu(); + header.set_path("foo.dat").unwrap(); + header.set_entry_type(EntryType::GNUSparse); + header.set_sparse(sparse).unwrap(); + header.set_extended(false).unwrap(); + header.set_cksum(); + + // TODO: add extended stuff to this testcase + let ext_header = GnuExtSparseHeader::new(); + + let mut data = Vec::new(); + let mut nulls = io::repeat(0).take(sparse_1.offset().unwrap()); + nulls.read_to_end(&mut data).unwrap(); + + let payload = String::from("test_data\n"); + for value in payload.bytes() { + data.push(value); + } + + let remaining = sparse_1.length().unwrap() - payload.len() as u64; + let mut nulls = io::repeat(0).take(remaining); + nulls.read_to_end(&mut data).unwrap(); + + assert!(std::str::from_utf8(&data[..0x1E00]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&data[0x1E00..0x1E00 + payload.len()]).unwrap(), + payload.as_str() + ); + + let mut ar = Builder::new(Vec::new()); + ar.append_sparse(&header, &ext_header, &*data).unwrap(); + + // read back data + let rdr = Cursor::new(t!(ar.into_inner())); + let mut ar = Archive::new(rdr); + let mut entries = t!(ar.entries()); + + let mut a = t!(entries.next().unwrap()); + let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; + a.read_exact(&mut bytes).unwrap(); + + assert_eq!(&*a.header().path_bytes(), b"foo.dat"); + + assert!(std::str::from_utf8(&bytes[..0x1E00]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert_eq!( + std::str::from_utf8(&bytes[0x1E00..0x1E00 + payload.len()]).unwrap(), + payload.as_str() + ); +} + +#[test] +fn sparse_builder_two() { + let mut sparse_1 = GnuSparseHeader::new(); + sparse_1.set_offset(0); + sparse_1.set_numbytes(512); + + let mut sparse_2 = GnuSparseHeader::new(); + sparse_2.set_offset(8096); + sparse_2.set_numbytes(0); + let mut sparse = Vec::new(); + sparse.push(sparse_1); + sparse.push(sparse_2); + + let mut header = Header::new_gnu(); + header.set_path("foo.dat").unwrap(); + header.set_entry_type(EntryType::GNUSparse); + header.set_sparse(sparse).unwrap(); + header.set_extended(false).unwrap(); + header.set_cksum(); + + // TODO: add extended stuff to this testcase + let ext_header = GnuExtSparseHeader::new(); + + let mut data = Vec::new(); + let mut nulls = io::repeat(0).take(sparse_1.offset().unwrap()); + nulls.read_to_end(&mut data).unwrap(); + + let payload = String::from("test_text\n"); + for value in payload.bytes() { + data.push(value); + } + + let remaining = sparse_1.length().unwrap() - payload.len() as u64; + let mut nulls = io::repeat(0).take(remaining); + nulls.read_to_end(&mut data).unwrap(); + + assert_eq!( + std::str::from_utf8(&data[..payload.len()]).unwrap(), + payload.as_str() + ); + assert!(std::str::from_utf8(&data[payload.len()..]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + + let mut nulls = io::repeat(0).take(sparse_2.offset().unwrap()); + nulls.read_to_end(&mut data).unwrap(); + + assert!(std::str::from_utf8(&data[0x1FA0..]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + + let mut ar = Builder::new(Vec::new()); + ar.append_sparse(&header, &ext_header, &*data).unwrap(); + + // read back data + let rdr = Cursor::new(t!(ar.into_inner())); + let mut ar = Archive::new(rdr); + let mut entries = t!(ar.entries()); + + let mut a = t!(entries.next().unwrap()); + let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; + a.read_exact(&mut bytes).unwrap(); + + assert_eq!(&*a.header().path_bytes(), b"foo.dat"); + + assert_eq!( + std::str::from_utf8(&data[..payload.len()]).unwrap(), + payload.as_str() + ); + assert!(std::str::from_utf8(&data[payload.len()..0x1FA0]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + assert!(std::str::from_utf8(&data[0x1FA0..]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); +} + +#[test] +fn sparse_builder_three() { + let mut sparse_1 = GnuSparseHeader::new(); + sparse_1.set_offset(4096); + sparse_1.set_numbytes(512); + + let mut sparse_2 = GnuSparseHeader::new(); + sparse_2.set_offset(11776); + sparse_2.set_numbytes(512); + + let mut sparse_3 = GnuSparseHeader::new(); + sparse_3.set_offset(16384); + sparse_3.set_numbytes(0); + + let mut sparse = Vec::new(); + sparse.push(sparse_1); + sparse.push(sparse_2); + sparse.push(sparse_3); + + let mut header = Header::new_gnu(); + header.set_path("foo.dat").unwrap(); + header.set_entry_type(EntryType::GNUSparse); + header.set_sparse(sparse).unwrap(); + header.set_extended(false).unwrap(); + header.set_cksum(); + + // TODO: add extended stuff to this testcase + let ext_header = GnuExtSparseHeader::new(); + + let mut data = Vec::new(); + let mut nulls = io::repeat(0).take(sparse_1.offset().unwrap()); + nulls.read_to_end(&mut data).unwrap(); + + assert!(std::str::from_utf8(&data[..0x1000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + + let payload_one = String::from("test_one\n"); + for value in payload_one.bytes() { + data.push(value); + } + + let remaining = sparse_1.length().unwrap() - payload_one.len() as u64; + let mut nulls = io::repeat(0).take(remaining); + nulls.read_to_end(&mut data).unwrap(); + + assert_eq!( + std::str::from_utf8(&data[0x1000..0x1000 + payload_one.len()]).unwrap(), + payload_one.as_str() + ); + assert!(std::str::from_utf8(&data[0x1000 + payload_one.len()..]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + + let remaining = sparse_2.offset().unwrap() - data.len() as u64; + let mut nulls = io::repeat(0).take(remaining); + nulls.read_to_end(&mut data).unwrap(); + + assert!( + std::str::from_utf8(&data[0x1000 + payload_one.len()..0x2E00]) + .unwrap() + .chars() + .all(|x| x == '\u{0}') + ); + + let payload_two = String::from("test_two\n"); + for value in payload_two.bytes() { + data.push(value); + } + + let remaining = sparse_2.length().unwrap() - payload_two.len() as u64; + let mut nulls = io::repeat(0).take(remaining); + nulls.read_to_end(&mut data).unwrap(); + + assert_eq!( + std::str::from_utf8(&data[0x2E00..0x2E00 + payload_two.len()]).unwrap(), + payload_two.as_str() + ); + assert!(std::str::from_utf8(&data[0x2E00 + payload_two.len()..]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + + let mut nulls = io::repeat(0).take(sparse_3.offset().unwrap() - data.len() as u64); + nulls.read_to_end(&mut data).unwrap(); + + assert!( + std::str::from_utf8(&data[0x2E00 + payload_two.len()..0x4000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}') + ); + + let mut ar = Builder::new(Vec::new()); + ar.append_sparse(&header, &ext_header, &*data).unwrap(); + + // read back data + let rdr = Cursor::new(t!(ar.into_inner())); + let mut ar = Archive::new(rdr); + let mut entries = t!(ar.entries()); + + let mut a = t!(entries.next().unwrap()); + let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; + a.read_exact(&mut bytes).unwrap(); + + assert_eq!(&*a.header().path_bytes(), b"foo.dat"); + + assert_eq!( + std::str::from_utf8(&data[0x1000..0x1000 + payload_one.len()]).unwrap(), + payload_one.as_str() + ); + + assert_eq!( + std::str::from_utf8(&data[0x2E00..0x2E00 + payload_two.len()]).unwrap(), + payload_two.as_str() + ); + + assert!( + std::str::from_utf8(&data[0x2E00 + payload_two.len()..0x4000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}') + ); +} + #[test] fn reading_sparse() { let rdr = Cursor::new(tar!("sparse.tar")); From 3d6f43d2b1e5e02ac922c8a44d1f16150c7bf311 Mon Sep 17 00:00:00 2001 From: Attila Repka Date: Mon, 29 May 2023 13:31:34 +0200 Subject: [PATCH 08/10] docs: fix append_sparse doc --- src/builder.rs | 17 +++++++++++------ src/header.rs | 3 +-- tests/all.rs | 18 ++++++------------ 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index ed1a4069..92b36f17 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -99,22 +99,27 @@ impl Builder { /// # Examples /// /// ``` - /// use tar::{Builder, GnuExtSparseHeader, Header}; + /// use tar::{Builder, EntryType, GnuExtSparseHeader, GnuSparseHeader, Header}; /// /// let mut header = Header::new_gnu(); + /// + /// let mut sparse_header = GnuSparseHeader::new(); + /// sparse_header.set_offset(512); + /// sparse_header.set_numbytes(0); + /// + /// let mut sparse = Vec::new(); + /// sparse.push(sparse_header); + /// /// header.set_path("foo").unwrap(); /// header.set_entry_type(EntryType::GNUSparse); /// header.set_sparse(sparse).unwrap(); /// header.set_extended(false).unwrap(); /// header.set_cksum(); /// - /// let mut ext_header = GnuExtSparseHeader::new(); - /// TODO: ext_header - /// - /// let mut data: &[u8] = &[1, 2, 3, 4]; + /// let mut data: &[u8] = &[0; 512]; /// /// let mut ar = Builder::new(Vec::new()); - /// ar.append_sparse(&header, &ext_header, data).unwrap(); + /// ar.append_sparse(&header, &GnuExtSparseHeader::default(), data).unwrap(); /// let data = ar.into_inner().unwrap(); /// ``` pub fn append_sparse( diff --git a/src/header.rs b/src/header.rs index d7248a17..a83006c2 100644 --- a/src/header.rs +++ b/src/header.rs @@ -1,4 +1,3 @@ -use std::convert::TryInto; #[cfg(unix)] use std::os::unix::prelude::*; #[cfg(windows)] @@ -1201,7 +1200,7 @@ impl GnuHeader { /// See `Header::set_extended` pub fn set_extended(&mut self, isextended: bool) -> io::Result<()> { - self.isextended[0] = isextended.try_into().unwrap(); // TODO: proper err handling + self.isextended[0] = isextended as u8; Ok(()) } diff --git a/tests/all.rs b/tests/all.rs index 922bf045..70d5c196 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -1245,9 +1245,6 @@ fn sparse_builder_one() { header.set_extended(false).unwrap(); header.set_cksum(); - // TODO: add extended stuff to this testcase - let ext_header = GnuExtSparseHeader::new(); - let mut data = Vec::new(); let mut nulls = io::repeat(0).take(sparse_1.offset().unwrap()); nulls.read_to_end(&mut data).unwrap(); @@ -1271,7 +1268,8 @@ fn sparse_builder_one() { ); let mut ar = Builder::new(Vec::new()); - ar.append_sparse(&header, &ext_header, &*data).unwrap(); + ar.append_sparse(&header, &GnuExtSparseHeader::default(), &*data) + .unwrap(); // read back data let rdr = Cursor::new(t!(ar.into_inner())); @@ -1314,9 +1312,6 @@ fn sparse_builder_two() { header.set_extended(false).unwrap(); header.set_cksum(); - // TODO: add extended stuff to this testcase - let ext_header = GnuExtSparseHeader::new(); - let mut data = Vec::new(); let mut nulls = io::repeat(0).take(sparse_1.offset().unwrap()); nulls.read_to_end(&mut data).unwrap(); @@ -1348,7 +1343,8 @@ fn sparse_builder_two() { .all(|x| x == '\u{0}')); let mut ar = Builder::new(Vec::new()); - ar.append_sparse(&header, &ext_header, &*data).unwrap(); + ar.append_sparse(&header, &GnuExtSparseHeader::default(), &*data) + .unwrap(); // read back data let rdr = Cursor::new(t!(ar.into_inner())); @@ -1401,9 +1397,6 @@ fn sparse_builder_three() { header.set_extended(false).unwrap(); header.set_cksum(); - // TODO: add extended stuff to this testcase - let ext_header = GnuExtSparseHeader::new(); - let mut data = Vec::new(); let mut nulls = io::repeat(0).take(sparse_1.offset().unwrap()); nulls.read_to_end(&mut data).unwrap(); @@ -1471,7 +1464,8 @@ fn sparse_builder_three() { ); let mut ar = Builder::new(Vec::new()); - ar.append_sparse(&header, &ext_header, &*data).unwrap(); + ar.append_sparse(&header, &GnuExtSparseHeader::default(), &*data) + .unwrap(); // read back data let rdr = Cursor::new(t!(ar.into_inner())); From fe4e18a46514b6bd14c2b8b1a627cfd446807e6d Mon Sep 17 00:00:00 2001 From: Attila Repka Date: Mon, 29 May 2023 14:40:12 +0200 Subject: [PATCH 09/10] tests: add extended sparse header tests --- src/header.rs | 28 +++++- tests/all.rs | 241 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 264 insertions(+), 5 deletions(-) diff --git a/src/header.rs b/src/header.rs index a83006c2..618214a4 100644 --- a/src/header.rs +++ b/src/header.rs @@ -1174,7 +1174,8 @@ impl GnuHeader { pub fn set_sparse(&mut self, sparse: Vec) -> io::Result<()> { if sparse.len() > self.sparse.len() { return Err(other(&format!( - "reached max allowed 4 sparse header size with: {}", + "reached max allowed {} sparse header size with: {}", + self.sparse.len(), sparse.len() ))); } @@ -1461,6 +1462,25 @@ impl GnuExtSparseHeader { unsafe { mem::transmute(self) } } + /// Sets extended sparse headers + pub fn set_sparse(&mut self, sparse: Vec) -> io::Result<()> { + if sparse.len() > self.sparse.len() { + return Err(other(&format!( + "reached max allowed {} sparse header size with: {}", + self.sparse().len(), + sparse.len() + ))); + } + let mut sparse_index = 0; + + for header in sparse.into_iter() { + self.sparse[sparse_index] = header; + sparse_index = sparse_index + 1; + } + + Ok(()) + } + /// Returns a slice of the underlying sparse headers. /// /// Some headers may represent empty chunks of both the offset and numbytes @@ -1469,6 +1489,12 @@ impl GnuExtSparseHeader { &self.sparse } + /// Sets the extended sparse flag inside this header. + pub fn set_extended(&mut self, isextended: bool) -> io::Result<()> { + self.isextended[0] = isextended as u8; + Ok(()) + } + /// Indicates if another sparse header should be following this one. pub fn is_extended(&self) -> bool { self.isextended[0] == 1 diff --git a/tests/all.rs b/tests/all.rs index 70d5c196..15894380 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -1249,7 +1249,7 @@ fn sparse_builder_one() { let mut nulls = io::repeat(0).take(sparse_1.offset().unwrap()); nulls.read_to_end(&mut data).unwrap(); - let payload = String::from("test_data\n"); + let payload = String::from("payload_data\n"); for value in payload.bytes() { data.push(value); } @@ -1316,7 +1316,7 @@ fn sparse_builder_two() { let mut nulls = io::repeat(0).take(sparse_1.offset().unwrap()); nulls.read_to_end(&mut data).unwrap(); - let payload = String::from("test_text\n"); + let payload = String::from("payload_data\n"); for value in payload.bytes() { data.push(value); } @@ -1406,7 +1406,7 @@ fn sparse_builder_three() { .chars() .all(|x| x == '\u{0}')); - let payload_one = String::from("test_one\n"); + let payload_one = String::from("payload_one\n"); for value in payload_one.bytes() { data.push(value); } @@ -1435,7 +1435,7 @@ fn sparse_builder_three() { .all(|x| x == '\u{0}') ); - let payload_two = String::from("test_two\n"); + let payload_two = String::from("payload_two\n"); for value in payload_two.bytes() { data.push(value); } @@ -1496,6 +1496,239 @@ fn sparse_builder_three() { ); } +#[test] +fn sparse_builder_extended() { + let mut sparse_1 = GnuSparseHeader::new(); + sparse_1.set_offset(4096); + sparse_1.set_numbytes(512); + + let mut sparse_2 = GnuSparseHeader::new(); + sparse_2.set_offset(12288); + sparse_2.set_numbytes(512); + + let mut sparse_3 = GnuSparseHeader::new(); + sparse_3.set_offset(20480); + sparse_3.set_numbytes(512); + + let mut sparse_4 = GnuSparseHeader::new(); + sparse_4.set_offset(28672); + sparse_4.set_numbytes(512); + + let mut sparse_vec = Vec::new(); + sparse_vec.push(sparse_1); + sparse_vec.push(sparse_2); + sparse_vec.push(sparse_3); + sparse_vec.push(sparse_4); + + let mut sparse_ext = GnuSparseHeader::new(); + sparse_ext.set_offset(30720); + sparse_ext.set_numbytes(512); + + let mut sparse_ext_vec = Vec::new(); + sparse_ext_vec.push(sparse_ext); + + let mut extended_header = GnuExtSparseHeader::new(); + extended_header.set_extended(true).unwrap(); + extended_header.set_sparse(sparse_ext_vec).unwrap(); + + let mut header = Header::new_gnu(); + header.set_path("foo.dat").unwrap(); + header.set_entry_type(EntryType::GNUSparse); + header.set_sparse(sparse_vec).unwrap(); + header.set_extended(true).unwrap(); + header.set_cksum(); + + let mut data = Vec::new(); + let mut nulls = io::repeat(0).take(sparse_1.offset().unwrap()); + nulls.read_to_end(&mut data).unwrap(); + + assert!(std::str::from_utf8(&data[..0x1000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + + let payload_one = String::from("payload_one\n"); + for value in payload_one.bytes() { + data.push(value); + } + + let remaining = sparse_1.length().unwrap() - payload_one.len() as u64; + let mut nulls = io::repeat(0).take(remaining); + nulls.read_to_end(&mut data).unwrap(); + + assert_eq!( + std::str::from_utf8(&data[0x1000..0x1000 + payload_one.len()]).unwrap(), + payload_one.as_str() + ); + assert!(std::str::from_utf8(&data[0x1000 + payload_one.len()..]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + + let remaining = sparse_2.offset().unwrap() - data.len() as u64; + let mut nulls = io::repeat(0).take(remaining); + nulls.read_to_end(&mut data).unwrap(); + + assert!( + std::str::from_utf8(&data[0x1000 + payload_one.len()..0x3000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}') + ); + + let payload_two = String::from("payload_two\n"); + for value in payload_two.bytes() { + data.push(value); + } + + let remaining = sparse_2.length().unwrap() - payload_two.len() as u64; + let mut nulls = io::repeat(0).take(remaining); + nulls.read_to_end(&mut data).unwrap(); + + assert_eq!( + std::str::from_utf8(&data[0x3000..0x3000 + payload_two.len()]).unwrap(), + payload_two.as_str() + ); + assert!(std::str::from_utf8(&data[0x3000 + payload_two.len()..]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + + let remaining = sparse_3.offset().unwrap() - data.len() as u64; + let mut nulls = io::repeat(0).take(remaining); + nulls.read_to_end(&mut data).unwrap(); + + assert!( + std::str::from_utf8(&data[0x3000 + payload_two.len()..0x5000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}') + ); + + let payload_three = String::from("payload_three\n"); + for value in payload_three.bytes() { + data.push(value); + } + + let remaining = sparse_3.length().unwrap() - payload_three.len() as u64; + let mut nulls = io::repeat(0).take(remaining); + nulls.read_to_end(&mut data).unwrap(); + + assert_eq!( + std::str::from_utf8(&data[0x5000..0x5000 + payload_three.len()]).unwrap(), + payload_three.as_str() + ); + + let mut nulls = io::repeat(0).take(sparse_4.offset().unwrap() - data.len() as u64); + nulls.read_to_end(&mut data).unwrap(); + + assert!( + std::str::from_utf8(&data[0x5000 + payload_three.len()..0x7000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}') + ); + + let remaining = sparse_4.offset().unwrap() - data.len() as u64; + let mut nulls = io::repeat(0).take(remaining); + nulls.read_to_end(&mut data).unwrap(); + + assert!( + std::str::from_utf8(&data[0x3000 + payload_two.len()..0x5000]) + .unwrap() + .chars() + .all(|x| x == '\u{0}') + ); + + let payload_four = String::from("payload_four\n"); + for value in payload_four.bytes() { + data.push(value); + } + + let remaining = sparse_4.length().unwrap() - payload_four.len() as u64; + let mut nulls = io::repeat(0).take(remaining); + nulls.read_to_end(&mut data).unwrap(); + + assert_eq!( + std::str::from_utf8(&data[0x7000..0x7000 + payload_four.len()]).unwrap(), + payload_four.as_str() + ); + + let mut nulls = io::repeat(0).take(sparse_ext.offset().unwrap() - data.len() as u64); + nulls.read_to_end(&mut data).unwrap(); + + assert!( + std::str::from_utf8(&data[0x7000 + payload_four.len()..0x7800]) + .unwrap() + .chars() + .all(|x| x == '\u{0}') + ); + + let payload_ext = String::from("payload_ext\n"); + for value in payload_ext.bytes() { + data.push(value); + } + + let remaining = sparse_ext.length().unwrap() - payload_ext.len() as u64; + let mut nulls = io::repeat(0).take(remaining); + nulls.read_to_end(&mut data).unwrap(); + + assert_eq!( + std::str::from_utf8(&data[0x7800..0x7800 + payload_ext.len()]).unwrap(), + payload_ext.as_str() + ); + + assert!(std::str::from_utf8(&data[0x7800 + payload_ext.len()..]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); + + let mut ar = Builder::new(Vec::new()); + ar.append_sparse(&header, &GnuExtSparseHeader::default(), &*data) + .unwrap(); + + // read back data + let rdr = Cursor::new(t!(ar.into_inner())); + let mut ar = Archive::new(rdr); + let mut entries = t!(ar.entries()); + + let mut a = t!(entries.next().unwrap()); + let mut bytes = vec![Default::default(); a.size().try_into().unwrap()]; + a.read_exact(&mut bytes).unwrap(); + + assert_eq!(&*a.header().path_bytes(), b"foo.dat"); + + assert_eq!( + std::str::from_utf8(&data[0x1000..0x1000 + payload_one.len()]).unwrap(), + payload_one.as_str() + ); + + assert_eq!( + std::str::from_utf8(&data[0x3000..0x3000 + payload_two.len()]).unwrap(), + payload_two.as_str() + ); + + assert_eq!( + std::str::from_utf8(&data[0x5000..0x5000 + payload_three.len()]).unwrap(), + payload_three.as_str() + ); + + assert_eq!( + std::str::from_utf8(&data[0x7000..0x7000 + payload_four.len()]).unwrap(), + payload_four.as_str() + ); + + assert_eq!( + std::str::from_utf8(&data[0x7800..0x7800 + payload_ext.len()]).unwrap(), + payload_ext.as_str() + ); + + assert!(std::str::from_utf8(&data[0x7800 + payload_ext.len()..]) + .unwrap() + .chars() + .all(|x| x == '\u{0}')); +} + #[test] fn reading_sparse() { let rdr = Cursor::new(tar!("sparse.tar")); From 61f643e47e35e1bbb0bb725c44aa144efbc9bd95 Mon Sep 17 00:00:00 2001 From: Attila Repka Date: Thu, 1 Jun 2023 21:05:17 +0200 Subject: [PATCH 10/10] docs: add missing docs for ext_header --- src/entry.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/entry.rs b/src/entry.rs index 61bb8a61..f44b3cc1 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -145,7 +145,9 @@ impl<'a, R: Read> Entry<'a, R> { &self.fields.header } - #[allow(missing_docs)] + /// Returns access to the extended header of this entry in the archive. + /// + /// This provides access to the underlying extended sparse headers. pub fn ext_header(&self) -> &GnuExtSparseHeader { &self.fields.ext_header }