oliphaunt-extension-postgis-linux-arm64-gnu 0.2.0

Cargo artifact crate for the postgis Oliphaunt native extension carrier on linux-arm64-gnu.
Documentation
use std::collections::BTreeMap;
use std::env;
use std::fs;
use std::io::{self, Read};
use std::path::{Path, PathBuf};

const SCHEMA: &str = "oliphaunt-artifact-manifest-v1";
const PRODUCT: &str = "oliphaunt-extension-postgis";
const VERSION: &str = env!("CARGO_PKG_VERSION");
const KIND: &str = "extension";
const TARGET: &str = "aarch64-unknown-linux-gnu";
const RUNTIME_PRODUCT: &str = "liboliphaunt-native";
const RUNTIME_VERSION: &str = "0.2.0";
const EXTENSIONS: &[&str] = &[
    "postgis",
];
const EXTENSION_DEPENDENCIES: &[(&str, &[&str])] = &[
    ("postgis", &[]),
];
const PART_ROOTS: &[&str] = &[
    oliphaunt_extension_postgis_linux_arm64_gnu_part_001::PAYLOAD_ROOT,
    oliphaunt_extension_postgis_linux_arm64_gnu_part_002::PAYLOAD_ROOT,
    oliphaunt_extension_postgis_linux_arm64_gnu_part_003::PAYLOAD_ROOT,
    oliphaunt_extension_postgis_linux_arm64_gnu_part_004::PAYLOAD_ROOT,
    oliphaunt_extension_postgis_linux_arm64_gnu_part_005::PAYLOAD_ROOT,
    oliphaunt_extension_postgis_linux_arm64_gnu_part_006::PAYLOAD_ROOT,
    oliphaunt_extension_postgis_linux_arm64_gnu_part_007::PAYLOAD_ROOT,
    oliphaunt_extension_postgis_linux_arm64_gnu_part_008::PAYLOAD_ROOT,
    oliphaunt_extension_postgis_linux_arm64_gnu_part_009::PAYLOAD_ROOT,
    oliphaunt_extension_postgis_linux_arm64_gnu_part_010::PAYLOAD_ROOT,
    oliphaunt_extension_postgis_linux_arm64_gnu_part_011::PAYLOAD_ROOT,
    oliphaunt_extension_postgis_linux_arm64_gnu_part_012::PAYLOAD_ROOT,
    oliphaunt_extension_postgis_linux_arm64_gnu_part_013::PAYLOAD_ROOT,
    oliphaunt_extension_postgis_linux_arm64_gnu_part_014::PAYLOAD_ROOT,
    oliphaunt_extension_postgis_linux_arm64_gnu_part_015::PAYLOAD_ROOT,
    oliphaunt_extension_postgis_linux_arm64_gnu_part_016::PAYLOAD_ROOT,
    oliphaunt_extension_postgis_linux_arm64_gnu_part_017::PAYLOAD_ROOT,
    oliphaunt_extension_postgis_linux_arm64_gnu_part_018::PAYLOAD_ROOT,
];

fn main() {
    emit_manifest();
}

fn emit_manifest() {
    let out_dir = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR is set"));
    let payload = out_dir.join("payload");
    if payload.exists() {
        fs::remove_dir_all(&payload).expect("remove stale Oliphaunt extension payload");
    }
    fs::create_dir_all(&payload).expect("create Oliphaunt extension payload directory");

    let part_roots = part_roots();
    if part_roots.is_empty() {
        if env::var_os("OLIPHAUNT_ARTIFACT_CRATE_REQUIRE_PAYLOAD").is_some() {
            panic!("missing Oliphaunt extension payload part crates");
        }
        return;
    }

    let mut chunk_files: BTreeMap<String, Vec<(usize, PathBuf)>> = BTreeMap::new();
    for root in part_roots {
        println!("cargo::rerun-if-changed={}", root.display());
        copy_complete_files(&root.join("files"), &payload).expect("copy complete extension payload files");
        collect_chunks(&root.join("chunks"), &root.join("chunks"), &mut chunk_files)
            .expect("collect extension payload chunks");
    }

    for (relative, mut chunks) in chunk_files {
        chunks.sort_by_key(|(index, _)| *index);
        for (expected, (actual, _)) in chunks.iter().enumerate() {
            if *actual != expected {
                panic!("non-contiguous Oliphaunt extension chunk indexes for {relative}");
            }
        }
        let output = payload.join(&relative);
        if let Some(parent) = output.parent() {
            fs::create_dir_all(parent).expect("create reconstructed extension file parent");
        }
        let mut writer = fs::File::create(&output).expect("create reconstructed extension payload file");
        for (_, path) in chunks {
            let mut reader = fs::File::open(&path).expect("open extension payload chunk");
            io::copy(&mut reader, &mut writer).expect("append extension payload chunk");
        }
    }

    let files = collect_files(&payload).expect("collect reconstructed extension payload files");
    if files.is_empty() {
        panic!("Oliphaunt extension payload part crates produced no files");
    }
    let manifest = out_dir.join("oliphaunt-artifact.toml");
    let mut text = format!(
        "schema = {SCHEMA:?}\nproduct = {PRODUCT:?}\nversion = {VERSION:?}\nkind = {KIND:?}\ntarget = {TARGET:?}\nruntime-product = {RUNTIME_PRODUCT:?}\nruntime-version = {RUNTIME_VERSION:?}\n"
    );
    if SCHEMA == "oliphaunt-artifact-manifest-v1" {
        if EXTENSIONS.len() != 1 {
            panic!("v1 extension manifest requires exactly one member");
        }
        text.push_str(&format!("extension = {:?}\n", EXTENSIONS[0]));
        append_dependencies(&mut text, EXTENSIONS[0]);
        append_manifest_files(&mut text, &payload, "[[files]]");
    } else if SCHEMA == "oliphaunt-artifact-manifest-v2" {
        let extensions_root = payload.join("extensions");
        let actual_members = directory_names(&extensions_root).expect("read reconstructed extension bundle members");
        let expected_members: Vec<String> = EXTENSIONS.iter().map(|value| (*value).to_owned()).collect();
        if actual_members != expected_members {
            panic!("reconstructed extension bundle member set mismatch: expected {expected_members:?}, got {actual_members:?}");
        }
        for extension in EXTENSIONS {
            text.push_str(&format!("\n[[extensions]]\nextension = {extension:?}\n"));
            append_dependencies(&mut text, extension);
            append_manifest_files(&mut text, &extensions_root.join(extension), "[[extensions.files]]");
        }
    } else {
        panic!("unsupported extension artifact manifest schema {SCHEMA}");
    }
    fs::write(&manifest, text).expect("write Oliphaunt extension artifact manifest");
    println!("cargo::metadata=manifest={}", manifest.display());
}

fn append_dependencies(text: &mut String, extension: &str) {
    let dependencies = EXTENSION_DEPENDENCIES.iter()
        .find(|(candidate, _)| *candidate == extension)
        .map(|(_, dependencies)| *dependencies)
        .unwrap_or_else(|| panic!("missing dependency metadata for extension {extension}"));
    text.push_str(&format!("dependencies = {dependencies:?}\n"));
}

fn append_manifest_files(text: &mut String, root: &Path, table: &str) {
    let files = collect_files(root).expect("collect extension member payload files");
    if files.is_empty() {
        panic!("Oliphaunt extension member payload produced no files under {}", root.display());
    }
    for file in files {
        let relative = file.strip_prefix(root)
            .expect("payload file stays under member root")
            .to_string_lossy()
            .replace(std::path::MAIN_SEPARATOR, "/");
        let sha256 = sha256_file(&file).expect("hash extension payload file");
        text.push_str(&format!(
            "\n{table}\nsource = {:?}\nrelative = {:?}\nsha256 = {:?}\nexecutable = false\n",
            file.display().to_string(), relative, sha256,
        ));
    }
}

fn directory_names(root: &Path) -> io::Result<Vec<String>> {
    let mut names = Vec::new();
    for entry in fs::read_dir(root)? {
        let entry = entry?;
        if entry.file_type()?.is_dir() {
            names.push(entry.file_name().to_string_lossy().into_owned());
        }
    }
    names.sort();
    Ok(names)
}

fn part_roots() -> Vec<PathBuf> {
    PART_ROOTS.iter().map(PathBuf::from).collect()
}

fn copy_complete_files(source: &Path, destination: &Path) -> io::Result<()> {
    if !source.is_dir() {
        return Ok(());
    }
    for entry in fs::read_dir(source)? {
        let entry = entry?;
        let path = entry.path();
        let output = destination.join(path.strip_prefix(source).unwrap_or(&path));
        copy_tree_entry(&path, &output)?;
    }
    Ok(())
}

fn copy_tree_entry(source: &Path, destination: &Path) -> io::Result<()> {
    let metadata = fs::metadata(source)?;
    if metadata.is_dir() {
        fs::create_dir_all(destination)?;
        for entry in fs::read_dir(source)? {
            let entry = entry?;
            copy_tree_entry(&entry.path(), &destination.join(entry.file_name()))?;
        }
    } else if metadata.is_file() {
        if let Some(parent) = destination.parent() {
            fs::create_dir_all(parent)?;
        }
        fs::copy(source, destination)?;
    }
    Ok(())
}

fn collect_chunks(
    root: &Path,
    current: &Path,
    chunks: &mut BTreeMap<String, Vec<(usize, PathBuf)>>,
) -> io::Result<()> {
    if !current.is_dir() {
        return Ok(());
    }
    for entry in fs::read_dir(current)? {
        let entry = entry?;
        let path = entry.path();
        let metadata = fs::metadata(&path)?;
        if metadata.is_dir() {
            collect_chunks(root, &path, chunks)?;
            continue;
        }
        if !metadata.is_file() {
            continue;
        }
        let relative = path.strip_prefix(root).unwrap_or(&path).to_string_lossy().replace(std::path::MAIN_SEPARATOR, "/");
        let (file_relative, part_index) = split_part_relative(&relative)
            .unwrap_or_else(|| panic!("invalid Oliphaunt extension chunk file name {relative}"));
        chunks.entry(file_relative).or_default().push((part_index, path));
    }
    Ok(())
}

fn split_part_relative(relative: &str) -> Option<(String, usize)> {
    let (file, index) = relative.rsplit_once(".part")?;
    if file.is_empty() || index.len() != 3 || !index.bytes().all(|byte| byte.is_ascii_digit()) {
        return None;
    }
    Some((file.to_owned(), index.parse().ok()?))
}

fn collect_files(root: &Path) -> io::Result<Vec<PathBuf>> {
    let mut files = Vec::new();
    collect_files_inner(root, &mut files)?;
    files.sort();
    Ok(files)
}

fn collect_files_inner(path: &Path, files: &mut Vec<PathBuf>) -> io::Result<()> {
    if !path.is_dir() {
        return Ok(());
    }
    for entry in fs::read_dir(path)? {
        let entry = entry?;
        let entry_path = entry.path();
        let metadata = fs::metadata(&entry_path)?;
        if metadata.is_dir() {
            collect_files_inner(&entry_path, files)?;
        } else if metadata.is_file() {
            files.push(entry_path);
        }
    }
    Ok(())
}


fn sha256_file(path: &Path) -> io::Result<String> {
    let mut file = fs::File::open(path)?;
    let mut state = [
        0x6a09e667_u32,
        0xbb67ae85,
        0x3c6ef372,
        0xa54ff53a,
        0x510e527f,
        0x9b05688c,
        0x1f83d9ab,
        0x5be0cd19,
    ];
    let mut pending = [0_u8; 64];
    let mut pending_len = 0_usize;
    let mut total_len = 0_u64;
    let mut input = [0_u8; 64 * 1024];

    loop {
        let read = file.read(&mut input)?;
        if read == 0 {
            break;
        }
        total_len = total_len.wrapping_add(read as u64);
        let mut offset = 0_usize;
        if pending_len != 0 {
            let copied = (64 - pending_len).min(read);
            pending[pending_len..pending_len + copied].copy_from_slice(&input[..copied]);
            pending_len += copied;
            offset += copied;
            if pending_len == 64 {
                sha256_compress(&mut state, &pending);
                pending_len = 0;
            }
        }
        while offset + 64 <= read {
            sha256_compress(&mut state, &input[offset..offset + 64]);
            offset += 64;
        }
        if offset != read {
            pending[..read - offset].copy_from_slice(&input[offset..read]);
            pending_len = read - offset;
        }
    }

    pending[pending_len] = 0x80;
    pending_len += 1;
    if pending_len > 56 {
        pending[pending_len..].fill(0);
        sha256_compress(&mut state, &pending);
        pending.fill(0);
    } else {
        pending[pending_len..56].fill(0);
    }
    pending[56..].copy_from_slice(&total_len.wrapping_mul(8).to_be_bytes());
    sha256_compress(&mut state, &pending);

    let mut output = String::with_capacity(64);
    for word in state {
        use std::fmt::Write as _;
        write!(&mut output, "{word:08x}").expect("write SHA-256 hex digest");
    }
    Ok(output)
}

fn sha256_compress(state: &mut [u32; 8], block: &[u8]) {
    const K: [u32; 64] = [
        0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
        0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
        0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
        0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
        0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
        0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
        0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
        0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
        0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
        0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
        0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
        0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
        0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
        0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
        0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
        0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
    ];
    let mut schedule = [0_u32; 64];
    for (index, bytes) in block.chunks_exact(4).take(16).enumerate() {
        schedule[index] = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
    }
    for index in 16..64 {
        let s0 = schedule[index - 15].rotate_right(7)
            ^ schedule[index - 15].rotate_right(18)
            ^ (schedule[index - 15] >> 3);
        let s1 = schedule[index - 2].rotate_right(17)
            ^ schedule[index - 2].rotate_right(19)
            ^ (schedule[index - 2] >> 10);
        schedule[index] = schedule[index - 16]
            .wrapping_add(s0)
            .wrapping_add(schedule[index - 7])
            .wrapping_add(s1);
    }

    let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = *state;
    for index in 0..64 {
        let sum1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
        let choose = (e & f) ^ ((!e) & g);
        let temporary1 = h
            .wrapping_add(sum1)
            .wrapping_add(choose)
            .wrapping_add(K[index])
            .wrapping_add(schedule[index]);
        let sum0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
        let majority = (a & b) ^ (a & c) ^ (b & c);
        let temporary2 = sum0.wrapping_add(majority);
        h = g;
        g = f;
        f = e;
        e = d.wrapping_add(temporary1);
        d = c;
        c = b;
        b = a;
        a = temporary1.wrapping_add(temporary2);
    }
    state[0] = state[0].wrapping_add(a);
    state[1] = state[1].wrapping_add(b);
    state[2] = state[2].wrapping_add(c);
    state[3] = state[3].wrapping_add(d);
    state[4] = state[4].wrapping_add(e);
    state[5] = state[5].wrapping_add(f);
    state[6] = state[6].wrapping_add(g);
    state[7] = state[7].wrapping_add(h);
}