WIP added state + pulling docker + now working on FS creation for VMs
This commit is contained in:
parent
7de455702a
commit
790d094b67
26
Cargo.toml
26
Cargo.toml
|
@ -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"
|
||||
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
// ©_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(())
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
pub mod fs_factory;
|
||||
|
||||
pub mod tmpfs_desc;
|
||||
pub mod targz_fs_desc;
|
||||
pub mod btrfs_desc;
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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))?
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
pub mod fds;
|
||||
mod handler;
|
||||
pub mod mounts;
|
||||
pub mod loop_handler;
|
||||
|
||||
pub use handler::ProcHandler;
|
||||
pub use mounts::MountHandler;
|
|
@ -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);
|
||||
|
Loading…
Reference in New Issue