use std::cmp::Ordering;
use std::fmt;
use std::fs::File;
use std::io;
use std::mem::MaybeUninit;
use std::ops::Index;
use std::ops::IndexMut;
use std::path::Path;
use crate::core::cache::Cache;
use crate::core::device::Source;
use crate::core::device::Tag;
use crate::core::entries::FsTabEntry;
use crate::core::errors::FsTabError;
use crate::core::errors::FsTabIterError;
use crate::core::iter::Direction;
use crate::core::iter::FsTabIter;
use crate::core::iter::FsTabIterMut;
use crate::core::iter::GenIterator;
use crate::owning_ref_from_ptr;
use crate::tables::GcItem;
use crate::tables::MountOption;
use crate::tables::ParserFlow;
use crate::ffi_utils;
#[derive(Debug)]
pub struct FsTab {
pub(crate) inner: *mut libmount::libmnt_table,
pub(crate) gc: Vec<GcItem>,
}
impl Drop for FsTab {
fn drop(&mut self) {
log::debug!("::drop deallocating `FsTab` instance");
unsafe { libmount::mnt_unref_table(self.inner) };
self.collect_garbage();
}
}
impl AsRef<FsTab> for FsTab {
#[inline]
fn as_ref(&self) -> &FsTab {
self
}
}
impl Index<usize> for FsTab {
type Output = FsTabEntry;
fn index(&self, index: usize) -> &Self::Output {
log::debug!("FsTab::index getting item at index: {:?}", index);
#[cold]
#[inline(never)]
#[track_caller]
fn indexing_failed() -> ! {
panic!("Index out of bounds");
}
let mut iter = FsTabIter::new(self).unwrap();
match iter.nth(index) {
Some(item) => item,
None => indexing_failed(),
}
}
}
impl IndexMut<usize> for FsTab {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
log::debug!("FsTab::index getting item at index: {:?}", index);
#[cold]
#[inline(never)]
#[track_caller]
fn indexing_failed() -> ! {
panic!("Index out of bounds");
}
let mut iter = FsTabIterMut::new(self).unwrap();
match iter.nth(index) {
Some(item) => item,
None => indexing_failed(),
}
}
}
impl<'a> IntoIterator for &'a FsTab {
type Item = &'a FsTabEntry;
type IntoIter = FsTabIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a> IntoIterator for &'a mut FsTab {
type Item = &'a mut FsTabEntry;
type IntoIter = FsTabIterMut<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
impl FsTab {
#[doc(hidden)]
#[allow(dead_code)]
pub(crate) fn incr_ref_counter(&mut self) {
unsafe { libmount::mnt_ref_table(self.inner) }
}
#[doc(hidden)]
#[allow(dead_code)]
pub(crate) fn decr_ref_counter(&mut self) {
unsafe { libmount::mnt_unref_table(self.inner) }
}
#[doc(hidden)]
pub(crate) fn from_ptr(ptr: *mut libmount::libmnt_table) -> FsTab {
Self {
inner: ptr,
gc: vec![],
}
}
#[doc(hidden)]
#[allow(dead_code)]
pub(crate) fn borrow_ptr(ptr: *mut libmount::libmnt_table) -> FsTab {
let mut table = Self::from_ptr(ptr);
table.incr_ref_counter();
table
}
pub fn new() -> Result<FsTab, FsTabError> {
log::debug!("FsTab::new creating a new `FsTab` instance");
let mut ptr = MaybeUninit::<*mut libmount::libmnt_table>::zeroed();
unsafe { ptr.write(libmount::mnt_new_table()) };
match unsafe { ptr.assume_init() } {
ptr if ptr.is_null() => {
let err_msg = "failed to create a new `FsTab`".to_owned();
log::debug!(
"FsTab::new {err_msg}. libmount::mnt_new_table returned a NULL pointer"
);
Err(FsTabError::Creation(err_msg))
}
ptr => {
log::debug!("FsTab::new created a new `FsTab` instance");
let table = Self::from_ptr(ptr);
Ok(table)
}
}
}
pub fn new_from_file<T>(file: T) -> Result<FsTab, FsTabError>
where
T: AsRef<Path>,
{
let file = file.as_ref();
let file_cstr = ffi_utils::as_ref_path_to_c_string(file)?;
log::debug!(
"FsTab::new_from_file creating a new `FsTab` from file {:?}",
file
);
let mut ptr = MaybeUninit::<*mut libmount::libmnt_table>::zeroed();
unsafe { ptr.write(libmount::mnt_new_table_from_file(file_cstr.as_ptr())) };
match unsafe { ptr.assume_init() } {
ptr if ptr.is_null() => {
let err_msg = format!("failed to create a new `FsTab` from file {:?}", file);
log::debug!(
"FsTab::new_from_file {}. libmount::mnt_new_table_from_file returned a NULL pointer",
err_msg
);
Err(FsTabError::Creation(err_msg))
}
ptr => {
log::debug!("FsTab::new_from_file created a new `FsTab` instance");
let table = Self::from_ptr(ptr);
Ok(table)
}
}
}
pub fn new_from_directory<T>(directory: T) -> Result<FsTab, FsTabError>
where
T: AsRef<Path>,
{
let dir = directory.as_ref();
let dir_cstr = ffi_utils::as_ref_path_to_c_string(dir)?;
log::debug!(
"FsTab::new_from_directory creating a new `FsTab` from files in {:?}",
dir
);
let mut ptr = MaybeUninit::<*mut libmount::libmnt_table>::zeroed();
unsafe { ptr.write(libmount::mnt_new_table_from_dir(dir_cstr.as_ptr())) };
match unsafe { ptr.assume_init() } {
ptr if ptr.is_null() => {
let err_msg = format!("failed to create a new `FsTab` from files in {:?}", dir);
log::debug!(
"FsTab::new_from_directory {}. libmount::mnt_new_table_from_dir returned a NULL pointer",
err_msg
);
Err(FsTabError::Creation(err_msg))
}
ptr => {
log::debug!("FsTab::new_from_directory created a new `FsTab` instance");
let table = Self::from_ptr(ptr);
Ok(table)
}
}
}
pub fn cache(&self) -> Option<&Cache> {
log::debug!("FsTab::cache getting associated path and tag cache");
let mut ptr = MaybeUninit::<*mut libmount::libmnt_cache>::zeroed();
unsafe { ptr.write(libmount::mnt_table_get_cache(self.inner)) };
match unsafe { ptr.assume_init() } {
ptr if ptr.is_null() => {
log::debug!("FsTab::cache failed to get associated path and tag cache. libmount::mnt_table_get_cache returned a NULL pointer");
None
}
ptr => {
log::debug!("FsTab::cache got associated path and tag cache");
let cache = owning_ref_from_ptr!(self, Cache, ptr);
Some(cache)
}
}
}
pub fn first(&self) -> Option<&FsTabEntry> {
log::debug!("FsTab::first getting reference to first table entry");
let mut ptr = MaybeUninit::<*mut libmount::libmnt_fs>::zeroed();
let result = unsafe { libmount::mnt_table_first_fs(self.inner, ptr.as_mut_ptr()) };
match result {
0 => {
log::debug!("FsTab::first got reference to first table entry");
let ptr = unsafe { ptr.assume_init() };
let entry = owning_ref_from_ptr!(self, FsTabEntry, ptr);
Some(entry)
}
code => {
log::debug!( "FsTab::first failed to get reference to first table entry. libmount::mnt_table_first_fs returned error code: {code:?}");
None
}
}
}
pub fn last(&self) -> Option<&FsTabEntry> {
log::debug!("FsTab::last getting reference to last table entry");
let mut ptr = MaybeUninit::<*mut libmount::libmnt_fs>::zeroed();
let result = unsafe { libmount::mnt_table_last_fs(self.inner, ptr.as_mut_ptr()) };
match result {
0 => {
log::debug!("FsTab::last got reference to last table entry");
let ptr = unsafe { ptr.assume_init() };
let entry = owning_ref_from_ptr!(self, FsTabEntry, ptr);
Some(entry)
}
code => {
log::debug!( "FsTab::last failed to get reference to last table entry. libmount::mnt_table_last_fs returned error code: {code:?}");
None
}
}
}
pub fn position(&self, entry: &FsTabEntry) -> Option<usize> {
log::debug!("FsTab::position searching for an entry in the table");
let result = unsafe { libmount::mnt_table_find_fs(self.inner, entry.inner) };
match result {
index if index > 0 => {
log::debug!(
"FsTab::position mount table contains entry at index: {:?}",
index
);
Some(index as usize)
}
code => {
log::debug!( "FsTab::position no matching entry in table: libmount::mnt_table_find_fs returned error code: {code:?}");
None
}
}
}
pub fn len(&self) -> usize {
let len = unsafe { libmount::mnt_table_get_nents(self.inner) };
log::debug!("FsTab::len value: {:?}", len);
len as usize
}
pub fn get(&self, index: usize) -> Option<&FsTabEntry> {
log::debug!(
"FsTab::get_mut getting reference of item at index: {:?}",
index
);
FsTabIter::new(self)
.ok()
.and_then(|mut iter| iter.nth(index))
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut FsTabEntry> {
log::debug!(
"FsTab::get_mut getting mutable reference of item at index: {:?}",
index
);
FsTabIterMut::new(self)
.ok()
.and_then(|mut iter| iter.nth(index))
}
#[doc(hidden)]
fn find_first_entry<'a, P>(
table: &mut Self,
iterator: *mut libmount::libmnt_iter,
predicate: P,
) -> Option<&'a FsTabEntry>
where
P: FnMut(&FsTabEntry) -> bool,
{
#[doc(hidden)]
unsafe extern "C" fn callback<P>(
entry_ptr: *mut libmount::libmnt_fs,
predicate_fn_ptr: *mut libc::c_void,
) -> libc::c_int
where
P: FnMut(&FsTabEntry) -> bool,
{
let entry = FsTabEntry::borrow_ptr(entry_ptr);
let predicate_fn = &mut *(predicate_fn_ptr as *mut P);
match predicate_fn(&entry) {
true => 1,
false => 0,
}
}
let data = Box::into_raw(Box::new(predicate));
let mut ptr = MaybeUninit::<*mut libmount::libmnt_fs>::zeroed();
let result = unsafe {
libmount::mnt_table_find_next_fs(
table.inner,
iterator,
Some(callback::<P>),
data as *mut _,
ptr.as_mut_ptr(),
)
};
match result {
0 => {
let _predicate = unsafe { Box::from_raw(data) };
log::debug!("FsTab::find_first_entry found first `FsTabEntry` matching predicate");
let entry_ptr = unsafe { ptr.assume_init() };
let entry = owning_ref_from_ptr!(table, FsTabEntry, entry_ptr);
Some(entry)
}
code => {
let _predicate = unsafe { Box::from_raw(data) };
let err_msg = "failed to find `FsTabEntry` matching predicate".to_owned();
log::debug!( "FsTab::find_first_entry {err_msg}. libmount::mnt_table_find_next_fs returned error code: {code:?}");
None
}
}
}
pub fn find_first<P>(&mut self, predicate: P) -> Option<&FsTabEntry>
where
P: FnMut(&FsTabEntry) -> bool,
{
log::debug!( "FsTab::find_first finding first table entry matching predicate while iterating Forward");
GenIterator::new(Direction::Forward)
.ok()
.and_then(|iterator| FsTab::find_first_entry(self, iterator.inner, predicate))
}
pub fn find_back_first<P>(&mut self, predicate: P) -> Option<&FsTabEntry>
where
P: FnMut(&FsTabEntry) -> bool,
{
log::debug!( "FsTab::find_back_first finding first table entry matching predicate while iterating Backward");
GenIterator::new(Direction::Backward)
.ok()
.and_then(|iterator| FsTab::find_first_entry(self, iterator.inner, predicate))
}
#[doc(hidden)]
fn lookup_source<'a>(
table: &mut Self,
direction: Direction,
source: &Source,
) -> Option<&'a FsTabEntry> {
let source_cstr = ffi_utils::as_ref_str_to_c_string(source.to_string()).ok()?;
let source_ptr = if source.is_pseudo_fs() {
std::ptr::null()
} else {
source_cstr.as_ptr()
};
log::debug!(
"FsTab::lookup_source searching {:?} for entry matching source {:?}",
direction,
source
);
let mut ptr = MaybeUninit::<*mut libmount::libmnt_fs>::zeroed();
unsafe {
ptr.write(libmount::mnt_table_find_source(
table.inner,
source_ptr,
direction as i32,
))
};
match unsafe { ptr.assume_init() } {
ptr if ptr.is_null() => {
let err_msg = format!(
"failed to find entry matching source {:?} while searching {:?}",
source, direction
);
log::debug!( "FsTab::lookup_source {err_msg}. libmount::mnt_table_find_source returned a NULL pointer");
None
}
ptr => {
log::debug!(
"FsTab::lookup_source found entry matching source {:?} while searching {:?}",
source,
direction
);
let entry = owning_ref_from_ptr!(table, FsTabEntry, ptr);
Some(entry)
}
}
}
pub fn find_source(&mut self, source: &Source) -> Option<&FsTabEntry> {
let direction = Direction::Forward;
log::debug!(
"FsTab::find_source searching {:?} for the first entry with a source matching {:?}",
direction,
source
);
Self::lookup_source(self, direction, source)
}
pub fn find_back_source(&mut self, source: &Source) -> Option<&FsTabEntry> {
let direction = Direction::Backward;
log::debug!(
"FsTab::find_back_source searching {:?} for the first entry with a source matching {:?}",
direction,
source
);
Self::lookup_source(self, direction, source)
}
#[doc(hidden)]
fn lookup_source_path<'a>(
table: &mut Self,
direction: Direction,
path: &Path,
) -> Option<&'a FsTabEntry> {
let path_cstr = ffi_utils::as_ref_path_to_c_string(path).ok()?;
let path_ptr = if path_cstr.is_empty() {
std::ptr::null()
} else {
path_cstr.as_ptr()
};
log::debug!(
"FsTab::lookup_source_path searching {:?} for entry matching source path {:?}",
direction,
path
);
let mut ptr = MaybeUninit::<*mut libmount::libmnt_fs>::zeroed();
unsafe {
ptr.write(libmount::mnt_table_find_srcpath(
table.inner,
path_ptr,
direction as i32,
))
};
match unsafe { ptr.assume_init() } {
ptr if ptr.is_null() => {
let err_msg = format!(
"failed to find entry matching source path {:?} while searching {:?}",
path, direction
);
log::debug!( "FsTab::lookup_source_path {err_msg}. libmount::mnt_table_find_srcpath returned a NULL pointer");
None
}
ptr => {
log::debug!(
"FsTab::lookup_source_path found entry matching source path {:?} while searching {:?}",
path,
direction
);
let entry = owning_ref_from_ptr!(table, FsTabEntry, ptr);
Some(entry)
}
}
}
pub fn find_source_path<T>(&mut self, path: T) -> Option<&FsTabEntry>
where
T: AsRef<Path>,
{
let path = path.as_ref();
let direction = Direction::Forward;
log::debug!(
"FsTab::find_source_path searching {:?} for the first entry with a source matching {:?}",
direction,
path
);
Self::lookup_source_path(self, direction, path)
}
pub fn find_back_source_path<T>(&mut self, path: T) -> Option<&FsTabEntry>
where
T: AsRef<Path>,
{
let path = path.as_ref();
let direction = Direction::Backward;
log::debug!(
"FsTab::find_back_source_path searching {:?} for the first entry with a source matching {:?}",
direction,
path
);
Self::lookup_source_path(self, direction, path)
}
pub fn intro_comments(&self) -> Option<&str> {
log::debug!("FsTab::intro_comments getting intro comment");
let mut ptr = MaybeUninit::<*const libc::c_char>::zeroed();
unsafe { ptr.write(libmount::mnt_table_get_intro_comment(self.inner)) };
match unsafe { ptr.assume_init() } {
ptr if ptr.is_null() => {
log::debug!("FsTab::intro_comments found no intro comment. libmount::mnt_table_get_intro_comment returned a NULL pointer");
None
}
ptr => {
let comment = ffi_utils::const_char_array_to_str_ref(ptr).ok();
log::debug!("FsTab::intro_comments value: {:?}", comment);
comment
}
}
}
pub fn trailing_comments(&self) -> Option<&str> {
log::debug!("FsTab::trailing_comments getting trailing comment");
let mut ptr = MaybeUninit::<*const libc::c_char>::zeroed();
unsafe { ptr.write(libmount::mnt_table_get_trailing_comment(self.inner)) };
match unsafe { ptr.assume_init() } {
ptr if ptr.is_null() => {
log::debug!("FsTab::trailing_comments found no trailing comment. libmount::mnt_table_get_trailing_comment returned a NULL pointer");
None
}
ptr => {
let comment = ffi_utils::const_char_array_to_str_ref(ptr).ok();
log::debug!("FsTab::trailing_comments value: {:?}", comment);
comment
}
}
}
#[doc(hidden)]
fn lookup_tag<'a>(table: &mut Self, direction: Direction, tag: &Tag) -> Option<&'a FsTabEntry> {
log::debug!(
"FsTab::lookup_tag searching {:?} for entry matching tag {:?}",
direction,
tag
);
let name_cstr = ffi_utils::as_ref_str_to_c_string(tag.name().to_string()).ok()?;
let value_cstr = ffi_utils::as_ref_str_to_c_string(tag.value()).ok()?;
let mut ptr = MaybeUninit::<*mut libmount::libmnt_fs>::zeroed();
unsafe {
ptr.write(libmount::mnt_table_find_tag(
table.inner,
name_cstr.as_ptr(),
value_cstr.as_ptr(),
direction as i32,
))
};
match unsafe { ptr.assume_init() } {
ptr if ptr.is_null() => {
let err_msg = format!(
"failed to find entry matching tag {:?} while searching {:?}",
tag, direction
);
log::debug!(
"FsTab::lookup_tag {}. libmount::mnt_table_find_tag returned a NULL pointer",
err_msg
);
None
}
ptr => {
log::debug!(
"FsTab::lookup_tag found entry matching tag {:?} while searching {:?}",
tag,
direction
);
let boxed = Box::new(ptr);
let (boxed_ptr, entry) = unsafe { FsTabEntry::ref_from_boxed_ptr(boxed) };
table.gc.push(boxed_ptr.into());
Some(entry)
}
}
}
pub fn find_source_tag(&mut self, tag: &Tag) -> Option<&FsTabEntry> {
let direction = Direction::Forward;
log::debug!(
"FsTab::find_source_tag searching {:?} for the first entry a tag matching {:?}",
direction,
tag
);
Self::lookup_tag(self, direction, tag)
}
pub fn find_back_source_tag(&mut self, tag: &Tag) -> Option<&FsTabEntry> {
let direction = Direction::Backward;
log::debug!(
"FsTab::find_back_source_tag searching {:?} for the first entry a tag matching {:?}",
direction,
tag
);
Self::lookup_tag(self, direction, tag)
}
#[doc(hidden)]
fn lookup_target<'a>(
table: &mut Self,
direction: Direction,
path: &Path,
) -> Option<&'a FsTabEntry> {
let path_cstr = ffi_utils::as_ref_path_to_c_string(path).ok()?;
log::debug!(
"FsTab::lookup_target searching {:?} for entry matching target {:?}",
direction,
path
);
let mut ptr = MaybeUninit::<*mut libmount::libmnt_fs>::zeroed();
unsafe {
ptr.write(libmount::mnt_table_find_target(
table.inner,
path_cstr.as_ptr(),
direction as i32,
))
};
match unsafe { ptr.assume_init() } {
ptr if ptr.is_null() => {
let err_msg = format!(
"failed to find entry matching target {:?} while searching {:?}",
path, direction
);
log::debug!( "FsTab::lookup_target {err_msg}. libmount::mnt_table_find_target returned a NULL pointer");
None
}
ptr => {
log::debug!(
"FsTab::lookup_target found entry matching target {:?} while searching {:?}",
path,
direction
);
let entry = owning_ref_from_ptr!(table, FsTabEntry, ptr);
Some(entry)
}
}
}
pub fn find_target<T>(&mut self, path: T) -> Option<&FsTabEntry>
where
T: AsRef<Path>,
{
let path = path.as_ref();
let direction = Direction::Forward;
log::debug!(
"FsTab::find_target searching {:?} for the first entry with a target matching {:?}",
direction,
path
);
Self::lookup_target(self, direction, path)
}
pub fn find_back_target<T>(&mut self, path: T) -> Option<&FsTabEntry>
where
T: AsRef<Path>,
{
let path = path.as_ref();
let direction = Direction::Backward;
log::debug!(
"FsTab::find_back_target searching {:?} for the first entry with a target matching {:?}",
direction,
path
);
Self::lookup_target(self, direction, path)
}
#[doc(hidden)]
fn lookup_target_with_options<'a>(
table: &mut Self,
direction: Direction,
path: &Path,
option_name: &str,
option_value: Option<&str>,
) -> Option<&'a FsTabEntry> {
let option_value = option_value.map_or_else(String::new, |value| value.to_owned());
let path_cstr = ffi_utils::as_ref_path_to_c_string(path).ok()?;
let opt_name_cstr = ffi_utils::as_ref_str_to_c_string(option_name).ok()?;
let opt_value_cstr = ffi_utils::as_ref_str_to_c_string(&option_value).ok()?;
let opt_value_ptr = if opt_value_cstr.is_empty() {
std::ptr::null()
} else {
opt_value_cstr.as_ptr()
};
let opt_value = if option_value.is_empty() {
option_value
} else {
format!(" with value {:?}", option_value)
};
log::debug!(
"FsTab::lookup_target_with_options searching {:?} for entry matching the combination of path {:?} and option {:?}{}",
direction,
path,
option_name,
opt_value
);
let mut ptr = MaybeUninit::<*mut libmount::libmnt_fs>::zeroed();
unsafe {
ptr.write(libmount::mnt_table_find_target_with_option(
table.inner,
path_cstr.as_ptr(),
opt_name_cstr.as_ptr(),
opt_value_ptr,
direction as i32,
))
};
match unsafe { ptr.assume_init() } {
ptr if ptr.is_null() => {
let err_msg = format!("found no entry matching the combination of path {:?} and option {:?}{} while searching {:?}", path, option_name, opt_value, direction );
log::debug!( "FsTab::lookup_target_with_options {err_msg}. libmount::mnt_table_find_target_with_option returned a NULL pointer");
None
}
ptr => {
log::debug!(
"FsTab::lookup_target_with_options found entry matching the combination of path {:?} and option {:?}{}",
path,
option_name,
opt_value
);
let entry = owning_ref_from_ptr!(table, FsTabEntry, ptr);
Some(entry)
}
}
}
pub fn find_target_with_option<P, T>(&mut self, path: P, option_name: T) -> Option<&FsTabEntry>
where
P: AsRef<Path>,
T: AsRef<str>,
{
let path = path.as_ref();
let option_name = option_name.as_ref();
let direction = Direction::Forward;
log::debug!( "FsTab::find_target_with_option searching {:?} for entry matching the combination of path {:?} and option {:?}", direction, path, option_name);
Self::lookup_target_with_options(self, direction, path, option_name, None)
}
pub fn find_back_target_with_option<P, T>(
&mut self,
path: P,
option_name: T,
) -> Option<&FsTabEntry>
where
P: AsRef<Path>,
T: AsRef<str>,
{
let path = path.as_ref();
let option_name = option_name.as_ref();
let direction = Direction::Backward;
log::debug!( "FsTab::find_back_target_with_option searching {:?} for entry matching the combination of path {:?} and option {:?}", direction, path, option_name);
Self::lookup_target_with_options(self, direction, path, option_name, None)
}
pub fn find_target_with_exact_option<P, T>(
&mut self,
path: P,
option: &MountOption,
) -> Option<&FsTabEntry>
where
P: AsRef<Path>,
{
let path = path.as_ref();
let direction = Direction::Forward;
log::debug!( "FsTab::find_target_with_option searching {:?} for entry matching the combination of path {:?} and option {:?}", direction, path, option);
Self::lookup_target_with_options(self, direction, path, option.name(), option.value())
}
pub fn find_back_target_with_exact_option<P, T>(
&mut self,
path: P,
option: &MountOption,
) -> Option<&FsTabEntry>
where
P: AsRef<Path>,
{
let path = path.as_ref();
let direction = Direction::Backward;
log::debug!( "FsTab::find_back_target_with_option searching {:?} for entry matching the combination of path {:?} and option {:?}", direction, path, option);
Self::lookup_target_with_options(self, direction, path, option.name(), option.value())
}
#[doc(hidden)]
fn lookup_pair<'a>(
table: &mut Self,
direction: Direction,
source: &Source,
target: &Path,
) -> Option<&'a FsTabEntry> {
let source_cstr = ffi_utils::as_ref_str_to_c_string(source.to_string()).ok()?;
let target_cstr = ffi_utils::as_ref_path_to_c_string(target).ok()?;
log::debug!(
"FsTab::lookup_pair searching {:?} for entry matching source/target pair {:?} / {:?}",
direction,
source,
target
);
let mut ptr = MaybeUninit::<*mut libmount::libmnt_fs>::zeroed();
unsafe {
ptr.write(libmount::mnt_table_find_pair(
table.inner,
source_cstr.as_ptr(),
target_cstr.as_ptr(),
direction as i32,
))
};
match unsafe { ptr.assume_init() } {
ptr if ptr.is_null() => {
let err_msg = format!(
"found no entry with source/target pair {:?} / {:?} while searching {:?}",
source, target, direction,
);
log::debug!(
"FsTab::lookup_pair {}. libmount::mnt_table_find_pair returned a NULL pointer",
err_msg
);
None
}
ptr => {
log::debug!(
"FsTab::lookup_pair found entry matching source/target pair {:?} / {:?}",
source,
target
);
let entry = owning_ref_from_ptr!(table, FsTabEntry, ptr);
Some(entry)
}
}
}
pub fn find_pair<T>(&mut self, source: &Source, target: T) -> Option<&FsTabEntry>
where
T: AsRef<Path>,
{
let target = target.as_ref();
log::debug!( "FsTab::find_pair searching table from top to bottom for entry with source/target pair {:?} / {:?}", source, target);
Self::lookup_pair(self, Direction::Forward, source, target)
}
pub fn find_back_pair<T>(&mut self, source: &Source, target: T) -> Option<&FsTabEntry>
where
T: AsRef<Path>,
{
let target = target.as_ref();
log::debug!( "FsTab::find_back_pair searching table from bottom to top for entry with source/target pair {:?} / {:?}", source, target);
Self::lookup_pair(self, Direction::Backward, source, target)
}
pub fn iter(&self) -> FsTabIter<'_> {
log::debug!("FsTab::iter creating a new `FsTabIter`");
FsTabIter::new(self).unwrap()
}
pub fn try_iter(&self) -> Result<FsTabIter<'_>, FsTabIterError> {
log::debug!("FsTab::try_iter creating a new `FsTabIter`");
FsTabIter::new(self)
}
pub fn iter_mut(&mut self) -> FsTabIterMut<'_> {
log::debug!("FsTab::iter_mut creating a new `FsTabIterMut`");
FsTabIterMut::new(self).unwrap()
}
pub fn try_iter_mut(&mut self) -> Result<FsTabIterMut<'_>, FsTabIterError> {
log::debug!("FsTab::try_iter_mut creating a new `FsTabIterMut`");
FsTabIterMut::new(self)
}
pub fn set_parser_error_handler<F>(&mut self, err_handler: F) -> Result<(), FsTabError>
where
F: Fn(&str, usize) -> ParserFlow,
{
log::debug!("FsTab::set_parser_error_handler setting up parser error handler");
#[doc(hidden)]
unsafe extern "C" fn parser_callback<F>(
table: *mut libmount::libmnt_table,
file_name: *const libc::c_char,
line: libc::c_int,
) -> libc::c_int
where
F: Fn(&str, usize) -> ParserFlow,
{
let file_name = ffi_utils::const_char_array_to_str_ref(file_name)
.ok()
.unwrap_or("");
let mut callback_ptr = MaybeUninit::<*mut libc::c_void>::zeroed();
unsafe {
callback_ptr.write(libmount::mnt_table_get_userdata(table));
}
let callback_ptr = unsafe { callback_ptr.assume_init() };
let handler = &mut *(callback_ptr as *mut F);
handler(file_name, line as usize) as i32
}
let user_data = Box::into_raw(Box::new(err_handler));
let result = unsafe { libmount::mnt_table_set_userdata(self.inner, user_data as *mut _) };
match result {
0 => {
let result = unsafe {
libmount::mnt_table_set_parser_errcb(self.inner, Some(parser_callback::<F>))
};
match result {
0 => {
log::debug!("FsTab::set_parser_error_handler set up parser error handler");
Ok(())
}
code => {
let err_msg = "failed to set parser syntax error handler".to_owned();
log::debug!( "FsTab::set_parser_error_handler {err_msg}. libmount::mnt_table_set_parser_errcb returned error code: {code:?}");
let _ = unsafe { Box::from_raw(user_data) };
Err(FsTabError::Config(err_msg))
}
}
}
code => {
let err_msg = "failed to set error handler as userdata".to_owned();
log::debug!( "FsTab::set_parser_error_handler {err_msg}. libmount::mnt_table_set_userdata returned error code: {code:?}");
let _ = unsafe { Box::from_raw(user_data) };
Err(FsTabError::Config(err_msg))
}
}
}
pub fn set_cache(&mut self, cache: Cache) -> Result<(), FsTabError> {
log::debug!("FsTab::set_cache setting up a cache of paths and tags");
unsafe {
libmount::mnt_ref_cache(cache.inner);
}
let result = unsafe { libmount::mnt_table_set_cache(self.inner, cache.inner) };
match result {
0 => {
log::debug!("FsTab::set_cache set up a cache of paths and tags");
Ok(())
}
code => {
let err_msg = "failed to set up a cache of paths and tags".to_owned();
log::debug!( "FsTab::set_cache {err_msg}. libmount::mnt_table_set_cache returned error code: {code:?}");
Err(FsTabError::Config(err_msg))
}
}
}
pub fn set_intro_comments<T>(&mut self, comment: T) -> Result<(), FsTabError>
where
T: AsRef<str>,
{
let comment = comment.as_ref();
let comment_cstr = ffi_utils::as_ref_str_to_c_string(comment)?;
log::debug!(
"FsTab::set_intro_comments setting intro comment to {:?}",
comment
);
let result =
unsafe { libmount::mnt_table_set_intro_comment(self.inner, comment_cstr.as_ptr()) };
match result {
0 => {
log::debug!(
"FsTab::set_intro_comments set intro comment to {:?}",
comment
);
Ok(())
}
code => {
let err_msg = format!("failed to set intro comment to {:?}", comment);
log::debug!("FsTab::set_intro_comments {}. libmount::mnt_table_set_intro_comment returned error code: {:?}", err_msg, code);
Err(FsTabError::Config(err_msg))
}
}
}
pub fn set_trailing_comments<T>(&mut self, comment: T) -> Result<(), FsTabError>
where
T: AsRef<str>,
{
let comment = comment.as_ref();
let comment_cstr = ffi_utils::as_ref_str_to_c_string(comment)?;
log::debug!(
"FsTab::set_trailing_comments setting trailing comment to {:?}",
comment
);
let result =
unsafe { libmount::mnt_table_set_trailing_comment(self.inner, comment_cstr.as_ptr()) };
match result {
0 => {
log::debug!(
"FsTab::set_trailing_comments set trailing comment to {:?}",
comment
);
Ok(())
}
code => {
let err_msg = format!("failed to set trailing comment to {:?}", comment);
log::debug!("FsTab::set_trailing_comments {}. libmount::mnt_table_set_trailing_comment returned error code: {:?}", err_msg, code);
Err(FsTabError::Config(err_msg))
}
}
}
fn collect_garbage(&mut self) {
while let Some(gc_item) = self.gc.pop() {
gc_item.destroy();
}
}
fn enable_comments(ptr: *mut libmount::libmnt_table, enable: bool) {
let op = if enable { 1 } else { 0 };
unsafe { libmount::mnt_table_enable_comments(ptr, op) }
}
pub fn import_with_comments(&mut self) {
log::debug!("FsTab::import_with_comments enabling comment parsing");
Self::enable_comments(self.inner, true)
}
pub fn import_without_comments(&mut self) {
log::debug!("FsTab::import_without_comments disabling comment parsing");
Self::enable_comments(self.inner, false)
}
pub fn import_directory<T>(&mut self, dir_path: T) -> Result<(), FsTabError>
where
T: AsRef<Path>,
{
let dir_path = dir_path.as_ref();
let dir_path_cstr = ffi_utils::as_ref_path_to_c_string(dir_path)?;
log::debug!(
"FsTab::import_dir importing table entries from files in {:?}",
dir_path
);
let result = unsafe { libmount::mnt_table_parse_dir(self.inner, dir_path_cstr.as_ptr()) };
match result {
0 => {
log::debug!(
"FsTab::import_directory imported table entries from files in {:?}",
dir_path
);
Ok(())
}
code => {
let err_msg = format!(
"failed to import table entries from files in {:?}",
dir_path
);
log::debug!("FsTab::import_directory {}. libmount::mnt_table_parse_dir returned error code: {:?}", err_msg, code);
Err(FsTabError::Import(err_msg))
}
}
}
pub fn import_etc_fstab(&mut self) -> Result<(), FsTabError> {
log::debug!("FsTab::import_etc_fstab import entries from /etc/fstab");
let result = unsafe { libmount::mnt_table_parse_fstab(self.inner, std::ptr::null()) };
match result {
0 => {
log::debug!("FsTab::import_etc_fstab imported entries from /etc/fstab");
Ok(())
}
code => {
let err_msg = "failed to import entries from /etc/fstab".to_owned();
log::debug!("FsTab::import_etc_fstab {}. libmount::mnt_table_parse_fstab returned error code: {:?}", err_msg, code);
Err(FsTabError::Import(err_msg))
}
}
}
pub fn import_file<T>(&mut self, file_path: T) -> Result<(), FsTabError>
where
T: AsRef<Path>,
{
let file_path = file_path.as_ref();
let file_path_cstr = ffi_utils::as_ref_path_to_c_string(file_path)?;
log::debug!(
"FsTab::import_file importing table entries from file {:?}",
file_path
);
let result = unsafe { libmount::mnt_table_parse_file(self.inner, file_path_cstr.as_ptr()) };
match result {
0 => {
log::debug!(
"FsTab::import_file imported table entries from file {:?}",
file_path
);
Ok(())
}
code => {
let err_msg = format!("failed to import table entries from file {:?}", file_path);
log::debug!("FsTab::import_file {}. libmount::mnt_table_parse_file returned error code: {:?}", err_msg, code);
Err(FsTabError::Import(err_msg))
}
}
}
pub fn import_from_stream<T>(&mut self, file: &File, parsing_errors: T) -> io::Result<()>
where
T: AsRef<Path>,
{
if ffi_utils::is_open_read_only(file)? || ffi_utils::is_open_read_write(file)? {
let parsing_errors = parsing_errors.as_ref();
let path_cstr = ffi_utils::as_ref_path_to_c_string(parsing_errors)?;
let file_stream = ffi_utils::read_only_c_file_stream_from(file)?;
log::debug!(
"FsTab::import_from_stream importing entries from file stream, saving parsing errors to {:?}",
parsing_errors
);
let result = unsafe {
libmount::mnt_table_parse_stream(
self.inner,
file_stream as *mut _,
path_cstr.as_ptr(),
)
};
match result {
0 => {
log::debug!("FsTab::import_from_stream imported entries from file stream, saving parsing errors to {:?}", parsing_errors);
Ok(())
}
code => {
let err_msg = format!(
"failed to import entries from file stream, saving parsing errors to {:?}",
parsing_errors
);
log::debug!("FsTab::import_from_stream {}. libmount::mnt_table_parse_stream returned error code: {:?}", err_msg, code);
Err(io::Error::from_raw_os_error(code))
}
}
} else {
let err_msg = "missing read permission for given file stream".to_owned();
log::debug!("FsTab::import_from_stream {}", err_msg);
Err(io::Error::from(io::ErrorKind::PermissionDenied))
}
}
pub fn append_to_intro_comments<T>(&mut self, comment: T) -> Result<(), FsTabError>
where
T: AsRef<str>,
{
let comment = comment.as_ref();
let comment_cstr = ffi_utils::as_ref_str_to_c_string(comment)?;
log::debug!(
"FsTab::append_to_intro_comments appending to intro comment: {:?}",
comment
);
let result =
unsafe { libmount::mnt_table_append_intro_comment(self.inner, comment_cstr.as_ptr()) };
match result {
0 => {
log::debug!(
"FsTab::append_to_intro_comments appended to intro comment: {:?}",
comment
);
Ok(())
}
code => {
let err_msg = format!("failed to append to intro comment: {:?}", comment);
log::debug!("FsTab::append_to_intro_comments {}. libmount::mnt_table_append_intro_comment returned error code: {:?}", err_msg, code);
Err(FsTabError::Action(err_msg))
}
}
}
pub fn append_to_trailing_comments<T>(&mut self, comment: T) -> Result<(), FsTabError>
where
T: AsRef<str>,
{
let comment = comment.as_ref();
let comment_cstr = ffi_utils::as_ref_str_to_c_string(comment)?;
log::debug!(
"FsTab::append_to_trailing_comments appending to trailing comment: {:?}",
comment
);
let result = unsafe {
libmount::mnt_table_append_trailing_comment(self.inner, comment_cstr.as_ptr())
};
match result {
0 => {
log::debug!(
"FsTab::append_to_trailing_comments appended to trailing comment: {:?}",
comment
);
Ok(())
}
code => {
let err_msg = format!("failed to append to trailing comment: {:?}", comment);
log::debug!("FsTab::append_to_trailing_comments {}. libmount::mnt_table_append_trailing_comment returned error code: {:?}", err_msg, code);
Err(FsTabError::Action(err_msg))
}
}
}
pub fn export_with_comments(&mut self) {
log::debug!("FsTab::export_with_comments enabling comment parsing");
Self::enable_comments(self.inner, true)
}
pub fn export_without_comments(&mut self) {
log::debug!("FsTab::export_without_comments disabling comment parsing");
Self::enable_comments(self.inner, false)
}
fn filter_by<F>(table: &mut Self, flags: u32, cmp_fn: F) -> Result<(), FsTabError>
where
F: FnMut(&FsTabEntry, &FsTabEntry) -> Ordering,
{
#[doc(hidden)]
unsafe extern "C" fn compare<F>(
table: *mut libmount::libmnt_table,
this: *mut libmount::libmnt_fs,
other: *mut libmount::libmnt_fs,
) -> libc::c_int
where
F: FnMut(&FsTabEntry, &FsTabEntry) -> Ordering,
{
let this = FsTabEntry::borrow_ptr(this);
let other = FsTabEntry::borrow_ptr(other);
let mut user_data_ptr = MaybeUninit::<*mut libc::c_void>::zeroed();
unsafe {
user_data_ptr.write(libmount::mnt_table_get_userdata(table));
}
let user_data = unsafe { user_data_ptr.assume_init() };
let fn_cmp = &mut *(user_data as *mut F);
match fn_cmp(&this, &other) {
Ordering::Less => -1,
Ordering::Equal => 0,
Ordering::Greater => 1,
}
}
let user_data = Box::into_raw(Box::new(cmp_fn));
let result = unsafe { libmount::mnt_table_set_userdata(table.inner, user_data as *mut _) };
match result {
0 => {
let result = unsafe {
libmount::mnt_table_uniq_fs(table.inner, flags as i32, Some(compare::<F>))
};
match result {
0 => {
log::debug!("FsTab::filter_by removed duplicates");
let _ = unsafe { Box::from_raw(user_data) };
Ok(())
}
code => {
let err_msg = "failed to remove duplicates from table".to_owned();
log::debug!( "FsTab::filter_by {err_msg}. libmount::mnt_table_uniq_fs returned error code: {code:?}");
let _ = unsafe { Box::from_raw(user_data) };
Err(FsTabError::Deduplicate(err_msg))
}
}
}
code => {
let err_msg = "failed to set the comparison function as userdata".to_owned();
log::debug!( "FsTab::filter_by {err_msg}. libmount::mnt_table_uniq_fs returned error code: {code:?}");
let _ = unsafe { Box::from_raw(user_data) };
Err(FsTabError::Deduplicate(err_msg))
}
}
}
pub fn dedup_first_by<F>(&mut self, cmp: F) -> Result<(), FsTabError>
where
F: FnMut(&FsTabEntry, &FsTabEntry) -> Ordering,
{
log::debug!("FsTab::dedup_first_by merging matching entries to the first occurrence");
Self::filter_by(self, libmount::MNT_UNIQ_FORWARD, cmp)
}
pub fn dedup_last_by<F>(&mut self, cmp: F) -> Result<(), FsTabError>
where
F: FnMut(&FsTabEntry, &FsTabEntry) -> Ordering,
{
log::debug!("FsTab::dedup_last_by merging matching entries to the last occurrence");
static MNT_UNIQ_BACKWARD: u32 = 0;
Self::filter_by(self, MNT_UNIQ_BACKWARD, cmp)
}
pub fn push(&mut self, element: FsTabEntry) {
self.try_push(element).unwrap()
}
pub fn try_push(&mut self, element: FsTabEntry) -> Result<(), FsTabError> {
log::debug!("FsTab::try_push adding a new table entry");
let result = unsafe { libmount::mnt_table_add_fs(self.inner, element.inner) };
match result {
0 => {
log::debug!("FsTab::push added a new table entry");
Ok(())
}
code => {
let err_msg = "failed to add a new table entry".to_owned();
log::debug!(
"FsTab::try_push {err_msg}. libmount::mnt_table_add_fs returned error code: {code:?}"
);
Err(FsTabError::Action(err_msg))
}
}
}
#[doc(hidden)]
fn insert_entry(
table: &mut Self,
after: bool,
pos: *mut libmount::libmnt_fs,
entry: *mut libmount::libmnt_fs,
) -> Result<(), FsTabError> {
let op = if after { 1 } else { 0 };
let op_str = if after {
"after".to_owned()
} else {
"before".to_owned()
};
let result = unsafe { libmount::mnt_table_insert_fs(table.inner, op, pos, entry) };
match result {
0 => {
log::debug!(
"FsTab::insert_entry inserted new entry {} reference",
op_str
);
Ok(())
}
code => {
let err_msg = format!("failed to insert new entry {} reference", op_str);
log::debug!( "FsTab::insert_entry {err_msg}. libmount::mnt_table_insert_fs returned error code: {code:?}");
Err(FsTabError::Action(err_msg))
}
}
}
pub fn push_front(&mut self, element: FsTabEntry) {
log::debug!("FsTab::push_front prepending new entry");
Self::insert_entry(self, true, std::ptr::null_mut(), element.inner).unwrap()
}
pub fn try_push_front(&mut self, element: FsTabEntry) -> Result<(), FsTabError> {
log::debug!("FsTab::try_push_front prepending new entry");
Self::insert_entry(self, true, std::ptr::null_mut(), element.inner)
}
pub fn insert(&mut self, index: usize, element: FsTabEntry) {
self.try_insert(index, element).unwrap()
}
pub fn try_insert(&mut self, index: usize, element: FsTabEntry) -> Result<(), FsTabError> {
log::debug!(
"FsTab::try_insert inserting new entry at index: {:?}",
index
);
let mut iter = FsTabIter::new(self)?;
match iter.nth(index) {
Some(position) => Self::insert_entry(self, false, position.inner, element.inner),
None => {
let err_msg = format!(
"failed to insert element at index: {:?}. Index out of bounds.",
index
);
log::debug!("FsTab::try_insert {err_msg}");
Err(FsTabError::IndexOutOfBounds(err_msg))
}
}
}
#[doc(hidden)]
fn move_entry(
after: bool,
source_table: *mut libmount::libmnt_table,
entry: *mut libmount::libmnt_fs,
dest_table: *mut libmount::libmnt_table,
position: *mut libmount::libmnt_fs,
) -> Result<(), FsTabError> {
log::debug!("FsTab::move_entry transferring entry between tables");
let op = if after { 1 } else { 0 };
let result =
unsafe { libmount::mnt_table_move_fs(source_table, dest_table, op, position, entry) };
match result {
0 => {
log::debug!("FsTab::move_entry transferred entry between tables");
Ok(())
}
code => {
let err_msg = "failed to transfer entry between tables".to_owned();
log::debug!(
"FsTab::move_entry {err_msg}. libmount::mnt_table_move_fs returned error code: {code:?}"
);
Err(FsTabError::Transfer(err_msg))
}
}
}
pub fn transfer(
&mut self,
index: usize,
destination: &mut FsTab,
dest_index: usize,
) -> Result<(), FsTabError> {
let mut iter = FsTabIter::new(self)?;
match iter.nth(index) {
Some(entry) if dest_index == 0 => {
log::debug!(
"FsTab::transfer transferring element a index: {:?} to start of destination table",
index
);
Self::move_entry(
true,
self.inner,
entry.inner,
destination.inner,
std::ptr::null_mut(),
)
}
Some(entry) if dest_index == destination.len() => {
log::debug!(
"FsTab::transfer transferring element a index: {:?} to end of destination table",
index
);
Self::move_entry(
false,
self.inner,
entry.inner,
destination.inner,
std::ptr::null_mut(),
)
}
Some(element) => {
let mut iter_dest = FsTabIter::new(destination)?;
match iter_dest.nth(dest_index) {
Some(position) => {
log::debug!( "FsTab::transfer transferring element at index {:?} to destination at index {:?}", index, dest_index);
Self::move_entry(
false,
self.inner,
element.inner,
destination.inner,
position.inner,
)
}
None => {
let err_msg = format!(
"failed to transfer element at index {:?} to index {:?} in destination table. Index out of bounds.", index,
dest_index
);
log::debug!("FsTab::transfer {err_msg}");
Err(FsTabError::IndexOutOfBounds(err_msg))
}
}
}
None => {
let err_msg = format!(
"failed to access element at index {:?} in source table. Index out of bounds.",
index
);
log::debug!("FsTab::transfer {err_msg}");
Err(FsTabError::IndexOutOfBounds(err_msg))
}
}
}
pub fn remove(&mut self, index: usize) -> FsTabEntry {
log::debug!("FsTab::remove removing entry from table");
let err_msg = format!("failed to find entry at index: {:?}", index);
let element: &FsTabEntry = self
.get(index)
.ok_or(Err::<&FsTabEntry, FsTabError>(
FsTabError::IndexOutOfBounds(err_msg),
))
.unwrap();
#[cold]
#[inline(never)]
#[track_caller]
fn assert_failed() -> ! {
panic!("cannot remove table entry. Not found");
}
let borrowed = FsTabEntry::borrow_ptr(element.inner);
let result = unsafe { libmount::mnt_table_remove_fs(self.inner, element.inner) };
match result {
0 => {
log::debug!("FsTab::remove removed entry from table");
borrowed
}
code => {
let err_msg = "failed to remove entry from table".to_owned();
log::debug!(
"FsTab::remove {}. libmount::mnt_table_remove_fs returned error code: {:?}",
err_msg,
code
);
drop(borrowed);
assert_failed()
}
}
}
pub fn clear(&mut self) -> Result<(), FsTabError> {
log::debug!("FsTab::clear removing all table entries");
unsafe {
match libmount::mnt_reset_table(self.inner) {
0 => {
log::debug!("FsTab::clear removed all table entries");
self.collect_garbage();
Ok(())
}
code => {
let err_msg = "failed to remove all table entries".to_owned();
log::debug!(
"FsTab::clear {err_msg}. libmount::mnt_reset_table returned error code: {code:?}"
);
Err(FsTabError::Action(err_msg))
}
}
}
}
pub fn write_file<T>(&mut self, file_path: T) -> Result<(), FsTabError>
where
T: AsRef<Path>,
{
let file_path = file_path.as_ref();
let file_path_cstr = ffi_utils::as_ref_path_to_c_string(file_path)?;
log::debug!("FsTab::write_file saving table content to {:?}", file_path);
let result =
unsafe { libmount::mnt_table_replace_file(self.inner, file_path_cstr.as_ptr()) };
match result {
0 => {
log::debug!("FsTab::write_file saved table content to {:?}", file_path);
Ok(())
}
code => {
let err_msg = format!("failed to save table content to {:?}", file_path);
log::debug!( "FsTab::write_file {err_msg}. libmount::mnt_table_replace_file returned error code: {code:?}");
Err(FsTabError::Export(err_msg))
}
}
}
pub fn write_stream(&mut self, file_stream: &mut File) -> io::Result<()> {
log::debug!("FsTab::write_stream writing mount table content to file stream");
if ffi_utils::is_open_write_only(file_stream)?
|| ffi_utils::is_open_read_write(file_stream)?
{
let file = ffi_utils::write_only_c_file_stream_from(file_stream)?;
let result = unsafe { libmount::mnt_table_write_file(self.inner, file as *mut _) };
match result {
0 => {
log::debug!("FsTab::write_stream wrote mount table content to file stream");
Ok(())
}
code => {
let err_msg = "failed to write mount table content to file stream".to_owned();
log::debug!( "FsTab::write_stream {err_msg}. libmount::mnt_table_write_file returned error code: {code:?}");
Err(io::Error::from_raw_os_error(code))
}
}
} else {
let err_msg = "you do not have permission to write in this file stream".to_owned();
log::debug!("FsTab::write_stream {err_msg}");
Err(io::Error::from(io::ErrorKind::PermissionDenied))
}
}
pub fn contains(&self, element: &FsTabEntry) -> bool {
let state = unsafe { libmount::mnt_table_find_fs(self.inner, element.inner) > 0 };
log::debug!("FsTab::contains value: {:?}", state);
state
}
pub fn is_empty(&self) -> bool {
let state = unsafe { libmount::mnt_table_is_empty(self.inner) == 1 };
log::debug!("FsTab::is_empty value: {:?}", state);
state
}
pub fn is_importing_comments(&self) -> bool {
let state = unsafe { libmount::mnt_table_with_comments(self.inner) == 1 };
log::debug!("FsTab::is_importing_comments value: {:?}", state);
state
}
pub fn is_exporting_comments(&self) -> bool {
let state = unsafe { libmount::mnt_table_with_comments(self.inner) == 1 };
log::debug!("FsTab::is_exporting_comments value: {:?}", state);
state
}
}
impl fmt::Display for FsTab {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut output: Vec<String> = vec![];
if let Some(intro) = self.intro_comments() {
output.push(intro.to_string());
}
for line in self.iter() {
output.push(line.to_string());
}
if let Some(trailing) = self.trailing_comments() {
output.push(trailing.to_string());
}
write!(f, "{}", output.join("\n"))
}
}
#[cfg(test)]
#[allow(unused_imports)]
mod tests {
use super::*;
use crate::core::device::BlockDevice;
use crate::core::device::Pseudo;
use crate::core::device::Source;
use crate::core::device::Tag;
use crate::core::fs::FileSystem;
use pretty_assertions::{assert_eq, assert_ne};
use std::fs::{File, OpenOptions};
use std::io::{Read, Seek, SeekFrom};
use std::str::FromStr;
use tempfile::{tempdir, tempfile};
#[test]
fn fs_tab_a_new_table_is_empty() -> crate::Result<()> {
let fs_tab = FsTab::new()?;
assert!(fs_tab.is_empty());
Ok(())
}
#[test]
fn fs_tab_an_empty_table_has_no_first_element() -> crate::Result<()> {
let fs_tab = FsTab::new()?;
let actual = fs_tab.first();
assert!(actual.is_none());
Ok(())
}
#[test]
fn fs_tab_an_empty_table_has_no_last_element() -> crate::Result<()> {
let fs_tab = FsTab::new()?;
let actual = fs_tab.last();
assert!(actual.is_none());
Ok(())
}
#[test]
#[should_panic(expected = "Index out of bounds")]
fn fs_tab_indexing_an_empty_table_triggers_a_panic() {
let fs_tab = FsTab::new().unwrap();
let _ = fs_tab[0];
}
#[test]
fn fs_tab_push_adds_an_element_to_a_table() -> crate::Result<()> {
let uuid = Tag::from_str("UUID=dd476616-1ce4-415e-9dbd-8c2fa8f42f0f")?;
let entry = FsTabEntry::builder()
.source(uuid)
.target("/")
.file_system_type(FileSystem::Ext4)
.mount_options("rw,relatime")
.backup_frequency(0)
.fsck_checking_order(1)
.build()?;
let mut fs_tab = FsTab::new()?;
fs_tab.push(entry);
assert_eq!(fs_tab.len(), 1);
Ok(())
}
#[test]
fn fs_tab_push_front_adds_an_element_at_the_head_of_the_table() -> crate::Result<()> {
let uuid = Tag::from_str("UUID=dd476616-1ce4-415e-9dbd-8c2fa8f42f0f")?;
let entry1 = FsTabEntry::builder()
.source(uuid)
.target("/")
.file_system_type(FileSystem::Ext4)
.mount_options("rw,relatime")
.backup_frequency(0)
.fsck_checking_order(1)
.build()?;
let block_device = BlockDevice::from_str("/dev/usbdisk")?;
let entry2 = FsTabEntry::builder()
.source(block_device)
.target("/media/usb")
.file_system_type(FileSystem::VFAT)
.mount_options("noauto")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let entry1_inner = entry1.inner;
let entry2_inner = entry2.inner;
let mut fs_tab = FsTab::new()?;
fs_tab.push_front(entry1);
fs_tab.push_front(entry2);
let first = fs_tab.first().unwrap();
let last = fs_tab.last().unwrap();
assert_eq!(fs_tab.len(), 2);
assert_eq!(first.inner, entry2_inner);
assert_eq!(last.inner, entry1_inner);
Ok(())
}
#[test]
fn fs_tab_a_table_of_size_1_has_the_same_first_and_last_element() -> crate::Result<()> {
let uuid = Tag::from_str("UUID=dd476616-1ce4-415e-9dbd-8c2fa8f42f0f")?;
let entry = FsTabEntry::builder()
.source(uuid)
.target("/")
.file_system_type(FileSystem::Ext4)
.mount_options("rw,relatime")
.backup_frequency(0)
.fsck_checking_order(1)
.build()?;
let mut fs_tab = FsTab::new()?;
fs_tab.push(entry);
let first = fs_tab.first();
let last = fs_tab.last();
assert_eq!(first, last);
Ok(())
}
#[test]
fn fs_tab_finds_the_first_predicate_match_from_the_top() -> crate::Result<()> {
let uuid = Tag::from_str("UUID=dd476616-1ce4-415e-9dbd-8c2fa8f42f0f")?;
let entry1 = FsTabEntry::builder()
.source(uuid)
.target("/")
.file_system_type(FileSystem::Ext4)
.mount_options("rw,relatime")
.backup_frequency(0)
.fsck_checking_order(1)
.build()?;
let block_device = BlockDevice::from_str("/dev/usbdisk")?;
let entry2 = FsTabEntry::builder()
.source(block_device)
.target("/media/usb")
.file_system_type(FileSystem::VFAT)
.mount_options("noauto")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let uuid = Tag::from_str("UUID=dd479919-1ce4-415e-9dbd-3c2ba3b42b0b")?;
let entry3 = FsTabEntry::builder()
.source(uuid)
.target("/media/disk")
.file_system_type(FileSystem::XFS)
.mount_options("noauto")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let entry4 = FsTabEntry::builder()
.source(Pseudo::None)
.target("/tmp")
.file_system_type(FileSystem::Tmpfs)
.mount_options("nosuid,nodev")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let mut fs_tab = FsTab::new()?;
fs_tab.push(entry1);
fs_tab.push(entry2);
fs_tab.push(entry3);
fs_tab.push(entry4);
let element = fs_tab.find_first(|element| element.has_any_fs_type("ext4,xfs"));
assert!(element.is_some());
let element = element.unwrap();
let actual = element.file_system_type();
let fs = FileSystem::Ext4;
let expected = Some(fs);
assert_eq!(actual, expected);
let target = Path::new("/");
let actual = element.target();
let expected = Some(target);
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn fs_tab_finds_the_first_predicate_match_from_the_bottom() -> crate::Result<()> {
let uuid = Tag::from_str("UUID=dd476616-1ce4-415e-9dbd-8c2fa8f42f0f")?;
let entry1 = FsTabEntry::builder()
.source(uuid)
.target("/")
.file_system_type(FileSystem::Ext4)
.mount_options("rw,relatime")
.backup_frequency(0)
.fsck_checking_order(1)
.build()?;
let block_device = BlockDevice::from_str("/dev/usbdisk")?;
let entry2 = FsTabEntry::builder()
.source(block_device)
.target("/media/usb")
.file_system_type(FileSystem::VFAT)
.mount_options("noauto")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let uuid = Tag::from_str("UUID=dd479919-1ce4-415e-9dbd-3c2ba3b42b0b")?;
let entry3 = FsTabEntry::builder()
.source(uuid)
.target("/media/disk")
.file_system_type(FileSystem::XFS)
.mount_options("noauto")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let entry4 = FsTabEntry::builder()
.source(Pseudo::None)
.target("/tmp")
.file_system_type(FileSystem::Tmpfs)
.mount_options("nosuid,nodev")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let mut fs_tab = FsTab::new()?;
fs_tab.push(entry1);
fs_tab.push(entry2);
fs_tab.push(entry3);
fs_tab.push(entry4);
let element = fs_tab.find_back_first(|element| element.has_any_fs_type("ext4,xfs"));
assert!(element.is_some());
let element = element.unwrap();
let actual = element.file_system_type();
let fs = FileSystem::XFS;
let expected = Some(fs);
assert_eq!(actual, expected);
let target = Path::new("/media/disk");
let actual = element.target();
let expected = Some(target);
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn fs_tab_can_can_advance_its_iterator_to_a_given_position() -> crate::Result<()> {
let uuid = Tag::from_str("UUID=dd476616-1ce4-415e-9dbd-8c2fa8f42f0f")?;
let entry1 = FsTabEntry::builder()
.source(uuid)
.target("/")
.file_system_type(FileSystem::Ext4)
.mount_options("rw,relatime")
.backup_frequency(0)
.fsck_checking_order(1)
.build()?;
let block_device = BlockDevice::from_str("/dev/usbdisk")?;
let entry2 = FsTabEntry::builder()
.source(block_device)
.target("/media/usb")
.file_system_type(FileSystem::VFAT)
.mount_options("noauto")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let entry3 = FsTabEntry::builder()
.source(Pseudo::None)
.target("/tmp")
.file_system_type(FileSystem::Tmpfs)
.mount_options("nosuid,nodev")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let entry2_inner = entry2.inner;
let entry3_inner = entry3.inner;
let mut fs_tab = FsTab::new()?;
fs_tab.push(entry1);
fs_tab.push(entry2);
fs_tab.push(entry3);
assert_eq!(fs_tab.len(), 3);
let mut iter = fs_tab.iter();
iter.advance_to(2).unwrap();
let actual = iter.next();
assert!(actual.is_none());
iter.advance_to(0).unwrap();
let actual = iter.next().unwrap().inner;
assert_eq!(actual, entry2_inner);
iter.advance_to(1).unwrap();
let actual = iter.next().unwrap().inner;
assert_eq!(actual, entry3_inner);
Ok(())
}
#[test]
fn fs_tab_can_iterate_forwards_over_table_entries() -> crate::Result<()> {
let uuid = Tag::from_str("UUID=dd476616-1ce4-415e-9dbd-8c2fa8f42f0f")?;
let entry1 = FsTabEntry::builder()
.source(uuid)
.target("/")
.file_system_type(FileSystem::Ext4)
.mount_options("rw,relatime")
.backup_frequency(0)
.fsck_checking_order(1)
.build()?;
let block_device = BlockDevice::from_str("/dev/usbdisk")?;
let entry2 = FsTabEntry::builder()
.source(block_device)
.target("/media/usb")
.file_system_type(FileSystem::VFAT)
.mount_options("noauto")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let entry3 = FsTabEntry::builder()
.source(Pseudo::None)
.target("/tmp")
.file_system_type(FileSystem::Tmpfs)
.mount_options("nosuid,nodev")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let entry1_inner = entry1.inner;
let entry2_inner = entry2.inner;
let entry3_inner = entry3.inner;
let mut fs_tab = FsTab::new()?;
fs_tab.push(entry1);
fs_tab.push(entry2);
fs_tab.push(entry3);
assert_eq!(fs_tab.len(), 3);
let mut iter = fs_tab.iter();
let first_inner = iter.next().unwrap().inner;
let second_inner = iter.next().unwrap().inner;
let third_inner = iter.next().unwrap().inner;
assert_eq!(first_inner, entry1_inner);
assert_eq!(second_inner, entry2_inner);
assert_eq!(third_inner, entry3_inner);
Ok(())
}
#[test]
fn fs_tab_can_iterate_backwards_over_table_entries() -> crate::Result<()> {
let uuid = Tag::from_str("UUID=dd476616-1ce4-415e-9dbd-8c2fa8f42f0f")?;
let entry1 = FsTabEntry::builder()
.source(uuid)
.target("/")
.file_system_type(FileSystem::Ext4)
.mount_options("rw,relatime")
.backup_frequency(0)
.fsck_checking_order(1)
.build()?;
let block_device = BlockDevice::from_str("/dev/usbdisk")?;
let entry2 = FsTabEntry::builder()
.source(block_device)
.target("/media/usb")
.file_system_type(FileSystem::VFAT)
.mount_options("noauto")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let entry3 = FsTabEntry::builder()
.source(Pseudo::None)
.target("/tmp")
.file_system_type(FileSystem::Tmpfs)
.mount_options("nosuid,nodev")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let entry1_inner = entry1.inner;
let entry2_inner = entry2.inner;
let entry3_inner = entry3.inner;
let mut fs_tab = FsTab::new()?;
fs_tab.push(entry1);
fs_tab.push(entry2);
fs_tab.push(entry3);
assert_eq!(fs_tab.len(), 3);
let mut iter = fs_tab.iter();
let first_inner = iter.next_back().unwrap().inner;
let second_inner = iter.next_back().unwrap().inner;
let third_inner = iter.next_back().unwrap().inner;
assert_eq!(first_inner, entry3_inner);
assert_eq!(second_inner, entry2_inner);
assert_eq!(third_inner, entry1_inner);
Ok(())
}
#[test]
fn fs_tab_can_iterate_alternately_forwards_then_backwards_over_table_entries(
) -> crate::Result<()> {
let uuid = Tag::from_str("UUID=dd476616-1ce4-415e-9dbd-8c2fa8f42f0f")?;
let entry1 = FsTabEntry::builder()
.source(uuid)
.target("/")
.file_system_type(FileSystem::Ext4)
.mount_options("rw,relatime")
.backup_frequency(0)
.fsck_checking_order(1)
.build()?;
let block_device = BlockDevice::from_str("/dev/usbdisk")?;
let entry2 = FsTabEntry::builder()
.source(block_device)
.target("/media/usb")
.file_system_type(FileSystem::VFAT)
.mount_options("noauto")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let entry3 = FsTabEntry::builder()
.source(Pseudo::None)
.target("/tmp")
.file_system_type(FileSystem::Tmpfs)
.mount_options("nosuid,nodev")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let entry1_inner = entry1.inner;
let entry2_inner = entry2.inner;
let entry3_inner = entry3.inner;
let mut fs_tab = FsTab::new()?;
fs_tab.push(entry1);
fs_tab.push(entry2);
fs_tab.push(entry3);
assert_eq!(fs_tab.len(), 3);
let mut iter = fs_tab.iter();
let first_inner = iter.next().unwrap().inner;
let second_inner = iter.next_back().unwrap().inner;
let third_inner = iter.next().unwrap().inner;
let fourth = iter.next_back();
let fifth = iter.next();
assert_eq!(first_inner, entry1_inner);
assert_eq!(second_inner, entry3_inner);
assert_eq!(third_inner, entry2_inner);
assert!(fourth.is_none());
assert!(fifth.is_none());
Ok(())
}
#[test]
fn fs_tab_can_iterate_alternately_backwards_then_forwards_over_table_entries(
) -> crate::Result<()> {
let uuid = Tag::from_str("UUID=dd476616-1ce4-415e-9dbd-8c2fa8f42f0f")?;
let entry1 = FsTabEntry::builder()
.source(uuid)
.target("/")
.file_system_type(FileSystem::Ext4)
.mount_options("rw,relatime")
.backup_frequency(0)
.fsck_checking_order(1)
.build()?;
let block_device = BlockDevice::from_str("/dev/usbdisk")?;
let entry2 = FsTabEntry::builder()
.source(block_device)
.target("/media/usb")
.file_system_type(FileSystem::VFAT)
.mount_options("noauto")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let entry3 = FsTabEntry::builder()
.source(Pseudo::None)
.target("/tmp")
.file_system_type(FileSystem::Tmpfs)
.mount_options("nosuid,nodev")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let entry1_inner = entry1.inner;
let entry2_inner = entry2.inner;
let entry3_inner = entry3.inner;
let mut fs_tab = FsTab::new()?;
fs_tab.push(entry1);
fs_tab.push(entry2);
fs_tab.push(entry3);
assert_eq!(fs_tab.len(), 3);
let mut iter = fs_tab.iter();
let first_inner = iter.next_back().unwrap().inner;
let second_inner = iter.next().unwrap().inner;
let third_inner = iter.next_back().unwrap().inner;
let fourth = iter.next();
let fifth = iter.next_back();
assert_eq!(first_inner, entry3_inner);
assert_eq!(second_inner, entry1_inner);
assert_eq!(third_inner, entry2_inner);
assert!(fourth.is_none());
assert!(fifth.is_none());
Ok(())
}
#[test]
fn fs_tab_can_index_into_a_table() -> crate::Result<()> {
let uuid = Tag::from_str("UUID=dd476616-1ce4-415e-9dbd-8c2fa8f42f0f")?;
let entry = FsTabEntry::builder()
.source(uuid)
.target("/")
.file_system_type(FileSystem::Ext4)
.mount_options("rw,relatime")
.backup_frequency(0)
.fsck_checking_order(1)
.build()?;
let expected = entry.inner;
let mut fs_tab = FsTab::new()?;
fs_tab.push(entry);
let actual = fs_tab[0].inner;
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn fs_tab_can_insert_an_element_at_a_predefined_position() -> crate::Result<()> {
let uuid = Tag::from_str("UUID=dd476616-1ce4-415e-9dbd-8c2fa8f42f0f")?;
let entry1 = FsTabEntry::builder()
.source(uuid)
.target("/")
.file_system_type(FileSystem::Ext4)
.mount_options("rw,relatime")
.backup_frequency(0)
.fsck_checking_order(1)
.build()?;
let block_device = BlockDevice::from_str("/dev/usbdisk")?;
let entry2 = FsTabEntry::builder()
.source(block_device)
.target("/media/usb")
.file_system_type(FileSystem::VFAT)
.mount_options("noauto")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let entry3 = FsTabEntry::builder()
.source(Pseudo::None)
.target("/tmp")
.file_system_type(FileSystem::Tmpfs)
.mount_options("nosuid,nodev")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let entry1_inner = entry1.inner;
let entry2_inner = entry2.inner;
let entry3_inner = entry3.inner;
let mut fs_tab = FsTab::new()?;
fs_tab.push(entry1);
fs_tab.push(entry2);
fs_tab.insert(1, entry3);
assert_eq!(fs_tab.len(), 3);
let first_inner = fs_tab[0].inner;
let second_inner = fs_tab[1].inner;
let third_inner = fs_tab[2].inner;
assert_eq!(first_inner, entry1_inner);
assert_eq!(second_inner, entry3_inner);
assert_eq!(third_inner, entry2_inner);
Ok(())
}
#[test]
fn fs_tab_can_remove_an_element_from_a_table() -> crate::Result<()> {
let uuid = Tag::from_str("UUID=dd476616-1ce4-415e-9dbd-8c2fa8f42f0f")?;
let entry = FsTabEntry::builder()
.source(uuid)
.target("/")
.file_system_type(FileSystem::Ext4)
.mount_options("rw,relatime")
.backup_frequency(0)
.fsck_checking_order(1)
.build()?;
let mut fs_tab = FsTab::new()?;
fs_tab.push(entry);
assert_eq!(fs_tab.len(), 1);
let item = fs_tab.remove(0);
let actual = item.tag().unwrap();
let expected: Tag = "UUID=dd476616-1ce4-415e-9dbd-8c2fa8f42f0f".parse()?;
assert_eq!(actual, expected);
let actual = item.file_system_type().unwrap();
let expected = FileSystem::Ext4;
assert_eq!(actual, expected);
let actual = item.mount_options().unwrap();
let expected = "rw,relatime";
assert_eq!(actual, expected);
assert_eq!(fs_tab.is_empty(), true);
Ok(())
}
#[test]
fn fs_tab_can_transfer_an_element_between_tables_to_destination_start() -> crate::Result<()> {
let uuid = Tag::from_str("UUID=dd476616-1ce4-415e-9dbd-8c2fa8f42f0f")?;
let entry1 = FsTabEntry::builder()
.source(uuid)
.target("/")
.file_system_type(FileSystem::Ext4)
.mount_options("rw,relatime")
.backup_frequency(0)
.fsck_checking_order(1)
.build()?;
let block_device = BlockDevice::from_str("/dev/usbdisk")?;
let entry2 = FsTabEntry::builder()
.source(block_device)
.target("/media/usb")
.file_system_type(FileSystem::VFAT)
.mount_options("noauto")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let entry3 = FsTabEntry::builder()
.source(Pseudo::None)
.target("/tmp")
.file_system_type(FileSystem::Tmpfs)
.mount_options("nosuid,nodev")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let entry1_inner = entry1.inner;
let entry2_inner = entry2.inner;
let entry3_inner = entry3.inner;
let mut source_table = FsTab::new()?;
source_table.push(entry1);
let mut dest_table = FsTab::new()?;
dest_table.push(entry2);
dest_table.push(entry3);
assert_eq!(source_table.len(), 1);
assert_eq!(dest_table.len(), 2);
source_table.transfer(0, &mut dest_table, 0)?;
assert_eq!(source_table.is_empty(), true);
assert_eq!(dest_table.len(), 3);
let first_inner = dest_table[0].inner;
let second_inner = dest_table[1].inner;
let third_inner = dest_table[2].inner;
assert_eq!(first_inner, entry1_inner);
assert_eq!(second_inner, entry2_inner);
assert_eq!(third_inner, entry3_inner);
Ok(())
}
#[test]
fn fs_tab_can_transfer_an_element_between_tables_to_destination_middle() -> crate::Result<()> {
let uuid = Tag::from_str("UUID=dd476616-1ce4-415e-9dbd-8c2fa8f42f0f")?;
let entry1 = FsTabEntry::builder()
.source(uuid)
.target("/")
.file_system_type(FileSystem::Ext4)
.mount_options("rw,relatime")
.backup_frequency(0)
.fsck_checking_order(1)
.build()?;
let block_device = BlockDevice::from_str("/dev/usbdisk")?;
let entry2 = FsTabEntry::builder()
.source(block_device)
.target("/media/usb")
.file_system_type(FileSystem::VFAT)
.mount_options("noauto")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let entry3 = FsTabEntry::builder()
.source(Pseudo::None)
.target("/tmp")
.file_system_type(FileSystem::Tmpfs)
.mount_options("nosuid,nodev")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let entry1_inner = entry1.inner;
let entry2_inner = entry2.inner;
let entry3_inner = entry3.inner;
let mut source_table = FsTab::new()?;
source_table.push(entry1);
let mut dest_table = FsTab::new()?;
dest_table.push(entry2);
dest_table.push(entry3);
assert_eq!(source_table.len(), 1);
assert_eq!(dest_table.len(), 2);
source_table.transfer(0, &mut dest_table, 1)?;
assert_eq!(source_table.is_empty(), true);
assert_eq!(dest_table.len(), 3);
let first_inner = dest_table[0].inner;
let second_inner = dest_table[1].inner;
let third_inner = dest_table[2].inner;
assert_eq!(first_inner, entry2_inner);
assert_eq!(second_inner, entry1_inner);
assert_eq!(third_inner, entry3_inner);
Ok(())
}
#[test]
fn fs_tab_can_transfer_an_element_between_tables_to_destination_end() -> crate::Result<()> {
let uuid = Tag::from_str("UUID=dd476616-1ce4-415e-9dbd-8c2fa8f42f0f")?;
let entry1 = FsTabEntry::builder()
.source(uuid)
.target("/")
.file_system_type(FileSystem::Ext4)
.mount_options("rw,relatime")
.backup_frequency(0)
.fsck_checking_order(1)
.build()?;
let block_device = BlockDevice::from_str("/dev/usbdisk")?;
let entry2 = FsTabEntry::builder()
.source(block_device)
.target("/media/usb")
.file_system_type(FileSystem::VFAT)
.mount_options("noauto")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let entry3 = FsTabEntry::builder()
.source(Pseudo::None)
.target("/tmp")
.file_system_type(FileSystem::Tmpfs)
.mount_options("nosuid,nodev")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let entry1_inner = entry1.inner;
let entry2_inner = entry2.inner;
let entry3_inner = entry3.inner;
let mut source_table = FsTab::new()?;
source_table.push(entry1);
let mut dest_table = FsTab::new()?;
dest_table.push(entry2);
dest_table.push(entry3);
assert_eq!(source_table.len(), 1);
assert_eq!(dest_table.len(), 2);
source_table.transfer(0, &mut dest_table, 2)?;
assert_eq!(source_table.is_empty(), true);
assert_eq!(dest_table.len(), 3);
let first_inner = dest_table[0].inner;
let second_inner = dest_table[1].inner;
let third_inner = dest_table[2].inner;
assert_eq!(first_inner, entry2_inner);
assert_eq!(second_inner, entry3_inner);
assert_eq!(third_inner, entry1_inner);
Ok(())
}
#[test]
fn fs_tab_writes_to_a_file_stream() -> crate::Result<()> {
let mut fs_tab = FsTab::new()?;
fs_tab.set_intro_comments("# /etc/fstab\n")?;
fs_tab.append_to_intro_comments("# Example from scratch\n")?;
fs_tab.append_to_intro_comments("\n")?;
let uuid = Tag::from_str("UUID=dd476616-1ce4-415e-9dbd-8c2fa8f42f0f")?;
let entry1 = FsTabEntry::builder()
.source(uuid)
.target("/")
.file_system_type(FileSystem::Ext4)
.mount_options("rw,relatime")
.backup_frequency(0)
.fsck_checking_order(1)
.build()?;
let block_device = BlockDevice::from_str("/dev/usbdisk")?;
let entry2 = FsTabEntry::builder()
.source(block_device)
.target("/media/usb")
.file_system_type(FileSystem::VFAT)
.mount_options("noauto")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
let entry3 = FsTabEntry::builder()
.source(Pseudo::None)
.target("/tmp")
.file_system_type(FileSystem::Tmpfs)
.mount_options("nosuid,nodev")
.backup_frequency(0)
.fsck_checking_order(0)
.build()?;
fs_tab.push(entry1);
fs_tab.push(entry2);
fs_tab.push(entry3);
assert_eq!(fs_tab.len(), 3);
let mut tmpfile: File = tempfile().unwrap();
fs_tab.export_with_comments();
fs_tab.write_stream(&mut tmpfile).unwrap();
tmpfile.seek(SeekFrom::Start(0)).unwrap();
let mut actual = String::new();
tmpfile.read_to_string(&mut actual).unwrap();
let expected = "# /etc/fstab\n# Example from scratch\n\nUUID=dd476616-1ce4-415e-9dbd-8c2fa8f42f0f / ext4 rw,relatime 0 1\n/dev/usbdisk /media/usb vfat noauto 0 0\nnone /tmp tmpfs nosuid,nodev 0 0\n";
assert_eq!(actual, expected);
Ok(())
}
#[test]
#[should_panic]
fn fs_tab_does_not_write_to_read_only_file_stream() {
let mut fs_tab = FsTab::new().unwrap();
fs_tab.set_intro_comments("# /etc/fstab\n").unwrap();
fs_tab
.append_to_intro_comments("# Example from scratch\n")
.unwrap();
fs_tab.append_to_intro_comments("\n").unwrap();
let uuid = Tag::from_str("UUID=dd476616-1ce4-415e-9dbd-8c2fa8f42f0f").unwrap();
let entry1 = FsTabEntry::builder()
.source(uuid)
.target("/")
.file_system_type(FileSystem::Ext4)
.mount_options("rw,relatime")
.backup_frequency(0)
.fsck_checking_order(1)
.build()
.unwrap();
let block_device = BlockDevice::from_str("/dev/usbdisk").unwrap();
let entry2 = FsTabEntry::builder()
.source(block_device)
.target("/media/usb")
.file_system_type(FileSystem::VFAT)
.mount_options("noauto")
.backup_frequency(0)
.fsck_checking_order(0)
.build()
.unwrap();
let entry3 = FsTabEntry::builder()
.source(Pseudo::None)
.target("/tmp")
.file_system_type(FileSystem::Tmpfs)
.mount_options("nosuid,nodev")
.backup_frequency(0)
.fsck_checking_order(0)
.build()
.unwrap();
fs_tab.push(entry1);
fs_tab.push(entry2);
fs_tab.push(entry3);
assert_eq!(fs_tab.len(), 3);
let tmpdir = tempdir().unwrap();
let tmpfile_path = tmpdir.path().join("read-only-file");
let file = File::create(&tmpfile_path).unwrap();
drop(file);
let mut tmpfile: File = OpenOptions::new().read(true).open(&tmpfile_path).unwrap();
fs_tab.export_with_comments();
fs_tab.write_stream(&mut tmpfile).unwrap();
}
}