From 759f15390c45139740b77141431d36c6d5cd3d1b Mon Sep 17 00:00:00 2001 From: Florian Klink Date: Sun, 4 May 2025 02:37:37 +0300 Subject: [PATCH] feat(nix-compat/nar): add copy functions This allows piping NAR data through a reader, and writing it back out to a writer. It can be used to validate a NAR to be syntactically correct, or to read exactly to the end of a NAR file if the size is not given externally. Change-Id: I0fc8d58e68783400d1cfee75c860138915974f3d Reviewed-on: https://cl.snix.dev/c/snix/+/30423 Tested-by: besadii Reviewed-by: edef Autosubmit: Florian Klink --- snix/nix-compat/src/nar/copy.rs | 58 +++++++++++++++++++++++++ snix/nix-compat/src/nar/copy_async.rs | 61 +++++++++++++++++++++++++++ snix/nix-compat/src/nar/mod.rs | 8 ++++ 3 files changed, 127 insertions(+) create mode 100644 snix/nix-compat/src/nar/copy.rs create mode 100644 snix/nix-compat/src/nar/copy_async.rs diff --git a/snix/nix-compat/src/nar/copy.rs b/snix/nix-compat/src/nar/copy.rs new file mode 100644 index 000000000..5e991736d --- /dev/null +++ b/snix/nix-compat/src/nar/copy.rs @@ -0,0 +1,58 @@ +use std::io; + +use super::reader; +use super::reader::Reader; +use super::writer; + +/// Reads through the entire NAR, and writes it back to a writer. +/// This verifies its syntactical correctness. +pub fn copy(r: &mut Reader<'_>, w: &mut W) -> io::Result<()> +where + W: std::io::Write, +{ + let node_r = reader::open(r)?; + let node_w = writer::open(w)?; + + copy_node(node_r, node_w) +} + +fn copy_node(node_r: reader::Node<'_, '_>, node_w: writer::Node<'_, W>) -> io::Result<()> +where + W: std::io::Write, +{ + match node_r { + reader::Node::Symlink { target } => node_w.symlink(&target)?, + reader::Node::File { executable, reader } => node_w.file( + executable, + reader.len(), + &mut std::io::BufReader::new(reader), + )?, + reader::Node::Directory(mut dir_reader) => { + let mut directory_w = node_w.directory()?; + while let Some(entry) = dir_reader.next()? { + let node_w = directory_w.entry(entry.name)?; + copy_node(entry.node, node_w)?; + } + + directory_w.close()?; + } + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use rstest::rstest; + use std::path::PathBuf; + + #[rstest] + fn roundtrip(#[files("src/nar/tests/*.nar")] path: PathBuf) { + let nar_src = std::fs::read(path).expect("must succeed"); + + let mut out_buf = Vec::new(); + + assert!(super::copy(&mut std::io::Cursor::new(&nar_src), &mut out_buf).is_ok()); + assert_eq!(nar_src, out_buf, "must roundtrip"); + } +} diff --git a/snix/nix-compat/src/nar/copy_async.rs b/snix/nix-compat/src/nar/copy_async.rs new file mode 100644 index 000000000..191b9e463 --- /dev/null +++ b/snix/nix-compat/src/nar/copy_async.rs @@ -0,0 +1,61 @@ +use std::io; + +use tokio::io::{AsyncBufRead, AsyncWrite}; + +use super::reader::r#async as reader; +use super::writer::r#async as writer; + +/// Reads through the entire NAR, and writes it back to a writer. +/// This verifies its syntactical correctness. +pub async fn copy(mut r: R, mut w: W) -> io::Result<()> +where + R: AsyncBufRead + Send + Unpin, + W: AsyncWrite + Send + Unpin, +{ + let node_r = reader::open(&mut r).await?; + let node_w = writer::open(&mut w).await?; + + copy_node(node_r, node_w).await +} + +async fn copy_node(node_r: reader::Node<'_, '_>, node_w: writer::Node<'_, '_>) -> io::Result<()> { + match node_r { + reader::Node::Symlink { target } => node_w.symlink(&target).await?, + reader::Node::File { + executable, + mut reader, + } => node_w.file(executable, reader.len(), &mut reader).await?, + reader::Node::Directory(mut dir_reader) => { + let mut directory_w = node_w.directory().await?; + while let Some(entry) = dir_reader.next().await? { + let node_w = directory_w.entry(entry.name).await?; + Box::pin(copy_node(entry.node, node_w)).await?; + } + + directory_w.close().await?; + } + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use rstest::rstest; + use std::path::PathBuf; + + #[rstest] + #[tokio::test] + async fn roundtrip(#[files("src/nar/tests/*.nar")] path: PathBuf) { + let nar_src = std::fs::read(path).expect("must succeed"); + + let mut out_buf = Vec::new(); + + assert!( + super::copy(&mut std::io::Cursor::new(&nar_src), &mut out_buf) + .await + .is_ok() + ); + assert_eq!(nar_src, out_buf, "must roundtrip"); + } +} diff --git a/snix/nix-compat/src/nar/mod.rs b/snix/nix-compat/src/nar/mod.rs index d0e8ee8a4..344c446f7 100644 --- a/snix/nix-compat/src/nar/mod.rs +++ b/snix/nix-compat/src/nar/mod.rs @@ -1,5 +1,13 @@ pub(crate) mod wire; +mod copy; pub mod listing; pub mod reader; pub mod writer; + +#[cfg(all(feature = "async", feature = "wire"))] +mod copy_async; + +pub use copy::copy; +#[cfg(all(feature = "async", feature = "wire"))] +pub use copy_async::copy as copy_async;