use super::InodeMigrationInfo;
use crate::fuse;
use crate::passthrough::inode_store::{InodeData, InodePathError, StrongInodeReference};
use crate::passthrough::stat::statx;
use crate::passthrough::util::{relative_path, FdPathError};
use crate::passthrough::PassthroughFs;
use crate::util::{other_io_error, ErrorContext};
use std::ffi::{CStr, CString};
use std::io;
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
struct Walker<'a> {
fs: &'a PassthroughFs,
#[allow(dead_code)] mode: Mode,
cancel: Option<Arc<AtomicBool>>,
}
pub(in crate::passthrough::device_state) struct Constructor<'a> {
walker: Walker<'a>,
}
pub(in crate::passthrough::device_state) struct ConfirmPaths<'a> {
walker: Walker<'a>,
}
pub(in crate::passthrough::device_state) struct ImplicitPathCheck<'a> {
walker: Walker<'a>,
}
pub(in crate::passthrough::device_state) enum Mode {
Constructor,
ConfirmPaths,
ImplicitPathCheck,
}
pub(in crate::passthrough) enum WrappedError {
Fallback(io::Error),
Unrecoverable(io::Error),
}
impl<'a> Constructor<'a> {
pub fn new(fs: &'a PassthroughFs, cancel: Arc<AtomicBool>) -> Self {
Constructor {
walker: Walker::new(fs, Mode::Constructor, Some(cancel)),
}
}
pub fn execute(self) -> bool {
match self.walker.run() {
Ok(()) => false,
Err(WrappedError::Fallback(err)) => {
warn!("Failed to construct inode paths: {err}");
true
}
Err(WrappedError::Unrecoverable(err)) => {
error!("Failed to construct inode paths: {err}; may be unable to migrate");
false
}
}
}
}
impl<'a> ConfirmPaths<'a> {
pub fn new(fs: &'a PassthroughFs) -> Self {
ConfirmPaths {
walker: Walker::new(fs, Mode::ConfirmPaths, None),
}
}
pub fn confirm_paths(self) -> io::Result<()> {
self.walker.run().map_err(WrappedError::into_inner)
}
}
impl<'a> ImplicitPathCheck<'a> {
pub fn new(fs: &'a PassthroughFs, cancel: Arc<AtomicBool>) -> Self {
ImplicitPathCheck {
walker: Walker::new(fs, Mode::ImplicitPathCheck, Some(cancel)),
}
}
pub fn check_paths(self) {
if let Err(err) = self.walker.run() {
let err = err.into_inner();
warn!("Double-check of all inode paths collected for migration failed: {err}")
}
}
}
impl<'a> Walker<'a> {
fn new(fs: &'a PassthroughFs, mode: Mode, cancel: Option<Arc<AtomicBool>>) -> Self {
Walker { fs, mode, cancel }
}
fn run(self) -> Result<(), WrappedError> {
let Some(root_node) = self.fs.inodes.get(fuse::ROOT_ID) else {
return if self.fs.inodes.is_empty() {
Ok(())
} else {
Err(WrappedError::Unrecoverable(other_io_error(
"Root node not found",
)))
};
};
let shared_dir_path = root_node.get_path(&self.fs.proc_self_fd).map_err(|err| {
WrappedError::Fallback(
io::Error::from(err).context("Failed to get shared directory's path"),
)
})?;
for inode_data in self.fs.inodes.iter() {
if self
.cancel
.as_ref()
.map(|c| c.load(Ordering::Relaxed))
.unwrap_or(false)
{
break;
}
if !self.should_update_inode(&inode_data) {
continue;
}
let set_path_result =
set_path_migration_info_from_proc_self_fd(&inode_data, self.fs, &shared_dir_path);
match self.mode {
Mode::Constructor => match set_path_result {
Ok(()) => (),
Err(WrappedError::Fallback(err)) => return Err(WrappedError::Fallback(err)),
Err(WrappedError::Unrecoverable(err)) => {
error!("Inode {}: {}", inode_data.inode, err)
}
},
Mode::ConfirmPaths | Mode::ImplicitPathCheck => {
if let Err(err) = set_path_result {
error!("Inode {}: {}", inode_data.inode, err.into_inner());
} else if let Some(new_info) =
inode_data.migration_info.lock().unwrap().as_ref()
{
info!("Found inode {}: {}", inode_data.inode, new_info.location);
}
}
}
}
Ok(())
}
fn should_update_inode(&self, inode_data: &InodeData) -> bool {
let mut migration_info_locked = inode_data.migration_info.lock().unwrap();
match (&self.mode, migration_info_locked.as_ref()) {
(Mode::ImplicitPathCheck, None) | (Mode::Constructor, Some(_)) => false,
(Mode::ConfirmPaths, None) | (Mode::Constructor, None) => true,
(Mode::ConfirmPaths, Some(migration_info))
| (Mode::ImplicitPathCheck, Some(migration_info)) => {
if let Err(err) = migration_info.check_path_presence(inode_data) {
let migration_info = migration_info_locked.take().unwrap();
warn!(
"Lost inode {} (former location: {}): {}; looking it up through /proc/self/fd",
inode_data.inode, migration_info.location, err
);
true
} else {
false
}
}
}
}
}
fn link_count(inode_data: &InodeData) -> Option<libc::nlink_t> {
inode_data
.get_file()
.ok()
.and_then(|f| statx(&f, None).ok())
.map(|stat| stat.st.st_nlink)
}
pub(in crate::passthrough) fn set_path_migration_info_from_proc_self_fd(
inode_data: &InodeData,
fs: &PassthroughFs,
shared_dir_path: &CStr,
) -> Result<(), WrappedError> {
let abs_path_result = inode_data.get_path(&fs.proc_self_fd);
let Ok(abs_path) = abs_path_result else {
let err = abs_path_result.unwrap_err();
let fall_back = match &err {
InodePathError::FdPathError(FdPathError::Deleted(_)) => {
link_count(inode_data).map(|n| n > 0).unwrap_or(false)
}
InodePathError::OutsideRoot => link_count(inode_data).map(|n| n > 1).unwrap_or(false),
InodePathError::NoFd(_) => false,
InodePathError::FdPathError(_) => true,
};
let err = io::Error::from(err).context("Failed to get path from /proc/self/fd");
return if fall_back {
Err(WrappedError::Fallback(err))
} else {
Err(WrappedError::Unrecoverable(err))
};
};
let rel_path = relative_path(&abs_path, shared_dir_path)
.map_err(|err| {
if link_count(inode_data).map(|n| n > 1).unwrap_or(false) {
WrappedError::Fallback(err)
} else {
WrappedError::Unrecoverable(err)
}
})?
.to_str()
.map_err(|err| {
WrappedError::Unrecoverable(other_io_error(format!(
"Path {abs_path:?} is not a UTF-8 string: {err}"
)))
})?
.to_string();
let path = Path::new(&rel_path);
let mut parent = fs
.inodes
.get_strong(fuse::ROOT_ID)
.map_err(WrappedError::Unrecoverable)?;
for element in path {
let element_cstr = CString::new(element.to_str().unwrap()).unwrap();
let entry = fs
.do_lookup(parent.get().inode, &element_cstr)
.map_err(WrappedError::Unrecoverable)?;
let entry_data = fs.inodes.get(entry.inode).unwrap();
let entry_inode = unsafe { StrongInodeReference::new_no_increment(entry_data, &fs.inodes) };
{
let entry_data = entry_inode.get();
let mut mig_info = entry_data.migration_info.lock().unwrap();
if mig_info.is_none() {
*mig_info = Some(
InodeMigrationInfo::new(
&fs.cfg,
parent,
&element_cstr,
&entry_data.file_or_handle,
)
.map_err(WrappedError::Unrecoverable)?,
);
}
}
parent = entry_inode;
}
if parent.get().inode != inode_data.inode {
return Err(WrappedError::Fallback(other_io_error(format!(
"Inode not found under path reported by /proc/self/fd ({rel_path:?})"
))));
}
Ok(())
}
impl WrappedError {
pub fn into_inner(self) -> io::Error {
match self {
WrappedError::Fallback(err) => err,
WrappedError::Unrecoverable(err) => err,
}
}
}