From eee866310da0999ead3085b17e3ab3d9a9af1ab1 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Thu, 28 Apr 2016 00:29:25 +0300 Subject: [PATCH 1/8] Add `list` example which list files in archive from stdin --- examples/list.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 examples/list.rs diff --git a/examples/list.rs b/examples/list.rs new file mode 100644 index 00000000..289b3fd0 --- /dev/null +++ b/examples/list.rs @@ -0,0 +1,14 @@ +extern crate tar; + +use std::io::{stdin}; + +use tar::Archive; + + +fn main() { + let mut arch = Archive::new(stdin()); + for file in arch.entries().unwrap() { + let f= file.unwrap(); + println!("{}", f.header().path().unwrap().display()); + } +} From 4d804104721c3e5c406c8d5a1cbc4df0e286560c Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Thu, 28 Apr 2016 00:31:26 +0300 Subject: [PATCH 2/8] Add GNUSparse file type --- src/entry_type.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/entry_type.rs b/src/entry_type.rs index 86df27c6..f73b77da 100644 --- a/src/entry_type.rs +++ b/src/entry_type.rs @@ -27,6 +27,8 @@ pub enum EntryType { GNULongName, /// GNU extension - long link name (link target) GNULongLink, + /// GNU extension - sparse file + GNUSparse, /// Global extended header XGlobalHeader, /// Extended Header @@ -60,6 +62,7 @@ impl EntryType { b'g' => EntryType::XGlobalHeader, b'L' => EntryType::GNULongName, b'K' => EntryType::GNULongLink, + b'S' => EntryType::GNUSparse, b => EntryType::__Nonexhaustive(b), } } @@ -79,6 +82,7 @@ impl EntryType { &EntryType::XGlobalHeader => b'g', &EntryType::GNULongName => b'L', &EntryType::GNULongLink => b'K', + &EntryType::GNUSparse => b'S', &EntryType::__Nonexhaustive(b) => b, } } From a222464dff2e35205587ca5f36f76008b26e57db Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Thu, 28 Apr 2016 21:01:28 +0300 Subject: [PATCH 3/8] Implement header parsing for sparse files --- src/archive.rs | 44 ++++++++++++++++++++++++++++++++++++++++++-- src/header.rs | 48 ++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 80 insertions(+), 12 deletions(-) diff --git a/src/archive.rs b/src/archive.rs index 9cc50411..763253c6 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -3,11 +3,13 @@ use std::cmp; use std::fs; use std::io::prelude::*; use std::io; +use std::mem::transmute; use std::marker; use std::path::{Path, Component}; use entry::EntryFields; use error::TarError; +use header::{GnuExtSparseHeader, octal_from}; use other; use {Entry, Header}; @@ -243,11 +245,49 @@ impl<'a> EntriesFields<'a> { return Err(other("archive header checksum mismatch")) } + header.sparse_chunks = if let Some(gnu) = header.as_gnu_mut() { + let mut chunks = Vec::new(); + for item in &gnu.sparse[..] { + if item.offset[0] != 0 && item.numbytes[0] != 0 { + let off = try!(octal_from(&item.offset[..])); + let nbytes = try!(octal_from(&item.numbytes[..])); + if nbytes > 0 { + chunks.push((off, nbytes)); + } + } + } + if gnu.isextended[0] == 1 { + loop { + let mut ext_header = [0u8; 512]; + try!(read_all(&mut &self.archive.inner, &mut ext_header)); + self.next += 512; + let sparse_header: &GnuExtSparseHeader; + sparse_header = unsafe { transmute(&ext_header) }; + for item in &sparse_header.sparse { + if item.offset[0] != 0 && item.numbytes[0] != 0 { + let off = try!(octal_from(&item.offset[..])); + let nbytes = try!(octal_from(&item.numbytes[..])); + if nbytes > 0 { + chunks.push((off, nbytes)); + } + } + } + if sparse_header.isextended[0] == 0 { + break; + } + } + } + chunks + } else { + Vec::new() + }; + + let data_size = try!(header.data_size()); let size = try!(header.size()); let ret = EntryFields { size: size, header: header, - data: (&self.archive.inner).take(size), + data: (&self.archive.inner).take(data_size), long_pathname: None, long_linkname: None, pax_extensions: None, @@ -255,7 +295,7 @@ impl<'a> EntriesFields<'a> { // Store where the next entry is, rounding up by 512 bytes (the size of // a header); - let size = (ret.size + 511) & !(512 - 1); + let size = (data_size + 511) & !(512 - 1); self.next += size; Ok(Some(ret.into_entry())) diff --git a/src/header.rs b/src/header.rs index 19ed156d..0f244f89 100644 --- a/src/header.rs +++ b/src/header.rs @@ -15,8 +15,10 @@ use other; /// Representation of the header of an entry in an archive #[repr(C)] +#[allow(missing_docs)] pub struct Header { bytes: [u8; 512], + pub sparse_chunks: Vec<(u64, u64)>, } /// Representation of the header of an entry in an archive @@ -99,6 +101,15 @@ pub struct GnuSparseHeader { pub numbytes: [u8; 12], } +#[repr(C)] +#[allow(missing_docs)] +pub struct GnuExtSparseHeader { + pub sparse: [GnuSparseHeader; 21], + pub isextended: [u8; 1], +} + + + impl Header { /// Creates a new blank GNU header. /// @@ -106,7 +117,7 @@ impl Header { /// extensions such as long path names, long link names, and setting the /// atime/ctime metadata attributes of files. pub fn new_gnu() -> Header { - let mut header = Header { bytes: [0; 512] }; + let mut header = Header { bytes: [0; 512], sparse_chunks: Vec::new() }; { let gnu = header.cast_mut::(); gnu.magic = *b"ustar "; @@ -123,7 +134,7 @@ impl Header { /// /// UStar is also the basis used for pax archives. pub fn new_ustar() -> Header { - let mut header = Header { bytes: [0; 512] }; + let mut header = Header { bytes: [0; 512], sparse_chunks: Vec::new() }; { let gnu = header.cast_mut::(); gnu.magic = *b"ustar\0"; @@ -139,17 +150,17 @@ impl Header { /// format limits the path name limit and isn't able to contain extra /// metadata like atime/ctime. pub fn new_old() -> Header { - Header { bytes: [0; 512] } + Header { bytes: [0; 512], sparse_chunks: Vec::new() } } fn cast(&self) -> &T { - assert_eq!(mem::size_of_val(self), mem::size_of::()); - unsafe { &*(self as *const Header as *const T) } + assert_eq!(mem::size_of_val(&self.bytes), mem::size_of::()); + unsafe { &*(&self.bytes as *const [u8; 512] as *const T) } } fn cast_mut(&mut self) -> &mut T { - assert_eq!(mem::size_of_val(self), mem::size_of::()); - unsafe { &mut *(self as *mut Header as *mut T) } + assert_eq!(mem::size_of_val(&self.bytes), mem::size_of::()); + unsafe { &mut *(&mut self.bytes as *mut [u8; 512] as *mut T) } } fn is_ustar(&self) -> bool { @@ -240,11 +251,28 @@ impl Header { } } + /// Returns the size of file's data this header represents. + /// + /// This is different from `Header::size` for sparse files, which have + /// some longer `size()` but shorter `data_size()`. + /// + /// May return an error if the field is corrupted. + pub fn data_size(&self) -> io::Result { + octal_from(&self.as_old().size) + } + /// Returns the file size this header represents. /// /// May return an error if the field is corrupted. pub fn size(&self) -> io::Result { - octal_from(&self.as_old().size) + match (self.entry_type(), self.as_gnu()) { + (EntryType::GNUSparse, Some(gnu)) => { + octal_from(&gnu.realsize) + } + _ => { + octal_from(&self.as_old().size) + } + } } /// Encodes the `size` argument into the size field of this header. @@ -623,7 +651,7 @@ impl Header { impl Clone for Header { fn clone(&self) -> Header { - Header { bytes: self.bytes } + Header { bytes: self.bytes, sparse_chunks: self.sparse_chunks.clone() } } } @@ -792,7 +820,7 @@ impl GnuHeader { } } -fn octal_from(slice: &[u8]) -> io::Result { +pub fn octal_from(slice: &[u8]) -> io::Result { let num = match str::from_utf8(truncate(slice)) { Ok(n) => n, Err(_) => return Err(other("numeric field did not have utf-8 text")), From faf737b63668f2f408e4b8d5af34a6628e9a0ec3 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Fri, 29 Apr 2016 00:31:12 +0300 Subject: [PATCH 4/8] Add extract_file example --- examples/extract_file.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 examples/extract_file.rs diff --git a/examples/extract_file.rs b/examples/extract_file.rs new file mode 100644 index 00000000..51c9aa56 --- /dev/null +++ b/examples/extract_file.rs @@ -0,0 +1,20 @@ +extern crate tar; + +use std::io::{stdin, stdout, copy}; +use std::env::args_os; +use std::path::Path; + +use tar::Archive; + + +fn main() { + let first_arg = args_os().skip(1).next().unwrap(); + let filename = Path::new(&first_arg); + let mut arch = Archive::new(stdin()); + for file in arch.entries().unwrap() { + let mut f = file.unwrap(); + if f.header().path().unwrap() == filename { + copy(&mut f, &mut stdout()).unwrap(); + } + } +} From 4c6cdcab8407343d3bab701feef61000c4060ce1 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Fri, 29 Apr 2016 00:41:51 +0300 Subject: [PATCH 5/8] Implement Read trait for Entry having sparse file --- src/archive.rs | 29 ++++++++++++-- src/entry.rs | 4 +- src/header.rs | 17 ++++---- src/lib.rs | 1 + src/reader.rs | 104 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 140 insertions(+), 15 deletions(-) create mode 100644 src/reader.rs diff --git a/src/archive.rs b/src/archive.rs index 763253c6..3255c486 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -10,6 +10,7 @@ use std::path::{Path, Component}; use entry::EntryFields; use error::TarError; use header::{GnuExtSparseHeader, octal_from}; +use reader::Reader; use other; use {Entry, Header}; @@ -245,7 +246,9 @@ impl<'a> EntriesFields<'a> { return Err(other("archive header checksum mismatch")) } - header.sparse_chunks = if let Some(gnu) = header.as_gnu_mut() { + let data_size = try!(header.data_size()); + let size = try!(header.size()); + let sparse_chunks = if let Some(gnu) = header.as_gnu_mut() { let mut chunks = Vec::new(); for item in &gnu.sparse[..] { if item.offset[0] != 0 && item.numbytes[0] != 0 { @@ -277,17 +280,35 @@ impl<'a> EntriesFields<'a> { } } } + if chunks.len() > 0 { + // Ensure chunks are in order and sized properly + let mut offset = 0; + let mut bytes = 0; + for chunk in &chunks { + if chunk.0 < offset { + return Err(other( + "out of order or overlapping sparse chunks")) + } + bytes += chunk.1; + offset = chunk.0 + chunk.1; + } + if bytes != data_size { + return Err(other("wrong total length of data for sparse file")) + } + if offset > size { + return Err(other("sparse chunk end exceeds file size")) + } + } chunks } else { Vec::new() }; - let data_size = try!(header.data_size()); - let size = try!(header.size()); let ret = EntryFields { size: size, + data: Reader::new(header.entry_type(), + &self.archive.inner, sparse_chunks, size), header: header, - data: (&self.archive.inner).take(data_size), long_pathname: None, long_linkname: None, pax_extensions: None, diff --git a/src/entry.rs b/src/entry.rs index 3516c164..db0aab26 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -9,10 +9,10 @@ use std::path::Path; use filetime::{self, FileTime}; use {Header, Archive, PaxExtensions}; -use archive::ArchiveInner; use error::TarError; use header::bytes2path; use other; +use reader::Reader; use pax::pax_extensions; /// A read-only view into an entry of an archive. @@ -33,7 +33,7 @@ pub struct EntryFields<'a> { pub pax_extensions: Option>, pub header: Header, pub size: u64, - pub data: io::Take<&'a ArchiveInner>, + pub data: Reader<'a>, } impl<'a, R: Read> Entry<'a, R> { diff --git a/src/header.rs b/src/header.rs index 0f244f89..db28ae72 100644 --- a/src/header.rs +++ b/src/header.rs @@ -18,7 +18,6 @@ use other; #[allow(missing_docs)] pub struct Header { bytes: [u8; 512], - pub sparse_chunks: Vec<(u64, u64)>, } /// Representation of the header of an entry in an archive @@ -117,7 +116,7 @@ impl Header { /// extensions such as long path names, long link names, and setting the /// atime/ctime metadata attributes of files. pub fn new_gnu() -> Header { - let mut header = Header { bytes: [0; 512], sparse_chunks: Vec::new() }; + let mut header = Header { bytes: [0; 512] }; { let gnu = header.cast_mut::(); gnu.magic = *b"ustar "; @@ -134,7 +133,7 @@ impl Header { /// /// UStar is also the basis used for pax archives. pub fn new_ustar() -> Header { - let mut header = Header { bytes: [0; 512], sparse_chunks: Vec::new() }; + let mut header = Header { bytes: [0; 512] }; { let gnu = header.cast_mut::(); gnu.magic = *b"ustar\0"; @@ -150,17 +149,17 @@ impl Header { /// format limits the path name limit and isn't able to contain extra /// metadata like atime/ctime. pub fn new_old() -> Header { - Header { bytes: [0; 512], sparse_chunks: Vec::new() } + Header { bytes: [0; 512] } } fn cast(&self) -> &T { - assert_eq!(mem::size_of_val(&self.bytes), mem::size_of::()); - unsafe { &*(&self.bytes as *const [u8; 512] as *const T) } + assert_eq!(mem::size_of_val(self), mem::size_of::()); + unsafe { &*(self as *const Header as *const T) } } fn cast_mut(&mut self) -> &mut T { - assert_eq!(mem::size_of_val(&self.bytes), mem::size_of::()); - unsafe { &mut *(&mut self.bytes as *mut [u8; 512] as *mut T) } + assert_eq!(mem::size_of_val(self), mem::size_of::()); + unsafe { &mut *(self as *mut Header as *mut T) } } fn is_ustar(&self) -> bool { @@ -651,7 +650,7 @@ impl Header { impl Clone for Header { fn clone(&self) -> Header { - Header { bytes: self.bytes, sparse_chunks: self.sparse_chunks.clone() } + Header { bytes: self.bytes } } } diff --git a/src/lib.rs b/src/lib.rs index f5cea41e..302d142a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,6 +41,7 @@ mod entry_type; mod error; mod header; mod pax; +mod reader; // FIXME(rust-lang/rust#26403): // Right now there's a bug when a DST struct's last field has more diff --git a/src/reader.rs b/src/reader.rs new file mode 100644 index 00000000..e81b4431 --- /dev/null +++ b/src/reader.rs @@ -0,0 +1,104 @@ +use std::io::{self, Read}; +use std::cmp::min; + +use EntryType; +use archive::ArchiveInner; + + +pub enum Reader<'a> { + Normal(io::Take<&'a ArchiveInner>), + Sparse { + data: &'a ArchiveInner, + blocks: Vec<(u64, u64)>, + block: usize, + position: u64, + size: u64, + }, +} + +impl<'a> Reader<'a> { + pub fn new<'x>(typ: EntryType, reader: &'x ArchiveInner, + sparse_chunks: Vec<(u64, u64)>, file_size: u64) + -> Reader<'x> + { + match typ { + EntryType::GNUSparse => { + Reader::Sparse { + data: reader, + blocks: sparse_chunks, + block: 0, + position: 0, + size: file_size, + } + } + _ => Reader::Normal(reader.take(file_size)), + } + } +} + +impl<'a> io::Read for Reader<'a> { + /// The reader implementation emits sparse file as fully alocated file + /// that has absent blocks zeroed + /// + /// This is non-optimal if you unpack to filesystem but correct and useful + /// if you feed the file to some streaming parser or whatever. + fn read(&mut self, mut into: &mut [u8]) -> io::Result { + match *self { + Reader::Normal(ref mut reader) => reader.read(into), + Reader::Sparse { + ref mut data, ref blocks, ref mut block, + ref mut position, size, + } => { + let mut bytes_read: usize = 0; + while into.len() > bytes_read { + let dest = &mut into[bytes_read..]; + if *block >= blocks.len() { + // after last block + if *position + dest.len() as u64 > size { + let bytes = (size - *position) as usize; + for i in &mut dest[..bytes] { *i = 0; } + *position += bytes as u64; + return Ok(bytes_read + bytes); + } + for i in &mut dest[..] { *i = 0; } + *position += dest.len() as u64; + return Ok(bytes_read + dest.len()); + } else if blocks[*block].0 > *position { + // before the next block + let bytes = min( + (blocks[*block].0 - *position) as usize, + dest.len()); + for i in &mut dest[..bytes] { *i = 0; } + *position += bytes as u64; + bytes_read += bytes; + } else { + let block_off = *position - blocks[*block].0; + debug_assert!(block_off < blocks[*block].1); + if block_off + (dest.len() as u64) < blocks[*block].1 { + // partially read block + let read = try!(data.read(dest)); + *position += read as u64; + return Ok(bytes_read + read); + } else { + // fully read block + debug_assert!(blocks[*block].1 <= + block_off + dest.len() as u64); + let bytes = (blocks[*block].1 - block_off) + as usize; + let real = try!(data.read(&mut dest[..bytes])); + *position += real as u64; + bytes_read += real; + if real < bytes { + // Partial read is returned as partial read + return Ok(bytes_read); + } else { + *block += 1; + } + } + } + } + return Ok(bytes_read); + } + } + } +} From c41bfba2728d726440a16007cc1790a97ec5e408 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Fri, 29 Apr 2016 22:56:25 +0300 Subject: [PATCH 6/8] Better encapsulation for code handling sparse files --- src/archive.rs | 28 +++++++++++++--------------- src/header.rs | 44 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/src/archive.rs b/src/archive.rs index 3255c486..768738f8 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -3,13 +3,12 @@ use std::cmp; use std::fs; use std::io::prelude::*; use std::io; -use std::mem::transmute; use std::marker; use std::path::{Path, Component}; use entry::EntryFields; use error::TarError; -use header::{GnuExtSparseHeader, octal_from}; +use header::GnuExtSparseHeader; use reader::Reader; use other; use {Entry, Header}; @@ -251,31 +250,30 @@ impl<'a> EntriesFields<'a> { let sparse_chunks = if let Some(gnu) = header.as_gnu_mut() { let mut chunks = Vec::new(); for item in &gnu.sparse[..] { - if item.offset[0] != 0 && item.numbytes[0] != 0 { - let off = try!(octal_from(&item.offset[..])); - let nbytes = try!(octal_from(&item.numbytes[..])); + if !item.is_empty() { + let off = try!(item.offset()); + let nbytes = try!(item.length()); if nbytes > 0 { chunks.push((off, nbytes)); } } } - if gnu.isextended[0] == 1 { + if gnu.is_extended() { loop { - let mut ext_header = [0u8; 512]; - try!(read_all(&mut &self.archive.inner, &mut ext_header)); + let mut sparse_header = GnuExtSparseHeader::new(); + try!(read_all(&mut &self.archive.inner, + sparse_header.as_mut_bytes())); self.next += 512; - let sparse_header: &GnuExtSparseHeader; - sparse_header = unsafe { transmute(&ext_header) }; - for item in &sparse_header.sparse { - if item.offset[0] != 0 && item.numbytes[0] != 0 { - let off = try!(octal_from(&item.offset[..])); - let nbytes = try!(octal_from(&item.numbytes[..])); + for item in sparse_header.chunks() { + if !item.is_empty() { + let off = try!(item.offset()); + let nbytes = try!(item.length()); if nbytes > 0 { chunks.push((off, nbytes)); } } } - if sparse_header.isextended[0] == 0 { + if !sparse_header.is_extended() { break; } } diff --git a/src/header.rs b/src/header.rs index db28ae72..0e12f44e 100644 --- a/src/header.rs +++ b/src/header.rs @@ -103,8 +103,9 @@ pub struct GnuSparseHeader { #[repr(C)] #[allow(missing_docs)] pub struct GnuExtSparseHeader { - pub sparse: [GnuSparseHeader; 21], - pub isextended: [u8; 1], + sparse: [GnuSparseHeader; 21], + isextended: [u8; 1], + padding: [u8; 7], } @@ -817,9 +818,46 @@ impl GnuHeader { pub fn set_ctime(&mut self, ctime: u64) { octal_into(&mut self.ctime, ctime); } + + /// Does header contain extended sparse file chunk headers + pub fn is_extended(&self) -> bool { + self.isextended[0] == 1 + } +} + +impl GnuSparseHeader { + /// Returns true if block is empty + pub fn is_empty(&self) -> bool { + self.offset[0] == 0 || self.numbytes[0] == 0 + } + /// Offset of the block from the start of the file + pub fn offset(&self) -> io::Result { + octal_from(&self.offset[..]) + } + /// Length of the block + pub fn length(&self) -> io::Result { + octal_from(&self.numbytes[..]) + } +} + +impl GnuExtSparseHeader { + pub fn new() -> GnuExtSparseHeader { + unsafe { mem::zeroed() } + } + /// Returns a view into this header as a byte array. + pub fn as_mut_bytes(&mut self) -> &mut [u8; 512] { + debug_assert_eq!(mem::size_of_val(self), 512); + unsafe { mem::transmute(self) } + } + pub fn chunks(&self) -> &[GnuSparseHeader] { + &self.sparse[..] + } + pub fn is_extended(&self) -> bool { + self.isextended[0] == 1 + } } -pub fn octal_from(slice: &[u8]) -> io::Result { +fn octal_from(slice: &[u8]) -> io::Result { let num = match str::from_utf8(truncate(slice)) { Ok(n) => n, Err(_) => return Err(other("numeric field did not have utf-8 text")), From 63aa260472b64abbd8d648dbcf751ed390a4e5ce Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Fri, 29 Apr 2016 23:21:00 +0300 Subject: [PATCH 7/8] Add sparse file test --- tests/all.rs | 33 +++++++++++++++++++++++++++++++++ tests/archives/sparse.tar | Bin 0 -> 10240 bytes 2 files changed, 33 insertions(+) create mode 100644 tests/archives/sparse.tar diff --git a/tests/all.rs b/tests/all.rs index 3cbc5670..b3e16194 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -530,3 +530,36 @@ fn encoded_long_name_has_trailing_nul() { t!(e.read_to_end(&mut name)); assert_eq!(name[name.len() - 1], 0); } + +#[test] +fn reading_sparse() { + let rdr = Cursor::new(tar!("sparse.tar")); + let mut ar = Archive::new(rdr); + let mut entries = t!(ar.entries()); + + let mut a = t!(entries.next().unwrap()); + let mut s = String::new(); + assert_eq!(&*a.header().path_bytes(), b"sparse_begin.txt"); + t!(a.read_to_string(&mut s)); + assert_eq!(&s[..5], "test\n"); + assert!(s[5..].chars().all(|x| x == '\u{0}')); + + let mut a = t!(entries.next().unwrap()); + let mut s = String::new(); + assert_eq!(&*a.header().path_bytes(), b"sparse_end.txt"); + t!(a.read_to_string(&mut s)); + assert!(s[..s.len() - 9].chars().all(|x| x == '\u{0}')); + assert_eq!(&s[s.len() - 9..], "test_end\n"); + + let mut a = t!(entries.next().unwrap()); + let mut s = String::new(); + assert_eq!(&*a.header().path_bytes(), b"sparse.txt"); + t!(a.read_to_string(&mut s)); + assert!(s[..0x1000].chars().all(|x| x == '\u{0}')); + assert_eq!(&s[0x1000..0x1000+6], "hello\n"); + assert!(s[0x1000+6..0x2fa0].chars().all(|x| x == '\u{0}')); + assert_eq!(&s[0x2fa0..0x2fa0+6], "world\n"); + assert!(s[0x2fa0+6..0x4000].chars().all(|x| x == '\u{0}')); + + assert!(entries.next().is_none()); +} diff --git a/tests/archives/sparse.tar b/tests/archives/sparse.tar new file mode 100644 index 0000000000000000000000000000000000000000..6f870c0e33de708b8f1ef003b6595b49d84f5111 GIT binary patch literal 10240 zcmeH}ZEnLL42Jo=Q{)7bo$zsx9iZwqv`vcIDo}05p9x)?iY-#4@MFr0l;9XJCSNueh7D-l|B-JOBuyT9T`Pzv^Cq(Kf9aeC0h!S)yh@>YX)6ybqF*|Am!(ssIyj%&ib#X z{q%Eb1J3AQPW#*WuZ>OTzgz}3rvC)#m-D}!=7q_LUoH7BSqS=q00@8p2!H?xfB*=900@8p2!H?xfB*=900@8p2!H?x KfB*>m3j*KP2xoBs literal 0 HcmV?d00001 From ff91870a9e598196d8dba5503cae38169adaaa98 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Fri, 29 Apr 2016 23:36:35 +0300 Subject: [PATCH 8/8] Add sparse file that has extended sparse headers --- tests/all.rs | 17 +++++++++++++++++ tests/archives/sparse.tar | Bin 10240 -> 10240 bytes 2 files changed, 17 insertions(+) diff --git a/tests/all.rs b/tests/all.rs index b3e16194..6a09da56 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -551,6 +551,23 @@ fn reading_sparse() { assert!(s[..s.len() - 9].chars().all(|x| x == '\u{0}')); assert_eq!(&s[s.len() - 9..], "test_end\n"); + let mut a = t!(entries.next().unwrap()); + let mut s = String::new(); + assert_eq!(&*a.header().path_bytes(), b"sparse_ext.txt"); + t!(a.read_to_string(&mut s)); + assert!(s[..0x1000].chars().all(|x| x == '\u{0}')); + assert_eq!(&s[0x1000..0x1000+5], "text\n"); + assert!(s[0x1000+5..0x3000].chars().all(|x| x == '\u{0}')); + assert_eq!(&s[0x3000..0x3000+5], "text\n"); + assert!(s[0x3000+5..0x5000].chars().all(|x| x == '\u{0}')); + assert_eq!(&s[0x5000..0x5000+5], "text\n"); + assert!(s[0x5000+5..0x7000].chars().all(|x| x == '\u{0}')); + assert_eq!(&s[0x7000..0x7000+5], "text\n"); + assert!(s[0x7000+5..0x9000].chars().all(|x| x == '\u{0}')); + assert_eq!(&s[0x9000..0x9000+5], "text\n"); + assert!(s[0x9000+5..0xb000].chars().all(|x| x == '\u{0}')); + assert_eq!(&s[0xb000..0xb000+5], "text\n"); + let mut a = t!(entries.next().unwrap()); let mut s = String::new(); assert_eq!(&*a.header().path_bytes(), b"sparse.txt"); 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!;