use std::{
convert::From,
fs::{create_dir, create_dir_all, read_dir, remove_dir, remove_file, rename, OpenOptions},
io::{prelude::*, ErrorKind},
path::{Path, PathBuf},
};
use nix::mount::{mount, umount, MsFlags};
use devicemapper::{DmDevice, LinearDev, LinearDevTargetParams, TargetLine};
use crate::{
engine::{
strat_engine::{
cmd::create_fs, dm::get_dm, serde_structs::FilesystemSave,
thinpool::filesystem::StratFilesystem,
},
types::{FilesystemUuid, Name, PoolUuid, StratisUuid},
},
stratis::StratisResult,
};
const RUN_DIR: &str = "/run/stratisd";
const FILESYSTEM_DIR: &str = "filesystems";
#[derive(Debug)]
pub struct MetadataVol {
dev: LinearDev,
mount_pt: PathBuf,
}
#[derive(Debug)]
struct MountedMDV<'a> {
mdv: &'a MetadataVol,
}
impl<'a> MountedMDV<'a> {
fn mount(mdv: &MetadataVol) -> StratisResult<MountedMDV> {
if let Err(err) = create_dir_all(&mdv.mount_pt) {
if err.kind() != ErrorKind::AlreadyExists {
return Err(From::from(err));
}
}
match mount(
Some(&mdv.dev.devnode()),
&mdv.mount_pt,
Some("xfs"),
MsFlags::empty(),
None as Option<&str>,
) {
Err(nix::Error::Sys(nix::errno::Errno::EBUSY)) => {
Ok(())
}
Err(err) => Err(err),
Ok(_) => Ok(()),
}?;
Ok(MountedMDV { mdv })
}
fn mount_pt(&self) -> &Path {
&self.mdv.mount_pt
}
}
impl<'a> Drop for MountedMDV<'a> {
fn drop(&mut self) {
if let Err(err) = umount(&self.mdv.mount_pt) {
warn!("Could not unmount MDV: {}", err);
} else if let Err(err) = remove_dir(&self.mdv.mount_pt) {
warn!("Could not remove MDV mount point: {}", err);
}
}
}
impl MetadataVol {
pub fn initialize(pool_uuid: PoolUuid, dev: LinearDev) -> StratisResult<MetadataVol> {
create_fs(&dev.devnode(), Some(StratisUuid::Pool(pool_uuid)), true)?;
MetadataVol::setup(pool_uuid, dev)
}
pub fn setup(pool_uuid: PoolUuid, dev: LinearDev) -> StratisResult<MetadataVol> {
let filename = format!(".mdv-{}", uuid_to_string!(pool_uuid));
let mount_pt: PathBuf = vec![RUN_DIR, &filename].iter().collect();
let mdv = MetadataVol { dev, mount_pt };
{
let mount = MountedMDV::mount(&mdv)?;
let filesystem_path = mount.mount_pt().join(FILESYSTEM_DIR);
if let Err(err) = create_dir(&filesystem_path) {
if err.kind() != ErrorKind::AlreadyExists {
return Err(From::from(err));
}
}
let _ = remove_temp_files(&filesystem_path)?;
}
Ok(mdv)
}
pub fn save_fs(
&self,
name: &Name,
uuid: FilesystemUuid,
fs: &StratFilesystem,
) -> StratisResult<()> {
let data = serde_json::to_string(&fs.record(name, uuid))?;
let path = self
.mount_pt
.join(FILESYSTEM_DIR)
.join(uuid_to_string!(uuid))
.with_extension("json");
let temp_path = path.with_extension("temp");
let _mount = MountedMDV::mount(self)?;
{
let mut f = OpenOptions::new()
.write(true)
.create(true)
.open(&temp_path)?;
f.write_all(data.as_bytes())?;
f.sync_all()?;
}
rename(temp_path, path)?;
Ok(())
}
pub fn rm_fs(&self, fs_uuid: FilesystemUuid) -> StratisResult<()> {
let fs_path = self
.mount_pt
.join(FILESYSTEM_DIR)
.join(uuid_to_string!(fs_uuid))
.with_extension("json");
let _mount = MountedMDV::mount(self)?;
if let Err(err) = remove_file(fs_path) {
if err.kind() != ErrorKind::NotFound {
return Err(From::from(err));
}
}
Ok(())
}
pub fn filesystems(&self) -> StratisResult<Vec<FilesystemSave>> {
let mut filesystems = Vec::new();
let mount = MountedMDV::mount(self)?;
for dir_e in read_dir(mount.mount_pt().join(FILESYSTEM_DIR))? {
let dir_e = dir_e?;
if dir_e.path().ends_with(".temp") {
continue;
}
let mut f = OpenOptions::new().read(true).open(&dir_e.path())?;
let mut data = Vec::new();
f.read_to_end(&mut data)?;
filesystems.push(serde_json::from_slice(&data)?);
}
Ok(filesystems)
}
pub fn teardown(&mut self) -> StratisResult<()> {
self.dev.teardown(get_dm())?;
Ok(())
}
pub fn suspend(&mut self) -> StratisResult<()> {
self.dev.suspend(get_dm(), true)?;
Ok(())
}
pub fn resume(&mut self) -> StratisResult<()> {
self.dev.resume(get_dm())?;
Ok(())
}
pub fn device(&self) -> &LinearDev {
&self.dev
}
pub fn set_table(
&mut self,
table: Vec<TargetLine<LinearDevTargetParams>>,
) -> StratisResult<()> {
self.dev.set_table(get_dm(), table)?;
Ok(())
}
}
fn remove_temp_files(dir: &Path) -> StratisResult<(u64, Vec<PathBuf>)> {
let mut found = 0;
let mut failed = Vec::new();
for path in read_dir(dir)?
.filter_map(|e| e.ok()) .map(|e| e.path())
.filter(|p| p.ends_with(".temp"))
{
found += 1;
remove_file(&path).unwrap_or_else(|_| failed.push(path));
}
Ok((found, failed))
}