oci-image 0.3.1

OCI container images on disk: image references, sha256 digests, one-pass layer unpack (whiteouts and opaque directories), gzip (all cores via lgz one-pass streaming decode; small layers on one core) and zstd layers, a content-addressed store of blobs and per-layer directories in overlayfs format (each layer unpacked once, shared between images, refcounted, collected), and overlay snapshot prep.
Documentation
#![allow(dead_code)]

use std::fs;
use std::io::Write;
use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::path::{Path, PathBuf};

/// One tar entry for a test layer.
pub enum E<'a> {
    Dir(&'a str),
    File(&'a str, &'a [u8]),
    Mode(&'a str, &'a [u8], u32),
    Link(&'a str, &'a str),
    Hard(&'a str, &'a str),
}

pub fn tar_bytes(entries: &[E]) -> Vec<u8> {
    let mut b = tar::Builder::new(Vec::new());
    for e in entries {
        let mut h = tar::Header::new_gnu();
        h.set_uid(0);
        h.set_gid(0);
        h.set_mtime(1_000_000);
        match e {
            E::Dir(p) => {
                h.set_entry_type(tar::EntryType::Directory);
                h.set_size(0);
                h.set_mode(0o755);
                b.append_data(&mut h, p, &[][..]).unwrap();
            }
            E::File(p, c) | E::Mode(p, c, _) => {
                let mode = if let E::Mode(_, _, m) = e { *m } else { 0o644 };
                h.set_entry_type(tar::EntryType::Regular);
                h.set_size(c.len() as u64);
                h.set_mode(mode);
                b.append_data(&mut h, p, *c).unwrap();
            }
            E::Link(p, target) => {
                h.set_entry_type(tar::EntryType::Symlink);
                h.set_size(0);
                h.set_mode(0o777);
                b.append_link(&mut h, p, target).unwrap();
            }
            E::Hard(p, target) => {
                h.set_entry_type(tar::EntryType::Link);
                h.set_size(0);
                h.set_mode(0o644);
                b.append_link(&mut h, p, target).unwrap();
            }
        }
    }
    b.into_inner().unwrap()
}

pub fn gzip(bytes: &[u8]) -> Vec<u8> {
    let mut e = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::fast());
    e.write_all(bytes).unwrap();
    e.finish().unwrap()
}

/// Gzip with a full flush every `every` input bytes, as `pigz --independent`
/// writes it: every segment decodes on its own.
pub fn gzip_full_flush(bytes: &[u8], every: usize) -> Vec<u8> {
    use flate2::{Compress, Compression, FlushCompress};
    let mut out = vec![0x1f, 0x8b, 8, 0, 0, 0, 0, 0, 0, 0xff];
    let mut c = Compress::new(Compression::fast(), false);
    let mut buf = vec![0u8; every * 2 + 1024];
    for (i, chunk) in bytes.chunks(every).enumerate() {
        let last = (i + 1) * every >= bytes.len();
        let flush = if last {
            FlushCompress::Finish
        } else {
            FlushCompress::Full
        };
        let mut input = chunk;
        loop {
            let (bi, bo) = (c.total_in(), c.total_out());
            c.compress(input, &mut buf, flush).unwrap();
            let used = (c.total_in() - bi) as usize;
            let made = (c.total_out() - bo) as usize;
            out.extend_from_slice(&buf[..made]);
            input = &input[used..];
            if input.is_empty() && made < buf.len() {
                break;
            }
        }
    }
    let mut crc = flate2::Crc::new();
    crc.update(bytes);
    out.extend_from_slice(&crc.sum().to_le_bytes());
    out.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
    out
}

pub fn zstd(bytes: &[u8]) -> Vec<u8> {
    zstd::encode_all(bytes, 3).unwrap()
}

pub fn blob(dir: &Path, name: &str, bytes: &[u8]) -> PathBuf {
    let p = dir.join(name);
    fs::write(&p, bytes).unwrap();
    p
}

/// Deterministic bytes that do not compress.
pub fn noise(len: usize, seed: u64) -> Vec<u8> {
    let mut x = seed | 1;
    (0..len)
        .map(|_| {
            x ^= x << 13;
            x ^= x >> 7;
            x ^= x << 17;
            x as u8
        })
        .collect()
}

/// Deterministic text that compresses like source code does.
pub fn text(len: usize) -> Vec<u8> {
    let words = [
        "fn ",
        "let ",
        "self",
        ".unwrap()",
        "\n",
        "    ",
        "match ",
        "Ok(",
        "path",
        "; ",
    ];
    noise(len, 11)
        .iter()
        .flat_map(|b| words[(*b % 10) as usize].bytes())
        .take(len)
        .collect()
}

/// Every path under `root` with its kind, mode and contents (or link target).
pub fn tree(root: &Path) -> Vec<(PathBuf, String)> {
    fn walk(root: &Path, dir: &Path, out: &mut Vec<(PathBuf, String)>) {
        for e in fs::read_dir(dir).unwrap().flatten() {
            let p = e.path();
            let rel = p.strip_prefix(root).unwrap().to_path_buf();
            let m = fs::symlink_metadata(&p).unwrap();
            let mode = m.permissions().mode() & 0o7777;
            if m.file_type().is_symlink() {
                let t = fs::read_link(&p).unwrap();
                out.push((rel, format!("link {}", t.display())));
            } else if m.is_dir() {
                out.push((rel, format!("dir {mode:o}")));
                walk(root, &p, out);
            } else {
                let c = fs::read(&p).unwrap();
                let shown = if c.len() > 64 {
                    format!("{} bytes", c.len())
                } else {
                    String::from_utf8_lossy(&c).into_owned()
                };
                out.push((rel, format!("file {mode:o} n{} {shown}", m.nlink())));
            }
        }
    }
    let mut out = Vec::new();
    walk(root, root, &mut out);
    out.sort();
    out
}