use std::{
cell::RefCell,
ffi::OsStr,
io::{self, Read, Seek, Write},
os::unix::ffi::OsStrExt,
path::{Path, PathBuf},
time::{Duration, SystemTime},
};
use fuser::{
AccessFlags, BsdFileFlags, CopyFileRangeFlags, Errno, FileHandle, Filesystem, FopenFlags,
INodeNo, IoctlFlags, KernelConfig, LockOwner, OpenAccMode, OpenFlags, PollEvents, PollFlags,
PollNotifier, RenameFlags, ReplyAttr, ReplyBmap, ReplyCreate, ReplyData, ReplyDirectory,
ReplyDirectoryPlus, ReplyEmpty, ReplyEntry, ReplyIoctl, ReplyLock, ReplyLseek, ReplyOpen,
ReplyPoll, ReplyStatfs, ReplyWrite, ReplyXattr, Request, TimeOrNow, WriteFlags,
};
use nix::{
fcntl::OFlag,
libc,
sys::stat::{self, SFlag},
};
use crate::{
Connection, Device, FileDiscriminant, FileKind, FileMode, Owner, RootId,
fuse::handle::{DirectoryEntry, DirectoryHandleState},
};
use super::{
handle::{FileHandleState, HandleState},
state::FuseStateGuard,
};
const DEFAULT_TTL: Duration = Duration::MAX;
const MAX_FILENAME_LEN: u32 = 255;
macro_rules! try_result {
($result:expr, $reply:expr) => {
match $result {
Ok(result) => result,
Err(error) => {
$reply.error(crate::Error::from(error).to_errno());
return Err(());
}
}
};
}
macro_rules! try_option {
($result:expr, $reply:expr, $error:expr) => {
match $result {
Some(result) => result,
None => {
$reply.error($error);
return Err(());
}
}
};
}
macro_rules! check_name_len {
($name:expr, $reply:expr) => {
if $name.as_encoded_bytes().len() > MAX_FILENAME_LEN as usize {
$reply.error(Errno::ENAMETOOLONG);
return;
}
};
}
#[derive(Debug)]
pub struct FuseAdapter {
state: FuseStateGuard,
litebox_path: Option<PathBuf>,
}
impl FuseAdapter {
pub fn new(
conn: Connection,
root: RootId,
root_path: &Path,
db_path: Option<std::path::PathBuf>,
) -> crate::Result<Self> {
Ok(Self {
state: FuseStateGuard::new(conn, root, root_path)?,
litebox_path: db_path,
})
}
}
impl Filesystem for FuseAdapter {
fn init(&mut self, _req: &Request, _config: &mut KernelConfig) -> std::io::Result<()> {
Ok(())
}
fn destroy(&mut self) {
self.state.commit().ok();
}
fn lookup(&self, _req: &Request, parent: INodeNo, name: &OsStr, reply: ReplyEntry) {
check_name_len!(name, reply);
self.state.exec(
|fs, state| {
let file_name = try_option!(name.to_str(), reply, Errno::ENOENT);
let file_path =
try_option!(state.inodes.path(parent), reply, Errno::ENOENT).join(file_name);
let mut file = try_result!(fs.open(file_path), reply);
let file_id = file.file_id();
let file_inode = try_option!(state.inodes.inode(file_id), reply, Errno::ENOENT);
let attrs = try_result!(file.attrs(), reply).with_inode(file_inode);
let generation = state.inodes.generation(file_inode);
reply.entry(&DEFAULT_TTL, &attrs, generation);
Ok(())
},
|_, _| {},
);
}
fn forget(&self, _req: &Request, _ino: INodeNo, _nlookup: u64) {}
fn getattr(&self, _req: &Request, ino: INodeNo, _fh: Option<FileHandle>, reply: ReplyAttr) {
self.state.exec(
|fs, state| {
let file_path = try_option!(state.inodes.path(ino), reply, Errno::ENOENT);
let mut file = try_result!(fs.open(file_path), reply);
let attrs = try_result!(file.attrs(), reply).with_inode(ino);
reply.attr(&DEFAULT_TTL, &attrs);
Ok(())
},
|_, _| {},
);
}
fn setattr(
&self,
_req: &Request,
ino: INodeNo,
mode: Option<u32>,
uid: Option<u32>,
gid: Option<u32>,
size: Option<u64>,
atime: Option<TimeOrNow>,
mtime: Option<TimeOrNow>,
ctime: Option<std::time::SystemTime>,
_fh: Option<FileHandle>,
_crtime: Option<std::time::SystemTime>,
_chgtime: Option<std::time::SystemTime>,
_bkuptime: Option<std::time::SystemTime>,
_flags: Option<BsdFileFlags>,
reply: ReplyAttr,
) {
let now = SystemTime::now();
self.state.exec(
|fs, state| {
let file_path =
try_option!(state.inodes.path(ino), reply, Errno::ENOENT).to_owned();
let mut file = try_result!(fs.open(file_path), reply);
if let Some(mode) = mode {
try_result!(file.set_mode(FileMode::from_bits_truncate(mode)), reply);
}
if let Some(uid) = uid {
try_result!(file.set_user(uid.into()), reply);
}
if let Some(gid) = gid {
try_result!(file.set_group(gid.into()), reply);
}
if let Some(size) = size {
try_result!(file.set_len(size), reply);
try_result!(file.touch_at(now), reply);
}
if let Some(atime) = atime {
let atime = match atime {
TimeOrNow::SpecificTime(atime) => atime,
TimeOrNow::Now => now,
};
try_result!(file.set_accessed(atime), reply);
}
if let Some(mtime) = mtime {
let mtime = match mtime {
TimeOrNow::SpecificTime(mtime) => mtime,
TimeOrNow::Now => now,
};
try_result!(file.set_modified(mtime), reply);
}
try_result!(file.set_changed(ctime.unwrap_or(now)), reply);
let attrs = try_result!(file.attrs(), reply).with_inode(ino);
reply.attr(&DEFAULT_TTL, &attrs);
Ok(())
},
|_, _| {},
);
}
fn readlink(&self, _req: &Request, ino: INodeNo, reply: ReplyData) {
self.state.exec(
|fs, state| {
let file_path = try_option!(state.inodes.path(ino), reply, Errno::ENOENT);
let file = try_result!(fs.open(file_path), reply);
let kind = file.kind();
match kind {
FileKind::Symlink { target } => reply.data(target.as_os_str().as_bytes()),
_ => reply.error(Errno::EINVAL),
}
Ok(())
},
|_, _| {},
);
}
fn mknod(
&self,
req: &Request,
parent: INodeNo,
name: &OsStr,
mode: u32,
_umask: u32,
rdev: u32,
reply: ReplyEntry,
) {
check_name_len!(name, reply);
let now = SystemTime::now();
self.state.exec(
|fs, state| {
let flags = SFlag::from_bits_truncate(mode);
let file_name = try_option!(name.to_str(), reply, Errno::EINVAL);
let parent_path = try_option!(state.inodes.path(parent), reply, Errno::ENOENT);
let file_path = parent_path.join(file_name);
let file_kind = if flags.contains(SFlag::S_IFREG) {
FileKind::Regular
} else if flags.contains(SFlag::S_IFCHR) {
let major = stat::major(rdev as u64);
let minor = stat::minor(rdev as u64);
FileKind::Char {
dev: Device::new(major, minor),
}
} else if flags.contains(SFlag::S_IFBLK) {
let major = stat::major(rdev as u64);
let minor = stat::minor(rdev as u64);
FileKind::Block {
dev: Device::new(major, minor),
}
} else if flags.contains(SFlag::S_IFIFO) {
FileKind::Pipe
} else if flags.contains(SFlag::S_IFSOCK) {
reply.error(Errno::EPERM);
return Err(());
} else {
reply.error(Errno::EINVAL);
return Err(());
};
let parent_default_acl = {
let mut parent_file = try_result!(fs.open(parent_path), reply);
try_result!(parent_file.touch_at(now), reply);
try_result!(parent_file.default_acl(), reply)
};
let owner = Owner {
user: req.uid().into(),
group: req.gid().into(),
};
let mut file = try_result!(fs.create(file_path.clone(), file_kind, owner), reply);
try_result!(
file.constrain_permissions(
&parent_default_acl,
Some(FileMode::from_bits_truncate(mode)),
),
reply
);
let attrs_without_inode = try_result!(file.attrs(), reply);
Ok((reply, file.file_id(), file_path, attrs_without_inode))
},
|(reply, file_id, file_path, attrs_without_inode), state| {
let file_inode = state.inodes.insert(file_path, file_id);
let attrs = attrs_without_inode.with_inode(file_inode);
let generation = state.inodes.generation(file_inode);
reply.entry(&DEFAULT_TTL, &attrs, generation);
},
);
}
fn mkdir(
&self,
req: &Request,
parent: INodeNo,
name: &OsStr,
mode: u32,
_umask: u32,
reply: ReplyEntry,
) {
check_name_len!(name, reply);
let now = SystemTime::now();
self.state.exec(
|fs, state| {
let file_name = try_option!(name.to_str(), reply, Errno::EINVAL);
let parent_path = try_option!(state.inodes.path(parent), reply, Errno::ENOENT);
let file_path = parent_path.join(file_name);
let parent_default_acl = {
let mut parent_file = try_result!(fs.open(parent_path), reply);
try_result!(parent_file.touch_at(now), reply);
try_result!(parent_file.default_acl(), reply)
};
let owner = Owner {
user: req.uid().into(),
group: req.gid().into(),
};
let mut file =
try_result!(fs.create(file_path.clone(), FileKind::Dir, owner), reply);
try_result!(
file.constrain_permissions(
&parent_default_acl,
Some(FileMode::from_bits_truncate(mode)),
),
reply
);
let attrs_without_inode = try_result!(file.attrs(), reply);
Ok((reply, file.file_id(), file_path, attrs_without_inode))
},
|(reply, file_id, file_path, attrs_without_inode), state| {
let file_inode = state.inodes.insert(file_path, file_id);
let attrs = attrs_without_inode.with_inode(file_inode);
let generation = state.inodes.generation(file_inode);
reply.entry(&DEFAULT_TTL, &attrs, generation);
},
);
}
fn unlink(&self, _req: &Request, parent: INodeNo, name: &OsStr, reply: ReplyEmpty) {
check_name_len!(name, reply);
let now = SystemTime::now();
self.state.exec(
|fs, state| {
let file_name = try_option!(name.to_str(), reply, Errno::ENOENT);
let parent_path = try_option!(state.inodes.path(parent), reply, Errno::ENOENT);
let file_path = parent_path.join(file_name);
let (file_id, file_discriminant) = {
let mut file = try_result!(fs.open(file_path.clone()), reply);
let discriminant = file.kind().discriminant();
if discriminant == FileDiscriminant::Dir {
reply.error(Errno::EISDIR);
return Err(());
}
try_result!(file.set_changed(now), reply);
(file.file_id(), discriminant)
};
{
let mut parent_file = try_result!(fs.open(parent_path), reply);
try_result!(parent_file.touch_at(now), reply);
}
if file_discriminant == FileDiscriminant::Regular {
try_result!(fs.unlink(&file_path), reply);
} else {
try_result!(fs.delete(&file_path), reply);
}
Ok((reply, file_id, file_path))
},
|(reply, file_id, file_path), state| {
state.inodes.remove(file_id, &file_path);
reply.ok();
},
);
}
fn rmdir(&self, _req: &Request, parent: INodeNo, name: &OsStr, reply: ReplyEmpty) {
check_name_len!(name, reply);
let now = SystemTime::now();
self.state.exec(
|fs, state| {
let file_name = try_option!(name.to_str(), reply, Errno::ENOENT);
let parent_path = try_option!(state.inodes.path(parent), reply, Errno::ENOENT);
let file_path = parent_path.join(file_name);
let (kind, file_id) = {
let file = try_result!(fs.open(file_path.clone()), reply);
(file.kind().to_owned(), file.file_id())
};
if kind != FileKind::Dir {
reply.error(Errno::ENOTDIR);
return Err(());
}
try_result!(fs.delete(&file_path), reply);
let mut parent_file = try_result!(fs.open(parent_path), reply);
try_result!(parent_file.touch_at(now), reply);
Ok((reply, file_id, file_path))
},
|(reply, file_id, file_path), state| {
state.inodes.remove(file_id, &file_path);
reply.ok();
},
);
}
fn symlink(
&self,
req: &Request,
parent: INodeNo,
link_name: &OsStr,
target: &Path,
reply: ReplyEntry,
) {
check_name_len!(link_name, reply);
let now = SystemTime::now();
self.state.exec(
|fs, state| {
let link_name = try_option!(link_name.to_str(), reply, Errno::EINVAL);
let link_parent_path = try_option!(state.inodes.path(parent), reply, Errno::ENOENT);
let link_path = link_parent_path.join(link_name);
let link_parent_default_acl = {
let mut link_parent_file = try_result!(fs.open(link_parent_path), reply);
try_result!(link_parent_file.touch_at(now), reply);
try_result!(link_parent_file.default_acl(), reply)
};
let link_owner = Owner {
user: req.uid().into(),
group: req.gid().into(),
};
let mut link_file = try_result!(
fs.create(
&link_path,
FileKind::Symlink {
target: target.to_owned()
},
link_owner,
),
reply
);
try_result!(
link_file.constrain_permissions(&link_parent_default_acl, None),
reply
);
let attrs_without_inode = try_result!(link_file.attrs(), reply);
Ok((reply, link_file.file_id(), link_path, attrs_without_inode))
},
|(reply, file_id, file_path, attrs_without_inode), state| {
let file_inode = state.inodes.insert(file_path, file_id);
let attrs = attrs_without_inode.with_inode(file_inode);
let generation = state.inodes.generation(file_inode);
reply.entry(&DEFAULT_TTL, &attrs, generation);
},
);
}
fn rename(
&self,
_req: &Request,
parent: INodeNo,
name: &OsStr,
newparent: INodeNo,
newname: &OsStr,
flags: RenameFlags,
reply: ReplyEmpty,
) {
check_name_len!(name, reply);
check_name_len!(newname, reply);
if flags.intersects(RenameFlags::RENAME_EXCHANGE | RenameFlags::RENAME_WHITEOUT) {
reply.error(Errno::EINVAL);
return;
}
let no_replace = flags.contains(RenameFlags::RENAME_NOREPLACE);
if parent == newparent && name == newname {
reply.ok();
return;
}
let now = SystemTime::now();
self.state.exec(
|fs, state| {
let src_name = try_option!(name.to_str(), reply, Errno::ENOENT);
let src_parent_path = try_option!(state.inodes.path(parent), reply, Errno::ENOENT);
let src_path = src_parent_path.join(src_name);
let dest_name = try_option!(newname.to_str(), reply, Errno::ENOENT);
let dest_parent_path =
try_option!(state.inodes.path(newparent), reply, Errno::ENOENT);
let dest_path = dest_parent_path.join(dest_name);
let (src_file_id, src_is_dir) = {
let src_file = try_result!(fs.open(&src_path), reply);
(src_file.file_id(), src_file.kind() == &FileKind::Dir)
};
let maybe_dest = match fs.open(&dest_path) {
Ok(dest_file) => Some((dest_file.file_id(), dest_file.kind().discriminant())),
Err(crate::Error::FileNotFound { .. }) => None,
Err(error) => {
reply.error(error.to_errno());
return Err(());
}
};
if let Some((dest_file_id, dest_kind)) = maybe_dest {
let dest_is_dir = dest_kind == FileDiscriminant::Dir;
if dest_file_id == src_file_id {
reply.ok();
return Err(());
}
if no_replace {
reply.error(Errno::EEXIST);
return Err(());
}
if src_is_dir && !dest_is_dir {
reply.error(Errno::ENOTDIR);
return Err(());
}
if !src_is_dir && dest_is_dir {
reply.error(Errno::EISDIR);
return Err(());
}
if dest_kind == FileDiscriminant::Regular {
try_result!(fs.unlink(&dest_path), reply);
} else {
try_result!(fs.delete(&dest_path), reply);
}
}
try_result!(fs.rename(&src_path, &dest_path), reply);
{
let mut renamed_file = try_result!(fs.open(&dest_path), reply);
try_result!(renamed_file.set_changed(now), reply);
}
Ok((
reply,
src_path,
dest_path,
maybe_dest.map(|(dest_file_id, _)| dest_file_id),
))
},
|(reply, src_path, dest_path, maybe_dest_file_id), state| {
if let Some(dest_file_id) = maybe_dest_file_id {
state.inodes.remove(dest_file_id, &dest_path);
}
state.inodes.remap_prefix(&src_path, &dest_path);
reply.ok();
},
);
}
fn link(
&self,
_req: &Request,
ino: INodeNo,
newparent: INodeNo,
newname: &OsStr,
reply: ReplyEntry,
) {
check_name_len!(newname, reply);
let now = SystemTime::now();
self.state.exec(
|fs, state| {
let target_path =
try_option!(state.inodes.path(ino), reply, Errno::ENOENT).to_owned();
let link_name = try_option!(newname.to_str(), reply, Errno::EINVAL);
let link_parent_path =
try_option!(state.inodes.path(newparent), reply, Errno::ENOENT).to_owned();
let link_path = link_parent_path.join(link_name);
{
let mut parent_file = try_result!(fs.open(&link_parent_path), reply);
try_result!(parent_file.touch_at(now), reply);
}
let mut file = try_result!(fs.open(&target_path), reply);
match file.link(&link_path) {
Ok(()) => {}
Err(crate::Error::NotARegularFile { .. }) => {
reply.error(Errno::EPERM);
return Err(());
}
Err(error) => {
reply.error(error.to_errno());
return Err(());
}
}
file.set_changed(now).ok();
let file_id = file.file_id();
let attrs_without_inode = try_result!(file.attrs(), reply);
Ok((reply, file_id, link_path, attrs_without_inode))
},
|(reply, file_id, link_path, attrs_without_inode), state| {
let file_inode = state.inodes.insert(link_path, file_id);
let attrs = attrs_without_inode.with_inode(file_inode);
let generation = state.inodes.generation(file_inode);
reply.entry(&DEFAULT_TTL, &attrs, generation);
},
);
}
fn open(&self, _req: &Request, ino: INodeNo, flags: OpenFlags, reply: ReplyOpen) {
let oflags = OFlag::from_bits_truncate(flags.0);
self.state.exec(
|fs, state| {
let path = try_option!(state.inodes.path(ino), reply, Errno::ENOENT).to_owned();
let file = try_result!(fs.open(&path), reply);
let file_id = file.file_id();
let fd = if matches!(file.kind(), FileKind::Regular) {
Some(try_result!(file.leak_fd(), reply))
} else {
None
};
Ok((reply, file_id, fd))
},
|(reply, file_id, fd), state| {
let fh = state.handles.open(HandleState::File(FileHandleState {
flags: oflags,
position: 0,
file_id,
fd: RefCell::new(fd),
}));
reply.opened(fh, FopenFlags::FOPEN_DIRECT_IO);
},
);
}
fn read(
&self,
_req: &Request,
_ino: INodeNo,
fh: FileHandle,
offset: u64,
size: u32,
_flags: OpenFlags,
_lock_owner: Option<LockOwner>,
reply: ReplyData,
) {
self.state.exec(
|fs, state| {
let (flags, file_id) = match state.handles.state(fh) {
Some(HandleState::File(s)) => (s.flags, s.file_id),
_ => {
reply.error(Errno::EBADF);
return Err(());
}
};
if OpenFlags(flags.bits()).acc_mode() == OpenAccMode::O_WRONLY {
reply.error(Errno::EBADF);
return Err(());
}
let mut file = try_result!(fs.open(file_id), reply);
try_result!(file.seek(io::SeekFrom::Start(offset)), reply);
let mut buf = vec![0u8; size as usize];
let mut total = 0;
while total < buf.len() {
let n = try_result!(
file.read(buf.get_mut(total..).expect("Out of bounds reading block.")),
reply
);
if n == 0 {
break;
}
total += n;
}
buf.truncate(total);
reply.data(&buf);
Ok(())
},
|_, _| {},
);
}
fn write(
&self,
_req: &Request,
_ino: INodeNo,
fh: FileHandle,
offset: u64,
data: &[u8],
_write_flags: WriteFlags,
_flags: OpenFlags,
_lock_owner: Option<LockOwner>,
reply: ReplyWrite,
) {
let now = SystemTime::now();
self.state.exec(
|fs, state| {
let (flags, file_id) = match state.handles.state(fh) {
Some(HandleState::File(s)) => (s.flags, s.file_id),
_ => {
reply.error(Errno::EBADF);
return Err(());
}
};
if OpenFlags(flags.bits()).acc_mode() == OpenAccMode::O_RDONLY {
reply.error(Errno::EBADF);
return Err(());
}
let mut file = try_result!(fs.open(file_id), reply);
let write_offset = if flags.contains(OFlag::O_APPEND) {
try_result!(file.len(), reply)
} else {
offset
};
try_result!(file.seek(io::SeekFrom::Start(write_offset)), reply);
let n = try_result!(file.write(data), reply);
try_result!(file.flush(), reply);
try_result!(file.touch_at(now), reply);
reply.written(n as u32);
Ok(())
},
|_, _| {},
);
}
fn flush(
&self,
_req: &Request,
_ino: INodeNo,
_fh: FileHandle,
_lock_owner: LockOwner,
reply: ReplyEmpty,
) {
reply.ok();
}
fn release(
&self,
_req: &Request,
_ino: INodeNo,
fh: FileHandle,
_flags: OpenFlags,
_lock_owner: Option<LockOwner>,
_flush: bool,
reply: ReplyEmpty,
) {
self.state.exec(
|fs, state| {
let fd = match state.handles.state(fh) {
Some(HandleState::File(s)) => s.fd.borrow_mut().take(),
_ => None,
};
if let Some(fd) = fd {
try_result!(fs.release(fd), reply);
}
Ok(reply)
},
|reply, state| {
state.handles.close(fh);
reply.ok();
},
);
}
fn fsync(
&self,
_req: &Request,
_ino: INodeNo,
_fh: FileHandle,
_datasync: bool,
reply: ReplyEmpty,
) {
match self.state.commit() {
Ok(()) => reply.ok(),
Err(error) => reply.error(error.to_errno()),
}
}
fn opendir(&self, _req: &Request, ino: INodeNo, _flags: OpenFlags, reply: ReplyOpen) {
self.state.exec(
|fs, state| {
let path = try_option!(state.inodes.path(ino), reply, Errno::ENOENT);
let entries = try_result!(fs.children(path), reply);
let mut fuse_entries = Vec::with_capacity(entries.len());
for entry in entries {
let file_name = entry
.path()
.file_name()
.expect("Directory entry path should have a file name.");
let inode = state
.inodes
.inode(entry.file_id())
.expect("Directory entry should already have an inode.");
let file_type = entry.kind().discriminant().to_fuse_file_type();
fuse_entries.push(DirectoryEntry {
file_name: file_name.to_owned(),
file_type,
inode,
});
}
Ok((reply, fuse_entries))
},
|(reply, entries), state| {
let fh = state
.handles
.open(HandleState::Directory(DirectoryHandleState { entries }));
reply.opened(fh, FopenFlags::empty());
},
);
}
fn readdir(
&self,
_req: &Request,
ino: INodeNo,
fh: FileHandle,
offset: u64,
mut reply: ReplyDirectory,
) {
let now = SystemTime::now();
self.state.exec(
|fs, state| {
let file_path = try_option!(state.inodes.path(ino), reply, Errno::ENOENT);
{
let mut file = try_result!(fs.open(file_path), reply);
try_result!(file.set_accessed(now), reply);
try_result!(file.set_changed(now), reply);
}
let entries = match state.handles.state(fh) {
None => {
reply.error(Errno::EBADF);
return Err(());
}
Some(HandleState::File(_)) => {
reply.error(Errno::ENOTDIR);
return Err(());
}
Some(HandleState::Directory(DirectoryHandleState { entries })) => entries,
};
for (i, dir_entry) in entries
.get(offset as usize..)
.unwrap_or(&[])
.iter()
.enumerate()
{
if reply.add(
dir_entry.inode,
offset + i as u64 + 1,
dir_entry.file_type,
&dir_entry.file_name,
) {
break;
}
}
reply.ok();
Ok(())
},
|(), _| {},
);
}
fn readdirplus(
&self,
_req: &Request,
_ino: INodeNo,
_fh: FileHandle,
_offset: u64,
reply: ReplyDirectoryPlus,
) {
reply.error(Errno::ENOSYS);
}
fn releasedir(
&self,
_req: &Request,
_ino: INodeNo,
fh: FileHandle,
_flags: OpenFlags,
reply: ReplyEmpty,
) {
self.state.exec(
|_fs, _state| Ok(()),
|_, state| {
state.handles.close(fh);
reply.ok()
},
);
}
fn fsyncdir(
&self,
_req: &Request,
_ino: INodeNo,
_fh: FileHandle,
_datasync: bool,
reply: ReplyEmpty,
) {
match self.state.commit() {
Ok(()) => reply.ok(),
Err(error) => reply.error(error.to_errno()),
}
}
fn statfs(&self, _req: &Request, _ino: INodeNo, reply: ReplyStatfs) {
self.state.exec(
|fs, _| {
let host_stats = self
.litebox_path
.as_deref()
.and_then(|p| nix::sys::statvfs::statvfs(p).ok());
let litebox_stats = match fs.stats() {
Ok(stats) => stats,
Err(error) => {
reply.error(error.to_errno());
return Err(());
}
};
match host_stats {
Some(s) => reply.statfs(
s.blocks(),
s.blocks_free(),
s.blocks_available(),
litebox_stats.file_count(),
i64::MAX as u64,
s.block_size() as u32,
MAX_FILENAME_LEN,
s.fragment_size() as u32,
),
None => reply.error(Errno::EIO),
}
Ok(())
},
|_, _| {},
);
}
fn setxattr(
&self,
_req: &Request,
ino: INodeNo,
name: &OsStr,
value: &[u8],
flags: i32,
_position: u32,
reply: ReplyEmpty,
) {
const XATTR_CREATE: i32 = 0x1;
const XATTR_REPLACE: i32 = 0x2;
let now = SystemTime::now();
self.state.exec(
|fs, state| {
let file_path =
try_option!(state.inodes.path(ino), reply, Errno::ENOENT).to_owned();
let mut file = try_result!(fs.open(&file_path), reply);
let mut xattrs = try_result!(file.xattrs(), reply);
if flags & XATTR_CREATE != 0 && xattrs.get(name).is_some() {
reply.error(Errno::EEXIST);
return Err(());
}
if flags & XATTR_REPLACE != 0 && xattrs.get(name).is_none() {
reply.error(Errno::ENODATA);
return Err(());
}
xattrs.set(name.to_owned(), value.to_vec());
try_result!(file.set_xattrs(&xattrs), reply);
try_result!(file.set_changed(now), reply);
reply.ok();
Ok(())
},
|_, _| {},
);
}
fn getxattr(&self, _req: &Request, ino: INodeNo, name: &OsStr, size: u32, reply: ReplyXattr) {
self.state.exec(
|fs, state| {
let file_path = try_option!(state.inodes.path(ino), reply, Errno::ENOENT);
let mut file = try_result!(fs.open(file_path), reply);
let xattrs = try_result!(file.xattrs(), reply);
let Some(attr_value) = xattrs.get(name) else {
reply.error(Errno::ENODATA);
return Err(());
};
if size == 0 {
reply.size(attr_value.len() as u32);
} else if attr_value.len() <= size as usize {
reply.data(attr_value);
} else {
reply.error(Errno::ERANGE);
return Err(());
}
Ok(())
},
|_, _| {},
);
}
fn listxattr(&self, _req: &Request, ino: INodeNo, size: u32, reply: ReplyXattr) {
self.state.exec(
|fs, state| {
let file_path = try_option!(state.inodes.path(ino), reply, Errno::ENOENT);
let mut file = try_result!(fs.open(file_path), reply);
let xattrs = try_result!(file.xattrs(), reply);
let mut list: Vec<u8> = Vec::new();
for (attr_name, _) in &xattrs {
list.extend_from_slice(attr_name.as_encoded_bytes());
list.push(0);
}
if size == 0 {
reply.size(list.len() as u32);
} else if list.len() <= size as usize {
reply.data(&list);
} else {
reply.error(Errno::ERANGE);
return Err(());
}
Ok(())
},
|_, _| {},
);
}
fn removexattr(&self, _req: &Request, ino: INodeNo, name: &OsStr, reply: ReplyEmpty) {
let now = SystemTime::now();
self.state.exec(
|fs, state| {
let file_path =
try_option!(state.inodes.path(ino), reply, Errno::ENOENT).to_owned();
let mut file = try_result!(fs.open(&file_path), reply);
let mut xattrs = try_result!(file.xattrs(), reply);
if xattrs.get(name).is_none() {
reply.error(Errno::ENODATA);
return Err(());
}
xattrs.remove(name);
try_result!(file.set_xattrs(&xattrs), reply);
try_result!(file.set_changed(now), reply);
reply.ok();
Ok(())
},
|_, _| {},
);
}
fn access(&self, _req: &Request, _ino: INodeNo, _mask: AccessFlags, reply: ReplyEmpty) {
reply.error(Errno::ENOSYS);
}
fn create(
&self,
req: &Request,
parent: INodeNo,
name: &OsStr,
mode: u32,
_umask: u32,
flags: i32,
reply: ReplyCreate,
) {
check_name_len!(name, reply);
let now = SystemTime::now();
let oflags = OFlag::from_bits_truncate(flags);
self.state.exec(
|fs, state| {
let file_name = try_option!(name.to_str(), reply, Errno::EINVAL);
let parent_path = try_option!(state.inodes.path(parent), reply, Errno::ENOENT);
let file_path = parent_path.join(file_name);
let parent_default_acl = {
let mut parent_file = try_result!(fs.open(parent_path), reply);
try_result!(parent_file.touch_at(now), reply);
try_result!(parent_file.default_acl(), reply)
};
let owner = Owner {
user: req.uid().into(),
group: req.gid().into(),
};
let mut file = try_result!(
fs.create(file_path.clone(), FileKind::Regular, owner),
reply
);
try_result!(
file.constrain_permissions(
&parent_default_acl,
Some(FileMode::from_bits_truncate(mode)),
),
reply
);
let file_id = file.file_id();
let attrs_without_inode = try_result!(file.attrs(), reply);
let fd = try_result!(file.leak_fd(), reply);
Ok((reply, file_id, file_path, attrs_without_inode, fd))
},
|(reply, file_id, file_path, attrs_without_inode, fd), state| {
let file_inode = state.inodes.insert(file_path, file_id);
let attrs = attrs_without_inode.with_inode(file_inode);
let generation = state.inodes.generation(file_inode);
let fh = state.handles.open(HandleState::File(FileHandleState {
flags: oflags,
position: 0,
file_id,
fd: RefCell::new(Some(fd)),
}));
reply.created(
&DEFAULT_TTL,
&attrs,
generation,
fh,
FopenFlags::FOPEN_DIRECT_IO,
);
},
);
}
fn getlk(
&self,
_req: &Request,
_ino: INodeNo,
_fh: FileHandle,
_lock_owner: LockOwner,
_start: u64,
_end: u64,
_typ: i32,
_pid: u32,
reply: ReplyLock,
) {
reply.error(Errno::ENOSYS);
}
fn setlk(
&self,
_req: &Request,
_ino: INodeNo,
_fh: FileHandle,
_lock_owner: LockOwner,
_start: u64,
_end: u64,
_typ: i32,
_pid: u32,
_sleep: bool,
reply: ReplyEmpty,
) {
reply.error(Errno::ENOSYS);
}
fn bmap(&self, _req: &Request, _ino: INodeNo, _blocksize: u32, _idx: u64, reply: ReplyBmap) {
reply.error(Errno::ENOSYS);
}
fn ioctl(
&self,
_req: &Request,
_ino: INodeNo,
_fh: FileHandle,
_flags: IoctlFlags,
_cmd: u32,
_in_data: &[u8],
_out_size: u32,
reply: ReplyIoctl,
) {
reply.error(Errno::ENOSYS);
}
fn poll(
&self,
_req: &Request,
_ino: INodeNo,
_fh: FileHandle,
_ph: PollNotifier,
_events: PollEvents,
_flags: PollFlags,
reply: ReplyPoll,
) {
reply.error(Errno::ENOSYS);
}
fn fallocate(
&self,
_req: &Request,
ino: INodeNo,
_fh: FileHandle,
offset: u64,
length: u64,
mode: i32,
reply: ReplyEmpty,
) {
if mode & !libc::FALLOC_FL_KEEP_SIZE != 0 {
reply.error(Errno::EOPNOTSUPP);
return;
}
if mode & libc::FALLOC_FL_KEEP_SIZE != 0 {
reply.ok();
return;
}
let Some(new_len) = offset.checked_add(length) else {
reply.error(Errno::EINVAL);
return;
};
let now = SystemTime::now();
self.state.exec(
|fs, state| {
let file_path =
try_option!(state.inodes.path(ino), reply, Errno::ENOENT).to_owned();
let mut file = try_result!(fs.open(file_path), reply);
let current_len = try_result!(file.len(), reply);
if new_len > current_len {
try_result!(file.set_len(new_len), reply);
try_result!(file.touch_at(now), reply);
}
reply.ok();
Ok(())
},
|_, _| {},
);
}
fn lseek(
&self,
_req: &Request,
_ino: INodeNo,
_fh: FileHandle,
_offset: i64,
_whence: i32,
reply: ReplyLseek,
) {
reply.error(Errno::ENOSYS);
}
fn copy_file_range(
&self,
_req: &Request,
_ino_in: INodeNo,
_fh_in: FileHandle,
_offset_in: u64,
_ino_out: INodeNo,
_fh_out: FileHandle,
_offset_out: u64,
_len: u64,
_flags: CopyFileRangeFlags,
reply: ReplyWrite,
) {
reply.error(Errno::ENOSYS);
}
}