use super::{InodeLocation, InodeMigrationInfo};
use crate::filesystem::DirectoryIterator;
use crate::fuse;
use crate::passthrough::file_handle::{FileHandle, SerializableFileHandle};
use crate::passthrough::inode_store::{InodeData, InodeIds, StrongInodeReference};
use crate::passthrough::stat::statx;
use crate::passthrough::{FileOrHandle, PassthroughFs};
use crate::read_dir::ReadDir;
use crate::util::{other_io_error, ResultErrorContext};
use std::convert::{TryFrom, TryInto};
use std::ffi::{CStr, CString};
use std::fmt::{self, Display};
use std::fs::File;
use std::io;
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
pub(in crate::passthrough) struct InodePath {
pub parent: StrongInodeReference,
pub filename: String,
}
pub(in crate::passthrough::device_state) struct Constructor<'a> {
fs: &'a PassthroughFs,
cancel: Arc<AtomicBool>,
}
impl InodePath {
pub fn new_with_cstr(parent_ref: StrongInodeReference, filename: &CStr) -> io::Result<Self> {
let utf8_name = filename.to_str().map_err(|err| {
other_io_error(format!(
"Cannot convert filename into UTF-8: {filename:?}: {err}"
))
})?;
Ok(InodePath {
parent: parent_ref,
filename: utf8_name.to_string(),
})
}
pub(super) fn for_each_strong_reference<F: FnMut(StrongInodeReference)>(self, mut f: F) {
f(self.parent);
}
pub(super) fn check_presence(
&self,
inode_data: &InodeData,
full_info: &InodeMigrationInfo,
) -> io::Result<()> {
let filename = CString::new(self.filename.clone())?;
let parent_fd = self.parent.get().get_file()?;
let st = statx(&parent_fd, Some(&filename))?;
if st.st.st_dev != inode_data.ids.dev {
return Err(other_io_error(format!(
"Device ID differs: Expected {}, found {}",
inode_data.ids.dev, st.st.st_dev
)));
}
let (fh, fh_ref) = if let Some(fh_ref) = full_info.file_handle.as_ref() {
(None, Some(fh_ref))
} else if let Ok(fh) = SerializableFileHandle::try_from(&inode_data.file_or_handle) {
(Some(fh), None)
} else {
(None, None)
};
if let Some(fh) = fh_ref.or(fh.as_ref()) {
let actual_fh = FileHandle::from_name_at_fail_hard(&parent_fd, &filename)
.err_context(|| "Failed to generate file handle")?;
fh.require_equal_without_mount_id(&actual_fh.into())
.map_err(other_io_error)
} else {
if st.st.st_ino != inode_data.ids.ino {
return Err(other_io_error(format!(
"Inode ID differs: Expected {}, found {}",
inode_data.ids.ino, st.st.st_ino
)));
}
Ok(())
}
}
}
impl From<InodePath> for InodeLocation {
fn from(path: InodePath) -> Self {
InodeLocation::Path(path)
}
}
impl Display for InodePath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let parent = self.parent.get();
let parent_mig_info_locked = parent.migration_info.lock().unwrap();
if let Some(parent_mig_info) = parent_mig_info_locked.as_ref() {
write!(f, "{}/{}", parent_mig_info.location, self.filename)
} else {
write!(f, "[inode {}]/{}", parent.inode, self.filename)
}
}
}
impl<'a> Constructor<'a> {
pub fn new(fs: &'a PassthroughFs, cancel: Arc<AtomicBool>) -> Self {
Constructor { fs, cancel }
}
pub fn execute(self) {
if let Ok(root) = self.fs.inodes.get_strong(fuse::ROOT_ID) {
self.recurse_from(root);
}
}
fn recurse_from(&self, root_ref: StrongInodeReference) {
let mut dir_buf = vec![0u8; 1024];
let mut remaining_dirs = vec![root_ref];
while let Some(inode_ref) = remaining_dirs.pop() {
let dirfd = match inode_ref.get().open_file(
libc::O_RDONLY | libc::O_NOFOLLOW | libc::O_CLOEXEC,
&self.fs.proc_self_fd,
) {
Ok(fd) => fd,
Err(err) => {
let dir_id = inode_ref.get().identify(&self.fs.proc_self_fd);
warn!("Failed to recurse into {dir_id}: {err}");
continue;
}
};
loop {
let read_dir_result = unsafe { ReadDir::new_no_seek(&dirfd, dir_buf.as_mut()) };
let mut entries = match read_dir_result {
Ok(entries) => entries,
Err(err) => {
let dir_id = inode_ref.get().identify(&self.fs.proc_self_fd);
warn!("Failed to read directory entries of {dir_id}: {err}");
break;
}
};
if entries.remaining() == 0 {
break;
}
while let Some(entry) = entries.next() {
if self.cancel.load(Ordering::Relaxed) {
return;
}
match self.discover(&inode_ref, &dirfd, entry.name) {
Ok(Some(entry_inode)) => {
remaining_dirs.push(entry_inode);
}
Ok(None) => (),
Err(err) => {
let dir_id = inode_ref.get().identify(&self.fs.proc_self_fd);
let name = entry.name.to_string_lossy();
warn!("Failed to discover entry {name} of {dir_id}: {err}");
}
}
}
}
}
}
fn discover<F: AsRawFd>(
&self,
parent_reference: &StrongInodeReference,
parent_fd: &F,
name: &CStr,
) -> io::Result<Option<StrongInodeReference>> {
let utf8_name = name.to_str().map_err(|err| {
other_io_error(format!(
"Cannot convert filename into UTF-8: {name:?}: {err}",
))
})?;
if utf8_name == "." || utf8_name == ".." {
return Ok(None);
}
let path_fd = {
let fd = self
.fs
.open_relative_to(parent_fd, name, libc::O_PATH, None)?;
unsafe { File::from_raw_fd(fd) }
};
let stat = statx(&path_fd, None)?;
let handle = self.fs.get_file_handle_opt(&path_fd, &stat)?;
let ids = InodeIds {
ino: stat.st.st_ino,
dev: stat.st.st_dev,
mnt_id: stat.mnt_id,
};
let is_directory = stat.st.st_mode & libc::S_IFMT == libc::S_IFDIR;
if let Ok(inode_ref) = self.fs.inodes.claim_inode(handle.as_ref(), &ids) {
let mig_info = InodeMigrationInfo::new_internal(
&self.fs.cfg,
InodePath {
parent: StrongInodeReference::clone(parent_reference),
filename: utf8_name.to_string(),
},
|| {
Ok(match &handle {
Some(h) => h.into(),
None => FileHandle::from_fd_fail_hard(&path_fd)?.into(),
})
},
)?;
*inode_ref.get().migration_info.lock().unwrap() = Some(mig_info);
return Ok(is_directory.then_some(inode_ref));
}
if !is_directory {
return Ok(None);
}
let file_or_handle = if let Some(h) = handle.as_ref() {
FileOrHandle::Handle(self.fs.make_file_handle_openable(h)?)
} else {
FileOrHandle::File(self.fs.guest_fds.allocate(path_fd)?)
};
let mig_info = InodeMigrationInfo::new_internal(
&self.fs.cfg,
InodePath {
parent: StrongInodeReference::clone(parent_reference),
filename: utf8_name.to_string(),
},
|| (&file_or_handle).try_into(),
)?;
let new_inode = InodeData {
inode: self.fs.next_inode.fetch_add(1, Ordering::Relaxed),
file_or_handle,
refcount: AtomicU64::new(1),
ids,
mode: stat.st.st_mode,
migration_info: Mutex::new(Some(mig_info)),
};
Ok(Some(self.fs.inodes.get_or_insert(new_inode)?))
}
}