Skip to content

Commit

Permalink
rust: Add wrapping for composefs-info dump
Browse files Browse the repository at this point in the history
This allows us to deserialize from a composefs file.

Signed-off-by: Colin Walters <[email protected]>
  • Loading branch information
cgwalters committed Aug 23, 2024
1 parent 0cde542 commit 6a82cc3
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 13 deletions.
1 change: 1 addition & 0 deletions rust/composefs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ libc = "0.2"
composefs-sys = { version = "0.1.0", path = "../composefs-sys" }

[dev-dependencies]
similar-asserts = "1.5.0"
tar = "0.4.38"
tempfile = "3.2.0"
102 changes: 102 additions & 0 deletions rust/composefs/src/dumpfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ use std::ffi::OsStr;
use std::ffi::OsString;
use std::fmt::Display;
use std::fmt::Write as WriteFmt;
use std::fs::File;
use std::io::BufRead;
use std::io::Write;
use std::os::unix::ffi::{OsStrExt, OsStringExt};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::str::FromStr;

use anyhow::Context;
Expand Down Expand Up @@ -478,10 +481,69 @@ impl<'p> Display for Entry<'p> {
}
}

/// Configuration for parsing a dumpfile
#[derive(Debug, Default)]
pub struct DumpConfig<'a> {
/// Only dump these toplevel filenames
pub filters: Option<&'a [&'a str]>,
}

/// Parse the provided composefs into dumpfile entries.
pub fn dump<F>(input: File, config: DumpConfig, mut handler: F) -> Result<()>
where
F: FnMut(Entry<'_>) -> Result<()> + Send,
{
let mut proc = Command::new("composefs-info");
proc.arg("dump");
if let Some(filter) = config.filters {
proc.args(filter.iter().flat_map(|f| ["--filter", f]));
}
proc.args(["/dev/stdin"])
.stdin(std::process::Stdio::from(input))
.stderr(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped());
let mut proc = proc.spawn().context("Spawning composefs-info")?;

// SAFETY: we set up these streams
let child_stdout = proc.stdout.take().unwrap();
let child_stderr = proc.stderr.take().unwrap();

std::thread::scope(|s| {
let stderr_copier = s.spawn(move || {
let mut child_stderr = std::io::BufReader::new(child_stderr);
let mut buf = Vec::new();
std::io::copy(&mut child_stderr, &mut buf)?;
anyhow::Ok(buf)
});

let child_stdout = std::io::BufReader::new(child_stdout);
for line in child_stdout.lines() {
let line = line.context("Reading dump stdout")?;
let entry = Entry::parse(&line)?.filter_special();
handler(entry)?;
}

let r = proc.wait()?;
let stderr = stderr_copier.join().unwrap()?;
if !r.success() {
let stderr = String::from_utf8_lossy(&stderr);
let stderr = stderr.trim();
anyhow::bail!("composefs-info dump failed: {r}: {stderr}")
}

Ok(())
})
}

#[cfg(test)]
mod tests {
use std::fs::File;

use crate::mkcomposefs::{mkcomposefs_from_buf, Config};

use super::*;

const SPECIAL_DUMP: &str = include_str!("../../../tests/assets/special.dump");
const SPECIALS: &[&str] = &["", "foo=bar=baz", r"\x01\x02", "-"];
const UNQUOTED: &[&str] = &["foo!bar", "hello-world", "--"];

Expand Down Expand Up @@ -621,4 +683,44 @@ mod tests {
);
}
}

#[test]
fn test_load_cfs() -> Result<()> {
let td = tempfile::tempdir()?;
let out_cfs = td.path().join("out.cfs");
let o = File::create_new(&out_cfs)?;
mkcomposefs_from_buf(Config::default(), SPECIAL_DUMP, o).unwrap();
let mut entries = String::new();
let input = File::open(&out_cfs)?;
dump(input, DumpConfig::default(), |e| {
writeln!(entries, "{e}")?;
Ok(())
})
.unwrap();
similar_asserts::assert_eq!(SPECIAL_DUMP, &entries);
Ok(())
}

#[test]
fn test_load_cfs_filtered() -> Result<()> {
const FILTERED: &str =
"/ 4096 40555 2 0 0 0 1633950376.0 - - - trusted.foo1=bar-1 user.foo2=bar-2\n\
/blockdev 0 60777 1 0 0 107690 1633950376.0 - - - trusted.bar=bar-2\n\
/inline 15 100777 1 0 0 0 1633950376.0 - FOOBAR\\nINAFILE\\n - user.foo=bar-2\n";
let td = tempfile::tempdir()?;
let out_cfs = td.path().join("out.cfs");
let o = File::create_new(&out_cfs)?;
mkcomposefs_from_buf(Config::default(), SPECIAL_DUMP, o).unwrap();
let mut entries = String::new();
let input = File::open(&out_cfs)?;
let mut filter = DumpConfig::default();
filter.filters = Some(&["blockdev", "inline"]);
dump(input, filter, |e| {
writeln!(entries, "{e}")?;
Ok(())
})
.unwrap();
similar_asserts::assert_eq!(FILTERED, &entries);
Ok(())
}
}
32 changes: 19 additions & 13 deletions rust/composefs/src/mkcomposefs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,29 +75,35 @@ pub fn mkcomposefs(
})
}

#[test]
fn test_mkcomposefs() -> Result<()> {
use super::dumpfile::Entry;
use std::fmt::Write as _;
let td = tempfile::tempdir()?;
let td = td.path();
let outpath = &td.join("out");
let o = File::create(outpath)?;
// Generate a composefs from an input string, which must
// be a textual composefs dump file.
#[cfg(test)]
pub(crate) fn mkcomposefs_from_buf(config: Config, buf: &str, output: File) -> Result<()> {
let (send, recv) = mpsc::sync_channel(5);
const CONTENT: &str = include_str!("../../../tests/assets/special.dump");
std::thread::scope(|s| {
let producer = s.spawn(move || {
for line in CONTENT.lines() {
if send.send(Entry::parse(line)?).is_err() {
for line in buf.lines() {
if send.send(crate::dumpfile::Entry::parse(line)?).is_err() {
break;
}
}
anyhow::Ok(())
});
mkcomposefs(Config::default(), recv, o)?;
mkcomposefs(config, recv, output)?;
producer.join().unwrap()?;
anyhow::Ok(())
})?;
})
}

#[test]
fn test_mkcomposefs() -> Result<()> {
use std::fmt::Write as _;
let td = tempfile::tempdir()?;
let td = td.path();
let outpath = &td.join("out");
let o = File::create(outpath)?;
const CONTENT: &str = include_str!("../../../tests/assets/special.dump");
mkcomposefs_from_buf(Config::default(), CONTENT, o)?;
let mut reparsed_content = String::new();
let o = File::open(outpath)?;
super::dump(o, |entry| {
Expand Down

0 comments on commit 6a82cc3

Please sign in to comment.