diff --git a/nix-rust/Cargo.lock b/nix-rust/Cargo.lock index 81074d60b..e4b61c444 100644 --- a/nix-rust/Cargo.lock +++ b/nix-rust/Cargo.lock @@ -666,6 +666,7 @@ name = "nix-rust" version = "0.1.0" dependencies = [ "assert_matches 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "futures-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/nix-rust/Cargo.toml b/nix-rust/Cargo.toml index 3cfd2a48b..20d1fdaa8 100644 --- a/nix-rust/Cargo.toml +++ b/nix-rust/Cargo.toml @@ -17,8 +17,9 @@ reqwest = { version = "0.9", default-features = false, features = ["rustls-tls"] http = "0.1" tokio = { version = "0.1", default-features = false } lazy_static = "1.4" +byteorder = "1.3" [dev-dependencies] hex = "0.3" assert_matches = "1.3" -proptest = "0.9" \ No newline at end of file +proptest = "0.9" diff --git a/nix-rust/src/c.rs b/nix-rust/src/c.rs index 1abd34198..542df93d2 100644 --- a/nix-rust/src/c.rs +++ b/nix-rust/src/c.rs @@ -1,4 +1,8 @@ -use super::{foreign::{self, CBox}, error, util, store}; +use super::{ + error, + foreign::{self, CBox}, + util, +}; #[no_mangle] pub extern "C" fn unpack_tarfile( @@ -10,7 +14,8 @@ pub extern "C" fn unpack_tarfile( #[no_mangle] pub extern "C" fn rust_test() { - use crate::store::Store; + /* + use crate::store::{self, Store}; use futures::future::{FutureExt, TryFutureExt}; use std::path::Path; @@ -39,4 +44,9 @@ pub extern "C" fn rust_test() { }; tokio::run(fut.boxed().compat()); + */ + + let file = std::fs::File::open("test.nar").unwrap(); + + crate::nar::parse(&mut std::io::BufReader::new(file)).unwrap(); } diff --git a/nix-rust/src/error.rs b/nix-rust/src/error.rs index 5717a7a47..4defc646d 100644 --- a/nix-rust/src/error.rs +++ b/nix-rust/src/error.rs @@ -8,6 +8,15 @@ pub enum Error { BadBase32, StorePathNameTooLong, BadStorePathName, + NarSizeFieldTooBig, + BadNarString, + BadNarPadding, + BadNarVersionMagic, + MissingNarOpenTag, + MissingNarCloseTag, + MissingNarField, + BadNarField(String), + BadExecutableField, IOError(std::io::Error), HttpError(reqwest::Error), Misc(String), @@ -37,6 +46,15 @@ impl fmt::Display for Error { write!(f, "store path name is longer than 211 characters") } Error::BadStorePathName => write!(f, "store path name contains forbidden character"), + Error::NarSizeFieldTooBig => write!(f, "size field in NAR is too big"), + Error::BadNarString => write!(f, "NAR string is not valid UTF-8"), + Error::BadNarPadding => write!(f, "NAR padding is not zero"), + Error::BadNarVersionMagic => write!(f, "unsupported NAR version"), + Error::MissingNarOpenTag => write!(f, "NAR open tag is missing"), + Error::MissingNarCloseTag => write!(f, "NAR close tag is missing"), + Error::MissingNarField => write!(f, "expected NAR field is missing"), + Error::BadNarField(s) => write!(f, "unrecognized NAR field '{}'", s), + Error::BadExecutableField => write!(f, "bad 'executable' field in NAR"), Error::IOError(err) => write!(f, "I/O error: {}", err), Error::HttpError(err) => write!(f, "HTTP error: {}", err), Error::Foreign(_) => write!(f, ""), // FIXME diff --git a/nix-rust/src/lib.rs b/nix-rust/src/lib.rs index 635c00aff..cda63d2c7 100644 --- a/nix-rust/src/lib.rs +++ b/nix-rust/src/lib.rs @@ -14,6 +14,7 @@ extern crate proptest; mod c; mod error; mod foreign; +mod nar; mod store; mod util; diff --git a/nix-rust/src/nar.rs b/nix-rust/src/nar.rs new file mode 100644 index 000000000..aa05d815b --- /dev/null +++ b/nix-rust/src/nar.rs @@ -0,0 +1,126 @@ +use crate::Error; +use byteorder::{LittleEndian, ReadBytesExt}; +use std::convert::TryFrom; +use std::io::Read; + +pub fn parse(input: &mut R) -> Result<(), Error> { + if String::read(input)? != NAR_VERSION_MAGIC { + return Err(Error::BadNarVersionMagic); + } + + parse_file(input) +} + +const NAR_VERSION_MAGIC: &str = "nix-archive-1"; + +fn parse_file(input: &mut R) -> Result<(), Error> { + if String::read(input)? != "(" { + return Err(Error::MissingNarOpenTag); + } + + if String::read(input)? != "type" { + return Err(Error::MissingNarField); + } + + match String::read(input)?.as_ref() { + "regular" => { + let mut executable = false; + let mut tag = String::read(input)?; + if tag == "executable" { + executable = true; + if String::read(input)? != "" { + return Err(Error::BadExecutableField); + } + tag = String::read(input)?; + } + if tag != "contents" { + return Err(Error::MissingNarField); + } + let contents = Vec::::read(input)?; + if String::read(input)? != ")" { + return Err(Error::MissingNarCloseTag); + } + } + "directory" => loop { + match String::read(input)?.as_ref() { + "entry" => { + if String::read(input)? != "(" { + return Err(Error::MissingNarOpenTag); + } + if String::read(input)? != "name" { + return Err(Error::MissingNarField); + } + let name = String::read(input)?; + if String::read(input)? != "node" { + return Err(Error::MissingNarField); + } + parse_file(input)?; + let tag = String::read(input)?; + if tag != ")" { + return Err(Error::MissingNarCloseTag); + } + } + ")" => break, + s => return Err(Error::BadNarField(s.into())), + } + }, + "symlink" => { + if String::read(input)? != "target" { + return Err(Error::MissingNarField); + } + let target = String::read(input)?; + if String::read(input)? != ")" { + return Err(Error::MissingNarCloseTag); + } + } + s => return Err(Error::BadNarField(s.into())), + } + + Ok(()) +} + +trait Deserialize: Sized { + fn read(input: &mut R) -> Result; +} + +impl Deserialize for String { + fn read(input: &mut R) -> Result { + let buf = Deserialize::read(input)?; + Ok(String::from_utf8(buf).map_err(|_| Error::BadNarString)?) + } +} + +impl Deserialize for Vec { + fn read(input: &mut R) -> Result { + let n: usize = Deserialize::read(input)?; + let mut buf = vec![0; n]; + input.read_exact(&mut buf)?; + skip_padding(input, n)?; + Ok(buf) + } +} + +fn skip_padding(input: &mut R, len: usize) -> Result<(), Error> { + if len % 8 != 0 { + let mut buf = [0; 8]; + let buf = &mut buf[0..8 - (len % 8)]; + input.read_exact(buf)?; + if !buf.iter().all(|b| *b == 0) { + return Err(Error::BadNarPadding); + } + } + Ok(()) +} + +impl Deserialize for u64 { + fn read(input: &mut R) -> Result { + Ok(input.read_u64::()?) + } +} + +impl Deserialize for usize { + fn read(input: &mut R) -> Result { + let n: u64 = Deserialize::read(input)?; + Ok(usize::try_from(n).map_err(|_| Error::NarSizeFieldTooBig)?) + } +}