2026-07-26 00:43:30 +09:00
|
|
|
use std::io::Write as _;
|
|
|
|
|
|
2026-07-19 05:12:57 +09:00
|
|
|
use super::nodes::{
|
|
|
|
|
DirectoryStream, OpenFile, ResolvedEntry, bsd_lock_operation, errno, fuse_lock, record_lock,
|
|
|
|
|
reply_result, reply_xattr, timestamp, unsupported_lock_flags, writable_xattr,
|
|
|
|
|
};
|
|
|
|
|
use super::{
|
2026-07-26 00:43:30 +09:00
|
|
|
Arc, AsFd, BsdFileFlags, Classification, CopyFileRangeFlags, DirectoryEntry, Errno,
|
|
|
|
|
FallocateFlags, FcntlArg, FileAttr, FileExt, FileHandle, FileType, Filesystem, FopenFlags,
|
|
|
|
|
FuseRenameFlags, Generation, Gid, INodeNo, IgnoredWorkspace, InitFlags, KernelConfig,
|
|
|
|
|
LockFlags, LockOwner, Mode, OFlags, OpenFlags, Ordering, OsStr, OsString, Path, RenameFlags,
|
|
|
|
|
ReplyAttr, ReplyCreate, ReplyData, ReplyDirectory, ReplyEmpty, ReplyEntry, ReplyLock,
|
|
|
|
|
ReplyLseek, ReplyOpen, ReplyStatfs, ReplyWrite, ReplyXattr, Request, SeekFrom, SystemTime, TTL,
|
|
|
|
|
TimeOrNow, Timestamps, Uid, WriteFlags, XattrFlags, bsd_flock, copy_file_range, fallocate,
|
|
|
|
|
fchmod, fchown, fcntl, fgetxattr, flistxattr, fremovexattr, fsetxattr, fstat, fstatvfs,
|
|
|
|
|
ftruncate, futimens, io, mknodat, open_flags, seek,
|
2026-07-19 05:12:57 +09:00
|
|
|
};
|
|
|
|
|
|
2026-07-19 03:02:44 +09:00
|
|
|
impl Filesystem for IgnoredWorkspace {
|
|
|
|
|
fn init(&mut self, _request: &Request, config: &mut KernelConfig) -> io::Result<()> {
|
|
|
|
|
config
|
|
|
|
|
.add_capabilities(
|
|
|
|
|
InitFlags::FUSE_AUTO_INVAL_DATA
|
|
|
|
|
| InitFlags::FUSE_POSIX_ACL
|
|
|
|
|
| InitFlags::FUSE_POSIX_LOCKS
|
|
|
|
|
| InitFlags::FUSE_FLOCK_LOCKS,
|
|
|
|
|
)
|
|
|
|
|
.map_err(|unsupported| {
|
|
|
|
|
io::Error::other(format!(
|
|
|
|
|
"required FUSE capabilities are unavailable: {unsupported:?}"
|
|
|
|
|
))
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn lookup(&self, _request: &Request, parent: INodeNo, name: &OsStr, reply: ReplyEntry) {
|
2026-07-26 00:43:30 +09:00
|
|
|
let _namespace = self
|
|
|
|
|
.namespace
|
|
|
|
|
.read()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
2026-07-19 03:02:44 +09:00
|
|
|
match self.child_path(parent, name) {
|
|
|
|
|
Ok(path) => self.reply_entry(&path, reply),
|
|
|
|
|
Err(error) => reply.error(errno(&error)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn getattr(
|
|
|
|
|
&self,
|
|
|
|
|
_request: &Request,
|
|
|
|
|
inode: INodeNo,
|
|
|
|
|
handle: Option<FileHandle>,
|
|
|
|
|
reply: ReplyAttr,
|
|
|
|
|
) {
|
2026-07-26 00:43:30 +09:00
|
|
|
let _namespace = self
|
|
|
|
|
.namespace
|
|
|
|
|
.read()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
2026-07-19 03:02:44 +09:00
|
|
|
let result = if let Some(handle) = handle {
|
|
|
|
|
self.with_file(handle, |open| {
|
|
|
|
|
let stat = fstat(open.file.as_fd())?;
|
|
|
|
|
let path = self.path_for(inode)?;
|
|
|
|
|
self.attributes(&ResolvedEntry {
|
|
|
|
|
path,
|
|
|
|
|
backing: open.backing,
|
|
|
|
|
stat,
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
self.path_for(inode).and_then(|path| {
|
|
|
|
|
let snapshot = self.classifier.snapshot_for(&path);
|
|
|
|
|
self.resolve(&snapshot, &path)
|
|
|
|
|
.and_then(|entry| self.attributes(&entry))
|
|
|
|
|
})
|
|
|
|
|
};
|
|
|
|
|
match result {
|
|
|
|
|
Ok(attributes) => reply.attr(&TTL, &attributes),
|
|
|
|
|
Err(error) => reply.error(errno(&error)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn setattr(
|
|
|
|
|
&self,
|
|
|
|
|
_request: &Request,
|
|
|
|
|
inode: INodeNo,
|
|
|
|
|
mode: Option<u32>,
|
|
|
|
|
uid: Option<u32>,
|
|
|
|
|
gid: Option<u32>,
|
|
|
|
|
size: Option<u64>,
|
|
|
|
|
atime: Option<TimeOrNow>,
|
|
|
|
|
mtime: Option<TimeOrNow>,
|
|
|
|
|
_ctime: Option<SystemTime>,
|
|
|
|
|
handle: Option<FileHandle>,
|
|
|
|
|
_crtime: Option<SystemTime>,
|
|
|
|
|
_chgtime: Option<SystemTime>,
|
|
|
|
|
_bkuptime: Option<SystemTime>,
|
|
|
|
|
_flags: Option<BsdFileFlags>,
|
|
|
|
|
reply: ReplyAttr,
|
|
|
|
|
) {
|
2026-07-26 00:43:30 +09:00
|
|
|
let _namespace = self
|
|
|
|
|
.namespace
|
|
|
|
|
.read()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
2026-07-19 03:02:44 +09:00
|
|
|
let operation = |open: &OpenFile| -> io::Result<FileAttr> {
|
2026-07-26 00:43:30 +09:00
|
|
|
open.migration_quiesced.store(false, Ordering::Release);
|
2026-07-19 03:02:44 +09:00
|
|
|
if let Some(mode) = mode {
|
|
|
|
|
fchmod(open.file.as_fd(), Mode::from_raw_mode(mode & 0o7777))?;
|
|
|
|
|
}
|
|
|
|
|
if uid.is_some() || gid.is_some() {
|
|
|
|
|
fchown(
|
|
|
|
|
open.file.as_fd(),
|
|
|
|
|
uid.map(Uid::from_raw),
|
|
|
|
|
gid.map(Gid::from_raw),
|
|
|
|
|
)?;
|
|
|
|
|
}
|
|
|
|
|
if let Some(size) = size {
|
|
|
|
|
ftruncate(open.file.as_fd(), size)?;
|
|
|
|
|
}
|
|
|
|
|
if atime.is_some() || mtime.is_some() {
|
|
|
|
|
futimens(
|
|
|
|
|
open.file.as_fd(),
|
|
|
|
|
&Timestamps {
|
|
|
|
|
last_access: timestamp(atime),
|
|
|
|
|
last_modification: timestamp(mtime),
|
|
|
|
|
},
|
|
|
|
|
)?;
|
|
|
|
|
}
|
2026-07-26 00:43:30 +09:00
|
|
|
self.record_changed(open.backing, &open.path);
|
2026-07-19 03:02:44 +09:00
|
|
|
let path = self.path_for(inode)?;
|
|
|
|
|
self.attributes(&ResolvedEntry {
|
|
|
|
|
path,
|
|
|
|
|
backing: open.backing,
|
|
|
|
|
stat: fstat(open.file.as_fd())?,
|
|
|
|
|
})
|
|
|
|
|
};
|
|
|
|
|
let result = if let Some(handle) = handle {
|
|
|
|
|
self.with_file(handle, operation)
|
|
|
|
|
} else {
|
|
|
|
|
let path = match self.path_for(inode) {
|
|
|
|
|
Ok(path) => path,
|
|
|
|
|
Err(error) => {
|
|
|
|
|
reply.error(errno(&error));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
let snapshot = self.classifier.snapshot_for(&path);
|
|
|
|
|
let entry = match self.resolve(&snapshot, &path) {
|
|
|
|
|
Ok(entry) => entry,
|
|
|
|
|
Err(error) => {
|
|
|
|
|
reply.error(errno(&error));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
if entry.stat.st_mode & libc::S_IFMT == libc::S_IFLNK {
|
|
|
|
|
if mode.is_some() || uid.is_some() || gid.is_some() || size.is_some() {
|
|
|
|
|
Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
|
|
|
|
|
} else {
|
|
|
|
|
self.root(entry.backing)
|
|
|
|
|
.set_symlink_times(
|
|
|
|
|
&path,
|
|
|
|
|
&Timestamps {
|
|
|
|
|
last_access: timestamp(atime),
|
|
|
|
|
last_modification: timestamp(mtime),
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.and_then(|()| {
|
|
|
|
|
self.root(entry.backing).stat(&path).and_then(|stat| {
|
|
|
|
|
self.attributes(&ResolvedEntry {
|
|
|
|
|
path,
|
|
|
|
|
backing: entry.backing,
|
|
|
|
|
stat,
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
let flags = if size.is_some() {
|
|
|
|
|
libc::O_RDWR
|
|
|
|
|
} else {
|
|
|
|
|
libc::O_RDONLY
|
|
|
|
|
};
|
|
|
|
|
self.open_file(inode, flags).and_then(|handle| {
|
|
|
|
|
let result = self.with_file(FileHandle(handle), operation);
|
|
|
|
|
self.files
|
|
|
|
|
.lock()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
|
|
|
.remove(&handle);
|
|
|
|
|
result
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
match result {
|
|
|
|
|
Ok(attributes) => reply.attr(&TTL, &attributes),
|
|
|
|
|
Err(error) => reply.error(errno(&error)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn readlink(&self, _request: &Request, inode: INodeNo, reply: ReplyData) {
|
2026-07-26 00:43:30 +09:00
|
|
|
let _namespace = self
|
|
|
|
|
.namespace
|
|
|
|
|
.read()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
2026-07-19 03:02:44 +09:00
|
|
|
let result = self.path_for(inode).and_then(|path| {
|
|
|
|
|
let snapshot = self.classifier.snapshot_for(&path);
|
|
|
|
|
let entry = self.resolve(&snapshot, &path)?;
|
|
|
|
|
self.root(entry.backing).readlink(&path)
|
|
|
|
|
});
|
|
|
|
|
match result {
|
|
|
|
|
Ok(bytes) => reply.data(&bytes),
|
|
|
|
|
Err(error) => reply.error(errno(&error)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn mknod(
|
|
|
|
|
&self,
|
|
|
|
|
_request: &Request,
|
|
|
|
|
parent: INodeNo,
|
|
|
|
|
name: &OsStr,
|
|
|
|
|
mode: u32,
|
|
|
|
|
umask: u32,
|
|
|
|
|
rdev: u32,
|
|
|
|
|
reply: ReplyEntry,
|
|
|
|
|
) {
|
2026-07-26 00:43:30 +09:00
|
|
|
let _namespace = self
|
|
|
|
|
.namespace
|
|
|
|
|
.read()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
2026-07-19 03:02:44 +09:00
|
|
|
if matches!(mode & libc::S_IFMT, libc::S_IFCHR | libc::S_IFBLK) {
|
|
|
|
|
reply.error(Errno::EPERM);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let result = (|| {
|
|
|
|
|
let path = self.child_path(parent, name)?;
|
|
|
|
|
let snapshot = self.classifier.snapshot_for(&path);
|
|
|
|
|
let backing = self.create_backing(&snapshot, &path, false)?;
|
|
|
|
|
let root = self.root(backing);
|
|
|
|
|
let parent_path = path.parent().unwrap_or_else(|| Path::new(""));
|
|
|
|
|
let directory = root.open_directory(parent_path)?;
|
|
|
|
|
mknodat(
|
|
|
|
|
directory.as_fd(),
|
|
|
|
|
name,
|
|
|
|
|
rustix::fs::FileType::from_raw_mode(mode),
|
|
|
|
|
Mode::from_raw_mode(mode & !umask),
|
|
|
|
|
rustix::fs::makedev(rdev >> 8, rdev & 0xff),
|
|
|
|
|
)?;
|
2026-07-26 00:43:30 +09:00
|
|
|
self.record_created(backing, &path)?;
|
2026-07-19 03:02:44 +09:00
|
|
|
Ok(path)
|
|
|
|
|
})();
|
|
|
|
|
match result {
|
|
|
|
|
Ok(path) => self.reply_entry(&path, reply),
|
|
|
|
|
Err(error) => reply.error(errno(&error)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn mkdir(
|
|
|
|
|
&self,
|
|
|
|
|
_request: &Request,
|
|
|
|
|
parent: INodeNo,
|
|
|
|
|
name: &OsStr,
|
|
|
|
|
mode: u32,
|
|
|
|
|
umask: u32,
|
|
|
|
|
reply: ReplyEntry,
|
|
|
|
|
) {
|
2026-07-26 00:43:30 +09:00
|
|
|
let _namespace = self
|
|
|
|
|
.namespace
|
|
|
|
|
.read()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
2026-07-19 03:02:44 +09:00
|
|
|
let result = (|| {
|
|
|
|
|
let path = self.child_path(parent, name)?;
|
|
|
|
|
let snapshot = self.classifier.snapshot_for(&path);
|
|
|
|
|
let backing = self.create_backing(&snapshot, &path, true)?;
|
|
|
|
|
self.root(backing)
|
|
|
|
|
.mkdir(&path, Mode::from_raw_mode((mode & !umask) & 0o7777))?;
|
2026-07-26 00:43:30 +09:00
|
|
|
self.record_created(backing, &path)?;
|
2026-07-19 03:02:44 +09:00
|
|
|
Ok(path)
|
|
|
|
|
})();
|
|
|
|
|
match result {
|
|
|
|
|
Ok(path) => self.reply_entry(&path, reply),
|
|
|
|
|
Err(error) => reply.error(errno(&error)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn unlink(&self, _request: &Request, parent: INodeNo, name: &OsStr, reply: ReplyEmpty) {
|
2026-07-26 00:43:30 +09:00
|
|
|
let _namespace = self
|
|
|
|
|
.namespace
|
|
|
|
|
.read()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
2026-07-19 03:02:44 +09:00
|
|
|
self.remove(parent, name, false, reply);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn rmdir(&self, _request: &Request, parent: INodeNo, name: &OsStr, reply: ReplyEmpty) {
|
2026-07-26 00:43:30 +09:00
|
|
|
let _namespace = self
|
|
|
|
|
.namespace
|
|
|
|
|
.read()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
2026-07-19 03:02:44 +09:00
|
|
|
self.remove(parent, name, true, reply);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn symlink(
|
|
|
|
|
&self,
|
|
|
|
|
_request: &Request,
|
|
|
|
|
parent: INodeNo,
|
|
|
|
|
name: &OsStr,
|
|
|
|
|
target: &Path,
|
|
|
|
|
reply: ReplyEntry,
|
|
|
|
|
) {
|
2026-07-26 00:43:30 +09:00
|
|
|
let _namespace = self
|
|
|
|
|
.namespace
|
|
|
|
|
.read()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
2026-07-19 03:02:44 +09:00
|
|
|
let result = (|| {
|
|
|
|
|
let path = self.child_path(parent, name)?;
|
|
|
|
|
let snapshot = self.classifier.snapshot_for(&path);
|
|
|
|
|
let backing = self.create_backing(&snapshot, &path, false)?;
|
|
|
|
|
self.root(backing).symlink(target, &path)?;
|
2026-07-26 00:43:30 +09:00
|
|
|
self.record_created(backing, &path)?;
|
2026-07-19 03:02:44 +09:00
|
|
|
Ok(path)
|
|
|
|
|
})();
|
|
|
|
|
match result {
|
|
|
|
|
Ok(path) => self.reply_entry(&path, reply),
|
|
|
|
|
Err(error) => reply.error(errno(&error)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn rename(
|
|
|
|
|
&self,
|
|
|
|
|
_request: &Request,
|
|
|
|
|
parent: INodeNo,
|
|
|
|
|
name: &OsStr,
|
|
|
|
|
new_parent: INodeNo,
|
|
|
|
|
new_name: &OsStr,
|
|
|
|
|
flags: FuseRenameFlags,
|
|
|
|
|
reply: ReplyEmpty,
|
|
|
|
|
) {
|
2026-07-26 00:43:30 +09:00
|
|
|
let _namespace = self
|
|
|
|
|
.namespace
|
|
|
|
|
.write()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
2026-07-19 03:02:44 +09:00
|
|
|
if flags.contains(FuseRenameFlags::RENAME_WHITEOUT) {
|
|
|
|
|
reply.error(Errno::EOPNOTSUPP);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let result = (|| {
|
|
|
|
|
let source = self.child_path(parent, name)?;
|
|
|
|
|
let destination = self.child_path(new_parent, new_name)?;
|
|
|
|
|
let snapshot = self.classifier.snapshot_for_paths(&[&source, &destination]);
|
|
|
|
|
let source_entry = self.resolve(&snapshot, &source)?;
|
|
|
|
|
let destination_backing = self.classify(
|
|
|
|
|
&snapshot,
|
|
|
|
|
&destination,
|
|
|
|
|
source_entry.stat.st_mode & libc::S_IFMT == libc::S_IFDIR,
|
|
|
|
|
);
|
|
|
|
|
if source_entry.backing != destination_backing {
|
2026-07-26 00:43:30 +09:00
|
|
|
let supported_flags =
|
|
|
|
|
flags.is_empty() || flags == FuseRenameFlags::RENAME_NOREPLACE;
|
|
|
|
|
let directory = source_entry.stat.st_mode & libc::S_IFMT == libc::S_IFDIR;
|
|
|
|
|
if source_entry.backing != Classification::Host
|
|
|
|
|
|| destination_backing != Classification::Isolated
|
|
|
|
|
{
|
|
|
|
|
return Err(io::Error::from_raw_os_error(libc::EXDEV));
|
|
|
|
|
}
|
|
|
|
|
if !supported_flags {
|
|
|
|
|
let _ = writeln!(
|
|
|
|
|
io::stderr().lock(),
|
|
|
|
|
"lazaret ignored-workspace refused host-to-isolated migration: unsupported rename flags"
|
|
|
|
|
);
|
|
|
|
|
return Err(io::Error::from_raw_os_error(libc::EXDEV));
|
|
|
|
|
}
|
|
|
|
|
if !directory {
|
|
|
|
|
let _ = writeln!(
|
|
|
|
|
io::stderr().lock(),
|
|
|
|
|
"lazaret ignored-workspace refused host-to-isolated migration: source is not a directory"
|
|
|
|
|
);
|
|
|
|
|
return Err(io::Error::from_raw_os_error(libc::EXDEV));
|
|
|
|
|
}
|
|
|
|
|
if self.wait_for_unquiesced_descendants(&source) {
|
|
|
|
|
let _ = writeln!(
|
|
|
|
|
io::stderr().lock(),
|
|
|
|
|
"lazaret ignored-workspace refused host-to-isolated migration: source has an open descendant"
|
|
|
|
|
);
|
|
|
|
|
return Err(io::Error::from_raw_os_error(libc::EXDEV));
|
|
|
|
|
}
|
|
|
|
|
let mut provenance = self
|
|
|
|
|
.provenance
|
|
|
|
|
.lock()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
|
|
|
|
crate::migration::migrate(
|
|
|
|
|
&self.host,
|
|
|
|
|
&self.isolated,
|
|
|
|
|
&provenance,
|
|
|
|
|
&source,
|
|
|
|
|
&destination,
|
|
|
|
|
)?;
|
|
|
|
|
provenance.remove_tree(&source);
|
|
|
|
|
return Ok(());
|
2026-07-19 03:02:44 +09:00
|
|
|
}
|
|
|
|
|
if destination == Path::new(".git/index") {
|
|
|
|
|
self.require_index_publication_safe(source_entry.backing, &source, &snapshot)?;
|
|
|
|
|
}
|
|
|
|
|
let flags = RenameFlags::from_bits_retain(flags.bits());
|
|
|
|
|
self.root(source_entry.backing)
|
2026-07-26 00:43:30 +09:00
|
|
|
.rename(&source, &destination, flags)?;
|
|
|
|
|
if source_entry.backing == Classification::Host {
|
|
|
|
|
let mut provenance = self
|
|
|
|
|
.provenance
|
|
|
|
|
.lock()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
|
|
|
|
provenance.remove_tree(&destination);
|
|
|
|
|
provenance.rename_tree(&source, &destination);
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
2026-07-19 03:02:44 +09:00
|
|
|
})();
|
|
|
|
|
reply_result(result, reply);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn link(
|
|
|
|
|
&self,
|
|
|
|
|
_request: &Request,
|
|
|
|
|
inode: INodeNo,
|
|
|
|
|
new_parent: INodeNo,
|
|
|
|
|
new_name: &OsStr,
|
|
|
|
|
reply: ReplyEntry,
|
|
|
|
|
) {
|
2026-07-26 00:43:30 +09:00
|
|
|
let _namespace = self
|
|
|
|
|
.namespace
|
|
|
|
|
.read()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
2026-07-19 03:02:44 +09:00
|
|
|
let result = (|| {
|
|
|
|
|
let source = self.path_for(inode)?;
|
|
|
|
|
let destination = self.child_path(new_parent, new_name)?;
|
|
|
|
|
let snapshot = self.classifier.snapshot_for_paths(&[&source, &destination]);
|
|
|
|
|
let source_entry = self.resolve(&snapshot, &source)?;
|
|
|
|
|
let destination_backing = self.classify(&snapshot, &destination, false);
|
|
|
|
|
if source_entry.backing != destination_backing {
|
|
|
|
|
return Err(io::Error::from_raw_os_error(libc::EXDEV));
|
|
|
|
|
}
|
|
|
|
|
self.root(source_entry.backing)
|
|
|
|
|
.link(&source, &destination)?;
|
|
|
|
|
Ok(destination)
|
|
|
|
|
})();
|
|
|
|
|
match result {
|
|
|
|
|
Ok(path) => self.reply_entry(&path, reply),
|
|
|
|
|
Err(error) => reply.error(errno(&error)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn open(&self, _request: &Request, inode: INodeNo, flags: OpenFlags, reply: ReplyOpen) {
|
2026-07-26 00:43:30 +09:00
|
|
|
let _namespace = self
|
|
|
|
|
.namespace
|
|
|
|
|
.read()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
2026-07-19 03:02:44 +09:00
|
|
|
match self.open_file(inode, flags.0) {
|
|
|
|
|
Ok(handle) => reply.opened(FileHandle(handle), FopenFlags::empty()),
|
|
|
|
|
Err(error) => reply.error(errno(&error)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn read(
|
|
|
|
|
&self,
|
|
|
|
|
_request: &Request,
|
|
|
|
|
_inode: INodeNo,
|
|
|
|
|
handle: FileHandle,
|
|
|
|
|
offset: u64,
|
|
|
|
|
size: u32,
|
|
|
|
|
_flags: OpenFlags,
|
|
|
|
|
_lock_owner: Option<LockOwner>,
|
|
|
|
|
reply: ReplyData,
|
|
|
|
|
) {
|
2026-07-26 00:43:30 +09:00
|
|
|
let _namespace = self
|
|
|
|
|
.namespace
|
|
|
|
|
.read()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
2026-07-19 03:02:44 +09:00
|
|
|
let result = self.with_file(handle, |open| {
|
|
|
|
|
let mut bytes = vec![0; size as usize];
|
|
|
|
|
let read = open.file.read_at(&mut bytes, offset)?;
|
|
|
|
|
bytes.truncate(read);
|
|
|
|
|
Ok(bytes)
|
|
|
|
|
});
|
|
|
|
|
match result {
|
|
|
|
|
Ok(bytes) => reply.data(&bytes),
|
|
|
|
|
Err(error) => reply.error(errno(&error)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn write(
|
|
|
|
|
&self,
|
|
|
|
|
_request: &Request,
|
|
|
|
|
_inode: INodeNo,
|
|
|
|
|
handle: FileHandle,
|
|
|
|
|
offset: u64,
|
|
|
|
|
data: &[u8],
|
|
|
|
|
_write_flags: WriteFlags,
|
|
|
|
|
flags: OpenFlags,
|
|
|
|
|
_lock_owner: Option<LockOwner>,
|
|
|
|
|
reply: ReplyWrite,
|
|
|
|
|
) {
|
|
|
|
|
let result = self.with_file(handle, |open| {
|
2026-07-26 00:43:30 +09:00
|
|
|
open.migration_quiesced.store(false, Ordering::Release);
|
2026-07-19 03:02:44 +09:00
|
|
|
let offset = if flags.0 & libc::O_APPEND != 0 {
|
|
|
|
|
open.file.metadata()?.len()
|
|
|
|
|
} else {
|
|
|
|
|
offset
|
|
|
|
|
};
|
2026-07-26 00:43:30 +09:00
|
|
|
let written = open.file.write_at(data, offset)?;
|
|
|
|
|
self.record_changed(open.backing, &open.path);
|
|
|
|
|
Ok(written)
|
2026-07-19 03:02:44 +09:00
|
|
|
});
|
|
|
|
|
match result {
|
|
|
|
|
Ok(written) => reply.written(u32::try_from(written).unwrap_or(u32::MAX)),
|
|
|
|
|
Err(error) => reply.error(errno(&error)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn flush(
|
|
|
|
|
&self,
|
|
|
|
|
_request: &Request,
|
|
|
|
|
_inode: INodeNo,
|
|
|
|
|
handle: FileHandle,
|
|
|
|
|
owner: LockOwner,
|
|
|
|
|
reply: ReplyEmpty,
|
|
|
|
|
) {
|
|
|
|
|
let result = self.with_file(handle, |open| {
|
|
|
|
|
self.release_posix_locks(open, owner)?;
|
2026-07-26 00:43:30 +09:00
|
|
|
open.file.sync_data()?;
|
|
|
|
|
open.migration_quiesced.store(true, Ordering::Release);
|
|
|
|
|
Ok(())
|
2026-07-19 03:02:44 +09:00
|
|
|
});
|
|
|
|
|
reply_result(result, reply);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn release(
|
|
|
|
|
&self,
|
|
|
|
|
_request: &Request,
|
|
|
|
|
_inode: INodeNo,
|
|
|
|
|
handle: FileHandle,
|
|
|
|
|
_flags: OpenFlags,
|
|
|
|
|
owner: Option<LockOwner>,
|
|
|
|
|
_flush: bool,
|
|
|
|
|
reply: ReplyEmpty,
|
|
|
|
|
) {
|
|
|
|
|
let open = self
|
|
|
|
|
.files
|
|
|
|
|
.lock()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
|
|
|
.remove(&handle.0);
|
|
|
|
|
let result = match (open, owner) {
|
|
|
|
|
(Some(open), Some(owner)) => self.release_posix_locks(&open, owner),
|
|
|
|
|
(Some(_), None) => Ok(()),
|
|
|
|
|
(None, _) => Err(io::Error::from_raw_os_error(libc::EBADF)),
|
|
|
|
|
};
|
|
|
|
|
reply_result(result, reply);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn fsync(
|
|
|
|
|
&self,
|
|
|
|
|
_request: &Request,
|
|
|
|
|
_inode: INodeNo,
|
|
|
|
|
handle: FileHandle,
|
|
|
|
|
datasync: bool,
|
|
|
|
|
reply: ReplyEmpty,
|
|
|
|
|
) {
|
|
|
|
|
let result = self.with_file(handle, |open| {
|
|
|
|
|
if datasync {
|
|
|
|
|
open.file.sync_data()
|
|
|
|
|
} else {
|
|
|
|
|
open.file.sync_all()
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
reply_result(result, reply);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn getlk(
|
|
|
|
|
&self,
|
|
|
|
|
_request: &Request,
|
|
|
|
|
_inode: INodeNo,
|
|
|
|
|
handle: FileHandle,
|
|
|
|
|
owner: LockOwner,
|
|
|
|
|
start: u64,
|
|
|
|
|
end: u64,
|
|
|
|
|
typ: i32,
|
|
|
|
|
_pid: u32,
|
|
|
|
|
flags: LockFlags,
|
|
|
|
|
reply: ReplyLock,
|
|
|
|
|
) {
|
|
|
|
|
if unsupported_lock_flags(flags) {
|
|
|
|
|
reply.error(Errno::EOPNOTSUPP);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if flags.contains(LockFlags::FUSE_LK_FLOCK) {
|
|
|
|
|
reply.locked(0, u64::MAX, libc::F_UNLCK, 0);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let result = self.with_file(handle, |open| {
|
|
|
|
|
let file = self.posix_lock_file(open, owner)?;
|
|
|
|
|
let mut lock = record_lock(start, end, typ)?;
|
|
|
|
|
fcntl(file.as_fd(), FcntlArg::F_OFD_GETLK(&mut lock))?;
|
|
|
|
|
fuse_lock(&lock)
|
|
|
|
|
});
|
|
|
|
|
match result {
|
|
|
|
|
Ok((start, end, typ, pid)) => reply.locked(start, end, typ, pid),
|
|
|
|
|
Err(error) => reply.error(errno(&error)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn setlk(
|
|
|
|
|
&self,
|
|
|
|
|
_request: &Request,
|
|
|
|
|
_inode: INodeNo,
|
|
|
|
|
handle: FileHandle,
|
|
|
|
|
owner: LockOwner,
|
|
|
|
|
start: u64,
|
|
|
|
|
end: u64,
|
|
|
|
|
typ: i32,
|
|
|
|
|
_pid: u32,
|
|
|
|
|
sleep: bool,
|
|
|
|
|
flags: LockFlags,
|
|
|
|
|
reply: ReplyEmpty,
|
|
|
|
|
) {
|
|
|
|
|
if unsupported_lock_flags(flags) {
|
|
|
|
|
reply.error(Errno::EOPNOTSUPP);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let result = self.with_file(handle, |open| {
|
|
|
|
|
if flags.contains(LockFlags::FUSE_LK_FLOCK) {
|
|
|
|
|
if (start, end) != (0, i64::MAX as u64) {
|
|
|
|
|
return Err(io::Error::from_raw_os_error(libc::EINVAL));
|
|
|
|
|
}
|
|
|
|
|
return Ok(bsd_flock(
|
|
|
|
|
open.file.as_fd(),
|
|
|
|
|
bsd_lock_operation(typ, sleep)?,
|
|
|
|
|
)?);
|
|
|
|
|
}
|
|
|
|
|
let file = self.posix_lock_file(open, owner)?;
|
|
|
|
|
let lock = record_lock(start, end, typ)?;
|
|
|
|
|
let command = if sleep {
|
|
|
|
|
FcntlArg::F_OFD_SETLKW(&lock)
|
|
|
|
|
} else {
|
|
|
|
|
FcntlArg::F_OFD_SETLK(&lock)
|
|
|
|
|
};
|
|
|
|
|
fcntl(file.as_fd(), command)?;
|
|
|
|
|
Ok(())
|
|
|
|
|
});
|
|
|
|
|
reply_result(result, reply);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn opendir(&self, _request: &Request, inode: INodeNo, _flags: OpenFlags, reply: ReplyOpen) {
|
2026-07-26 00:43:30 +09:00
|
|
|
let _namespace = self
|
|
|
|
|
.namespace
|
|
|
|
|
.read()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
2026-07-19 03:02:44 +09:00
|
|
|
let result = (|| {
|
|
|
|
|
let path = self.path_for(inode)?;
|
|
|
|
|
let snapshot = self.classifier.snapshot_for(&path);
|
|
|
|
|
let entry = self.resolve(&snapshot, &path)?;
|
|
|
|
|
let directory = self.root(entry.backing).open_directory(&path)?;
|
|
|
|
|
let mut entries = vec![
|
|
|
|
|
DirectoryEntry {
|
|
|
|
|
inode,
|
|
|
|
|
kind: FileType::Directory,
|
|
|
|
|
name: OsString::from("."),
|
|
|
|
|
},
|
|
|
|
|
DirectoryEntry {
|
|
|
|
|
inode: if path.as_os_str().is_empty() {
|
|
|
|
|
INodeNo::ROOT
|
|
|
|
|
} else {
|
|
|
|
|
let parent = path.parent().unwrap_or_else(|| Path::new(""));
|
|
|
|
|
let parent_entry = self.resolve(&snapshot, parent)?;
|
|
|
|
|
self.inode_for(&parent_entry)
|
|
|
|
|
},
|
|
|
|
|
kind: FileType::Directory,
|
|
|
|
|
name: OsString::from(".."),
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
entries.extend(self.visible_entries(&path, &snapshot)?);
|
|
|
|
|
let handle = self.next_handle.fetch_add(1, Ordering::Relaxed);
|
|
|
|
|
self.directories
|
|
|
|
|
.lock()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
|
|
|
.insert(
|
|
|
|
|
handle,
|
|
|
|
|
DirectoryStream {
|
|
|
|
|
directory,
|
|
|
|
|
entries,
|
|
|
|
|
_snapshot: snapshot,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
Ok(handle)
|
|
|
|
|
})();
|
|
|
|
|
match result {
|
|
|
|
|
Ok(handle) => reply.opened(FileHandle(handle), FopenFlags::empty()),
|
|
|
|
|
Err(error) => reply.error(errno(&error)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn readdir(
|
|
|
|
|
&self,
|
|
|
|
|
_request: &Request,
|
|
|
|
|
_inode: INodeNo,
|
|
|
|
|
handle: FileHandle,
|
|
|
|
|
offset: u64,
|
|
|
|
|
mut reply: ReplyDirectory,
|
|
|
|
|
) {
|
|
|
|
|
let directories = self
|
|
|
|
|
.directories
|
|
|
|
|
.lock()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
|
|
|
|
let Some(directory) = directories.get(&handle.0) else {
|
|
|
|
|
reply.error(Errno::EBADF);
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
for (index, entry) in directory
|
|
|
|
|
.entries
|
|
|
|
|
.iter()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.skip(usize::try_from(offset).unwrap_or(usize::MAX))
|
|
|
|
|
{
|
|
|
|
|
if reply.add(entry.inode, (index + 1) as u64, entry.kind, &entry.name) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
reply.ok();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn releasedir(
|
|
|
|
|
&self,
|
|
|
|
|
_request: &Request,
|
|
|
|
|
_inode: INodeNo,
|
|
|
|
|
handle: FileHandle,
|
|
|
|
|
_flags: OpenFlags,
|
|
|
|
|
reply: ReplyEmpty,
|
|
|
|
|
) {
|
|
|
|
|
self.directories
|
|
|
|
|
.lock()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
|
|
|
.remove(&handle.0);
|
|
|
|
|
reply.ok();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn fsyncdir(
|
|
|
|
|
&self,
|
|
|
|
|
_request: &Request,
|
|
|
|
|
_inode: INodeNo,
|
|
|
|
|
handle: FileHandle,
|
|
|
|
|
datasync: bool,
|
|
|
|
|
reply: ReplyEmpty,
|
|
|
|
|
) {
|
|
|
|
|
let directories = self
|
|
|
|
|
.directories
|
|
|
|
|
.lock()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
|
|
|
|
let result = directories
|
|
|
|
|
.get(&handle.0)
|
|
|
|
|
.ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))
|
|
|
|
|
.and_then(|stream| {
|
|
|
|
|
if datasync {
|
|
|
|
|
stream.directory.sync_data()
|
|
|
|
|
} else {
|
|
|
|
|
stream.directory.sync_all()
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
reply_result(result, reply);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn statfs(&self, _request: &Request, _inode: INodeNo, reply: ReplyStatfs) {
|
|
|
|
|
match self
|
|
|
|
|
.host
|
|
|
|
|
.duplicate()
|
|
|
|
|
.and_then(|file| Ok(fstatvfs(file.as_fd())?))
|
|
|
|
|
{
|
|
|
|
|
Ok(stat) => reply.statfs(
|
|
|
|
|
stat.f_blocks,
|
|
|
|
|
stat.f_bfree,
|
|
|
|
|
stat.f_bavail,
|
|
|
|
|
stat.f_files,
|
|
|
|
|
stat.f_ffree,
|
|
|
|
|
u32::try_from(stat.f_bsize).unwrap_or(u32::MAX),
|
|
|
|
|
u32::try_from(stat.f_namemax).unwrap_or(u32::MAX),
|
|
|
|
|
u32::try_from(stat.f_frsize).unwrap_or(u32::MAX),
|
|
|
|
|
),
|
|
|
|
|
Err(error) => reply.error(errno(&error)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn setxattr(
|
|
|
|
|
&self,
|
|
|
|
|
_request: &Request,
|
|
|
|
|
inode: INodeNo,
|
|
|
|
|
name: &OsStr,
|
|
|
|
|
value: &[u8],
|
|
|
|
|
flags: i32,
|
|
|
|
|
position: u32,
|
|
|
|
|
reply: ReplyEmpty,
|
|
|
|
|
) {
|
2026-07-26 00:43:30 +09:00
|
|
|
let _namespace = self
|
|
|
|
|
.namespace
|
|
|
|
|
.read()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
2026-07-19 03:02:44 +09:00
|
|
|
if position != 0 || !writable_xattr(name) {
|
|
|
|
|
reply.error(Errno::ENOTSUP);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let result = self.with_temporary_file(inode, |file| {
|
|
|
|
|
Ok(fsetxattr(
|
|
|
|
|
file.as_fd(),
|
|
|
|
|
name,
|
|
|
|
|
value,
|
|
|
|
|
XattrFlags::from_bits_retain(u32::from_ne_bytes(flags.to_ne_bytes())),
|
|
|
|
|
)?)
|
|
|
|
|
});
|
2026-07-26 00:43:30 +09:00
|
|
|
if result.is_ok()
|
|
|
|
|
&& let Ok(path) = self.path_for(inode)
|
|
|
|
|
{
|
|
|
|
|
let snapshot = self.classifier.snapshot_for(&path);
|
|
|
|
|
if let Ok(entry) = self.resolve(&snapshot, &path) {
|
|
|
|
|
self.record_changed(entry.backing, &path);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-19 03:02:44 +09:00
|
|
|
reply_result(result, reply);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn getxattr(
|
|
|
|
|
&self,
|
|
|
|
|
_request: &Request,
|
|
|
|
|
inode: INodeNo,
|
|
|
|
|
name: &OsStr,
|
|
|
|
|
size: u32,
|
|
|
|
|
reply: ReplyXattr,
|
|
|
|
|
) {
|
|
|
|
|
let result = self.with_temporary_file(inode, |file| {
|
|
|
|
|
let mut bytes = vec![0; 64 * 1024];
|
|
|
|
|
let length = fgetxattr(file.as_fd(), name, &mut bytes)?;
|
|
|
|
|
bytes.truncate(length);
|
|
|
|
|
Ok(bytes)
|
|
|
|
|
});
|
|
|
|
|
reply_xattr(result, size, reply);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn listxattr(&self, _request: &Request, inode: INodeNo, size: u32, reply: ReplyXattr) {
|
|
|
|
|
let result = self.with_temporary_file(inode, |file| {
|
|
|
|
|
let mut bytes = vec![0; 64 * 1024];
|
|
|
|
|
let length = flistxattr(file.as_fd(), &mut bytes)?;
|
|
|
|
|
bytes.truncate(length);
|
|
|
|
|
Ok(bytes)
|
|
|
|
|
});
|
|
|
|
|
reply_xattr(result, size, reply);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn removexattr(&self, _request: &Request, inode: INodeNo, name: &OsStr, reply: ReplyEmpty) {
|
2026-07-26 00:43:30 +09:00
|
|
|
let _namespace = self
|
|
|
|
|
.namespace
|
|
|
|
|
.read()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
2026-07-19 03:02:44 +09:00
|
|
|
if !writable_xattr(name) {
|
|
|
|
|
reply.error(Errno::ENOTSUP);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let result = self.with_temporary_file(inode, |file| Ok(fremovexattr(file.as_fd(), name)?));
|
2026-07-26 00:43:30 +09:00
|
|
|
if result.is_ok()
|
|
|
|
|
&& let Ok(path) = self.path_for(inode)
|
|
|
|
|
{
|
|
|
|
|
let snapshot = self.classifier.snapshot_for(&path);
|
|
|
|
|
if let Ok(entry) = self.resolve(&snapshot, &path) {
|
|
|
|
|
self.record_changed(entry.backing, &path);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-19 03:02:44 +09:00
|
|
|
reply_result(result, reply);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn create(
|
|
|
|
|
&self,
|
|
|
|
|
_request: &Request,
|
|
|
|
|
parent: INodeNo,
|
|
|
|
|
name: &OsStr,
|
|
|
|
|
mode: u32,
|
|
|
|
|
umask: u32,
|
|
|
|
|
flags: i32,
|
|
|
|
|
reply: ReplyCreate,
|
|
|
|
|
) {
|
2026-07-26 00:43:30 +09:00
|
|
|
let _namespace = self
|
|
|
|
|
.namespace
|
|
|
|
|
.read()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
2026-07-19 03:02:44 +09:00
|
|
|
let result = (|| {
|
|
|
|
|
let path = self.child_path(parent, name)?;
|
|
|
|
|
let snapshot = self.classifier.snapshot_for(&path);
|
|
|
|
|
let backing = self.create_backing(&snapshot, &path, false)?;
|
2026-07-26 00:43:30 +09:00
|
|
|
let existed = self.root(backing).stat(&path).is_ok();
|
2026-07-19 03:02:44 +09:00
|
|
|
let file = self.root(backing).open_entry(
|
|
|
|
|
&path,
|
|
|
|
|
open_flags(flags) | OFlags::CREATE,
|
|
|
|
|
Mode::from_raw_mode((mode & !umask) & 0o7777),
|
|
|
|
|
)?;
|
|
|
|
|
let stat = fstat(file.as_fd())?;
|
2026-07-26 00:43:30 +09:00
|
|
|
if existed {
|
|
|
|
|
self.record_changed(backing, &path);
|
|
|
|
|
} else {
|
|
|
|
|
self.record_created(backing, &path)?;
|
|
|
|
|
}
|
2026-07-19 03:02:44 +09:00
|
|
|
let attributes = self.attributes(&ResolvedEntry {
|
2026-07-26 00:43:30 +09:00
|
|
|
path: path.clone(),
|
2026-07-19 03:02:44 +09:00
|
|
|
backing,
|
|
|
|
|
stat,
|
|
|
|
|
})?;
|
|
|
|
|
let handle = self.next_handle.fetch_add(1, Ordering::Relaxed);
|
|
|
|
|
self.files
|
|
|
|
|
.lock()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
|
|
|
.insert(
|
|
|
|
|
handle,
|
|
|
|
|
Arc::new(OpenFile {
|
|
|
|
|
file,
|
|
|
|
|
backing,
|
2026-07-26 00:43:30 +09:00
|
|
|
path,
|
2026-07-19 03:02:44 +09:00
|
|
|
access_mode: flags & libc::O_ACCMODE,
|
2026-07-26 00:43:30 +09:00
|
|
|
migration_quiesced: std::sync::atomic::AtomicBool::new(
|
|
|
|
|
flags & libc::O_ACCMODE == libc::O_RDONLY,
|
|
|
|
|
),
|
2026-07-19 03:02:44 +09:00
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
Ok((attributes, handle))
|
|
|
|
|
})();
|
|
|
|
|
match result {
|
|
|
|
|
Ok((attributes, handle)) => reply.created(
|
|
|
|
|
&TTL,
|
|
|
|
|
&attributes,
|
|
|
|
|
Generation(0),
|
|
|
|
|
FileHandle(handle),
|
|
|
|
|
FopenFlags::empty(),
|
|
|
|
|
),
|
|
|
|
|
Err(error) => reply.error(errno(&error)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn fallocate(
|
|
|
|
|
&self,
|
|
|
|
|
_request: &Request,
|
|
|
|
|
_inode: INodeNo,
|
|
|
|
|
handle: FileHandle,
|
|
|
|
|
offset: u64,
|
|
|
|
|
length: u64,
|
|
|
|
|
mode: i32,
|
|
|
|
|
reply: ReplyEmpty,
|
|
|
|
|
) {
|
2026-07-26 00:43:30 +09:00
|
|
|
let _namespace = self
|
|
|
|
|
.namespace
|
|
|
|
|
.read()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
2026-07-19 03:02:44 +09:00
|
|
|
let result = self.with_file(handle, |open| {
|
2026-07-26 00:43:30 +09:00
|
|
|
open.migration_quiesced.store(false, Ordering::Release);
|
|
|
|
|
fallocate(
|
2026-07-19 03:02:44 +09:00
|
|
|
open.file.as_fd(),
|
|
|
|
|
FallocateFlags::from_bits_retain(u32::from_ne_bytes(mode.to_ne_bytes())),
|
|
|
|
|
offset,
|
|
|
|
|
length,
|
2026-07-26 00:43:30 +09:00
|
|
|
)?;
|
|
|
|
|
self.record_changed(open.backing, &open.path);
|
|
|
|
|
Ok(())
|
2026-07-19 03:02:44 +09:00
|
|
|
});
|
|
|
|
|
reply_result(result, reply);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn lseek(
|
|
|
|
|
&self,
|
|
|
|
|
_request: &Request,
|
|
|
|
|
_inode: INodeNo,
|
|
|
|
|
handle: FileHandle,
|
|
|
|
|
offset: i64,
|
|
|
|
|
whence: i32,
|
|
|
|
|
reply: ReplyLseek,
|
|
|
|
|
) {
|
|
|
|
|
let position = match whence {
|
|
|
|
|
libc::SEEK_SET => u64::try_from(offset).ok().map(SeekFrom::Start),
|
|
|
|
|
libc::SEEK_CUR => Some(SeekFrom::Current(offset)),
|
|
|
|
|
libc::SEEK_END => Some(SeekFrom::End(offset)),
|
|
|
|
|
libc::SEEK_DATA => u64::try_from(offset).ok().map(SeekFrom::Data),
|
|
|
|
|
libc::SEEK_HOLE => u64::try_from(offset).ok().map(SeekFrom::Hole),
|
|
|
|
|
_ => None,
|
|
|
|
|
};
|
|
|
|
|
let result = position
|
|
|
|
|
.ok_or_else(|| io::Error::from_raw_os_error(libc::EINVAL))
|
|
|
|
|
.and_then(|position| {
|
|
|
|
|
self.with_file(handle, |open| Ok(seek(open.file.as_fd(), position)?))
|
|
|
|
|
});
|
|
|
|
|
match result {
|
|
|
|
|
Ok(position) => match i64::try_from(position) {
|
|
|
|
|
Ok(position) => reply.offset(position),
|
|
|
|
|
Err(_) => reply.error(Errno::EOVERFLOW),
|
|
|
|
|
},
|
|
|
|
|
Err(error) => reply.error(errno(&error)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn copy_file_range(
|
|
|
|
|
&self,
|
|
|
|
|
_request: &Request,
|
|
|
|
|
_inode_in: INodeNo,
|
|
|
|
|
handle_in: FileHandle,
|
|
|
|
|
offset_in: u64,
|
|
|
|
|
_inode_out: INodeNo,
|
|
|
|
|
handle_out: FileHandle,
|
|
|
|
|
offset_out: u64,
|
|
|
|
|
length: u64,
|
|
|
|
|
flags: CopyFileRangeFlags,
|
|
|
|
|
reply: ReplyWrite,
|
|
|
|
|
) {
|
|
|
|
|
if !flags.is_empty() {
|
|
|
|
|
reply.error(Errno::EINVAL);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let files = self
|
|
|
|
|
.files
|
|
|
|
|
.lock()
|
|
|
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
|
|
|
|
let result = (|| {
|
|
|
|
|
let input = files
|
|
|
|
|
.get(&handle_in.0)
|
|
|
|
|
.ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
|
|
|
|
|
let output = files
|
|
|
|
|
.get(&handle_out.0)
|
|
|
|
|
.ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
|
2026-07-26 00:43:30 +09:00
|
|
|
output.migration_quiesced.store(false, Ordering::Release);
|
2026-07-19 03:02:44 +09:00
|
|
|
if input.backing != output.backing {
|
|
|
|
|
return Err(io::Error::from_raw_os_error(libc::EXDEV));
|
|
|
|
|
}
|
|
|
|
|
let mut input_offset = offset_in;
|
|
|
|
|
let mut output_offset = offset_out;
|
|
|
|
|
copy_file_range(
|
|
|
|
|
input.file.as_fd(),
|
|
|
|
|
Some(&mut input_offset),
|
|
|
|
|
output.file.as_fd(),
|
|
|
|
|
Some(&mut output_offset),
|
|
|
|
|
usize::try_from(length).unwrap_or(usize::MAX),
|
|
|
|
|
)
|
|
|
|
|
.map_err(io::Error::from)
|
|
|
|
|
})();
|
|
|
|
|
match result {
|
|
|
|
|
Ok(written) => reply.written(u32::try_from(written).unwrap_or(u32::MAX)),
|
|
|
|
|
Err(error) => reply.error(errno(&error)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|