Skip to content

Commit

Permalink
Can now import and mount images
Browse files Browse the repository at this point in the history
  • Loading branch information
allisonkarlitskaya committed Oct 7, 2024
1 parent 77c0d4b commit 263f7c7
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 145 deletions.
19 changes: 17 additions & 2 deletions src/bin/cfsctl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,20 @@ enum Command {
Cat {
reference: String,
},
/// Imports a composefs image (unsafe!)
ImportImage {
reference: String,
},
/// Stores a tar file as a splitstream in the repository.
ImportTar {
reference: String,
tarfile: Option<PathBuf>,
},
/// Mounts a composefs
Mount {
name: String,
mountpoint: String,
},
}

fn main() {
Expand All @@ -49,9 +58,15 @@ fn main() {
},
Command::Cat { reference } => {
repo.merge_splitstream(&reference, &mut std::io::stdout()).expect("merge");
}
},
Command::ImportImage { reference, } => {
repo.import_image(&reference, &mut std::io::stdin()).expect("image-import");
},
Command::ImportTar { reference, tarfile: _ } => {
repo.import_tar(&reference, &mut std::io::stdin()).expect("tar-import");
}
},
Command::Mount { name, mountpoint } => {
repo.mount(&name, &mountpoint).expect("mount");
},
}
}
133 changes: 2 additions & 131 deletions src/bin/mount.rs
Original file line number Diff line number Diff line change
@@ -1,136 +1,7 @@
use std::os::fd::{
OwnedFd,
BorrowedFd,
AsFd,
AsRawFd
};

use anyhow::Result;
use rustix::mount::{
FsMountFlags,
FsOpenFlags,
MountAttrFlags,
MoveMountFlags,
UnmountFlags,
fsconfig_create,
fsconfig_set_string,
fsmount,
fsopen,
move_mount,
unmount,
};

use composefs_experiments::{
fsverity,
tmpdir,
};

struct FsHandle {
pub fd: OwnedFd,
}

impl FsHandle {
pub fn open(name: &str) -> Result<FsHandle> {
Ok(FsHandle { fd: fsopen(name, FsOpenFlags::FSOPEN_CLOEXEC)? })
}
}

impl AsFd for FsHandle {
fn as_fd(&self) -> BorrowedFd {
return self.fd.as_fd();
}
}

impl Drop for FsHandle {
fn drop(&mut self) {
let mut buffer = [0u8; 1024];
loop {
match rustix::io::read(&self.fd, &mut buffer) {
Err(_) => return, // ENODATA, among others?
Ok(0) => return,
Ok(size) => eprintln!("{}", String::from_utf8(buffer[0..size].to_vec()).unwrap()),
}
}
}
}

struct TmpMount {
pub dir: tmpdir::TempDir,
}

impl TmpMount {
pub fn mount(fs: BorrowedFd) -> Result<TmpMount> {
let tmp = tmpdir::TempDir::new()?;
let mnt = fsmount(fs, FsMountFlags::FSMOUNT_CLOEXEC, MountAttrFlags::empty())?;
move_mount(mnt.as_fd(), "", rustix::fs::CWD, &tmp.path, MoveMountFlags::MOVE_MOUNT_F_EMPTY_PATH)?;
Ok(TmpMount { dir: tmp })
}
}

impl Drop for TmpMount {
fn drop(&mut self) {
unmount(&self.dir.path, UnmountFlags::DETACH)
.expect("umount(MNT_DETACH) failed");
}
}

struct MountOptions<'a> {
image: &'a str,
basedir: &'a str,
digest: Option<&'a str>,
verity: bool,
}

fn proc_self_fd<A: AsFd>(fd: &A) -> String {
format!("/proc/self/fd/{}", fd.as_fd().as_raw_fd())
}

impl<'a> MountOptions<'a> {
pub fn new(image: &'a str, basedir: &'a str) -> MountOptions<'a> {
MountOptions { image, basedir, digest: None, verity: false }
}

pub fn set_require_verity(&mut self) {
self.verity = true;
}

pub fn set_digest(&mut self, digest: &'a str) {
self.digest = Some(digest);
}

fn mount(self, mountpoint: &str) -> Result<()> {
let image = std::fs::File::open(self.image)?;

if let Some(expected) = self.digest {
let measured: fsverity::Sha256HashValue = fsverity::ioctl::fs_ioc_measure_verity(&image)?;
if expected != hex::encode(measured) {
panic!("expected {:?} measured {:?}", expected, measured);
}
}

let erofs = FsHandle::open("erofs")?;
fsconfig_set_string(erofs.as_fd(), "source", proc_self_fd(&image))?;
fsconfig_create(erofs.as_fd())?;

let overlayfs = FsHandle::open("overlay")?;
fsconfig_set_string(overlayfs.as_fd(), "metacopy", "on")?;
fsconfig_set_string(overlayfs.as_fd(), "redirect_dir", "on")?;

// unfortunately we can't do this via the fd: we need a tmpdir mountpoint
let tmp = TmpMount::mount(erofs.as_fd())?; // NB: must live until the "create" operation
fsconfig_set_string(overlayfs.as_fd(), "lowerdir+", &tmp.dir.path)?;
fsconfig_set_string(overlayfs.as_fd(), "datadir+", self.basedir)?;
fsconfig_create(overlayfs.as_fd())?;

let mnt = fsmount(overlayfs.as_fd(), FsMountFlags::FSMOUNT_CLOEXEC, MountAttrFlags::empty())?;
move_mount(mnt.as_fd(), "", rustix::fs::CWD, mountpoint, MoveMountFlags::MOVE_MOUNT_F_EMPTY_PATH)?;

Ok(())
}
}

use clap::Parser;

use composefs_experiments::mount::MountOptions;

/// mount a composefs
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
Expand Down
2 changes: 1 addition & 1 deletion src/bin/splitstream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ fn main() {
&mut std::io::stdin(),
&mut std::io::stdout(),
|data: &[u8]| -> Result<Sha256HashValue> {
repo.ensure_object(&data)
repo.ensure_object(data)
}
).expect("split");
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod util;
pub mod repository;
pub mod fsverity;
pub mod mount;
pub mod splitstream;
pub mod tar;
pub mod tmpdir;
134 changes: 134 additions & 0 deletions src/mount.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
use std::os::fd::{
OwnedFd,
BorrowedFd,
AsFd,
AsRawFd
};

use anyhow::Result;
use rustix::mount::{
FsMountFlags,
FsOpenFlags,
MountAttrFlags,
MoveMountFlags,
UnmountFlags,
fsconfig_create,
fsconfig_set_string,
fsmount,
fsopen,
move_mount,
unmount,
};

use crate::{
fsverity,
tmpdir,
};

struct FsHandle {
pub fd: OwnedFd,
}

impl FsHandle {
pub fn open(name: &str) -> Result<FsHandle> {
Ok(FsHandle { fd: fsopen(name, FsOpenFlags::FSOPEN_CLOEXEC)? })
}
}

impl AsFd for FsHandle {
fn as_fd(&self) -> BorrowedFd {
return self.fd.as_fd();
}
}

impl Drop for FsHandle {
fn drop(&mut self) {
let mut buffer = [0u8; 1024];
loop {
match rustix::io::read(&self.fd, &mut buffer) {
Err(_) => return, // ENODATA, among others?
Ok(0) => return,
Ok(size) => eprintln!("{}", String::from_utf8(buffer[0..size].to_vec()).unwrap()),
}
}
}
}

struct TmpMount {
pub dir: tmpdir::TempDir,
}

impl TmpMount {
pub fn mount(fs: BorrowedFd) -> Result<TmpMount> {
let tmp = tmpdir::TempDir::new()?;
let mnt = fsmount(fs, FsMountFlags::FSMOUNT_CLOEXEC, MountAttrFlags::empty())?;
move_mount(mnt.as_fd(), "", rustix::fs::CWD, &tmp.path, MoveMountFlags::MOVE_MOUNT_F_EMPTY_PATH)?;
Ok(TmpMount { dir: tmp })
}
}

impl Drop for TmpMount {
fn drop(&mut self) {
unmount(&self.dir.path, UnmountFlags::DETACH)
.expect("umount(MNT_DETACH) failed");
}
}

fn proc_self_fd<A: AsFd>(fd: A) -> String {
format!("/proc/self/fd/{}", fd.as_fd().as_raw_fd())
}

pub fn mount_fd<F: AsFd>(image: F, basedir: &str, mountpoint: &str) -> Result<()> {
let erofs = FsHandle::open("erofs")?;
fsconfig_set_string(erofs.as_fd(), "source", proc_self_fd(&image))?;
fsconfig_create(erofs.as_fd())?;

let overlayfs = FsHandle::open("overlay")?;
fsconfig_set_string(overlayfs.as_fd(), "metacopy", "on")?;
fsconfig_set_string(overlayfs.as_fd(), "redirect_dir", "on")?;

// unfortunately we can't do this via the fd: we need a tmpdir mountpoint
let tmp = TmpMount::mount(erofs.as_fd())?; // NB: must live until the "create" operation
fsconfig_set_string(overlayfs.as_fd(), "lowerdir+", &tmp.dir.path)?;
fsconfig_set_string(overlayfs.as_fd(), "datadir+", basedir)?;
fsconfig_create(overlayfs.as_fd())?;

let mnt = fsmount(overlayfs.as_fd(), FsMountFlags::FSMOUNT_CLOEXEC, MountAttrFlags::empty())?;
move_mount(mnt.as_fd(), "", rustix::fs::CWD, mountpoint, MoveMountFlags::MOVE_MOUNT_F_EMPTY_PATH)?;

Ok(())
}

pub struct MountOptions<'a> {
image: &'a str,
basedir: &'a str,
digest: Option<&'a str>,
verity: bool,
}

impl<'a> MountOptions<'a> {
pub fn new(image: &'a str, basedir: &'a str) -> MountOptions<'a> {
MountOptions { image, basedir, digest: None, verity: false }
}

pub fn set_require_verity(&mut self) {
self.verity = true;
}

pub fn set_digest(&mut self, digest: &'a str) {
self.digest = Some(digest);
}

pub fn mount(self, mountpoint: &str) -> Result<()> {
let image = std::fs::File::open(self.image)?;

if let Some(expected) = self.digest {
let measured: fsverity::Sha256HashValue = fsverity::ioctl::fs_ioc_measure_verity(&image)?;
if expected != hex::encode(measured) {
panic!("expected {:?} measured {:?}", expected, measured);
}
}

mount_fd(image, self.basedir, mountpoint)
}
}
Loading

0 comments on commit 263f7c7

Please sign in to comment.