WIP added state + pulling docker + now working on FS creation for VMs

This commit is contained in:
kitzman 2021-12-23 00:38:19 +02:00
parent 7de455702a
commit 790d094b67
21 changed files with 1490 additions and 106 deletions

View File

@ -7,6 +7,14 @@ edition = "2018"
[dependencies]
log = "0.4.11"
env_logger = "0.8.2"
num-traits = "0.2"
num-derive = "0.3"
lazy_static = "1.4.0"
syscalls = "0.3.5"
nix = "0.23.1"
mktemp = "0.4.1"
walkdir = "2.3.2"
serde = { version = "1.0.125", features = ["derive"] }
serde_json = "1.0.64"
@ -14,15 +22,23 @@ serde_json = "1.0.64"
clap = "2.33.3"
regex = "1.4.0"
num-traits = "0.2"
num-derive = "0.3"
syscalls = "0.3.5"
lazy_static = "1.4.0"
sha2 = "0.10.0"
base58 = "0.2.0"
tokio = { version = "1.15.0", features = ["full"] }
futures = { version = "0.3.17" }
dkregistry = { git = "https://github.com/camallo/dkregistry-rs", rev = "421c28c" }
[lib]
name = "nstool"
name = "moksha"
path = "src/lib.rs"
[[bin]]
name = "nstool"
path = "src/bin/nstool.rs"
[[bin]]
name = "moksha-state"
path = "src/bin/moksha_state.rs"

248
src/bin/moksha_state.rs Normal file
View File

@ -0,0 +1,248 @@
use moksha::types::Result;
use moksha::errors::Error;
use moksha::state::{AbstractState, GlobalState};
use moksha::state::fs_store::*;
use moksha::fs::fs_factory::*;
use moksha::fs::btrfs_desc::BtrfsDesc;
use moksha::fs::targz_fs_desc::TargzFsDesc;
use num_traits::ToPrimitive;
use std::path::PathBuf;
use dkregistry::v2::Client;
// use tokio::prelude::*;
// use futures::executor::block_on;
use futures::*;
use std::sync::Arc;
use clap::{App, Arg};
use log;
const DESCRIPTION: &str = "moksha-state helps you manage your moksha state store";
const DEFAULT_STORE_ROOT: &str = "/var/lib/moksha";
enum Command {
Init,
Pull,
NotImplemented
}
impl From<&str> for Command {
fn from(str_command: &str) -> Command {
match str_command {
"init" => Command::Init,
"pull" => Command::Pull,
_ => Command::NotImplemented,
}
}
}
// async fn main() -> Result<(), Box<dyn std::error::Error>> {
#[tokio::main]
async fn main() -> Result<()> {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
let arg_matches = App::new(env!("CARGO_PKG_NAME"))
.version(env!("CARGO_PKG_VERSION"))
.author(env!("CARGO_PKG_AUTHORS"))
.about(DESCRIPTION)
.subcommand(
App::new("init")
.arg(
Arg::with_name("root_dir")
.short("b")
.long("root-dir")
.takes_value(true)
.help("root dir (default: '/var/lib/moksha')"),
)
)
.subcommand(
App::new("pull")
.arg(
Arg::with_name("root_dir")
.short("b")
.long("root-dir")
.takes_value(true)
.help("root dir (default: '/var/lib/moksha')"),
)
.arg(
Arg::with_name("image")
.short("i")
.long("image")
.takes_value(true)
.help("image to pull")
)
)
.get_matches();
let command_tuple = arg_matches.subcommand();
let command: Command = command_tuple.0.into();
let command_args = command_tuple.1;
log::debug!("moksha-store started");
match (command, command_args) {
(Command::Init, Some(list_opts)) => {
let store_root = list_opts
.value_of("root_dir")
.unwrap_or(DEFAULT_STORE_ROOT)
.to_string();
let store_root_path = PathBuf::from(store_root);
let mut global_state = GlobalState::open(
store_root_path,
Some(GlobalState::empty_variant()))?;
let test_item = &mut FsStoreItem {
item_name: FsStoreItemName {
domain: "kitzplatz".to_string(),
name: "dummy".to_string(),
version: "0.1".to_string(),
overlay: 0
},
item_hash: FsStoreItemId {
id: "".to_string()
},
prev_overlay: None,
fs_type: FsItemType::Btrfs,
encryption: None,
contents_checksum: None,
base_path: PathBuf::new()
};
global_state.fs_store.add_item(test_item)?;
Ok(())
}
(Command::Pull, Some(list_opts)) => {
let store_root = list_opts
.value_of("root_dir")
.unwrap_or(DEFAULT_STORE_ROOT)
.to_string();
let store_root_path = PathBuf::from(store_root);
let image_id_opt = list_opts
.value_of("image");
if image_id_opt.is_none() {
log::error!("command {} requires an 'image' argument", command_tuple.0);
return Ok(println!("{}", arg_matches.usage()));
}
let image_id = image_id_opt.unwrap().split_terminator(":").collect::<Vec<&str>>();
let image_name = *image_id.first().unwrap();
let image_version = *image_id.last().unwrap();
let mut global_state = GlobalState::open(
store_root_path,
None)?;
let registry_client = Client::configure()
.registry("registry-1.docker.io")
.build()?
.authenticate(&[format!("repository:{}:pull", image_name).as_str()]).await?;
registry_client.is_auth().await?;
let image_manifest = registry_client
.get_manifest(image_name, image_version)
.await?;
// log::info!("manifest: {:?}", image_manifest);
log::info!("successfully pulled manifest for {}:{}", image_name, image_version);
// let mut last_fs_item = None;
log::info!("pulling layers...");
let blob_results: Vec<Result<Vec<u8>>> = future::join_all(
image_manifest
.layers_digests(None)?
.iter()
.map(Arc::new)
.map(|layer| {
log::info!("pulling {}", layer);
registry_client
.get_blob(image_name, layer.as_str())
.and_then(|blob| async move {
log::info!("finished pulling {}", layer);
Ok(blob)
})
.map_err(Error::DockerRegistryError)
}))
.await;
log::info!("finished pulling");
log::info!("writing to disk");
let mut last_overlay = None;
// processing blobs
// todo: write sha256 as an attr, check for already existing images
// make this part of a pulling fiber
for (i, blob_result) in blob_results.iter().enumerate() {
let blob = blob_result.as_deref().map_err(|e| {
log::error!("unable to pull blob: {}", e);
Error::StateError(format!("unable to pull blob: {}", e))
})?;
let new_item = &mut FsStoreItem {
item_name: FsStoreItemName {
domain: "docker".to_string(),
name: image_name.to_string(),
version: image_version.to_string(),
overlay: i.to_u64().unwrap()
},
item_hash: FsStoreItemId {
id: "".to_string()
},
prev_overlay: last_overlay.clone(),
fs_type: FsItemType::Btrfs,
encryption: None,
contents_checksum: None,
base_path: PathBuf::new()
};
// adding of new item, and data backup the tar
// todo check for layer type
new_item.reset_id();
log::debug!("processing blob {}", new_item.item_hash.id.clone());
global_state.fs_store.add_item(new_item)?;
new_item.write_attr_raw("backup.tar.gz".into(), blob.to_vec())?;
log::debug!("wrote backup for {}", new_item.item_hash.id.clone());
log::debug!("writing btrfs fs for {}", new_item.item_hash.id.clone());
new_item.wait_for_lock()?;
new_item.lock()?;
let mut fs_factory =
FsFactory::<TargzFsDesc, BtrfsDesc>::new(new_item.base_path().join("image"));
fs_factory.set_data(blob.to_vec())
.or_else(|e| { new_item.unlock()?; Err(e) })?;
fs_factory.finish()
.or_else(|e| { new_item.unlock()?; Err(e) })?;
new_item.unlock()?;
last_overlay = Some(new_item.item_hash.id.clone());
log::debug!("done processing {}", new_item.item_hash.id.clone());
}
log::info!("finished persisting");
return Ok(())
}
(_, _) => {
log::error!("command {} not implemented", command_tuple.0);
Ok(println!("{}", arg_matches.usage()))
}
}
}

View File

@ -1,10 +1,10 @@
use nstool::proc::ProcHandler;
use moksha::unix::ProcHandler;
use nstool::namespace::{NamespaceBundle, NamespaceBundleStruct, NamespaceOption, NamespaceType};
use nstool::provider::{JailProvider, NamespaceProvider};
use nstool::types::{Pid, Result};
use moksha::namespace::{NamespaceBundle, NamespaceBundleStruct, NamespaceOption, NamespaceType};
use moksha::provider::{JailProvider, NamespaceProvider};
use moksha::types::{Pid, Result};
use nstool::errors::Error;
use moksha::errors::Error;
use clap::{App, Arg};

View File

@ -2,7 +2,8 @@ use std::{error, fmt};
use clap::Error as ClapError;
use regex::Error as RegexError;
// use fs_extra::error::{Error as FsError};
use std::convert::Infallible as StdInfallible;
use serde_json::Error as SerdeJsonError;
@ -14,6 +15,8 @@ use std::io::Error as IOError;
use std::num::ParseIntError;
use std::path::StripPrefixError;
use dkregistry::errors::{Error as DkRegError};
use crate::types::Pid;
#[derive(Debug)]
@ -26,14 +29,21 @@ pub enum Error {
NamespaceParsingError(String),
NamespaceValidationError(String),
LoopCtrlError(String),
LoopDeviceError(String),
StateError(String),
StripPrefixError(StripPrefixError),
StateGetItemError(String),
FilesystemFactoryError(String),
FilesystemDescError(String),
DockerRegistryError(DkRegError),
DockerImageSourceError(String),
WriterParseError(String),
Infallible(StdInfallible),
Infallible(StdInfallible),
CStringError(NulError),
SerdeJsonError(SerdeJsonError),
@ -47,6 +57,8 @@ pub enum Error {
ParseIntError(ParseIntError),
RegexError(RegexError),
IOError(IOError),
FsError(more_fs::Error)
// FsExtraError(FsError)
}
impl error::Error for Error {
@ -102,6 +114,18 @@ impl From<regex::Error> for Error {
}
}
// impl From<FsError> for Error {
// fn from(o: FsError) -> Error {
// Error::FsExtraError(o)
// }
// }
impl From<more_fs::Error> for Error {
fn from(o: more_fs::Error) -> Error {
Error::FsError(o)
}
}
impl From<ParseIntError> for Error {
fn from(o: ParseIntError) -> Error {
Error::ParseIntError(o)
@ -120,6 +144,12 @@ impl From<StripPrefixError> for Error {
}
}
impl From<DkRegError> for Error {
fn from(o: DkRegError) -> Error {
Error::DockerRegistryError(o)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {

121
src/fs/btrfs_desc.rs Normal file
View File

@ -0,0 +1,121 @@
use crate::fs::fs_factory::FilesystemDesc;
use crate::types::Result;
use crate::errors::Error;
use crate::unix::mounts::{MountHandler, MountDesc, FsType, MountOpt};
use crate::unix::loop_handler::LoopHandler;
use std::io::Write;
use std::path::PathBuf;
use mktemp::Temp;
use std::process::Command;
use std::cmp;
use std::fs;
const BLOCK_SIZE: usize = 1024;
const BTRFS_MIN_SIZE: usize = BLOCK_SIZE * 1024 * 128;
/*
* Descriptor for a btrfs filesystem
*/
pub struct BtrfsDesc {
path: PathBuf,
mount_desc: Option<MountDesc>
}
impl FilesystemDesc for BtrfsDesc {
fn from_path(path: PathBuf) -> Result<Self> {
Ok(BtrfsDesc{path, mount_desc: None})
}
fn from_mem(_data: Vec<u8>) -> Result<Self> {
Err(Error::FilesystemDescError("no mem support for btrfs (forever?)".to_string()))
}
fn get_path(&self) -> PathBuf { self.path.clone() }
fn format(&self, handler: &MountHandler, size: usize) -> Result<()> {
// checking that the fs is not in use
if handler.is_mounted(self.path.to_str().unwrap().into())? {
return Err(Error::FilesystemDescError(
format!("path {:?} already mounted", self.path.clone())))
}
// creating new image
log::debug!("creating empty btrfs image on {:?}", self.path.clone());
let temp_img_path = Temp::new_file()?.to_path_buf();
let mut temp_img = fs::OpenOptions::new()
.read(false)
.write(true)
.append(false)
.create(true)
.open(temp_img_path.clone())?;
let zero_vec = vec![0; BLOCK_SIZE];
// inserting zero into the file
for _ in 0..(cmp::max(size, BTRFS_MIN_SIZE) / BLOCK_SIZE + 1) {
temp_img.write_all(zero_vec.as_slice())?;
}
// creating the filesystem
log::debug!("formatting image as btrfs: {:?}", self.path.clone());
let mut format_cmd = Command::new("mkfs.btrfs");
format_cmd.arg(temp_img_path.clone());
let mut format_process = format_cmd.spawn()?;
let format_result = format_process.wait()?;
if !format_result.success() {
return Err(Error::FilesystemDescError(
format!("could not format {:?} as btrfs: {}",
temp_img_path.clone().to_path_buf(),
format_result)));
}
// replacing old filesystem with the new one
fs::copy(temp_img_path.clone(), self.path.clone())?;
fs::remove_file(temp_img_path)?;
Ok(())
}
fn mount(&mut self, handler: &MountHandler, loopctl: &LoopHandler, path: PathBuf)
-> Result<()> {
if !path.exists() {
return Err(Error::FilesystemDescError("fs does not exist".into()))
}
// preparing the loop device
let loopdev = loopctl.loop_file(self.path.clone())?;
log::debug!("mounting btrfs {:?} on {:?}", loopdev.loop_dev, path.clone());
// preparing the mount
let temp_mount = MountDesc {
source: loopdev.loop_dev.to_str().unwrap().into(),
target: path.to_str().unwrap().into(),
partition_type: FsType::Btrfs,
options: vec![MountOpt::Noatime],
data: None,
};
handler.mount(temp_mount.clone())?;
log::debug!("mounting success: btrfs {:?} on {:?}", loopdev.loop_dev, path.clone());
self.mount_desc = Some(temp_mount);
Ok(())
}
fn umount(&mut self, handler: &MountHandler, loopctl: &LoopHandler) -> Result<()> {
if self.mount_desc.is_none() {
return Err(Error::FilesystemDescError("not mounted".into()));
}
// unmounting
handler.umount(self.mount_desc.clone().unwrap(), false, false)?;
// unlooping
loopctl.unloop_file(self.path.clone())
}
}

197
src/fs/fs_factory.rs Normal file
View File

@ -0,0 +1,197 @@
use crate::types::Result;
use crate::errors::*;
use crate::unix::mounts::MountHandler;
use crate::unix::loop_handler::LoopHandler;
// use fs_extra;
use walkdir::WalkDir;
use num_traits::ToPrimitive;
use std::fs;
use std::path::PathBuf;
use std::os::unix::fs::*;
use mktemp::Temp;
use std::marker::PhantomData;
// // filesystem mount operations
// #[derive(Debug)]
// struct FsMount {
// source: PathBuf,
// target: PathBuf,
// mount_type: String,
// fs_type: String,
// }
// trait FsMountOp {
// fn new(mounts: Vec<FsMount>) -> Self;
// fn mount() -> Result<()>;
// }
// filesystem descriptor
pub trait FilesystemDesc {
fn from_path(path: PathBuf) -> Result<Self> where Self: Sized;
fn from_mem(data: Vec<u8>) -> Result<Self> where Self: Sized;
fn format(&self, handler: &MountHandler, size: usize) -> Result<()>;
fn get_path(&self) -> PathBuf;
fn mount(&mut self, handler: &MountHandler, loopctl: &LoopHandler, path: PathBuf) -> Result<()>;
fn umount(&mut self, handler: &MountHandler, loopctl: &LoopHandler) -> Result<()>;
}
// filesystem factory
pub struct FsFactory<I: FilesystemDesc, O: FilesystemDesc> {
_i: PhantomData<I>,
_o: PhantomData<O>,
source_decided: bool,
data: Option<Vec<u8>>,
source: Option<PathBuf>,
target: PathBuf
}
impl<I: FilesystemDesc, O: FilesystemDesc> FsFactory<I, O> {
pub fn new(target: PathBuf) -> Self {
FsFactory {
_i: PhantomData,
_o: PhantomData,
source_decided: false,
data: None,
source: None,
target
}
}
pub fn set_data(&mut self, data: Vec<u8>) -> Result<()> {
if self.source_decided {
return Err(Error::FilesystemFactoryError(
format!("source already decided for {:?}", self.target)))
}
self.source_decided = true;
self.data = Some(data);
Ok(())
}
pub fn set_source(&mut self, path: PathBuf) -> Result<()> {
if self.source_decided {
return Err(Error::FilesystemFactoryError(
format!("source already decided for {:?}", self.target)))
}
if !path.is_file() {
return Err(Error::FilesystemFactoryError(
format!("source path {:?} does not exist", path)))
}
self.source_decided = true;
self.source = Some(path);
Ok(())
}
// TODO: this should happen in a namespace, as copying files owned by root,
// while keeping the permissions, is normally forbidden
pub fn finish(&self) -> Result<O> {
if !self.source_decided {
return Err(Error::FilesystemFactoryError(
format!("source for {:?} not decided", self.target)))
}
log::debug!("building fs descriptors");
let mounts = &mut MountHandler::new(PathBuf::from("/")); // this root has to be configured
let loopctl = &mut LoopHandler::new(None)?;
let mut idesc = if self.data.is_some() {
I::from_mem(self.data.as_ref().unwrap().to_owned())?
} else {
I::from_path(self.source.clone().unwrap())?
};
let temp_dir_input = Temp::new_dir()?.release();
let temp_dir_output = Temp::new_dir()?.release();
let odesc_size = if self.data.is_some() {
self.data.as_ref().unwrap().len()
} else {
let input_metadata = fs::metadata(self.source.clone().unwrap())?;
input_metadata.size().to_usize().unwrap()
};
let mut odesc = O::from_path(self.target.clone())?;
odesc.format(mounts, odesc_size)?;
log::debug!("mounting fs descriptors");
idesc.mount(mounts, loopctl, temp_dir_input.clone())?;
odesc.mount(mounts, loopctl, temp_dir_output.clone())?;
// let mut copy_opts = fs_extra::dir::CopyOptions::new();
// copy_opts.overwrite = true;
// copy_opts.copy_inside = true;
// let input_items = fs::read_dir(temp_dir_input.clone())?
// .into_iter()
// .map(|r| r.map(|di| di.path().to_str().unwrap().to_string()))
// .map(|r| r.map_err(Error::IOError))
// .collect::<Result<Vec<String>>>()?;
log::debug!("copying data from {:?} to {:?}",
temp_dir_input.clone(), temp_dir_output.clone());
// for input_item in input_items {
// let copied_path = &PathBuf::from(input_item);
// let elem_name = copied_path.file_name().unwrap();
// more_fs::copy_dir_all(copied_path, temp_dir_output.clone().join(elem_name))?;
// }
// fs_extra::copy_items(
// &input_items,
// &temp_dir_output.clone(),
// &copy_opts)?;
copy_recursively(temp_dir_input.clone(), temp_dir_output.clone())?;
// let temp_mount = MountDesc {
// source: "tmpfs".to_string(),
// target: temp_dir.to_str().unwrap().to_string(),
// partition_type: FsType::Tmpfs,
// options: vec![MountOpt::Noatime, MountOpt::Noexec],
// data: None,
// };
log::debug!("fs creation done, unmounting & exiting");
idesc.umount(mounts, loopctl)?;
odesc.umount(mounts, loopctl)?;
Ok(odesc)
}
}
fn copy_recursively(source: PathBuf, destination: PathBuf) -> Result<()> {
for entry_r in WalkDir::new(source) {
let entry = entry_r?;
let metadata = entry.metadata()?;
let rel_path = entry.path().strip_prefix(source)?;
if entry.path_is_symlink() {
} else if metadata.is_dir() {
fs::DirBuilder::new()
.mode(metadata.mode())
.create(destination.clone().join(rel_path))?;
fs::create_dir(destination.clone().join(rel_path))?;
} else if metadata.is_file() {
fs::copy(entry.path(), destination.clone().join(rel_path))?;
} else {
return Err(Error::FilesystemFactoryError(
format!("unknown file type: {:?}", source)));
}
}
Ok(())
}

5
src/fs/mod.rs Normal file
View File

@ -0,0 +1,5 @@
pub mod fs_factory;
pub mod tmpfs_desc;
pub mod targz_fs_desc;
pub mod btrfs_desc;

127
src/fs/targz_fs_desc.rs Normal file
View File

@ -0,0 +1,127 @@
use crate::fs::fs_factory::FilesystemDesc;
use crate::types::Result;
use crate::errors::Error;
use crate::unix::mounts::{MountHandler, MountDesc, FsType, MountOpt};
use crate::unix::loop_handler::LoopHandler;
use std::path::PathBuf;
use mktemp::Temp;
use std::process::Command;
use std::fs;
/*
* Descriptor for a (virtual & ephemeral) .tar.gz filesystem
*/
pub struct TargzFsDesc {
path: PathBuf,
is_from_mem: bool,
mount_desc: Option<MountDesc>
}
impl FilesystemDesc for TargzFsDesc {
fn from_path(path: PathBuf) -> Result<Self> {
if !path.exists() {
return Err(Error::FilesystemDescError("archive does not exist".into()))
}
Ok(TargzFsDesc{path, is_from_mem: false, mount_desc: None})
}
fn from_mem(data: Vec<u8>) -> Result<Self> {
let mut temp_ar = Temp::new_path().release();
temp_ar.set_extension("tar.gz");
fs::write(temp_ar.clone(), data)?;
Ok(TargzFsDesc{
path: temp_ar,
is_from_mem: true,
mount_desc: None
})
}
fn get_path(&self) -> PathBuf { self.path.clone() }
fn format(&self, _handler: &MountHandler, _size: usize) -> Result<()> {
Err(Error::FilesystemDescError("tar.gz archives cannot be formatted".into()))
}
fn mount(&mut self, handler: &MountHandler, _loopctl: &LoopHandler, path: PathBuf)
-> Result<()> {
let source_path_str = self.path
.to_str()
.map(Ok)
.unwrap_or_else(|| Err(Error::FilesystemDescError("unable to parse path".into())))?;
let target_path_str = path
.to_str()
.map(Ok)
.unwrap_or_else(|| Err(Error::FilesystemDescError("unable to parse path".into())))?;
// preparing the unpacking area
let temp_mount = MountDesc {
source: "tmpfs".to_string(),
target: target_path_str.into(),
partition_type: FsType::Tmpfs,
options: vec![MountOpt::Noatime],
data: None,
};
handler.mount(temp_mount.clone())?;
// unpacking
let mut tar_cmd = Command::new("tar");
tar_cmd.arg("-xf");
tar_cmd.arg(source_path_str);
tar_cmd.arg("-C");
tar_cmd.arg(target_path_str);
let mut tar_process = tar_cmd.spawn()?;
let unpack_result = tar_process.wait()?;
if !unpack_result.success() {
handler.umount(temp_mount.clone(), true, false)?;
return Err(Error::FilesystemDescError("unpacking failed".into()));
}
self.mount_desc = Some(temp_mount);
Ok(())
}
fn umount(&mut self, handler: &MountHandler, _loopctl: &LoopHandler)
-> Result<()> {
if self.mount_desc.is_none() {
return Err(Error::FilesystemDescError("not mounted".into()));
}
// packing back the contents
if !self.is_from_mem {
// unpacking
let mut temp_ar = Temp::new_path();
temp_ar.set_extension("tar.gz");
let mut tar_cmd = Command::new("tar");
tar_cmd.arg("-a");
tar_cmd.arg("-cf");
tar_cmd.arg(self.path.clone());
tar_cmd.arg("-C");
tar_cmd.arg(self.mount_desc.clone().unwrap().target);
tar_cmd.arg(".");
let mut tar_process = tar_cmd.spawn()?;
let pack_result = tar_process.wait()?;
if !pack_result.success() {
handler.umount(self.mount_desc.clone().unwrap(), true, false)?;
return Err(Error::FilesystemDescError("unpacking failed".into()));
}
fs::copy(temp_ar.clone(), self.path.clone())?;
fs::remove_file(temp_ar)?;
}
// unmounting the tar
handler.umount(self.mount_desc.clone().unwrap(), false, false)?;
self.mount_desc = None;
Ok(())
}
}

47
src/fs/tmpfs_desc.rs Normal file
View File

@ -0,0 +1,47 @@
use crate::fs::fs_factory::FilesystemDesc;
use crate::types::Result;
use crate::errors::Error;
use crate::unix::mounts::{MountHandler, MountDesc, FsType, MountOpt};
use crate::unix::loop_handler::LoopHandler;
use std::path::PathBuf;
/*
* Descriptor for a Tmpfs ephemeral filesystem
*/
struct TmpfsDesc {
mount_desc: Option<MountDesc>
}
impl FilesystemDesc for TmpfsDesc {
fn from_path(_path: PathBuf) -> Result<Self> { Ok(TmpfsDesc{mount_desc: None}) }
fn from_mem(_data: Vec<u8>) -> Result<Self> { Ok(TmpfsDesc{mount_desc: None}) }
fn format(&self, _handler: &MountHandler, _size: usize) -> Result<()> { Ok(()) }
fn get_path(&self) -> PathBuf { PathBuf::new() }
fn mount(&mut self, handler: &MountHandler, _loopctl: &LoopHandler, path: PathBuf)
-> Result<()> {
let temp_mount = MountDesc {
source: "tmpfs".to_string(),
target: path.to_str().unwrap().to_string(),
partition_type: FsType::Tmpfs,
options: vec![MountOpt::Noatime],
data: None,
};
handler.mount(temp_mount.clone())?;
self.mount_desc = Some(temp_mount);
Ok(())
}
fn umount(&mut self, handler: &MountHandler, _loopctl: &LoopHandler) -> Result<()> {
if self.mount_desc.is_none() {
return Err(Error::FilesystemDescError("not mounted".into()));
}
handler.umount(self.mount_desc.clone().unwrap(), true, false)?;
self.mount_desc = None;
Ok(())
}
}

View File

@ -5,7 +5,8 @@ pub mod c_types;
pub mod errors;
pub mod namespace;
pub mod proc;
pub mod unix;
pub mod provider;
pub mod state;
pub mod types;
pub mod fs;

View File

@ -1,7 +1,7 @@
use crate::namespace::{NamespaceOption, NamespaceType};
use crate::proc::mounts::{FsType, MountDesc, MountOpt};
use crate::proc::MountHandler;
use crate::proc::ProcHandler;
use crate::unix::mounts::{FsType, MountDesc, MountOpt};
use crate::unix::MountHandler;
use crate::unix::ProcHandler;
use crate::provider::JailProvider;
use crate::errors::{Errno, Error};

View File

@ -1,118 +1,287 @@
use serde::de::DeserializeOwned;
use serde::Serialize;
use crate::errors::Error;
use crate::types::Result;
use crate::state::state_type::*;
use std::fs;
use std::io::{Read, Write};
use std::path::PathBuf;
use std::thread;
use std::time::Duration;
use std::collections::HashSet;
const LOCK_FILENAME: &str = ".lock";
const RLOCK_FILENAME: &str = ".rlock";
const TYPE_FILENAME: &str = ".state_type";
const LOOP_WAIT_MS: u64 = 100;
pub trait AbstractState {
fn from_path(path: PathBuf) -> Self;
fn id() -> StateType;
/*
* Dummy state to handle uninitialized state dirs
*/
struct DummyState {
base_path: PathBuf
}
impl AbstractState for DummyState {
fn open_internal(_: PathBuf, _: Option<DummyState>) -> Result<DummyState> {
Err(Error::StateError("DummyState cannot be instantiated".to_string()))
}
fn desc() -> String {
"".to_string()
}
fn empty_variant() -> DummyState {
DummyState {
base_path: PathBuf::new()
}
}
fn base_path(&self) -> PathBuf {
self.base_path.clone()
}
fn state_path(&self) -> PathBuf {
PathBuf::new()
}
}
/*
* Abstract state
*/
pub trait AbstractState {
// static methods
fn open_internal(path: PathBuf, init: Option<Self>) -> Result<Self>
where Self: Sized;
fn desc() -> String;
fn empty_variant() -> Self
where Self: Sized;
// required methods
fn base_path(&self) -> PathBuf;
fn state_path(&self) -> PathBuf;
fn init(&self) -> Result<()> {
self.wait_for_lock()?;
self.lock()?;
let base_path = self.base_path();
let type_file_path = base_path.join(TYPE_FILENAME);
/*
* initialization and opening
*/
fn open(path: PathBuf, init: Option<Self>) -> Result<Self>
where Self: Sized {
log::debug!("opening (init? {}) at {}", init.is_some(), path.to_string_lossy());
let type_file = &mut fs::OpenOptions::new()
.read(true)
.write(true)
.append(false)
.open(type_file_path)
.map_err(|e| {self.unlock().unwrap(); e})?;
// checking if path is absolute
if path.is_relative() {
return Err(Error::StateError(
format!("the path {} is relative", path.to_string_lossy())))
}
// checking if directory exists
if init.is_some() {
fs::create_dir_all(&path)?;
} else {
fs::read_dir(&path)?;
}
let type_file_contents = &mut String::new();
type_file.read_to_string(type_file_contents)
.map_err(|e| {self.unlock().unwrap(); e})?;
let dummy = DummyState{base_path: path.clone()};
dummy.wait_for_lock()?;
dummy.lock()?;
let found_types = &mut match type_file_contents.trim().len() {
0 => StateTypeCol { state_types: HashSet::new()},
_ => serde_json::from_str::<StateTypeCol>(type_file_contents)
.map_err(|e| {self.unlock().unwrap(); e})?
};
let type_file_path = path.join(TYPE_FILENAME);
if !found_types.state_types.insert(Self::id()) {
log::warn!("warning: store item {} already initiated",
self.base_path().to_str().unwrap());
}
type_file.write(serde_json::to_string(found_types)
.map_err(|e| {self.unlock().unwrap(); e})?
.into_bytes().as_slice())
.map_err(|e| {self.unlock().unwrap(); e})?;
type_file.sync_all().map_err(|e| {self.unlock().unwrap(); e})?;
self.unlock()?;
Ok(())
// checking state type
if init.is_some() && !type_file_path.exists() {
log::debug!("creating state type file for {}", Self::desc());
fs::write(type_file_path, Self::desc())
.or_else(|e| {
log::debug!("error reading state type file: {}", e);
dummy.unlock()?;
Err(Error::IOError(e))
})?;
} else {
if init.is_some() {
log::warn!("initializing {} on {}, state file already exists",
Self::desc(), type_file_path.to_string_lossy())
}
log::debug!("opening state type file for {}", Self::desc());
let found_type = fs::read_to_string(type_file_path)
.or_else(|e| {
log::debug!("error reading state type file: {}", e);
dummy.unlock()?;
Err(Error::IOError(e))
})?;
if found_type.to_owned() != Self::desc() {
dummy.unlock()?;
return Err(Error::StateError(
format!("type {} does not match required type {}", found_type, Self::desc())));
}
}
let s = Self::open_internal(path, init);
dummy.unlock()?;
s
}
fn get_item<T: AbstractState>(&self, item_state_path: PathBuf) -> Result<T> {
let rel_item_path =
match item_state_path.is_relative() {
true => Ok(item_state_path),
false =>
item_state_path.strip_prefix(self.state_path())
.map( |p| p.to_path_buf())
}?;
let rel_item_path = match item_state_path.is_relative() {
true => Ok(item_state_path),
false => item_state_path
.strip_prefix(self.state_path())
.map(|p| p.to_path_buf()),
}?;
let abs_item_path = self.base_path().join(rel_item_path);
let abs_item_path = self.base_path().join(rel_item_path);
if !abs_item_path.is_dir() {
return Err(Error::StateGetItemError("could not find item".to_string()));
}
Ok(T::from_path(abs_item_path))
if !abs_item_path.is_dir() {
return Err(Error::StateGetItemError("could not find item".to_string()));
}
T::open(abs_item_path, None)
}
/*
* attributes
*/
fn write_attr<M: Serialize>(&self, attr: String, data: M) -> Result<()> {
self.lock()?;
fs::write(self.base_path().join(attr), serde_json::to_vec_pretty(&data)?)?;
self.unlock()?;
Ok(())
}
fn read_attr<M: DeserializeOwned>(&self, attr: String) -> Result<M> {
self.lock()?;
let raw_data = fs::read(self.base_path().join(attr))?;
let data = serde_json::from_slice(raw_data.as_slice())?;
self.unlock()?;
Ok(data)
}
/*
* attributes (raw)
*/
fn write_attr_raw(&self, attr: String, data: Vec<u8>) -> Result<()> {
self.lock()?;
fs::write(self.base_path().join(attr), data)?;
self.unlock()?;
Ok(())
}
fn read_attr_raw(&self, attr: String) -> Result<Vec<u8>> {
self.lock()?;
let data = fs::read(self.base_path().join(attr))?;
self.unlock()?;
Ok(data)
}
/*
* locking functions
*/
fn lock(&self) -> Result<()> {
let base_path = self.base_path();
if self.is_locked()? {
return Err(Error::StateError(
format!("state {} already locked",
base_path.to_str().unwrap())))
}
let base_path = self.base_path();
if self.is_locked()? {
return Err(Error::StateError(format!(
"state {} already locked",
base_path.to_str().unwrap()
)));
}
fs::File::create(base_path.join(LOCK_FILENAME))?;
Ok(())
fs::File::create(base_path.join(LOCK_FILENAME))?;
Ok(())
}
fn unlock(&self) -> Result<()> {
let base_path = self.base_path();
if !self.is_locked()? {
return Err(Error::StateError(
format!("state {} already unlocked",
base_path.to_str().unwrap())))
}
let base_path = self.base_path();
if !self.is_locked()? {
return Err(Error::StateError(format!(
"state {} already unlocked",
base_path.to_string_lossy()
)));
}
fs::remove_file(base_path.join(LOCK_FILENAME))?;
Ok(())
fs::remove_file(base_path.join(LOCK_FILENAME))?;
Ok(())
}
fn is_locked(&self) -> Result<bool> {
Ok(self.base_path().join(LOCK_FILENAME).exists())
Ok(self.base_path().join(LOCK_FILENAME).exists() ||
self.base_path().join(RLOCK_FILENAME).exists())
}
fn wait_for_lock(&self) -> Result<()> {
while self.is_locked()? {
thread::sleep(Duration::from_millis(LOOP_WAIT_MS))
}
Ok(())
while self.is_locked()? {
thread::sleep(Duration::from_millis(LOOP_WAIT_MS))
}
Ok(())
}
/*
* read locking functions
*/
fn rlock(&self) -> Result<()> {
let lock_path = self.base_path().join(RLOCK_FILENAME);
if self.is_rlocked()? {
return Err(Error::StateError(format!(
"state {} already locked",
self.base_path().to_string_lossy()
)));
}
let current_count = fs::read_to_string(lock_path.clone())
.map_err(Error::IOError)
.and_then(|lock_count_str: String| -> Result<u128> {
lock_count_str
.parse::<u128>()
.map_err(Error::ParseIntError)
})
.or::<Error>(Ok(0))?;
fs::write(lock_path, current_count.to_string().as_bytes())?;
Ok(())
}
fn unrlock(&self) -> Result<()> {
let lock_path = self.base_path().join(RLOCK_FILENAME);
if !lock_path.exists() {
return Err(Error::StateError(format!(
"state {} already unlocked",
self.base_path().to_string_lossy()
)));
}
let current_count = fs::read_to_string(lock_path.clone())
.map_err(Error::IOError)
.and_then(|lock_count_str: String| -> Result<u128> {
lock_count_str
.parse::<u128>()
.map_err(Error::ParseIntError)
})
.or::<Error>(Ok(0))?;
if current_count < 2 {
fs::remove_file(lock_path.clone())?;
} else {
fs::write(lock_path, (current_count - 1).to_string())?;
}
Ok(())
}
fn is_rlocked(&self) -> Result<bool> {
Ok(self.base_path().join(LOCK_FILENAME).exists())
}
fn wait_for_rlock(&self) -> Result<()> {
while self.is_rlocked()? {
thread::sleep(Duration::from_millis(LOOP_WAIT_MS))
}
Ok(())
}
}

207
src/state/fs_store.rs Normal file
View File

@ -0,0 +1,207 @@
use crate::state::AbstractState;
use crate::types::Result;
use std::borrow::BorrowMut;
use std::path::PathBuf;
use std::fs;
use serde::*;
use sha2::{Sha512, Digest};
use base58::*;
use std::str::FromStr;
const FS_STORE_TYPE: &str = "fs_store";
const FS_STORE_ITEM_PREFIX: &str = "item_";
const FS_STORE_METADATA_FILENAME: &str = "metadata";
const FS_STORE_IMAGE_FILENAME: &str = "image";
/*
* Filesystem item
*/
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FsItemType {
Ext4,
Fat32,
Btrfs
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FsItemEncryption {}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FsStoreItemId {
pub id: String,
}
#[derive(Hash, Debug, Clone, Serialize, Deserialize)]
pub struct FsStoreItemName {
pub domain: String,
pub name: String,
pub version: String,
pub overlay: u64,
}
impl FsStoreItemName {
fn hash(&self) -> String {
let mut hasher = Sha512::default();
hasher.update(self.domain.clone());
hasher.update(self.name.clone());
hasher.update(self.version.clone());
hasher.update(self.overlay.to_string());
let hashed_data = hasher.finalize();
hashed_data.as_slice().to_base58()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FsStoreItem {
pub item_name: FsStoreItemName,
pub item_hash: FsStoreItemId,
pub prev_overlay: Option<String>,
pub fs_type: FsItemType,
pub encryption: Option<FsItemEncryption>,
pub contents_checksum: Option<String>,
#[serde(skip_serializing, skip_deserializing)]
pub base_path: PathBuf
}
impl AbstractState for FsStoreItem {
fn desc() -> String { "fs_store_item".to_string() }
fn empty_variant() -> FsStoreItem {
FsStoreItem {
item_name: FsStoreItemName {
domain: "".to_string(),
name: "".to_string(),
version: "".to_string(),
overlay: 0
},
item_hash: FsStoreItemId {
id: "".to_string()
},
prev_overlay: None,
fs_type: FsItemType::Btrfs,
encryption: None,
contents_checksum: None,
base_path: PathBuf::new()
}
}
fn base_path(&self) -> PathBuf {
self.base_path.clone()
}
fn state_path(&self) -> PathBuf {
PathBuf::from_str("/").unwrap()
}
fn open_internal(path: PathBuf, init: Option<FsStoreItem>) -> Result<FsStoreItem>
where Self: Sized {
let metadata_filename = path.join(FS_STORE_METADATA_FILENAME);
if init.is_none() {
let metadata_raw = fs::read_to_string(metadata_filename)?;
let read_item: FsStoreItem = serde_json::from_str(metadata_raw.as_str())?;
return Ok(read_item);
} else {
let mut new_item = init.unwrap();
new_item.reset_id();
new_item.base_path = path;
// this would result in a deadlock i guess... for now
// new_item.commit_metadata()?;
Ok(new_item)
}
}
}
impl FsStoreItem {
pub fn reset_id(&mut self) {
self.item_hash.id = self.item_name.hash();
}
pub fn commit_metadata(&self) -> Result<()> {
self.write_attr(FS_STORE_METADATA_FILENAME.to_string(), self.clone())
}
pub fn write_image(&self, data: Vec<u8>) -> Result<()> {
self.write_attr_raw(FS_STORE_IMAGE_FILENAME.into(), data)
}
pub fn read_image(&self) -> Result<Vec<u8>> {
self.read_attr_raw(FS_STORE_IMAGE_FILENAME.into())
}
}
/*
* Filesystem store
*/
pub struct FsStore {
base_path: PathBuf,
fs_items: Vec<FsStoreItem>,
}
impl AbstractState for FsStore {
fn desc() -> String { FS_STORE_TYPE.to_string() }
fn base_path(&self) -> PathBuf { self.base_path.clone() }
fn state_path(&self) -> PathBuf { self.base_path.clone() }
fn empty_variant() -> FsStore {
FsStore {
base_path: PathBuf::new(),
fs_items: Vec::new()
}
}
fn open_internal(path: PathBuf, _init: Option<FsStore>) -> Result<FsStore> {
let mut found_fs_items = Vec::new();
for found_item in fs::read_dir(path.clone())? {
let found_item = found_item?;
let found_item_path = found_item.path();
let found_item_name = found_item.file_name().into_string();
if found_item_name.is_err() {
log::warn!("malformed item: {}", found_item.file_name().to_string_lossy());
continue;
}
let found_item_name = found_item_name.unwrap();
if found_item_path.is_dir() && found_item_name.starts_with(FS_STORE_ITEM_PREFIX) {
let new_item = FsStoreItem::open(path.clone().join(found_item_name), None)?;
found_fs_items.push(new_item);
}
}
Ok(FsStore {
base_path: path,
fs_items: found_fs_items
})
}
}
impl FsStore {
pub fn add_item(&mut self, item: &mut FsStoreItem) -> Result<()> {
item.reset_id();
let new_item_dirname = format!("{}{}", FS_STORE_ITEM_PREFIX, item.item_hash.id.clone());
let new_item_path = self.base_path().join(new_item_dirname);
log::debug!("adding item {:?} to {:?}", item, new_item_path);
self.lock()?;
let mut new_item = FsStoreItem::open(new_item_path, Some(item.clone()))?;
new_item.commit_metadata()?;
self.unlock()?;
self.fs_items.push(new_item.clone());
item.clone_from(new_item.borrow_mut());
Ok(())
}
}

View File

@ -1,15 +1,44 @@
use crate::state::{AbstractState, FsStore};
use crate::types::Result;
use std::path::PathBuf;
use std::str::FromStr;
const GLOBAL_STATE_TYPE: &str = "global_state";
const FS_STORE_PATH: &str = "root-filesystems";
pub struct GlobalState {
base_path: PathBuf,
fs_store: u64,
item_store: u64,
network_store: u64
pub fs_store: FsStore,
// network_state: u64,
// box_state: u64
}
impl GlobalState {
fn new(base_path: PathBuf) -> GlobalState {
GlobalState { base_path, fs_store: 0, item_store: 0, network_store: 0 }
impl AbstractState for GlobalState {
fn desc() -> String { GLOBAL_STATE_TYPE.to_string() }
fn empty_variant() -> GlobalState {
GlobalState {
base_path: PathBuf::new(),
fs_store: FsStore::empty_variant()
}
}
fn base_path(&self) -> PathBuf {
self.base_path.clone()
}
fn state_path(&self) -> PathBuf {
PathBuf::from_str("/").unwrap()
}
fn open_internal(path: PathBuf, init: Option<GlobalState>) -> Result<GlobalState> {
let fs_store_path = path.join(FS_STORE_PATH.to_string());
Ok(GlobalState {
base_path: path,
fs_store: FsStore::open(fs_store_path, init.map(|s| s.fs_store))?
})
}
}

View File

@ -1,6 +1,8 @@
mod global_state;
mod abstract_state;
pub mod fs_store;
mod global_state;
pub mod state_type;
pub use global_state::GlobalState;
pub use abstract_state::AbstractState;
pub use fs_store::{FsStore, FsStoreItem, FsStoreItemName};
pub use global_state::GlobalState;

View File

@ -1,11 +1,15 @@
use std::collections::HashSet;
use serde::{Serialize, Deserialize};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Hash, Clone, Debug)]
pub struct StateType { pub state_type: String }
pub struct StateType {
pub state_type: String,
}
impl Eq for StateType {}
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
pub struct StateTypeCol { pub state_types: HashSet<StateType> }
pub struct StateTypeCol {
pub state_types: HashSet<StateType>,
}
impl Eq for StateTypeCol {}

165
src/unix/loop_handler.rs Normal file
View File

@ -0,0 +1,165 @@
use crate::types::Result;
use crate::errors::{Error, Errno};
use syscalls::*;
use num_traits::FromPrimitive;
use std::fs;
use std::ops::Index;
use std::os::unix::prelude::IntoRawFd;
use std::path::PathBuf;
use std::cell::RefCell;
const LOOP_CTL_DEFAULT_PATH: &str = "/dev/loop-control";
const LOOP_CTL_GET_FREE: i64 = 0x4C82;
const LOOP_SET_FD: i64 = 0x4C00;
#[derive(Debug, Clone)]
pub struct LoopAssoc {
pub loop_no: i64,
pub loop_file: PathBuf,
pub loop_dev: PathBuf
}
#[derive(Debug, Clone)]
pub struct LoopHandler {
control: PathBuf,
associations: RefCell<Vec<LoopAssoc>>
}
impl LoopHandler {
pub fn new(control_path_arg: Option<PathBuf>) -> Result<Self> {
let control_path = control_path_arg.unwrap_or(PathBuf::from(LOOP_CTL_DEFAULT_PATH));
let control_loop = fs::OpenOptions::new()
.read(true)
.write(true)
.create(false)
.open(control_path.clone())
.map_err(|e| {
Error::LoopCtrlError(
format!("unable to open loop control: {}", e))
})?;
let control_loop_fd = control_loop.into_raw_fd();
let free_result = unsafe { syscall!(SYS_ioctl, control_loop_fd, LOOP_CTL_GET_FREE) };
if free_result.is_err() {
return Err(Error::LoopCtrlError(
format!("unable to allocate free loop: {:?}",
Errno::from_i64(free_result.err().unwrap()))));
}
Ok(LoopHandler{
control: control_path.clone(),
associations: RefCell::new(Vec::new())
})
}
pub fn loop_file(&self, filepath: PathBuf) -> Result<LoopAssoc> {
log::debug!("mounting {:?} as loop", filepath.clone());
let control_loop = fs::OpenOptions::new()
.read(true)
.write(true)
.create(false)
.open(self.control.clone())
.map_err(|e| {
Error::LoopCtrlError(
format!("unable to open loop control: {}", e))
})?;
log::debug!("allocating new loop device");
let control_loop_fd = control_loop.into_raw_fd();
let free_result = unsafe { syscall!(SYS_ioctl, control_loop_fd, LOOP_CTL_GET_FREE) };
if free_result.is_err() {
return Err(Error::LoopCtrlError(
format!("unable to allocate free loop: {:?}",
Errno::from_i64(free_result.err().unwrap()))));
}
let free_loop_dev_no = free_result.ok().unwrap();
let free_loop_dev_path = self.control.clone()
.parent()
.unwrap_or(PathBuf::from("/").as_path())
.join(PathBuf::from(format!("loop{}", free_loop_dev_no)));
log::debug!("opening loop device {:?}", free_loop_dev_path.clone());
let free_loop_dev = fs::OpenOptions::new()
.read(true)
.write(true)
.create(false)
.open(free_loop_dev_path.clone())
.map_err(|e| {
Error::LoopCtrlError(
format!("unable to open loop: {}", e))
})?;
let image_file = fs::OpenOptions::new()
.read(true)
.write(true)
.create(false)
.open(filepath.clone())
.map_err(|e| {
Error::LoopCtrlError(
format!("unable to open file image: {}", e))
})?;
let free_loop_fd = free_loop_dev.into_raw_fd();
let image_file_fd = image_file.into_raw_fd();
log::debug!("looping {:?} on {:?}", filepath.clone(), free_loop_dev_path.clone());
let loop_result = unsafe { syscall!(SYS_ioctl, free_loop_fd, LOOP_SET_FD, image_file_fd) };
if loop_result.is_err() {
return Err(Error::LoopCtrlError(
format!("unable to loop image: {:?}",
Errno::from_i64(loop_result.err().unwrap()))));
}
log::debug!("looping success: {:?} on {:?}", filepath.clone(), free_loop_dev_path.clone());
let new_loop_assoc = LoopAssoc {
loop_dev: free_loop_dev_path.clone(),
loop_file: filepath.clone(),
loop_no: free_loop_dev_no
};
self.associations.borrow_mut().push(new_loop_assoc.clone());
Ok(new_loop_assoc)
}
pub fn unloop_file(&self, filepath: PathBuf) -> Result<()> {
let associations = self.associations.borrow();
let associations_iter = associations.iter();
let found_loop_assoc = associations_iter
.filter(|assoc| assoc.loop_file == filepath.clone())
.collect::<Vec<&LoopAssoc>>();
let loop_assoc = found_loop_assoc.first();
if loop_assoc.is_none() {
return Err(Error::LoopDeviceError(
format!("loop path {:?} is not mounted or managed", filepath.clone())))
}
log::debug!("unlooping {:?} on {:?}", filepath.clone(),
loop_assoc.unwrap().loop_dev.clone());
let disassoc_result =
unsafe { syscall!(SYS_ioctl, loop_assoc.unwrap().loop_no) };
if disassoc_result.is_err() {
return Err(Error::LoopCtrlError(
format!("unable to disassociate loop dev: {:?}",
Errno::from_i64(disassoc_result.err().unwrap()))));
}
log::debug!("unlooping success: {:?} on {:?}",
filepath.clone(), loop_assoc.unwrap().loop_dev.clone());
// remove loop association
// self.associations.borrow_mut().
Ok(())
}
}

View File

@ -1,6 +1,7 @@
pub mod fds;
mod handler;
pub mod mounts;
pub mod loop_handler;
pub use handler::ProcHandler;
pub use mounts::MountHandler;

View File

@ -18,6 +18,7 @@ pub enum FsType {
Ext3,
Ext4,
Vfat,
Btrfs,
Tmpfs,
Proc,
Sysfs,
@ -39,6 +40,7 @@ impl FromStr for FsType {
"ext3" => FsType::Ext3,
"ext4" => FsType::Ext4,
"vfat" => FsType::Vfat,
"btrfs" => FsType::Btrfs,
"tmpfs" => FsType::Tmpfs,
"proc" => FsType::Proc,
"sysfs" => FsType::Sysfs,
@ -60,6 +62,7 @@ impl ToString for FsType {
FsType::Ext4 => "ext4",
FsType::Vfat => "vfat",
FsType::Tmpfs => "tmpfs",
FsType::Btrfs => "btrfs",
FsType::Proc => "proc",
FsType::Sysfs => "sysfs",
FsType::Devtmpfs => "devtmpfs",
@ -167,6 +170,18 @@ impl MountHandler {
Ok(mount_descriptors)
}
pub fn is_mounted(&self, path: String) -> Result<bool> {
let current_mounts = self.get_mounts()?;
Ok(current_mounts
.iter()
.find(|mount_desc| {
let source_path = mount_desc.source.clone();
let target_path = mount_desc.target.clone();
path == source_path || path == target_path
})
.is_some())
}
pub fn umount(&self, mount_desc: MountDesc, force: bool, lazy: bool) -> Result<()> {
cpointer_string!(mnt_target_cptr, mnt_target_cstring, mount_desc.target);