#![allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::doc_markdown,
clippy::elidable_lifetime_names,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::must_use_candidate,
clippy::unreadable_literal
)]
mod count;
mod decode;
mod encode;
mod independent;
pub mod io;
mod refmut;
use crate::count::Count;
use crate::decode::{decode_from_end, decode_from_start};
use crate::encode::encode;
use crate::io::{ChapterReader, ChapterReaderImpl, ChapterWriter};
use crate::refmut::RefMut;
use flate2::Crc;
use flate2::read::{DeflateDecoder, GzDecoder};
use flate2::write::DeflateEncoder;
use std::io::{Error, ErrorKind, Read, Result, Seek, SeekFrom, Write};
use std::mem;
pub extern crate tar;
pub use crate::independent::IndependentRead;
#[doc(no_inline)]
pub use flate2::Compression;
pub struct TgzWriter<W: Write> {
state: TgzWriterState<W>,
crc: Crc,
level: Compression,
chapters: u32,
boundaries_written: u32,
last_boundary: u64,
}
enum TgzWriterState<W: Write> {
Empty(W),
Chapter(DeflateEncoder<Count<W>>),
Finished(W),
Failed(ErrorKind),
}
pub struct TgzReader<R> {
data: R,
boundaries: Vec<u64>,
chapter: u32,
}
static GZIP_HEADER: [u8; 10] = [
0x1F, 0x8B, 8, 0, 0, 0, 0, 0, 0, 255, ];
static TAR_TERMINATION_SECTIONS: [u8; 1024] = [0; 1024];
impl<W> TgzWriter<W>
where
W: Write,
{
pub fn new(out: W, level: Compression) -> Self {
TgzWriter {
state: TgzWriterState::Empty(out),
crc: Crc::new(),
level,
chapters: 0,
boundaries_written: 0,
last_boundary: 0,
}
}
#[track_caller]
pub fn create_chapter(&mut self) -> tar::Builder<ChapterWriter<'_, W>> {
if let TgzWriterState::Finished(_) = self.state {
panic!("called create_chapter on a TgzWriter that is already finished");
}
let index = self.chapters;
self.chapters = self.chapters.strict_add(1);
tar::Builder::new(ChapterWriter {
tgz: self,
index,
deferred_termination_sections: false,
})
}
pub fn chapters(&self) -> u32 {
self.chapters
}
#[track_caller]
pub fn finish(&mut self) -> Result<()> {
if let TgzWriterState::Finished(_) = self.state {
panic!("TgzWriter got finished twice");
}
self.do_finish()?;
Ok(())
}
pub fn into_inner(mut self) -> Result<W> {
self.do_finish()?;
let TgzWriterState::Finished(writer) =
mem::replace(&mut self.state, TgzWriterState::Failed(ErrorKind::Other))
else {
unreachable!()
};
Ok(writer)
}
fn write_boundary(&mut self) -> Result<()> {
match mem::replace(&mut self.state, TgzWriterState::Failed(ErrorKind::Other)) {
TgzWriterState::Empty(mut writer) => {
writer.write_all(&GZIP_HEADER)?;
self.boundaries_written += 1;
self.state =
TgzWriterState::Chapter(DeflateEncoder::new(Count::new(writer), self.level));
Ok(())
}
TgzWriterState::Chapter(out) => {
let mut writer = out.flush_finish()?;
let bfinal = false;
let payload = writer.position().strict_sub(self.last_boundary);
self.last_boundary = writer.position();
let boundary = encode(bfinal, payload);
writer.write_all(boundary.as_slice())?;
self.boundaries_written += 1;
self.state = TgzWriterState::Chapter(DeflateEncoder::new(writer, self.level));
Ok(())
}
TgzWriterState::Finished(_) => unreachable!(),
TgzWriterState::Failed(kind) => Err(Error::new(kind, "TgzWriter error")),
}
}
fn do_finish(&mut self) -> Result<()> {
while self.boundaries_written == 0 || self.boundaries_written < self.chapters {
self.write_boundary()?;
}
match mem::replace(&mut self.state, TgzWriterState::Failed(ErrorKind::Other)) {
TgzWriterState::Empty(_) => unreachable!(),
TgzWriterState::Chapter(mut out) => {
out.write_all(&TAR_TERMINATION_SECTIONS)?;
self.crc.update(&TAR_TERMINATION_SECTIONS);
let writer = out.flush_finish()?;
let bfinal = true;
let mut payload = writer.position().strict_sub(self.last_boundary);
if self.chapters == 0 {
payload += GZIP_HEADER.len() as u64;
}
let boundary = encode(bfinal, payload);
let mut writer = writer.into_inner();
writer.write_all(boundary.as_slice())?;
writer.write_all(&self.crc.sum().to_le_bytes())?;
writer.write_all(&self.crc.amount().to_le_bytes())?;
writer.flush()?;
self.state = TgzWriterState::Finished(writer);
Ok(())
}
TgzWriterState::Finished(writer) => {
self.state = TgzWriterState::Finished(writer);
Ok(())
}
TgzWriterState::Failed(kind) => {
self.state = TgzWriterState::Failed(kind);
Err(Error::new(kind, "TgzWriter error"))
}
}
}
}
impl<W> Drop for TgzWriter<W>
where
W: Write,
{
fn drop(&mut self) {
let _ = self.do_finish();
}
}
impl<'a, W> Write for ChapterWriter<'a, W>
where
W: Write,
{
fn write(&mut self, buf: &[u8]) -> Result<usize> {
if buf.is_empty() {
return Ok(0);
}
if self.deferred_termination_sections {
if self.tgz.boundaries_written > self.index {
match &mut self.tgz.state {
TgzWriterState::Chapter(out) => {
if let Err(err) = out.write_all(&TAR_TERMINATION_SECTIONS) {
self.tgz.state = TgzWriterState::Failed(err.kind());
return Err(err);
}
}
TgzWriterState::Empty(_) | TgzWriterState::Finished(_) => unreachable!(),
TgzWriterState::Failed(kind) => {
return Err(Error::new(*kind, "TgzWriter error"));
}
}
}
self.deferred_termination_sections = false;
}
if buf == TAR_TERMINATION_SECTIONS {
self.deferred_termination_sections = true;
return Ok(buf.len());
}
while self.tgz.boundaries_written <= self.index {
if let Err(err) = self.tgz.write_boundary() {
self.tgz.state = TgzWriterState::Failed(err.kind());
return Err(err);
}
}
let n = match &mut self.tgz.state {
TgzWriterState::Chapter(out) => out.write(buf)?,
TgzWriterState::Empty(_) | TgzWriterState::Finished(_) => unreachable!(),
TgzWriterState::Failed(kind) => {
return Err(Error::new(*kind, "TgzWriter error"));
}
};
self.tgz.crc.update(&buf[..n]);
Ok(n)
}
fn flush(&mut self) -> Result<()> {
match &mut self.tgz.state {
TgzWriterState::Empty(_) => Ok(()),
TgzWriterState::Chapter(out) => out.flush(),
TgzWriterState::Finished(_) => unreachable!(),
TgzWriterState::Failed(kind) => Err(Error::new(*kind, "TgzWriter error")),
}
}
}
impl<R> TgzReader<R>
where
R: Read + Seek,
{
pub fn open(mut read: R) -> Result<Self> {
let start = read.stream_position()?;
let mut prefix_buf = [0u8; GZIP_HEADER.len()];
let mut payload_buf = [0u8; encode::MAX_BYTES_FINAL];
let mut boundaries = Vec::new();
if read.read_exact(&mut prefix_buf).is_ok()
&& prefix_buf == GZIP_HEADER
&& let Ok(seek) = read.seek(SeekFrom::End(-(encode::MAX_BYTES_FINAL as i64 + 4)))
&& read.read_exact(&mut payload_buf).is_ok()
&& let Some((payload_start, mut payload)) = decode_from_end(&payload_buf)
&& {
let mut boundary = seek.strict_add(payload_start as u64);
boundaries.push(boundary);
loop {
let Some(prev) = boundary.checked_sub(payload) else {
break false;
};
if prev == start {
break true;
}
boundaries.push(prev);
if prev == start.strict_add(GZIP_HEADER.len() as u64) {
break true;
}
if read.seek(SeekFrom::Start(prev)).is_ok()
&& read
.read_exact(&mut payload_buf[..encode::MAX_BYTES_NONFINAL])
.is_ok()
&& let Some(prev_payload) = decode_from_start(&payload_buf)
{
boundary = prev;
payload = prev_payload;
} else {
break false;
}
}
}
&& u32::try_from(boundaries.len()).is_ok()
{
boundaries.reverse();
} else {
let end = read.seek(SeekFrom::End(0))?;
boundaries.clear();
boundaries.push(0);
boundaries.push(end.saturating_sub(start));
read.seek(SeekFrom::Start(start))?;
}
Ok(TgzReader {
data: read,
boundaries,
chapter: 0,
})
}
pub fn chapters(&self) -> u32 {
self.boundaries.len() as u32 - 1
}
pub fn next_chapter(&mut self) -> Option<tar::Archive<ChapterReader<'_, R>>> {
if self.boundaries[0] == 0 {
if self.chapter != 0 {
return None;
}
self.chapter = 1;
Some(tar::Archive::new(ChapterReader {
imp: ChapterReaderImpl::Dumb(GzDecoder::new(RefMut::Borrowed(&mut self.data))),
}))
} else {
if self.chapter == self.boundaries.len() as u32 - 1 {
return None;
}
let index = self.chapter;
let begin = self.boundaries[index as usize];
let end = self.boundaries[index as usize + 1];
self.chapter += 1;
Some(tar::Archive::new(ChapterReader {
imp: ChapterReaderImpl::SeekTo(begin..end, RefMut::Borrowed(&mut self.data)),
}))
}
}
#[track_caller]
pub fn jump_to_chapter(&mut self, i: u32) -> tar::Archive<ChapterReader<'_, R>> {
assert!(i < self.chapters());
self.chapter = i;
self.next_chapter().unwrap()
}
#[track_caller]
pub fn independent_read_chapter<'a>(&self, i: u32) -> Result<tar::Archive<ChapterReader<'a, R>>>
where
R: IndependentRead + 'a,
{
assert!(i < self.chapters());
let data = self.data.independent_clone()?;
if self.boundaries[0] == 0 {
Ok(tar::Archive::new(ChapterReader {
imp: ChapterReaderImpl::Dumb(GzDecoder::new(RefMut::Owned(Box::new(data)))),
}))
} else {
let begin = self.boundaries[i as usize];
let end = self.boundaries[i as usize + 1];
Ok(tar::Archive::new(ChapterReader {
imp: ChapterReaderImpl::SeekTo(begin..end, RefMut::Owned(Box::new(data))),
}))
}
}
#[track_caller]
pub fn compressed_size_of_chapter(&self, i: u32) -> u64 {
assert!(i < self.chapters());
let begin = self.boundaries[i as usize];
let end = self.boundaries[i as usize + 1];
end - begin
}
}
impl<'a, R> Read for ChapterReader<'a, R>
where
R: Read + Seek,
{
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
match &mut self.imp {
imp @ ChapterReaderImpl::SeekTo(..) => {
let ChapterReaderImpl::SeekTo(range, mut data) =
mem::replace(imp, ChapterReaderImpl::Failed(ErrorKind::Other))
else {
unreachable!()
};
data.seek(SeekFrom::Start(range.start))?;
let mut decoder = DeflateDecoder::new(data.take(range.end - range.start));
let result = decoder.read(buf);
*imp = ChapterReaderImpl::Smart(decoder);
result
}
ChapterReaderImpl::Smart(data) => data.read(buf),
ChapterReaderImpl::Dumb(gz) => gz.read(buf),
ChapterReaderImpl::Failed(kind) => Err(Error::new(*kind, "TgzReader error")),
}
}
}