Add NAR parser
This commit is contained in:
parent
6317f0f7a0
commit
7f08975050
6 changed files with 160 additions and 3 deletions
1
nix-rust/Cargo.lock
generated
1
nix-rust/Cargo.lock
generated
|
@ -666,6 +666,7 @@ name = "nix-rust"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"assert_matches 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"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)",
|
"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)",
|
"http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|
|
@ -17,6 +17,7 @@ reqwest = { version = "0.9", default-features = false, features = ["rustls-tls"]
|
||||||
http = "0.1"
|
http = "0.1"
|
||||||
tokio = { version = "0.1", default-features = false }
|
tokio = { version = "0.1", default-features = false }
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
|
byteorder = "1.3"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hex = "0.3"
|
hex = "0.3"
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
use super::{foreign::{self, CBox}, error, util, store};
|
use super::{
|
||||||
|
error,
|
||||||
|
foreign::{self, CBox},
|
||||||
|
util,
|
||||||
|
};
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn unpack_tarfile(
|
pub extern "C" fn unpack_tarfile(
|
||||||
|
@ -10,7 +14,8 @@ pub extern "C" fn unpack_tarfile(
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn rust_test() {
|
pub extern "C" fn rust_test() {
|
||||||
use crate::store::Store;
|
/*
|
||||||
|
use crate::store::{self, Store};
|
||||||
use futures::future::{FutureExt, TryFutureExt};
|
use futures::future::{FutureExt, TryFutureExt};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
@ -39,4 +44,9 @@ pub extern "C" fn rust_test() {
|
||||||
};
|
};
|
||||||
|
|
||||||
tokio::run(fut.boxed().compat());
|
tokio::run(fut.boxed().compat());
|
||||||
|
*/
|
||||||
|
|
||||||
|
let file = std::fs::File::open("test.nar").unwrap();
|
||||||
|
|
||||||
|
crate::nar::parse(&mut std::io::BufReader::new(file)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,15 @@ pub enum Error {
|
||||||
BadBase32,
|
BadBase32,
|
||||||
StorePathNameTooLong,
|
StorePathNameTooLong,
|
||||||
BadStorePathName,
|
BadStorePathName,
|
||||||
|
NarSizeFieldTooBig,
|
||||||
|
BadNarString,
|
||||||
|
BadNarPadding,
|
||||||
|
BadNarVersionMagic,
|
||||||
|
MissingNarOpenTag,
|
||||||
|
MissingNarCloseTag,
|
||||||
|
MissingNarField,
|
||||||
|
BadNarField(String),
|
||||||
|
BadExecutableField,
|
||||||
IOError(std::io::Error),
|
IOError(std::io::Error),
|
||||||
HttpError(reqwest::Error),
|
HttpError(reqwest::Error),
|
||||||
Misc(String),
|
Misc(String),
|
||||||
|
@ -37,6 +46,15 @@ impl fmt::Display for Error {
|
||||||
write!(f, "store path name is longer than 211 characters")
|
write!(f, "store path name is longer than 211 characters")
|
||||||
}
|
}
|
||||||
Error::BadStorePathName => write!(f, "store path name contains forbidden character"),
|
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::IOError(err) => write!(f, "I/O error: {}", err),
|
||||||
Error::HttpError(err) => write!(f, "HTTP error: {}", err),
|
Error::HttpError(err) => write!(f, "HTTP error: {}", err),
|
||||||
Error::Foreign(_) => write!(f, "<C++ exception>"), // FIXME
|
Error::Foreign(_) => write!(f, "<C++ exception>"), // FIXME
|
||||||
|
|
|
@ -14,6 +14,7 @@ extern crate proptest;
|
||||||
mod c;
|
mod c;
|
||||||
mod error;
|
mod error;
|
||||||
mod foreign;
|
mod foreign;
|
||||||
|
mod nar;
|
||||||
mod store;
|
mod store;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
|
|
126
nix-rust/src/nar.rs
Normal file
126
nix-rust/src/nar.rs
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
use crate::Error;
|
||||||
|
use byteorder::{LittleEndian, ReadBytesExt};
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
pub fn parse<R: Read>(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<R: Read>(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::<u8>::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<R: Read>(input: &mut R) -> Result<Self, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deserialize for String {
|
||||||
|
fn read<R: Read>(input: &mut R) -> Result<Self, Error> {
|
||||||
|
let buf = Deserialize::read(input)?;
|
||||||
|
Ok(String::from_utf8(buf).map_err(|_| Error::BadNarString)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deserialize for Vec<u8> {
|
||||||
|
fn read<R: Read>(input: &mut R) -> Result<Self, Error> {
|
||||||
|
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<R: Read>(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<R: Read>(input: &mut R) -> Result<Self, Error> {
|
||||||
|
Ok(input.read_u64::<LittleEndian>()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deserialize for usize {
|
||||||
|
fn read<R: Read>(input: &mut R) -> Result<Self, Error> {
|
||||||
|
let n: u64 = Deserialize::read(input)?;
|
||||||
|
Ok(usize::try_from(n).map_err(|_| Error::NarSizeFieldTooBig)?)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue