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 a26eb31d..92b36f17 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -3,8 +3,11 @@ 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; +use crate::GnuSparseHeader; use crate::{other, EntryType, Header}; /// A structure for building archives @@ -71,6 +74,63 @@ impl Builder { Ok(self.obj.take().unwrap()) } + /// 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, 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 data: &[u8] = &[0; 512]; + /// + /// let mut ar = Builder::new(Vec::new()); + /// ar.append_sparse(&header, &GnuExtSparseHeader::default(), 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(self.get_mut(), header, ext_header, &mut data) + } + /// Adds a new entry to this archive. /// /// This function will append the header specified, followed by contents of @@ -406,16 +466,102 @@ impl Builder { } } +fn append_sparse( + mut dst: &mut dyn Write, + header: &Header, + ext_header: &GnuExtSparseHeader, + 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())?; + if gnu.is_extended() { + dst.write_all(ext_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() { + for block in ext_header.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 +679,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/src/entry.rs b/src/entry.rs index c5cef6b7..f44b3cc1 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,13 @@ impl<'a, R: Read> Entry<'a, R> { &self.fields.header } + /// 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 + } + /// 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/src/header.rs b/src/header.rs index 65f665e0..618214a4 100644 --- a/src/header.rs +++ b/src/header.rs @@ -123,6 +123,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 +571,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 +1170,42 @@ 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 {} sparse header size with: {}", + self.sparse.len(), + 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 as u8; + + 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() } } @@ -1374,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 @@ -1382,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 1e7b264e..15894380 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}; @@ -11,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 { @@ -1029,6 +1032,703 @@ fn encoded_long_name_has_trailing_nul() { assert!(header_name.starts_with(b"././@LongLink\x00")); } +#[test] +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()); + + 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"); + tar.append_sparse(&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_end.txt"); + assert_eq!( + std::str::from_utf8(&bytes[bytes.len() - 9..]).unwrap(), + "test_end\n" + ); + tar.append_sparse(&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(&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.txt"); + assert_eq!( + 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" + ); + tar.append_sparse(&a.header(), a.ext_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()); + + 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 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(); + + 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("payload_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, &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!(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(); + + 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("payload_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_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, &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[..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(); + + 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()..0x2E00]) + .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[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, &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[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 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"));