diff --git a/src/bin/cfsctl.rs b/src/bin/cfsctl.rs index 5da56bf..49f4086 100644 --- a/src/bin/cfsctl.rs +++ b/src/bin/cfsctl.rs @@ -1,3 +1,4 @@ +use std::path::PathBuf; use anyhow::Result; use clap::{Parser, Subcommand}; @@ -16,7 +17,7 @@ use composefs_experiments::{ #[clap(name = "cfsctl", version)] pub struct App { #[clap(long, group="repopath")] - repo: Option, + repo: Option, #[clap(long, group="repopath")] user: bool, #[clap(long, group="repopath")] diff --git a/src/bin/mount.rs b/src/bin/mount.rs index bd9a0e6..eb09e2e 100644 --- a/src/bin/mount.rs +++ b/src/bin/mount.rs @@ -1,3 +1,4 @@ +use std::path::PathBuf; use clap::Parser; use composefs_experiments::mount::MountOptions; @@ -13,7 +14,7 @@ struct Args { mountpoint: String, #[arg(short, long)] - basedir: String, + basedir: PathBuf, #[arg(short, long)] digest: Option, diff --git a/src/mount.rs b/src/mount.rs index bc35ef3..45d5993 100644 --- a/src/mount.rs +++ b/src/mount.rs @@ -1,8 +1,11 @@ -use std::os::fd::{ - OwnedFd, - BorrowedFd, - AsFd, - AsRawFd +use std::{ + path::Path, + os::fd::{ + OwnedFd, + BorrowedFd, + AsFd, + AsRawFd, + }, }; use anyhow::Result; @@ -78,7 +81,7 @@ fn proc_self_fd(fd: A) -> String { format!("/proc/self/fd/{}", fd.as_fd().as_raw_fd()) } -pub fn mount_fd(image: F, basedir: &str, mountpoint: &str) -> Result<()> { +pub fn mount_fd(image: F, basedir: &Path, mountpoint: &str) -> Result<()> { let erofs = FsHandle::open("erofs")?; fsconfig_set_string(erofs.as_fd(), "source", proc_self_fd(&image))?; fsconfig_create(erofs.as_fd())?; @@ -101,13 +104,13 @@ pub fn mount_fd(image: F, basedir: &str, mountpoint: &str) -> Result<() pub struct MountOptions<'a> { image: &'a str, - basedir: &'a str, + basedir: &'a Path, digest: Option<&'a str>, verity: bool, } impl<'a> MountOptions<'a> { - pub fn new(image: &'a str, basedir: &'a str) -> MountOptions<'a> { + pub fn new(image: &'a str, basedir: &'a Path) -> MountOptions<'a> { MountOptions { image, basedir, digest: None, verity: false } } diff --git a/src/oci/tar.rs b/src/oci/tar.rs index c4dadf5..e3cb95d 100644 --- a/src/oci/tar.rs +++ b/src/oci/tar.rs @@ -117,24 +117,13 @@ fn symlink_target_from_tar(pax: Option>, gnu: Vec, short: &[u8]) -> } } -fn get_entry(reader: &mut SplitStreamReader) -> Result>> { +pub fn get_entry(reader: &mut SplitStreamReader) -> Result>> { let mut gnu_longlink: Vec = vec![]; let mut gnu_longname: Vec = vec![]; let mut pax_longlink: Option> = None; let mut pax_longname: Option> = None; let mut xattrs = vec![]; - // no root entry in the tar - println!("{}", Entry { - path: Cow::Borrowed(Path::new("/")), - uid: 0, - gid: 0, - mode: FileType::Directory.as_raw_mode() | 0o755, - mtime: Mtime { sec: 0, nsec: 0 }, - item: Item::Directory { size: 0, nlink: 1 }, - xattrs: vec![] - }); - loop { let mut buf = [0u8; 512]; if !reader.read_inline_exact(&mut buf)? || buf == [0u8; 512] { @@ -250,6 +239,17 @@ fn get_entry(reader: &mut SplitStreamReader) -> Result(split_stream: &mut R) -> Result<()> { + // no root entry in the tar + println!("{}", Entry { + path: Cow::Borrowed(Path::new("/")), + uid: 0, + gid: 0, + mode: FileType::Directory.as_raw_mode() | 0o755, + mtime: Mtime { sec: 0, nsec: 0 }, + item: Item::Directory { size: 0, nlink: 1 }, + xattrs: vec![] + }); + let mut reader = SplitStreamReader::new(split_stream); while let Some(entry) = get_entry(&mut reader)? { println!("{}", entry); diff --git a/src/repository.rs b/src/repository.rs index ebcc98c..c5d232e 100644 --- a/src/repository.rs +++ b/src/repository.rs @@ -62,7 +62,7 @@ use crate::{ pub struct Repository { repository: OwnedFd, - path: String, + path: PathBuf, } impl Drop for Repository { @@ -73,13 +73,13 @@ impl Drop for Repository { } impl Repository { - pub fn open_path(path: String) -> Result { + pub fn open_path(path: PathBuf) -> Result { // O_PATH isn't enough because flock() let repository = open(&path, OFlags::RDONLY, Mode::empty()) - .with_context(|| format!("Cannot open composefs repository '{path}'"))?; + .with_context(|| format!("Cannot open composefs repository {path:?}"))?; flock(&repository, FlockOperation::LockShared). - with_context(|| format!("Cannot lock repository '{path}'"))?; + with_context(|| format!("Cannot lock repository {path:?}"))?; Ok(Repository { repository, path }) } @@ -88,11 +88,11 @@ impl Repository { let home = std::env::var("HOME") .with_context(|| "$HOME must be set when in user mode")?; - Repository::open_path(format!("{}/.var/lib/composefs", home)) + Repository::open_path(PathBuf::from(home).join(".var/lib/composefs")) } pub fn open_system() -> Result { - Repository::open_path("/sysroot/composefs".to_string()) + Repository::open_path(PathBuf::from("/sysroot/composefs".to_string())) } fn ensure_parent>(&self, path: P) -> Result<()> { @@ -177,10 +177,10 @@ impl Repository { let mut hash = Sha256HashValue::EMPTY; let linkbytes = linkpath.to_bytes(); if linkbytes.len() != 67 || &linkbytes[0..3] != b"../" { - bail!("Incorrectly formatted symlink {}/{}", self.path, filename); + bail!("Incorrectly formatted symlink {:?}/{:?}", self.path, filename); } hex::decode_to_slice(&linkpath.to_bytes()[3..], &mut hash). - with_context(|| format!("Incorrectly formatted symlink {}/{}", self.path, filename))?; + with_context(|| format!("Incorrectly formatted symlink {:?}/{:?}", self.path, filename))?; Ok(Some(hash)) } } @@ -294,7 +294,7 @@ impl Repository { pub fn mount(self, name: &str, mountpoint: &str) -> Result<()> { let image = self.open_in_category("images", name)?; - let object_path = format!("{}/objects", self.path); + let object_path = self.path.join("objects"); mount_fd(image, &object_path, mountpoint) } diff --git a/tests/repo.rs b/tests/repo.rs index fb34bcb..1fce7ef 100644 --- a/tests/repo.rs +++ b/tests/repo.rs @@ -1,3 +1,69 @@ +use std::{ + fs::create_dir_all, + path::PathBuf, + fmt::Write, +}; + +use anyhow::{ + Context, + Result, +}; + +use composefs_experiments::{ + oci, + repository::Repository, + splitstream::SplitStreamReader, +}; + +fn append_data(builder: &mut tar::Builder>, name: &str, size: usize) -> Result<()> { + let mut header = tar::Header::new_ustar(); + header.set_uid(0); + header.set_gid(0); + header.set_mode(0o700); + header.set_entry_type(tar::EntryType::Regular); + header.set_size(size as u64); + Ok(builder.append_data(&mut header, name, vec![0u8; size].as_slice())?) +} + +fn example_layer() -> Result> { + let mut builder = tar::Builder::new(vec![]); + append_data(&mut builder, "file0", 0)?; + append_data(&mut builder, "file4095", 4095)?; + append_data(&mut builder, "file4096", 4096)?; + append_data(&mut builder, "file4097", 4097)?; + Ok(builder.into_inner()?) +} + +fn home_var_tmp() -> Result { + // We can't use /tmp because that's usually a tmpfs (no fsverity) + // We also can't use /var/tmp because it's an overlayfs in toolbox (no fsverity) + // So let's try something in the user's homedir? + let home = std::env::var("HOME") + .with_context(|| "$HOME must be set when in user mode")?; + let tmp = PathBuf::from(home).join(".var/tmp"); + create_dir_all(&tmp)?; + Ok(tmp) +} + #[test] -fn test_repo() { +fn test_layer() -> Result<()> { + let layer = example_layer()?; + + let tmpfile = tempfile::TempDir::with_prefix_in("composefs-test-", home_var_tmp()?)?; + let repo = Repository::open_path(tmpfile.path().to_path_buf())?; + oci::import_layer(&repo, "name", &mut layer.as_slice())?; + + let mut dump = String::new(); + let mut split_stream = repo.open_stream("refs/name")?; + let mut reader = SplitStreamReader::new(&mut split_stream); + while let Some(entry) = oci::tar::get_entry(&mut reader)? { + writeln!(dump, "{}", entry)?; + } + assert_eq!(dump, "\ +/file0 0 100700 1 0 0 0 0.0 - - +/file4095 4095 100700 1 0 0 0 0.0 - - 5372beb83c78537c8970c8361e3254119fafdf1763854ecd57d3f0fe2da7c719 +/file4096 4096 100700 1 0 0 0 0.0 - - babc284ee4ffe7f449377fbf6692715b43aec7bc39c094a95878904d34bac97e +/file4097 4097 100700 1 0 0 0 0.0 - - 093756e4ea9683329106d4a16982682ed182c14bf076463a9e7f97305cbac743 +"); + Ok(()) }