use crate::fuse;
use crate::passthrough::device_state::preserialization::{
self, HandleMigrationInfo, InodeMigrationInfo,
};
use crate::passthrough::device_state::serialized;
use crate::passthrough::inode_store::InodeData;
use crate::passthrough::mount_fd::MountFds;
use crate::passthrough::util::relative_path;
use crate::passthrough::{Handle, HandleData, MigrationMode, PassthroughFs};
use crate::util::{other_io_error, ResultErrorContext};
use std::collections::{HashMap, HashSet};
use std::convert::TryFrom;
use std::ffi::CString;
use std::io;
use std::sync::atomic::Ordering;
struct MountPathsBuilder<'a> {
mount_fds: &'a MountFds,
shared_dir_path: CString,
}
impl TryFrom<serialized::PassthroughFs> for Vec<u8> {
type Error = io::Error;
fn try_from(state: serialized::PassthroughFs) -> io::Result<Self> {
postcard::to_stdvec(&state).map_err(other_io_error)
}
}
impl From<&PassthroughFs> for serialized::PassthroughFsV2 {
fn from(fs: &PassthroughFs) -> Self {
let handles_map = fs.handles.read().unwrap();
let inodes: Vec<serialized::Inode> = fs.inodes.iter().map(|inode| {
inode
.as_ref()
.as_serialized(fs)
.unwrap_or_else(|err| {
warn!(
"Failed to serialize inode {} (st_dev={}, mnt_id={}, st_ino={}): {err}; marking as invalid",
inode.inode, inode.ids.dev, inode.ids.mnt_id, inode.ids.ino
);
serialized::Inode {
id: inode.inode,
refcount: inode.refcount.load(Ordering::Relaxed),
location: serialized::InodeLocation::Invalid,
file_handle: None,
}
})
}).collect();
let mount_paths = if fs.cfg.migration_mode == MigrationMode::FileHandles {
match MountPathsBuilder::new(fs) {
Ok(mpb) => mpb.build(inodes.iter()),
Err(err) => {
warn!(
"Cannot collect mount points: {err}; will not be able to migrate any inodes"
);
HashMap::new()
}
}
} else {
HashMap::new()
};
let handles = handles_map
.iter()
.map(|(handle, data)| (*handle, data.as_ref()).into())
.collect();
serialized::PassthroughFsV2 {
v1: serialized::PassthroughFsV1 {
inodes,
next_inode: fs.next_inode.load(Ordering::Relaxed),
handles,
next_handle: fs.next_handle.load(Ordering::Relaxed),
negotiated_opts: fs.into(),
},
mount_paths,
}
}
}
impl From<&PassthroughFs> for serialized::NegotiatedOpts {
fn from(fs: &PassthroughFs) -> Self {
serialized::NegotiatedOpts {
writeback: fs.writeback.load(Ordering::Relaxed),
announce_submounts: fs.announce_submounts.load(Ordering::Relaxed),
posix_acl: fs.posix_acl.load(Ordering::Relaxed),
sup_group_extension: fs.sup_group_extension.load(Ordering::Relaxed),
}
}
}
impl InodeData {
fn as_serialized(&self, fs: &PassthroughFs) -> io::Result<serialized::Inode> {
let id = self.inode;
let refcount = self.refcount.load(Ordering::Relaxed);
let migration_info_locked = self.migration_info.lock().unwrap();
let migration_info = migration_info_locked
.as_ref()
.ok_or_else(|| other_io_error("Failed to reconstruct inode location"))?;
assert_eq!(
(id == fuse::ROOT_ID),
matches!(
migration_info.location,
preserialization::InodeLocation::RootNode
)
);
let location = migration_info.as_serialized()?;
let file_handle = if fs.cfg.migration_verify_handles {
let handle = migration_info
.file_handle
.as_ref()
.ok_or_else(|| other_io_error("No prepared file handle found"))?;
Some(handle.clone())
} else {
None
};
Ok(serialized::Inode {
id,
refcount,
location,
file_handle,
})
}
}
impl InodeMigrationInfo {
fn as_serialized(&self) -> io::Result<serialized::InodeLocation> {
Ok(match &self.location {
preserialization::InodeLocation::RootNode => serialized::InodeLocation::RootNode,
preserialization::InodeLocation::Path(preserialization::find_paths::InodePath {
parent,
filename,
}) => {
let parent = unsafe { parent.get_raw() };
let filename = filename.clone();
serialized::InodeLocation::Path { parent, filename }
}
preserialization::InodeLocation::FileHandle(
preserialization::file_handles::FileHandle { handle },
) => serialized::InodeLocation::FileHandle {
handle: handle.clone(),
},
})
}
}
impl From<(Handle, &HandleData)> for serialized::Handle {
fn from(handle: (Handle, &HandleData)) -> Self {
let source = (&handle.1.migration_info).into();
serialized::Handle {
id: handle.0,
inode: handle.1.inode,
source,
}
}
}
impl From<&HandleMigrationInfo> for serialized::HandleSource {
fn from(repr: &HandleMigrationInfo) -> Self {
match repr {
HandleMigrationInfo::OpenInode { flags } => {
serialized::HandleSource::OpenInode { flags: *flags }
}
}
}
}
impl<'a> MountPathsBuilder<'a> {
fn new(fs: &'a PassthroughFs) -> io::Result<Self> {
assert!(fs.cfg.migration_mode == MigrationMode::FileHandles);
let Some(mount_fds) = fs.mount_fds.as_ref() else {
return Err(other_io_error("No mount FD map found"));
};
let Some(root_node) = fs.inodes.get(fuse::ROOT_ID) else {
if fs.inodes.is_empty() {
return Ok(MountPathsBuilder {
mount_fds,
shared_dir_path: CString::new("").unwrap(),
});
} else {
return Err(other_io_error(
"Root node (shared directory) not in inode store",
));
}
};
let shared_dir_path = root_node
.get_path(&fs.proc_self_fd)
.map_err(io::Error::from)
.err_context(|| "Failed to get shared directory path")?;
Ok(MountPathsBuilder {
mount_fds,
shared_dir_path: shared_dir_path.to_owned(),
})
}
fn get_mount_path(&mut self, mnt_id: u64) -> io::Result<String> {
let path = self
.mount_fds
.get_mount_root(mnt_id)
.map_err(other_io_error)?;
let c_path = CString::new(path.clone())
.map_err(|_| other_io_error(format!("Cannot convert path ({path}) to C string")))?;
let c_relative_path = match relative_path(&c_path, &self.shared_dir_path) {
Ok(rp) => rp,
Err(_) => return Ok(".".to_string()),
};
let relative_path = c_relative_path.to_str().map_err(|_| {
other_io_error(format!(
"Path {c_relative_path:?} cannot be converted to UTF-8"
))
})?;
if relative_path.is_empty() {
Ok(".".to_string())
} else {
Ok(relative_path.to_string())
}
}
fn build<'b, I: Iterator<Item = &'b serialized::Inode>>(
mut self,
iter: I,
) -> HashMap<u64, String> {
let mount_ids: HashSet<u64> = iter
.filter_map(|si| match &si.location {
serialized::InodeLocation::FileHandle { handle } => Some(handle.mount_id()),
_ => None,
})
.collect();
let mut map = HashMap::new();
for mount_id in mount_ids {
match self.get_mount_path(mount_id) {
Ok(path) => {
map.insert(mount_id, path);
}
Err(err) => warn!(
"Failed to get mount ID {mount_id}'s root: {err}; \
will not be able to migrate inodes on this filesystem"
),
}
}
map
}
}