@ 261027.7 · fetching
current
//! Host-only packer. The input is read-only, and its sibling CLI is never
//! opened.

use std::{
    env, fs,
    io::{self, Cursor, Read, Seek, SeekFrom, Write},
    path::PathBuf,
};

/// Verifies a decoded payload without retaining a second raw addon image.
/// `Payload::decode_into` also seeks to validate the native file kind, so the
/// original raw bytes serve as the readable backing store after each compare.
struct RawVerifier<'a> {
    raw: &'a [u8],
    cursor: Cursor<&'a [u8]>,
    written: usize,
}

impl<'a> RawVerifier<'a> {
    fn new(raw: &'a [u8]) -> Self {
        Self {
            raw,
            cursor: Cursor::new(raw),
            written: 0,
        }
    }
}

impl Write for RawVerifier<'_> {
    fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
        let end = self.written.checked_add(buffer.len()).ok_or_else(|| {
            io::Error::new(io::ErrorKind::InvalidData, "decoded addon length overflow")
        })?;
        if self.raw.get(self.written..end) != Some(buffer) {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "decoded addon differs from packer input",
            ));
        }
        self.written = end;
        Ok(buffer.len())
    }

    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}

impl Read for RawVerifier<'_> {
    fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
        self.cursor.read(buffer)
    }
}

impl Seek for RawVerifier<'_> {
    fn seek(&mut self, position: SeekFrom) -> io::Result<u64> {
        self.cursor.seek(position)
    }
}

use swc_native_addon::{
    format::{pack, NativeTarget, Payload, MAX_SIZE},
    platform,
};

fn run() -> Result<(), Box<dyn std::error::Error>> {
    let mut args = env::args_os().skip(1);
    let mut input = None;
    let mut output = None;
    let mut target = None;
    while let Some(arg) = args.next() {
        if arg == "--input" && input.is_none() {
            input = args.next().map(PathBuf::from);
        } else if arg == "--output" && output.is_none() {
            output = args.next().map(PathBuf::from);
        } else if arg == "--target" && target.is_none() {
            target = args.next().and_then(|value| value.into_string().ok());
        } else {
            return Err(
                "usage: swc-native-addon-pack --input <final-stripped.node> --output \
                 <payload.swcn> --target <supported-target>"
                    .into(),
            );
        }
    }
    let input = input.ok_or("--input is required")?;
    let output = output.ok_or("--output is required")?;
    let target = NativeTarget::parse(&target.ok_or("--target is required")?)?;
    let input = fs::canonicalize(input)?;
    let parent = output
        .parent()
        .filter(|p| !p.as_os_str().is_empty())
        .unwrap_or_else(|| std::path::Path::new("."));
    let parent = fs::canonicalize(parent)?;
    let output = parent.join(output.file_name().ok_or("output must name a file")?);
    if fs::canonicalize(&output).ok().as_ref() == Some(&input) {
        return Err("output must not overwrite the raw addon".into());
    }
    // Requiring a distinct payload suffix also prevents accidentally replacing
    // a sibling `swc` executable or treating the payload as a loadable carrier.
    if output.extension().and_then(|s| s.to_str()) != Some("swcn") {
        return Err("output must have the .swcn payload extension".into());
    }
    if fs::metadata(&input)?.len() > MAX_SIZE {
        return Err("raw addon exceeds 2 GiB".into());
    }
    let raw = fs::read(&input)?;
    let bytes = pack(&raw, target)?;
    let payload = Payload::parse(&bytes)?;
    let mut decoded = RawVerifier::new(&raw);
    payload.decode_into(&mut decoded)?;
    if decoded.written != raw.len() {
        return Err("packer verification did not reproduce the raw addon".into());
    }
    let mut staged = tempfile::Builder::new()
        .prefix(".swc-payload-")
        .tempfile_in(&parent)?;
    staged.write_all(&bytes)?;
    staged.as_file().sync_all()?;
    let staged = staged.into_temp_path();
    platform::replace_file(&staged, &output)?;
    platform::sync_directory(&parent)?;
    println!(
        "raw={} payload={} sha512={}",
        raw.len(),
        bytes.len(),
        payload.header.cache_key()
    );
    Ok(())
}

fn main() {
    if let Err(error) = run() {
        eprintln!("swc-native-addon-pack: {error}");
        std::process::exit(1);
    }
}