use std::fs::File;
use std::io::{self, BufReader, Read, Seek, SeekFrom, Write};
use std::path::Path;
use std::sync::mpsc::{self, Receiver, SyncSender};
use std::sync::{Arc, Mutex};
use std::thread;
use crate::error::Context;
use crate::{Digest, Hasher, Result, UnpackOptions};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Decoder {
Plain,
GzipStream,
GzipParallel,
Zstd,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Compression {
None,
Gzip,
Zstd,
}
impl Compression {
pub fn detect(head: &[u8]) -> Self {
if head.starts_with(&[0x1f, 0x8b]) {
Self::Gzip
} else if head.starts_with(&[0x28, 0xb5, 0x2f, 0xfd]) {
Self::Zstd
} else {
Self::None
}
}
}
pub const ZSTD_OFF: &str = "a zstd layer: this build of oci-image has no feature `zstd`";
#[cfg(feature = "zstd")]
fn zstd_reader<'a, R: Read + Send + 'a>(
r: R,
what: impl FnOnce() -> String,
) -> Result<Box<dyn Read + Send + 'a>> {
Ok(Box::new(zstd::stream::read::Decoder::new(r).ctx(what)?))
}
#[cfg(not(feature = "zstd"))]
fn zstd_reader<'a, R: Read + Send + 'a>(
_r: R,
_what: impl FnOnce() -> String,
) -> Result<Box<dyn Read + Send + 'a>> {
Err(crate::Error::Invalid(ZSTD_OFF.into()))
}
pub fn decoder(blob: &[u8]) -> Result<Box<dyn Read + Send + '_>> {
Ok(match Compression::detect(blob) {
Compression::None => Box::new(blob),
Compression::Gzip => Box::new(flate2::read::MultiGzDecoder::new(blob)),
Compression::Zstd => zstd_reader(blob, || "zstd header".into())?,
})
}
pub(crate) enum TarDigest {
None,
Inline(Arc<Mutex<Hasher>>),
Thread(thread::JoinHandle<Digest>),
}
impl TarDigest {
pub(crate) fn finish(self) -> Option<Digest> {
match self {
Self::None => None,
Self::Inline(h) => Some(std::mem::take(&mut *h.lock().unwrap()).finish()),
Self::Thread(t) => t.join().ok(),
}
}
}
pub(crate) fn open_layer(
path: &Path,
opts: &UnpackOptions,
digest: bool,
) -> Result<(Box<dyn Read + Send>, Decoder, TarDigest)> {
let what = || format!("opening layer {}", path.display());
let mut f = File::open(path).ctx(what)?;
let mut head = [0u8; 4];
let n = read_up_to(&mut f, &mut head).ctx(what)?;
f.seek(SeekFrom::Start(0)).ctx(what)?;
let len = f.metadata().ctx(what)?.len();
let (reader, decoder): (Box<dyn Read + Send>, Decoder) = match Compression::detect(&head[..n]) {
Compression::None => (
Box::new(BufReader::with_capacity(1 << 20, f)),
Decoder::Plain,
),
Compression::Zstd => (zstd_reader(f, what)?, Decoder::Zstd),
Compression::Gzip if opts.parallel_gzip && opts.threads() > 1 => {
let threads = opts.threads();
let decoder = if len >= lgz::stream::SERIAL_BELOW as u64 {
Decoder::GzipParallel
} else {
Decoder::GzipStream
};
let (reader, hash) = lgz_stream(f, len, threads, digest);
let d = match hash {
Some(t) => TarDigest::Thread(t),
None => TarDigest::None,
};
return Ok((Box::new(reader), decoder, d));
}
Compression::Gzip => (
Box::new(flate2::read::MultiGzDecoder::new(BufReader::with_capacity(
1 << 20,
f,
))),
Decoder::GzipStream,
),
};
if !digest {
return Ok((reader, decoder, TarDigest::None));
}
let hasher = Arc::new(Mutex::new(Hasher::default()));
let reader = HashReader {
inner: reader,
hasher: hasher.clone(),
};
Ok((Box::new(reader), decoder, TarDigest::Inline(hasher)))
}
struct HashReader<R> {
inner: R,
hasher: Arc<Mutex<Hasher>>,
}
impl<R: Read> Read for HashReader<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let n = self.inner.read(buf)?;
self.hasher.lock().unwrap().update(&buf[..n]);
Ok(n)
}
}
const PIECE: usize = 1 << 20;
const PIECES: usize = 8;
type Piece = io::Result<Arc<Vec<u8>>>;
fn lgz_stream(
file: File,
len: u64,
threads: usize,
digest: bool,
) -> (PieceReader, Option<thread::JoinHandle<Digest>>) {
let (tx, rx) = mpsc::sync_channel::<Piece>(PIECES);
let (hash_tx, hash) = if digest {
let (htx, hrx) = mpsc::sync_channel::<Arc<Vec<u8>>>(PIECES);
let t = thread::spawn(move || {
let mut h = Hasher::default();
for piece in hrx {
h.update(&piece);
}
h.finish()
});
(Some(htx), Some(t))
} else {
(None, None)
};
thread::spawn(move || {
let mut out = Tee {
buf: Vec::with_capacity(PIECE),
tx: tx.clone(),
hash: hash_tx,
};
let r = lgz::stream::decode_sized(file, &mut out, threads, Some(len));
let end = match r {
Ok(_) => out.flush(),
Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e.to_string())),
};
if let Err(e) = end {
let _ = tx.send(Err(e));
}
});
(
PieceReader {
rx,
cur: Arc::new(Vec::new()),
pos: 0,
},
hash,
)
}
struct Tee {
buf: Vec<u8>,
tx: SyncSender<Piece>,
hash: Option<SyncSender<Arc<Vec<u8>>>>,
}
impl Tee {
fn send(&mut self) -> io::Result<()> {
if self.buf.is_empty() {
return Ok(());
}
let piece = Arc::new(std::mem::replace(&mut self.buf, Vec::with_capacity(PIECE)));
let gone = || io::Error::new(io::ErrorKind::BrokenPipe, "the layer reader stopped");
if let Some(h) = &self.hash {
h.send(piece.clone()).map_err(|_| gone())?;
}
self.tx.send(Ok(piece)).map_err(|_| gone())
}
}
impl Write for Tee {
fn write(&mut self, b: &[u8]) -> io::Result<usize> {
let n = b.len().min(PIECE - self.buf.len());
self.buf.extend_from_slice(&b[..n]);
if self.buf.len() == PIECE {
self.send()?;
}
Ok(n)
}
fn flush(&mut self) -> io::Result<()> {
self.send()
}
}
pub(crate) struct PieceReader {
rx: Receiver<Piece>,
cur: Arc<Vec<u8>>,
pos: usize,
}
impl Read for PieceReader {
fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
while self.pos == self.cur.len() {
match self.rx.recv() {
Ok(piece) => {
self.cur = piece?;
self.pos = 0;
}
Err(_) => return Ok(0),
}
}
let n = out.len().min(self.cur.len() - self.pos);
out[..n].copy_from_slice(&self.cur[self.pos..self.pos + n]);
self.pos += n;
Ok(n)
}
}
fn read_up_to(r: &mut impl Read, buf: &mut [u8]) -> io::Result<usize> {
let mut n = 0;
while n < buf.len() {
match r.read(&mut buf[n..])? {
0 => break,
k => n += k,
}
}
Ok(n)
}